-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Open
Labels
bugSomething isn't workingSomething isn't workingpendingawaiting review/confirmation by maintainerawaiting review/confirmation by maintainer
Description
Checked other resources
- This is a bug, not a usage question. For questions, please use the LangChain Forum (https://forum.langchain.com/).
- I added a clear and detailed title that summarizes the issue.
- I read what a minimal reproducible example is (https://stackoverflow.com/help/minimal-reproducible-example).
- I included a self-contained, minimal example that demonstrates the issue INCLUDING all the relevant imports. The code run AS IS to reproduce the issue.
Example Code
from typing import Annotated, TypedDict
from langchain_core.messages import AIMessage, HumanMessage
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import ToolNode
class State(TypedDict):
messages: Annotated[list, add_messages]
# Create two simple subgraphs
def create_subgraph(name: str):
def node(state: State):
return {"messages": [AIMessage(content=f"Response from {name}")]}
builder = StateGraph(State)
builder.add_node("respond", node)
builder.add_edge(START, "respond")
builder.add_edge("respond", END)
return builder.compile()
subgraph_a = create_subgraph("subgraph_a")
subgraph_b = create_subgraph("subgraph_b")
@tool
def call_subgraph_a(query: str) -> str:
"""Call subgraph A"""
result = subgraph_a.invoke({"messages": [HumanMessage(content=query)]})
return result["messages"][-1].content
@tool
def call_subgraph_b(query: str) -> str:
"""Call subgraph B"""
result = subgraph_b.invoke({"messages": [HumanMessage(content=query)]})
return result["messages"][-1].content
def model_node(state: State):
# Model returns two parallel tool calls
return {
"messages": [
AIMessage(
content="",
tool_calls=[
{"id": "tool_call_A", "name": "call_subgraph_a", "args": {"query": "hello"}},
{"id": "tool_call_B", "name": "call_subgraph_b", "args": {"query": "world"}},
],
)
]
}
builder = StateGraph(State)
builder.add_node("model", model_node)
builder.add_node("tools", ToolNode([call_subgraph_a, call_subgraph_b]))
builder.add_edge(START, "model")
builder.add_edge("model", "tools")
builder.add_edge("tools", END)
graph = builder.compile()
async def main():
async for chunk in graph.astream(
{"messages": [HumanMessage(content="Call both subgraphs")]},
stream_mode=["updates", "messages"],
subgraphs=True,
):
namespace, event_type, payload = chunk
# Try to extract tool_call_id from payload
tool_call_id = "?"
if event_type == "messages" and isinstance(payload, tuple):
msg = payload[0]
if hasattr(msg, 'tool_call_id'):
tool_call_id = msg.tool_call_id
print(f"namespace: {namespace}, tool_call_id: {tool_call_id}")
import asyncio
asyncio.run(main())
----------
Output:
namespace: ()
event_type: messages
tool_call_id: ?
---
namespace: ()
event_type: updates
tool_call_id: ?
---
namespace: ('tools:1032c8d0-64e7-7979-524c-31671560f76a',)
event_type: messages
tool_call_id: ?
---
namespace: ('tools:1032c8d0-64e7-7979-524c-31671560f76a', '1')
event_type: messages
tool_call_id: ?
---
namespace: ('tools:1032c8d0-64e7-7979-524c-31671560f76a',)
event_type: updates
tool_call_id: ?
---
namespace: ('tools:1032c8d0-64e7-7979-524c-31671560f76a', '1')
event_type: updates
tool_call_id: ?
---
namespace: ()
event_type: messages
tool_call_id: tool_call_A
---
namespace: ()
event_type: messages
tool_call_id: tool_call_B
---
namespace: ()
event_type: updates
tool_call_id: ?
---Error Message and Stack Trace (if applicable)
Description
Problem:
When streaming from a graph with subgraphs=True, there is no way to correlate the subgraph's namespace with the original tool_call_id from the parent graph.
Timeline of the issue:
| Stage | namespace | tool_call_id | Issue |
|---|---|---|---|
| Subgraph streaming | ('tools:uuid',) |
❌ Unknown | Need correlation but cannot get it |
| Subgraph streaming | ('tools:uuid', '1') |
❌ Unknown | Need correlation but cannot get it |
| ToolMessage returned | () |
✅ tool_call_A |
Too late, streaming already ended |
| ToolMessage returned | () |
✅ tool_call_B |
Too late, streaming already ended |
Why this matters:
In a streaming UI, when displaying parallel tool executions, we need to show each subgraph's streaming output under its corresponding tool call:
┌─────────────────────────────────┐
│ [tool_call_A] call_subgraph_a │
│ └─ streaming... │ ← Which namespace does this belong to?
├─────────────────────────────────┤
│ [tool_call_B] call_subgraph_b │
│ └─ streaming... │ ← Which namespace does this belong to?
└─────────────────────────────────┘
When we receive a chunk with namespace: ('tools:uuid', '1'), we cannot determine whether it belongs to tool_call_A or tool_call_B.
Expected behavior:
One of the following:
- Include
tool_call_idin the streaming chunk metadata - Provide a mapping from
namespacetotool_call_idaccessible during streaming - Use
tool_call_idas part of the namespace (e.g.,('tools:tool_call_A',)instead of('tools:uuid',))
System Info
System Information
OS: MacOS M4
OS Version: MacOS 15.7.3
Python Version: 3.13.9
Package Information
langgraph: 1.0.5
langchain-core: 1.2.7
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
bugSomething isn't workingSomething isn't workingpendingawaiting review/confirmation by maintainerawaiting review/confirmation by maintainer