From e61c228002332d717d79ca07f6492e3eb206936c Mon Sep 17 00:00:00 2001 From: shy3130 <415333856@qq.com> Date: Fri, 10 Jul 2026 17:06:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(ai):=20base=5Furl=20=E5=BD=92=E4=B8=80?= =?UTF-8?q?=E5=8C=96=E4=BF=9D=E7=95=99=E9=9D=9E=20v1=20=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E6=AE=B5,=20=E4=BF=AE=E5=A4=8D=20GLM=20/v4=20=E8=A2=AB=20404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit normalize_openai_base_url 旧实现只检查「是否以 /v1 结尾」, 不是就强制补 /v1。 智谱 GLM 的 OpenAI 兼容地址是 /api/paas/v4, 被补成 /api/paas/v4/v1, SDK 再拼 /chat/completions → 不存在路径 → 404。 改用正则检测 URL 末尾已有的版本段 (/v1、/v2、/v4、/v1.1 等): - 已有版本段 → 保持原样 - 无版本段 → 补 /v1 (无版本号网关行为不变) - 去 /chat/completions 后缀逻辑保留 补 4 条单测覆盖 GLM /v4 场景, 原有 v1/无版本网关回归不变。 Closes #60 --- backend/app/services/ai_provider.py | 16 ++++++++++++---- backend/tests/test_ai_provider.py | 19 +++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) 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,