160 of 610

智能体循环:AI 如何从回答问题走向完成任务

B
ByteByteGo

聊天机器人回答一个问题,智能体(agent)完成一项任务。这看起来差别很大。不过,两者之间的距离比表面上更近。

智能体就是被放进一个循环中的大语言模型(LLM),由模型自己决定循环何时停止。关于智能体的一切有趣之处,都源自这次转变。让它们有用的自主性、让它们昂贵的成本,以及搭建智能体时的设计难题,都可以追溯到这里。

把这次转变放进更大的图景中看,会更容易理解。

围绕语言模型构建的软件经历了一条可以辨认的演进路径。最初是一次 LLM 调用:接收输入,产生输出。接着出现了函数调用,让模型在需要信息或想采取行动时能够调用工具。随后,开发者开始在代码中把多次调用串起来,让每一步的输出成为下一步的输入,以处理单次调用难以解决的问题。最近的阶段就是智能体:开发者把循环本身的控制权交给模型,让它不断迭代,直到自己判定工作已经完成。

本文会逐步介绍这条演进路径。我们也会看看智能体怎样组织起来、模型每一轮会做哪些选择、什么支撑结构把整个系统维系在一起,以及什么时候智能体才是合适的模式。

原作者声明:本文依据多个来源公开分享的细节撰写。如发现不准确之处,请留言指出。

基础

要理解循环的有趣之处,得先弄清循环里的基本单元。

一次没有额外能力的大语言模型调用,是一个无状态函数。文本输入,文本输出;在模型看来,每次调用通常彼此独立。如果我们想让它获取今天的天气,或更新数据库中的一条记录,模型本身没有完成这两件事的手段。它可以笼统地描述天气,也能谈论一条数据库记录该怎样修改,但要获取实际的天气预报,或操作实际的数据库,还需要额外的能力。

让模型能够在真实系统中发挥作用的,是能力增强。

换句话说,我们让它能够调用自己定义的函数,这通常称为工具使用(tool use)或函数调用(function calling)。我们让它访问检索层,在运行时调取相关文档。我们也让它能够记录需要在不同调用之间保留的信息,发挥记忆的作用。

Anthropic 把这些能力的组合称为增强型 LLM(augmented LLM),并将其视为每个智能体系统的基础单元。其他一切,都由增强型 LLM 这个构件组合而来。

大多数开发者都已经用过这个单元,尽管可能用了其他名称。例子包括能够运行 Python 代码的聊天助手、接入你所提供服务的函数调用 API,以及从你的文档中检索内容的检索增强生成(RAG)应用。这些系统很有用,但也仍然只是一次调用。模型产生输出,系统返回结果,交互就结束了,直到下一个请求到来。1

增强型 LLM:工具、记忆和检索围绕模型组成基础单元

增强型 LLM:基础构件。

工作流

当一个问题超出单次调用的处理能力,会怎样?

自然的做法,是按照我们设计的顺序把调用串起来;这种模式称为提示链(prompt chaining)。链中的每一步都是一次独立的 LLM 调用,一步的输出成为下一步的输入。我们可能用第一次调用起草提纲,第二次调用把提纲扩展成段落,第三次调用把段落翻译成另一种语言。调用链事先固定下来,开发者写明哪些步骤会运行、按什么顺序运行,以及每一步的提示词是什么。

Anthropic 把这一组模式归为工作流(workflow)。

在工作流系统中,LLM 及其工具按照开发者设计的代码路径接受编排。除了提示链,还有其他工作流模式:

  • 路由(routing): 对输入进行分类,再将它送到专门的处理程序。
  • 并行化(parallelization): 同时运行多个子任务。
  • 编排者—工作者(orchestrator-worker): 由一个管理型 LLM 将任务的各个部分交给专门的 LLM。
  • 评估者—优化者(evaluator-optimizer): 一次调用负责生成,另一次负责批评,循环迭代,直到质量足够好。

细节各有不同,但每种工作流都有同一个特点:在模型看到输入之前,开发者已经在设计阶段确定了步骤数量和经过这些步骤的路径。2今天,大多数基于 LLM 的生产系统都是工作流。它们可预测、可调试,通常也比完整的智能体便宜。

从单次调用、调用链到智能体循环的架构演进

从单次调用,到调用链,再到循环:架构的演进。译注:原图中间调用链的末端箭头标为 input;按该流程,应为 output。

循环

把 LLM 包在一个循环里,让模型决定循环何时退出,我们就得到了智能体。循环本身只是普通代码。运行时调用 LLM,读取输出,派发输出中指定的行动,再把结果放回模型能够看到的内容中,然后再次调用模型。这个过程一直持续,直到模型产生表示最终答案的响应。

