• 正文
  • 相关推荐
申请入驻 产业图谱

为什么你的智能体总是在“瞎编”?一文搞懂ReAct:让 AI 学会“边想边做”

16小时前
180
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

拿到结果就必须给答案?算命先生 还是侦探?

01、一个翻车的股票查询

INCIDENT · 一次真实事故

先用选股举一个例子。

我们用上一篇文章的思想写一个简化版的选股小助手,用户问它:“帮我看看苹果和SpaceX,哪个现在更贵,然后告诉我更贵的那只这几天是涨还是跌。”

模型看到问题,判断需要查两次股价,“啪”一下同时发起了两个 get_stock_price 调用,一个查AAPL一个查SPCX,我们的代码乖乖执行,把两个结果都喂回去。然后呢?

!然后就没有然后了

模型直接编了一句“苹果更贵,最近呈上涨趋势”就把话说完了。后半句纯属瞎编——它压根没有调用第三个工具去查涨跌走势,拿到两个股价之后就以为任务完成了,直接开始“编故事”忽悠用户。

问题出在哪?上一篇的代码里,我们的逻辑是:第一次调用工具→拿到结果→立刻逼模型生成最终答案,说白了就是“只许模型转一次接”。可这个任务明明需要“先比较出谁更贵,再根据这个中间结论决定接下来该查什么”,这是一个需要多轮、有依赖关系的任务,而我们的代码只给了它一次伸手的机会。

这就是今天要讲的东西——ReAct(Reasoning + Acting)——要解决的核心问题

02、破案的侦探,和瞎蒙的算命先生

ANALOGY · 两种决策方式

想理解 ReAct,我最喜欢拿“侦探破案”打比方。

福尔摩斯破案时是怎么工作的?他不会看一眼现场就直接甩出凶手名字。他的套路永远是:

这个脚印很深,说明凶手体重较重(推理)——我需要去查一下附近有没有体重较重的嫌疑人(行动:调查)——查到了,有一个人符合(观察)——但他的不在场证明很充分,说明凶手另有其人,我得重新推理(再次推理)……

福尔摩斯的厉害之处,不是他一步到位猜对了凶手,而是他每走一步,都会先说出自己的推理逻辑,再决定下一步该调查什么,调查完再根据新线索调整推理——这是一个不断循环、步步为营的过程。

而算命先生呢?摸你手一次,立刻给你判个“命里缺水”,不管准不准,反正就这一锤子买卖,绝不回头修正。

上一篇里我们写的智能体,本质上更像那个算命先生:拿到工具结果,立刻被逼着输出“最终答案”,没有回头修正、继续深挖的机会。

ReAct,就是要把智能体从“算命先生”训练成“福尔摩斯”

03、ReAct 到底是啥:Thought → Action → Observation 的循环

MECHANISM · 循环公式

ReAct 这个概念来自一篇论文(Reasoning and Acting),核心思想特别朴素,就是让模型在每次决定“要不要用工具、用哪个工具”之前,先用大白话把自己的推理过程说出来,然后再采取行动,行动完拿到的结果又反过来影响下一轮的推理。写成公式就是:

...flow

Thought(我现在怎么想的)

→ Action(基于这个想法,我决定采取什么行动)

→ Observation(行动后我观察到了什么结果)

→ 回到 Thought(根据新的观察,我重新想一下)

→ ……如此循环,直到我觉得可以给出最终答案了

拿上面的例子套一下,一个“合格”的 ReAct 循环应该长这样:

...trace

Thought 1:用户想知道两只股票哪个更贵,我需要先分别查一下它们的价格。

Action 1:get_stock_price("AAPL"),get_stock_price("SPCX")

Observation 1:苹果 324.98美元,SpaceX 136.83美元

Thought 2:苹果明显更贵,现在我需要查一下苹果最近的涨跌走势,才能回答用户的第二个问题。

Action 2:get_stock_trend("AAPL")

Observation 2:最近1月上涨了6%

Thought 3:现在信息齐全了,我可以给出完整答案了。

Final Answer:苹果(324.98美元)比SpaceX(136.83美元)更贵,且最近1个月呈上涨趋势,涨幅约6%。

