构建高效 Agent [译]

在过去一年中,我们与各行各业数十个团队合作,帮助他们基于大型语言模型(LLM)构建 Agent。我们发现,最成功的实现并不依赖复杂的框架或专业化的库,而是使用了简单且可组合的模式。

在本文中,我们将分享与客户合作以及我们自身构建 Agent 时所积累的经验,并为开发者提供一些构建高效 Agent 的实用建议。

什么是 Agent?

“Agent”这个词有多种定义。有些客户将其定义为完全自主的系统,可以在较长时间内独立运行,使用各种工具完成复杂任务。另一些客户则用该词来指代更具流程化的实现,它们按照预先定义的工作流执行。
在 Anthropic,我们将以上所有变化形式都归类为Agentic 系统,但在结构上会区分工作流(workflow)与Agent

  • 工作流 是指通过预定义的代码路径来编排 LLM 与工具的系统。

  • Agent 则是指由 LLM 动态地指挥自己的流程和工具使用方式的系统,始终由 LLM 来掌控完成任务的方式。

下文中,我们将详细探讨这两种 Agentic 系统。在附录 1(“Agent 在实践中的应用”)中,我们将描述两个客户在这些系统上取得显著成效的典型场景。

何时(以及何时不)使用 Agent

在使用 LLM 构建应用时,我们建议从最简单的方案着手,只有在需要时才增加复杂度。这意味着很多时候根本不需要构建 Agentic 系统。Agentic 系统通常会牺牲一定的延迟和成本来获取更好的任务性能,你需要考虑这种取舍是否值得。

如果需要增加复杂度,工作流能够在明确的任务中提供可预测性与一致性,而在需要灵活性和模型驱动决策的大规模场景下,Agent 则是更好的选择。然而,对于很多应用来说,只要能结合检索(retrieval)和上下文示例来优化一次性 LLM 调用,通常就已足够。

何时以及如何使用框架

目前有许多框架能够让 Agentic 系统的实现更加容易,包括:

  • LangChain 的 LangGraph

  • Amazon Bedrock 的 AI Agent framework

  • Rivet(一个可拖拽的 GUI LLM 工作流构建器),

  • 以及 Vellum(另一个用于构建和测试复杂工作流的 GUI 工具)。

这些框架通过简化调用 LLM、定义/解析工具以及串联多次调用等标准底层操作,使开发者可以更轻松地入门。但它们往往也会增加额外的抽象层,可能掩盖底层提示与回复,从而导致调试难度上升。此外,这些框架也可能让你陷入一种“添砖加瓦”的诱惑:在一个简单的设置就够用的情况下,去增加不必要的复杂性。

因此,我们建议开发者先直接使用 LLM API:很多模式只需几行代码就可以实现。如果使用框架,一定要了解其底层代码的逻辑。对于底层实现存在错误假设,是导致客户出错的常见原因。

可参考我们的 cookbook 中的示例实现。

构建模块、工作流与 Agent

本节将介绍我们在实际生产环境中常见的几种 Agentic 系统模式。我们将从基础构建模块 —— 增强型 LLM(augmented LLM)开始,逐步扩展到简单可组合的工作流,最后到自主的 Agent。

构建模块:增强型 LLM

Agentic 系统的基本构建模块是一个经过增强的 LLM(augmented LLM),它配备了检索(retrieval)、工具(tools)和记忆(memory)等功能。我们当前的模型能够主动使用这些能力 —— 它会自己生成搜索查询、选择合适的工具,并决定需要保留哪些信息。

增强型 LLM

建议在实现时关注以下两点:

  1. 根据你的具体用例来定制这些功能;

  2. 为 LLM 提供简洁、文档完备的接口。

实现增强功能的方式有很多,其中一种方法是使用我们近期发布的 Model Context Protocol,它允许开发者借助简单的客户端实现与越来越多的第三方工具生态系统集成。

在后续内容中,我们将默认每一次 LLM 调用都可以使用这些增强能力。

