fix(openai): accommodate dict response items in streaming (#36899)

This commit is contained in:
ccurme
2026-04-20 15:44:01 -04:00
committed by GitHub
parent 8fec4e7cee
commit 19b0805bc1
3 changed files with 48 additions and 7 deletions

View File

@@ -4589,6 +4589,15 @@ def _construct_lc_result_from_responses_api(
return ChatResult(generations=[ChatGeneration(message=message)])
def _coerce_chunk_response(resp: Any) -> Any:
# dict `response` items on stream events have been observed in the wild
if isinstance(resp, dict):
from openai.types.responses import Response
return Response.model_validate(resp)
return resp
def _convert_responses_chunk_to_generation_chunk(
chunk: Any,
current_index: int, # index in content
@@ -4686,14 +4695,16 @@ def _convert_responses_chunk_to_generation_chunk(
}
)
elif chunk.type == "response.created":
id = chunk.response.id
response_metadata["id"] = chunk.response.id # Backwards compatibility
response = _coerce_chunk_response(chunk.response)
id = response.id
response_metadata["id"] = response.id # Backwards compatibility
elif chunk.type in ("response.completed", "response.incomplete"):
response = _coerce_chunk_response(chunk.response)
msg = cast(
AIMessage,
(
_construct_lc_result_from_responses_api(
chunk.response, schema=schema, output_version=output_version
response, schema=schema, output_version=output_version
)
.generations[0]
.message

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import copy
from typing import Any
from unittest.mock import MagicMock, patch
@@ -986,3 +987,32 @@ def test_responses_stream_function_call_preserves_namespace() -> None:
assert first_block.get("namespace") == "my_namespace", (
f"Expected namespace 'my_namespace', got {first_block.get('namespace')}"
)
def test_responses_stream_tolerates_dict_response_field() -> None:
"""Regression test for `AttributeError: 'dict' object has no attribute 'id'`.
The OpenAI SDK types `<event>.response` strictly as `Response`, but raw dicts
have been observed in the wild.
"""
stream = copy.deepcopy(responses_stream)
first_event = stream[0]
assert isinstance(first_event, ResponseCreatedEvent)
first_event.response = first_event.response.model_dump(mode="json") # type: ignore[assignment]
assert isinstance(first_event.response, dict)
llm = ChatOpenAI(model="o4-mini", use_responses_api=True)
mock_client = MagicMock()
def mock_create(*args: Any, **kwargs: Any) -> MockSyncContextManager:
return MockSyncContextManager(stream)
mock_client.responses.create = mock_create
full: BaseMessageChunk | None = None
with patch.object(llm, "root_client", mock_client):
for chunk in llm.stream("test"):
assert isinstance(chunk, AIMessageChunk)
full = chunk if full is None else full + chunk
assert isinstance(full, AIMessageChunk)
assert full.id == "resp_123"

View File

@@ -624,7 +624,7 @@ typing = [
[[package]]
name = "langchain-core"
version = "1.3.0a2"
version = "1.3.0"
source = { editable = "../../core" }
dependencies = [
{ name = "jsonpatch" },
@@ -1120,7 +1120,7 @@ wheels = [
[[package]]
name = "openai"
version = "2.29.0"
version = "2.32.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
@@ -1132,9 +1132,9 @@ dependencies = [
{ name = "tqdm" },
{ name = "typing-extensions" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b4/15/203d537e58986b5673e7f232453a2a2f110f22757b15921cbdeea392e520/openai-2.29.0.tar.gz", hash = "sha256:32d09eb2f661b38d3edd7d7e1a2943d1633f572596febe64c0cd370c86d52bec", size = 671128, upload-time = "2026-03-17T17:53:49.599Z" }
sdist = { url = "https://files.pythonhosted.org/packages/ed/59/bdcc6b759b8c42dd73afaf5bf8f902c04b37987a5514dbc1c64dba390fef/openai-2.32.0.tar.gz", hash = "sha256:c54b27a9e4cb8d51f0dd94972ffd1a04437efeb259a9e60d8922b8bd26fe55e0", size = 693286, upload-time = "2026-04-15T22:28:19.434Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/d0/b1/35b6f9c8cf9318e3dbb7146cc82dab4cf61182a8d5406fc9b50864362895/openai-2.29.0-py3-none-any.whl", hash = "sha256:b7c5de513c3286d17c5e29b92c4c98ceaf0d775244ac8159aeb1bddf840eb42a", size = 1141533, upload-time = "2026-03-17T17:53:47.348Z" },
{ url = "https://files.pythonhosted.org/packages/1e/c1/d6e64ccd0536bf616556f0cad2b6d94a8125f508d25cfd814b1d2db4e2f1/openai-2.32.0-py3-none-any.whl", hash = "sha256:4dcc9badeb4bf54ad0d187453742f290226d30150890b7890711bda4f32f192f", size = 1162570, upload-time = "2026-04-15T22:28:17.714Z" },
]
[[package]]