假设你问个人资料助手:
帮我查一下保存的读书会通知,报名截止日期是哪天?如果有调整,以调整后的通知为准。
助手有两个工具:"搜索通知"用于查找资料,"读取通知"用于取得某份资料的全文。模型根据问题提出工具请求,程序检查后执行,再把结果交给模型。
如果只有两份短通知,搜索时直接返回全文,就可能足够回答。现在换成资料较多、文件较长的情况:搜索先返回编号、标题和简短摘要,需要了解细节时,再按编号读取正文。
这样,第一次搜索完成以后,任务还没有结束。模型需要先看到找到了哪些通知,再决定读哪一份;读完之后,还要判断能否回答,或者是否需要继续查。
要让助手接着做,程序需要把每次执行的真实结果送回下一次判断。 "判断---行动---观察"的循环,就从这条反馈关系开始。
1. 下一步需要什么,可能要做完这一步才知道
先约定本次任务的范围:只根据当前用户获准访问的已保存通知回答,不查询报名系统的实时状态。两项工具也只读资料,不修改通知。
下面是一条用于解释流程的假设路径。本次搜索在约定范围内已查完,共找到 D1、D2 两份通知,属于同一主办方、同一场读书会;表中动作是设计示例。
| 本轮已有的信息 | 模型可提出的下一步 | 程序执行后取得的信息 |
|---|---|---|
| 用户要核对报名截止日期,尚未取得通知 | 搜索"读书会 报名" | 找到 D1《读书会报名通知》和 D2《读书会报名时间调整通知》,附编号、发布日期和摘要 |
| 搜索结果中有一份调整通知,但还没读到完整修改内容 | 读取 D2 | 正文写明:"本次读书会报名截止日期提前到2026年9月18日,原通知中的截止日期作废。" |
| 已取得 D2 的完整修改说明,且确认对应用户问的活动 | 给出有依据的回答 | 不再执行工具,向用户呈现答案 |
这时可以回答:
按查到的 D2《读书会报名时间调整通知》,报名截止日期已提前到 2026 年 9 月 18 日,原通知中的日期作废。
第一轮结束时,新增的是"找到了哪些资料";第二轮结束时,新增的是"调整通知具体改了什么"。这些变化分别解决不同缺口,才使任务能够走到回答。
模型可以在最初就提出"先搜索,再读调整通知"的计划,但 D2 的编号和具体内容仍要等执行后才能取得。计划描述的是准备做什么,下一步操作所用的资料标识和回答依据,则应来自实际返回。
2. 程序怎样把一次调用接成循环
为便于看清回路,这里每轮只处理一个工具请求。程序每次将当前问题、工具说明和已有结果交给模型,接收一个下一步动作;执行并取得反馈后,再发起下一次模型请求。
沿着这张图看,三个动作各有具体含义:
- 判断:模型根据用户目标和当前依据,提出下一步需要做什么。例如读 D2,或者说明现有信息不足。
- 行动:程序检查工具名称、参数和权限,执行允许的操作。模型提出"读取 D2"以后,仍要由程序真正读取。
- 观察:程序把执行得到的内容带回,模型在后续请求中据此重新判断。这里的"观察"包括成功取得的正文,也包括未找到、拒绝访问或读取失败。
关键连接点是图中的回路:工具结果进入后续输入,下一次判断才能利用它。给同一个模型连续发三次请求,如果每次都只有原问题,新的查询结果没有带入,就没有建立这条反馈关系。
程序还要提供明确出口。这个小助手可以先设置模型请求次数上限,并为模型调用和工具执行设定超时,防止重复查询或等待一直持续;达到上限时,报告已完成什么、还缺什么。模型选择回复用户,也只表示本次处理告一段落:补问意味着还在等信息,给出答案则仍需要核对是否有依据。
3. 下一轮必须知道"为什么查"和"查到了什么"
假设读取 D2 成功,程序下一次只把通知正文发给模型。模型虽然看到了日期,却可能缺少原问题,不知道用户问的是报名截止日、通知发布日期,还是当前能否报名。
相反,如果程序只保留原问题,没有加入 D2 的结果,下一轮又缺少刚刚取得的依据。
所以,最小循环需要持续保留一份与当前任务有关的记录。对于前面的路径,这份记录可以按发生顺序组织为:
text
用户问题:核对保存的读书会通知中的报名截止日期,有调整则采用调整后的通知。
第 1 次工具请求:搜索"读书会 报名"。
对应结果:找到 D1、D2,附各自的标题、发布日期和摘要。
第 2 次工具请求:读取 D2。
对应结果:D2 的完整正文,说明截止日期提前到 2026 年 9 月 18 日,原日期作废。
这是记录内容的示意,不是某个接口要求的消息格式。实际使用时,应按接口约定保存模型返回的调用项,并把工具结果对应到原来的调用。这样才能辨认某份结果属于哪一次搜索或读取。
组织输入时,先保留稳定的任务规则和工具说明,再放本次用户问题,已经发生的调用与结果随进展追加。模型下一次能使用哪些信息,取决于程序实际传入了什么,或接口按约定续接了什么。程序在本地执行过一个函数,并不会自动让下一次模型请求知道结果。
失败也需要进入这份记录。例如,D2 读取超时,程序应带回"读取 D2 超时,未取得正文"。模型此时只能知道有一份调整通知,不能据标题猜出调整后的日期。如果允许再次尝试,可以提出重读;如果无法继续,就应说明仍未核实修改内容。
从这条失败分支可以看出,循环并不是每轮都离成功更近。它让后续判断得到最新情况,也可能让系统及时发现当前任务无法继续。
4. ReAct 怎样把判断、行动和反馈连起来
ReAct 研究项目将推理与行动交替组织:模型结合当前情况生成推理文字和下一步动作,外部环境执行动作并返回观察结果,再供后续判断使用。
作者公开的问答实现中,这个过程有明确的程序对应关系。其 HotpotQA 示例代码每轮先让模型生成推理文字和动作,并在准备生成观察结果的位置停止;随后由程序执行动作,取得环境返回,再把这一步的动作与真实观察追加到后续输入。
这解释了为什么工具反馈必须经过程序:搜索返回了什么,由搜索执行结果决定。模型生成的文字不能直接充当"这次已经查到的内容"。
作者编写的提示示例展示了一次调整动作的过程:搜索 High Plains 后,反馈指出这个名称可能指向不同地区,于是下一步改为更明确的 High Plains (United States)。新的搜索条件来自刚得到的歧义信息。这是用于向模型示范的轨迹,说明了反馈怎样影响动作选择;它本身不代表任意模型都能稳定完成同样的判断。
原始 ReAct 方案显式使用了推理文字。理解更一般的工具循环时,还可以直接观察模型提出了什么请求、程序执行了什么、返回了什么,以及下一步如何变化。这样的调用记录足以检查反馈有没有传回、动作是否有依据;不需要把可见的解释文字当作模型全部内部推理的记录。
5. 什么情况下值得让模型每轮重新判断
回看"搜索通知---读取 D2---回答"这条顺利路径,普通程序也可以安排相同的步骤。资料关系和处理规则足够稳定时,固定流程容易检查,也能减少中间的模型请求。
让模型每轮选择动作,更适合下一步取决于当时取得的内容,而且这些分支难以提前列全的任务。例如,通知正文引用了另一份补充说明,需要继续核对;搜索结果出现同名活动,需要判断是否先补问;读取失败后,需要结合已有依据决定能回答到哪里。
这种灵活性也有成本:每增加一次判断,通常会增加一次模型请求的等待和费用,传入的任务记录也会占用更多词元(Token)。模型还可能选择无关资料,或者反复执行没有进展的搜索。因此,应让每次新动作对应一个具体缺口,并由程序控制允许继续的范围。
可以用三个变化检验自己是否理解了循环:
- 搜索已经返回两份通知的完整正文,修改关系也很明确,还要再读取 D2 吗? 如果没有新的核对需求,就不必增加这一步。动作是否需要,取决于还缺什么依据。
- 读取 D2 失败,下一轮却仍然收到一份"读取成功"的旧结果,会怎样? 模型可能沿旧情况继续回答。需要核对结果与调用的对应关系,并准确带回本次失败,循环才能反映当前进展。
- 连续两次用同样条件搜索,都得到相同的空结果,还能因为"需要继续判断"而一直查吗? 如果没有新的信息、条件或暂时故障可供处理,重复执行缺少继续的理由;程序应限制次数,并让助手说明当前范围内未找到。
一个有用的循环,让每次行动取得的事实改变后续判断:有了充分依据就回答,出现新缺口就选择相应动作,条件不允许时就停止并说明。判断、执行和反馈之间的连接是否真实、完整,决定了多次调用能否共同推进同一个任务。