Checked other resources
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_id in the streaming chunk metadata
- Provide a mapping from
namespace to tool_call_id accessible during streaming
- Use
tool_call_id as 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
Checked other resources
Example Code
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'snamespacewith the originaltool_call_idfrom the parent graph.Timeline of the issue:
('tools:uuid',)('tools:uuid', '1')()tool_call_A()tool_call_BWhy 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:
When we receive a chunk with
namespace: ('tools:uuid', '1'), we cannot determine whether it belongs totool_call_Aortool_call_B.Expected behavior:
One of the following:
tool_call_idin the streaming chunk metadatanamespacetotool_call_idaccessible during streamingtool_call_idas part of the namespace (e.g.,('tools:tool_call_A',)instead of('tools:uuid',))System Info
System Information
Package Information