LangGraph Agents

Course Content

LangGraph Agents

7 sections · 49 lessons

How do you represent tool calls in the graph (tool node, router node, validation node)?


The tool loop with a policy stepagenttools_conditionpolicyToolNodeblocks over Rs 5,000oneToolMessage per callA blocked call still gets a ToolMessage, or the next model call fails.
Putting the refund limit in its own node made the block a visible step in the trace, with the exact arguments refused.

What you need to know

Python
from typing import Literalfrom langchain_core.messages import ToolMessagefrom langgraph.graph import StateGraph, MessagesState, START, ENDfrom langgraph.prebuilt import ToolNode, tools_conditionfrom langgraph.types import Commandtools = [lookup_order, issue_refund]llm_with_tools = llm.bind_tools(tools)def agent(state: MessagesState):    return {"messages": [llm_with_tools.invoke(state["messages"])]}def policy(state: MessagesState) -> Command[Literal["tools", "agent"]]:    calls = state["messages"][-1].tool_calls    too_big = [c for c in calls if c["name"] == "issue_refund" and c["args"]["amount_inr"] > 5000]    if not too_big:        return Command(goto="tools")    blocked = [ToolMessage("Blocked: refunds above Rs 5,000 need a human.",                           tool_call_id=c["id"]) for c in calls]    return Command(update={"messages": blocked}, goto="agent")b = StateGraph(MessagesState)b.add_node("agent", agent)b.add_node("policy", policy)b.add_node("tools", ToolNode(tools))b.add_edge(START, "agent")b.add_conditional_edges("agent", tools_condition, {"tools": "policy", END: END})b.add_edge("tools", "agent")graph = b.compile()

Roles

  • Model node — decides which tool and which arguments.
  • Router — tools_condition looks only at whether tool calls exist.
  • Policy node — code that allows, blocks or pauses. If it blocks, it must still add a ToolMessage for 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 ToolNode run tools in parallel?" — Yes, when one AI message contains several tool calls; results come back as separate ToolMessages.
  • "How does a tool read graph state?" — Add a runtime: ToolRuntime parameter (or InjectedState); it is filled in by ToolNode and hidden from the model's schema.
  • "Can a tool change state directly?" — Yes, by returning Command(update={...}) that includes a ToolMessage for its call id.