langchain - 💡(How to fix) Fix stream_events(version="v3"): reasoning content dropped from AIMessage when message also contains a tool_call

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…

I'm running a create_agent with Gemini (thinking enabled) and a tool, then streaming the run via astream_events(version="v3"). The model's reasoning streams perfectly during the run — ChatModelStream.reasoning emits the full thinking text live.

Expected: when the agent's tool-calling step finishes, the persisted AIMessage in the final graph state preserves both the reasoning block and the tool_call in content, the same way ainvoke() does on the same model.

Actual: the persisted AIMessage has content = [{type: tool_call, ...}] — the reasoning that streamed through .reasoning is silently dropped. It's not in content, not in additional_kwargs, not in response_metadata. The next AIMessage in the run (text-only response after the tool result) is unaffected; only AIMessages that contain a tool_call lose their reasoning.

Error Message

Error Message and Stack Trace (if applicable)

Root Cause

I'm running a create_agent with Gemini (thinking enabled) and a tool, then streaming the run via astream_events(version="v3"). The model's reasoning streams perfectly during the run — ChatModelStream.reasoning emits the full thinking text live.

Expected: when the agent's tool-calling step finishes, the persisted AIMessage in the final graph state preserves both the reasoning block and the tool_call in content, the same way ainvoke() does on the same model.

Actual: the persisted AIMessage has content = [{type: tool_call, ...}] — the reasoning that streamed through .reasoning is silently dropped. It's not in content, not in additional_kwargs, not in response_metadata. The next AIMessage in the run (text-only response after the tool result) is unaffected; only AIMessages that contain a tool_call lose their reasoning.

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.

Code Example

import asyncio
from langchain.agents import create_agent
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.tools import tool
from langchain_core.messages import AIMessage


@tool
def get_weather(city: str) -> str:
    """Get the weather for a city."""
    return f"Sunny in {city}"


async def main():
    llm = ChatGoogleGenerativeAI(
        model="gemini-2.5-pro",
        thinking_budget=2048,
        include_thoughts=True,
    )
    agent = create_agent(llm, [get_weather])

    run = await agent.astream_events(
        {"messages": [{"role": "user",
                       "content": "What is the weather in San Francisco?"}]},
        version="v3",
    )

    print("============ STREAM ============")
    async for chat_stream in run.messages:
        print("\n--- streamed AIMessage ---")
        print("[reasoning] ", end="", flush=True)
        async for chunk in chat_stream.reasoning:
            print(chunk, end="", flush=True)
        print("\n[text] ", end="", flush=True)
        async for chunk in chat_stream.text:
            print(chunk, end="", flush=True)
        print()

    final = await run.output()
    print("\n\n============ final state ============")
    for m in final["messages"]:
        if isinstance(m, AIMessage):
            print("\n--- AIMessage ---")
            print("content:   ", m.content)
            print("tool_calls:", m.tool_calls)


asyncio.run(main())

---

============ STREAM ============

--- streamed AIMessage #1 ---
[reasoning] **Querying the Weather**

I'm zeroing in on the task at hand. The user wants San Francisco's weather. I've pinpointed the `get_weather` tool as the solution. Now, I'm just formulating the correct tool call. Specifically, I'm setting the `city` parameter to "San Francisco" and will wrap the tool call in a `print` statement.


**Formulating the Code**

I'm solidifying the final code block. I've got the `get_weather` tool call prepared and the city parameter set. The next step is to neatly package this tool call within the `print` function. Then, I'll ensure it's accurately embedded within the `tool_code` block before submitting the response.



[text]

--- streamed AIMessage #2 ---
[reasoning]
[text] Sunny in San Francisco

--- streamed AIMessage #3 ---
[reasoning]
[text] The weather in San Francisco is sunny.



============ final state ============

--- AIMessage ---
content:    [{'type': 'tool_call', 'id': '6d665264-4fd0-46a3-889e-f04e47548ed0', 'name': 'get_weather', 'args': {'city': 'San Francisco'}}]
tool_calls: [{'name': 'get_weather', 'args': {'city': 'San Francisco'}, 'id': '6d665264-4fd0-46a3-889e-f04e47548ed0', 'type': 'tool_call'}] **<--- Missing reasoning**

