背景
在前两篇教程中,我们分别学习了 LangGraph 的快速入门和 StateGraph 基础。本文将深入探讨 LangGraph 的进阶特性------工具调用(Tool Calling) 和 条件路由(Conditional Routing),通过构建一个支持 Tavily 搜索引擎的聊天机器人,掌握 ReAct 模式的完整实现。
实现原理
ReAct 模式
ReAct(Reasoning + Acting)是 LLM Agent 的核心模式:
markdown
Reasoning(推理) → Acting(行动) → Observation(观察)
│ │ │
▼ ▼ ▼
理解用户需求 调用工具获取数据 整合结果生成回答
判断需要什么信息 执行外部操作 形成完整回复
工具调用架构
scss
┌─────────────────────────────────────────────────────────┐
│ 工具调用架构 │
├─────────────────────────────────────────────────────────┤
│ 1. 工具定义 │ TavilySearchResults │
│ 2. 工具绑定 │ llm.bind_tools(tools) │
│ 3. 工具节点 │ BasicToolNode 执行工具调用 │
│ 4. 条件路由 │ route_tools 动态判断 │
│ 5. 循环执行 │ chatbot ↔ tools 形成 ReAct 循环 │
└─────────────────────────────────────────────────────────┘
条件边 vs 普通边
| 特性 | 普通边 | 条件边 |
|---|---|---|
| 定义方式 | add_edge() |
add_conditional_edges() |
| 路由逻辑 | 固定 | 动态(基于状态) |
| 返回值 | 无 | 节点名称或 END |
| 使用场景 | 确定流程 | 分支决策 |
代码实现
完整代码
python
"""
LangGraph 教程 - 为聊天机器人添加工具
本示例演示如何为 StateGraph 聊天机器人添加网页搜索工具。
官方教程地址:https://langchain-ai.github.io/langgraph/tutorials/introduction/
"""
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=UserWarning)
from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import ToolMessage
from langchain_community.tools.tavily_search import TavilySearchResults
from dotenv import load_dotenv
import json
import os
load_dotenv()
# 检查 Tavily API Key 是否设置
if not os.getenv("TAVILY_API_KEY"):
raise ValueError("TAVILY_API_KEY 未设置,请在 .env 文件中配置")
# ==================== 1. 定义状态 ====================
class State(TypedDict):
"""定义图的状态结构。"""
messages: Annotated[list, add_messages]
# ==================== 2. 定义工具节点 ====================
class BasicToolNode:
"""工具节点:运行 LLM 请求的工具。"""
def __init__(self, tools: list) -> None:
self.tools_by_name = {tool.name: tool for tool in tools}
def __call__(self, inputs: dict):
message = inputs.get("messages", [])[-1]
outputs = []
for tool_call in message.tool_calls:
tool_result = self.tools_by_name[tool_call["name"]].invoke(
tool_call["args"]
)
outputs.append(
ToolMessage(
content=json.dumps(tool_result, ensure_ascii=False),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
return {"messages": outputs}
# ==================== 3. 定义路由函数 ====================
def route_tools(state: State):
"""
条件边路由函数。
检查聊天机器人输出中是否包含 tool_calls。
"""
if isinstance(state, list):
ai_message = state[-1]
elif messages := state.get("messages", []):
ai_message = messages[-1]
else:
raise ValueError(f"No messages found in input state to tool_edge: {state}")
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools"
return END
# ==================== 4. 创建图 ====================
def create_graph():
"""创建并编译 StateGraph。"""
graph_builder = StateGraph(State)
llm = ChatOpenAI(
model="Qwen/Qwen3-Next-80B-A3B-Instruct",
openai_api_key=os.getenv("SILICONFLOW_API_KEY"),
openai_api_base="https://api.siliconflow.cn/v1",
temperature=0.7
)
# 创建 Tavily 搜索工具
tool = TavilySearchResults(max_results=2)
tools = [tool]
# 绑定工具到 LLM
llm_with_tools = llm.bind_tools(tools)
def chatbot(state: State):
"""聊天机器人节点。"""
return {"messages": [llm_with_tools.invoke(state["messages"])]}
# 添加节点
graph_builder.add_node("chatbot", chatbot)
tool_node = BasicToolNode(tools=tools)
graph_builder.add_node("tools", tool_node)
# 添加边
graph_builder.add_edge(START, "chatbot")
# 添加条件边
graph_builder.add_conditional_edges(
"chatbot",
route_tools,
{"tools": "tools", END: END}
)
# 添加边:从 tools 回到 chatbot(形成循环)
graph_builder.add_edge("tools", "chatbot")
return graph_builder.compile()
# ==================== 5. 运行 ====================
def stream_graph_updates(graph, user_input: str):
"""流式处理图更新。"""
for event in graph.stream({"messages": [{"role": "user", "content": user_input}]}):
for value in event.values():
if "messages" in value:
last_message = value["messages"][-1]
if hasattr(last_message, "content") and last_message.content:
if not isinstance(last_message, ToolMessage):
print("助手:", last_message.content)
def main():
"""主函数 - 运行交互式聊天机器人。"""
print("🤖 LangGraph 工具增强聊天机器人已启动!")
print("=" * 50)
print("提示:输入 'quit'、'exit' 或 'q' 退出对话\n")
graph = create_graph()
while True:
try:
user_input = input("用户: ")
if user_input.lower() in ["quit", "exit", "q"]:
print("\n👋 再见!")
break
stream_graph_updates(graph, user_input)
print()
except KeyboardInterrupt:
print("\n\n👋 再见!")
break
except Exception as e:
print(f"发生错误: {e}")
break
if __name__ == "__main__":
main()
核心代码解析
工具绑定机制
python
# 创建工具
tool = TavilySearchResults(max_results=2)
tools = [tool]
# 绑定工具到 LLM
llm_with_tools = llm.bind_tools(tools)
bind_tools() 将工具描述注入 LLM 系统提示,使 LLM 能够:
- 理解每个工具的功能和参数
- 判断何时需要调用工具
- 生成符合规范的
tool_calls
工具节点实现
python
class BasicToolNode:
def __init__(self, tools: list) -> None:
self.tools_by_name = {tool.name: tool for tool in tools}
def __call__(self, inputs: dict):
message = inputs.get("messages", [])[-1]
outputs = []
for tool_call in message.tool_calls:
# 执行工具调用
tool_result = self.tools_by_name[tool_call["name"]].invoke(
tool_call["args"]
)
# 封装为 ToolMessage
outputs.append(
ToolMessage(
content=json.dumps(tool_result),
name=tool_call["name"],
tool_call_id=tool_call["id"],
)
)
return {"messages": outputs}
条件路由函数
python
def route_tools(state: State):
"""
条件边路由函数。
返回 "tools" 或 END,决定下一步执行路径。
"""
ai_message = state.get("messages", [])[-1]
if hasattr(ai_message, "tool_calls") and len(ai_message.tool_calls) > 0:
return "tools" # 有工具调用
return END # 无工具调用,结束
图结构定义
python
# 添加节点
graph_builder.add_node("chatbot", chatbot)
graph_builder.add_node("tools", tool_node)
# 普通边
graph_builder.add_edge(START, "chatbot")
# 条件边
graph_builder.add_conditional_edges(
"chatbot",
route_tools,
{"tools": "tools", END: END}
)
# 循环边
graph_builder.add_edge("tools", "chatbot")
图结构:
sql
START ──▶ chatbot ──┬──▶ END
│
└──▶ tools ──▶ chatbot(循环)
运行效果
执行
bash
pip install langgraph langchain langchain-openai langchain-community pydantic python-dotenv typing-extensions
python 03添加工具.py
输出

架构图解
状态流转
sql
┌─────────┐ ┌─────────────┐ ┌─────────┐
│ START │────▶│ chatbot │────▶│ END │
└─────────┘ └──────┬──────┘ └─────────┘
│
┌────────┴────────┐
│ 有 tool_calls? │
└────────┬────────┘
│
┌────────┴────────┐
▼ ▼
┌─────────┐ ┌─────────┐
│ tools │──────▶│ chatbot│
└─────────┘ └─────────┘
执行时序
sql
User Input
│
▼
┌─────────────────┐
│ chatbot 节点 │
│ LLM 推理 │
│ 输出 tool_calls│
└─────────────────┘
│
▼
┌─────────────────┐
│ route_tools │
│ 返回 "tools" │
└─────────────────┘
│
▼
┌─────────────────┐
│ tools 节点 │
│ 执行搜索 │
│ 输出 ToolMessage│
└─────────────────┘
│
▼
┌─────────────────┐
│ chatbot 节点 │
│ 接收工具结果 │
│ 生成最终回答 │
└─────────────────┘
│
▼
┌─────────────────┐
│ route_tools │
│ 返回 END │
└─────────────────┘
│
▼
Output
总结
本文通过实战代码展示了 LangGraph 工具调用的核心机制:
- 工具绑定 :
bind_tools()让 LLM 感知可用工具 - 工具节点 :
BasicToolNode执行实际的工具调用 - 条件路由 :
add_conditional_edges()实现动态分支 - ReAct 循环:chatbot ↔ tools 形成推理-行动闭环
- 消息类型 :
ToolMessage传递工具执行结果
进阶方向
- 多工具支持:计算器、数据库、API 等
- 工具选择策略:让 LLM 智能选择工具
- 错误处理:工具调用失败的容错机制
- 人机协作:Human-in-the-loop 节点
参考
📌 本文首发于掘金,作者:AI探索者 🔗 转载请注明出处
💡 如果觉得有帮助,欢迎点赞、评论、收藏! 🎯 你的支持是我持续创作的动力!