工作流:Prompt Chaining

Prompt Chaining 将任务分解为一系列步骤,每次 LLM 调用都基于上一次调用的输出继续处理。你还可以在任何中间步骤加入编程检查(下图中的“gate”),以确保流程保持在正确轨道上。

Prompt Chaining 工作流

何时使用此工作流: 当一个任务可以被简单而干净地分解为固定子任务时,这种工作流非常理想。它的主要目标是用更多的调用次数来换取更高的准确度,使每次 LLM 调用只需处理更简单的子任务,而牺牲一些时延。

示例:

  • 先生成营销文案,然后将其翻译成另一种语言。

  • 先写一个文档的大纲,对大纲进行检查,确保符合某些标准,然后再基于这个大纲写出完整文档。

工作流:Routing(路由)

Routing 会对输入进行分类,并将其定向到一个专门的后续任务。通过这种工作流,可以将关注点分离,并编写更具针对性的 Prompt。如果没有这种工作流,对某一种输入做的优化可能会影响对其他输入的性能。

Routing 工作流

何时使用此工作流: 适用于需要处理复杂任务且存在明确的类别区分,并且可以准确地进行分类(无论是通过 LLM 还是传统分类模型/算法)时。

示例:

  • 将不同类型的客户服务请求(通用问题、退款申请、技术支持等)引导到不同的下游流程、Prompt 以及工具。

  • 将常见且简单的问题路由至更小的模型(如 Claude 3.5 Haiku),将更棘手或不常见的问题路由至更强大的模型(如 Claude 3.5 Sonnet),以优化成本和速度。

工作流:并行化(Parallelization)

有些情况下,LLM 可以同时在一个任务的不同部分上工作,然后通过编程方式将它们的输出聚合起来。并行化工作流主要有两种形式:

  • 分块(Sectioning):将任务拆分成相互独立的子任务并行执行。

  • 投票(Voting):对同一个任务多次运行,获取不同的答案或思路。

并行化工作流

何时使用此工作流: 如果可以并行处理子任务以提高速度,或需要多种视角/尝试来获得更高置信度的结果,并行化会很有效。对于多方面因素较多的复杂任务,让每个 LLM 调用聚焦处理一个特定方面,往往可以带来更好的性能。

示例:

  • 分块(Sectioning)

    • 做防护措施(guardrail)时,让一个模型实例处理用户查询,另一个模型实例检查是否有不当内容或不恰当的请求。与让同一个模型在一次调用中同时处理防护和回答相比,这种方式通常效果更好。

    • 在自动化评估(evals)中,对 LLM 的表现进行评估时,让每个模型调用分别评估模型在某一个方面的表现。

  • 投票(Voting)

    • 检查一段代码是否有漏洞,使用不同的 Prompt 多次审查,一旦任何一个审查认为有问题,就会标记出来。

    • 评估某段内容是否不恰当,可以让不同的 Prompt 分别评估不同方面,或采用不同的投票门槛,以平衡假阳性和假阴性。

工作流:主控-工作器(Orchestrator-Workers)

在主控-工作器工作流中,一个中央的 LLM 会根据任务动态分解出若干子任务,分配给不同的“工作器”LLM,并最终汇总它们的结果。

主控-工作器工作流

何时使用此工作流: 当无法预测需要的子任务种类和数量(比如在编程场景中,需要修改多少个文件以及如何修改会随任务需求的不同而变化)时,该工作流特别适用。虽然它在结构上与并行化相似,但最大的区别在于它的灵活度:子任务并不是预先定义好的,而是由主控根据特定输入动态决定。

示例:

  • 编码产品,它需要在每次执行时对多个文件进行复杂修改。

  • 需要从多个信息源搜集并分析信息,从中找出可能相关的信息进行搜索任务。

工作流:评估者-优化器(Evaluator-Optimizer)

在评估者-优化器工作流中,先由一个 LLM 生成回应,另一个 LLM 对该回应进行评估与反馈,随后在循环中不断优化。

