本章目标:深入理解 ReAct 推理模式,掌握用
create_agent(LangGraph V1.0 起从langchain.agents导入,原名create_react_agent)快速构建 Agent、手动实现 Agent 循环、多工具 Agent 设计,以及 Agent 与 Chain 的选型判断。
前期回顾
🚀 写给零基础开发者(5分钟读懂 Agent)
1.1 什么是 Agent?用一个比喻理解
想象你在指挥一个助理完成一项任务:"帮我计划一次北京→上海的出差,整理日程表并发送邮件。"
普通程序员(没有 Agent)的做法:
步骤1:我写代码查询机票 → 步骤2:我写代码查询酒店 → 步骤3:我手动整合结果 → 步骤4:我写代码发邮件
每一步都要我预先知道做什么,预先写好代码,无法应对变化。
有了 Agent 之后:
用户说:"帮我计划出差" → Agent 自主决定:先查机票 → 根据机票结果查酒店 → 发现冲突就重新搜索 → 整合信息 → 发邮件
AI 自己决定步骤,自己执行,自己检查,直到完成任务。
一句话总结:Agent = 能自主思考 + 主动调用工具 + 循环改进直到完成任务的 AI 助理。
1.2 为什么需要 Agent?没有它会有什么问题?
❌ 只用 LLM(无 Agent/无工具):能力有限、结果不可信
# 纯 LLM 对话,没有工具,没有 Agent
from langchain_openai import ChatOpenAI
llm = ChatOpenAI(model="qwen-plus", ...)
# 问题1:实时数据 → LLM 只能猜测
response = llm.invoke("今天北京天气怎么样?")
# 输出:❌ "今天北京天气晴朗..." (LLM 瞎编的!它根本不知道今天天气)
# 问题2:精确计算 → LLM 会出错
response = llm.invoke("sqrt(256) × 17 + 2^8 是多少?")
# 输出:❌ 可能是 "4352"(大模型数学常出错,无法保证精确)
# 问题3:多步骤开放性任务 → LLM 无法真正执行
response = llm.invoke("查询一下我公司的最新销售数据,然后生成报表")
# 输出:❌ "我无法访问你公司的数据..." (LLM 根本没有工具访问能力)
❌ 只用硬编码程序(无 LLM):灵活性极差
# 假设你用 if-else 写了一个"智能"助手
def smart_assistant(user_input: str) -> str:
if "天气" in user_input:
return query_weather() # 只能处理天气
elif "计算" in user_input:
return do_calculation() # 只能处理计算
else:
return "我不理解你的问题" # 稍微变个说法就失败
# 用户说:"现在几度?需要带伞吗?"
# ❌ 程序不认识"几度"这个词,无法处理
# 用户说:"帮我算一下出发前还有几天,然后查一下目的地天气,决定带哪件外套"
# ❌ 多步骤任务完全无法处理(需要先算日期,再查天气,再决策)
✅ LLM + Agent:两者优点结合
用户输入 → LLM 理解意图(自然语言理解)
→ Agent 自主决定调用哪些工具
→ 工具执行真实操作(精确、实时)
→ LLM 分析结果、决定下一步
→ 循环直到完成任务
→ 输出可信的最终答案
总结对比表:
| 维度 | 只用 LLM | 只用代码 | LLM + Agent |
|---|---|---|---|
| 自然语言理解 | ✅ 强 | ❌ 弱 | ✅ 强 |
| 实时数据获取 | ❌ 不支持 | ✅ 支持 | ✅ 支持 |
| 精确计算 | ⚠️ 不可靠 | ✅ 精确 | ✅ 精确 |
| 处理未知任务 | ⚠️ 有限 | ❌ 需重写代码 | ✅ 自动路由 |
| 多步骤动态任务 | ❌ 无法规划 | ❌ 需预先设计 | ✅ 自主规划 |
| 适应语言变化 | ✅ 强 | ❌ 弱 | ✅ 强 |
| 错误自我修复 | ❌ 不支持 | ❌ 不支持 | ✅ 支持 |
1.3 什么场景该用 Agent?(4题自测)
遇到以下问题,答"是"就需要 Agent:
- ❓ 任务完成的步骤数是不确定的吗?(比如:可能需要 2 步,也可能需要 5 步)
- ❓ 任务需要访问实时数据(天气、股价、数据库)吗?
- ❓ 任务需要自主决策(根据上一步结果决定下一步)吗?
- ❓ 任务可能需要在失败后自动重试或换一种方式吗?
答了 2 个以上"是" → 考虑使用 Agent
1.4 典型 Agent 使用场景
| 场景 | 举例 | 为什么用 Agent |
|---|---|---|
| 数据分析助手 | "分析过去一年的销售数据,找出下滑原因" | 步骤不确定:可能需要多次查询、比对 |
| 智能客服 | "查询订单状态,计算退款,发通知" | 多系统工具调用,顺序动态决定 |
| 代码助手 | "写一个函数,测试,如有错误修复" | 需要执行→观察→改进的循环 |
| 研究助手 | "调研竞品,总结优缺点,写报告" | 搜索→阅读→分析→撰写,步骤不固定 |
| 任务规划 | "安排下周出差行程,订机票酒店" | 多工具、多决策、条件判断 |
| 数据管道 | "从数据库取数,清洗,存到新表" | 需要根据数据质量动态调整清洗步骤 |
不适合 Agent 的场景:
- 固定格式的文本生成(用 Chain 更快更稳定)
- 单次查询问答(直接用 LLM 即可)
- 确定步骤的工作流(用 Chain + Tools)
一、什么是 Agent?和 Chain 有什么区别?
在第03章,我们学了 Chain:预先定义好执行步骤,输入进来,按固定顺序走完所有步骤,输出结果。Chain 很适合确定性任务(如:翻译→润色→输出),但面对开放性问题就力不从心了。
Agent(智能体) 则完全不同:
- Chain:你决定执行哪些步骤,执行几次
- Agent:LLM 决定执行哪些步骤,执行几次,何时停止
flowchart TD
subgraph Chain["Chain(确定性流程)"]
C1[步骤1: 翻译] --> C2[步骤2: 润色] --> C3[步骤3: 输出]
end
subgraph Agent["Agent(自主决策循环)"]
A1[LLM思考] --> A2{需要工具?}
A2 -->|是| A3[执行工具]
A3 --> A4[观察结果]
A4 --> A1
A2 -->|任务完成| A5[输出答案]
end
Agent 内部消息流详解:
sequenceDiagram
participant U as 用户
participant A as Agent
participant L as LLM (大脑)
participant T as 工具 (手)
U->>A: "今天距离春节还有多少天?"
A->>L: 发送问题 + 可用工具列表
L->>A: 决策:调用 get_current_time 工具
A->>T: 执行 get_current_time()
T->>A: "2025年03月10日,周一"
A->>L: 发送工具结果
L->>A: 决策:调用 date_calculator 工具
A->>T: date_calculator(start="2025-03-10", end="2026-01-29")
T->>A: "相差 325 天,约 46 周"
A->>L: 发送工具结果
L->>A: 任务完成,给出最终答案
A->>U: "距离2026年春节还有325天,约46周"
什么时候用 Agent?什么时候用 Chain?
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 格式转换、文字处理 | Chain | 步骤确定,Chain 更可靠、更快 |
| 复杂问答(需检索) | Agent | 需要决定用哪些工具,几步才够 |
| 多步骤数学问题 | Agent | 步骤数不确定 |
| 代码调试 | Agent | 需要执行-观察-修复的循环 |
| 固定报告生成 | Chain | 每次都是同样的步骤 |
二、ReAct 模式:AI 的"思考-行动-观察"循环
ReAct(Reasoning + Acting)是目前最主流的 Agent 模式,由普林斯顿大学 2022 年提出。
它的核心是三个步骤的循环:
Thought(思考): 我需要知道 3 × 17 的结果和今天的日期
Action(行动): calculator.invoke({"expression": "3 * 17"})
Observation(观察): 51
Thought(思考): 已知乘法结果是 51,现在获取日期
Action(行动): get_current_time.invoke({})
Observation(观察): 2025年03月15日
Thought(思考): 我现在有了所有需要的信息
Final Answer(最终答案): 3 乘以 17 等于 51。今天是 2025年3月15日。
这个模式为什么有效?因为它将"推理"和"行动"明确分开,每次行动后都基于真实结果重新推理,避免了 LLM 的"幻觉"(凭空编造)。
三、Step 1:快速创建 ReAct Agent
代码文件:lessons/05_agents/01_react_agent.py
运行命令:
cd ai-agent-test
python lessons/05_agents/01_react_agent.py
核心步骤:定义工具 → 创建 LLM → 组装 Agent → 运行
import math
import os
import sys
from datetime import datetime
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool # @tool 装饰器:把函数变成 LLM 可调用的工具
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent # 核心:一行创建 ReAct Agent(LangGraph V1.0 起推荐从 langchain.agents 导入,原名 create_react_agent)
from pydantic import BaseModel, Field
# ── 步骤1:初始化 LLM ──────────────────────────────────────────
def create_llm() -> ChatOpenAI:
"""创建百炼 API 实例。"""
api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
print("错误:请设置环境变量 DASHSCOPE_API_KEY")
sys.exit(1)
return ChatOpenAI(
model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=api_key,
# temperature 不设置(默认),Agent 本身会控制推理的确定性
)
# ── 步骤2:定义工具(工具是 Agent 的"手")──────────────────────
# 关键:工具的 docstring 非常重要!LLM 会读这段描述来决定"什么时候用这个工具"
class CalculatorInput(BaseModel):
# Field 的 description 帮助 LLM 理解参数含义
expression: str = Field(description="数学表达式,如 '2 + 3'、'sqrt(16)'、'2 ** 10'")
@tool(args_schema=CalculatorInput) # args_schema 让 LLM 知道参数结构
def calculator(expression: str) -> str:
"""
计算数学表达式。
支持基本运算(+、-、*、/)、幂运算(**)、
以及常见数学函数:sqrt(平方根)、abs(绝对值)、round(四舍五入)。
"""
try:
# 安全的 eval:只允许数学函数,禁止执行系统命令
safe_globals = {
"__builtins__": {}, # 禁用所有内置函数(防止注入攻击)
"sqrt": math.sqrt, # 只开放数学函数
"abs": abs,
"round": round,
"pow": pow,
"pi": math.pi,
"e": math.e,
"log": math.log,
"sin": math.sin,
"cos": math.cos,
}
result = eval(expression, safe_globals)
return f"{expression} = {result}" # 返回值要清晰,LLM 会读取并解读
except Exception as e:
return f"计算错误:{str(e)}"
@tool
def get_current_datetime() -> str:
"""
获取当前的日期和时间。
返回格式:年-月-日 时:分:秒,以及星期几。
当用户询问"现在几点"、"今天几号"、"今天星期几"时使用此工具。
"""
now = datetime.now()
weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
weekday = weekdays[now.weekday()]
return f"当前时间:{now.strftime('%Y年%m月%d日 %H:%M:%S')},{weekday}"
class StringOpsInput(BaseModel):
text: str = Field(description="要操作的文本")
operation: str = Field(
description="操作类型:'upper'(大写)、'lower'(小写)、'reverse'(反转)、"
"'length'(长度)、'count_words'(统计词数)、'title'(标题格式)"
)
@tool(args_schema=StringOpsInput)
def string_operations(text: str, operation: str) -> str:
"""
对字符串执行各种操作:转换大小写、反转、统计长度等。
当用户需要对文本进行格式化、分析或变换时使用。
"""
ops = {
"upper": lambda t: t.upper(),
"lower": lambda t: t.lower(),
"reverse": lambda t: t[::-1],
"length": lambda t: str(len(t)),
"count_words": lambda t: str(len(t.split())),
"title": lambda t: t.title(),
}
if operation not in ops:
return f"不支持的操作:{operation}。支持:{', '.join(ops.keys())}"
result = ops[operation](text)
return f"操作 '{operation}' 的结果:{result}"
# ── 步骤3:展示 Agent 推理过程(学习和调试用)────────────────────
def show_agent_steps(result: dict):
"""展示 Agent 每一步做了什么,帮助你理解 ReAct 循环。"""
messages = result.get("messages", [])
print(f"\n=== Agent 执行过程(共 {len(messages)} 条消息)===")
for i, msg in enumerate(messages):
msg_type = msg.__class__.__name__
if msg_type == "HumanMessage":
# 用户输入
print(f"\n[{i+1}] 👤 用户:{msg.content}")
elif msg_type == "AIMessage":
if msg.tool_calls:
# LLM 决定调用某个工具(ReAct 的 Action 步骤)
for tc in msg.tool_calls:
print(f"\n[{i+1}] 🤖 AI(决策):决定调用工具 {tc['name']},参数:{tc['args']}")
else:
# LLM 认为已完成,给出最终答案(ReAct 的 Final Answer)
print(f"\n[{i+1}] 🤖 AI(最终回答):{msg.content}")
elif msg_type == "ToolMessage":
# 工具执行结果(ReAct 的 Observation 步骤)
content_preview = msg.content[:200]
print(f"\n[{i+1}] 🔧 工具结果(Observation):{content_preview}")
# ── 步骤4:组装并运行 Agent ──────────────────────────────────────
def main():
llm = create_llm()
# 注册工具:Agent 会知道这些工具的名称、描述和参数
tools = [calculator, string_operations, get_current_datetime]
print("创建 ReAct Agent...")
print(f"可用工具:{[t.name for t in tools]}")
# create_agent 是快捷方式,一行创建完整的 ReAct Agent
# LangGraph V1.0 起推荐:from langchain.agents import create_agent(原名 create_react_agent)
# 内部自动处理:工具绑定、消息历史、循环逻辑、终止判断
agent = create_agent(
llm, # 大脑:负责推理和决策
tools=tools, # 手:执行具体操作
)
# ── 示例1:单工具任务(简单路由)────────────────────────────
print("\n" + "=" * 60)
print("示例1:简单数学计算(Agent 会选择 calculator 工具)")
print("=" * 60)
result = agent.invoke({
"messages": [HumanMessage(content="计算 sqrt(144) + 2**8 的结果")]
})
show_agent_steps(result)
print(f"\n✅ 最终答案:{result['messages'][-1].content}")
# ── 示例2:多步骤任务(Agent 自主规划步骤)──────────────────
print("\n" + "=" * 60)
print("示例2:多步骤推理(Agent 会调用多个工具)")
print("=" * 60)
result = agent.invoke({
"messages": [HumanMessage(
content="现在几点了?另外帮我算一下:如果我从现在开始跑步2.5小时,"
"每小时跑8公里,我能跑多少公里?"
)]
})
show_agent_steps(result)
print(f"\n✅ 最终答案:{result['messages'][-1].content}")
if __name__ == "__main__":
main()
关键设计原则解析:
- 工具 docstring 要清晰:LLM 通过读 docstring 决定调用哪个工具,描述越准确,路由越正确
- 使用
args_schema(Pydantic):让 LLM 知道参数格式,减少参数传递错误 - 工具返回值要有意义:返回
"结果:42"比"42"更好,LLM 能更好理解上下文 - 安全计算:
eval时设置{"__builtins__": {}}禁用内置函数,防止代码注入
四、Step 2:理解 Agent 的内部机制(手动实现循环)
代码文件:lessons/05_agents/02_tool_calling_agent.py
create_agent(原名 create_react_agent,LangGraph V1.0 起更名)帮我们封装了所有细节。理解内部机制对调试和高级定制非常重要:
import os
import sys
from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
class ManualAgent:
"""
手动实现的 Agent 循环,展示每一步的内部工作机制。
这和 create_agent 内部做的事情完全一样,只是我们自己写出来看清楚。
"""
def __init__(self, llm: ChatOpenAI, tools: list, max_iterations: int = 10):
# bind_tools:告诉 LLM "你有这些工具可以调用"
# 调用后 LLM 的响应中会包含 tool_calls 字段
self.llm_with_tools = llm.bind_tools(tools)
# 建立工具名称 → 工具对象的映射,方便快速查找
self.tool_map = {t.name: t for t in tools}
self.max_iterations = max_iterations # 防止无限循环
def run(self, user_message: str, system_prompt: str = None, verbose: bool = True) -> str:
"""运行完整的 Agent 循环,直到任务完成或超过最大迭代次数。"""
# 初始化消息历史(这是 Agent 的"记忆")
messages = []
if system_prompt:
# SystemMessage:定义 Agent 的角色和行为准则
messages.append(SystemMessage(content=system_prompt))
messages.append(HumanMessage(content=user_message)) # 用户输入
if verbose:
print(f"🎯 任务:{user_message}\n")
# ============================================================
# Agent 核心循环(这就是 ReAct 的实现)
# ============================================================
for iteration in range(self.max_iterations):
if verbose:
print(f"--- 第 {iteration + 1} 轮推理 ---")
# ── 步骤A:调用 LLM 推理 ──────────────────────────────
# LLM 收到消息历史,决定:调用某个工具?还是直接给出答案?
response = self.llm_with_tools.invoke(messages)
messages.append(response) # 把 AI 响应加入历史(很重要!)
# ── 步骤B:检查是否有工具调用请求 ─────────────────────
if not response.tool_calls:
# tool_calls 为空 → LLM 认为任务已完成,直接给答案
if verbose:
print(f"✅ AI 给出最终答案(无需更多工具)")
return response.content # 返回最终答案
# ── 步骤C:执行 LLM 请求的工具 ─────────────────────────
if verbose:
print(f"🔧 LLM 决定调用 {len(response.tool_calls)} 个工具:")
for tool_call in response.tool_calls:
tool_name = tool_call["name"] # 工具名称
tool_args = tool_call["args"] # 工具参数
tool_id = tool_call["id"] # 工具调用的唯一 ID(关联请求和响应)
if verbose:
print(f" → 工具:{tool_name},参数:{tool_args}")
# 执行工具(带错误处理)
if tool_name not in self.tool_map:
tool_result = f"错误:未找到工具 '{tool_name}'"
else:
try:
tool_result = self.tool_map[tool_name].invoke(tool_args)
except Exception as e:
tool_result = f"工具执行错误:{str(e)}"
if verbose:
print(f" 结果:{str(tool_result)[:150]}")
# ── 步骤D:把工具结果加入消息历史 ──────────────────
# ToolMessage 必须包含 tool_call_id,与步骤C的 tool_id 对应
# LLM 通过这个 ID 知道"这个结果是回答哪个工具调用的"
messages.append(
ToolMessage(
content=str(tool_result),
tool_call_id=tool_id, # ⚠️ 这个 ID 必须匹配,否则 LLM 会混乱
)
)
# 步骤E:回到步骤A,LLM 基于新的工具结果继续推理
if verbose:
print()
# 超过最大迭代次数(可能工具一直出错,或任务过于复杂)
return "已达到最大迭代次数,请尝试更简单的问题。"
# ── 使用示例 ──────────────────────────────────────────────────
@tool
def search_knowledge_base(query: str) -> str:
"""在知识库中搜索相关信息。"""
knowledge_base = {
"langchain": "LangChain是一个AI应用开发框架,提供链、工具、Agent等组件,简化LLM应用开发。",
"langgraph": "LangGraph是基于图状态机的AI工作流框架,支持循环、条件分支和多Agent协作。",
"python": "Python是一种高级编程语言,以简洁易读著称。1991年由Guido van Rossum创建。",
}
for key, value in knowledge_base.items():
if key.lower() in query.lower():
return f"【{key}】{value}"
return f"知识库中未找到关于'{query}'的信息"
@tool
def calculate(expression: str) -> str:
"""计算数学表达式,支持基本四则运算和幂运算。"""
try:
result = eval(expression, {"__builtins__": {}})
return f"{expression} = {result}"
except Exception as e:
return f"计算错误:{str(e)}"
def demo_agent_loop(llm):
"""演示手动 Agent 循环。"""
tools = [search_knowledge_base, calculate]
agent = ManualAgent(llm=llm, tools=tools, max_iterations=10)
system_prompt = "你是一个知识渊博的AI助手,可以搜索知识库和进行计算。回答时要全面、准确。"
# 任务1:单一工具
print("=" * 60)
print("测试:综合任务(查询 + 计算)")
print("=" * 60)
answer = agent.run(
"Python有多少年历史了(2025年算,Python 1991年创建)?另外,LangGraph是什么?",
system_prompt=system_prompt,
)
print(f"\n最终答案:\n{answer}")
理解 Agent 循环的关键:消息历史就是 Agent 的"记忆"
初始消息:[HumanMessage("问题")]
↓ LLM 推理
第1轮后:[HumanMessage, AIMessage(tool_calls=[calculator])]
↓ 执行工具
[HumanMessage, AIMessage(tool_calls=[calculator]), ToolMessage("结果")]
↓ LLM 推理(看到了工具结果)
第2轮后:[..., AIMessage("最终答案")] ← 无 tool_calls,任务完成
五、Step 3:多工具 Agent 与复杂任务
代码文件:lessons/05_agents/03_multi_tool_agent.py
真实 Agent 需要处理多种类型的任务,这要求工具设计合理、分工明确:
import math
import os
import sys
from datetime import datetime, timedelta
from langchain_core.messages import HumanMessage
from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langchain.agents import create_agent # LangGraph V1.0 起推荐从 langchain.agents 导入
from pydantic import BaseModel, Field
# ── 数学工具组 ──────────────────────────────────────────────────
@tool
def basic_calculator(expression: str) -> str:
"""基础计算器:支持 +、-、*、/、** 运算和括号。"""
try:
safe_globals = {"__builtins__": {}, "abs": abs, "round": round, "pow": pow}
result = eval(expression, safe_globals)
return f"{expression} = {result}"
except Exception as e:
return f"计算错误:{str(e)}"
@tool
def statistics_calculator(numbers: str) -> str:
"""
统计计算器:计算一组数字的平均值、最大值、最小值、总和。
数字用逗号分隔,例如:'1, 2, 3, 4, 5'
"""
try:
nums = [float(n.strip()) for n in numbers.split(",")]
return (
f"数据:{nums}\n"
f"总和:{sum(nums)}\n"
f"平均值:{sum(nums)/len(nums):.4f}\n"
f"最大值:{max(nums)}\n"
f"最小值:{min(nums)}"
)
except ValueError as e:
return f"数字解析错误:{str(e)}"
# ── 时间工具组 ──────────────────────────────────────────────────
@tool
def get_current_time() -> str:
"""获取当前日期和时间,包括星期几和当年第几天。"""
now = datetime.now()
weekdays = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
return (
f"当前时间:{now.strftime('%Y-%m-%d %H:%M:%S')}\n"
f"星期:{weekdays[now.weekday()]}\n"
f"今天是今年第 {now.timetuple().tm_yday} 天"
)
class DateCalcInput(BaseModel):
start_date: str = Field(description="起始日期(格式:YYYY-MM-DD,如 2024-01-01)")
end_date: str = Field(description="结束日期(格式:YYYY-MM-DD,如 2024-12-31)")
@tool(args_schema=DateCalcInput)
def date_calculator(start_date: str, end_date: str) -> str:
"""计算两个日期之间的天数差。"""
try:
start = datetime.strptime(start_date, "%Y-%m-%d")
end = datetime.strptime(end_date, "%Y-%m-%d")
days = abs((end - start).days)
return (
f"从 {start_date} 到 {end_date}:\n"
f" 相差 {days} 天\n"
f" 约 {days//7} 周 {days%7} 天\n"
f" 约 {days/30:.1f} 个月"
)
except ValueError as e:
return f"日期格式错误:{str(e)}"
# ── 展示工具调用过程 ──────────────────────────────────────────────
def run_agent_task(agent, task: str):
"""运行 Agent 任务并展示推理过程。"""
print(f"\n{'='*60}")
print(f"任务:{task}")
print("=" * 60)
result = agent.invoke({"messages": [HumanMessage(content=task)]})
# 展示每一步的工具调用(让你看清 Agent 在做什么)
for msg in result["messages"]:
msg_type = msg.__class__.__name__
if msg_type == "AIMessage":
if msg.tool_calls:
for tc in msg.tool_calls:
print(f" 🔧 调用工具:{tc['name']},参数:{tc['args']}")
else:
print(f"\n✅ 最终答案:{msg.content}")
elif msg_type == "ToolMessage":
print(f" └─ 结果:{msg.content[:100]}")
def main():
api_key = os.getenv("DASHSCOPE_API_KEY")
if not api_key:
print("错误:请设置 DASHSCOPE_API_KEY")
sys.exit(1)
llm = ChatOpenAI(
model="qwen-plus",
base_url="https://dashscope.aliyuncs.com/compatible-mode/v1",
api_key=api_key,
)
# 注册所有工具
tools = [basic_calculator, statistics_calculator, get_current_time, date_calculator]
print(f"多工具 Agent 初始化完成,可用工具:{[t.name for t in tools]}\n")
# create_agent 自动管理所有工具路由(LangGraph V1.0 起推荐使用 create_agent)
agent = create_agent(llm, tools=tools)
# 任务1:单工具路由
run_agent_task(agent, "计算一个半径为5的圆的面积(π×r²),保留两位小数。")
# 任务2:多工具协作(先查时间,再计算日期差)
run_agent_task(
agent,
"今天是几号?请计算从2024年1月1日到今天共经过了多少天?然后除以7,算出大约几周。"
)
# 任务3:统计分析
run_agent_task(
agent,
"计算以下成绩的平均分和最高分:85, 92, 78, 96, 88, 75, 91"
)
if __name__ == "__main__":
main()
六、开发 Agent 的完整流程
无论开发什么 Agent,都遵循这 5 个步骤:
flowchart LR
S1[1. 分析需求\n确定任务边界] --> S2[2. 设计工具\n每个工具一个职责]
S2 --> S3[3. 单独测试工具\n不经过 LLM 验证]
S3 --> S4[4. 组装 Agent\ncreate_agent]
S4 --> S5[5. 端到端测试\n验证推理链路]
S5 -->|发现问题| S2
步骤1:分析需求,确认是否需要 Agent
# 自问:这个任务的步骤是动态的吗?
# 示例:用户输入"帮我分析这段文字,算出词频最高的5个词,然后生成词云描述"
# 步骤不确定:可能需要先分析、再计算、再生成
# 需要工具:文本分析、计数计算
# → 适合 Agent!
步骤2:设计工具(每个工具只做一件事)
# ✅ 好的工具设计:职责单一,描述清晰
@tool
def count_word_frequency(text: str) -> str:
"""
统计文本中每个词出现的频率。
返回按频率排序的词频列表。
适用于:需要分析文本词汇分布时。
"""
# ...
# ❌ 不好的工具设计:职责混乱,描述模糊
@tool
def process_text(text: str, mode: str) -> str:
"""处理文本。mode 可以是多种类型。""" # LLM 不知道什么时候用这个工具
# ...
步骤3:单独测试工具(不经过 LLM)
# 先直接调用工具,确认功能正确
# 这一步不需要任何 API 费用!
# 测试 calculator
result = calculator.invoke({"expression": "sqrt(144)"})
print(result) # 期望:12.0
# 测试 get_current_datetime
result = get_current_datetime.invoke({})
print(result) # 期望:当前日期时间
# 检查工具 Schema(这是 LLM 看到的描述)
import json
print(json.dumps(calculator.args_schema.model_json_schema(), indent=2, ensure_ascii=False))
步骤4:组装 Agent 并测试推理
# 最简单的 Agent 组装
# 最简单的 Agent 组装(LangGraph V1.0 起使用 create_agent)
agent = create_agent(llm, tools=tools)
# 先测试简单问题(确认基础路由正确)
result = agent.invoke({"messages": [HumanMessage(content="计算 2 + 2")]})
print(result["messages"][-1].content)
# 再测试多步骤问题
result = agent.invoke({"messages": [HumanMessage(content="现在几点?再算一下 sqrt(169)")]})
步骤5:生产化改造
from langgraph.checkpoint.memory import MemorySaver
# 添加记忆(多轮对话)
memory = MemorySaver()
agent_with_memory = create_agent(
llm,
tools=tools,
checkpointer=memory, # 自动保存对话历史
state_modifier="""你是专业的 AI 助手。
规则:
1. 所有计算必须使用工具,不得凭空给出数字
2. 如果工具出错,告知用户并解释原因
3. 用简洁的中文回答
"""
)
# 使用 thread_id 区分不同用户的会话
config = {"configurable": {"thread_id": "user_001"}}
def chat_with_memory(message: str) -> str:
"""支持多轮对话的 Agent 调用。"""
result = agent_with_memory.invoke(
{"messages": [HumanMessage(content=message)]},
config=config,
)
return result["messages"][-1].content
# 多轮对话测试
print(chat_with_memory("我叫张三,我的生日是 1990-05-15"))
print(chat_with_memory("今天距离我的下一个生日还有几天?")) # Agent 记住了生日
print(chat_with_memory("我叫什么名字?")) # Agent 记住了姓名
七、Agent 调试与验证指南
7.1 验证工具是否被正确调用
# 方法1:查看完整消息历史
def debug_agent_run(agent, question: str):
"""调试模式:打印完整的推理链路。"""
result = agent.invoke({"messages": [HumanMessage(content=question)]})
messages = result["messages"]
print(f"📊 Agent 执行统计:")
print(f" 总消息数:{len(messages)}")
tool_calls = sum(1 for m in messages
if m.__class__.__name__ == "AIMessage" and m.tool_calls)
print(f" 工具调用轮次:{tool_calls}")
print("\n📋 完整推理链路:")
for i, msg in enumerate(messages):
class_name = msg.__class__.__name__
if class_name == "HumanMessage":
print(f" [{i+1}] 👤 用户:{msg.content}")
elif class_name == "AIMessage":
if msg.tool_calls:
for tc in msg.tool_calls:
print(f" [{i+1}] 🤖 AI决策:调用 {tc['name']},参数:{tc['args']}")
elif msg.content:
print(f" [{i+1}] 🤖 AI回答:{msg.content}")
elif class_name == "ToolMessage":
print(f" [{i+1}] 🔧 工具结果:{msg.content[:100]}")
return result["messages"][-1].content
# 方法2:检查工具 Schema(验证 LLM 是否能理解工具)
import json
for t in tools:
schema = t.args_schema.model_json_schema() if t.args_schema else {}
print(f"\n工具:{t.name}")
print(f"描述:{t.description[:100]}")
print(f"参数:{json.dumps(schema.get('properties', {}), ensure_ascii=False)}")
7.2 验证清单
运行 Agent 后,逐项检查:
- 工具正确选择:Agent 是否选择了预期的工具?(不是瞎猜一个)
- 参数传递正确:工具接收到的参数格式是否正确?
- 工具结果被使用:最终答案是否基于工具返回的真实数据?
- 步骤合理:推理轮数是否合理?(避免过多轮次)
- 答案符合预期:最终回答是否准确、完整?
7.3 常见错误与解决方案
| 问题 | 现象 | 根本原因 | 解决方案 |
|---|---|---|---|
| Agent 不调用工具 | 直接给出猜测的答案 | docstring 不够明确,或工具不匹配问题 | 优化工具描述,加"当…时使用此工具" |
| Agent 死循环 | 一直调用工具不结束 | 工具总是返回错误,LLM 一直重试 | 设置 recursion_limit + 改善工具健壮性 |
| 工具参数错误 | KeyError、TypeError | args_schema 不准确 | 用 Pydantic BaseModel 明确参数类型和描述 |
| 多工具时选错 | 用了错误的工具 | 工具 docstring 相似,LLM 混淆 | 让各工具描述的适用场景更有区分度 |
| 答案不准确 | 结果不对 | LLM 没读工具结果,自行推理 | 在 system_prompt 强调"必须基于工具结果" |
| 推理步骤过多 | 耗时长、费用高 | 任务太复杂或工具粒度太细 | 合并相关工具,或拆分大任务 |
八、课后练习
- 天气 Agent:添加真实天气查询工具(调用 OpenWeatherMap API),让 Agent 根据天气给出穿衣建议。
- 研究 Agent:实现一个"研究助手",能搜索信息(模拟 Wikipedia)、做数学计算、写结构化摘要。
- 调试实践:故意给工具传入错误参数(如
calculator.invoke({"expression": "2 ++ 3"})),观察 Agent 如何处理错误并重试。 - 挑战题(多步骤):让 Agent 解决:"如果 A 城市温度是 86°F,B 城市比 A 高 5°C,B 城市温度是多少°F?"(需要温度换算工具和计算工具的配合)
- 生产化练习:给 Agent 添加
MemorySaver,实现一个支持5轮以上多轮对话的助手,验证它能记住之前的对话内容。
本章知识点总结
Agent 核心概念
├── ReAct 模式:Thought → Action → Observation → 循环
├── 工具 = @tool 装饰器 + 清晰的 docstring
├── LLM = 大脑(决策),工具 = 手(执行)
└── create_agent = 快速组装,自动管理循环(原名 create_react_agent,LangGraph V1.0 起更名)
vs 其他方案
├── Chain:固定步骤 → Agent:动态决策
├── 纯 LLM:无工具 → Agent:有工具执行能力
└── 纯代码:硬编码路由 → Agent:自然语言路由
开发流程
1. 确认需要 Agent(动态步骤 + 工具 + 自主决策)
2. 设计工具(每工具一职责,docstring 要准确)
3. 单独测试工具(不经过 LLM)
4. 组装 Agent + 系统提示
5. 端到端验证(调试推理链路)
📌 下一章预告:AI 很厉害,但它不知道你私有数据库里有什么。第06章学 RAG(检索增强生成),让 AI 基于你的私有文档回答问题,彻底解决"幻觉"和"知识过时"的问题。
作者:阿聪谈架构
公众号:阿聪谈架构(分享后端架构 / AI / Java 技术文章)
相关代码关注公众号:【阿聪谈架构】 回复:AI专栏代码
本文属于《AI开发入门系列》,后续会持续更新。 关注博主,第一时间收到最新文章,获取完整学习路线与资料