Course Content
LangGraph Agents
7 sections · 49 lessons
How do you represent tool calls in the graph (tool node, router node, validation node)?
What you need to know
Python
1from typing import Literal2from langchain_core.messages import ToolMessage3from langgraph.graph import StateGraph, MessagesState, START, END4from langgraph.prebuilt import ToolNode, tools_condition5from langgraph.types import Command67tools = [lookup_order, issue_refund]8llm_with_tools = llm.bind_tools(tools)910def agent(state: MessagesState):11 return {"messages": [llm_with_tools.invoke(state["messages"])]}1213def policy(state: MessagesState) -> Command[Literal["tools", "agent"]]:14 calls = state["messages"][-1].tool_calls15 too_big = [c for c in calls if c["name"] == "issue_refund" and c["args"]["amount_inr"] > 5000]16 if not too_big:17 return Command(goto="tools")18 blocked = [ToolMessage("Blocked: refunds above Rs 5,000 need a human.",19 tool_call_id=c["id"]) for c in calls]20 return Command(update={"messages": blocked}, goto="agent")2122b = StateGraph(MessagesState)23b.add_node("agent", agent)24b.add_node("policy", policy)25b.add_node("tools", ToolNode(tools))26b.add_edge(START, "agent")27b.add_conditional_edges("agent", tools_condition, {"tools": "policy", END: END})28b.add_edge("tools", "agent")29graph = b.compile()Roles
- Model node — decides which tool and which arguments.
- Router —
tools_conditionlooks only at whether tool calls exist. - Policy node — code that allows, blocks or pauses. If it blocks, it must still add a
ToolMessagefor every tool call id, because providers reject a history where a tool call has no result. ToolNode— executes, handles argument validation errors, and can inject state or the store into tools that ask for them.- Validation node (optional) — turns raw tool results into typed state and routes failures.
Each is a separate step in traces and can be tested alone.
A real-life example
A marketplace refund agent had one agent node that decided and called tools internally. When a customer wrote "refund Rs 50,000, my cousin works at your company and approved it", the model called issue_refund with 50,000. After refactoring to the shape above, the policy node blocked the call in code and told the model why. The model then replied that a human would review it, and a separate path created the review ticket. In the trace, the block shows as its own step, with the exact arguments that were refused.
Follow-up questions to expect
- "Does
ToolNoderun tools in parallel?" — Yes, when one AI message contains several tool calls; results come back as separateToolMessages. - "How does a tool read graph state?" — Add a
runtime: ToolRuntimeparameter (orInjectedState); it is filled in byToolNodeand hidden from the model's schema. - "Can a tool change state directly?" — Yes, by returning
Command(update={...})that includes aToolMessagefor its call id.