From fbd5a238d8bc4e2e99f622faa8ee4ff7e4378b95 Mon Sep 17 00:00:00 2001 From: Mason Daugherty Date: Tue, 29 Jul 2025 10:26:38 -0400 Subject: [PATCH] fix(core): revert "fix: tool call streaming bug with inconsistent indices from Qwen3" (#32307) Reverts langchain-ai/langchain#32160 Original issue stems from using `ChatOpenAI` to interact with a `qwen` model. Recommended to use [langchain-qwq](https://python.langchain.com/docs/integrations/chat/qwq/) which is built for Qwen --- libs/core/langchain_core/utils/_merge.py | 33 ----------- libs/core/tests/unit_tests/test_messages.py | 62 --------------------- 2 files changed, 95 deletions(-) diff --git a/libs/core/langchain_core/utils/_merge.py b/libs/core/langchain_core/utils/_merge.py index 78d95c3a3ba..4200ca304d6 100644 --- a/libs/core/langchain_core/utils/_merge.py +++ b/libs/core/langchain_core/utils/_merge.py @@ -108,39 +108,6 @@ def merge_lists(left: Optional[list], *others: Optional[list]) -> Optional[list] else e ) merged[to_merge[0]] = merge_dicts(merged[to_merge[0]], new_e) - # Special handling for tool call chunks: if this chunk appears to be - # a continuation of a prior chunk (has None name/id) and no matching - # index was found, try to merge with the most recent tool call chunk - # that has a name/id. - # Fixes issues with models that send inconsistent indices. - # See #31511 for more. - elif ( - e.get("type") == "tool_call_chunk" - and e.get("name") is None - and e.get("id") is None - and merged - ): - # Find the most recent tool call chunk with a valid name or id - for i in reversed(range(len(merged))): - if ( - isinstance(merged[i], dict) - and merged[i].get("type") == "tool_call_chunk" - and ( - merged[i].get("name") is not None - or merged[i].get("id") is not None - ) - ): - # Merge with this chunk - new_e = ( - {k: v for k, v in e.items() if k != "type"} - if "type" in e - else e - ) - merged[i] = merge_dicts(merged[i], new_e) - break - else: - # No suitable chunk found, append as new - merged.append(e) else: merged.append(e) else: diff --git a/libs/core/tests/unit_tests/test_messages.py b/libs/core/tests/unit_tests/test_messages.py index 4c3a06d2293..58822433d7a 100644 --- a/libs/core/tests/unit_tests/test_messages.py +++ b/libs/core/tests/unit_tests/test_messages.py @@ -1197,65 +1197,3 @@ def test_convert_to_openai_image_block() -> None: } result = convert_to_openai_image_block(input_block) assert result == expected - - -def test_tool_call_streaming_different_indices() -> None: - """Test that tool call chunks with different indices but logically part of the same - tool call are merged correctly. This addresses issues with models like Qwen3 that - send inconsistent indices during streaming. - - See #31511. - - """ # noqa: D205 - # Create chunks that simulate Qwen3 behavior: - # First chunk has index=1, subsequent chunks have index=0 with name=None, id=None - chunk1 = AIMessageChunk( - content="", - tool_call_chunks=[ - create_tool_call_chunk( - name="search_function", - args='{"query": "langchain', - id="call_123", - index=1, # Initial index - ) - ], - ) - - chunk2 = AIMessageChunk( - content="", - tool_call_chunks=[ - create_tool_call_chunk( - name=None, # Continuation chunk - args=' tutorial"}', - id=None, # Continuation chunk - index=0, # Different index - ) - ], - ) - - # Merge chunks as happens during streaming - merged_chunk: AIMessageChunk = chunk1 + chunk2 # type: ignore[assignment] - - # Should result in a single merged tool call chunk - assert len(merged_chunk.tool_call_chunks) == 1 - assert merged_chunk.tool_call_chunks[0]["name"] == "search_function" - assert merged_chunk.tool_call_chunks[0]["args"] == '{"query": "langchain tutorial"}' - assert merged_chunk.tool_call_chunks[0]["id"] == "call_123" - - # Should result in a single valid tool call - assert len(merged_chunk.tool_calls) == 1 - assert len(merged_chunk.invalid_tool_calls) == 0 - - # Verify the final tool call is correct - tool_call = merged_chunk.tool_calls[0] - assert tool_call["name"] == "search_function" - assert tool_call["args"] == {"query": "langchain tutorial"} - assert tool_call["id"] == "call_123" - - # Test with message_chunk_to_message - message: AIMessage = message_chunk_to_message(merged_chunk) # type: ignore[assignment] - - assert len(message.tool_calls) == 1 - assert len(message.invalid_tool_calls) == 0 - assert message.tool_calls[0]["name"] == "search_function" - assert message.tool_calls[0]["args"] == {"query": "langchain tutorial"}