langchain - ✅(Solved) Fix [Bug]: Missing initial <think> tag in state accumulation when using stream_mode=["messages"] with Reasoning Models (vLLM/MiniMax) [1 pull requests, 1 comments, 2 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#36214Fetched 2026-04-08 01:26:21
View on GitHub
Comments
1
Participants
2
Timeline
7
Reactions
0
Author
Participants
Timeline (top)
labeled ×4commented ×1cross-referenced ×1issue_type_added ×1

I am encountering a weird formatting issue when using reasoning models (like MiniMax 2.5) with LangGraph. When I enable stream_mode=["messages"], the opening <think> tag is completely missing from the streamed AI chunks. More severely, because LangGraph accumulates these chunks to build the final state, the final AIMessage saved in the Graph State also permanently misses the opening <think> tag, but still contains the closing </think> tag. The Phenomenon: Without stream_mode=["messages"] (e.g., using only updates or values): The Graph State works perfectly. The AIMessage in the state contains the complete paired tags: <think>...</think>. With stream_mode=["messages"]: The streamed chunks miss the opening tag. Consequently, the final accumulated message in the Graph State is corrupted, looking like this: Here is my reasoning...</think> Final Answer. It seems that merely "observing" the stream via messages mode corrupts the final accumulated message history in the state. Expected behavior The final accumulated AIMessage in the graph state should be exactly the same (containing both <think> and </think>) regardless of whether stream_mode=["messages"] is enabled or not.

Error Message

Error Message and Stack Trace (if applicable)

Root Cause

I am encountering a weird formatting issue when using reasoning models (like MiniMax 2.5) with LangGraph. When I enable stream_mode=["messages"], the opening <think> tag is completely missing from the streamed AI chunks. More severely, because LangGraph accumulates these chunks to build the final state, the final AIMessage saved in the Graph State also permanently misses the opening <think> tag, but still contains the closing </think> tag. The Phenomenon: Without stream_mode=["messages"] (e.g., using only updates or values): The Graph State works perfectly. The AIMessage in the state contains the complete paired tags: <think>...</think>. With stream_mode=["messages"]: The streamed chunks miss the opening tag. Consequently, the final accumulated message in the Graph State is corrupted, looking like this: Here is my reasoning...</think> Final Answer. It seems that merely "observing" the stream via messages mode corrupts the final accumulated message history in the state. Expected behavior The final accumulated AIMessage in the graph state should be exactly the same (containing both <think> and </think>) regardless of whether stream_mode=["messages"] is enabled or not.

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.

Other Dependencies

anthropic: 0.86.0 filetype: 1.2.0 google-genai: 1.68.0 httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.1.3 openai: 2.29.0 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pyyaml: 6.0.3 requests: 2.31.0 requests-toolbelt: 1.0.0 tenacity: 9.1.4 tiktoken: 0.7.0 typing-extensions: 4.15.0 uuid-utils: 0.14.1 wcmatch: 10.1 websockets: 16.0 xxhash: 3.6.0 zstandard: 0.25.0

PR fix notes

PR #36276: fix(openai): preserve reasoning_content in chat completions

Description (problem / solution / changelog)

Summary

  • preserve raw easoning_content on assistant messages parsed from chat-completions responses
  • preserve streamed easoning_content deltas on chat-completions chunks
  • surface preserved reasoning in the OpenAI content-block translator when no structured reasoning block is already present

Why

Some OpenAI-compatible backends stream reasoning separately via easoning_content. ChatOpenAI currently drops those chat-completions deltas, so observing stream_mode=["messages"] can lose the opening <think> portion during chunk accumulation even when the backend's non-streamed response keeps it intact.

This keeps the compatibility behavior narrow: the raw field is still not standardized, but if a backend returns it we no longer discard it during parsing or content-block translation.

Testing

  • OPENAI_API_KEY=test python -m uv run --group test pytest tests/unit_tests/chat_models/test_base.py
  • python -m uv run --group test ruff check langchain_openai/chat_models/base.py tests/unit_tests/chat_models/test_base.py ../../core/langchain_core/messages/block_translators/openai.py

Areas for review

  • the added translator fallback only inserts a reasoning block when one is not already present
  • the new preservation path is limited to raw easoning_content passthrough, not broader third-party field normalization

AI assistance

  • This PR was prepared with AI assistance. I reviewed the generated changes, validated the touched tests locally, and verified the touched lint checks locally.

Closes #36214

Changed files

  • libs/core/langchain_core/messages/block_translators/openai.py (modified, +23/-6)
  • libs/partners/openai/langchain_openai/chat_models/base.py (modified, +16/-9)
  • libs/partners/openai/tests/unit_tests/chat_models/test_base.py (modified, +72/-0)

Code Example

from langchain.agents import create_agent as _create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import TodoListMiddleware
from langgraph.graph.state import CompiledStateGraph

def create_agent() -> CompiledStateGraph:
    model = ChatOpenAI(
        model="MiniMax-M2.5",  
        api_key="sk-xxxxx", 
        base_url="https://127.0.0.1:8000/v1",
    )
    
    middleware = [
        TodoListMiddleware(),
    ]
    
    system_prompt = """You are a helpful assistant that helps users accomplish tasks using tools.
"""

    agents = _create_agent(
        model=model,
        system_prompt=system_prompt,
        middleware=middleware,
    )
    return agents

if __name__ == "__main__":
    from langchain_core.messages import HumanMessage
    state = {
        "messages": HumanMessage(content="Hello. Please write an article about artificial intelligence for me, utilizing the `write_tode` tool for planning purposes. I want to test your ability to follow instructions and your proficiency in using tools.")
    }

    agent = create_agent()
    #
    # for msg in agent.invoke(state)["messages"]:
    #     msg.pretty_print()

    for chunk in agent.stream(state, stream_mode=["messages", "updates"], version="v1"):
        # if chunk["type"] == "updates":
        print(chunk)

---
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

No response

Reproduction Steps / Example Code (Python)

from langchain.agents import create_agent as _create_agent
from langchain_openai import ChatOpenAI
from langchain.agents.middleware import TodoListMiddleware
from langgraph.graph.state import CompiledStateGraph

def create_agent() -> CompiledStateGraph:
    model = ChatOpenAI(
        model="MiniMax-M2.5",  
        api_key="sk-xxxxx", 
        base_url="https://127.0.0.1:8000/v1",
    )
    
    middleware = [
        TodoListMiddleware(),
    ]
    
    system_prompt = """You are a helpful assistant that helps users accomplish tasks using tools.
"""

    agents = _create_agent(
        model=model,
        system_prompt=system_prompt,
        middleware=middleware,
    )
    return agents

if __name__ == "__main__":
    from langchain_core.messages import HumanMessage
    state = {
        "messages": HumanMessage(content="Hello. Please write an article about artificial intelligence for me, utilizing the `write_tode` tool for planning purposes. I want to test your ability to follow instructions and your proficiency in using tools.")
    }

    agent = create_agent()
    #
    # for msg in agent.invoke(state)["messages"]:
    #     msg.pretty_print()

    for chunk in agent.stream(state, stream_mode=["messages", "updates"], version="v1"):
        # if chunk["type"] == "updates":
        print(chunk)

Error Message and Stack Trace (if applicable)

Description

I am encountering a weird formatting issue when using reasoning models (like MiniMax 2.5) with LangGraph. When I enable stream_mode=["messages"], the opening <think> tag is completely missing from the streamed AI chunks. More severely, because LangGraph accumulates these chunks to build the final state, the final AIMessage saved in the Graph State also permanently misses the opening <think> tag, but still contains the closing </think> tag. The Phenomenon: Without stream_mode=["messages"] (e.g., using only updates or values): The Graph State works perfectly. The AIMessage in the state contains the complete paired tags: <think>...</think>. With stream_mode=["messages"]: The streamed chunks miss the opening tag. Consequently, the final accumulated message in the Graph State is corrupted, looking like this: Here is my reasoning...</think> Final Answer. It seems that merely "observing" the stream via messages mode corrupts the final accumulated message history in the state. Expected behavior The final accumulated AIMessage in the graph state should be exactly the same (containing both <think> and </think>) regardless of whether stream_mode=["messages"] is enabled or not.

System Info

System Information

OS: Windows OS Version: 10.0.26100 Python Version: 3.11.15 | packaged by conda-forge | (main, Mar 5 2026, 16:36:00) [MSC v.1944 64 bit (AMD64)]

Package Information

langchain_core: 1.2.21 langchain: 1.2.13 langsmith: 0.7.22 deepagents: 0.4.12 langchain_anthropic: 1.4.0 langchain_google_genai: 4.2.1 langchain_openai: 1.1.12 langgraph_sdk: 0.3.12

Optional packages not installed

deepagents-cli

Other Dependencies

anthropic: 0.86.0 filetype: 1.2.0 google-genai: 1.68.0 httpx: 0.28.1 jsonpatch: 1.33 langgraph: 1.1.3 openai: 2.29.0 orjson: 3.11.7 packaging: 26.0 pydantic: 2.12.5 pyyaml: 6.0.3 requests: 2.31.0 requests-toolbelt: 1.0.0 tenacity: 9.1.4 tiktoken: 0.7.0 typing-extensions: 4.15.0 uuid-utils: 0.14.1 wcmatch: 10.1 websockets: 16.0 xxhash: 3.6.0 zstandard: 0.25.0

extent analysis

Fix Plan

To resolve the issue with the missing opening <think> tag in the streamed AI chunks, we need to modify the stream method in the agent class to properly handle the accumulation of chunks.

Here are the steps:

  • Modify the stream method to keep track of the opening tags.
  • Accumulate the chunks correctly to include the opening tags in the final message.

Example code:

class Agent:
    # ...

    def stream(self, state, stream_mode, version):
        # Initialize an empty list to store the accumulated chunks
        accumulated_chunks = []
        opening_tags = []

        for chunk in self._stream(state, stream_mode, version):
            # Check if the chunk contains an opening tag
            if "<think>" in chunk:
                opening_tags.append("<think>")

            # Accumulate the chunks
            accumulated_chunks.append(chunk)

            # Check if the chunk contains a closing tag
            if "</think>" in chunk:
                # If there are opening tags, remove the last one
                if opening_tags:
                    opening_tags.pop()

                # Yield the accumulated chunks
                yield {"type": "messages", "content": "".join(accumulated_chunks)}

                # Reset the accumulated chunks
                accumulated_chunks = []

        # Yield any remaining accumulated chunks
        if accumulated_chunks:
            yield {"type": "messages", "content": "".join(accumulated_chunks)}

Verification

To verify that the fix worked, you can test the stream method with the stream_mode=["messages"] and check if the final accumulated message in the graph state contains both the opening <think> and closing </think> tags.

Example code:

agent = create_agent()
state = {
    "messages": HumanMessage(content="Hello. Please write an article about artificial intelligence for me, utilizing the `write_tode` tool for planning purposes. I want to test your ability to follow instructions and your proficiency in using tools.")
}

for chunk in agent.stream(state, stream_mode=["messages"], version="v1"):
    print(chunk)

# Check the final accumulated message in the graph state
final_message = agent.invoke(state)["messages"][-1].content
print(final_message)

Extra Tips

  • Make sure to test the stream method with different stream_mode values to ensure that the fix works correctly in all cases.
  • Consider adding additional logging or debugging statements to help identify any issues that may arise during the accumulation of chunks.

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 [Bug]: Missing initial <think> tag in state accumulation when using stream_mode=["messages"] with Reasoning Models (vLLM/MiniMax) [1 pull requests, 1 comments, 2 participants]