提到 2024 年和 2025 年 AI 领域最火的词,AI Agent(智能体) 绝对榜上有名。我们不再满足于仅仅和 ChatGPT 聊天,而是希望它能变成一个超级助理:帮我们查资料、订机票、跑代码、处理 Excel。
但是,大语言模型(LLM)本质上是一个“缸中之脑”,它被困在服务器里,没有手脚,也无法感知当下真实的世界。为了让它成为真正能干活的 Agent,我们需要赋予它一套行动规划框架。
今天,我们就来深度扒一扒目前最经典、应用最广泛的 Agent 规划框架——ReAct。
一、 为什么我们需要 ReAct?
在 ReAct 出现之前,我们通常是怎么使用大模型的?
- Standard Prompting(标准提示):直接问“北京今天穿什么?”模型根据历史数据瞎猜一个温度回答你。(缺点:容易幻觉,缺乏实时信息)
- Chain of Thought / CoT(思维链):在 Prompt 里加一句“请一步步思考”。模型会输出:“因为北京现在是冬天,所以天气应该很冷...”(缺点:虽然逻辑变好了,但依然基于旧知识,无法与外部世界交互)
- Act-Only(纯行动):直接让模型输出调用天气的代码。(缺点:缺乏前置思考,一旦报错,模型不知道如何补救)
2022 年,普林斯顿大学和 Google 联合提出了一篇神仙论文:《ReAct: Synergizing Reasoning and Acting in Language Models》。
作者发现:如果把“推理(Reasoning)”和“行动(Acting)”结合起来,大模型的能力会发生质的飞跃。 就像人类解决问题一样:先想一想,做个动作,看看结果,再接着想。
二、 什么是 ReAct?(核心机制拆解)
ReAct 是 Reasoning(推理)和 Acting(行动)的缩写。
它的核心思想是让大模型在一个死循环里不断执行以下三个步骤,直到任务完成:
- 🤔 Thought(思考):当前我应该做什么?
- 🛠️ Action(行动):为了实现这个想法,我需要调用什么工具?(比如:搜索引擎、计算器、本地 API)
- 👀 Observation(观察):工具执行后返回了什么结果?
💡 举个栗子:查询北京天气并建议穿搭
如果我们用 ReAct 框架来处理 “北京今天天气怎么样?我该穿什么?” 这个任务,大模型的内心戏和外部动作是这样的:
User: 帮我看看北京今天天气如何,推荐一下穿搭。
Thought 1: 用户想知道北京的天气和穿搭建议。我需要先获取北京今天的实时天气。 Action 1: 调用工具
get_weather(location="北京")Observation 1: (系统执行工具后返回) 【北京今天气温:-5℃,大风】Thought 2: 现在我知道北京很冷,只有-5℃,而且有大风。我可以根据这个信息给出穿搭建议了。 Action 2: 调用工具
Finish(answer="北京今天气温-5℃,有大风。建议穿厚羽绒服,戴好防风帽和围巾。")
看到没有?通过 Thought -> Action -> Observation 的交替循环,模型不再是盲目瞎猜,而是有理有据、步步为营地解决问题。
三、 手搓 ReAct 核心代码(告别调包侠)
很多同学学习 Agent 时直接上手 LangChain,但 create_react_agent 一行代码隐藏了所有细节。为了让大家看透本质,我们不使用任何高级 Agent 库,用最原生的 Python 代码手写一个极简 ReAct 引擎。
1. 定义系统 Prompt
我们需要明确告诉大模型它的行为规则,并且告诉它有哪些工具可用。
system_prompt = """
你是一个聪明的 AI 助手,你可以通过遵循以下格式来解决用户的问题。
你必须严格按照这三个步骤循环:
Thought: 思考你需要做什么
Action: 执行具体的动作,必须是如下格式:函数名(参数)
Observation: 观察动作的结果
你可以使用的工具有:
1. calculate(expression: str): 计算数学表达式,例如 calculate("2 + 2")
2. search_weather(city: str): 查询城市天气,例如 search_weather("北京")
当你得到了最终答案,请使用以下格式结束:
Thought: 我已经知道了最终答案
Action: Finish("你的最终回答")
"""
2. 模拟外部工具(Tools)
准备几个简单的 Python 函数当作外部环境。
def calculate(expression):
try:
return str(eval(expression))
except Exception as e:
return f"计算错误: {e}"
def search_weather(city):
# 这里模拟调用天气 API
weather_data = {"北京": "晴,-2度", "上海": "小雨,10度", "广州": "阴,20度"}
return weather_data.get(city, "未知天气信息")
# 工具路由字典
tools_map = {
"calculate": calculate,
"search_weather": search_weather
}
3. 实现 ReAct 循环控制引擎
这就是 Agent 的“大脑控制中枢”。它不断调用 LLM 获取思考和动作,拦截动作去执行工具,再把结果喂回给 LLM。
import openai
import re
# 注意:请替换为你自己的 API 密钥和客户端初始化逻辑
client = openai.Client(api_key="your_api_key")
def react_agent(query):
# 初始化历史对话记录
messages =[
{"role": "system", "content": system_prompt},
{"role": "user", "content": query}
]
max_iterations = 5 # 防止死循环的保险丝
for i in range(max_iterations):
print(f"\n--- 第 {i+1} 轮思考 ---")
# 1. 让 LLM 输出 Thought 和 Action
response = client.chat.completions.create(
model="gpt-4", # 或其他模型
messages=messages,
stop=["Observation:"] # 核心技巧:一旦 LLM 准备输出 Observation,就立刻停住!
)
llm_output = response.choices[0].message.content.strip()
print(llm_output)
messages.append({"role": "assistant", "content": llm_output})
# 2. 解析 Action(使用正则提取工具名和参数)
if "Finish" in llm_output:
final_answer = re.search(r'Finish\("(.*?)"\)', llm_output, re.S)
print("\n🎉 任务完成:", final_answer.group(1) if final_answer else llm_output)
break
action_match = re.search(r'Action:\s*(\w+)\((.*?)\)', llm_output)
if action_match:
func_name = action_match.group(1)
arg_str = action_match.group(2).strip('"\'')
# 3. 执行工具,生成 Observation
print(f"🔧 系统拦截到动作,执行工具:[{func_name}] 参数:[{arg_str}]")
if func_name in tools_map:
obs_result = tools_map[func_name](arg_str)
else:
obs_result = "错误:找不到该工具"
observation = f"\nObservation: {obs_result}"
print(observation)
# 4. 把 Observation 加入上下文,进入下一轮
messages.append({"role": "user", "content": observation})
else:
print("解析 Action 失败,要求重试...")
messages.append({"role": "user", "content": "请严格遵循 Thought 和 Action 格式"})
# 测试我们手搓的 Agent
react_agent("北京的天气怎么样?如果温度加上25度是多少?")
4. 运行过程全解析
当你运行这段代码,你会看到控制台打印出如下类似的神奇过程:
--- 第 1 轮思考 --- Thought: 用户问了两个问题,第一个是北京的天气,我需要先查一下北京天气。 Action: search_weather("北京") 🔧 系统拦截到动作,执行工具:[search_weather] 参数:[北京] Observation: 晴,-2度
--- 第 2 轮思考 --- Thought: 刚才查到了北京气温是-2度。第二个问题是温度加上25度是多少。我需要计算 -2 + 25。 Action: calculate("-2 + 25") 🔧 系统拦截到动作,执行工具:[calculate] 参数:[-2 + 25] Observation: 23
--- 第 3 轮思考 --- Thought: 我已经计算出了结果,可以回答用户了。 Action: Finish("北京今天是晴天,气温-2度。将温度加上25度后的结果是23度。")
🎉 任务完成:北京今天是晴天,气温-2度。将温度加上25度后的结果是23度。
💡 划重点:注意代码中的 stop=["Observation:"]。这是一个非常关键的 Trick。如果不加这个,LLM 可能会自言自语,连带把伪造的观察结果也一起输出(即幻觉)。通过 Stop 参数,我们在它“准备看结果”的那一刹那打断它,让真实的程序接管工具调用,再把真实的观测塞回去。
四、 ReAct 框架的优缺点
通过手写代码,我们已经完全理解了它的机制。在真实的生产环境中,我们不得不正视它的优缺点:
🌟 优点
- 可解释性极强:每一步都有
Thought记录,如果 Agent 出错了,开发者可以立刻看出是“想错了”还是“工具用错了”,极其方便 Debug。 - 容错能力(Self-Correction):如果遇到 API 报错,Observation 会返回错误信息。模型在下一轮 Thought 看到错误后,通常会自动调整策略(比如换一个搜索词)。
⚠️ 局限性
- Token 消耗巨大:每一轮都要把之前所有的 Thought、Action、Observation 历史作为 Prompt 传进去。如果任务需要十几个步骤,上下文会迅速膨胀,既贵又慢。
- 陷入死循环:对于弱智一点的模型(或者小参数模型),可能会出现反复调用同一个工具,或者在 Action 和 Observation 之间来回震荡“死机”的情况。
- 缺乏宏观规划:ReAct 偏向于“走一步看一步”。对于需要拆解为几十个子任务的超级复杂目标,它容易“迷失方向”。(这也催生了后来的
Plan-and-Solve等更高级的框架)。
五、 总结
ReAct 本质上并不是什么高深晦涩的算法底层,而是一种巧妙的 Prompt 提示工程结构 + 程序循环控制。它巧妙地利用了 LLM 强大的文本推理能力,将其与外部真实世界搭起了一座桥梁。
了解了最原始的 ReAct 循环,以后你再去阅读 LangChain、AutoGPT 或是 LlamaIndex 的源码时,就会有一种“拨云见日”的感觉:它们底层无非是在包装这个循环,处理更复杂的工具输入输出解析而已。
关于 AI Agent 的探索才刚刚开始。 从 ReAct 出发,未来我们还有 Multi-Agent(多智能体协同)、Plan-and-Execute 等等更有趣的架构。
如果这篇文章让你对 AI Agent 有了更清晰的认识,欢迎点赞、收藏并在评论区交流探讨! 你在开发 Agent 时遇到过哪些坑?欢迎在评论区分享~ 👇