mirror of
https://github.com/hwchase17/langchain.git
synced 2026-06-09 10:17:00 +00:00
fix(openai): accommodate dict response items in streaming (#36899)
This commit is contained in:
@@ -4589,6 +4589,15 @@ def _construct_lc_result_from_responses_api(
|
|||||||
return ChatResult(generations=[ChatGeneration(message=message)])
|
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(
|
def _convert_responses_chunk_to_generation_chunk(
|
||||||
chunk: Any,
|
chunk: Any,
|
||||||
current_index: int, # index in content
|
current_index: int, # index in content
|
||||||
@@ -4686,14 +4695,16 @@ def _convert_responses_chunk_to_generation_chunk(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
elif chunk.type == "response.created":
|
elif chunk.type == "response.created":
|
||||||
id = chunk.response.id
|
response = _coerce_chunk_response(chunk.response)
|
||||||
response_metadata["id"] = chunk.response.id # Backwards compatibility
|
id = response.id
|
||||||
|
response_metadata["id"] = response.id # Backwards compatibility
|
||||||
elif chunk.type in ("response.completed", "response.incomplete"):
|
elif chunk.type in ("response.completed", "response.incomplete"):
|
||||||
|
response = _coerce_chunk_response(chunk.response)
|
||||||
msg = cast(
|
msg = cast(
|
||||||
AIMessage,
|
AIMessage,
|
||||||
(
|
(
|
||||||
_construct_lc_result_from_responses_api(
|
_construct_lc_result_from_responses_api(
|
||||||
chunk.response, schema=schema, output_version=output_version
|
response, schema=schema, output_version=output_version
|
||||||
)
|
)
|
||||||
.generations[0]
|
.generations[0]
|
||||||
.message
|
.message
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import copy
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from unittest.mock import MagicMock, patch
|
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", (
|
assert first_block.get("namespace") == "my_namespace", (
|
||||||
f"Expected namespace 'my_namespace', got {first_block.get('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"
|
||||||
|
|||||||
8
libs/partners/openai/uv.lock
generated
8
libs/partners/openai/uv.lock
generated
@@ -624,7 +624,7 @@ typing = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "langchain-core"
|
name = "langchain-core"
|
||||||
version = "1.3.0a2"
|
version = "1.3.0"
|
||||||
source = { editable = "../../core" }
|
source = { editable = "../../core" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "jsonpatch" },
|
{ name = "jsonpatch" },
|
||||||
@@ -1120,7 +1120,7 @@ wheels = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "openai"
|
name = "openai"
|
||||||
version = "2.29.0"
|
version = "2.32.0"
|
||||||
source = { registry = "https://pypi.org/simple" }
|
source = { registry = "https://pypi.org/simple" }
|
||||||
dependencies = [
|
dependencies = [
|
||||||
{ name = "anyio" },
|
{ name = "anyio" },
|
||||||
@@ -1132,9 +1132,9 @@ dependencies = [
|
|||||||
{ name = "tqdm" },
|
{ name = "tqdm" },
|
||||||
{ name = "typing-extensions" },
|
{ 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 = [
|
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]]
|
[[package]]
|
||||||
|
|||||||
Reference in New Issue
Block a user