LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 1 · Basic Introduction
1 1-1 Simple Graph
2 1-2 Chain
3 1-3 Router
4 1-4 Agent
5 1-5 Agent Memory
6 1-6 Deployment
SUM Module Summary
HomeLangGraphModule 1 · Basic Getting Started1-3 Router
📓 Notebook 📖 Explained
LANGGRAPH MODULE 1 · LESSON 3

Router: Let the LLM Decide Which Path to Take
ToolNode · tools_condition · Conditional Routing

The Chain from the previous lesson only brought tool-call requests back. In this lesson, the tools actually run.
The LLM acts as the router and decides whether to answer directly or call a tool.

1 The Problem Left from the Previous Lesson: The Tool Was Not Executed

Recall the graph from the previous lesson, Chain: START → tool_calling_llm → END. When you ask "Multiply 2 and 3", the LLM returns an AIMessage with tool_calls, but the graph goes straight to END. The tool request simply sits in State and nobody handles it.

The Problem

The Chain graph has only one node. The LLM says, "I want to call a tool," but the graph has nowhere to execute that call. The user receives a "tool-call request", not the final answer.

Router solves exactly this problem by adding two things:

2 The Core Idea of Router: The LLM as the Router

The name Router comes from the network-router metaphor: choose which path a packet should take based on conditions. Here, the LLM is the router:

Router Decision Logic

User input
tool_calling_llm
tools_condition checks: does the LLM response have tool_calls?
Has tool_calls
tools
Execute multiply(2,3) → ToolMessage
END
No tool_calls
END
Return the text answer directly
This Is a Simple Agent

Router is the simplest form of an Agent: the LLM decides on its own whether to call a tool based on the user's intent. "The LLM controls the execution flow" is the definition of an Agent. Note that at this point, after the tool finishes, the graph ends immediately; it does not send the result back to the LLM. The next lesson, Agent, will implement the full "think → tool → think" loop.

3 ToolNode: A Built-in Node for Executing Tool Calls

ToolNode is a built-in, prebuilt LangGraph node dedicated to executing tool calls requested by the LLM. You do not need to write the execution logic yourself:

from langgraph.prebuilt import ToolNode

# Define the tool function
def multiply(a: int, b: int) -> int:
    """Multiply a and b."""
    return a * b

# ToolNode receives the list of tools and automatically handles execution logic
tool_node = ToolNode([multiply])

# Register it as a graph node
builder.add_node("tools", tool_node)

What Does ToolNode Do Internally?

ToolNode takes the latest AIMessage from State, reads its tool_calls, and then:

# After ToolNode executes, messages in State become:
# [
#   HumanMessage("Multiply 2 and 3"),
#   AIMessage(tool_calls=[{name:"multiply", args:{a:2,b:3}}]),
#   ToolMessage(content="6", name="multiply", tool_call_id="call_xxx")  ← added
# ]
ToolNode Supports Parallel Execution

If the LLM requests multiple tool calls in one response (parallel tool calling), ToolNode will execute all tools in parallel to improve efficiency.

4 tools_condition: A Built-in Conditional Routing Function

tools_condition is LangGraph's built-in routing function specifically for checking whether the LLM requested a tool:

from langgraph.prebuilt import tools_condition

# A hand-written equivalent of tools_condition (to help you understand what it does):
def tools_condition(state: MessagesState):
    last_message = state["messages"][-1]  # Take the latest message
    if last_message.tool_calls:           # Is there a tool-call request?
        return "tools"                    # → Go to the tools node
    return END                            # → End

# Usage: as the routing function for conditional edges
builder.add_conditional_edges(
    "tool_calling_llm",   # Start from this node
    tools_condition,       # Use this function to decide where to go
    # Optional: explicitly specify possible routing targets (improves readability)
    # {"tools": "tools", END: END}
)

tools_condition is the production-grade version of the "routing function (decide_mood)" we learned in 1-1. It does exactly the same thing: read State and return the next node name.

