langchain - ✅(Solved) Fix Streaming tool call executed with empty args {} due to SSE fragmentation of tool call arguments [6 pull requests, 13 comments, 8 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
langchain-ai/langchain#35514Fetched 2026-04-08 00:25:55
View on GitHub
Comments
13
Participants
8
Timeline
34
Reactions
0
Timeline (top)
commented ×13cross-referenced ×7referenced ×5labeled ×4

When streaming tool calls via OpenRouter (tested with deepseek/deepseek-v3.2), the agent executes tools with empty args {}, causing Field required validation errors. The root cause is that OpenRouter's upstream providers fragment tool call arguments across multiple SSE chunks, and langchain parses the first empty chunk as a valid tool call.

Instrumented streaming output showing the problem:

[TOOL_CHUNK] name=my_tool args='' id=call_34db6a0a9d314d71a8a74b3d
[TOOL_CALL_PARSED] name=my_tool args={}           ← parsed as valid, tool executed with empty args
[TOOL_CHUNK] name= args='{' id=
[TOOL_CHUNK] name= args='"url": "http://mywebsite.com"' id=
[TOOL_CHUNK] name= args='}' id=

The first chunk arrives with name="my_tool" and args="". parse_partial_json("") returns {} — a valid but empty dict. The ToolNode executes the tool immediately with these empty args. The subsequent chunks containing the actual arguments arrive too late.

This fragmentation is common across providers. I tested all 8 upstream providers available for this model on OpenRouter. Most exhibit this pattern, with varying frequency. The failure rate in production depends on which provider OpenRouter routes to and timing conditions.

When the agent encounters the error, it often retries the tool call in a loop, generating the same fragmented pattern repeatedly until hitting the recursion limit.

JS-side fix exists: This is the same root cause reported and fixed on the JS side in langchain-ai/langchainjs#8394 (PR langchain-ai/langchainjs#8419). The fix concatenates all tool_call_chunks belonging to the same tool call before parsing. As far as I can tell, this hasn't been ported to the Python side.

Workaround: Setting streaming=False on ChatOpenAI reliably resolves the issue.

Error Message

ToolException: Error executing tool my_tool: 1 validation error for my_toolArguments url Field required [type=missing, input_value={}, input_type=dict] at langgraph/prebuilt/tool_node.py at langchain_core/tools/base.py

Root Cause

When streaming tool calls via OpenRouter (tested with deepseek/deepseek-v3.2), the agent executes tools with empty args {}, causing Field required validation errors. The root cause is that OpenRouter's upstream providers fragment tool call arguments across multiple SSE chunks, and langchain parses the first empty chunk as a valid tool call.

Fix Action

Fix / Workaround

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Workaround: Setting streaming=False on ChatOpenAI reliably resolves the issue.

PR fix notes

PR #35517: fix(core): properly merge fragmented tool_call_chunks in AIMessageChunk

Description (problem / solution / changelog)

问题

根据 issue #35514,某些提供商(如 OpenRouter 配合 deepseek/deepseek-v3.2)在流式传输工具调用时,会将参数分散在多个 SSE 分片中。这导致工具在参数不完整时就被执行。

修复

  1. init_tool_calls() 方法 - 按 id 分组 tool_call_chunks,合并参数后再解析 JSON

  2. add_ai_message_chunks() 函数 - 合并多个 AIMessageChunk 时,也按 id 分组合并 tool_call_chunks

  3. 单元测试 - 添加了针对 SSE 分片场景的测试用例

测试场景

  • 单个工具调用的参数分片
  • 多个工具调用的分片
  • 合并后的完整参数验证

Fixes #35514

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +33/-6)
  • libs/core/tests/unit_tests/messages/test_ai.py (modified, +203/-149)

PR #35528: fix: Streaming tool call executed with empty args {} due to SSE fra...

Description (problem / solution / changelog)

Summary

Fixes #35514

Changes

Auto-generated contribution addressing: Streaming tool call executed with empty args {} due to SSE fragmentation of tool call arguments

Testing

  • Ran the project's test suite
  • Verified the fix addresses the issue

Contributed via automated open-source bot

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +6/-1)
  • libs/core/tests/unit_tests/messages/test_ai_issue_35514.py (added, +99/-0)
  • libs/core/tests/unit_tests/test_messages.py (modified, +5/-1)

PR #35617: fix: Streaming tool call executed with empty args {} due to SSE fra...

