【三个月 AI Agent 实战学习】Day 24 详细展开:第一个 Agent —— 使用 LangChain 构建数学专家
Day 24 详细展开:第一个 Agent —— 使用 LangChain 构建数学专家
欢迎来到第二十四天!昨天我们手动管理了工具调用的循环,虽然可行但代码较为繁琐。今天我们将使用 LangChain 提供的 create_react_agent 和 AgentExecutor 来构建一个真正的 Agent,它会自动处理 Thought-Action-Observation 循环,我们只需定义好工具和模型即可。通过这个项目,你将体会到框架带来的巨大便利,并理解 Agent 执行器的内部机制。

一、今日学习目标
- 理解 LangChain 中 Agent 的组成:模型、工具、提示词模板、执行器。
- 掌握使用
create_react_agent创建 ReAct 风格的 Agent。 - 学会使用
AgentExecutor运行 Agent,并获取最终结果和中间步骤。 - 通过给 Agent 配备计算器和搜索工具,观察它如何自主决定调用哪个工具、如何处理多步任务。
- 对比手动 ReAct 循环与框架实现的差异,理解抽象的价值。
二、详细实现步骤
步骤 1:环境准备
确保已安装 langchain、langchain-openai、langchain-community(如果使用搜索工具需要)。今天我们将使用一个简单的模拟搜索工具,不依赖外部 API。如果尚未安装,执行:
pip install langchain langchain-openai
新建 first_agent.py,导入所需模块:
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool
from langchain.agents import create_react_agent, AgentExecutor
from langchain_core.prompts import PromptTemplate
load_dotenv()
# 初始化模型
llm = ChatOpenAI(
model="deepseek-chat",
api_key=os.getenv("DEEPSEEK_API_KEY"),
base_url="https://api.deepseek.com",
temperature=0.1
)
步骤 2:定义工具
我们定义两个工具:calculator(计算器)和 search(模拟搜索)。沿用昨天的风格,确保 docstring 清晰。
@tool
def calculator(expression: str) -> str:
"""计算数学表达式,支持加减乘除和括号。输入为字符串格式的数学表达式,返回计算结果。"""
try:
allowed = set("0123456789+-*/(). ")
if not set(expression).issubset(allowed):
return "错误:表达式包含非法字符"
result = eval(expression)
return str(result)
except Exception as e:
return f"错误:{e}"
@tool
def search(query: str) -> str:
"""搜索信息,输入关键词或问题,返回相关答案。"""
knowledge = {
"北京天气": "北京今天晴,26°C,湿度 40%。",
"中国首都": "中国的首都是北京。",
"人工智能": "人工智能是计算机科学的一个分支,研究如何让机器模拟人类智能。",
"深度学习": "深度学习是机器学习的一个子集,基于人工神经网络。"
}
for key, value in knowledge.items():
if key in query:
return value
return "未找到相关信息。"
步骤 3:创建 ReAct 提示词模板
LangChain 的 create_react_agent 需要一个包含特定变量的提示词模板。这些变量包括 tools、tool_names、agent_scratchpad 和 input。我们可以使用 LangChain 自带的 hub 拉取标准 ReAct 提示词,也可以手动定义。为了更直观,我们手动构造一个中文的提示词模板。
react_template = """你是一个智能助手,可以通过调用工具来回答用户问题。你可以使用以下工具:
{tools}
工具名称列表:{tool_names}
使用以下格式进行推理和行动:
问题:用户提出的问题
思考:你应该思考接下来要做什么
行动:要使用的工具名称,必须是 [{tool_names}] 中的一个
行动输入:传递给工具的输入
观察:工具返回的结果
...(这个思考/行动/行动输入/观察可以重复 N 次)
思考:我现在知道最终答案了
最终答案:对用户问题的最终回答
开始!
问题:{input}
思考:{agent_scratchpad}"""
prompt = PromptTemplate.from_template(react_template)
关键变量说明:
{tools}:工具的描述文本,由 LangChain 自动填充。{tool_names}:工具名称列表,帮助模型知道有哪些工具可用。{agent_scratchpad}:中间步骤存储区域,模型在循环中会不断更新此处的思考与行动记录。{input}:用户输入的问题。
步骤 4:创建 Agent
使用 create_react_agent 将模型、工具和提示词模板组合起来:
agent = create_react_agent(llm, tools, prompt)
该函数返回一个 Runnable,它能够根据输入生成下一步的行动或最终答案。
步骤 5:创建 AgentExecutor 并运行
AgentExecutor 负责执行 Agent 的循环:调用 Agent 获取行动,执行工具,将观察结果反馈给 Agent,直到得到最终答案。
agent_executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True, # 打印中间步骤,便于观察
handle_parsing_errors=True, # 自动处理格式错误
max_iterations=5 # 防止死循环
)
verbose=True 会在控制台输出 Agent 的每一步思考、行动和观察,非常有助于调试。
测试运行:
result = agent_executor.invoke({"input": "请帮我计算 (12 + 7) * 3,然后搜索一下北京天气"})
print("最终答案:", result["output"])
步骤 6:观察运行过程
运行脚本,你会看到类似以下的输出(简化):
> Entering new AgentExecutor chain...
思考:我需要同时进行计算和搜索。先计算数学表达式,再搜索天气。
行动:calculator
行动输入:(12 + 7) * 3
观察:57
思考:计算结果是57。接下来搜索北京天气。
行动:search
行动输入:北京天气
观察:北京今天晴,26°C,湿度 40%。
思考:我已经得到了所有信息。
最终答案:计算结果是57;北京今天晴,26°C,湿度 40%。
> Finished chain.
最终答案:计算结果是57;北京今天晴,26°C,湿度 40%。
Agent 自动完成了多步工具调用,并正确整合了结果。
步骤 7:处理解析错误和边界情况
AgentExecutor 提供了 handle_parsing_errors=True,当 Agent 输出格式不符合预期时,它会尝试自动修复(将错误信息反馈给 Agent)。这比我们昨天手动处理要方便得多。
我们还可以测试一个需要多轮修正的问题,例如故意让计算器返回错误,观察 Agent 如何应对:
# 修改 calculator 工具,在表达式包含字母时返回错误
# (已有的逻辑会返回错误信息)
result = agent_executor.invoke({"input": "请计算 abc + 5"})
print(result["output"])
Agent 会先调用计算器,得到错误信息,然后可能会重新尝试或给出无法计算的答案。
步骤 8:对比手动 ReAct
回顾 Day 19/22 我们手动编写的 ReAct 循环,对比今天的代码:
| 方面 | 手动 ReAct | LangChain Agent |
|---|---|---|
| 循环管理 | 自己写 while 循环,解析 Action,执行工具,追加 Observation | AgentExecutor 自动完成 |
| 格式错误处理 | 需要自己检测并提醒模型 | handle_parsing_errors=True 自动处理 |
| 工具描述注入 | 手动拼接工具描述 | 自动从工具对象生成 |
| 中间步骤存储 | 手动维护消息列表 | 自动管理 agent_scratchpad |
| 代码量 | 较长 | 很少 |
框架大大简化了开发,让我们能够专注于工具设计和业务逻辑。
三、常见问题与调试
Q1:create_react_agent 提示找不到变量。
→ 确保提示词模板中包含所有必需变量:tools、tool_names、agent_scratchpad、input。变量名必须完全一致。
Q2:Agent 输出的“行动输入”格式不对(比如包含多余引号或换行)。
→ 启用 handle_parsing_errors=True 可以缓解。也可以降低温度,或在提示词中强调格式。如果问题严重,可以考虑自定义输出解析器,但今天先依赖默认处理。
Q3:Agent 陷入循环,不断调用工具却不给出最终答案。
→ 设置 max_iterations 限制最大步数,并在提示词中加入“当你拥有足够信息时,请立即输出最终答案”。另外,检查工具描述是否让模型误以为需要更多信息。
Q4:如何查看 Agent 的中间步骤(Thought/Action)?
→ 设置 verbose=True 即可在控制台打印。如果需要编程获取中间步骤,可以使用 return_intermediate_steps=True 参数,返回结果中会包含 intermediate_steps 列表。
Q5:能否使用 LangChain Hub 上的标准 ReAct 提示词?
→ 可以。使用 from langchain import hub; prompt = hub.pull("hwchase17/react") 获取英文提示词。对于中文场景,自定义模板可能更合适。我们这里手动定义是为了深入理解。
Q6:AgentExecutor 的 handle_parsing_errors 是如何工作的?
→ 当解析器无法从模型输出中提取有效行动时,它会捕获错误,并将错误信息作为观察反馈给 Agent,让 Agent 重新输出。这相当于自动重试机制。
四、今日总结与作业
今天你完成了:
- ✅ 使用 LangChain 的
create_react_agent和AgentExecutor构建了第一个 Agent。 - ✅ 理解了 Agent 的组成:模型、工具、提示词、执行器。
- ✅ 观察了 Agent 自主进行多步工具调用的过程,体验了框架的便利。
- ✅ 学会了如何处理解析错误和限制循环次数。
今日作业(必做):
- 修改提示词模板,加入更多约束,例如“如果问题不需要工具,直接给出最终答案”,测试 Agent 在闲聊场景下的表现。
- 为 Agent 添加第三个工具
get_time(返回当前时间,无参数),测试用户提问“现在几点了?”和“请计算 5*6 并且告诉我现在的时间”,观察 Agent 是否能同时调用多个工具。 - 设置
return_intermediate_steps=True,运行同一个问题,打印出所有中间步骤(包括工具名称、输入、输出),理解 Agent 的完整执行轨迹。 - 思考:如果要让 Agent 使用外部搜索 API(如 DuckDuckGo),你需要如何修改工具定义?请写出伪代码或说明。
明日预告: 我们将深入 Agent 的思考过程,学习如何通过日志和回调函数调试 Agent,掌握解析器的工作原理,为构建更复杂的 Agent 奠定基础。
有任何问题欢迎随时提问!
更多推荐


所有评论(0)