
ReAct Agent执行细节深度解析从 Thought-Action-Observation 循环到安全护栏2025 年 11 月4 个 LangChain Agent 因未设置步数限制连续运行了11 天消耗$47,000才被发现。问题不在于模型不够聪明——而是 Agent 的 ReAct 循环缺少基本的安全护栏。Thought、Action、Observation 这三步看似简单但每一步都可能让 Agent 进入死循环、Token 爆炸、工具误调用。真正可用的 Agent不是能推理的模型而是有边界控制的推理系统。一、ReAct 框架核心一个三元循环如何让模型边想边做1.1 什么是 ReActReActReasoning Acting是 2022 年由 Google 和普林斯顿提出的框架核心思想是让语言模型交替进行推理和行动而不是一次性生成最终答案。传统 LLM 模式Chain-of-Thought- 用户提问 → 模型思考 → 生成最终答案- 问题模型在真空中思考无法获取外部信息ReAct 模式- 用户提问 → Thought(思考) → Action(行动) → Observation(观察) → Thought → Action → ... → 最终答案- 核心模型可以边想边做、边做边看、看了再想1.2 三元循环详解每一步的具体内容阶段做什么举例Thought思考分析当前信息决定下一步做什么。输出的是推理不是行动用户想查北京天气我需要调用天气 API。先确认一下城市参数是否正确。Action行动执行一个具体操作——调用工具、搜索文件、发送请求。输出的是工具调用get_weather(cityBeijing)Observation观察接收行动的结果作为下一轮思考的输入。这是 Agent 与外部世界的感官{city: Beijing, temp: 28, weather: 晴}关键洞察ReAct 的核心不是模型更聪明了而是模型能从外部世界获取反馈并根据反馈调整行为。这跟人类的试错学习非常相似——你试了一个方法看到结果决定下一步怎么走。1.3 一个完整的 ReAct 执行示例用户帮我看看明天北京的天气如果下雨就提醒我带伞。 第 1 轮 Thought: 用户想知道明天北京天气。我需要先获取天气预报。 Action: get_weather(city北京, date2026-06-29) Observation: {date: 2026-06-29, weather: 中雨, temp: 22-28℃} 第 2 轮 Thought: 明天北京中雨需要提醒用户带伞。 Final Answer: 明天6月29日北京有中雨气温22-28℃出门记得带伞总共 2 轮就完成了——这就是标准的多步推理执行。二、Agent 如何知道自己该干什么——状态感知与工具选择2.1 Agent 的自我意识从哪来Agent 不像传统程序有明确的状态变量。它的状态感知来自上下文中积累的信息Agent 的状态 累积的上下文。每一轮循环中Agent 看到的上下文包括System Prompt— 角色定义、安全边界、工具列表用户原始任务— 最初的目标所有历史 Thought— 之前的推理链所有历史 Action Observation— 已执行的工具调用和结果当前轮次的思考空间— 模型正在生成的 ThoughtAgent 通过阅读这些累积信息判断我已经做了什么还缺什么接下来该做什么2.2 工具选择的决策逻辑Agent 选择工具不是随机试而是基于语义匹配 上下文推理工具选择的三个输入1. 当前 Thought 的结论 — 我需要查天气2. 可用工具的 name description —get_weather: 查询指定城市的天气3. 工具的参数 schema —{city: string, date: string}决策过程模型内部推理非显式代码1. 将 Thought 与所有工具描述做语义匹配2. 筛选出相关工具通常 1-3 个候选3. 检查是否具备调用条件参数是否齐全4. 选择最佳工具填充参数生成 tool_call关键洞察Agent 选择工具的过程完全基于语言理解没有硬编码的 if-else。这意味着当用户说帮我查查明天要不要带伞时Agent 能理解这等于查天气 判断降水概率——即使 tool description 里没有提到伞。2.3 Observation 之后的下一步判断Observation 返回后Agent 面临三个选择判断条件行为继续执行Observation 提供了新信息但还不够完成任务进入下一轮 Thought继续规划调整方向Observation 显示之前的路径不对如工具返回空、报错换一个工具或调整参数重试生成答案Observation 已经提供了足够信息停止工具调用生成 Final Answer示例Observation 驱动方向调整第 1 轮 Thought: 需要读取 config.yaml Action: read_file(config.yaml) Observation: Error: File not found 第 2 轮 Thought: config.yaml 不存在可能文件名是 config.yml。先搜索一下目录里有哪些配置文件。 Action: search_file(patternconfig.*) Observation: [config.yml, config.prod.yml] 第 3 轮 Thought: 找到了 config.yml现在读取它。 Action: read_file(config.yml) Observation: ...文件内容 → 继续处理...三、ReAct vs If-Else为什么不能把控制流写死3.1 If-Else 的天花板传统编程的控制流是预定义的# 传统 if-else 模式 def handle_user_request(task): if 天气 in task: city extract_city(task) weather get_weather(city) if 雨 in weather: return f{weather}记得带伞 else: return f{weather} elif 文件 in task: # 读文件、处理... pass else: return 我不知道怎么处理这种方式的致命缺陷问题说明组合爆炸每增加一种工具可能的执行路径呈指数增长。10 个工具 × 10 步 100 种组合if-else 根本写不完无法应对未知查天气读文件发邮件这种组合需求必须提前写死分支缺乏纠错工具调用失败只能按预设的错误处理走无法换个思路试试不灵活用户说帮我看看明天要不要带伞if-else 匹配不到关键词就傻了3.2 ReAct 如何解决ReAct 的本质差异if-else 模式人提前定义所有可能的路径ReAct 模式模型在每一步现场决定路径类比if-else 跟着导航开走错了只能掉头ReAct 看着路牌开走错了可以绕路。关键能力来源不是代码写得好而是模型具备语义理解 常识推理 试错调整。关键洞察ReAct 的灵活性本质上来自将控制流从代码层提升到语义层。控制流不再由程序员预定义而是由模型在上下文中实时推理生成。这意味着你不需要预见到所有可能的情况——你只需要给模型工具和判断标准它会自己找出路径。3.3 但灵活性也有代价ReAct 的优势同时也是风险来源好处- 可以自动组合多个工具完成复杂任务- 遇到错误能换策略重试- 用户用自然语言表达意图即可不需要精确命令代价- 模型可能想太多进入无意义的工具调用循环- 每一步都要消耗 Token成本线性增长- 没有硬边界可能永远停不下来- 模型的现场决定不一定是最优解四、安全护栏一最大步数限制——最简单也最有效4.1 为什么必须限制步数回到开头的 $47,000 案例4 个 Agent 连续运行 11 天根本原因是没有步数上限。Agent 在某个任务上陷入循环不断调用工具、获取空结果、再次调用——永不停机。无步数限制的典型灾难场景轮次 1: Thought: 我需要读文件 X 轮次 2: Action: read_file(x) → Observation: 文件不存在 轮次 3: Thought: 文件不存在换个路径试试 轮次 4: Action: read_file(/path/x) → Observation: 文件不存在 轮次 5: Thought: 再换个路径... ... 轮次 847: 还在试...每一步都消耗 Token 工具调用成本且没有进度。4.2 如何设置合理的步数上限步数限制的经验值基于生产环境数据轻量任务单次查询/简单操作max_steps 5-10例查天气、翻译文本、格式化代码中等任务多步推理/文件操作max_steps 15-25例代码审查、Bug 修复、文档生成复杂任务多工具编排/项目级操作max_steps 30-50例项目初始化、全链路测试、数据库迁移原则宁可设低快速失败也不要设太高。快速失败 → 用户重新描述需求 → 2 秒后重新开始设太高 → Agent 在错误的路径上消耗大量 Token → 浪费钱和时间。实现方式示例框架配置agent.invoke(task, max_iterations20)Prompt 注入你最多只能执行 15 步操作之后必须给出当前结果代码层硬限制在 while 循环中if step_count MAX_STEPS: break4.3 超限后的行为设计步数用完后不只是停止——需要优雅降级停止所有工具调用总结已完成的工作我已经完成了 A、B、C 三个步骤说明未完成的部分D 步骤因为步数用完未执行建议用户如何继续你可以直接要求我执行 D 步骤错误做法直接丢弃上下文返回我无法完成任务正确做法保留进展让用户可以从断点继续五、安全护栏二重复动作检测——别让 Agent 原地打转5.1 什么是重复动作循环Agent 最常见的失控模式不是步子太多而是同样的动作重复执行模式一相同工具 相同参数round 3: search_file(config.*) → 3 个结果 round 4: search_file(config.*) → 还是那 3 个结果 round 5: search_file(config.*) → 又来一遍...模式二A→B→A→B 摆动round 3: read_file(x) → 文件太大 round 4: grep(keyword, x) → 找到了 round 5: read_file(x) → 又读一遍... round 6: grep(keyword, x) → 又搜一遍...模式三渐进式重复最隐蔽round 3: search_file(config.*) → 无结果 round 4: search_file(*config*) → 无结果 round 5: search_file(*.yaml) → 无结果 round 6: search_file(*.yml) → 无结果 ...同类型工具无限尝试不同参数5.2 检测策略策略一精确匹配检测完全相同的工具调用- 记录每轮的 (tool_name, params_hash)- 如果连续 2 次完全相同 → 触发告警 → 强制 Agent 换方向策略二语义相似匹配检测换汤不换药的调用- 计算连续工具调用的语义相似度- 如果相似度 0.8 → 可能陷入变体循环策略三类别计数检测一直在搜但找不到的模式- 统计同类型工具的连续调用次数- 如果 read_file 连续失败 5 次 → 建议 Agent 换个思路策略四进度检测最根本的方法- 每轮对比 Observation 是否包含新信息- 如果连续 3 轮 Observation 无实质性进展 → 可能陷入停滞策略检测能力误报风险推荐度精确匹配相同调用几乎为零必备语义相似变体循环中等推荐类别计数渐进式重复较低建议有进度检测信息停滞较高需调阈值作为兜底5.3 触发检测后的行为检测到重复 → Agent 必须切换策略而不是继续重复第 1 次检测在下一轮的 Thought 前注入提醒 → 注意你刚才的 search_file 已经执行过相同参数没有新结果。请换一种方法。第 2 次检测强制切换工具类别 → 你已经连续 5 次执行搜索类操作。请停止搜索尝试其他方法获取信息。第 3 次检测强制终止并降级 → 检测到重复动作循环。停止执行向用户汇报当前进展。关键洞察重复检测不是为了打断Agent而是为了让 Agent在错误的方向上少浪费 Token。3 轮重复被终止远比 50 轮重复后自然停止要经济。六、安全护栏三降级机制——当 Agent 走不通时有路可退6.1 为什么需要降级Agent 设计中最容易被忽视的是失败路径。大多数 Agent 只有成功路径的设计——假设模型总能推理出正确结果。但现实是工具返回格式不符合预期 → Agent 反复尝试解析缺少关键工具 → Agent 用现有工具绕路越绕越远歧义任务描述 → Agent 在两个解释之间反复横跳上下文太长 → 模型开始遗忘早期信息决策质量持续下降6.2 三级降级策略Level 1: 自动重试透明降级- 触发条件工具调用失败网络超时、参数格式错误- 行为自动修正参数后重试最多 2 次、换成同类替代工具、对用户完全透明- 示例read_file(config.yaml)失败 → 自动 tryread_file(./config.yaml)→ 自动 trysearch_file(config.*)先确认存在Level 2: 简化目标半透明降级- 触发条件步数接近上限 或 重复检测触发- 行为放弃完美方案选择能用的方案通知用户最优方案无法完成以下是当前最佳结果- 示例原目标分析并修复所有代码问题 → 降级后我发现 3 个问题已修复 2 个第 3 个需要你确认Level 3: 安全退出显式降级- 触发条件步数用尽 或 重复检测 2 次以上- 行为停止所有操作、输出已完成的工作 未完成的工作 建议的下一步、将完整上下文保留- 示例我在以下方面取得了进展 已读取项目结构15 个文件 已识别 3 个潜在问题 未完成修复步数用尽 建议请让我 focus 在某个具体文件上继续降级层级何时触发用户体验核心思想Level 1单次操作失败无感知内部消化小问题Level 2接近限制轻感知交付部分价值Level 3已达上限明确告知保留进展允许继续七、好处与坑ReAct Agent 的实战体验7.1 三大核心好处好处一模型可以自己规划路径- 传统方式人必须把每一步都写清楚- ReAct 方式描述目标Agent 自己找路- 案例帮我给这个项目加单元测试 → Agent 自动扫描项目结构 → 识别源文件 → 检查现有测试 → 补充缺失测试好处二遇到意外能自行调整- 传统 if-else预定义的错误处理覆盖不到的情况 → 崩溃- ReAct工具返回 unexpected → Thought 分析 → 换方案继续- 案例read_file(config.json)返回 YAML 格式内容 → 传统代码 JSON.parse 报错崩溃 → ReAct Agent 理解是 YAML → 用 YAML 解析器好处三多工具自然编排- ReAct 天然支持不固定顺序的工具编排- 不像工作流引擎需要预先定义 DAG- 模型根据当前状态动态决定下一个工具是什么7.2 五个常见坑坑一Token 消耗不可控- 每轮 Thought Action Observation 都占 Token- 复杂任务 30 轮 → 可能消耗 50K-100K tokens- 成本可能是单次调用的 10-30 倍- 缓解方案设置合理的 max_steps、使用 Tool Search 减少工具定义 Token、上下文压缩坑二模型想太多- 简单任务也可能被 Agent 复杂化- 读一个文件 → Agent 可能先 ls、再思考、再读文件- 缓解方案System Prompt 中加入简洁原则、检测过度思考模式坑三Observation 依赖性- 模型决策完全依赖 Observation 的准确性和完整性- 工具返回格式变化 → Agent 可能误解- 缓解方案工具返回格式标准化、关键工具返回加入置信度字段坑四上下文窗口污染- 每轮都追加新内容到上下文- 早期关键信息用户任务、约束可能被挤出注意力焦点- 缓解方案关键约束在每轮 System Prompt 中重复注入、定期自动总结坑五模型幻觉在中间步骤放大- Chain-of-Thought 的幻觉只在最终答案体现- ReAct 的幻觉会在中间 Thought 中放大错误 Thought → 错误 Action → 错误 Observation → 更错误 Thought- 缓解方案重要 Action 前加确认步骤、工具返回中加入校验信息7.3 一张表总结维度传统 if-elseReAct Agent控制流定义程序员预定义模型现场推理灵活性低只能处理预见的情况高可应对未预见的需求工具调用错误处理预定义 catch 块动态调整策略多工具编排需手动写编排逻辑模型自动编排Token 成本固定、可控动态、可能失控可调试性高断点、日志低黑盒推理链适用场景确定性流程探索性、创造性任务安全边界代码级硬边界需要 Prompt 代码双重护栏八、总结生产级 ReAct Agent 的安全设计清单核心理念ReAct Agent 的强大之处在于灵活性——模型可以根据实际情况动态规划执行路径。但灵活性必须配边界控制——没有护栏的灵活性就是失控。好的 Agent 设计不是让模型更聪明而是让聪明的模型在安全边界内运行。