Agent 核心架构:思考-行动-观察循环(ReAct)
ReAct 是 Agent 最经典的工作模式:思考要做什么 → 执行动作 → 观察结果 → 再思考下一步。这篇深入讲 ReAct 的原理、Prompt 模板设计、循环控制、停止条件,以及实际开发中的常见问题。
大家好,我是黒漂技术佬。
上一篇讲了 Agent 是什么,这篇深入核心架构:ReAct 循环。
Reason + Act = 推理 + 行动。Agent 不是一次性输出答案,而是像人解决问题一样,先想想要干嘛,动手做,看看结果,再决定下一步,循环往复直到搞定。
这篇讲 ReAct 的原理、Prompt 怎么写、循环怎么控制、怎么判断任务完成。
一、ReAct 是什么?
来源
ReAct 是 2022 年普林斯顿和 Google 提出的方法,论文《ReAct: Synergizing Reasoning and Acting in Language Models》。
核心思想:让大模型把「推理过程」和「行动」交替输出,边想边做。
循环结构
┌─────────────────────────────────────┐ │ Thought(思考):我现在要做什么? │ │ ↓ │ │ Action(行动):调用某个工具 │ │ ↓ │ │ Observation(观察):工具返回结果 │ │ ↓ │ │ 再 Thought:根据结果想下一步 │ └─────────── 循环直到完成 ────────────┘每一轮输出一个 Thought + Action,拿到 Observation 后进入下一轮。
最终输出
当模型判断信息足够了,就输出 Final Answer,结束循环。
二、完整的 ReAct 示例
问题
张三出生于哪一年?他创办的公司上市了吗?
ReAct 过程
问题:张三出生于哪一年?他创办的公司上市了吗? Thought: 我需要先知道张三是谁、创办了什么公司,然后查他的出生年份和公司上市情况。 先搜索张三的基本信息。 Action: 搜索「张三 创始人 公司」 Observation: 张三,1980年出生,某科技公司创始人兼CEO,公司名为"未来科技", 2015年成立,主要做人工智能。 Thought: 知道了,张三1980年出生,创办了未来科技。接下来要查这家公司有没有上市。 Action: 搜索「未来科技 是否上市」 Observation: 未来科技于2021年在科创板上市,股票代码688XXX。 Thought: 信息都齐了:张三1980年出生,他的公司未来科技2021年科创板上市。 可以给出最终答案了。 Final Answer: 张三出生于1980年。他创办的未来科技公司已于2021年在科创板上市。两轮思考+行动,就把问题解决了。
三、ReAct Prompt 模板
系统提示词(System Prompt)
告诉模型怎么按 ReAct 格式输出。核心要素:
- 说明有哪些工具可用
- 规定输出格式(Thought / Action / Action Input)
- 说明循环规则
- 什么时候输出 Final Answer
一个标准模板
你是一个智能助手,可以使用以下工具来回答问题: 工具列表: - 搜索:输入关键词,搜索互联网信息 - 计算器:输入数学表达式,计算结果 - 天气查询:输入城市名,获取实时天气 回答问题时严格按照以下格式输出: Thought: 你对当前问题的思考,说明接下来要做什么 Action: 工具名称 Action Input: 工具的输入参数 Observation: 工具返回的结果(这部分由系统填入,不用你写) (然后继续下一轮 Thought/Action,直到信息足够) 当你认为已经有足够信息回答问题时,输出: Final Answer: 最终的答案 注意: 1. 每次只输出一个 Action 2. 必须有 Observation 才能继续思考 3. 不确定的时候就搜索,不要瞎猜 4. 信息足够就立刻给出 Final Answer,不要多余步骤为什么格式要严格?
因为程序要解析模型的输出,提取 Action 和 Action Input,然后去调用工具。格式乱了解析失败,整个循环就断了。
实际开发中,Function Calling 原生支持的模型更方便,不用手写解析。但理解 ReAct 的原理很重要。
四、循环控制流程
程序侧的执行流程
# 伪代码defagent_run(question,max_steps=10):history=""step=0whilestep<max_steps:# 1. 把历史拼到 prompt 里,调用大模型prompt=system_prompt+history+f"\n问题:{question}"response=llm(prompt)# 2. 解析模型输出if"Final Answer:"inresponse:returnextract_answer(response)action,action_input=parse_action(response)history+=response+"\n"# 3. 调用工具observation=call_tool(action,action_input)history+=f"Observation:{observation}\n"step+=1return"达到最大步数,未能完成任务"关键要素
- 历史记录:每轮的 Thought/Action/Observation 都要传给模型,它要看着前面的过程继续想
- 解析输出:从模型回复里提取 Action 和参数
- 工具调用:执行实际的工具函数
- 停止条件:输出 Final Answer 或者达到最大步数
五、停止条件
正常停止:Final Answer
模型自己判断信息足够了,输出 Final Answer,循环结束。
异常停止
1. 最大步数限制
防止模型死循环,设一个最大轮数(比如 10 步),到了就强制停止。
ifstep>=max_steps:return"执行步数超限,任务未完成"2. 重复 Action 检测
连续调用同一个工具同样的参数,说明卡住了,强制停止。
3. 错误累积
工具连续报错,说明方向不对,停止并提示。
4. 超时
整体执行时间设上限,防止某个步骤卡住太久。
六、ReAct 的变种
1. CoT(Chain of Thought)
只有思考,没有行动。就是让模型一步步想,但不调用外部工具。适合纯推理问题。
问题:一个苹果3块,买5个多少钱? 思考:一个3块,5个就是3×5。 计算:3×5=15 答案:15块ReAct = CoT + 工具调用。
2. Plan-and-Execute
先做完整规划,再按计划执行。
Plan: 1. 查A 2. 查B 3. 对比总结 执行步骤1... 执行步骤2... 执行步骤3...适合任务比较明确、步骤可以预先规划的场景。ReAct 是走一步看一步,Plan-and-Execute 是先想好全部再做。
3. Reflexion(反思型)
执行完之后反思效果好不好,不好就重来。
执行 → 评估结果 → 反思问题 → 重新执行多了一个反思的步骤,质量更高但也更慢更贵。
七、Function Calling vs 手写 ReAct
原生 Function Calling
现在主流大模型(GPT、Claude、Qwen、DeepSeek)都原生支持 Function Calling / Tool Use。
你给模型一份工具的 JSON Schema 描述,模型直接输出要调用哪个函数、参数是什么,结构化输出,不用自己解析。
// 工具描述{"name":"search","description":"搜索互联网信息","parameters":{"type":"object","properties":{"query":{"type":"string","description":"搜索关键词"}},"required":["query"]}}模型返回:
{"tool_calls":[{"name":"search","arguments":{"query":"张三 出生日期"}}]}对比
| 手写 ReAct Prompt | 原生 Function Calling | |
|---|---|---|
| 格式稳定性 | 容易乱,解析容易失败 | 结构化输出,稳定 |
| 工具数量 | 少了还行,多了容易乱 | 支持多工具 |
| 兼容性 | 所有模型都能用 | 需要模型支持 |
| 开发成本 | 高,要写解析逻辑 | 低,直接用API |
实际开发优先用原生 Function Calling。但理解 ReAct 的原理有助于调试和设计 Agent。
八、常见问题和优化
问题 1:模型不按格式输出
原因:Prompt 约束不够强,或者模型能力不够。
解决:
- 系统提示词写得更详细、更严格
- 给 1-2 个示例(Few-shot)
- 用支持 Function Calling 的模型
- 输出解析加容错,匹配不到就重试
问题 2:死循环,反复调用同一个工具
原因:模型判断不了信息够不够,或者工具返回的结果它理解不了。
解决:
- 最大步数限制
- 重复检测,连续 2-3 次同样的调用就强制停止
- Prompt 里强调「信息足够就给 Final Answer」
- 工具返回结果做摘要,别把太长的原文直接塞进去
问题 3:跳过工具,直接瞎答
原因:模型偷懒,或者没意识到需要用工具。
解决:
- Prompt 里强调「不知道的就搜索,不要编造」
- 系统提示词里加「禁止直接回答需要实时信息的问题」
- 用更听话的模型(比如 GPT-4)
问题 4:上下文太长爆了
原因:轮次多了,历史记录越来越长,token 超了。
解决:
- 工具结果做摘要,只保留关键信息
- 历史记录截断,只保留最近几轮
- 用总结代替完整历史
问题 5:工具调用参数错
原因:模型没理解工具参数的含义,或者格式不对。
解决:
- 工具描述写清楚每个参数的含义和格式
- 加参数校验,不对就让模型重试
- 给调用示例
九、一个最小可运行的 ReAct Agent(伪代码)
tools={"search":lambdaquery:search_web(query),"calc":lambdaexpr:str(eval(expr)),}system_prompt=""" 你可以使用以下工具: - search: 搜索,输入关键词 - calc: 计算器,输入数学表达式 格式: Thought: 思考 Action: 工具名 Action Input: 参数 Observation: 结果 信息足够就输出 Final Answer: 答案 """defrun(query):history=""for_inrange(10):# 最多10步prompt=system_prompt+history+f"\n问题:{query}\nThought:"resp=llm(prompt)if"Final Answer:"inresp:returnresp.split("Final Answer:")[-1].strip()# 解析 actionaction=extract(resp,"Action:")action_input=extract(resp,"Action Input:")# 调用工具obs=tools[action](action_input)history+=f"Thought:{resp}\nObservation:{obs}\n"return"超出步数限制"实际开发不用自己写这么底层,LangChain 之类的框架都封装好了。但原理就是这么回事。
十、本篇小结
- ReAct = Reason(推理)+ Act(行动),思考-行动-观察循环
- 标准格式:Thought → Action → Observation,循环直到 Final Answer
- 程序侧控制循环:拼历史 → 调模型 → 解析动作 → 调工具 → 拼结果 → 下一轮
- 停止条件:输出 Final Answer、达到最大步数、重复检测、超时
- 变种:CoT(纯思考)、Plan-and-Execute(先规划再执行)、Reflexion(带反思)
- 实际开发优先用原生 Function Calling,比手写 ReAct 稳定很多
- 常见问题:格式乱、死循环、跳过工具瞎答、上下文太长、参数错
下一篇讲工具调用:Function Calling 的具体用法、工具怎么定义、多工具怎么选、常见的工具类型。
我是黒漂技术佬。