Description (problem / solution / changelog)

Summary

Resolves #35514.

What changed

<!-- Claude filled in the implementation details at commit time -->

Implemented a fix based on the issue description. See the diff for specifics.

Testing

  • Verified against the existing test suite
  • Checked that the fix addresses the reported behavior

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +39/-15)
  • libs/core/tests/unit_tests/messages/test_ai.py (modified, +86/-0)

PR #35634: fix(core): prevent premature tool_call parsing on empty SSE args

Description (problem / solution / changelog)

Description

Fixes #35514

When streaming tool calls via SSE, some LLM providers (OpenRouter, DeepSeek) split tool_call arguments across multiple chunks. The first chunk carries name + id but empty args (""). Previously, the empty string fell through to else {} in init_tool_calls, producing a valid tool_calls entry with args={}. Downstream code (ToolNode, user streaming handlers) treated this as a complete tool call and executed the tool prematurely with empty arguments.

Root Cause

In AIMessageChunk.init_tool_calls() (line 543):

args_ = parse_partial_json(chunk["args"]) if chunk["args"] else {}

chunk["args"] is "" (empty string) → falsy → args_ = {} → creates a valid tool_call with empty args.

Fix

Skip tool_call_chunks whose args is explicitly the empty string "":

  • args="" (empty string): streaming not started yet → skip (no premature tool_call)
  • args=None: no args info (backward compat) → treat as {} (unchanged)
  • args="{}" (explicit empty dict): valid no-arg tool call → parse normally (unchanged)

The final accumulated chunk (after merge_lists concatenation across all SSE fragments) carries the full args string and parses correctly.

Verification

# Before fix: chunk1.tool_calls = [{name: "my_tool", args: {}}]  ← WRONG
# After fix:  chunk1.tool_calls = []                              ← CORRECT

# Accumulated (chunk1 + chunk2 + chunk3 + chunk4):
# tool_calls = [{name: "my_tool", args: {"url": "http://example.com"}}]  ← CORRECT (unchanged)

Tests

  • Updated existing test expectation for empty-string args behavior
  • Added regression test test_sse_fragmented_tool_calls_no_premature_parse — verifies individual chunks don't produce premature tool_calls but accumulated result is correct
  • Added test_no_arg_tool_call_still_works — verifies tools with args="{}" still work
  • All 1703 core unit tests pass
  • All 308 OpenAI partner unit tests pass

Issue link

Fixes #35514

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +6/-0)
  • libs/core/tests/unit_tests/messages/test_ai.py (modified, +64/-0)
  • libs/core/tests/unit_tests/test_messages.py (modified, +2/-1)

PR #35743: fix(core): prevent empty args in streaming tool calls from SSE fragmentation

Description (problem / solution / changelog)

Summary

  • Changes the truthiness check if chunk["args"] to if chunk["args"] is not None in AIMessageChunk.tool_calls property.
  • When SSE fragmentation delivers an empty string "" as args, the old truthiness check treated it as falsy and defaulted to {}, causing a tool call to be emitted with empty args before the full JSON arrived.
  • With the fix, "" is passed to parse_partial_json("") which returns None, correctly routing the chunk to invalid_tool_calls until more data arrives.

Fixes #35514

Test plan

  • Verify existing streaming tool call tests pass
  • Test with SSE-fragmented responses where args arrive as empty string before JSON payload

This PR was developed with AI agent assistance.

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +5/-1)

PR #35769: fix(core): avoid premature tool call parsing on empty SSE args

Description (problem / solution / changelog)

Summary

  • skip tool-call chunks whose args field is the empty string, so fragmented SSE tool calls are not parsed into {} too early
  • keep valid no-argument tool calls working when the provider explicitly sends args="{}"
  • add regression coverage for fragmented SSE tool-call chunks and for explicit empty-object tool calls

Why

Issue #35514 reports that streaming providers can emit a first tool-call chunk with name/id but args="", followed by later chunks that carry the real JSON payload. Python currently parses that first empty string as {}, which lets downstream tool execution run too early with empty arguments.

This change defers parsing until the fragmented chunks are merged, matching the documented streaming model and the already-known JS-side behavior.

Fixes #35514.

Validation

  • uv run --all-groups ruff check langchain_core/messages/ai.py tests/unit_tests/messages/test_ai.py
  • uv run --group test pytest tests/unit_tests/messages/test_ai.py
  • direct runtime replay that merges SSE-fragmented tool_call_chunks into a final parsed tool call
  • repeated the full local gate twice consecutively (16 passed each pass)

