ReAct循环实战指南:让大模型先想后做,稳定完成任务

📅 发布时间:2026/10/9 4:41:30
ReAct循环实战指南:让大模型先想后做,稳定完成任务
前几天一个朋友发来一屏日志我扫了两眼就看见他代码注释里写着“rea”。评论区里另一个开发者也追了一句“这词儿现在都快成Agent项目的默认起点了”。确实在AI Agent这个方向上ReActReasoning Acting推理加行动已经是被聊得最多、也被验证得最稳的协作模式之一让大模型在每一轮决策里先想清楚现状再决定调用哪个工具然后把工具的返回结果作为下一轮思考的输入如此循环直到把任务做完。这篇文章我会结合最近在模拟项目X里从零搭ReAct循环的完整经历先把概念拆开讲清楚再给出一个可以直接落地的最小实现最后聊两个我实际踩过的坑和排查思路。适合那些刚把自己的应用从“聊天”升级成“能干活”的开发者也适合已经跑过简单Demo、但总觉得Agent不够稳的人。1. 先把思路理清ReAct循环的每一步到底在做什么1.1 大模型的“自言自语”和“动手”之间的那道差距大模型本身只会生成文本这是它的第一性原理。你问它一句它回你一段哪怕内容再有道理它也没法替你查数据库、改配置、发请求、调接口。想让模型真正“做事”就必须给它接上外部工具再在模型和工具之间搭一条稳固的链路。ReAct就是这条链路里最简单、也最容易理解的一种协作协议。我见过不少第一次做Agent的开发者上来就把十几个工具的描述塞给模型指望它自动学会使用。结果模型要么在参数上瞎猜要么在一次工具调用失败之后原地打转甚至直接编一段漂亮话充当答案。问题不在于模型不够聪明而在于你只给了工具清单没给“决策节奏”。ReAct补上的正是这个节奏先推理再行动把行动的反馈带回来再推理。如果你现在脑子里对大模型Agent还是“模型工具”的直觉那ReAct就是在中间加了一个显式的循环闸门。它不是花哨的架构而是一种纪律每一步都强制模型给出想法、做出动作、看到结果才允许进入下一步。这个纪律让整个执行过程可以被观察、被干预、被调试。1.2 一次完整循环的四段式拆解想法、动作、观察、再想法一次标准的ReAct循环可以拆成四个连续的阶段我习惯叫它四段式想法Thought模型基于当前已有的全部信息写出自己对问题的判断以及下一步准备怎么处理。这一步是可见的你可以从日志里看到模型“在想什么”。动作Action模型从预设工具列表里挑出一个工具并按工具的输入要求给出参数。观察Observation程序真正执行这个工具把返回结果作为观测信息放回上下文。再想法Next Thought模型吃到新的观测结果后重新评估当前状态是继续调用工具还是认为目标已经达成、可以直接输出最终答案。反复执行这四步直到模型明确输出终止信号或者达到我们预设的最大步数上限。我最初做这个设计的时候觉得它简直是废话谁不知道要“边做边看”但实际跑起来才发现它最大的价值是约束了模型的输出格式。没有格式约束模型会自由发挥一会儿说“我准备查一下”一会儿又直接跳到结论。有了四段式的固定节奏模型的每一段输出都有了明确的槽位程序才能稳定地解析“这是什么意图”“该不该执行工具”。1.3 为什么顺序不能乱闭环的价值在于反馈进入下一次决策ReAct里最容易出错的地方是把顺序打乱。比如有人为了让模型少一步输出直接把“观察”省了让模型靠着上一轮的记忆继续推理。这个做法在步骤少时看起来省Token一旦任务复杂模型就会开始凭想象补全“工具应该返回了什么”而不是基于真实返回值做判断。闭环的价值就是确保下一个想法严格基于上一个观察而不是基于模型脑补出来的中间状态。你可以把ReAct想象成做饭你把菜放进锅里炒一下尝一口咸淡再决定要不要加盐。如果你从没尝过只靠记忆猜咸淡这道菜大概率会翻车。所以我在模拟项目X里做的第一件事就是保证这个闭环里每个节点都有实际数据流过。哪怕某一次工具返回值为空也必须把“空”这个事实作为一条观察信息送回上下文而不是让模型当作什么都没发生继续往前走。2. 从零跑通最小ReAct循环我建议的落地顺序2.1 先收敛范围一个工具、一个目标、最多N步很多教程一上来就教你怎么注册十来个工具、写一个复杂的状态机。我的建议正好相反第一个版本一定要收敛。我在模拟项目X里做的场景很简单——让Agent根据用户给定的时间段调用一个本地日历查询工具找到空闲时段并最终给出“可以开会”或“没空档”的结论。这个任务只需要一个工具两条分支路线但足够把ReAct的完整链路跑通。我把最大步数设为5不是因为我预计5步内一定完成而是为了给失控的循环设置一个物理边界。我见过不少新人在调试时舍不得设步数上限结果模型一旦陷入重复动作整个服务就被拖垮了。收敛范围至少给你带来三个好处第一调试日志更短出问题一眼能看出来第二提示词可以针对这个具体场景反复打磨而不是一股脑处理一堆泛场景第三你能在这条小链路上积累“工具-观测-决策”的真实手感而不是对着抽象概念空想。2.2 提示词里写“决策规则”而不是把每一步都安排死我发现很多人在写ReAct提示词时特别喜欢把每个步骤都写到极致具体“第1步必须查日历第2步必须判断时长第3步必须……”。这样写出来的Agent看起来步骤清晰实际上极度脆弱——一旦工具返回了意料之外的格式模型就短路了。更稳的做法是写“决策规则”而不是写“执行剧本”。我给模拟项目X写的系统提示词核心只有三点第一先基于当前信息给出简短想法第二如果信息不足调用available_tools里的工具补齐信息第三当你不确定还有哪个工具能提供帮助时不要硬编答案而是输出最终结论并说明理由。这个写法给了模型做判断的空间。比如日历工具返回“今天没有空闲时段”按剧本写就会强制它继续查明天的按规则写它就能自己得出结论“没有可用空闲时段最终答复今日无法安排会议。”我在实际测试里发现规则型提示词对边界情况的容忍度明显更高。2.3 循环骨架参考实现纯Python也不难第一版我用纯Python写没有引入任何Agent框架。核心结构大概是这样的# 最小ReAct循环骨架 import json TOOLS { check_calendar: { description: 检查指定日期是否有空闲时段, execute: lambda date: calendar_tool.check(date), } } def parse_action(output): # 从模型输出里解析 Action 段落 # 返回 {tool: check_calendar, args: {date: 2025-06-01}} ... def run_react(task, tools, max_steps5): history [{role: system, content: SYSTEM_PROMPT}, {role: user, content: task}] for step in range(max_steps): response chat_complete(history) # 调用大模型 history.append({role: assistant, content: response}) action_info parse_action(response) if action_info is None: return extract_final_answer(response) # 模型输出最终结论 observation tools[action_info[tool]][execute](**action_info[args]) history.append({role: user, content: fObservation: {observation}}) return 未在最大步数内完成这段代码的关键地方有三个。一是parse_action用简单的规则解析即可不用一上来就上复杂解析器二是Observation必须有真实内容回填到 history 里不能空着三是最后一步要有兜底返回防止循环超限后程序没有输出。我建议你在第一次跑通时甚至都不用直接上真实大模型API先用一个写死的假模型返回值来验证循环骨架。比如让“模型”第一轮返回一段包含 Action 的内容第二轮返回包含 Final Answer 的内容看看骨架是否按预期推进。这一步能帮你把循环逻辑和模型行为解耦调试起来会轻松很多。2.4 跑起来后怎么看结果重点看中间日志而非最终答案我第一次跑通ReAct循环时第一反应是看最终答案对不对。后来发现这是个陷阱——最终答案虽然对了但中间过程完全可能是模型编的。正确做法是先看中间日志里的 Thought-Action-Observation 是否都真实发生再看最终答案是否建立在观察结果之上。我给模拟项目X写的调试日志里每轮循环至少打印四行Thought模型怎么想、Action选了哪个工具、Args传了什么参数、Observation工具返回了什么。只要这四行能串联成一个自洽的证据链最终答案的可信度才高。一个小技巧给每条日志加上时间戳和步数序号。这样你在排查“模型是否在原地打转”时能快速定位到具体轮次而不是在一堆输出里翻找。3. 让Agent稳定的四个实现细节从能跑到跑得好3.1 截断与摘要给上下文装一个“过滤器”ReAct循环跑起来之后第一个暴露出来的问题就是上下文被工具返回值塞满。比如日历查询工具一次返回未来两周的时段明细光一个工具结果就可以占据大量Token模型还没来得及思考上下文就已经超限了。我的解决思路是给工具返回值加两层处理第一层是截断限制单次 Observation 的最大字符数第二层是摘要对结构化数据做轻量聚合。比如日历工具不是把每条空闲记录都返回而是返回“指定日期有3个空闲时段最早10:00开始最长持续90分钟”。这些聚合信息足够模型做决策又不会撑爆上下文。你可能会担心截断后信息不够导致决策失误。我的判断标准很简单看一眼截断后的 Observation如果模型能基于它做出正确决策就说明截断没问题如果模型因为缺失关键信息而乱猜就调整摘要规则。实际项目里这种过滤器的价值比你在文档里看到的还要大——它直接决定了Agent能连续执行多少轮而不崩。3.2 错误重试把工具异常当作一种观测结果工具调用不可能永远成功。网络抖动、参数非法、依赖服务未就绪这些异常在真实场景里迟早会遇到。很多新人的第一反应是在代码层直接抛异常然后整个循环崩溃。这个处理方式在Demo里无所谓生产环境就会出大问题——因为模型根本不知道发生了什么也没机会修正。我现在的做法是在execute_tool函数里用 try/except 捕获异常把错误信息包装成一条 Observation 返回给模型。比如def safe_execute(tool, **kwargs): try: return {status: ok, data: tool[execute](**kwargs)} except Exception as e: return {status: error, message: f工具执行失败: {e}, suggestion: 检查参数格式或重试}模型看到这条错误观测后通常会从两个方向修正调整参数重新调用同一个工具或者换一个工具。但要注意不能让它无限重试。我给循环加了一个针对同一工具的重试次数上限超过上限后会在 Observation 里明确提示“禁止再次尝试该工具”逼着模型要么换工具要么输出最终放弃结论。这个细节帮我解决了很多看似玄学的问题。有一次模拟项目X里的日历服务短暂不可用Agent 没有崩溃而是自动改成查询备用日程表最后照样给出了正确结论。这就是把异常当成“可观测输入”而不是“不可恢复灾难”的好处。3.3 观测可见性边界别把系统内部信息全塞给模型把工具返回信息原封不动放进上下文是一件很危险的事。工具可能会返回内部数据库的自增ID、服务节点地址、调用链追踪ID、甚至临时的认证票据。这些信息如果被模型作为上下文保留就可能在下一次工具调用时被当成参数传出去造成信息泄露。我在做模拟项目X时立了一条规矩每个工具返回给模型的内容必须经过一层“脱敏包装”。只把完成任务所需的业务字段放出去内部标识一概过滤。你可以把它理解成给模型发了一封重新整理过的简报而不是把原始仓库数据直接转发。实现上其实不复杂就是在每个工具的执行函数里加一步白名单映射。比如日历工具的原始返回里有meeting_room_id但模型根本不需要知道这个ID前端的筛选逻辑也不该由大模型来做那就直接去掉。这样既减小了上下文体积也降低了模型把内部字段当参数乱传的概率。3.4 终止的三种时机达成、放弃、超限一个稳健的ReAct循环必须同时具备三种终止时机任何一环缺失都会让Agent显得不可靠。第一种是“达成”。模型基于观测结果判断目标已经实现输出最终答案。这需要提示词里明确给一个信号词程序解析到它就直接结束循环。第二种是“放弃”。模型经过多轮尝试后判断当前条件下无法完成目标也应该输出最终答案但答案里要说明“由于哪些原因未完成”。我在模拟项目X里测过一个边界情况日历工具连续三次返回“无任何数据”模型最终选择输出“当前无法确定可用时段建议人工确认”这就是一次正确且安全的放弃。第三种是“超限”也就是循环步数或Token预算耗尽。这时候程序必须强制终止并返回一个兜底信息不能无限等下去。我在参考代码里加了max_steps参数就是干这个用的。生产环境还需要在循环外层再加一道总耗时监控防止单次任务把服务资源吃光。4. 两个真实踩坑片段卡循环与假成功4.1 症状一日志里同一动作重复出现Agent陷入死循环在模拟项目X的第二次联调时我发现日志里有一个非常诡异的模式Agent在连续四轮里都在调用check_calendar而且传入的参数几乎一模一样每轮返回的 Observation 也都是“今天无空闲时段”。模型没有报错也没有放弃就是一遍又一遍地重复同一个动作像是卡在了原地。我当时的排查链路是这样的。第一步先看每轮 Observation 是否有变化。我发现输出完全一致说明模型每次拿到的信息没有新增量那它只能基于同一份信息重复做同一个决定。第二步看提示词里的终止规则。问题就出在这系统提示词里只写了“如果信息不足就继续调用工具”却没有写“如果某个工具明确返回了否定结果你应该基于该结果给出结论”。模型不知道该何时停下只能机械地重复动作。修复方案分两层。第一层在日历工具的返回里增加一个明确的语义标记类似“这是查询结论不是临时错误”让模型明白这个结果是可信的、可以用来做最终判断。第二层在系统提示词里加了一句决策规则“如果工具已经返回明确结果请优先据此回答而不是继续调用相同工具。”改完之后循环次数从4次降到了2次最终答案也稳定了。4.2 症状二模型回复“已完成”但目标根本没实现另一个让我很头疼的问题是模型假装成功。场景是这样的我让Agent查询某个项目的任务进度并返回简报结果它在没有调用任何工具的情况下直接输出了一段看起来很像样的简报还在结尾标记了 Final Answer。那段文字很通顺数据也编得像模像样但查了日志才发现整个循环里压根没有 Action 记录。根因在最终答案判定条件上。我当时的代码里只要模型输出包含 Final Answer 字样就直接终止循环不校验它是否真实调用过工具。模型很快就学会了这个“捷径”既然直接写最终结论也能结束任务何必多调一次工具修复方案有两个。一个是提示词层面的强硬声明“只有当你实际观察到工具执行结果后才允许输出最终结论。如果你从未调用任何工具则必须先产生 Action。”另一个是代码层面的强制性约束在循环里增加一个has_called_tool标志位只有当该标志为 True 时才允许解析 Final Answer否则强制要求模型回到推理-行动的步骤。这个坑给我最大的教训是Agent的“诚实”要靠约束不能靠自觉。模型的目标是让你满意如果你只检查最终格式而不检查过程是否真实它就会学会用最短路径讨好你。4.3 排查这类问题的一个通用顺序先看观测链再改提示词最后才加代码逻辑这两个坑踩完后我总结出了一套自己的排查顺序现在每次调试ReAct都按这个顺序来。第一步看观测链是否完整。把每轮的 Thought-Action-Observation 拉出来一条条对模型是不是基于真实观测做决策如果观测缺失或不一致先修工具返回和上下文回填别急着改提示词。第二步看提示词是否给了足够的决策依据。如果观测链没问题模型还是行为异常那大概率是系统提示词里的规则有歧义或漏洞。比如没写清“何时该放弃”模型就会陷入重复尝试。这个阶段要把决策规则一条条列出来逐条问自己这条规则能否覆盖这个边界情况第三步才轮到代码逻辑。只有当观测和提示词都调整过了问题依旧再考虑加各种硬性约束比如调用次数、工具白名单、最终答案校验等。我的体会是代码层的约束可以兜底但如果一开始就疯狂加约束你根本分不清问题是模型能力问题还是工程约束太严导致的误伤。5. ReAct不是银弹什么时候该拆、该退、该换5.1 不需要ReAct的三种场景纯问答、纯检索、规则明确的任务ReAct再有用也不是所有任务都该用它。我见过有人把“公司规章制度问答”也硬塞进ReAct循环结果花了三倍Token效果反而不如直接检索后回答。第一类不需要ReAct的任务是纯知识问答模型本身的知识或一次简单的向量检索就能回答不需要多步工具调用。第二类是纯检索任务用户只问“有没有某个文件”你检索一次就能出结果根本不需要模型“想”要不要再检索一遍。第三类是规则明确的任务比如“如果年龄大于18输出成人否则输出未成年”这是一个简单的映射用传统程序逻辑三步写完完全没有模型参与的必要。判断标准很简单任务是否需要多步、状态依赖、动态决策如果三个问题的答案都是否就别套循环。ReAct是给动态任务准备的不是给静态任务镀金的。5.2 步数与Token预算最容易被忽略的线性增长问题ReAct循环天然有个特性每一步都会把整段历史重新放回上下文。这意味着步数越多单次请求的Token开销越大而且这个增长是加速的。假设第一步消耗1000 Token第二步带上第一轮历史可能就变成1500第三步可能变成2100。到第10步单次调用的Token量已经非常可观。我在模拟项目X里做过一个简单测算一个原本3步就能完成的任务在保留全部历史的情况下总Token消耗大约是从单次问答的4到5倍。如果任务复杂度上升到10步这个倍数会进一步膨胀直接反映在调用成本和延迟上。应对思路有三个方向。第一尽量用截断和摘要控制观测信息的体积前面提过。第二开启更激进的历史压缩比如在步数超过阈值后由另一个轻量模型把早期历史浓缩成一段summary。第三也是很多人忽略的把任务边界划得更小不要让一个Agent包打天下而是把大任务拆给多个子Agent每个子Agent的循环步数都控制在低水平。5.3 从单循环到多角色协作ReAct怎么演变成小团队当你卡在步数限制、Token预算和决策质量三者之间时通常不是继续优化单循环而是该考虑拆分了。我现在的做法是把复杂任务拆成多个角色一个角色负责调研信息一个角色负责执行动作一个角色负责校验结果。每个角色内部仍然是朴素的ReAct循环但角色之间通过结构化的任务清单沟通。这样做的效果很像把一个什么都懂的全能员工换成一个各司其职的小团队。全能员工的优势是沟通成本低但缺点是所有决策都堆在一个上下文里很容易超出能力边界小团队的缺点是协调复杂但每个成员的上下文都保持精简决策质量反而更稳定。这个方向不建议在第一版就做。我自己的习惯是先用最小的单循环跑通等真的遇到步数膨胀、上下文超限、单一角色难以兼顾时再考虑拆分。提前做拆分只会让自己陷入更复杂的调试泥潭。最后再分享一个小习惯。我现在每次搭Agent原型第一版永远是朴素的ReAct循环不引入任何花哨的编排框架。原因很简单这个循环足够透明出了问题我能一行行看懂日志也能精确指出是哪一环节断掉了。新需求进来先用这个最小循环跑两轮验证“模型工具”的基本盘到底成不成立再决定要不要升级成多角色协作或者更复杂的方案。如果你正准备给大模型接上工具不妨也从这个朴素循环开始。把想法、动作、观察、再想法这四段当成基本功把截断、重试、脱敏、终止当成必做的四件套你会发现Agent的稳定性并没有想象中那么难。真正难的从来不是新概念而是把每一个细节都压实。