fix(LLM): 「该档无可用模型」补专用变体并统一为 503
- AgcModelResolveError 新增 NoModelForTier,与「某模型不可用」/「档位不够」区分;code() 仍复用 MODEL_UNAVAILABLE - 代理路径 resolve_requested_for(None, access) 不再折叠成 Unavailable,agc_model_resolve_error 把 NoModelForTier 映射到 503,与 /api/llm/models 列表端点一致(评审第 8 条) - public_model_catalog 直接复用 agc_model_resolve_error(NoModelForTier),消除手写 503 + 字面量错误码 - 更新 model_access.rs 契约注释与 module-runtime / api-server 测试
This commit is contained in:
@@ -127,6 +127,14 @@ mod model_catalog_tests {
|
||||
assert_eq!(unavailable.status_code(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tier_without_model_maps_to_service_unavailable_with_model_unavailable_code() {
|
||||
// 与 `/api/llm/models` 列表端点同一条件、同一状态码:代理路径不能再落到 422。
|
||||
let error = agc_model_resolve_error(module_runtime::AgcModelResolveError::NoModelForTier);
|
||||
assert_eq!(error.status_code(), StatusCode::SERVICE_UNAVAILABLE);
|
||||
assert_eq!(error.code(), "MODEL_UNAVAILABLE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn access_lookup_failure_maps_to_service_unavailable_with_dedicated_code() {
|
||||
// 读链路失败必须是独立的 503 + 专用错误码,不能与 403 档位不足 / 422 模型不可用混用,
|
||||
@@ -277,13 +285,10 @@ fn public_model_catalog(
|
||||
) -> Result<LlmModelsResponse, AppError> {
|
||||
let OwnerAgcModelCatalog { catalog, access } = owner_catalog;
|
||||
// 该档一个可用模型都没有属于后台目录配置问题:明确报错,而不是下发一个不可解析的默认项。
|
||||
// 与代理路径共用 `agc_model_resolve_error`,保证同一条件在两个端点都是 503 + MODEL_UNAVAILABLE。
|
||||
let default_model_id = catalog
|
||||
.default_model_id_for(access)
|
||||
.ok_or_else(|| {
|
||||
AppError::from_status(StatusCode::SERVICE_UNAVAILABLE)
|
||||
.with_code(AgcModelResolveError::Unavailable.code())
|
||||
.with_message("当前会员档位没有可用模型")
|
||||
})?
|
||||
.ok_or_else(|| agc_model_resolve_error(AgcModelResolveError::NoModelForTier))?
|
||||
.to_string();
|
||||
let models = catalog
|
||||
.available_models_for(access)
|
||||
@@ -370,11 +375,13 @@ async fn load_owner_llm_catalog(
|
||||
})
|
||||
}
|
||||
|
||||
/// 模型解析错误的统一映射:档位不够是 403 + 专用错误码,目录外 / 停用仍是 422。
|
||||
/// 模型解析错误的统一映射:档位不够是 403 + 专用错误码,目录外 / 停用是 422;
|
||||
/// 「该档没有任何可用模型」是目录 / 档位配置问题,按 503 处理,和 `/api/llm/models` 列表端点一致。
|
||||
fn agc_model_resolve_error(error: AgcModelResolveError) -> AppError {
|
||||
let status = match error {
|
||||
AgcModelResolveError::NotAvailableForPlan => StatusCode::FORBIDDEN,
|
||||
AgcModelResolveError::Unavailable => StatusCode::UNPROCESSABLE_ENTITY,
|
||||
AgcModelResolveError::NoModelForTier => StatusCode::SERVICE_UNAVAILABLE,
|
||||
};
|
||||
AppError::from_status(status)
|
||||
.with_code(error.code())
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
//!
|
||||
//! LLM 路由对外错误码(三条互不重叠,客户端据此区分处理):
|
||||
//! - `MODEL_NOT_AVAILABLE_FOR_PLAN`(403):模型存在且启用,但当前会员档位不够(`module-runtime` 解析)。
|
||||
//! - `MODEL_UNAVAILABLE`(422,列表无可用模型时 503):模型不在目录、已停用,或该档在目录里没有任何可用模型。
|
||||
//! - `MODEL_UNAVAILABLE`(422;该档在目录里没有任何可用模型时 503):模型不在目录或已停用;
|
||||
//! 「整个档位没有可用模型」属于目录 / 档位配置问题,列表端点与代理端点统一按 503 上报。
|
||||
//! - `MODEL_ACCESS_UNAVAILABLE`(503):读取账号权限档本身失败(SpacetimeDB 传输 / procedure / 鉴权)。
|
||||
//! 注意**缺会员行 / 缺档位目录行不算这条**:module procedure 已按 `Basic` 失败关闭并返回 `ok: true`。
|
||||
//! 能走到这条说明读链路不可用,不能把付费账号静默降级成 `Basic`。
|
||||
|
||||
@@ -69,16 +69,18 @@ impl From<RuntimeProfileMembershipModelAccess> for AgcModelAccess {
|
||||
/// 模型解析失败原因;调用方据此选择错误码与 HTTP 状态,不靠字符串匹配。
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum AgcModelResolveError {
|
||||
/// 标识不在目录、模型已停用,或该档位没有任何可用模型。
|
||||
/// 标识不在目录、模型已停用。
|
||||
Unavailable,
|
||||
/// 模型存在且启用,但当前会员档位不够(越权)。
|
||||
NotAvailableForPlan,
|
||||
/// 该会员档位在目录里没有任何可用模型:属于目录 / 档位配置问题,不是某个模型的问题。
|
||||
NoModelForTier,
|
||||
}
|
||||
|
||||
impl AgcModelResolveError {
|
||||
pub const fn code(self) -> &'static str {
|
||||
match self {
|
||||
Self::Unavailable => "MODEL_UNAVAILABLE",
|
||||
Self::Unavailable | Self::NoModelForTier => "MODEL_UNAVAILABLE",
|
||||
Self::NotAvailableForPlan => MODEL_NOT_AVAILABLE_FOR_PLAN,
|
||||
}
|
||||
}
|
||||
@@ -87,6 +89,7 @@ impl AgcModelResolveError {
|
||||
match self {
|
||||
Self::Unavailable => "所选模型不可用,请刷新模型列表",
|
||||
Self::NotAvailableForPlan => "当前会员档位不能使用该模型,请升级会员或重新选择模型",
|
||||
Self::NoModelForTier => "当前会员档位没有可用模型",
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -146,7 +149,7 @@ impl AgcModelCatalog {
|
||||
///
|
||||
/// `requested` 为空或占位标识时用该档默认模型;显式标识必须是目录内、启用、且档位允许的模型。
|
||||
/// 目录外 / 已停用 → [`AgcModelResolveError::Unavailable`];档位不够 → [`AgcModelResolveError::NotAvailableForPlan`],
|
||||
/// **不静默回退**成其它模型。
|
||||
/// **不静默回退**成其它模型;该档没有任何可用模型 → [`AgcModelResolveError::NoModelForTier`]。
|
||||
pub fn resolve_requested_for(
|
||||
&self,
|
||||
requested: Option<&str>,
|
||||
@@ -165,12 +168,12 @@ impl AgcModelCatalog {
|
||||
None => {
|
||||
let id = self
|
||||
.default_model_id_for(access)
|
||||
.ok_or(AgcModelResolveError::Unavailable)?;
|
||||
.ok_or(AgcModelResolveError::NoModelForTier)?;
|
||||
self.models
|
||||
.iter()
|
||||
.find(|model| model.id == id && model.is_available_for(access))
|
||||
.map(|model| model.model_id.as_str())
|
||||
.ok_or(AgcModelResolveError::Unavailable)
|
||||
.ok_or(AgcModelResolveError::NoModelForTier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -287,12 +290,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plan_with_no_available_model_falls_back_to_unavailable() {
|
||||
fn plan_with_no_available_model_reports_no_model_for_tier() {
|
||||
let mut catalog = catalog_from_ids(&["model-a"], 0);
|
||||
mark_full(&mut catalog, "model-a");
|
||||
assert_eq!(catalog.default_model_id_for(AgcModelAccess::Basic), None);
|
||||
assert_eq!(
|
||||
catalog.resolve_requested_for(None, AgcModelAccess::Basic),
|
||||
Err(AgcModelResolveError::NoModelForTier)
|
||||
);
|
||||
// 显式点了目录外的模型仍是「该模型不可用」,不能和「整个档位没有模型」混为一谈。
|
||||
assert_eq!(
|
||||
catalog.resolve_requested_for(Some("model-missing"), AgcModelAccess::Basic),
|
||||
Err(AgcModelResolveError::Unavailable)
|
||||
);
|
||||
}
|
||||
@@ -307,6 +315,12 @@ mod tests {
|
||||
AgcModelResolveError::Unavailable.code(),
|
||||
"MODEL_UNAVAILABLE"
|
||||
);
|
||||
// 「该档没有可用模型」沿用 MODEL_UNAVAILABLE 错误码,但调用方按变体把它映射到 503,
|
||||
// 与列表端点的状态保持一致。
|
||||
assert_eq!(
|
||||
AgcModelResolveError::NoModelForTier.code(),
|
||||
"MODEL_UNAVAILABLE"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user