diff --git a/backend/app/services/ai_provider.py b/backend/app/services/ai_provider.py index 16af234..203f711 100644 --- a/backend/app/services/ai_provider.py +++ b/backend/app/services/ai_provider.py @@ -64,6 +64,16 @@ def normalize_codex_command(command: str | None, *, strict: bool = True) -> str: return CODEX_DEFAULT_COMMAND +def normalize_openai_base_url(url: str) -> str: + """Return the OpenAI-compatible base URL expected by the OpenAI SDK.""" + 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 + + def codex_cli_available() -> bool: try: _codex_base_command() @@ -177,7 +187,7 @@ def _openai_client(api_key: str, timeout: float): user_agent = secrets_store.get_ai_config("ai_user_agent", "") or settings.ai_user_agent return AsyncOpenAI( api_key=api_key, - base_url=secrets_store.get_ai_config("ai_base_url", settings.ai_base_url), + base_url=normalize_openai_base_url(secrets_store.get_ai_config("ai_base_url", settings.ai_base_url)), timeout=timeout, max_retries=2, default_headers={"User-Agent": user_agent}, diff --git a/backend/tests/test_ai_provider.py b/backend/tests/test_ai_provider.py new file mode 100644 index 0000000..3db10c7 --- /dev/null +++ b/backend/tests/test_ai_provider.py @@ -0,0 +1,15 @@ +from __future__ import annotations + +from app.services.ai_provider import normalize_openai_base_url + + +def test_normalize_openai_base_url_adds_v1_for_root_gateway(): + assert normalize_openai_base_url("http://ai.zedbox.cn:8080") == "http://ai.zedbox.cn:8080/v1" + + +def test_normalize_openai_base_url_preserves_v1_base(): + assert normalize_openai_base_url("http://ai.zedbox.cn:8080/v1") == "http://ai.zedbox.cn:8080/v1" + + +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"