对比一下上一篇那个“翻车版本”,区别一目了然:ReAct 版本允许模型在拿到第一轮结果后,重新思考、再发起新一轮行动,而不是被摁着头强行给出答案。这也是为什么很多资料把 ReAct 循环叫做“智能体的心跳”,它不是一锤子买卖,而是一下一下地“想—做—看—再想”,直到任务真正完成。

04、动手写一个真正会“回头修正”的智能体

HANDS-ON · while 循环实战

理解了原理,我们把上一篇那个只能“转一次接”的代码,升级成能完整循环的 ReAct 版本。核心改动就一个:把“调用工具→生成最终答案”这两步写成的死板流程,改造成一个 while 循环,让模型可以持续“想—做—看”,直到它自己觉得信息齐全了为止。

这次依然用 deepseek-v4-flash 来跑:

...python

import json
from openai import OpenAI

client = OpenAI(
api_key="sk-xxxxx",
base_url="https://api.deepseek.com",
)

# ------- 工具函数(假数据模拟,实际项目里换成真实 API 请求) -------
def get_stock_price(code: str) -> str:
fake_db = {
"AAPL": "324.98美元",
"SPCX": "136.83美元",
}

return fake_db.get(code, "暂无该股票数据")

def get_stock_trend(code: str) -> str:
fake_db = {
"AAPL": "最近1个月上涨6%",
"SPCX": "最近1个月下帖29%",
}

return fake_db.get(code, "暂无该股票走势数据")

tool_map = {
"get_stock_price": get_stock_price,
"get_stock_trend": get_stock_trend,
}

tools_schema = [
{
"type": "function",
"function": {
"name": "get_stock_price",
"description": "查询指定股票代码的当前价格",
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "股票代码",
}
},
"required": ["code"],
},
},
},
{
"type": "function",
"function": {
"name": "get_stock_trend",
"description": "查询指定股票代码最近的涨跌走势",
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "股票代码",
}
},
"required": ["code"],
},
},
},
]

# 关键改动:用系统提示词,明确要求模型每次行动前先说出推理过程
SYSTEM_PROMPT = """
你是一个善于逐步推理的助手。

每次决定是否调用工具前,
请先在回复中用一两句话说明你的推理过程(Thought),
再决定要不要调用工具。

如果当前信息已经足够回答用户问题,
就不要再调用任何工具,直接给出最终答案。
"""

def run_react_agent(user_input: str, max_steps: int = 6):
messages = [
{
"role": "system",
"content": SYSTEM_PROMPT,
},
{
"role": "user",
"content": user_input,
},
]

for step in range(max_steps):
response = client.chat.completions.create(
model="deepseek-v4-flash",
messages=messages,
tools=tools_schema,
)

reply = response.choices[0].message

# 把模型这一轮的"心里话"打印出来,方便我们观察它的推理过程
if reply.content:
print(f" [Thought {step + 1}] {reply.content}")

# 核心判断:模型这一轮还想不想继续调用工具?
if not reply.tool_calls:
# 不想调用了,说明它认为信息已经齐全,这就是 Final Answer
return reply.content

# 将模型的回复加入消息列表
messages.append(reply)

# 模型可能一次性发起多个工具调用
# 比如同时查询两只股票
for call in reply.tool_calls:
func = tool_map.get(call.function.name)

args = json.loads(call.function.arguments)

result = (
func(**args)
if func
else f"未知工具:{call.function.name}"
)

print(
f" [Action] "
f"{call.function.name}({args}) → {result}"
)

messages.append(
{
"role": "tool",
"tool_call_id": call.id,
"content": str(result),
}
)

# 循环继续,模型会看到这一轮的 Observation,
# 进入下一次 Thought

return "抱歉,思考轮数超过上限,未能得出最终答案。"

if __name__ == "__main__":
answer = run_react_agent(
"帮我看看苹果(AAPL)和SpaceX(SPCX),"
"哪个现在更贵,然后告诉我更贵的那只这几天是涨还是跌。"
)

print("n最终答案:", answer)

跑起来后,你会在终端看到一段完整的“心路历程”,大概像这样:

output