每次迭代包含四个步骤,可以称为感知、推理、行动和观察:

  • 感知(perceive): 循环把当前状态交给模型,其中包括原始任务、迄今发生的事情,以及任何新输入。
  • 推理(reason): 轮到模型产生响应,说明下一步该做什么,无论是提问、调用工具,还是给出最终答案、结束任务。
  • 行动(act): 运行时执行模型所要求的行动。
  • 观察(observe): 行动结果被记录并纳入状态,模型在下一次感知步骤中就能看到它。

智能体的感知、推理、行动、观察四步循环

感知、推理、行动、观察:每个智能体内部的四步循环。

最重要的细节,是由谁决定循环何时停止。在工作流中,开发者在设计阶段决定运行多少步骤;在智能体中,模型在运行时做这个决定。模型通过产生一份被运行时解释为最终答案的输出,来退出循环。开发者通常会为迭代次数设置硬性上限,常称为 max-turns 参数,但这个上限主要是一张安全网。主要的停止信号来自模型。

观察之所以也应当是一个独立、重要的步骤,原因相同。

模型需要先看到行动的结果,再决定下一步。去掉观察,就会让循环退化成调用链;模型会依据先前的预期继续运行,而无法依据来自外部世界的新结果。每次行动之后都有观察的闭环,才让模型能够边做边调整。

建立起循环之后,下一个问题是:每一轮究竟会发生什么?

决策

在循环的每一轮中,模型的输出会选择四个分支之一,运行时按这个选择执行。这样的分支让循环显得智能,因为 LLM 在选择采取哪一种动作。这四个分支是:

  • 最终答案(final answer): 模型产生一份相当于对原始任务的完整回复。运行时将其解释为退出循环的信号,返回结果,然后停止。
  • 工具调用(tool call): 模型产生一条指令,要求运行时用指定参数调用指定函数。运行时执行工具,记录返回内容,将结果追加到对话状态,再把控制权交回模型,让循环继续。
  • 移交(handoff): 模型判定当前任务应该交给另一个智能体,通常是拥有自己提示词和工具集的专门智能体。运行时切换正在运行的智能体,并将同一个状态交给新智能体,让循环在新的身份下继续。
  • 继续思考(continued thought): 模型产生一轮只包含思考的推理。运行时记录这些思考,将其放回状态,再次运行模型。这个分支最常见于 ReAct 风格的实现,我们马上就会介绍。

最终答案、工具调用、移交和继续思考四个决策分支

四个分支的决策树。译注:原图最右侧的分支标题也写成了 Handoff;根据正文及该框内的说明,它对应 continued thought,即继续思考。

OpenAI 的 Agents SDK 将前三个分支列为其循环直接支持的行为。第四个更多取决于提示词怎样编写,不一定对应一条独立的代码路径。

ReAct

ReAct 是一种提示模式,名称代表推理(Reasoning)加行动(Acting)。它要求模型在同一个响应中交替安排推理步骤与行动步骤,让模型能够思考该做什么、采取行动、看到结果,再作调整。

一段 ReAct 轨迹读起来像结构化日志。模型先产生一个想法,然后采取行动,再接收运行时返回的观察结果,接着产生另一个想法,如此继续,直到得到最终答案。

设想一个处理客服事务的智能体:

  • 用户询问自己最近一笔订单的状态。
  • 模型先产生一个想法,推断自己需要找到那笔订单,而订单服务是合适的查询位置。
  • 然后,它以用户 ID 为参数调用 get_recent_order 函数,采取行动。
  • 运行时执行调用,并返回观察结果:订单编号是 9152,下单日期为 5 月 14 日。
  • 模型产生另一个想法,判断现在需要查询这笔订单的配送状态。
  • 它以订单 ID 为参数调用 get_shipping_status 函数。返回的观察结果表示,订单正在运输中,预计 5 月 29 日送达。
  • 到这一步,模型已经拿到了所需信息,于是产生最终答案,为用户概括订单状态。

见下图:

ReAct 客服示例:查询最近订单,再查询其配送状态

思考、行动、观察,再重复:ReAct 循环如何逐步完成一项任务。

从这个流程中,有两点值得考虑:

  • 第一,每一次行动都以观察结果为依据。这正是上一节所说的闭环在实际流程中的表现。
  • 第二,推理步骤确实在做事;模型依靠这些步骤,根据迄今获得的信息,决定下一项合理的行动。

ReAct 是填充循环的一种方式。在我们今天使用的智能体框架中,它是最常见的模式,普及程度远高于其他模式。

防护机制

防护机制(guardrails)应当位于循环与外部世界交互的地方。它们是架构本身的一部分,应当与循环一起设计。

