Skip to content

Streaming tool call executed with empty args {} due to SSE fragmentation of tool call arguments #35514

@backer-and

Description

@backer-and

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugRelated to a bug, vulnerability, unexpected error with an existing featurecore`langchain-core` package issues & PRsexternalopenai`langchain-openai` package issues & PRs

    Type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions