mirror of
https://github.com/hwchase17/langchain.git
synced 2026-04-03 19:04:23 +00:00
fix(openai): preserve namespace field in streaming function_call chunks (#36108)
This commit is contained in:
@@ -4723,16 +4723,17 @@ def _convert_responses_chunk_to_generation_chunk(
|
||||
"index": current_index,
|
||||
}
|
||||
)
|
||||
content.append(
|
||||
{
|
||||
"type": "function_call",
|
||||
"name": chunk.item.name,
|
||||
"arguments": chunk.item.arguments,
|
||||
"call_id": chunk.item.call_id,
|
||||
"id": chunk.item.id,
|
||||
"index": current_index,
|
||||
}
|
||||
)
|
||||
function_call_content: dict = {
|
||||
"type": "function_call",
|
||||
"name": chunk.item.name,
|
||||
"arguments": chunk.item.arguments,
|
||||
"call_id": chunk.item.call_id,
|
||||
"id": chunk.item.id,
|
||||
"index": current_index,
|
||||
}
|
||||
if getattr(chunk.item, "namespace", None) is not None:
|
||||
function_call_content["namespace"] = chunk.item.namespace
|
||||
content.append(function_call_content)
|
||||
elif chunk.type == "response.output_item.done" and chunk.item.type in (
|
||||
"compaction",
|
||||
"web_search_call",
|
||||
|
||||
@@ -10,6 +10,9 @@ from openai.types.responses import (
|
||||
ResponseContentPartAddedEvent,
|
||||
ResponseContentPartDoneEvent,
|
||||
ResponseCreatedEvent,
|
||||
ResponseFunctionCallArgumentsDeltaEvent,
|
||||
ResponseFunctionCallArgumentsDoneEvent,
|
||||
ResponseFunctionToolCallItem,
|
||||
ResponseInProgressEvent,
|
||||
ResponseOutputItemAddedEvent,
|
||||
ResponseOutputItemDoneEvent,
|
||||
@@ -791,3 +794,195 @@ def test_responses_stream_with_image_generation_multiple_calls() -> None:
|
||||
with patch.object(llm, "root_client", mock_client):
|
||||
chunks = list(llm_with_tools.stream("test again"))
|
||||
assert len(chunks) > 0
|
||||
|
||||
|
||||
def test_responses_stream_function_call_preserves_namespace() -> None:
|
||||
"""Test that namespace field is preserved in streaming function_call chunks."""
|
||||
function_call_stream = [
|
||||
ResponseCreatedEvent(
|
||||
response=Response(
|
||||
id="resp_ns",
|
||||
created_at=1749734255.0,
|
||||
error=None,
|
||||
incomplete_details=None,
|
||||
instructions=None,
|
||||
metadata={},
|
||||
model="gpt-4o-2025-01-01",
|
||||
object="response",
|
||||
output=[],
|
||||
parallel_tool_calls=True,
|
||||
temperature=1.0,
|
||||
tool_choice="auto",
|
||||
tools=[],
|
||||
top_p=1.0,
|
||||
background=False,
|
||||
max_output_tokens=None,
|
||||
previous_response_id=None,
|
||||
reasoning=None,
|
||||
service_tier="auto",
|
||||
status="in_progress",
|
||||
text=ResponseTextConfig(format=ResponseFormatText(type="text")),
|
||||
truncation="disabled",
|
||||
usage=None,
|
||||
user=None,
|
||||
),
|
||||
sequence_number=0,
|
||||
type="response.created",
|
||||
),
|
||||
ResponseInProgressEvent(
|
||||
response=Response(
|
||||
id="resp_ns",
|
||||
created_at=1749734255.0,
|
||||
error=None,
|
||||
incomplete_details=None,
|
||||
instructions=None,
|
||||
metadata={},
|
||||
model="gpt-4o-2025-01-01",
|
||||
object="response",
|
||||
output=[],
|
||||
parallel_tool_calls=True,
|
||||
temperature=1.0,
|
||||
tool_choice="auto",
|
||||
tools=[],
|
||||
top_p=1.0,
|
||||
background=False,
|
||||
max_output_tokens=None,
|
||||
previous_response_id=None,
|
||||
reasoning=None,
|
||||
service_tier="auto",
|
||||
status="in_progress",
|
||||
text=ResponseTextConfig(format=ResponseFormatText(type="text")),
|
||||
truncation="disabled",
|
||||
usage=None,
|
||||
user=None,
|
||||
),
|
||||
sequence_number=1,
|
||||
type="response.in_progress",
|
||||
),
|
||||
ResponseOutputItemAddedEvent(
|
||||
item=ResponseFunctionToolCallItem(
|
||||
id="fc_123",
|
||||
arguments="",
|
||||
call_id="call_123",
|
||||
name="search_tool",
|
||||
type="function_call",
|
||||
namespace="my_namespace",
|
||||
status="in_progress",
|
||||
),
|
||||
output_index=0,
|
||||
sequence_number=2,
|
||||
type="response.output_item.added",
|
||||
),
|
||||
ResponseFunctionCallArgumentsDeltaEvent(
|
||||
delta='{"query":',
|
||||
item_id="fc_123",
|
||||
output_index=0,
|
||||
sequence_number=3,
|
||||
type="response.function_call_arguments.delta",
|
||||
),
|
||||
ResponseFunctionCallArgumentsDeltaEvent(
|
||||
delta='"test"}',
|
||||
item_id="fc_123",
|
||||
output_index=0,
|
||||
sequence_number=4,
|
||||
type="response.function_call_arguments.delta",
|
||||
),
|
||||
ResponseFunctionCallArgumentsDoneEvent(
|
||||
arguments='{"query":"test"}',
|
||||
item_id="fc_123",
|
||||
name="search_tool",
|
||||
output_index=0,
|
||||
sequence_number=5,
|
||||
type="response.function_call_arguments.done",
|
||||
),
|
||||
ResponseOutputItemDoneEvent(
|
||||
item=ResponseFunctionToolCallItem(
|
||||
id="fc_123",
|
||||
arguments='{"query":"test"}',
|
||||
call_id="call_123",
|
||||
name="search_tool",
|
||||
type="function_call",
|
||||
namespace="my_namespace",
|
||||
status="completed",
|
||||
),
|
||||
output_index=0,
|
||||
sequence_number=6,
|
||||
type="response.output_item.done",
|
||||
),
|
||||
ResponseCompletedEvent(
|
||||
response=Response(
|
||||
id="resp_ns",
|
||||
created_at=1749734255.0,
|
||||
error=None,
|
||||
incomplete_details=None,
|
||||
instructions=None,
|
||||
metadata={},
|
||||
model="gpt-4o-2025-01-01",
|
||||
object="response",
|
||||
output=[
|
||||
ResponseFunctionToolCallItem(
|
||||
id="fc_123",
|
||||
arguments='{"query":"test"}',
|
||||
call_id="call_123",
|
||||
name="search_tool",
|
||||
type="function_call",
|
||||
namespace="my_namespace",
|
||||
status="completed",
|
||||
),
|
||||
],
|
||||
parallel_tool_calls=True,
|
||||
temperature=1.0,
|
||||
tool_choice="auto",
|
||||
tools=[],
|
||||
top_p=1.0,
|
||||
background=False,
|
||||
max_output_tokens=None,
|
||||
previous_response_id=None,
|
||||
reasoning=None,
|
||||
service_tier="default",
|
||||
status="completed",
|
||||
text=ResponseTextConfig(format=ResponseFormatText(type="text")),
|
||||
truncation="disabled",
|
||||
usage=ResponseUsage(
|
||||
input_tokens=10,
|
||||
input_tokens_details=InputTokensDetails(cached_tokens=0),
|
||||
output_tokens=20,
|
||||
output_tokens_details=OutputTokensDetails(reasoning_tokens=0),
|
||||
total_tokens=30,
|
||||
),
|
||||
user=None,
|
||||
),
|
||||
sequence_number=7,
|
||||
type="response.completed",
|
||||
),
|
||||
]
|
||||
|
||||
llm = ChatOpenAI(
|
||||
model="gpt-4o", use_responses_api=True, output_version="responses/v1"
|
||||
)
|
||||
mock_client = MagicMock()
|
||||
|
||||
def mock_create(*args: Any, **kwargs: Any) -> MockSyncContextManager:
|
||||
return MockSyncContextManager(function_call_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)
|
||||
|
||||
function_call_blocks = [
|
||||
block
|
||||
for block in full.content
|
||||
if isinstance(block, dict) and block.get("type") == "function_call"
|
||||
]
|
||||
assert len(function_call_blocks) > 0
|
||||
|
||||
first_block = function_call_blocks[0]
|
||||
assert first_block.get("namespace") == "my_namespace", (
|
||||
f"Expected namespace 'my_namespace', got {first_block.get('namespace')}"
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user