Changed files

  • libs/core/langchain_core/messages/ai.py (modified, +5/-0)
  • libs/core/tests/unit_tests/messages/test_ai.py (modified, +84/-0)

Code Example

import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool

@tool
def my_tool(url: str) -> str:
    """Fetch data from the given URL."""
    return f"Fetched: {url}"

llm = ChatOpenAI(
    model="deepseek/deepseek-v3.2",
    api_key=os.environ.get("OPENROUTER_API_KEY"),
    base_url="https://openrouter.ai/api/v1",
)

agent = create_agent(
    model=llm,
    tools=[my_tool],
    system_prompt="You are a helpful assistant.",
)

import asyncio

async def main():
    async for stream_mode, data in agent.astream(
        {"messages": [{"role": "user", "content": 'Use my_tool with "http://mywebsite.com"'}]},
        stream_mode=["messages", "updates"],
    ):
        if stream_mode == "messages":
            token, metadata = data
            if hasattr(token, 'tool_call_chunks') and token.tool_call_chunks:
                for tc in token.tool_call_chunks:
                    print(f"[TOOL_CHUNK] name={tc.get('name')} args={tc.get('args')!r} id={tc.get('id')}")
            if hasattr(token, 'tool_calls') and token.tool_calls:
                for tc in token.tool_calls:
                    print(f"[TOOL_CALL_PARSED] name={tc.get('name')} args={tc.get('args')}")

asyncio.run(main())

---

ToolException: Error executing tool my_tool: 1 validation error for my_toolArguments
url
  Field required [type=missing, input_value={}, input_type=dict]
    at langgraph/prebuilt/tool_node.py
    at langchain_core/tools/base.py

---

[TOOL_CHUNK] name=my_tool args='' id=call_34db6a0a9d314d71a8a74b3d
[TOOL_CALL_PARSED] name=my_tool args={}           ← parsed as valid, tool executed with empty args
[TOOL_CHUNK] name= args='{' id=
[TOOL_CHUNK] name= args='"url": "http://mywebsite.com"' id=
[TOOL_CHUNK] name= args='}' id=
RAW_BUFFERClick to expand / collapse

Checked other resources

  • This is a bug, not a usage question.
  • I added a clear and descriptive title that summarizes this issue.
  • I used the GitHub search to find a similar question and didn't find it.
  • I am sure that this is a bug in LangChain rather than my code.
  • The bug is not resolved by updating to the latest stable version of LangChain (or the specific integration package).
  • This is not related to the langchain-community package.
  • I posted a self-contained, minimal, reproducible example. A maintainer can copy it and run it AS IS.

Package (Required)

  • langchain
  • langchain-openai
  • langchain-anthropic
  • langchain-classic
  • langchain-core
  • langchain-model-profiles
  • langchain-tests
  • langchain-text-splitters
  • langchain-chroma
  • langchain-deepseek
  • langchain-exa
  • langchain-fireworks
  • langchain-groq
  • langchain-huggingface
  • langchain-mistralai
  • langchain-nomic
  • langchain-ollama
  • langchain-openrouter
  • langchain-perplexity
  • langchain-qdrant
  • langchain-xai
  • Other / not sure / general

Related Issues / PRs

#32562, #30563, #32016, langchain-ai/langchainjs#8394

Reproduction Steps / Example Code (Python)

import os
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain.tools import tool

@tool
def my_tool(url: str) -> str:
    """Fetch data from the given URL."""
    return f"Fetched: {url}"

llm = ChatOpenAI(
    model="deepseek/deepseek-v3.2",
    api_key=os.environ.get("OPENROUTER_API_KEY"),
    base_url="https://openrouter.ai/api/v1",
)

agent = create_agent(
    model=llm,
    tools=[my_tool],
    system_prompt="You are a helpful assistant.",
)

import asyncio

async def main():
    async for stream_mode, data in agent.astream(
        {"messages": [{"role": "user", "content": 'Use my_tool with "http://mywebsite.com"'}]},
        stream_mode=["messages", "updates"],
    ):
        if stream_mode == "messages":
            token, metadata = data
            if hasattr(token, 'tool_call_chunks') and token.tool_call_chunks:
                for tc in token.tool_call_chunks:
                    print(f"[TOOL_CHUNK] name={tc.get('name')} args={tc.get('args')!r} id={tc.get('id')}")
            if hasattr(token, 'tool_calls') and token.tool_calls:
                for tc in token.tool_calls:
                    print(f"[TOOL_CALL_PARSED] name={tc.get('name')} args={tc.get('args')}")

