从回答问题到完成任务

假设你要做一个采购助手,用户问:“仓库里还有多少支黑色签字笔?不够 20 支的话,需要补多少?”

完成这个任务,需要查询库存,再根据结果计算。设计应用时,不能预先假定库存是多少,也不能让模型凭空填写查询结果。

ReAct 提供了一种组织方式:让模型根据已有信息决定下一步行动,获取外部反馈,再调整后续步骤。它把推理与行动交替组织起来,使任务能够随着新信息逐步推进。(react-lm.github.io)

ReAct 到底是什么

ReAct 来自 Reasoning 与 Acting,可以理解为“推理与行动相结合”。这里的推理负责分析任务、判断缺少什么信息;行动负责与外部环境交互,例如搜索资料、查询数据库。两者通过反馈连接起来。(doi.org)

经典表达包含三个部分:

  • Thought(思考):分析当前状态,确定下一步目标。
  • Action(行动):发出具体操作请求。
  • Observation(观察):接收操作产生的真实结果。

模型读取观察结果后,继续判断,直到能够回答或完成任务。这种交替可以按任务需要安排,不要求每次行动前都输出一大段推理。(research.google)

在下面的应用设计中,我们只保留简短的行动说明,方便调试;执行器真正依赖的是工具名称、参数和返回结果。

为什么需要这个循环

只依靠模型已有知识进行推理,无法直接取得外部环境中的新信息;只安排一串动作,又可能缺少对目标、进度和异常的判断。ReAct 将两者结合,让观察结果影响下一步决策。(research.google)

回到采购助手:如果查询返回多个同名商品,就应先确认规格;如果只找到一个商品,就可以继续读取库存。这种路径需要根据结果选择。

不过,步骤明确的任务可以直接写成固定流程。引入自主决策通常会增加调用成本和等待时间,因此应从简单方案开始,在确实需要灵活调整路径时再使用 Agent。(anthropic.com)

把循环拆成可实现的程序

建议先搭一个小型执行器,明确三方职责:

  1. 模型选择下一步动作,输出结构化请求。
  2. 程序检查请求,调用允许使用的工具。
  3. 工具返回结果,程序将结果加入上下文,再次调用模型。

这对应了 ReAct 中“推理指导行动、环境反馈支持后续推理”的过程。(research.google)

为这个示例约定两种输出:action 表示调用工具,final 表示结束并回答。工具请求必须包含名称和参数;结束时必须给出答案。把这些约定连同工具用途、参数含义一起交给模型。

一个查库存的小例子

以下数据完全用于演示。假设工具 get_stock 返回黑色签字笔的可用库存为 8 支,一次成功执行可以写成:

用户:黑色签字笔够 20 支吗?不够需要补多少?

行动说明:查询该商品的可用库存。
Action:get_stock(item="黑色签字笔")
Observation:{"item": "黑色签字笔", "available": 8}

Final:目前有 8 支,不足 20 支,还需要补 12 支。

这里将“可用库存”设为明确字段,避免把已被其他订单占用的数量也算进去。本例只计算缺口,不执行采购。

下面是简化的 Python 伪代码,函数需要按实际模型接口实现:

tools = {"get_stock": get_stock}
history = [user_message(question)]

for _ in range(6):
    step = model_next(history, tool_specs)

    if step["type"] == "final":
        answer = step["answer"]
        break

    # 检查工具白名单、参数类型及访问权限
    validate_action(step, tools)

    try:
        result = tools[step["tool"]](**step["args"])
    except ToolError:
        result = {"error": "查询失败,请调整请求或停止"}

    history.append(action_message(step))
    history.append(observation_message(result))
else:
    answer = "达到执行上限,暂未完成查询。"

这个示例把最大轮数设为六轮。动作和结果都写入历史,因此下一次模型调用能看到上一轮发生了什么。实际接入时,还应按所用接口的消息格式,将工具结果与对应的调用关联。

开发时先处理这些坑

建议把下面几项作为这个小项目的实现要求:

观察结果必须来自执行器。 模型提出查询请求后,由程序调用工具,再注入返回值。不要让模型自行续写一段“查询成功”,然后把它当成真实库存。

工具描述要具体。 对 get_stock,说明商品如何匹配、库存单位是什么、查不到时返回什么。清晰的工具定义与参数说明,是可靠使用工具的重要基础。(anthropic.com)

停止条件要写进程序。 除了轮数限制,再设置超时和重复调用检查。同样的参数连续失败时,结束并说明缺失信息,避免无意义地继续查询。

将查询与修改分开授权。 本例只开放读取库存。如果以后加入采购功能,应在程序层检查数量、预算和用户授权,不能仅凭模型输出就提交订单。

用失败样例验证流程。 除了“库存为 8”,再测试商品不存在、返回多个规格、工具超时等情况。验收时检查最终答案是否有真实结果支持,而不只看文字是否流畅。

小结

理解 ReAct,可以抓住一个循环:根据当前信息选择行动,取得真实反馈,再决定下一步。它让推理与环境交互共同推动任务完成。(doi.org)

入门时,可以先做上面的只读库存助手:一个工具、一种任务、明确的停止条件。跑通后逐步增加异常处理和工具种类。每扩展一步,都检查模型做了什么、工具实际返回什么,以及最终答案是否与结果一致。

参考来源