评估者-优化器工作流

何时使用此工作流: 当我们有明确的评估标准,并且在有可衡量的收益情况下可以进行多次迭代时,这种工作流尤其有效。如果人类提出的反馈能够显著提升 LLM 的回应质量,并且 LLM 自身也能提供这样的反馈,那么此流程就是理想的(类似人类写作时进行多次打磨的过程)。

示例:

  • 文学翻译,初始译文可能无法捕捉所有细微之处,但如果评估者 LLM 能提供有用的批评意见,就能进一步提升译文质量。

  • 需要多轮搜索和分析才能收集到全面信息的复杂搜索任务,让评估者决定是否继续执行更多搜索。

Agent

随着 LLM 在理解复杂输入、进行推理和规划、可靠使用工具以及从错误中恢复方面不断成熟,Agent 的应用正在生产环境中逐渐兴起。Agent 的工作通常始于一个来自人类用户的命令或互动讨论。当任务目标明确后,Agent 会自行规划并独立执行,必要时会再次与用户沟通以获取更多信息或判断。在执行任务的过程中,Agent 需要在每个步骤获取“真实”环境反馈(例如,工具调用结果或代码执行结果)来评估进展。如有需要,Agent 可以在检查点或者遇到障碍时暂停并寻求人类反馈。通常任务在完成时终止,也常见会设置停止条件(例如最大迭代次数)来保持控制。

Agent 能处理高度复杂的任务,但它们的实现通常很简单:本质上就是 LLM 利用环境反馈在循环中调用工具。因此,设计合理的工具集及其文档至关重要。在附录 2(“针对工具的 Prompt Engineering”)中,我们将深入探讨工具开发的最佳实践。

自主 Agent

何时使用 Agent: 当问题是开放式的,无法提前预测需要多少步骤,或无法写死固定路径时,可使用 Agent。LLM 可能会进行多轮操作,你需要对其决策具备一定程度的信任。由于 Agent 的自主性,特别适合在可靠环境下规模化处理任务。

Agent 的自治带来了更高的成本,也会产生错误累积的风险。我们建议在沙盒环境中进行大量测试,并配合适当的防护措施(guardrail)。

示例:
以下示例来自我们自己的实现:

一个编码 Agent 的高层流程

组合与定制这些模式

这些构建模块并非强制性的固定方案,而是开发者可以根据不同用例进行塑造与组合的常见模式。与任何其他 LLM 功能一样,成功的关键在于对性能的测量和实现迭代。再次强调:只有当确有证据表明更高复杂度会带来更好的效果时,才应该引入这份复杂度。

总结

在 LLM 领域取得成功并不在于构建最复杂的系统,而在于构建最适合自身需求的系统。先从简单的 Prompt 开始,结合全面的评估进行优化,只有在更简单的方法无法达到预期时,才使用多步骤的 Agentic 系统。

在实现 Agent 时,我们通常遵循三个核心原则:

  1. 在 Agent 的设计中保持简洁

  2. 注重透明度,明确展示 Agent 的规划步骤。

  3. 通过完善的工具文档与测试来精心打造 Agent-计算机接口(ACI)。

各种框架能够帮助你快速入门,但在进入生产环境后,如果需要更高可控性,不要犹豫去减少抽象层,使用最基础的组件。只要遵循以上这些原则,你就能构建出既强大又可靠、可维护且能赢得用户信任的 Agent。

鸣谢

本文由 Erik Schluntz 和 Barry Zhang 撰写,内容基于我们在 Anthropic 构建 Agent 的实践经验,以及我们非常感激的客户们所分享的宝贵见解。

附录 1:Agent 在实践中的应用

在与客户的合作中,我们发现有两个非常有前景的 AI Agent 应用,能很好地体现本文所讨论模式的实际价值。它们都表明,Agent 的最大价值往往表现在既需要对话又需要行动、有清晰的成功标准、允许反馈循环、并具备有意义的人类监管的任务中。