asyncio.run(main())

Error Message and Stack Trace (if applicable)

ToolException: Error executing tool my_tool: 1 validation error for my_toolArguments
url
  Field required [type=missing, input_value={}, input_type=dict]
    at langgraph/prebuilt/tool_node.py
    at langchain_core/tools/base.py

Description

When streaming tool calls via OpenRouter (tested with deepseek/deepseek-v3.2), the agent executes tools with empty args {}, causing Field required validation errors. The root cause is that OpenRouter's upstream providers fragment tool call arguments across multiple SSE chunks, and langchain parses the first empty chunk as a valid tool call.

Instrumented streaming output showing the problem:

[TOOL_CHUNK] name=my_tool args='' id=call_34db6a0a9d314d71a8a74b3d
[TOOL_CALL_PARSED] name=my_tool args={}           ← parsed as valid, tool executed with empty args
[TOOL_CHUNK] name= args='{' id=
[TOOL_CHUNK] name= args='"url": "http://mywebsite.com"' id=
[TOOL_CHUNK] name= args='}' id=

The first chunk arrives with name="my_tool" and args="". parse_partial_json("") returns {} — a valid but empty dict. The ToolNode executes the tool immediately with these empty args. The subsequent chunks containing the actual arguments arrive too late.

This fragmentation is common across providers. I tested all 8 upstream providers available for this model on OpenRouter. Most exhibit this pattern, with varying frequency. The failure rate in production depends on which provider OpenRouter routes to and timing conditions.

When the agent encounters the error, it often retries the tool call in a loop, generating the same fragmented pattern repeatedly until hitting the recursion limit.

JS-side fix exists: This is the same root cause reported and fixed on the JS side in langchain-ai/langchainjs#8394 (PR langchain-ai/langchainjs#8419). The fix concatenates all tool_call_chunks belonging to the same tool call before parsing. As far as I can tell, this hasn't been ported to the Python side.

Workaround: Setting streaming=False on ChatOpenAI reliably resolves the issue.

System Info

System Information

OS: Linux OS Version: Ubuntu 22.04 Python Version: 3.12.3

Package Information

langchain_core: 1.2.14 langchain: 1.2.10 langchain_openai: 1.1.10 langgraph: 1.0.9

Other Dependencies

openai: 2.21.0 pydantic: 2.12.5 httpx: 0.28.1

extent analysis

Problem Summary

The issue is that when streaming tool calls via OpenRouter, the agent executes tools with empty args {} causing Field required validation errors.

Root Cause Analysis

The root cause is that OpenRouter's upstream providers fragment tool call arguments across multiple SSE chunks, and langchain parses the first empty chunk as a valid tool call.

Fix Plan

To fix this issue, we need to concatenate all tool_call_chunks belonging to the same tool call before parsing. We can do this by modifying the ChatOpenAI class to concatenate the chunks.

Step-by-Step Solution

  1. Modify the ChatOpenAI class:
import json

class ChatOpenAI:
    # ...

    async def _parse_tool_call_chunks(self, chunks):
        # Concatenate all chunks belonging to the same tool call
        tool_call_chunks = []
        current_tool_call = None
        for chunk in chunks:
            if chunk.get('name'):
                current_tool_call = chunk.get('name')
                tool_call_chunks.append(chunk)
            else:
                if current_tool_call:
                    tool_call_chunks.append(chunk)
        # Parse the concatenated chunks
        return json.loads(json.dumps(tool_call_chunks))
  1. Update the create_agent function:
def create_agent(model, tools, system_prompt):
    # ...
    llm = ChatOpenAI(
        model=model,
        api_key=os.environ.get("OPENROUTER_API_KEY"),
        base_url="https://openrouter.ai/api/v1",
    )
    # ...

Verification

To verify that the fix worked, you can run the same reproduction steps as before and check that the tool calls are executed with the correct arguments.

Extra Tips

  • Make sure to update the langchain_core package to the latest version.
  • If you're using a custom provider, make sure to test it thoroughly after applying the fix.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

langchain - ✅(Solved) Fix Streaming tool call executed with empty args {} due to SSE fragmentation of tool call arguments [6 pull requests, 13 comments, 8 participants]