作为参考,OpenAI 的 Agents SDK 记录了三类防护机制,按它们在循环中运行的位置区分:

  • 输入防护: 在第一轮、智能体的主模型看到任何内容之前运行。3这时可以拦截提示注入尝试、违反政策的请求,或超出智能体处理范围的输入。一种常见模式是用小而快的模型来做防护,让更昂贵的主模型只在输入通过检查后才运行。
  • 工具防护: 包在每次函数工具调用的外面。工具输入防护在工具执行前运行,可以阻止调用,或用一条返回给模型的消息替代调用。工具输出防护在工具执行后运行,可以改写或阻止结果,让它无法进入对话状态。工具防护之所以重要,是因为循环正是通过工具接触我们在意的系统,而这种接触需要监督。
  • 输出防护: 在循环决定终止之后、用户看到结果之前,对最终响应运行。它们是政策执行的最后一层,可以用来拦截敏感数据泄露,或智能体不应代表公司作出的断言。

输入、输出、工具输入和工具输出的防护位置

防护机制应位于循环与外部世界交互的各个接口。输入防护的执行模式见译注。

从结构上看,防护机制位于循环与外部世界相接的每个接口。

取舍

把循环的控制权交给模型,能力很强,但也会带来三个代价。每位开发者在采用这个模式前,都应该了解它们:

  • 错误累积: 把步骤串起来以后,每一步的可靠性会面临严峻考验,数学结果并不乐观。如果模型在循环中每一步的成功率都是 95%,那么十步全部正确的联合概率大约为 60%。延长到二十步,联合成功率就下降到大约 36%。4智能体的自主性带来更高成本,也带来错误累积的可能。编程智能体比开放式任务智能体表现更好的原因,是测试反馈提高了每一步的可靠性,从而缩短了必须成功的调用链的有效长度。
  • 循环周围的支撑结构: 循环外的执行框架(harness)与循环内的模型同样重要。例如,Anthropic 分享过一次内部实验:即使是运行在 Claude Agent SDK 中的前沿模型,仅凭高层次的提示词,也没能构建出达到生产质量的 Web 应用。解决办法来自支撑结构的改进,而非更好的模型。他们搭建了一个初始化智能体,在开始实现任何功能之前列出功能清单;一个编程智能体,每个会话选取一项功能;一个在不同会话之间传递的进度文件;以及供智能体恢复状态的 git 历史记录。
  • 用错工具: 智能体常常并不合适。找到最简单的解决方案,只在有必要时增加复杂性,这很重要;有时甚至意味着完全避开智能体系统。工作流提供可预测性和一致性。智能体提供灵活性,并为这种灵活性付出延迟、金钱,以及更难预测的故障形态这些代价。许多问题用调用链解决,效果会更好。

单步成功率为 99%、95% 和 90% 时,联合成功率随循环次数下降

可靠性以乘法累积:长循环需要很高的单步准确率。图示采用的简化假设见译注。

结语

智能体循环位于一条可以辨认的演进路径的末端。

我们从一次接收文本、返回文本的 LLM 调用开始。加入工具、检索和记忆,得到增强型 LLM。单次调用不够用时,就把调用串成工作流。智能体是下一级:开发者在这里把控制流本身交给模型。

循环内部,四个步骤不断重复。模型感知当前状态,对其进行推理,采取行动,再观察结果。每一轮,模型的输出都会选择四个分支之一:最终答案、工具调用、移交和继续思考。ReAct 是填充循环最常见的提示模式,推理与行动在其中交替出现。防护机制位于循环与外部世界交互的每一处。

这套设计带来三个实际代价:各步骤可靠性累积的问题、生产环境中的循环所需的执行框架,以及工作流能否以更低成本解决问题这一选择。

参考资料


本文由 AI 翻译。

Footnotes

  1. 译注:一次应用交互可以包含多次模型调用。当前 OpenAI Agents SDK 文档将一次 Runner 运行视为一个逻辑上的对话回合,其中可以发生多次 LLM 调用、工具调用和智能体移交;应区分逻辑回合与实际模型调用次数。 ↩

  2. 译注:Anthropic 的原始分类依据预定义的代码路径区分工作流。在其编排者—工作者模式中,子任务由编排者依据输入动态确定;评估者—优化者也可以循环。因此,“每种工作流的步骤数量都预先固定”概括得过强。 ↩

  3. 译注:按当前 OpenAI Agents SDK 文档,输入防护默认与智能体并行运行,可能在检查结束前就已产生模型调用或工具操作。设置 run_in_parallel=False 才会先完成检查、再启动智能体。工具防护也需要配置在相应函数工具上,不会自动保护所有工具。 ↩

  4. 译注:本段和图中的数字是简化示意,假设每一步的成功率相同,且各步骤是否成功相互独立。此时 n 步全部成功的概率为 0.95^n:十步约 59.9%,二十步约 35.8%。这些数字没有测量某个实际智能体的端到端成功率。 ↩