导语:在上一讲中,我们理解了 LangGraph 的革命性思想——用“图”来编排 Agent。理论总是让人兴奋,但真正的掌握源于实践。本篇文章将是一份“保姆级”的教程,我们将暂时抛开复杂的理论,从零开始,手把手、一步步地带你构建一个功能完整、但逻辑清晰的 LangGraph 应用。我们将构建一个“智能工具助手”,它能根据你的问题,从多个可用工具中(例如,网页搜索、计算器)智能地选择一个来执行。跟随本教程,你将获得第一次完整的、端到端的 LangGraph 开发体验,为你后续构建更复杂的系统奠定最坚实的基础。
目录
- 项目目标:构建一个“智能工具助手” Agent
- 功能需求:能够使用网页搜索,也能进行数学计算
- 技术选型:LangGraph + Tavily Search API + LangChain
- 步骤一:环境准备与工具定义
- 安装必要的库:
langgraph,langchain-openai,tavily-python - 获取 API Keys:OpenAI/DeepSeek API Key 和 Tavily API Key
- 定义我们的工具集:
TavilySearchResults和一个自定义的python_calculator
- 安装必要的库:
- 步骤二:定义图的状态(State)
- 使用
TypedDict创建AgentState - 状态中需要包含哪些关键信息?(
messages,sender, etc.)
- 使用
- 步骤三:创建图的“节点”(Nodes)
- 设计节点
call_model:作为 Agent 的“大脑”,负责决策 - 设计节点
call_tool:作为 Agent 的“双手”,负责执行工具 - 代码实现:编写每个节点的具体函数
- 设计节点
- 步骤四:定义图的“边”(Edges),连接工作流
- 设计入口:从哪里开始?
- 设计条件边
should_continue:如何根据模型输出来决定下一步走向? - 连接所有节点:
add_node,set_entry_point,add_conditional_edges,add_edge
- 步骤五:编译并运行你的图
app = workflow.compile():从图定义到可执行应用- 传入输入,并使用
.stream()进行可视化调试 - 观察 Agent 如何在搜索和计算之间做出正确选择
- 代码复盘与关键点解析
Annotated[List[BaseMessage], operator.add]的含义是什么?bind_tools的作用是什么?- 条件边
should_continue是如何实现路由的?
- 总结:恭喜!你已掌握构建 Agent 工作流的基本功
1. 项目目标:构建一个“智能工具助手” Agent
我们今天的目标是构建一个 Agent,它不仅仅能调用一个工具,而是能从多个工具中,根据用户的问题,智能地选择一个合适的工具来使用。
功能需求:
- 当用户提出事实性、需要网络查询的问题时(例如“马斯克最近在忙啥?”),Agent 应该调用网页搜索工具。
- 当用户提出需要数学计算的问题时(例如“34 * 5.67 等于多少?”),Agent 应该调用计算器工具。
- 如果用户的问题不需要工具,Agent 应该能直接回答。
技术选型:
- 图编排:
langgraph - LLM:
langchain_openai(可接入 OpenAI GPT 系列或 DeepSeek 等兼容模型) - 网页搜索:
tavily-python(Tavily 是一个专为 LLM 设计的搜索引擎,效果很好,提供免费 API) - 计算器:我们将自定义一个简单的 Python 函数作为计算器工具。
2. 步骤一:环境准备与工具定义
安装必要的库
打开你的终端,运行以下命令:
pip install langgraph langchain_openai tavily-python
获取 API Keys
-
LLM API Key:你需要一个 OpenAI 或 DeepSeek 的 API Key。在终端中设置环境变量:
# 如果使用 DeepSeek export OPENAI_API_KEY="YOUR_DEEPSEEK_API_KEY" export OPENAI_API_BASE="https://api.deepseek.com/v1" # 如果使用 OpenAI # export OPENAI_API_KEY="YOUR_OPENAI_API_KEY" -
Tavily API Key:
- 访问 Tavily AI 官网 并注册一个账户。
- 在你的账户仪表盘中,找到你的 API Key。
- 在终端中设置环境变量:
export TAVILY_API_KEY="YOUR_TAVILY_API_KEY"
定义我们的工具集
现在,我们来创建 Agent 可以使用的“双手”。
# multi_tool_agent.py
from langchain_community.tools.tavily_search import TavilySearchResults
from langchain_core.tools import tool
# 1. Tavily 搜索工具
# TavilySearchResults 是一个预置好的工具类,可以直接实例化
# description 会被自动填充,你也可以自定义
search_tool = TavilySearchResults(max_results=2)
# 2. 自定义计算器工具
@tool
def python_calculator(code: str):
"""
A Python REPL tool that can execute simple Python code for calculations.
Use this for any math questions. The input must be a valid Python expression.
Example: `2 * 3`, `5**4`, `10 / 2`.
"""
try:
# 使用 exec 可能有安全风险,这里简化处理,生产环境需要沙箱
result = eval(code)
return result
except Exception as e:
return f"Error: {str(e)}"
# 将所有工具放入一个列表
tools = [search_tool, python_calculator]
我们现在拥有了一个工具列表 tools,里面包含了两个功能截然不同的工具。
3. 步骤二:定义图的状态(State)
State 是 LangGraph 的核心,它像一个在流水线上流转的“篮子”,每个工位(节点)都可以往里面添加或读取东西。我们的“篮子”需要能装下对话的全部历史记录。
# multi_tool_agent.py (续)
from typing import TypedDict, Annotated, List
from langchain_core.messages import BaseMessage
import operator
# `Annotated` 和 `operator.add` 是一个特殊的组合
# 它告诉 LangGraph,当多个节点都返回 'messages' 时
# 不要用新的覆盖旧的,而是将新旧消息列表“相加”
class AgentState(TypedDict):
messages: Annotated[List[BaseMessage], operator.add]
我们定义了一个名为 AgentState 的 TypedDict。它只有一个字段 messages。Annotated 的用法我们会在最后的“代码复盘”中详细解释,现在你只需要知道,这是一种让消息历史可以被累加的魔法。
4. 步骤三:创建图的“节点”(Nodes)
我们的 Agent 工作流需要两个核心的“工位”:
agent节点:负责调用 LLM 进行思考和决策。tool_executor节点:负责接收 LLM 的指令,并执行相应的工具。
让我们来实现这两个节点函数。
# multi_tool_agent.py (续)
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import ToolNode
# --- 1. 初始化 LLM 和工具执行器 ---
llm = ChatOpenAI(model="deepseek-chat") # 或者 "gpt-4-turbo"
# 将我们的工具列表绑定给 LLM,这样 LLM 才知道它有哪些工具可用
llm_with_tools = llm.bind_tools(tools)
# ToolNode 是 LangGraph 提供的一个预置好的节点,专门用来执行工具
# 它会自动解析 tool_calls,调用相应的工具,并返回 ToolMessage
tool_executor_node = ToolNode(tools)
# --- 2. 定义我们的节点函数 ---
# a. Agent 节点:负责调用 LLM
def agent_node(state: AgentState):
"""
The primary node that drives the agent's decision-making process.
It takes the current state (conversation history) and calls the LLM.
"""
print("--- Calling Agent Node (LLM) ---")
response = llm_with_tools.invoke(state['messages'])
# 返回一个字典,其 key 必须是 AgentState 中定义的字段
return {"messages": [response]}
# b. 工具执行节点
# 我们直接使用预置的 ToolNode,无需自己编写函数
# def tool_node(state: AgentState):
# ... (ToolNode 替我们完成了这部分工作)
这里我们做了几件重要的事情:
- 通过
llm.bind_tools(tools),我们将工具的 JSON Schema 信息“注入”到了 LLM 的每一次调用中。这样,LLM 在思考时,就会知道search_tool和python_calculator的存在和用法。 - 我们直接使用了
langgraph.prebuilt.ToolNode。这是一个非常有用的预置节点,它帮我们完成了“解析tool_calls-> 循环执行工具 -> 构造ToolMessage”这一套标准流程,大大简化了我们的代码。 - 我们编写了
agent_node函数,它的职责非常纯粹:就是调用 LLM。
5. 步骤四:定义图的“边”(Edges),连接工作流
现在我们有了“篮子”(State)和“工位”(Nodes),是时候用“传送带”(Edges)把它们连接起来,形成一条完整的流水线了。
# multi_tool_agent.py (续)
from langgraph.graph import StatefulGraph, END
# --- 1. 定义条件边函数 ---
def should_continue(state: AgentState):
"""
A router function that determines the next step based on the LLM's output.
"""
print("--- Checking for Tool Calls ---")
last_message = state['messages'][-1]
# 如果没有工具调用,说明 LLM 已经给出了最终答案,结束流程
if not last_message.tool_calls:
print("No tool calls. Ending.")
return "end"
# 如果有工具调用,继续执行工具
else:
print("Tool calls found. Continuing.")
return "continue"
# --- 2. 组装图 ---
workflow = StatefulGraph(AgentState)
# 添加节点
workflow.add_node("agent", agent_node)
workflow.add_node("tool_executor", tool_executor_node)
# 设置入口点
workflow.set_entry_point("agent")
# 添加条件边
workflow.add_conditional_edges(
# 决策的起点是 'agent' 节点
"agent",
# 决策的函数是 should_continue
should_continue,
# 决策的路径映射
{
"continue": "tool_executor", # 如果返回 "continue",则走向 'tool_executor' 节点
"end": END, # 如果返回 "end",则结束 (END 是特殊标识)
},
)
# 添加常规边
# 当 'tool_executor' 节点执行完毕后,工作流总是应该回到 'agent' 节点
# 让 Agent 基于工具的执行结果,进行下一步的思考
workflow.add_edge("tool_executor", "agent")
让我们来解读一下这张“流水线设计图”:
- 入口 (
set_entry_point):所有任务都从agent节点开始。 agent节点执行:调用 LLM。- 条件路由 (
add_conditional_edges):在agent节点之后,调用should_continue函数进行判断。- 路径 A:如果 LLM 的回复不包含
tool_calls,should_continue返回"end",流程直接走向终点END。 - 路径 B:如果 LLM 的回复包含
tool_calls,should_continue返回"continue",流程走向tool_executor节点。
- 路径 A:如果 LLM 的回复不包含
tool_executor节点执行:执行 LLM 请求的工具。- 返回循环 (
add_edge):tool_executor节点执行完毕后,流程无条件地返回到agent节点,形成一个完美的“思考 -> 行动 -> 观察 -> 再思考”的循环。
6. 步骤五:编译并运行你的图
我们的流水线已经设计完毕,现在只需要按下“启动”按钮。
# multi_tool_agent.py (续)
from langchain_core.messages import HumanMessage
# 编译图,得到一个可执行的 app
app = workflow.compile()
# --- 现在,让我们来测试它! ---
# 测试一:网页搜索
print("\n\n--- Test Case 1: Web Search ---")
inputs1 = {"messages": [HumanMessage(content="What's on Elon Musk's mind recently?")]}
for output in app.stream(inputs1, {"recursion_limit": 5}): # 设置递归限制以防意外死循环
for key, value in output.items():
print(f"Output from node '{key}':")
print(value)
print("---")
print("\n")
# 测试二:数学计算
print("\n\n--- Test Case 2: Calculator ---")
inputs2 = {"messages": [HumanMessage(content="What is 34 * 5.67?")]}
for output in app.stream(inputs2, {"recursion_limit": 5}):
for key, value in output.items():
print(f"Output from node '{key}':")
print(value)
print("---")
print("\n")
# 测试三:直接回答
print("\n\n--- Test Case 3: Direct Answer ---")
inputs3 = {"messages": [HumanMessage(content="Hello!")]}
for output in app.stream(inputs3, {"recursion_limit": 5}):
for key, value in output.items():
print(f"Output from node '{key}':")
print(value)
print("---")
print("\n")
将以上所有代码片段整合到一个 multi_tool_agent.py 文件中,然后在终端运行 python multi_tool_agent.py。
你将会看到非常详细的输出,清晰地展示了 Agent 的每一步行动:
- 在测试一中,
agent节点会决策调用search_tool,然后tool_executor节点会执行它。 - 在测试二中,
agent节点会决策调用python_calculator,然后tool_executor节点会执行它。 - 在测试三中,
agent节点会直接生成回复,should_continue将返回"end",流程直接结束。
你已经成功构建了一个可以在多个工具间进行智能选择的 Agent!
7. 代码复盘与关键点解析
-
Annotated[List[BaseMessage], operator.add]的含义是什么?Annotated是 Pythontyping模块中的一个功能,它允许我们为类型添加额外的元数据。operator.add就是我们添加的元数据。- LangGraph 在处理状态更新时,会检查这个元数据。如果它发现了
operator.add,它就会执行“加法”操作(对于列表就是list.extend),而不是默认的“覆盖”操作。这确保了messages列表是持续累加的,而不是每次都被新消息替换。
-
bind_tools的作用是什么?- 这是一个便捷的方法,等同于在每次调用
.invoke()时,手动将工具的 JSON Schema 传入tools参数。 llm.bind_tools(tools)返回一个新的Runnable对象,这个对象在被调用时,会自动将工具信息附加上去。这让我们的agent_node代码更简洁。
- 这是一个便捷的方法,等同于在每次调用
-
条件边
should_continue是如何实现路由的?- 这是 LangGraph 控制流的核心。
add_conditional_edges的path_function(即我们的should_continue)是实现动态路由的关键。 - 它就像一个铁路上的“道岔”,根据
State中的当前信息(最新的消息是否包含tool_calls),决定将工作流的“列车”引向哪条“轨道”(tool_executor节点或END)。
- 这是 LangGraph 控制流的核心。
8. 总结:恭喜!你已掌握构建 Agent 工作流的基本功
通过这个“保姆级”的教程,你不仅构建了你的第一个真正意义上的 LangGraph 应用,更重要的是,你亲身体验了用图的思维来设计和编排 Agent 工作流的全过程。
你学会了:
- 如何定义一个可累加的状态(
AgentState)。 - 如何将 Agent 的思考(
agent_node)和行动(tool_executor_node)拆分成独立的节点。 - 如何使用强大的条件边(
add_conditional_edges)来实现 Agent 的动态决策。 - 如何将这一切编译成一个可执行、可观测的
app。
你已经掌握了构建复杂、可控、可预测的 Agent 所需的最核心的基本功。在接下来的课程中,我们将基于这些基本功,去探索更令人兴奋的应用,比如多 Agent 协作、人机交互等。