From fda48f7f8b1f1f01ee8b6c1f24ed70c676f3e10f Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Wed, 3 Jun 2026 04:42:17 +0800 Subject: [PATCH] fix: reject invalid chat completion payloads --- .../_chat_completion_client.py | 12 ++++++++++++ .../openai/test_openai_chat_completion_client.py | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/python/packages/openai/agent_framework_openai/_chat_completion_client.py b/python/packages/openai/agent_framework_openai/_chat_completion_client.py index a6878c9f2d..e46e63e2c5 100644 --- a/python/packages/openai/agent_framework_openai/_chat_completion_client.py +++ b/python/packages/openai/agent_framework_openai/_chat_completion_client.py @@ -684,6 +684,18 @@ def _prepare_options(self, messages: Sequence[Message], options: Mapping[str, An def _parse_response_from_openai(self, response: ChatCompletion, options: Mapping[str, Any]) -> ChatResponse: """Parse a response from OpenAI into a ChatResponse.""" + if not hasattr(response, "choices"): + preview = repr(response) + if len(preview) > 500: + preview = f"{preview[:497]}..." + raise ChatClientException( + maybe_append_azure_endpoint_guidance( + f"{type(self)} service returned an invalid OpenAI Chat Completions API response: " + f"expected an object with 'choices', got {type(response).__name__}: {preview}", + azure_endpoint=self.azure_endpoint, + ) + ) + response_metadata = self._get_metadata_from_chat_response(response) messages: list[Message] = [] finish_reason: FinishReason | None = None diff --git a/python/packages/openai/tests/openai/test_openai_chat_completion_client.py b/python/packages/openai/tests/openai/test_openai_chat_completion_client.py index 85e12b8626..9a3f1da110 100644 --- a/python/packages/openai/tests/openai/test_openai_chat_completion_client.py +++ b/python/packages/openai/tests/openai/test_openai_chat_completion_client.py @@ -61,6 +61,21 @@ def test_get_response_is_defined_on_openai_class() -> None: assert all(parameter.kind != inspect.Parameter.VAR_KEYWORD for parameter in signature.parameters.values()) +def test_parse_response_rejects_non_chat_completion_object() -> None: + client = OpenAIChatCompletionClient(model="test-model", api_key="test-key") + response: Any = "plain backend error" + + with pytest.raises(ChatClientException) as exc_info: + client._parse_response_from_openai(response, options={}) + + exception_message = str(exc_info.value) + assert "invalid OpenAI Chat Completions API response" in exception_message + assert "expected an object with 'choices'" in exception_message + assert "got str" in exception_message + assert "plain backend error" in exception_message + assert "'str' object has no attribute 'system_fingerprint'" not in exception_message + + def test_init_uses_explicit_parameters() -> None: signature = inspect.signature(RawOpenAIChatCompletionClient.__init__)