
很多人第一次看到 ReAct 这个词还以为是哪个前端框架出了新版本。其实不是ReAct 来自一篇 AI 论文全称是 Reasoning Acting也就是让大语言模型LLM在思考的同时真正动手调用工具。它在 Agent 开发里的地位基本相当于“Hello World”之于编程。现在主流的 Agent 框架底层核心循环基本都是这个思路只是有的套了一层壳、有的换了套说辞。这篇文章不打算堆概念。我会先把 ReAct 的底层逻辑讲清楚——为什么光有思考不够为什么光有行动也会翻车两者到底怎么配合才能出活然后给出 6 个不同场景的完整实例从最简单的数字计算到多工具协作每一个都拆到最细的粒度给你看。不管你是刚接触 Agent 开发还是已经写过一些 Agent 但总觉得哪里不对劲这篇都能帮你把这块拼图补完整。1. 先把 ReAct 这个词拆开Reasoning 和 Acting 到底在干嘛1.1 为什么纯靠“想”不够用先说个反常识的事实大语言模型本身不会算数也不会查最新信息更不会帮你操作任何系统。你问它“128973 乘以 457 等于多少”它大概率会给你一个看起来合理但实际是错的答案。这不是因为它笨而是因为它的工作方式是“预测下一个词”不是“执行计算”。同样你问一个模型“今天北京天气怎么样”如果它的训练数据只到某个时间点它只能根据统计规律编一个答案出来而且编得特别自信。这就是纯推理的局限第一知识有时效性模型参数里存的信息是训练那一刻的快照第二模型没法真正执行操作它只能“说”不能“做”第三幻觉问题在它不确定的时候它倾向于编一个听起来顺滑的答案而不是说“我不知道”。以上这些就是纯 Reasoning 的死角。那如果只给模型一堆工具让它随便调呢问题也很大。模型不知道该先用哪个、用完之后结果怎么处理、这个结果是不是合理的。它就像一个人拿到了工具箱但没有任何施工图纸看到什么拿什么最后大概率会把事情搞得更乱。更麻烦的是如果中间某一步工具返回了异常结果模型如果缺少判断能力就会把这个异常结果当成正确答案继续往下走一错到底。所以结论很直接光想不行光做也不行必须“边想边做、边做边想”这就是 ReAct 的核心主张。1.2 ReAct 的核心循环Thought-Action-ObservationReAct 把一次任务的解决过程拆成了三个环节循环Thought推理模型用自然语言描述自己当前的想法。比如“用户想比较两款手机我需要先获取 A 手机的相机参数”。Action行动模型根据推理结果决定调用哪个工具并传入参数。比如调用搜索工具关键词是“A 手机 相机 参数”。Observation观察工具返回结果把真实信息回传给模型。可能是搜索到的文本、计算器的输出、代码的执行结果等。然后模型会基于 Observation 再生成新的 Thought进入下一轮循环一直到它认为任务完成输出最终答案。这个过程听起来简单但它的精妙之处在于Thought 赋予了模型“规划能力”Action 赋予了模型“执行能力”Observation 则保证了模型的每一步都基于真实信息而不是凭空猜测这是它区别于普通 Prompt 调用的关键。1.3 ReAct 和 CoT、纯 Tool Calling 的区别很多人容易把 ReAct 和另外两个概念搞混一个是 CoT思维链Chain of Thought一个是 Tool Calling工具调用。CoT 的做法是让模型“先把思考过程写出来再给答案”它确实是 ReAct 里 Thought 环节的前身但它只有“想”没有“做”。模型写了一大段推理最后输出的还是训练数据里已有的知识外部世界的信息进不来模型自己也改不了任何现实状态。Tool Calling 则偏向另一个极端。它把工具列表传给模型模型根据用户问题直接选择工具并生成调用参数然后把工具的返回结果交给模型生成最终回复。这种模式的问题是它缺乏中间推理过程。模型选工具的依据是什么如果多个工具都能完成类似功能选哪个如果工具返回的结果不理想要不要换一个这些问题如果不在 Prompt 里写清楚纯 Tool Calling 就很容易变成“撞运气式调用”。ReAct 把两者的优势合并在一起给每个 Action 前后都加上了“为什么这样做”的推理。用大白话说CoT 是在脑子里写草稿Tool Calling 是直接上手干活ReAct 是每干一步都先想清楚为什么要这么干、干完看看结果再想下一步。这个微小的差异在实际项目里带来的稳定性提升是巨大的。方式有没有推理能不能调用工具出错后能不能自我修正普通 Prompt无显式推理无不能答错就是错了CoT有无不能思考再周密也无法验证Tool Calling无显式推理有很难结果异常时缺少判断依据ReAct有有能Observation 反馈推动下一轮修正2. 6 个实例怎么选出来的每条都踩在一个关键能力上2.1 由浅入深的选择逻辑市面上讲 ReAct 的文章很多但大部分只给一个示例讲完思想就结束了。我这次特意准备了 6 个实例是因为 ReAct 这个模式在不同的任务难度下呈现出来的面貌是完全不一样的。在最简单的情况下你只需要一个 Thought 配一个 Action 就能收尾。稍微复杂一点你需要连续多个 Action 来搜集信息然后统一做分析。再复杂一点你还需要处理工具调用失败、需要根据中间结果调整计划、需要把多个工具的输出整合成一个结构化结果。如果只在最基础的层面理解 ReAct你写出来的 Agent 稍微碰点真实业务场景就会崩。所以这套实例本质上是一条完整的“能力阶梯”从最基础的、不涉及工具也能看懂的环节开始一直走到多工具协作。2.2 六个实例分别覆盖什么死角我按“你会在真实开发中遇到的难度递增梯次”来选了这 6 个场景实例核心场景主要展示的能力点实例一5000 元预算计算用 Thought 拆解计算步骤用工具保证数值准确实例二两款手机对比如何通过检索获取新知识再结合常识做判断实例三把一段评论转成 JSON结构化输出展示推理如何保证格式规范实例四三天旅行规划多步骤任务拆解Agent 自主安排行动顺序实例五写代码再自己修 bug代码执行与错误反馈Observation 驱动自我修正实例六用户反馈表分析多工具灵活协作串起完整业务链路每个实例都代表 ReAct 在某一个能力维度上的体现把它们串起来看你脑子里就会有一个完整的 Agent 工作模型。接下来我们逐个拆开看。3. 逐个拆解 6 个实例看 ReAct 的真实工作流3.1 实例一5000 块预算怎么算数学推理 计算器工具场景用户问“我有 5000 块预算想买一件 800 块的夹克、一双 1200 块的鞋、一条 300 块的腰带买完之后还剩多少如果剩的钱想再买个 400 块的背包够吗”如果直接让模型心算它可能会给你一个错误的数字。但如果设计一个 ReAct 循环加了计算器工具流程会是这样Thought用户需要计算总消费和剩余预算。我需要先计算三件商品的总价再用 5000 减掉这个总价。为了精确我用计算器工具完成加减法。Action调用 calculator800 1200 300Observation2300Thought三件商品总价是 2300。剩余预算 5000 - 2300 2700。用户最后问的是 2700 够不够买 400 块的背包所以 2700 大于 400答案是够的。这个比较很简单我直接基于 2700 判断。Finish剩余 2700 元买 400 元背包绰绰有余。这个实例看着简单但它体现了一个关键思想模型负责设计计算路径、选择工具、解释结果而真正“算出数字”的活交给外部工具。我见过很多新手在这里会犯一个错误觉得例子简单就直接让模型自己算不接工具。但实战中你的计算任务不会永远是三位数加减法一旦涉及汇率、折扣叠加、税费模型出错的概率会指数上升。从一开始就养成“凡是数值计算一律走工具”的习惯能帮你省掉大量调 bug 时间。3.2 实例二两款手机哪个更值得买检索 对比推理场景用户问“我预算 4000 左右想在 A 和 B 两款手机里选一个平时主要拍照和刷视频哪个更适合我”这个问题单独抛给模型它能答但答案里的“参数”很可能已经过时甚至某些机型可能是模型训练数据里不存在的。ReAct 的处理流程会是这样Thought用户预算 4000 左右需求是拍照和刷视频。我需要拿到两款手机的核心配置尤其是相机参数、屏幕素质和价格。我的知识可能过时所以先搜索最新信息。Action调用 search(A 手机 最新款 价格 相机配置)ObservationA 手机起售价 3999后置主摄 5000 万像素支持 OIS 光学防抖屏幕 6.7 英寸 OLED120Hz 刷新率。ThoughtA 的关键信息拿到了。接下来查 B。Action调用 search(B 手机 最新款 价格 相机配置)ObservationB 手机起售价 4299后置主摄 4800 万像素支持 3 倍长焦屏幕 6.5 英寸 OLED90Hz 刷新率。Thought对比一下。价格上 A 便宜 300 块更符合 4000 预算。拍照方面A 主摄像素更高且有光学防抖对日常拍照更友好B 多了一颗长焦镜头但用户主要拍生活照和视频长焦使用频率未必高。屏幕方面 A 的刷新率也更高刷视频更流畅。综合来看A 更适合这个用户。Finish推荐 A 手机理由写清楚给用户。这个实例最有价值的地方在于模型通过检索拿到了真实数据再结合“用户需求”进行推理而不是背参数。搜索这个 Action 的价值不只是补全信息它同时把模型的“知识截止日期”往后推到了当下这个能力在真实 Agent 系统里至关重要。3.3 实例三把一段评论变成 JSON结构化输出场景用户在系统里输入了一段产品评论“这耳机降噪真好坐地铁完全听不到噪音就是戴久了耳朵有点疼”要求 Agent 输出一个 JSON包含情感倾向、关键优点和缺点。纯 Prompt 方式也能得到 JSON但字段名偶尔会变有时候多一个空格、有时候换了个 key尤其当你要求它“判断情感倾向给出数值”时它给的分数往往没有客观依据。ReAct 的流程可以这样设计Thought任务是把评论解析成结构化 JSON包含 sentiment情感倾向、positive_points优点列表、negative_points缺点列表。我需要按语义拆解这条评论不能只凭整体印象而是先切分句子再归因。Action调用 text_analyzer(这耳机降噪真好坐地铁完全听不到噪音就是戴久了耳朵有点疼)让一个更小、更可控的模型返回句子级情感标注。Observation句子1“这耳机降噪真好” 情感 positive句子2“坐地铁完全听不到噪音” 情感 positive句子3“就是戴久了耳朵有点疼” 情感 negative。Thought句子级结果已经拿到了。整体情感倾向是正向为主因为 2 条正面、1 条负面负面属于舒适度问题而非核心功能问题所以 sentiment 可以标记为 positive综合评分可以给 0.8。优点里放“降噪效果好”缺点里放“长时间佩戴舒适度一般”。Finish{sentiment: positive, score: 0.8, positive_points: [降噪效果好], negative_points: [长时间佩戴舒适度一般]}这里有一个细节值得注意我没有让 ReAct 的 Thought 直接去“感受”这条评论而是先调用了一个更细粒度的分析工具。在复杂业务里这其实是 Agent 的常用手段——先用小模型或专用模型做一个中间结果再用主模型做综合判断这样既省 token 又提升准确率。3.4 实例四三天旅行规划多步骤任务拆解场景用户说“我想在下个月 15 号到 17 号去杭州玩三天预算 3500 左右主要在市区想看西湖、逛博物馆、吃本地菜帮我做个简单行程。”这类任务最大的特点是没有“唯一正确答案”但行动路径是多步骤的而且 Action 之间有先后依赖关系。Thought用户需求包含三个信息点时间下月 15-17 号、预算3500、偏好西湖、博物馆、本地菜。我需要先查天气确定出行建议再查这几个地点的开放时间和周边餐饮信息最后把预算拆分到住宿、餐饮、门票和交通。Action调用 search(杭州 下月15号到17号 天气)Observation预计 15-17 号杭州多云转小雨温度 12-18 度需要带伞和薄外套。Thought天气信息影响行李和行程安排下雨天适合优先安排室内活动比如博物馆。接下来查西湖周边和博物馆的开放情况。Action调用 search(杭州博物馆 开放时间 预约要求)Observation杭州博物馆周二闭馆免费开放需要提前在公众号预约。Thought下月 15 号是周四博物馆正常开放。那行程可以这样安排第一天下午到杭州后去西湖第二天上午博物馆、下午吃本地菜第三天上午附近逛逛、午后返回。Action调用 hotel_search(杭州 西湖区 下月15-17 预算 200-300每晚)Observation附近有两家符合预算的酒店一家 260 每晚一家 288 每晚。Thought住两晚按 288 算也就是 576。餐饮每天 150 左右三天 450。市内交通预估 200。门票基本免费。总预算大概 576 450 200 1226远低于 3500用户预算很宽裕可以考虑在餐饮和住宿上稍微提升。Finish给出行程表附上预算明细和天气提醒。注意看这个流程每一步 Thought 都在复用上一步 Observation 的信息再决定下一步 Action。这就是 ReAct 处理多步任务的核心能力——信息在循环中不断累积计划在执行中不断微调。如果你用一次 Prompt 让模型直接出完整行程它给出的方案会是“一次性编造”而不是“逐步推演”中间的应变能力基本为零。3.5 实例五让 Agent 自己写代码再自己修 bug代码执行 错误反馈场景用户让 Agent 写一个 Python 函数计算给定数字的阶乘并测试 20 的阶乘结果。这一步开始工具的作用就不仅仅是“查资料”了而是真正执行模型生成的代码。Thought用户要的是阶乘函数。最简单的是递归写法我直接生成代码并执行测试。Action调用 code_executor(def factorial(n): return 1 if n 1 else n * factorial(n-1); print(factorial(20)))Observation执行成功输出 2432902008176640000。Thought结果看起来合理20 的阶乘确实是这个数字。任务完成但为了保险我再快速验证一下边界情况。Action调用 code_executor(print(factorial(0) 1, factorial(1) 1, factorial(5) 120))ObservationTrue True TrueFinish函数实现正确并附上测试结果。这个实例要展示的核心不是“模型会写代码”而是 ReAct 模式天然支持模型在写完之后立即通过工具执行看到结果再决定是不是要补充测试、修改逻辑。这种“验证—反馈—修正”的闭环是代码类 Agent 能落地的根本原因。我再补充一个真实踩过的细节如果你用的是递归实现测试 100 的阶乘很可能会触发递归深度上限返回 RecursionError。在 ReAct 循环里这个错误会被放进 Observation模型看到之后就可以主动改用迭代版本或者 math.factorial 重写。没有这个循环模型生成的代码在用户手里报错了它自己是完全不知情的。3.6 实例六用户反馈表分析多工具协作场景产品运营给 Agent 丢来一张 CSV 表格里面是 500 条用户反馈要求 Agent 找出最集中的 3 个问题并给出改进建议。这个实例集合了读取文件、统计分析、语义归类、方案建议等多个环节是 ReAct 目前最高频的真实业务形态。Thought任务分三步第一读取 CSV 看数据结构第二对反馈文本做关键词/主题聚类第三根据占比给出优先级建议。Action调用 read_file(feedback.csv)Observation文件共 500 行字段包括 user_id、feedback_text、submitted_at。Thought数据结构清楚了。下一步需要统计反馈文本里的高频关键词我调用关键词提取工具先看全量分布。Action调用 keyword_extractor(feedback.csv 里的 feedback_text 列)Observation高频词前五物流慢 34%客服响应慢 26%包装破损 18%价格太贵 12%其他 10%。Thought物流慢和客服响应慢加起来占 60%是绝对大头。包装破损虽然只有 18%但和物流体系强相关可能是运输环节造成的。我需要看看这三类反馈的原文确认具体表述。Action调用 search_feedback(feedback.csv, [物流慢, 客服响应, 包装破损])Observation物流类反馈集中在“三天没更新物流信息”“派送延误”客服类集中在“转人工要排队十分钟以上”包装类提到“箱子有压痕”“内部缓冲不足”。Thought三类问题指向很明确物流信息透明度不足、客服接入效率低、包装防护不够。基于这个判断建议优先级应该是先解决物流和客服两个影响面最大的问题包装问题可以同步优化。Finish输出 3 个核心问题列表每个问题附证据原文摘要和可落地的改进建议。这个实例最大的价值是展示了 ReAct 的一个隐藏优势灵活切换工具。你会注意到Agent 每走一步调用的工具都不一样读文件的、提取关键词的、条件检索的、整合分析的全都不一样。它在每一步都会根据当前掌握的情况动态决定下一个工具是什么。这种“计划跟着信息走”的动态性才是 ReAct 真正的大杀器。4. 新手最容易踩的坑以及排查思路4.1 消息太长把上下文撑爆了这是 ReAct 实现里最经典的新手错误。Agent 在循环里不断调用工具每次返回的 Observation 都往上下文里塞。如果你给 Agent 的上下文窗口是 8K它可能到第三轮就把窗口占满了然后模型开始遗忘最早的信息或者直接报错。解决办法很朴素给 Observation 做截断。比如只保留返回结果的前 500 个字符或者用摘要工具把长文本压缩成几个要点再喂给模型。另外日志里要加一个 token 计数超过阈值自动触发清理机制。4.2 死循环Agent 为什么停不下来新手写 ReAct 时常遇到一种情况Agent 在一个问题上反复调用同一个工具每次 Observation 都一样但 Thought 还坚持要再试一次。这就是典型的“卡死在循环里”。原因通常是两个。第一Prompt 里没有明确告诉模型“什么时候该停止”模型不知道输出 Finish 是合法的结束动作。第二max_iterations 没有设置或者设得过大。我现在的习惯是在任何 Agent 里都硬性设置最大循环次数20 轮就上限超过直接返回超时错误这样至少不会让用户无限等待。同时要在 Prompt 里强调“如果 Observation 结果和上一轮一致请结束循环不要重复调用。”4.3 Thought 变成废话循环失去意义ReAct 的灵魂在于 Thought 要真正服务决策。但很多新手写出来的 Thought 是“我调用了 xxx 工具它返回了 xxx”这只是在复述流水账没有任何推理含量。一个健康的 Thought 应该包含三个要素对当前处境的理解、为什么选这个下一步、期望从结果里得到什么。如果模型输出的 Thought 里没有“因为”“所以”“如果”这类逻辑连接词那就要考虑是不是 Prompt 引导不够或者模型本身没理解这个角色。你可以在系统提示里加一句“Thought 部分请解释原因不要描述动作本身。”4.4 工具定义不清楚模型乱选工具工具列表里包含几十个工具时模型经常选错。根源往往不在模型而在工具描述写得不行。比如一个工具描述是“获取天气”另一个是“获取城市信息”模型面对“杭州下雨吗”这个问题时可能分不清该调哪个。工具描述要写成“什么时候用”和“怎么用”的组合。比如“获取天气当用户询问某地某时的天气情况时调用参数包括城市和日期如需判断是否下雨、是否带伞也使用此工具”。这样模型选择工具的准确率会大幅提升。4.5 失败后不会恢复一错到底这是 ReAct 目前比较难啃的问题当工具返回错误时有的模型会假装没看到继续按原来的思路走。比如搜索接口超时返回 500模型却写“根据搜索结果显示……”这就是需要修正的行为。我建议在 Prompt 里明确加入错误处理分支“如果工具返回异常、超时或空结果请优先换一种方式重新尝试比如改用另一个关键词、换用另一个工具如果连续失败 3 次向用户说明当前不可用不要编造结果。”同时工具返回的结构里最好包含状态字段让模型能一眼判断成功还是失败。4.6 常见问题速查表现象可能原因排查方向Agent 循环调用同一工具没有停止条件检查 max_iterations检查 Observation 是否有效更新最终答案信息过时没有用搜索工具查看循环里是否有检索 Action输出格式不稳定Thought 环节没有做格式规划在第一次 Thought 里就要求模型先定输出 schema工具调用参数乱填工具描述不清晰重写工具描述加入“何时用”和“参数含义”上下文越用越长Observation 没有截断对工具结果做长度限制或摘要处理Agent 开始编造工具结果缺少错误处理指令在系统提示中加入失败分支规则禁止虚构 Observation5. 从“看懂”到“会用”一点实操心得5.1 先固定循环再加工具很多初学者上手就贪多一次性接五六个复杂工具结果直接被各种参数和返回格式淹没。我自己的习惯是先写一个只有一种工具的 ReAct 循环比如只有计算器然后跑通 10 个不同的问题把 Thought-Action-Observation 的格式吃透再逐步加搜索、加代码执行器。每加一个工具都要观察模型会不会在新旧工具之间产生混淆。工具不是越多越好而是模型“调得准”才算数。5.2 日志是第一生产力ReAct 是一个循环系统调 bug 的核心就是看每一轮的 Thought、Action、Observation。我强烈建议你在 Agent 外层包一个详细日志模块把每一轮的这三个要素全部打印出来。很多时候用户说“Agent 回答得很蠢”我打开日志一看发现是第三轮开始 Thought 就跑偏了后面的轮次都在错误方向上越走越远。5.3 一个偷懒的技巧先手写一次完整轨迹在你写 Prompt 之前先扮演要解决的问题亲手写一遍期望中的 ReAct 轨迹包括每一轮的 Thought 说什么、Action 传什么参、Observation 大概长什么样。然后把这个轨迹里的“理想 Thought”作为示例写进 Prompt 的 few-shot 部分。这个习惯帮我节省了大量调试时间模型的初次命中率直接上一个台阶。5.4 从底层写一次别只调框架最后说一个个人偏好。市面上 LangChain、LlamaIndex 这类框架都内置了 ReAct 模式很多人图省事直接调用出了问题却不知道从哪排查。我建议每个想做 Agent 的人至少用底层 LLM API 手写一次 ReAct 循环就是自己维护 messages 列表自己写工具调用的判断逻辑自己处理 Observation 返回。当你亲手实现过一遍这个循环再回头看那些框架的封装你会觉得一切都很清晰排查问题也不会一头雾水。我第一次手写 ReAct 循环时光是处理“模型输出了 Action 但参数是非法 JSON”这一个问题就折腾了两天。后来我在 Prompt 里加了两个示例问题立刻缓解。这个领域就是这样看起来原理很清晰但细节里全是门道。希望这 6 个实例能让你少踩几个我踩过的坑把 ReAct 真正用到自己的 Agent 项目里去。