LLM Response Typetools_condition ReturnsNext Step
AIMessage (plain text)"__end__"End directly and return to the user
AIMessage (with tool_calls)"tools"Go to ToolNode to execute the tool

5 Complete Router diagram: full code analysis

from langgraph.graph import StateGraph, MessagesState, START, END
from langgraph.prebuilt import ToolNode, tools_condition

# ─── 工具定义 ───
def multiply(a: int, b: int) -> int:
    """Multiply a and b."""
    return a * b

# ─── LLM 绑定工具 ───
llm_with_tools = llm.bind_tools([multiply])

# ─── LLM Node ───
def tool_calling_llm(state: MessagesState):
    return {"messages": [llm_with_tools.invoke(state["messages"])]}

# ─── 构建图 ───
builder = StateGraph(MessagesState)

builder.add_node("tool_calling_llm", tool_calling_llm)  # LLM 节点
builder.add_node("tools", ToolNode([multiply]))         # 工具执行节点

builder.add_edge(START, "tool_calling_llm")            # 入口
builder.add_conditional_edges(                          # 条件分支
    "tool_calling_llm",
    tools_condition,   # 有工具调用→tools,没有→END
)
builder.add_edge("tools", END)                          # 工具执行完→结束

graph = builder.compile()

The complete structure of the Router graph

__start__
↓ Fixed edge
tool_calling_llm
↓ tools_condition conditional edge
There are tool_calls → "tools"
tools (ToolNode)
↓ Fixed edge
__end__
None tool_calls → END
__end__

6 Detailed explanation of the two execution paths

Path A: User asks a normal question (no tools required)

messages = [HumanMessage(content="Hello, how are you?")]
result = graph.invoke({"messages": messages})

# 执行路径:
# START → tool_calling_llm(LLM 直接回复,无 tool_calls)
#       → tools_condition 返回 END
#       → 结束
#
# 最终 messages:
# [HumanMessage("Hello, how are you?"), AIMessage("Hi! I'm fine...")]

Path B: The user lets LLM do the calculations (tools required)

messages = [HumanMessage(content="What is 2 multiplied by 3?")]
result = graph.invoke({"messages": messages})

# 执行路径:
# START → tool_calling_llm(LLM 请求工具)
#       → tools_condition 返回 "tools"
#       → ToolNode 执行 multiply(2, 3) = 6
#       → END
#
# 最终 messages:
# [
#   HumanMessage("What is 2 multiplied by 3?"),
#   AIMessage(tool_calls=[{name:"multiply",args:{a:2,b:3}}]),
#   ToolMessage(content="6", name="multiply")  ← 工具执行结果
# ]
Limitations of Router: Still incomplete

Router executed the tool, but putToolMessage("6")Return directly to the user,Instead of letting LLM interpret the result and answer it in natural language. "6" is just the raw output of the tool.

A truly user-friendly experience is when LLM sees the tool results and says "2 times 3 equals 6" in natural language. This is what the Agent will do in the next lesson - pass the ToolMessage back to the LLM.

7Router vs Chain: Which step forward?

Chain (previous lecture)

  • Only one LLM node
  • The tool call request is returned as is and is not executed.
  • Graph structure: linear
  • Unable to complete tool tasks

Router (this lecture)

  • LLM Node + ToolNode
  • The tool is actually executed and the results are returned
  • Graph structure: conditional branch
  • Can complete tool tasks, but the results are no longer interpreted by LLM

Two pre-built components introduced in this lecture

componentstypeeffectEquivalent to
ToolNodePre-built NodeAutomate tool calls for LLM requestsWrite a function yourself that parses tool_calls and executes it
tools_conditionPre-built routing functionsDetermine whether there is a tool call and return the target node nameWrite your own decide_mood style conditional function
The value of pre-built components

ToolNodeandtools_conditionIt is a "standard part" provided by LangGraph to help you write less boilerplate code. But you can completely eliminate them and implement equivalent functions yourself - if you understand the 1-1 graph construction pattern, you can see clearly what these two components are doing.