A. 客户支持

客户支持场景可以将熟悉的聊天机器人界面与通过工具集成所增强的能力结合起来。这非常适合更开放式的 Agent,因为:

  • 客服对话本身往往是对话式的,但也需要访问外部信息与执行操作;

  • 可以集成工具来获取客户数据、订单历史及知识库文章;

  • 处理诸如发放退款或更新工单等操作可以通过程序化方式完成;

  • 成功与否可以通过用户定义的解决方案来进行衡量。

已经有多家公司通过“按成功次数付费”的商业模式(仅对成功解决的请求计费)来证明这种方案的可行性,表明它们对自家 Agent 的效率颇有信心。

B. 编码 Agent

软件开发领域对 LLM 功能的需求与日俱增,能力也逐渐从简单的代码补全演进到自主问题求解。Agent 在这里效果显著,因为:

  • 可以通过自动化测试来验证代码是否正确;

  • Agent 可以基于测试结果反复迭代解决方案;

  • 问题域本身结构化、定义明确;

  • 可以通过客观指标来衡量输出质量。

在我们自己的实现中,Agent 可以只根据 Pull Request 的描述就解决 SWE-bench Verified 基准中的真实 GitHub 问题。尽管自动化测试可以验证功能,但在更广泛的系统需求上依然需要人工审查。

附录 2:针对工具的 Prompt Engineering

无论你正在构建哪种 Agentic 系统,工具在其中都可能是重要组成部分。工具(Tools) 让 Claude 能通过在 API 中声明工具的确切结构与定义来与外部服务和 API 交互。当 Claude 需要调用工具时,它会在 API 响应中包含一个工具使用块。因此,工具的定义和规范需要和整体 Prompt 一样得到足够重视的 Prompt Engineering。在此简短的附录中,我们将讨论如何为你的工具进行 Prompt Engineering。

通常实现同一个操作可能有多种方式。比如,可以通过一个 diff(差异补丁)或重写整个文件来描述对文件的修改;对结构化输出,可以将代码写在 Markdown 或 JSON 中。在软件工程中,这些差异在本质上可能只是“表面形式”,理论上可以无损地互相转换。然而,对 LLM 来说,有些格式明显比另一些更难编写。写一个 diff 时,需要事先知道要改动多少行并在块头(chunk header)中标明;而在 JSON 中书写代码(相对于 Markdown)还需要额外地转义换行符和引号。

我们对选择工具格式的建议如下:

  • 给模型留出足够的 tokens 去“思考”,避免让它一下子把自己限制在死胡同。

  • 保持格式尽量接近模型在互联网上自然学习到的文本形式。

  • 避免让模型承担过多“格式负担”,比如准确计算成百上千行的行数,或者在写代码时逐行转义。

一个经验法则是:人机界面(HCI)往往需要大量精心设计,同样也要投入同等的精力来打造优良的Agent-计算机接口(ACI)。以下是一些思考角度:

  • 站在模型的角度思考。如果只是看着工具的描述和参数就需要“思考”很久才能理解该如何调用,那么对模型来说也一样困难。一个好的工具定义通常包含示例用法、边界情况、输入格式要求,以及与其他工具的清晰差异。

  • 考虑如何通过修改参数名称或描述让调用方式更清晰。把它当成给团队中一位新手开发者写的文档,这在有大量类似工具时尤其重要。

  • 通过在 workbench 中运行大量输入示例来观察模型是如何使用工具的,找出出现偏差的地方并迭代修正。

  • 采用 Poka-yoke 思想来设计工具。通过修改工具的参数,使得犯错变得更难。

在为 SWE-bench 构建我们的 Agent 时,我们实际上在优化工具上所花的时间比优化整体 Prompt 还多。举个例子,我们发现,当 Agent 在项目根目录以外的地方时,它常在使用相对路径的工具时出错。为解决此问题,我们把这个工具改成只能使用绝对路径 —— 模型随即可以毫无障碍地正确使用它。