diff --git a/app/agent/llm/helper.py b/app/agent/llm/helper.py index 5e7e44eb..34ce43e8 100644 --- a/app/agent/llm/helper.py +++ b/app/agent/llm/helper.py @@ -656,6 +656,38 @@ class LLMHelper: headers["User-Agent"] = normalized_user_agent return headers or None + @classmethod + def _should_use_openai_responses_api( + cls, + provider: str, + model: str | None, + runtime: dict[str, Any], + ) -> bool | None: + """ + 判断官方 ChatGPT API Key 模式是否应使用 Responses API。 + + GPT-5/o 系推理模型在 Chat Completions 中组合 function tools 与 + reasoning_effort 时会被官方端点拒绝,因此 ChatGPT 官方 API Key + 模式需要显式切到 Responses API;通用 OpenAI-compatible 入口保持 + provider 目录解析出的默认行为,避免误伤第三方兼容服务。 + """ + runtime_use_responses_api = runtime.get("use_responses_api") + if runtime_use_responses_api is not None: + return bool(runtime_use_responses_api) + + provider_name = (provider or "").strip().lower() + if provider_name != "chatgpt": + return None + + base_url = str(runtime.get("base_url") or "").strip().lower() + if "api.openai.com" not in base_url: + return None + + model_name = cls._normalize_model_name(model) + if model_name.startswith(("gpt-5", "o1", "o3", "o4")): + return True + return None + @classmethod def _resolve_thinking_level( cls, @@ -760,6 +792,11 @@ class LLMHelper: model=model_name, thinking_level=normalized_thinking_level, ) + use_responses_api = cls._should_use_openai_responses_api( + provider=provider_name, + model=model_name, + runtime=runtime, + ) if runtime["runtime"] == "google": # 修补 Gemini 2.5 思考模型的 thought_signature 兼容性 @@ -832,7 +869,7 @@ class LLMHelper: stream_usage=True, openai_proxy=settings.PROXY_HOST, default_headers=default_headers, - use_responses_api=runtime.get("use_responses_api"), + use_responses_api=use_responses_api, **thinking_kwargs, ) diff --git a/tests/test_llm_helper_testcall.py b/tests/test_llm_helper_testcall.py index 98b2da01..2c4c8775 100644 --- a/tests/test_llm_helper_testcall.py +++ b/tests/test_llm_helper_testcall.py @@ -599,6 +599,34 @@ class LlmHelperTestCallTest(unittest.TestCase): self.assertEqual(len(calls), 1) self.assertEqual(calls[0].get("reasoning_effort"), "xhigh") + def test_get_llm_uses_responses_api_for_chatgpt_reasoning_models(self): + """校验 ChatGPT 官方推理模型会切换到 Responses API。""" + calls = [] + + class _FakeChatOpenAI: + def __init__(self, **kwargs): + calls.append(kwargs) + self.model = kwargs["model"] + self.profile = None + + with patch.dict( + sys.modules, + {"langchain_openai": SimpleNamespace(ChatOpenAI=_FakeChatOpenAI)}, + ): + asyncio.run( + llm_module.LLMHelper.get_llm( + provider="chatgpt", + model="gpt-5.4", + thinking_level="max", + api_key="sk-test", + base_url="https://api.openai.com/v1", + ) + ) + + self.assertEqual(len(calls), 1) + self.assertTrue(calls[0].get("use_responses_api")) + self.assertEqual(calls[0].get("reasoning_effort"), "xhigh") + def test_get_llm_uses_gemini_builtin_thinking_controls(self): calls = []