AI Agent实战系列·第2篇
LangChain、AutoGPT背后的核心思想
写在前面
上期回顾: 第1篇我们用100行代码实现了一个最简单的Agent,理解了"感知-决策-执行"的基本循环。
本期你将收获:
- ✅ 理解ReAct论文的核心思想
- ✅ 掌握Thought-Action-Observation模式
- ✅ 实现一个生产级的ReAct Agent
- ✅ 学会设计高质量的Prompt
一、从一个真实失败案例说起
上周我让第一版的Agent帮我做数据分析,结果翻车了:
任务:分析sales.csv,找出销售额最高的月份
Agent的执行过程:
🔧 读取文件 sales.csv
📊 文件内容:date,product,sales\n2024-01...
💭 思考:我看到了数据,现在需要分析
🔧 使用计算器:sum(数据)
❌ 错误:计算器无法处理这种数据
💭 思考:那我试试搜索
🔧 搜索:如何分析CSV数据
📊 搜索结果:[一堆教程]
💭 思考:emmm...我不知道该怎么办了
❌ 任务失败
问题出在哪?
Agent虽然知道要"边想边做",但它的思考质量太低:
- ❌ 没有明确的推理链条
- ❌ 不知道自己做到了哪一步
- ❌ 缺乏对结果的反思
这就像一个新手解题:虽然知道要步骤,但思路混乱。
二、ReAct:给Agent装上"推理引擎"
2022年,普林斯顿和Google的研究员提出了ReAct(Reasoning and Acting)模式,彻底解决了这个问题。
2.1 ReAct的核心理念
ReAct = Reasoning(推理)+ Acting(行动)
传统Agent:想一下 → 做 → 看结果 → 再想
ReAct Agent:详细推理 → 做 → 反思结果 → 详细推理 → 做...
2.2 ReAct的三要素
┌─────────────────────────────────────┐
│ │
│ Thought (思考) │
│ - 当前状态是什么? │
│ - 我要达成什么目标? │
│ - 下一步该做什么? │
│ - 为什么要这样做? │
│ ↓ │
│ Action (行动) │
│ - 具体的工具调用 │
│ ↓ │
│ Observation (观察) │
│ - 执行结果 │
│ - 是否符合预期? │
│ - 需要调整策略吗? │
│ ↓ │
└─────┘ (循环) │
2.3 对比理解
让我们看一个具体例子:
任务: 查询"北京明天的天气温度是否适合穿短袖"
❌ 普通Agent的思考
行动:查询天气
结果:明天25度
行动:搜索穿衣建议
结果:25度可以穿短袖
完成
✅ ReAct Agent的思考
Thought: 这个问题需要两步:
1. 先获取明天北京的温度
2. 判断这个温度是否适合穿短袖
让我先查天气
Action: weather_api
Action Input: {"city": "北京", "date": "明天"}
Observation: 明天北京 15-25度,多云
Thought: 温度范围是15-25度。这个温度波动较大。
早晚15度偏凉,中午25度较暖。
我需要考虑全天情况,给出更周到的建议。
短袖可能不够,建议带件外套。
Action: Finish
Action Input: 明天北京温度15-25度,建议穿长袖+外套,
中午热的话可以脱掉外套,但不建议只穿短袖。
Final Answer: [详细建议]
看到区别了吗?ReAct会:
- 📝 明确说明推理过程
- 🎯 清楚知道当前进度
- 🤔 对结果进行深度分析
- 💡 给出更周到的建议
三、ReAct Prompt设计精髓
ReAct的魔力90%来自于精心设计的Prompt。让我们深入拆解:
3.1 经典ReAct Prompt模板
REACT_PROMPT = """
你是一个问题解决专家,可以使用以下工具:
{tool_descriptions}
使用以下格式回答问题:
Question: 用户的问题
Thought: 你应该思考要做什么
Action: 要使用的工具,必须是[{tool_names}]之一
Action Input: 工具的输入参数
Observation: 工具的返回结果
... (这个 Thought/Action/Action Input/Observation 可以重复N次)
Thought: 我现在知道最终答案了
Final Answer: 对用户问题的最终回答
重要规则:
1. 每次只能使用一个工具
2. 必须等待Observation后才能继续
3. 详细说明你的推理过程
4. 如果遇到错误,分析原因并调整策略
开始!
Question: {question}
Thought:
"""
3.2 Prompt设计的5个关键点
1️⃣ 明确的格式约束
# ❌ 模糊的指令
"思考一下然后使用工具"
# ✅ 明确的格式
"使用以下格式:
Thought: [你的思考]
Action: [工具名称]
Action Input: [参数]"
2️⃣ 提供思考框架
# ✅ 好的Thought提示
Thought:
- 当前状态:我已经知道了什么?
- 目标:我要达成什么?
- 策略:下一步应该做什么?为什么?
3️⃣ 展示Few-shot示例
REACT_PROMPT = """
示例:
Question: 计算23*47的结果是否大于1000
Thought: 我需要先计算23*47,然后判断是否大于1000
Action: calculator
Action Input: 23 * 47
Observation: 1081
Thought: 1081确实大于1000
Final Answer: 是的,23*47=1081,大于1000
现在轮到你了:
Question: {question}
"""
4️⃣ 错误处理指导
如果工具返回错误:
1. 仔细分析错误原因
2. 检查输入参数是否正确
3. 考虑是否需要换个工具
4. 不要重复同样的错误操作
5️⃣ 终止条件
当满足以下条件时使用Final Answer:
1. 你已经有足够信息回答问题
2. 或者,所有可用工具都无法继续推进
3. 给出明确的答案或说明无法完成的原因
四、完整实现:生产级ReAct Agent
现在让我们实现一个真正可用的ReAct Agent。
4.1 工具定义
"""
tools.py - 工具定义
"""
from typing import Dict, Any, Callable
import json
class Tool:
"""工具基类"""
def __init__(self, name: str, description: str, func: Callable):
self.name = name
self.description = description
self.func = func
def run(self, input_str: str) -> str:
"""执行工具"""
try:
# 解析输入
if input_str.startswith('{'):
inputs = json.loads(input_str)
result = self.func(**inputs)
else:
result = self.func(input_str)
return str(result)
except Exception as e:
return f"Error: {str(e)}"
# ========== 具体工具实现 ==========
def calculator(expression: str) -> float:
"""执行数学计算"""
try:
# 安全的计算
allowed_names = {"abs": abs, "round": round, "min": min, "max": max}
result = eval(expression, {"__builtins__": {}}, allowed_names)
return result
except:
raise ValueError(f"无法计算表达式: {expression}")
def search(query: str) -> str:
"""网络搜索(这里用模拟数据)"""
# 实际应该调用搜索API
mock_results = {
"天气": "北京明天 15-25度,多云",
"新闻": "今日头条新闻...",
}
for key in mock_results:
if key in query:
return mock_results[key]
return f"关于'{query}'的搜索结果:[模拟数据]"
def wikipedia(query: str) -> str:
"""维基百科查询(模拟)"""
return f"维基百科关于'{query}'的信息:[模拟内容]"
# ========== 创建工具实例 ==========
TOOLS = [
Tool(
name="Calculator",
description="用于数学计算。输入:数学表达式(如'23*47')",
func=calculator
),
Tool(
name="Search",
description="搜索实时信息。输入:搜索关键词",
func=search
),
Tool(
name="Wikipedia",
description="查询维基百科。输入:查询主题",
func=wikipedia
)
]
4.2 ReAct Agent核心实现
"""
react_agent.py - ReAct Agent实现
"""
import re
import openai
from typing import List, Dict
from tools import Tool
class ReActAgent:
"""ReAct模式的Agent实现"""
def __init__(self, tools: List[Tool], model: str = "gpt-3.5-turbo"):
self.tools = {tool.name: tool for tool in tools}
self.model = model
def run(self, question: str, max_iterations: int = 10, verbose: bool = True):
"""执行任务"""
# 构建初始prompt
prompt = self._build_prompt(question)
if verbose:
print(f"\n{'='*60}")
print(f"🎯 Question: {question}")
print(f"{'='*60}\n")
iteration = 0
while iteration < max_iterations:
iteration += 1
# 调用LLM
response = self._call_llm(prompt)
if verbose:
print(f"【第{iteration}轮】")
print(response)
print()
# 检查是否完成
if "Final Answer:" in response:
answer = self._extract_final_answer(response)
if verbose:
print(f"{'='*60}")
print(f"✅ Final Answer: {answer}")
print(f"{'='*60}\n")
return answer
# 解析并执行Action
action, action_input = self._parse_action(response)
if action:
# 执行工具
observation = self._execute_action(action, action_input)
if verbose:
print(f"📊 Observation: {observation}")
print(f"{'-'*60}\n")
# 更新prompt
prompt += f"{response}\nObservation: {observation}\nThought: "
else:
# 没有找到有效的Action,提示继续
prompt += f"{response}\nThought: "
return "任务未在规定步数内完成"
def _build_prompt(self, question: str) -> str:
"""构建ReAct prompt"""
# 工具描述
tool_descriptions = "\n".join([
f"- {name}: {tool.description}"
for name, tool in self.tools.items()
])
tool_names = ", ".join(self.tools.keys())
prompt = f"""Answer the following question using these tools:
{tool_descriptions}
Use this format:
Question: the input question
Thought: think about what to do step by step
Action: the tool to use, must be one of [{tool_names}]
Action Input: the input to the tool
Observation: the result from the tool
... (repeat Thought/Action/Action Input/Observation as needed)
Thought: I now know the final answer
Final Answer: the final answer to the question
Important:
- Think step by step
- Only use one tool at a time
- Wait for Observation before continuing
- If you make a mistake, analyze why and try a different approach
Begin!
Question: {question}
Thought: """
return prompt
def _call_llm(self, prompt: str) -> str:
"""调用大语言模型"""
response = openai.ChatCompletion.create(
model=self.model,
messages=[{"role": "user", "content": prompt}],
temperature=0,
max_tokens=500
)
return response.choices[0].message.content.strip()
def _parse_action(self, response: str) -> tuple:
"""解析Action和Action Input"""
# 匹配 Action: xxx
action_match = re.search(r"Action:\s*(.+?)(?:\n|$)", response)
# 匹配 Action Input: xxx
input_match = re.search(r"Action Input:\s*(.+?)(?:\n|$)", response)
if action_match and input_match:
action = action_match.group(1).strip()
action_input = input_match.group(1).strip()
return action, action_input
return None, None
def _execute_action(self, action: str, action_input: str) -> str:
"""执行工具"""
if action not in self.tools:
return f"Error: Tool '{action}' not found. Available tools: {list(self.tools.keys())}"
tool = self.tools[action]
return tool.run(action_input)
def _extract_final_answer(self, response: str) -> str:
"""提取最终答案"""
match = re.search(r"Final Answer:\s*(.+?)(?:\n|$)", response, re.DOTALL)
if match:
return match.group(1).strip()
return response
4.3 使用示例
"""
main.py - 使用示例
"""
from react_agent import ReActAgent
from tools import TOOLS
# 设置API Key
import openai
openai.api_key = "your-api-key-here"
# 创建Agent
agent = ReActAgent(tools=TOOLS)
# 测试1:简单计算
print("\n" + "="*60)
print("测试1:计算问题")
print("="*60)
agent.run("计算 (123 + 456) * 2 的结果是多少?")
# 测试2:需要搜索
print("\n" + "="*60)
print("测试2:需要搜索信息")
print("="*60)
agent.run("北京明天的天气怎么样?")
# 测试3:多步推理
print("\n" + "="*60)
print("测试3:复杂推理")
print("="*60)
agent.run("23乘以47的结果是否大于1000?")
4.4 运行效果
============================================================
🎯 Question: 23乘以47的结果是否大于1000?
============================================================
【第1轮】
Thought: 我需要先计算23乘以47的值,然后判断结果是否大于1000
Action: Calculator
Action Input: 23 * 47
📊 Observation: 1081
------------------------------------------------------------
【第2轮】
Thought: 计算结果是1081,我现在需要判断1081是否大于1000。
显然,1081 > 1000,所以答案是肯定的。
Final Answer: 是的,23×47=1081,这个结果大于1000
============================================================
✅ Final Answer: 是的,23×47=1081,这个结果大于1000
============================================================
五、ReAct vs 普通Agent:实测对比
让我们用同一个任务测试两种Agent:
任务: "分析一下,如果我每天存100元,一年能存多少钱?如果这些钱投资年化5%的理财,一年后总共有多少?"
普通Agent的表现
【第1轮】
思考:计算一年的存款
工具:calculator(100 * 365)
结果:36500
【第2轮】
思考:计算理财收益
工具:calculator(36500 * 0.05)
结果:1825
【第3轮】
答案:37325元
❌ 问题: 答案错了!理财收益应该是逐步投入的,不是一次性投入。
ReAct Agent的表现
【第1轮】
Thought: 这个问题有两个部分:
1. 每天存100元,一年总存款
2. 这些钱投资理财的收益
第一个问题简单,100*365即可。
但第二个问题需要注意:钱是每天存入的,不是一次性投入,
所以收益计算要考虑时间因素。
先算总存款:
Action: Calculator
Action Input: 100 * 365
Observation: 36500
【第2轮】
Thought: 一年总存款36500元。
现在关键问题是理财收益。由于是每天存入,
平均每笔钱只存了半年(第一天的钱存365天,最后一天的钱存1天,
平均约182.5天)。
简化计算:36500 * 0.05 * 0.5 = 912.5
这是个近似值。
Action: Calculator
Action Input: 36500 * 0.05 * 0.5
Observation: 912.5
【第3轮】
Thought: 理财收益约912.5元。
总额 = 本金36500 + 收益912.5 = 37412.5元
Action: Calculator
Action Input: 36500 + 912.5
Observation: 37412.5
【第4轮】
Thought: 我现在知道最终答案了
Final Answer:
如果每天存100元,一年总共存入36500元。
由于是逐日存入(而非一次性投入),平均存期约半年,
按年化5%理财,收益约912.5元。
一年后总共约有37412.5元。
注:这是简化计算,实际收益取决于具体的计息方式。
✅ 看到区别了吗?
- ReAct考虑了逐日投入的特点
- 提供了更准确的计算逻辑
- 给出了注意事项
六、ReAct的5个进阶技巧
技巧1:添加自我反思
Thought: 我刚才的做法是否正确?
- 检查:输入参数是否有误?
- 检查:选择的工具是否合适?
- 检查:逻辑推理是否严密?
技巧2:使用中间变量
Thought: 让我定义一些变量来组织思路
- total_days = 365
- daily_saving = 100
- annual_rate = 0.05
这样更清晰
技巧3:分解复杂问题
Thought: 这个问题比较复杂,让我分解为3个子问题:
1. 先解决A
2. 再解决B
3. 最后整合
技巧4:记录已知信息
Thought: 目前我已知:
- X = 100
- Y = 200
还需要知道:
- Z = ?
技巧5:错误恢复
Thought: 上一步出错了,让我分析原因:
错误信息:XXX
可能原因:YYY
新的策略:ZZZ
七、常见问题与解决方案
Q1: 为什么Agent一直重复同样的操作?
原因: Prompt没有强调"不要重复"
解决:
prompt += """
Important:
- If an action failed, try a DIFFERENT approach
- Don't repeat the same action with the same input
"""
Q2: Agent的思考太简单怎么办?
解决: 使用思考模板
Thought:
- Current situation: [what I know]
- Goal: [what I want to achieve]
- Plan: [next step and why]
- Potential issues: [what could go wrong]
Q3: 如何处理工具返回的错误?
解决: 在Observation后添加错误处理提示
if "Error" in observation:
prompt += "\n(Hint: The tool returned an error. Analyze the error and try a different approach)"
八、实战练习
练习1:增强计算能力
任务: 让Agent处理这个问题
"计算从1到100的所有偶数之和"
提示: 可能需要循环或使用数学公式
练习2:多工具协作
任务: 添加一个文件读取工具
def read_file(filename: str) -> str:
with open(filename, 'r') as f:
return f.read()
让Agent读取文件并分析内容
练习3:优化Prompt
任务: 改进Prompt,让Agent:
- 思考更详细
- 更少出错
- 给出更好的答案
总结
今天我们深入学习了ReAct模式:
✅ 核心思想: Reasoning(推理) + Acting(行动)
✅ 关键要素: Thought → Action → Observation循环
✅ 成功关键: 高质量的Prompt设计
✅ 实战价值: 显著提升Agent的可靠性和智能程度
💡 核心洞察: ReAct的强大在于"显式推理"——让AI把思考过程说出来,就像教学生做题要写步骤一样。
下期预告
第3篇:《工具系统设计:给AI Agent装上"手和脚"》
下期我们将深入探讨:
- 如何设计一个健壮的工具系统
- Docker沙箱:让Agent安全地执行代码
- 5个强大的实用工具实现
- 工具编排和链式调用
如果这篇文章对你有帮助,欢迎点赞、分享!
本文是《AI Agent实战系列》的文章,后续还会更新AI Agent进阶玩法。如果你也AI Agent感兴趣,请关注公众号【架构之旅】,第一时间解锁全套实战教程,错过不再补~