From dada36d6df30fd5a698ad70e38a1df737959a48a Mon Sep 17 00:00:00 2001 From: fxbin Date: Mon, 28 Sep 2026 00:13:13 +0800 Subject: [PATCH] =?UTF-8?q?fix(backend):=20=E6=A8=A1=E5=9E=8B=E5=90=8D?= =?UTF-8?q?=E5=90=AB=E6=96=9C=E6=9D=A0=E6=97=B6=E6=8C=89=E6=98=BE=E5=BC=8F?= =?UTF-8?q?=20provider=20=E8=B7=AF=E7=94=B1=E4=BF=AE=E5=A4=8D=20litellm=20?= =?UTF-8?q?=E6=8B=92=E7=BB=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/services/llm/model_resolver.py | 16 ++++++---- backend/tests/test_llm_models.py | 37 ++++++++++++++++++++++ docs/quality/regression-matrix.md | 7 ++++ 3 files changed, 53 insertions(+), 7 deletions(-) diff --git a/backend/app/services/llm/model_resolver.py b/backend/app/services/llm/model_resolver.py index 241de93f..09754f2c 100644 --- a/backend/app/services/llm/model_resolver.py +++ b/backend/app/services/llm/model_resolver.py @@ -51,20 +51,22 @@ def resolve_litellm_model(model: ModelLike) -> str: return explicit_model model_id = _clean(model.model_id) or "" - if "/" in model_id: - return model_id - provider = _clean(params.get("litellm_provider") or litellm_params.get("custom_llm_provider") or model.provider) if provider == "custom": - # litellm 没有 "custom" provider,"custom/" 会被解析成未知路由 - # (对可达端点也会打出错误路径)。选了"完全自定义"预设但只填裸模型名 - # 时,唯一可行的路由是 OpenAI 兼容网关;模型名自带 "/" 的已在上面原样返回。 + # litellm 没有 "custom" provider,"custom/" 会被解析成未知路由。 + # 选了「完全自定义」预设时唯一可行的路由是 OpenAI 兼容网关——模型名 + # 含 "/"(如组织/模型命名 XingChenAGI/Xing4.0-29B)也必须走该路由: + # 原样透传会被 litellm 把首段当 provider 解析而报 BadRequestError(#83)。 return f"openai/{model_id}" if provider: - return f"{provider}/{model_id}" + # 显式 provider 优先;模型名已带同前缀时去重,避免 openai/openai/...。 + prefix = f"{provider}/" + return model_id if model_id.startswith(prefix) else f"{provider}/{model_id}" + # 无任何 provider 信息时,含 "/" 的模型名视为用户显式给出的 litellm 路由串 + # (如 deepseek/deepseek-chat)原样返回;裸模型名同样原样返回。 return model_id diff --git a/backend/tests/test_llm_models.py b/backend/tests/test_llm_models.py index 3a9a6f60..fcdeb709 100644 --- a/backend/tests/test_llm_models.py +++ b/backend/tests/test_llm_models.py @@ -122,6 +122,43 @@ def test_shared_model_resolver_prefers_explicit_litellm_model(): assert resolve_litellm_model(model) == "openai/deepseek-v4-flash-free" +def test_shared_model_resolver_routes_slash_namespaced_custom_model_as_openai(): + """#83:组织/模型命名(XingChenAGI/Xing4.0-29B)在 custom 预设下必须走 + OpenAI 兼容路由,原样透传会被 litellm 当未知 provider 拒绝。""" + model = SimpleNamespace( + provider="custom", + model_id="XingChenAGI/Xing4.0-29B", + api_base="https://api.xingchen.example/v1", + ) + + assert resolve_litellm_model(model) == "openai/XingChenAGI/Xing4.0-29B" + + +def test_shared_model_resolver_routes_slash_namespaced_model_with_explicit_provider(): + """显式 provider(如 openai 预设 + 自定义网关)时,组织/模型命名按该 provider 路由。""" + model = SimpleNamespace( + provider="openai", + model_id="XingChenAGI/Xing4.0-29B", + api_base="https://api.xingchen.example/v1", + ) + + assert resolve_litellm_model(model) == "openai/XingChenAGI/Xing4.0-29B" + + +def test_shared_model_resolver_dedupes_provider_prefix(): + """模型名已带同 provider 前缀时不重复拼接。""" + model = SimpleNamespace(provider="openai", model_id="openai/gpt-4.1-mini", api_base=None) + + assert resolve_litellm_model(model) == "openai/gpt-4.1-mini" + + +def test_shared_model_resolver_keeps_slash_model_id_without_any_provider(): + """无任何 provider 信息时,含 "/" 的模型名保持原样(用户显式路由串)。""" + model = SimpleNamespace(provider="", model_id="deepseek/deepseek-chat", api_base=None) + + assert resolve_litellm_model(model) == "deepseek/deepseek-chat" + + def test_shared_model_resolver_routes_bare_custom_provider_as_openai_compatible(): # "完全自定义"预设落库的 provider=custom + 裸模型名:litellm 没有 custom # provider,唯一可行路由是 OpenAI 兼容网关。 diff --git a/docs/quality/regression-matrix.md b/docs/quality/regression-matrix.md index 9ca2669d..49b09dff 100644 --- a/docs/quality/regression-matrix.md +++ b/docs/quality/regression-matrix.md @@ -88,6 +88,13 @@ - 症状:`_cache_warmup_task` 非 CancelledError 异常会中断后续全部清理步骤;jieba 预热 await 无超时且 `to_thread` 不可取消,可挂死停机;整体停机无 deadline。 - 回归测试:`tests/test_shutdown_prewarm.py`(异常不外抛且留痕 / jieba 超时不挂死 / 正常与已取消路径)。owner:#73(Parent #6,修复 PR #77:`_shutdown_prewarm_tasks`)。 +### D-11 含斜杠模型名的网关模型被 litellm 拒绝 — #83 ✅ 已修复关闭(2026-09-27) +- 症状:model_test 对 `XingChenAGI/Xing4.0-29B` 报 `litellm.BadRequestError: LLM Provider NOT provided`。 +- 根因:`resolve_litellm_model` 对含 `/` 的 model_id 原样透传、跳过 provider 路由;「组织/模型」命名被 litellm 按首段当未知 provider 解析。影响 model_test 与分析管线全调用面。 +- 边界:llm/model_resolver / 模型目录;严重度 P1;复现性 always(该命名格式)。 +- 回归测试:`tests/test_llm_models.py` 四场景(custom+org/model → `openai/` 前缀、显式 provider+org/model、同前缀去重、无 provider 保持原样)。 +- owner:#83。 + ## 三、关键流程基线(9 项) 状态标记:✅ = 2026-09-27 在 main @ 7203847 新鲜复跑通过;📋 = 现有套件覆盖、未逐项复跑(跑全量即覆盖)。