diff --git a/backend/app/services/ai_provider.py b/backend/app/services/ai_provider.py index 5e2b1ba..cef3ae5 100644 --- a/backend/app/services/ai_provider.py +++ b/backend/app/services/ai_provider.py @@ -64,14 +64,22 @@ def normalize_codex_command(command: str | None, *, strict: bool = True) -> str: return CODEX_DEFAULT_COMMAND +_VERSION_SEGMENT_RE = re.compile(r"/v\d+(?:\.\d+)?$", re.IGNORECASE) + + def normalize_openai_base_url(url: str) -> str: - """Return the OpenAI-compatible base URL expected by the OpenAI SDK.""" + """Return the OpenAI-compatible base URL expected by the OpenAI SDK. + + 识别 URL 中已有的版本段 (/v1、/v2、/v4 等) 时保持原样 —— 部分 OpenAI 兼容 + 服务用非 v1 的版本号 (如智谱 GLM 用 /api/paas/v4), 旧实现无条件补 /v1 会拼成 + 不存在的 /api/paas/v4/v1/chat/completions 导致 404。仅在无版本段时才补 /v1。 + """ base = (url or "").strip().rstrip("/") if base.endswith("/chat/completions"): base = base[: -len("/chat/completions")].rstrip("/") - if not base.endswith("/v1"): - base = f"{base}/v1" - return base + if _VERSION_SEGMENT_RE.search(base): + return base + return f"{base}/v1" def codex_cli_available() -> bool: diff --git a/backend/tests/test_ai_provider.py b/backend/tests/test_ai_provider.py index 52dc35b..a40cf49 100644 --- a/backend/tests/test_ai_provider.py +++ b/backend/tests/test_ai_provider.py @@ -18,6 +18,25 @@ def test_normalize_openai_base_url_strips_chat_completions_path(): assert normalize_openai_base_url("http://ai.zedbox.cn:8080/v1/chat/completions") == "http://ai.zedbox.cn:8080/v1" +def test_normalize_openai_base_url_preserves_glm_v4(): + """智谱 GLM 用 /api/paas/v4, 不能强制补成 /v4/v1 (会 404)。""" + assert normalize_openai_base_url("https://open.bigmodel.cn/api/paas/v4") == "https://open.bigmodel.cn/api/paas/v4" + + +def test_normalize_openai_base_url_strips_chat_completions_from_glm_v4(): + """用户填完整 /v4/chat/completions 时, 去掉后缀归一化为 /v4。""" + assert normalize_openai_base_url("https://open.bigmodel.cn/api/paas/v4/chat/completions") == "https://open.bigmodel.cn/api/paas/v4" + + +def test_normalize_openai_base_url_preserves_other_version_segments(): + """其它非 v1 版本号 (/v2 等) 也应保持原样。""" + assert normalize_openai_base_url("https://example.com/api/v2") == "https://example.com/api/v2" + + +def test_normalize_openai_base_url_strips_trailing_slash(): + assert normalize_openai_base_url("https://open.bigmodel.cn/api/paas/v4/") == "https://open.bigmodel.cn/api/paas/v4" + + def test_format_openai_error_hides_html_gateway_body(): response = httpx.Response( 504,