1. 从会聊天到会干活ReAct 到底改变了什么大多数人第一次接触大语言模型都是被它的能说会道震住的。你问它一个问题它给你一段像模像样的回答遣词造句比很多人都讲究。但只要你真的拿它去做点实际的事比如查一下今天的天气、算一笔复杂的账、或者根据一堆零散信息做决策就会立刻发现一个尴尬的事实它只会说不会做。你问它现在外面下雨吗它可能会一本正经地编一个答案出来因为它根本没有能力去获取实时信息也没有能力去执行任何动作。这个问题的本质是模型被训练成了一个条件概率生成器——给定前文预测下一个最可能出现的词。它没有行动这个概念也没有观察结果这个环节。它活在一个纯文本的封闭世界里所有的输出都是对输入的概率性延续。ReAct 这个思维范式的出现就是为了打破这堵墙。ReAct 是 Reasoning 和 Acting 的合成词核心思想非常朴素让模型在推理和行动之间交替进行。模型先想一步决定要做什么然后真的去做这个动作拿到一个真实的观察结果再基于这个结果继续想下一步如此循环直到任务完成。我第一次看到这个思路的时候感觉就像给一个只会纸上谈兵的参谋配了一个能跑腿的通信兵。参谋负责分析局势、制定计划通信兵负责去前线侦察、把真实情况带回来。两者配合才能做出靠谱的决策。这个类比虽然粗糙但抓住了 ReAct 的精髓推理负责想清楚行动负责拿到真实信息两者缺一不可。ReAct 适合谁来学如果你只是拿模型做做文本润色、写写邮件那暂时用不上。但如果你想用模型去解决需要多步推理、需要调用外部工具、需要根据中间结果动态调整策略的问题——比如智能客服、自动化数据分析、代码调试助手、复杂问答系统——那 ReAct 就是你必须掌握的基础范式。它不是一个具体的框架或库而是一种设计 Agent 的思维方式理解了它你再看 LangChain、AutoGPT 这些工具就会觉得豁然开朗。2. 拆开 ReAct 的循环Thought、Action、Observation 三件套2.1 为什么是这三个环节而不是别的ReAct 的循环结构看起来简单但每个环节的存在都有其不可替代的理由。我见过不少人第一次实现 Agent 的时候直接把思考和行动合并成一步让模型直接输出要调用的工具和参数结果就是模型经常在没想清楚的情况下乱调工具或者调了一个根本不需要的工具。这就是省掉显式 Thought 环节的代价。Thought思考环节的作用是让模型在采取行动之前先把当前的局势理一遍。它要回答几个问题我现在知道什么我还缺什么信息我下一步应该做什么才能拿到这个信息这个环节的输出是自然语言不要求结构化因为它的目的是理清思路而不是执行。你可以把它理解成模型在自言自语。Action行动环节是真正产生外部影响的一步。模型在这里决定调用哪个工具、传什么参数。这一步的输出必须是结构化的因为下游的系统要解析它、执行它。常见的格式是Action: 工具名加上Action Input: 参数或者用 JSON 格式。格式的选择取决于你的解析器怎么写但核心是必须让程序能可靠地提取出工具名和参数。Observation观察环节是外部世界返回的结果。它不是模型生成的而是工具执行后真实返回的。这一点极其关键——Observation 是 ReAct 区别于纯推理链的根本所在。纯推理链比如 Chain-of-Thought从头到尾都是模型自己在编中间步骤再合理也可能是错的。而 ReAct 的每一步都锚定在真实的观察结果上模型没法凭空捏造。这三个环节串起来就形成了一个闭环想 → 做 → 看 → 再想 → 再做 → 再看。循环的终止条件通常是模型在 Thought 中判断我已经有足够信息回答问题了然后输出 Final Answer。2.2 一个完整的循环长什么样光说结构太抽象我直接给你看一个完整的循环轨迹。假设用户问帮我查一下某城市今天的天气如果下雨就提醒我带伞。第一轮循环Thought: 用户想知道某城市今天的天气并且需要根据天气决定是否带伞。我需要调用天气查询工具来获取实时天气信息。 Action: get_weather Action Input: {city: 某城市, date: today} Observation: {condition: 小雨, temperature: 18-24°C, humidity: 85%}第二轮循环Thought: 天气查询结果显示今天有小雨。用户说了如果下雨就提醒带伞所以我需要给出带伞的建议。我已经有足够信息回答问题了。 Final Answer: 某城市今天有小雨气温 18-24°C湿度较高。建议你出门带伞路面可能湿滑注意安全。你看整个过程模型做了两次思考执行了一次行动拿到了一次观察。第一次思考是为了决定查天气第二次思考是为了根据查询结果做判断。如果没有第一次的显式思考模型可能会直接编一个天气出来如果没有第二次思考模型可能拿到数据后不知道怎么用。2.3 和 Chain-of-Thought 的本质区别很多人会把 ReAct 和 Chain-of-ThoughtCoT搞混觉得都是让模型一步步想。这个理解只对了一半。CoT 确实是让模型一步步推理但它的每一步都是模型自己生成的文本没有任何外部信息的注入。CoT 适合的是纯逻辑推理问题比如数学题、逻辑谜题这些问题的所有信息都在题目里不需要外部查询。ReAct 则是在 CoT 的基础上插入了 Action 和 Observation 这两个环节。它适合的是信息不完整、需要动态获取的问题。你可以这样理解CoT 是一个人在房间里苦思冥想ReAct 是一个人一边想一边打电话问人、一边上网查资料。前者适合闭卷考试后者适合开卷解决实际问题。我个人的经验是如果一个任务的所有信息都能在 prompt 里给全那用 CoT 就够了没必要上 ReAct因为 ReAct 的循环会消耗更多的 token 和时间。但如果任务需要实时数据、需要调用外部 API、需要根据中间结果调整策略那 ReAct 就是更合适的选择。3. 工具设计ReAct 能不能跑起来八成看这一步3.1 工具描述写得好模型才用得对ReAct Agent 的能力上限很大程度上取决于你给它配了什么工具以及这些工具的描述写得怎么样。我见过太多人花大量时间调 prompt、调模型参数结果 Agent 还是频繁调错工具最后发现根本原因是工具描述写得太模糊。工具描述的核心要求是让模型在只看描述的情况下就能判断这个工具是干什么的什么时候该用它参数该怎么填。这三点缺一不可。举个例子假设你有一个查询订单状态的工具。差的描述是查询订单。好的描述是根据订单号查询订单的当前状态包括已下单、已发货、已签收、已取消。输入参数为订单号订单号是 12 位数字字符串。当用户询问订单进度、物流状态、是否发货时使用此工具。你看好的描述里包含了功能说明、参数格式、使用场景。模型看到这样的描述就知道什么时候该调、怎么调。而差的描述模型只能靠猜。3.2 工具粒度太粗和太细都是坑工具粒度是另一个容易踩坑的地方。粒度太粗一个工具干太多事模型很难填对参数粒度太细工具数量爆炸模型选择困难。我的经验法则是一个工具只做一件事但这件事要有明确的业务含义。比如查询订单和修改订单地址应该是两个工具而不是一个订单管理工具。但查询订单状态和查询订单物流如果底层是同一个接口就没必要拆成两个合并成一个查询订单详情就够了。工具数量控制在 5 到 15 个之间比较舒服。少于 5 个可能覆盖不了业务场景多于 15 个模型的选择准确率会明显下降而且 prompt 里塞太多工具描述也会挤占上下文空间。3.3 参数校验别让模型把错误参数传进来模型不是程序员它填参数的时候经常犯低级错误该填数字的填了字符串该填日期格式的填了今天该填枚举值的填了一个不存在的选项。如果你不在工具层面做校验这些错误就会一路传到下游系统轻则报错重则产生脏数据。我的做法是在工具函数入口处做严格的参数校验校验失败时返回一个清晰的错误信息作为 Observation。比如模型传了一个不存在的城市名工具返回{error: 城市 某某市 未找到请检查城市名是否正确}。模型看到这个 Observation下一轮 Thought 就会意识到自己传错了然后修正。这里有个细节错误信息要写得让模型能理解并自我修正。如果你只返回{error: invalid parameter}模型可能不知道错在哪下一轮还是传同样的错误参数。但如果你返回{error: 参数 date 格式错误应为 YYYY-MM-DD 格式你传入的是 today}模型就能明确知道该怎么改。3.4 工具返回结果的处理别把原始数据直接丢给模型工具返回的原始数据往往包含大量模型不需要的字段。比如一个天气 API 可能返回几十个字段但模型只需要温度、天气状况、湿度这几个。如果你把原始 JSON 直接塞进 Observation会浪费大量 token还可能干扰模型的判断。我的做法是在工具函数里做一层数据清洗只返回模型需要的关键字段。同时返回格式要尽量结构化、简洁。比如{ city: 某城市, condition: 小雨, temperature: 18-24°C, humidity: 85% }而不是把 API 返回的原始嵌套结构直接丢过去。这一步看起来不起眼但对 Agent 的稳定性和成本控制影响很大。4. 循环控制什么时候停、什么时候重试、什么时候放弃4.1 最大迭代次数必须设而且要设得合理ReAct 的循环如果不加限制模型可能会陷入死循环一直调同一个工具、一直拿到同样的结果、一直想不出下一步。这种情况在模型遇到它解决不了的问题时特别容易发生。所以最大迭代次数是必须设的。设多少合适我的经验是 8 到 15 轮。太少了复杂任务还没完成就被截断太多了一旦模型卡住会白白烧掉大量 token。但光设上限还不够你还需要在达到上限时给用户一个合理的反馈而不是直接抛一个达到最大迭代次数的错误。我的做法是让 Agent 在最后一轮强制输出一个 Final Answer内容是我尝试了 X 步但未能完全解决该问题目前已知的信息是……建议你……。这样至少用户能拿到部分结果而不是一个冷冰冰的报错。4.2 重复检测模型卡住时的自救机制除了最大迭代次数我还建议加一个重复检测机制。具体来说就是记录每一轮的 Action 和 Action Input如果连续两轮完全一样就说明模型卡住了需要干预。干预的方式有两种一种是直接终止循环返回当前已有的信息另一种是在下一轮的 prompt 里插入一个提示比如你上一轮已经调用过这个工具并得到了结果请基于已有信息继续推理不要重复调用。我实测下来第二种方式在多数情况下能让模型跳出死循环因为它给了模型一个明确的信号你卡住了换个思路。4.3 超时控制别让一个慢工具拖垮整个 Agent如果 Agent 调用的某个工具响应特别慢比如一个需要跑几十秒的数据查询整个循环就会被卡住。用户等半天看不到任何输出体验极差。我的做法是给每个工具调用设一个超时时间比如 10 秒。超时后返回一个 Observation内容是工具调用超时请稍后重试或换一种方式获取信息。模型看到这个可以选择重试也可以选择换一个工具或者直接告诉用户当前无法获取该信息。这个机制在工具依赖外部服务的时候特别重要。外部服务偶尔抽风是常态你不能让 Agent 因为一个外部服务的抖动就整个挂掉。4.4 异常处理工具报错时模型该怎么办工具执行失败是不可避免的网络超时、参数错误、权限不足、服务不可用各种情况都可能发生。关键不是避免失败而是失败后 Agent 能不能优雅地处理。我的原则是工具报错时返回给模型的 Observation 必须包含足够的信息让模型能判断是重试、换工具、还是放弃。比如参数错误返回具体的参数问题模型可以修正后重试。网络超时返回服务暂时不可用模型可以决定是否重试。权限不足返回当前无权访问该资源模型应该放弃这个方向换其他方式。最怕的是工具报错后返回一个空结果或者一个模型看不懂的错误码模型拿到这种 Observation 会一脸懵然后开始瞎猜最后输出一个完全错误的答案。5. Prompt 工程让模型稳定输出结构化轨迹的技巧5.1 格式约束用 Few-shot 示例比用文字描述管用ReAct 的 prompt 里最重要的部分是告诉模型输出格式。你可以用文字描述请按照 Thought、Action、Observation 的格式输出但实测下来模型经常不听话格式五花八门。更可靠的做法是给几个完整的 Few-shot 示例。示例里包含一个完整的循环轨迹让模型照着模仿。模型在模仿格式这件事上比理解文字描述强得多。示例的数量不用多2 到 3 个就够了。但示例要覆盖不同的场景一个单步就能解决的、一个需要多步的、一个工具报错后修正的。这样模型见过的模式多了泛化能力也更强。5.2 停止序列让模型在该停的地方停ReAct 的循环里模型输出完 Action Input 之后就应该停下来等外部系统执行工具并返回 Observation。但模型不知道什么时候该停它可能会继续往下编 Observation这就乱套了。解决办法是设置停止序列stop sequence。比如把\nObservation:设为停止符模型一旦生成到这个字符串就停止输出。这样模型就只会输出到 Action Input 为止后面的 Observation 由外部系统填入。这个细节看起来小但不设的话模型会自己编 Observation整个 ReAct 就退化成了 CoT失去了锚定真实信息的意义。5.3 温度参数低温度更适合 ReActReAct 需要模型稳定地输出结构化格式所以温度参数不宜设高。我的经验是 0 到 0.3 之间比较合适。温度太高模型会开始发挥创意格式就容易跑偏。但也不是所有环节都要低温度。如果你希望模型在 Thought 环节有更多的探索性可以稍微调高一点。不过在实际工程中我通常统一用一个较低的温度保证稳定性优先。5.4 上下文管理循环多了 token 会爆ReAct 的循环会把每一轮的 Thought、Action、Observation 都追加到上下文里。循环到第 10 轮的时候上下文可能已经非常长了。如果不做管理很快就会超出模型的上下文窗口。我的做法是保留最近 N 轮的完整轨迹更早的轮次只保留摘要。比如把前几轮的 Thought 和 Observation 压缩成一句话之前已经查询了天气和交通状况结果显示有小雨且早高峰拥堵。这样既保留了关键信息又控制了 token 消耗。另一种做法是用一个单独的总结模型定期把历史轨迹压缩。但这个方案复杂度更高适合对成本敏感的大规模应用。6. 实测中那些文档不会告诉你的坑6.1 模型会假装调用了工具这是我在实际项目里遇到的最隐蔽的坑。模型有时候会在 Thought 里写我需要调用天气工具然后在 Action 里写了一个工具名但 Action Input 是空的或者格式不对。更狡猾的是它会在 Observation 里自己编一个结果然后继续往下推理。这种假装调用的行为在模型不确定该怎么做的时候特别容易出现。防范的办法是在解析 Action 的时候做严格校验工具名必须在已注册的工具列表里Action Input 必须是合法的 JSON解析失败就返回一个错误 Observation强制模型重新输出。6.2 工具描述里的隐藏指令会干扰模型如果你在工具描述里写了一些看起来像指令的话模型可能会把它当成任务的一部分。比如你在描述里写注意调用此工具前请先确认用户身份模型可能会真的在 Thought 里纠结要不要先确认身份即使当前任务根本不需要。所以工具描述要写得干净只描述工具本身的功能和参数不要夹带任何流程性的指令。流程控制应该放在系统 prompt 里而不是工具描述里。6.3 多工具场景下的选择困难当工具数量超过 10 个的时候模型选错工具的概率会明显上升。我试过的一个缓解办法是给工具分组在 prompt 里先让模型选择工具类别再在类别内选择具体工具。这个两阶段的选择方式比让模型直接从 20 个工具里选准确率高不少。另一个办法是动态工具加载根据用户问题的关键词先筛选出相关的几个工具只把这几个工具的描述放进 prompt。这样模型每次面对的选择少了准确率自然就上去了。6.4 Observation 太长会淹没关键信息有些工具返回的结果特别长比如一个搜索工具返回了 10 条结果每条都有标题、摘要、链接。这么长的 Observation 塞进上下文模型很容易抓不住重点。我的做法是在工具层面做摘要只返回最相关的 3 条结果并且把每条结果压缩到一两句话。如果模型需要更多细节它可以再调一次工具传更具体的参数。这样既控制了上下文长度又保证了信息的可用性。7. 从单 Agent 到多 AgentReAct 的扩展思路7.1 什么时候该考虑多 Agent单 Agent 的 ReAct 能解决大部分问题但遇到特别复杂的任务时会显得力不从心。比如一个任务同时涉及数据查询、数据分析、报告生成三个环节每个环节都需要不同的工具集和不同的推理策略。这时候把所有工具都塞给一个 Agent它的选择负担太重容易出错。多 Agent 的思路是把任务拆成几个子任务每个子任务交给一个专门的 Agent每个 Agent 只负责自己领域的工具和推理。Agent 之间通过消息传递来协作。7.2 多 Agent 协作的两种模式一种是流水线模式Agent A 完成自己的部分把结果传给 Agent BB 继续处理再传给 C。这种模式适合步骤明确的流程化任务。另一种是辩论模式多个 Agent 对同一个问题给出各自的答案然后互相评审、辩论最后达成共识。这种模式适合需要多角度分析的决策类任务。我个人的经验是流水线模式更实用也更容易调试。辩论模式听起来很酷但实际落地时Agent 之间的辩论很容易跑偏而且成本高。7.3 多 Agent 的通信协议设计多 Agent 协作的关键是通信协议。每个 Agent 的输出格式要统一这样下游 Agent 才能可靠地解析。我通常定义一个简单的消息结构{ from: agent_name, task: 子任务描述, result: 执行结果, status: success/failed, next_action: 建议下一步 }这个结构简单但够用。关键是status字段让下游 Agent 知道上游是成功了还是失败了失败了要不要重试或者换方案。8. 评估与调试怎么知道你的 ReAct Agent 到底行不行8.1 别只看最终答案要看轨迹评估 ReAct Agent 最容易犯的错误就是只看最终答案对不对。但最终答案对不代表 Agent 的推理过程是对的。它可能是蒙对的也可能是中间走了弯路但最后碰巧绕回来了。我的做法是同时评估轨迹质量。具体看几个指标工具调用的准确率该调的时候调了没不该调的时候有没有乱调、参数填写的正确率、循环步数步数太多说明推理效率低、以及是否出现了重复调用。这些指标能帮你定位问题。如果最终答案对但工具调用准确率低说明 Agent 是在瞎猫碰死耗子换个问题可能就挂了。8.2 构建测试集覆盖正常、边界、异常三类场景测试集要覆盖三类场景正常场景标准问题工具齐全、边界场景信息不完整、需要多步推理、异常场景工具报错、参数非法、超时。正常场景用来验证基本功能边界场景用来验证推理能力异常场景用来验证鲁棒性。三类场景的比例大概是 5:3:2。很多人只测正常场景上线后一遇到异常就崩就是因为异常场景没覆盖到。8.3 日志出问题时能复现是关键ReAct Agent 的调试比普通程序难因为它的行为有随机性。同一个问题两次运行可能走不同的轨迹。所以日志必须记录完整每一轮的 Thought、Action、Action Input、Observation、以及时间戳和 token 消耗。有了完整日志出问题时你才能复现当时的轨迹定位是哪一步出了问题。我建议日志按 session 分组每个 session 一个文件方便查找。9. 我踩过的几个印象深刻的坑第一个坑是工具返回值没有做类型统一。有的工具返回字符串有的返回 JSON有的返回列表。模型拿到不同类型的 Observation处理方式就不稳定。后来我强制所有工具返回统一的 JSON 结构问题就解决了。第二个坑是prompt 里工具描述的顺序影响选择。我发现模型倾向于选择 prompt 里靠前的工具即使靠后的工具更合适。后来我把工具按使用频率排序高频的放前面准确率有所提升。但这个规律不是绝对的不同模型表现不一样需要实测。第三个坑是循环终止条件写得太死。我一开始要求模型必须输出 Final Answer: 才终止结果模型有时候会输出 最终答案 或者 答案导致循环停不下来。后来我改成用正则匹配多种变体鲁棒性就好了很多。第四个坑是忽略了 token 成本。ReAct 的循环很烧 token一个复杂任务跑下来成本可能是单次调用的几十倍。后来我在工具返回结果里做了更激进的摘要并且限制了历史轨迹的长度成本才降下来。10. 一些实用的参数和配置参考下面这张表是我在实际项目中总结的一些常用配置供你参考。具体数值需要根据你的模型、任务和成本预算调整。配置项推荐值说明最大迭代次数8-15复杂任务取上限简单任务取下限温度参数0-0.3保证格式稳定Thought 环节可略高工具数量5-15超过 15 个建议分组或动态加载单工具超时10 秒外部服务依赖强的可适当放宽历史轨迹保留最近 5 轮完整 更早摘要平衡信息完整性和 token 成本Few-shot 示例数2-3 个覆盖单步、多步、异常三类场景重复检测阈值连续 2 轮相同触发后插入提示或终止这些数值不是金科玉律但作为一个起点是靠谱的。你可以先按这套配置跑起来然后根据实际表现微调。11. 写在最后的一点个人体会ReAct 这个范式我用了挺长时间最大的感受是它的威力不在于模型有多聪明而在于它把模型的想和外部世界的真接上了。模型再聪明如果活在自己的世界里也只能是纸上谈兵。一旦它能拿到真实的信息、执行真实的动作它的价值就完全不一样了。但反过来ReAct 也把工程的复杂度提上来了。你要设计工具、要处理异常、要控制循环、要管理上下文、要评估轨迹。这些都不是调一个 API 那么简单。我见过不少人兴冲冲地搭了一个 ReAct Agent跑了两天发现效果不稳定就放弃了。其实问题往往不在 ReAct 本身而在于工程细节没做到位。如果你正准备上手 ReAct我的建议是先从最简单的场景开始一个工具、一个任务把整个循环跑通把日志打全把异常处理做好。然后再逐步加工具、加场景、加复杂度。别一上来就搞多 Agent、搞复杂工具链那样出了问题你根本不知道是哪一层的问题。最后分享一个小技巧在开发阶段把每一轮的完整轨迹打印到控制台人肉看几遍。你会对模型的行为模式有非常直观的感受这比看任何文档都管用。看多了你就能预判模型在什么情况下会犯错然后提前在 prompt 或工具层面做防范。这个人肉调试的阶段是绕不过去的也是最有价值的。