LangChain LangGraph
Module 1 Module 2 Module 3 Module 4 Module 5 Module 6
Module 1 · 基础入门
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 · 基础入门1-3 Router
📓 Notebook 📖 Explained
LANGGRAPH MODULE 1 · LESSON 3

Router:让 LLM 决定走哪条路
ToolNode · tools_condition · 条件路由

上一讲的 Chain 只会把工具调用请求"带回来",这一讲让工具真正被执行。
LLM 扮演路由器,决定是直接回答还是调用工具。

1 上一讲遗留的问题:工具没有被执行

回顾上一讲(Chain)的图:START → tool_calling_llm → END。当你问"Multiply 2 and 3",LLM 返回了一个带 tool_calls 的 AIMessage,但图直接走到 END 了——工具请求就躺在 State 里没人理。

问题所在

Chain 图只有一个节点。LLM 说"我要调用工具",但图没有地方去执行这个调用。用户得到的是一个"工具调用请求",而不是最终答案。

Router 要解决的就是这个问题,需要新增两个东西:

2 Router 的核心思想:LLM 作为路由器

Router 这个名字来自网络路由器的比喻——根据条件决定数据包走哪条路。在这里,LLM 就是路由器

Router 的决策逻辑

用户输入
tool_calling_llm
tools_condition 判断:LLM 的回复有没有 tool_calls?
有 tool_calls
tools
执行 multiply(2,3) → ToolMessage
END
没有 tool_calls
END
直接返回文字答案
这就是一个简单的 Agent

Router 是最简单的 Agent 形态:LLM 根据用户意图,自主决定是否调用工具。"LLM 控制执行流程"——这就是 Agent 的定义。注意此时工具执行完毕后直接结束,不会把结果再传回给 LLM。下一讲(Agent)才会实现完整的"思考→工具→思考"循环。

3 ToolNode:执行工具调用的内置节点

ToolNode 是 LangGraph 内置的预构建节点,专门用来执行 LLM 请求的工具调用。你不需要自己写执行逻辑:

from langgraph.prebuilt import ToolNode

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

# ToolNode 接收工具列表,自动处理执行逻辑
tool_node = ToolNode([multiply])

# 注册为图的节点
builder.add_node("tools", tool_node)

ToolNode 在内部做了什么?

ToolNode 从 State 里取出最新的 AIMessage,读取其中的 tool_calls,然后:

# ToolNode 执行后,State 里的 messages 变成:
# [
#   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")  ← 新增
# ]
ToolNode 支持并行执行

如果 LLM 在一次回复里请求了多个工具调用(parallel tool calling),ToolNode 会并行执行所有工具,提高效率。

4 tools_condition:内置的条件路由函数

tools_condition 是 LangGraph 内置的路由函数,专门用于判断"LLM 有没有请求工具":

from langgraph.prebuilt import tools_condition

# tools_condition 的等效手写版本(帮助你理解它做了什么):
def tools_condition(state: MessagesState):
    last_message = state["messages"][-1]  # 取最新的消息
    if last_message.tool_calls:           # 有工具调用请求?
        return "tools"                    # → 去 tools 节点
    return END                            # → 结束

# 用法:作为条件边的路由函数
builder.add_conditional_edges(
    "tool_calling_llm",   # 从这个节点出发
    tools_condition,       # 用这个函数决定去哪
    # 可选:明确指定可能的路由目标(提高可读性)
    # {"tools": "tools", END: END}
)

tools_condition 就是我们在 1-1 里学到的"路由函数(decide_mood)"的生产级版本。它做的事情完全一样:读 State,返回下一个节点名。

LLM 回复类型tools_condition 返回下一步
AIMessage(纯文字)"__end__"直接结束,返回给用户
AIMessage(含 tool_calls)"tools"去 ToolNode 执行工具

5 完整 Router 图:代码全解析

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()

Router 图的完整结构

__start__
↓ 固定边
tool_calling_llm
↓ tools_condition 条件边
有 tool_calls → "tools"
tools (ToolNode)
↓ 固定边
__end__
无 tool_calls → END
__end__

6 两种执行路径详解

路径 A:用户问普通问题(不需要工具)

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...")]

路径 B:用户让 LLM 做计算(需要工具)

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")  ← 工具执行结果
# ]
Router 的局限:仍然不完整

Router 执行了工具,但把 ToolMessage("6") 直接返回给用户,而不是再让 LLM 解读这个结果然后用自然语言回答。"6" 只是工具的原始输出。

真正的用户友好体验是:LLM 看到工具结果后,用自然语言说"2 乘以 3 等于 6"。这就是下一讲 Agent 要做的事——把 ToolMessage 再传回给 LLM。

7 Router vs Chain:前进了哪一步?

Chain(上一讲)

  • 只有一个 LLM 节点
  • 工具调用请求原样返回,不执行
  • 图结构:线性
  • 不能完成工具任务

Router(这一讲)

  • LLM 节点 + ToolNode
  • 工具真正被执行,返回结果
  • 图结构:条件分支
  • 能完成工具任务,但结果不再经过 LLM 解读

这一讲引入的两个预构建组件

组件类型作用等价于
ToolNode预构建 Node自动执行 LLM 请求的工具调用自己写一个解析 tool_calls 并执行的函数
tools_condition预构建路由函数判断是否有工具调用,返回目标节点名自己写 decide_mood 风格的条件函数
预构建组件的价值

ToolNodetools_condition 是 LangGraph 提供的"标准零件",帮你少写样板代码。但你完全可以不用它们,自己实现等价功能——理解了 1-1 的图构建模式,就能看透这两个组件在做什么。