--- AIMessage ---
content:    [{'type': 'text', 'text': 'The weather in San Francisco is sunny.\n', 'index': 0}]
tool_calls: []
RAW_BUFFERClick to expand / collapse

Submission checklist

  • 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

No response

Reproduction Steps / Example Code (Python)

import asyncio
from langchain.agents import create_agent
from langchain_google_genai import ChatGoogleGenerativeAI
from langchain_core.tools import tool
from langchain_core.messages import AIMessage


@tool
def get_weather(city: str) -> str:
    """Get the weather for a city."""
    return f"Sunny in {city}"


async def main():
    llm = ChatGoogleGenerativeAI(
        model="gemini-2.5-pro",
        thinking_budget=2048,
        include_thoughts=True,
    )
    agent = create_agent(llm, [get_weather])

    run = await agent.astream_events(
        {"messages": [{"role": "user",
                       "content": "What is the weather in San Francisco?"}]},
        version="v3",
    )

    print("============ STREAM ============")
    async for chat_stream in run.messages:
        print("\n--- streamed AIMessage ---")
        print("[reasoning] ", end="", flush=True)
        async for chunk in chat_stream.reasoning:
            print(chunk, end="", flush=True)
        print("\n[text] ", end="", flush=True)
        async for chunk in chat_stream.text:
            print(chunk, end="", flush=True)
        print()

    final = await run.output()
    print("\n\n============ final state ============")
    for m in final["messages"]:
        if isinstance(m, AIMessage):
            print("\n--- AIMessage ---")
            print("content:   ", m.content)
            print("tool_calls:", m.tool_calls)


asyncio.run(main())

Error Message and Stack Trace (if applicable)

============ STREAM ============

--- streamed AIMessage #1 ---
[reasoning] **Querying the Weather**

I'm zeroing in on the task at hand. The user wants San Francisco's weather. I've pinpointed the `get_weather` tool as the solution. Now, I'm just formulating the correct tool call. Specifically, I'm setting the `city` parameter to "San Francisco" and will wrap the tool call in a `print` statement.


**Formulating the Code**

I'm solidifying the final code block. I've got the `get_weather` tool call prepared and the city parameter set. The next step is to neatly package this tool call within the `print` function. Then, I'll ensure it's accurately embedded within the `tool_code` block before submitting the response.



[text]

--- streamed AIMessage #2 ---
[reasoning]
[text] Sunny in San Francisco

--- streamed AIMessage #3 ---
[reasoning]
[text] The weather in San Francisco is sunny.



============ final state ============

--- AIMessage ---
content:    [{'type': 'tool_call', 'id': '6d665264-4fd0-46a3-889e-f04e47548ed0', 'name': 'get_weather', 'args': {'city': 'San Francisco'}}]
tool_calls: [{'name': 'get_weather', 'args': {'city': 'San Francisco'}, 'id': '6d665264-4fd0-46a3-889e-f04e47548ed0', 'type': 'tool_call'}] **<--- Missing reasoning**

--- AIMessage ---
content:    [{'type': 'text', 'text': 'The weather in San Francisco is sunny.\n', 'index': 0}]
tool_calls: []

Description

I'm running a create_agent with Gemini (thinking enabled) and a tool, then streaming the run via astream_events(version="v3"). The model's reasoning streams perfectly during the run — ChatModelStream.reasoning emits the full thinking text live.

Expected: when the agent's tool-calling step finishes, the persisted AIMessage in the final graph state preserves both the reasoning block and the tool_call in content, the same way ainvoke() does on the same model.

Actual: the persisted AIMessage has content = [{type: tool_call, ...}] — the reasoning that streamed through .reasoning is silently dropped. It's not in content, not in additional_kwargs, not in response_metadata. The next AIMessage in the run (text-only response after the tool result) is unaffected; only AIMessages that contain a tool_call lose their reasoning.

System Info

OS: Linux, Python 3.12.9 langchain_core: 1.4.0 langchain: 1.3.0 langchain_google_genai: 4.2.2 langgraph: 1.2.0

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