fix(ai): base_url 归一化保留非 v1 版本段, 修复 GLM /v4 被 404

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
This commit is contained in:
shy3130
2026-07-10 17:06:17 +08:00
parent fd0a83b276
commit e61c228002
2 changed files with 31 additions and 4 deletions
+12 -4
View File
@@ -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:
+19
View File
@@ -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,