mirror of
https://github.com/langchain-ai/langgraph.git
synced 2026-08-23 08:02:23 +02:00
## Summary When `ToolNode` receives a bare `[tool_call]` list via the Send API (the dispatch shape `create_agent` will use once langchain-ai/langchain#36960 lands), hydrate `ToolRuntime.state` from the current channel values instead of requiring the dispatcher to inline the full agent state dict into every `Send.arg`. Motivation: the paired langchain PR drops the `ToolCallWithContext` wrapper from `create_agent`'s tool dispatch, which eliminates an O(N²) storage term on `__pregel_tasks` checkpoint writes. Without this companion change there would be no path for the tool node to see the graph state. ## What changed - `libs/prebuilt/langgraph/prebuilt/tool_node.py` — `_extract_state` grows a third branch for list-form input. When the input is a list whose last entry is a `ToolCall` dict, read the current channel values via `CONFIG_KEY_READ` and return them as the state dict. The full new logic is four lines inline in `_extract_state`: ```python read = config.get(CONF, {}).get(CONFIG_KEY_READ) if read is None: return {} # Pregel installs CONFIG_KEY_READ as # `functools.partial(local_read, scratchpad, channels, managed, task)`. channels = read.args[1] return cast("dict[str, Any]", read(list(channels), False)) ``` - No changes to the pregel read machinery (`local_read`, `ChannelRead`). - Only channel values are read; managed values have their own injection path (`ToolRuntime.context`, `InjectedContext`) and were never in the pre-fix inlined state dict, so we don't add them here. - Falls back to `{}` when invoked outside a Pregel context (e.g. direct `ToolNode(...).invoke([tool_call])` from a test harness), which preserves existing `ToolNode` direct-invocation test behavior. - `libs/prebuilt/tests/test_on_tool_call.py` — two new tests covering the list-form hydration path (sync + async). They build a `functools.partial` that matches Pregel's real `CONFIG_KEY_READ` shape and assert `ToolRuntime.state` reflects the current channel values. ## Why it's safe - **Same snapshot semantics as before.** `Send` is emitted at end-of-super-step-N; consumed at start-of-super-step-N+1. Channels at that point reflect every write from super-step N (including the new AIMessage the tool calls originated from). Parallel tool tasks in the tools super-step all read the same values since sibling writes don't land until end-of-super-step. - **Legacy `ToolCallWithContext` path preserved.** External dispatchers that still inline state continue to work unchanged — `_extract_state` checks that branch first. ## Test plan - [x] `make test` in `libs/prebuilt` — **204 pass** - [x] Two new hydration tests (sync + async) green - [x] `make format` / `make lint` / `mypy` clean --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LangGraph Prebuilt
This library defines high-level APIs for creating and executing LangGraph agents and tools.
Important
This library is meant to be bundled with
langgraph, don't install it directly
Agents
langgraph-prebuilt provides an implementation of a tool-calling ReAct-style agent - create_react_agent:
pip install langchain-anthropic
from langchain_anthropic import ChatAnthropic
from langgraph.prebuilt import create_react_agent
# Define the tools for the agent to use
def search(query: str):
"""Call to surf the web."""
# This is a placeholder, but don't tell the LLM that...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tools = [search]
model = ChatAnthropic(model="claude-3-7-sonnet-latest")
app = create_react_agent(model, tools)
# run the agent
app.invoke(
{"messages": [{"role": "user", "content": "what is the weather in sf"}]},
)
Tools
ToolNode
langgraph-prebuilt provides an implementation of a node that executes tool calls - ToolNode:
from langgraph.prebuilt import ToolNode
from langchain_core.messages import AIMessage
def search(query: str):
"""Call to surf the web."""
# This is a placeholder, but don't tell the LLM that...
if "sf" in query.lower() or "san francisco" in query.lower():
return "It's 60 degrees and foggy."
return "It's 90 degrees and sunny."
tool_node = ToolNode([search])
tool_calls = [{"name": "search", "args": {"query": "what is the weather in sf"}, "id": "1"}]
ai_message = AIMessage(content="", tool_calls=tool_calls)
# execute tool call
tool_node.invoke({"messages": [ai_message]})
ValidationNode
langgraph-prebuilt provides an implementation of a node that validates tool calls against a pydantic schema - ValidationNode:
from pydantic import BaseModel, field_validator
from langgraph.prebuilt import ValidationNode
from langchain_core.messages import AIMessage
class SelectNumber(BaseModel):
a: int
@field_validator("a")
def a_must_be_meaningful(cls, v):
if v != 37:
raise ValueError("Only 37 is allowed")
return v
validation_node = ValidationNode([SelectNumber])
validation_node.invoke({
"messages": [AIMessage("", tool_calls=[{"name": "SelectNumber", "args": {"a": 42}, "id": "1"}])]
})
Agent Inbox
The library contains schemas for using the Agent Inbox with LangGraph agents. Learn more about how to use Agent Inbox here.
from langgraph.types import interrupt
from langgraph.prebuilt.interrupt import HumanInterrupt, HumanResponse
def my_graph_function():
# Extract the last tool call from the `messages` field in the state
tool_call = state["messages"][-1].tool_calls[0]
# Create an interrupt
request: HumanInterrupt = {
"action_request": {
"action": tool_call['name'],
"args": tool_call['args']
},
"config": {
"allow_ignore": True,
"allow_respond": True,
"allow_edit": False,
"allow_accept": False
},
"description": _generate_email_markdown(state) # Generate a detailed markdown description.
}
# Send the interrupt request inside a list, and extract the first response
response = interrupt([request])[0]
if response['type'] == "response":
# Do something with the response
...