Skip to content

Python: fix: handle string response in _get_metadata_from_chat_response (Closes #6234)#6257

Closed
kuishou68 wants to merge 1 commit into
microsoft:mainfrom
kuishou68:fix/issue-6234-str-response
Closed

Python: fix: handle string response in _get_metadata_from_chat_response (Closes #6234)#6257
kuishou68 wants to merge 1 commit into
microsoft:mainfrom
kuishou68:fix/issue-6234-str-response

Conversation

@kuishou68
Copy link
Copy Markdown

Closes #6234

Problem

When a non-conformant OpenAI-compatible backend returns a plain string instead of a ChatCompletion object, _parse_response_from_openai() passes it directly to _get_metadata_from_chat_response(), which crashes with:

AttributeError: 'str' object has no attribute 'system_fingerprint'

Fix

Two defensive changes:

  1. _parse_response_from_openai(): Added an isinstance(response, str) check at the top of the method. If the response is a string, a descriptive ChatClientException is raised with the invalid payload included for debugging — following the same pattern used elsewhere in this file for error handling.

  2. _get_metadata_from_chat_response(): Changed response.system_fingerprint to getattr(response, "system_fingerprint", None) as defense-in-depth, so even if an unexpected non-ChatCompletion object slips through, it won't crash with an AttributeError.

Testing

  • The change follows existing error-handling patterns already used in _parse_response_from_openai (see BadRequestError handling at lines 557-571).
  • The getattr guard is consistent with how logprobs is already handled in _get_metadata_from_chat_choice (line 810).

Co-authored-by: lingxiu58 86288566+lingxiu58@users.noreply.github.com
Co-authored-by: pojian68 232320289+pojian68@users.noreply.github.com

microsoft#6234)

Co-authored-by: lingxiu58 <86288566+lingxiu58@users.noreply.github.com>
Co-authored-by: pojian68 <232320289+pojian68@users.noreply.github.com>
Signed-off-by: Cocoon-Break <54054995+kuishou68@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 2, 2026 01:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR hardens OpenAI chat completion response parsing by adding defensive type-checking and making metadata extraction tolerant of missing fields.

Changes:

  • Add a guard that raises a ChatClientException when the service returns a str instead of a ChatCompletion.
  • Make system_fingerprint extraction resilient to SDK/service responses where the attribute is absent.

Comment on lines +687 to +693
if isinstance(response, str):
raise ChatClientException(
maybe_append_azure_endpoint_guidance(
f"{type(self)} service returned an invalid response (str instead of ChatCompletion): {response!r}",
azure_endpoint=self.azure_endpoint,
),
)
if isinstance(response, str):
raise ChatClientException(
maybe_append_azure_endpoint_guidance(
f"{type(self)} service returned an invalid response (str instead of ChatCompletion): {response!r}",
@moonbox3 moonbox3 added the python label Jun 2, 2026
@github-actions github-actions Bot changed the title fix: handle string response in _get_metadata_from_chat_response (Closes #6234) Python: fix: handle string response in _get_metadata_from_chat_response (Closes #6234) Jun 2, 2026
"""Get metadata from a chat response."""
return {
"system_fingerprint": response.system_fingerprint,
"system_fingerprint": getattr(response, "system_fingerprint", None),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we mirror this onto the streaming path too? The fix hardens non-streaming, but here response.system_fingerprint is still a direct deref, and _parse_response_update_from_openai has no str guard. A provider returning the same malformed payload while streaming hits the cryptic AttributeError again. The outer except Exception still wraps it into ChatClientException, so it's loud, just without the specific "str instead of ..." message this PR adds for non-streaming. Same contract on both paths?


def _parse_response_from_openai(self, response: ChatCompletion, options: Mapping[str, Any]) -> ChatResponse:
"""Parse a response from OpenAI into a ChatResponse."""
if isinstance(response, str):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we covering the new branch? Couldn't find a test exercising the str -> ChatClientException path in python/packages/openai/tests/openai/test_openai_chat_completion_client.py. Existing direct-call tests make it nearly free: client._parse_response_from_openai("some string", {}) inside pytest.raises(ChatClientException), asserting substring "str instead of ChatCompletion" rather than the full message (Azure suffix is env-dependent). Pins the contract so a refactor can't quietly regress it back to the AttributeError.

@eavanvalkenburg
Copy link
Copy Markdown
Member

This is a duplicate of #6270 that solves for two issues, closing this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

4 participants