前言
你有没有试过让大模型帮你做一件稍微复杂点的事?比如:“帮我查一下今天上海的天气,再根据天气推荐穿什么,顺便算算如果去外滩玩大概要花多少钱。”
大多数时候,模型会直接给你一长串看似合理的回答。但仔细一看,它可能把气温记错了,或者把“外滩门票”这种根本不存在的东西编出来了。更要命的是,它不会中途停下来核实信息,也不会在发现自己算错了之后回头修正。
这就是传统“一次性生成”模式的局限——模型像一个只会写报告的书呆子,把所有思考都憋在脑子里,最后一次性吐出来。一旦中间哪一步出错,整篇报告就跟着歪了。
直到2022年,一群研究者提出了一个叫 ReAct 的框架,事情开始变得有趣起来。
ReAct的全称是 Reasoning + Acting(推理 + 行动)。它的核心思想非常简单,却异常有效:让AI像人一样,边想边干,边干边观察,再根据观察结果继续想。
听起来是不是有点像我们去一个从未去过的地方?先计划怎么走,然后向着目标地点走去。途中不免要看看路牌,然后发现自己走错了,再问路人该怎么走,然后纠正自己的行动路线,走到正确的路上,然后看看路牌......如此循环往复
今天我们就来好好聊聊这个“边想边干”的哲学,以及如何用不到一百行Python代码,亲手搓一个能真正“干活”的简单Agent。
一、ReAct到底解决了什么问题?
在ReAct出现之前,AI Agent大致走两条路:
- 书呆子——纯推理路线(比如Chain-of-Thought):让模型把思考过程全部写出来,再给出最终答案。好处是逻辑清晰,坏处是它无法获取外部真实信息,只能在自己的知识边界里兜圈子。
- 莽夫——纯行动路线(比如早期的工具调用):模型直接决定调用某个工具,拿到结果后就结束。好处是能碰触真实世界,坏处是缺少中间的思考过程,容易做出盲目行动。
ReAct把这两条路巧妙地拧在一起。它强制模型在每一步都明确输出三种东西:
- Thought(思考):我现在在想什么?接下来要做什么?为什么?
- Action(行动):具体调用哪个工具,参数是什么?
- Observation(观察):工具返回了什么结果?
然后把这个Observation再塞回模型,让它继续Thought……如此循环,直到模型认为任务完成,输出最终答案。
这种“思考-行动-观察”的闭环,有四个巨大的好处:
第一,可解释性大幅提升。你能清楚地看到模型每一步在想什么,出错时也更容易定位问题。
第二,错误纠正能力变强。如果某一步工具返回的结果和预期不符,模型可以在下一步的Thought里意识到“刚才可能查错了”,然后换个方式再试。
第三,减少幻觉。出错后立刻调整,边想边干,才不容易在回答时“瞎说”。
第四,处理实时信息。能够随时获取最新数据,完美解决模型知识库更新不及时的问题。
这就像一个靠谱的助手:他不会闷头写完一堆东西再交给你,而是边做边跟你汇报进度,发现问题立刻调整。
二、ReAct的工作流程,拆开来看
我们用一个具体例子把流程走一遍。
假设任务是:“查询当前比特币价格,并告诉我如果我现在有1万元人民币,大概能买多少个比特币。”
一个典型的ReAct循环可能是这样的:
Thought: 用户想知道比特币价格,以及1万元能买多少。我需要先获取最新价格。
Action: Search[比特币 当前价格]
Observation: 比特币当前价格约为 650,000 人民币/个
Thought: 已经知道价格了,现在可以计算了。1万元除以650000,大约是0.015个。
Action: Calculator[10000 / 650000]
Observation: 0.015384615384615385
Thought: 计算完成,可以给最终答案了。
Final Answer: 当前比特币价格约65万元人民币一个,1万元大约可以买0.0154个比特币。
注意几个关键点:
- Thought必须先于Action。模型不能直接跳到行动,必须先说明自己的意图。这强制它“想清楚再动手”。
- Action的格式要严格。通常用
工具名[参数]的形式,方便程序解析。 - Observation是真实世界的反馈。这一步完全由外部工具返回,模型无法“编造”观察结果。
- 循环可以继续。如果价格查询失败,或者计算结果异常,模型可以在下一个Thought里决定重新查询或换个工具。
整个过程就像一个状态机,状态在“思考中→行动中→观察中”之间不断切换,直到遇到“Final Answer”。
三、为什么ReAct特别适合Agent?
你可能会问:现在很多框架(LangChain、LlamaIndex、AutoGPT)都支持工具调用了,还需要专门强调ReAct吗?
答案是:ReAct不只是一种工具调用方式,它更是一种“思考范式”。
很多现代Agent框架底层其实都在用ReAct的变体。它们只不过把“Thought / Action / Observation”的文本格式,包装成了更优雅的函数调用接口(Function Calling)。但核心循环并没有变——模型依然需要决定“下一步做什么”,执行后拿到结果,再决定下一步。
ReAct真正的价值在于它把“推理过程”显式化了。这带来了几个实际好处:
- 调试友好:你可以打印出每一步的Thought,快速定位模型哪里想歪了。
- 可干预:你甚至可以在中间插入人工Observation,强制模型沿着正确方向走。
- 少幻觉:因为关键事实来自工具返回的Observation,而不是模型自己“回忆”,幻觉明显减少。
当然,ReAct也不是万能的。它的主要缺点是:
- Token消耗大:每一步都要把历史Thought和Observation塞进上下文,长任务很容易把上下文撑爆。
- 对模型能力要求高:如果模型本身推理能力弱,它可能会陷入“反复调用同一个无效工具”的死循环。
- 格式脆弱:如果模型输出的Action格式稍微乱一点,解析就会失败。
不过这些问题都有对应的工程解法(压缩历史、加反思机制、用结构化输出等),并不影响ReAct作为Agent基础范式的地位。
四、动手时间:用Python手写一个简单的ReAct Agent
理论说得再多,不如亲手写一个。下面我们用最朴素的方式,实现一个能真正调用工具的ReAct Agent。
为了不依赖外部付费API,我们这里用一个“模拟大模型”的方式演示核心逻辑。你完全可以把它替换成真正的OpenAI、Claude或本地模型调用。
1. 定义工具
先准备两个最基础的工具:一个搜索(这里用假数据模拟),一个计算器。
import re
from typing import Dict, Callable
# 工具字典
tools: Dict[str, Callable] = {}
def search(query: str) -> str:
"""模拟搜索工具"""
fake_db = {
"比特币价格": "当前比特币价格约为 650000 人民币/个",
"上海天气": "上海今天多云,气温 22°C,湿度 65%",
"北京天气": "北京今天晴,气温 18°C,空气质量良好",
}
for key, value in fake_db.items():
if key in query:
return value
return f"没有找到关于「{query}」的相关信息。"
def calculator(expression: str) -> str:
"""简单计算器,只支持基本运算"""
try:
# 安全起见,只允许数字和基本运算符
if not re.match(r'^[\d\s\+\-\*\/\.\(\)]+$', expression):
return "表达式包含非法字符"
result = eval(expression)
return str(result)
except Exception as e:
return f"计算错误: {e}"
tools["Search"] = search
tools["Calculator"] = calculator
2. 写一个超级简陋的“大模型”
真正项目中你会调用OpenAI的API。这里我们用一个规则引擎来模拟,方便大家看清整个流程。
def mock_llm(prompt: str) -> str:
"""
一个极度简化的模拟LLM。
真实场景请替换成 openai.ChatCompletion.create(...) 之类的调用。
"""
# 简单根据prompt内容决定输出
if "比特币" in prompt and "价格" in prompt and "Observation" not in prompt[-200:]:
return """Thought: 用户想知道比特币价格以及1万元能买多少,我需要先查询当前价格。
Action: Search[比特币价格]"""
if "650000" in prompt and "Calculator" not in prompt:
return """Thought: 已经获取到比特币价格为650000元,现在可以计算1万元能买多少个。
Action: Calculator[10000 / 650000]"""
if "0.015" in prompt or "0.015384" in prompt:
return """Thought: 计算完成,可以给出最终答案了。
Final Answer: 当前比特币价格约为65万元人民币一个。如果你有1万元人民币,大约可以买 0.0154 个比特币。"""
# 默认兜底
return """Thought: 我需要更多信息才能回答。
Action: Search[未知查询]"""
3. 核心ReAct循环
这是整个Agent的心脏。
def run_react_agent(question: str, max_steps: int = 5) -> str:
"""
运行一个简单的ReAct Agent
"""
# 初始prompt
history = f"""你是一个智能助手,可以使用以下工具:
Search[查询内容] - 搜索信息
Calculator[数学表达式] - 计算数学表达式
请严格按照以下格式回答:
Thought: 你的思考过程
Action: 工具名[参数]
或者
Thought: 你的思考过程
Final Answer: 最终答案
用户问题:{question}
"""
print("=" * 50)
print(f"用户问题:{question}")
print("=" * 50)
for step in range(max_steps):
print(f"\n--- 第 {step + 1} 步 ---")
# 1. 让模型思考并决定行动
response = mock_llm(history)
print(response)
# 2. 解析是否是Final Answer
if "Final Answer:" in response:
final = response.split("Final Answer:")[-1].strip()
print("\n" + "=" * 50)
print("任务完成!")
return final
# 3. 解析Action
action_match = re.search(r"Action:\s*(\w+)\[(.*?)\]", response, re.DOTALL)
if not action_match:
print("无法解析Action,结束。")
break
tool_name = action_match.group(1).strip()
tool_input = action_match.group(2).strip()
# 4. 执行工具
if tool_name not in tools:
observation = f"未知工具: {tool_name}"
else:
observation = tools[tool_name](tool_input)
print(f"Observation: {observation}")
# 5. 把Observation加入历史,继续循环
history += f"\n{response}\nObservation: {observation}\n"
return "达到最大步数,未能完成任务。"
4. 跑起来看看
if __name__ == "__main__":
result = run_react_agent("查询当前比特币价格,并告诉我1万元人民币能买多少个比特币")
print("\n最终结果:", result)
运行后你会看到类似这样的输出:
==================================================
用户问题:查询当前比特币价格,并告诉我1万元人民币能买多少个比特币
==================================================
--- 第 1 步 ---
Thought: 用户想知道比特币价格以及1万元能买多少,我需要先查询当前价格。
Action: Search[比特币价格]
Observation: 当前比特币价格约为 650000 人民币/个
--- 第 2 步 ---
Thought: 已经获取到比特币价格为650000元,现在可以计算1万元能买多少个。
Action: Calculator[10000 / 650000]
Observation: 0.015384615384615385
--- 第 3 步 ---
Thought: 计算完成,可以给出最终答案了。
Final Answer: 当前比特币价格约为65万元人民币一个。如果你有1万元人民币,大约可以买 0.0154 个比特币。
==================================================
任务完成!
恭喜,我们刚刚亲手实现了一个最简版的ReAct Agent。
虽然它很粗糙(模拟LLM、工具也极其有限),但核心骨架已经完整了:Thought → Action → Observation → 再Thought……这个循环,就是ReAct精髓所在
结语
ReAct最打动我的地方,其实不是它的技术有多高深,而是它重新定义了“智能”的呈现方式。
以前我们期待AI给出一个完美的最终答案。现在我们更希望它能像一个靠谱的同事:遇到问题先思考,不确定就去查,查完了再算,算错了就回头改。整个过程透明、可干预、可解释。
这种“边想边干”的模式,正在悄悄改变我们和AI协作的方式。
下次当你再让大模型帮你做复杂任务时,不妨试试明确要求它用ReAct的方式输出。你会发现,那个原本只会“一本正经胡说八道”的模型,突然开始变得靠谱起来。
而这一切的起点,不过是让它学会说三句话:
我在想什么。
我准备做什么。
我看到了什么。
然后,循环往复,直到事情被真正解决。
这大概就是ReAct最迷人的地方——它没有发明什么全新的魔法,只是把人类解决问题时最朴素的节奏,教给了机器。