[Thought 1] 我来查一下苹果(AAPL)和SpaceX(SPCX)的当前价格。[Action] get_stock_price({'code': 'AAPL'}) → 324.98美元

[Action] get_stock_price({'code': 'SPCX'}) → 136.83美元

[Thought 2] 好的,苹果(AAPL)当前价格是 **324.98美元**,SpaceX

(SPCX)是 **136.83美元**,所以 **苹果更贵**。接下来查一下苹果这几天的涨跌走势。

[Action] get_stock_trend({'code': 'AAPL'}) → 最近1个月上涨6%

[Thought 3] ## 结果汇总

 

| 股票 | 当前价格 |

|------|---------|

| **苹果 (AAPL)** | **324.98 美元** ✅ 更贵 |

| SpaceX (SPCX) | 136.83 美元 |

**结论:苹果更贵。**

至于苹果这几天的走势——根据数据,**苹果最近1个月上涨了 6%**,所以这几天总体是 **上涨 ** 的态势。

最终答案: ## 结果汇总

| 股票 | 当前价格 |

|------|---------|

| **苹果 (AAPL)** | **324.98 美元** ✅ 更贵 |

| SpaceX (SPCX) | 136.83 美元 |

**结论:苹果更贵。**

至于苹果这几天的走势——根据数据,**苹果最近1个月上涨了 6%**,所以这几天总体是 **上涨 ** 的态势。

这一次,模型没有在拿到第一批数据后就“编故事”,而是老老实实地推理出“我还需要再查一步”,然后真的又发起了一轮新的工具调用。这就是 ReAct 循环的威力,它把智能体从“一锤子买卖”变成了“能自己判断任务有没有做完”的持续决策过程。

细心的你可能发现了,代码里我加了个 max_steps=6 的上限。这不是随手写的,而是一个必须要有的安全阀:

!防止死循环烧穿额度

万一模型陷入某种逻辑死循环(比如反复查同一个数据、永远觉得信息不够),没有这个上限,你的程序就会陷入无限循环,直接把你的API额度烧穿。这个“防止死循环”的话题,我们会在第7篇错误处理里更系统地展开。

05、有没有“想”,到底差在哪?

COMPARE · 效果对比

我们直接做个对比,感受一下有没有显式推理这一步,效果差多少:

NO THOUGHT

一口气把能想到的工具全调了一遍,被迫“收尾”,哪怕信息不够也得瞎编答案凑数,像让算命先生“必须在30秒内下结论”。

WITH THOUGHT

每走一步都显式“复盘”,现在掌握什么信息、还缺什么、下一步该干嘛。任务完成度更高,可调试性也直线上升。

以前模型“黑箱”一样直接吐结果,你压根不知道它是怎么想的、错在哪一步;现在每一步的 Thought 都被打印出来,你调试的时候一眼就能看出,“哦,它是在第二步的推理里理解错了用户意图”,定位问题的效率天差地别。

这也是为什么几乎所有正经的智能体框架,不管是 LangChain 的 AgentExecutor,还是 AutoGPT 那套东西,底层都跑着类似 ReAct 的循环骨架。它不是什么高深的黑科技,就是老老实实地把“人类解决问题时会做的事”:想一步、做一步、看结果、再想一步,搬到了代码里,用一个 while 循环实现出来。

下一个问题:想是想明白了,但它记不住

写在最后

跑几次上面的例子你会发现一个新问题:这个 ReAct 循环,只在单次任务内部“能想会做”,可一旦这次对话结束,你换个新问题问它“那刚才查的苹果走势具体是哪个月?”,它一脸懵:因为上一次对话的所有 Thought 和 Observation,早就随着这次调用结束烟消云散了。

它有了“边想边做”的能力,却依然是个“金鱼记忆”——没有长期记忆,没法把这次查到的信息沉淀下来,供未来的对话复用。

下一篇要解决的问题:如何给智能体装上一套记忆系统

让它不只是在单次任务里能循环思考,还能跨越多次对话,记住关键信息,甚至学会取舍:哪些信息该记、哪些该及时忘掉,避免记忆过载变成噪音。下一篇见。

 

我是写代码的中年人,长期专注于 AI 算法、智能体、以及 AI 工程化落地。

相关推荐