【译】如何构建有效的代理系统 - Anthropic
"本文是对 Anthropic 的 How to Build Effective Agents 一文的翻译,大部分内容来自于 LLM ,个人做了一些删改与润色。"
本文是对 Anthropic 的 How to Build Effective Agents 一文的翻译,大部分内容来自于 LLM ,个人做了一些删改与润色。
虽然说大家都确定下来 Agent 应该翻译为 代理 ,但是现实交流似乎还是习惯直接用 Agent 这个词,因此本文中会有不少地方不对 Agent 作翻译。
在过去的一年里,我们与数十个团队合作,构建了横跨多个行业的 LLM Agents 系统( large language model agents)。在这些代理系统中,最成功的代理系统并没有使用复杂的框架或者特定的库,相反,他们使用简单的可组合模式进行构建。
在这篇文章中,我们分享了从与客户合作及自行构建代理中学到的经验,并为开发者提供了构建高效代理的实用建议。
1. 那么,什么是 Agents ?
Agents 可以通过多种方式定义。一些客户将 Agents 定义为完全自主的系统,这些系统在较长时间内独立运行,可以自主使用各种工具完成复杂任务。其他人则用该术语来描述遵循预定义工作流程的更具规范性的实现。在 Anthropic,我们将所有这些变体归类为 Agents 系统,但在 工作流(workflows) 和 代理(agents) 之间做出了重要的架构区分:
- 工作流(workflows) 是通过预定义代码路径协调LLMs和工具的系统。
- 代理(agents) 则是指 LLM 动态指导自身流程和工具使用的系统,保持对任务完成方式的控制。
下面,我们将详细探讨这两种类型的代理系统。在 附录 1(“Agents 实践”) 中,我们描述了客户在使用这类系统时发现特别有价值的两个领域。
2. 什么时候使用 Agents ?
在构建使用LLMs的应用程序时,我们建议寻找尽可能简单的解决方案,仅在需要时增加复杂性。这一般意味着完全不构建 Agents 系统 去实现。Agents 系统通常以 延迟 和 成本 为代价以换取更好的任务性能,您应审慎考虑这种代价(tradeoffs)究竟值不值得。
当需要更高的复杂性时,工作流 为定义明确的任务提供了可预测性和一致性,而 代理 则在需要大规模灵活性和模型驱动决策时是更好的选择。然而,对于许多应用来说,通过 检索(retrieval) 和 上下文示例(in-context examples) 去优化单个LLM的调用效果就足够好了。
3. 何时且如何使用框架?
有许多框架能够帮助你方便地实现 代理系统(agentic systems),包括:
- LangChain 团队的 LangGraph (译者吐槽,LangChain 本身有多难用懂得都懂。我挺喜欢 LangGraph 的抽象的)。
- Amzazon Bedrock 的 AI 代理框架。
- Rivet,一个拖拽式的、支持图形化界面的LLM工作流构建器。
- Vellum,另一个用于构建和测试复杂工作流的 GUI 工具。
这些框架通过简化诸如调用LLMs、定义和解析工具以及将调用链式连接等标准低级任务,使得入门变得容易。然而,它们常常会创建额外的抽象层,这些层可能会掩盖底层的提示和响应,使得调试变得更加困难。它们还可能造成明明通过简单的设置便可以解决问题的情况下增加复杂性。
我们建议开发者最好直接使用 LLM 底层 API 进行调用:许多模式只需几行代码即可实现。如果你确实使用了框架,请确保你理解其底层代码。对底层机制的错误假设是实践中客户遇到的错误的常见来源。
4. 构建模块(blocks)、工作流以及代理
在本节中,我们将探讨在生产环境中常见的代理系统模式。我们将从基础构建模块——增强型LLM(the augmented LLM)——开始讨论,通过逐步增加复杂性,为您从简单的组合式的工作流开始,介绍到自主代理(autonomous agents)为止。
4.1 基础模块
1. 构建基础模块:增强型LLM
代理系统的基本构建模块是一个增强了检索、工具和记忆等功能的LLM。我们当前的模型能够主动利用这些能力——生成自己的搜索查询(即 retrieval)、选择合适的工具(即 tool call),并决定保留哪些信息(即 memory ,记忆功能)。
实现上,我们建议您重点关注这两个方面:
- 将这些能力定制化以适应您的特定用例
- 确保它们为您的LLM提供一个易于使用且文档完善的接口。
实现这些增强功能的方法有很多,其中一种方法是通过我们最近发布的 模型上下文协议(Model Context Protocol,即 MCP 协议),该协议允许开发者通过简单的客户端实现与不断增长的第三方工具生态系统集成。
译者注:MCP 协议适用于构建直接面向用户的对话式程序。我个人不推荐将它应用在复杂的代理系统中。我觉得这是 Anthropic 自己的广告。
在本文的剩余部分,我们将假设每个LLM调用都能访问这些增强功能。
4.2 工作流
1. 工作流:提示词链(Prompt chaining)
提示词链可以将任务分解为一系列的步骤,其中每个LLM调用会处理前一个调用的输出。您可以在任何中间步骤上添加程序化的(programmatic ,指通过具体的代码程序进行结果的检查)检查(见下图中的“gate”),以确保过程仍在正轨上。
译者注:提示词链指的是你预先把任务分解为不同的提示词,按顺序将对应的调用 LLM 串联起来,而不是让 LLM 自己通过思维链在一次调用中完整整个任务。
以译者自身的实践经验来讲,妄图通过一次调用完成复杂任务的,往往效果不会太好。感觉这是注意力机制造成的。
何时使用:该工作流非常适合 任务能够轻松且清晰地分解为固定子任务 的情况。其主要目标是通过使每次LLM调用成为更简单的任务,通过延迟(trade off latency)为代价换取更高的准确性。
例如:
- 生成营销文案,然后将其翻译成另一种语言。
- 撰写文档大纲,检查大纲是否符合特定标准,然后根据大纲编写文档。
2. 工作流:路由(Routing)
路由通过 LLM 对输入进行分类并将其引导至专门的后续任务。这种工作流程允许关注点分离,并构建更专业的提示。如果没有这种工作流程,针对一种输入的优化可能会损害其他输入的性能。
何时使用: 路由适用于处理复杂任务,这些任务具有明显不同的类别,更适合分开处理,并且分类可以通过LLM或更传统的分类模型/算法准确完成。
例如:
- 将不同类型的客户服务查询(一般问题、退款请求、技术支持)引导至不同的下游流程、提示和工具。
- 将简单/常见问题路由到较小的模型(如 Claude 3.5 Haiku),将困难/不常见的问题路由到更强大的模型(如 Claude 3.5 Sonnet),以 优化成本和速度 。
3. 工作流:并行化(Parallelization)
LLMs 有时可以同时处理一个任务,并通过编程方式聚合它们的输出。这种工作流程,即并行化,主要体现在两个关键变体中:
- 分段(sectioning)式任务:将任务分解为并行运行的独立子任务。
- 投票(voting)式任务:多次运行同一任务以获得多样化的输出。
何时使用:当划分的子任务可以并行化以加快速度,或者需要多个视角或尝试以获得更高置信度的结果时,并行化是有效的。对于具有多重考量的复杂任务,LLMs通常在每个考量由单独的LLM调用处理时表现更好,这样可以集中注意力于每个特定方面。
例如:
- 分段式的任务:
- 在实施防护措施时,一个模型实例处理用户查询,而另一个模型实例则筛选其中的不当内容或请求。这种方法通常比让同一个LLM调用同时处理防护措施和核心响应表现更好。
- 自动化评估(automating eval)以评估LLM性能,其中每次LLM调用都会评估模型在给定提示下性能的不同方面。
- 投票式任务:
- 审查一段代码以查找漏洞,其中多个不同的提示会审查并在发现问题时标记代码。
- 评估给定内容是否不当,通过多个提示评估不同方面或要求不同的投票阈值以平衡误报和漏报。
4. 工作流:协调者-工作者模式(Orchestrator-workers)
在协调者-工作者工作流中,一个中央LLM动态地分解任务,将其分配给工作者LLMs,并综合它们的结果。
何时使用:此工作流非常适合那些无法预测所需子任务的复杂任务(例如,在编码中,需要更改的文件数量以及每个文件中更改的性质可能取决于任务)。尽管它在拓扑结构上相似,但与并行化的关键区别在于其灵活性——子任务不是预先定义的,而是由协调器根据特定输入确定的。
例如:
- 每次对多个文件进行复杂更改的编码产品。
- 搜索涉及从多个来源收集和分析信息以寻找可能相关信息的任务。
5. 工作流:评估者-优化者模式(Evaluator-optimizer)
在评估者-优化者工作流程中,一个LLM调用生成响应,而另一个则在循环中提供评估和反馈,直到作为评估者的认为该结果可以通过才输出结果。
何时使用:当我们有明确的评估标准,并且迭代改进能带来可衡量的价值时,此工作流程尤为有效。适合的两个标志是,首先,当人类明确表达反馈时,LLM 的响应可以得到显著改善;其次,LLM 能够提供此类反馈。这类似于人类作家在创作一份精炼文档时可能经历的迭代写作过程。
例如:
- 文学翻译中存在一些细微差别,翻译者LLM可能最初无法捕捉到,但评估者LLM可以提供有用的批评。
- 需要多轮搜索和分析以收集全面信息的复杂搜索任务,评估者决定是否需要进行进一步搜索。
4.3 代理 Agents
随着LLMs在关键能力上的成熟——理解复杂输入、参与推理和规划、可靠地使用工具以及从错误中恢复——Agents 正在生产中崭露头角。Agents 的工作始于人类用户的命令或互动讨论。一旦任务明确,Agents 便可以独立规划和操作,可能返回给人类以获取更多信息或判断。在执行过程中,Agents 在每一步从环境中获取“真实情况”(如工具调用结果或代码执行)以评估其进展至关重要。代理随后可以在检查点或遇到阻碍时暂停以获取人类反馈。任务通常在完成时终止,但为了保持控制,包含停止条件(如最大迭代次数)也很常见。
代理能够处理复杂的任务,但其实现通常较为直接。它们通常只是能够在循环中基于环境反馈调用工具的 LLMs。因此,如何清晰且深思熟虑地设计工具集及其文档至关重要。我们在附录 2(“工具调用的提示词工程”,Prompt Engineering your Tools)中详细阐述了工具开发的最佳实践。
何时使用: 代理适用于开放式问题,这些问题难以或无法预测所需的步骤数量,且无法硬编码固定路径。LLM 可能会运行多个回合,因此您必须对其决策有一定程度的信任。代理的自主性使其在受信任的环境中扩展任务时成为理想选择。
例如:
- 一个编程代理(coding agent),用于解决 SWE-bench 任务,这些任务涉及根据任务描述对多个文件进行编辑
- Claude 模型的 使用计算机功能 的参考实现,该功能使得 Claude 能够调用计算机去完成任务。
5. 结合并自定义这些模式
这些构建模块并非一成不变。它们是开发者可以根据不同使用场景进行调整和组合的常见模式。与任何LLM功能一样,成功的关键在于衡量性能并迭代实现。再次强调:仅 当明显改善结果时,才应考虑增加复杂性。
6. 概要
在LLM领域的成功,并不在于构建最复杂的系统,而在于构建适合你需求的 正确 系统。从简单的提示开始,通过全面评估进行优化,仅在更简单的解决方案不足时,才添加多步骤的代理系统。
在实现代理时,我们尝试遵循三个核心原则:
- 在您的代理设计中保持简洁。
- 优先考虑透明度,明确展示代理的规划步骤。
- 通过全面的工具文档和测试,精心打造您的代理-计算机界面(ACI)。
框架可以帮助您快速入门,但在进入生产阶段时,不要犹豫减少抽象层并使用基本组件进行构建。遵循这些原则,您可以创建不仅功能强大,而且可靠、可维护并受用户信任的代理。
附录 1:Agents 实践
我们与客户的合作揭示了 AI 代理的两个特别有前景的应用,这些应用展示了上述模式的实用价值。这两个应用都说明了代理如何为需要对话和行动、具有明确成功标准、能够实现反馈循环并整合有意义的人类监督的任务增加最大价值。
A. 客户支持
客户支持 能够将熟悉的聊天机器人界面与通过工具集成增强的功能相结合。这对于更开放的代理(open-ended agents)来说是一个自然的选择,因为:
- 在同时需要访问外部信息和操作时,它支持更加自然地遵循对话流程的交互模式。
- 工具可以集成以提取客户数据、订单历史记录和知识库文章。
- 诸如发放退款或更新票据等操作可以通过编程方式处理。
- 可以通过用户定义的解决方案来明确衡量是否成功。
B. 编程Agents
软件开发领域展示了LLM功能的显著潜力,其能力已从代码补全发展到自主解决问题。代理之所以特别有效,原因在于:
- 代码解决方案可通过自动化测试进行验证。
- 代理可以使用测试结果作为反馈来迭代解决方案。
- 问题空间(problem space)定义明确且结构清晰。
- 输出质量可以客观衡量。
附录 2:
无论你正在构建哪种 Agent 系统,工具很可能都是你的 Agent 的重要组成部分。工具使 Claude 能够通过在我们的 API 中指定其确切结构和定义来与外部服务和 API 交互。当 Claude 响应时,如果它计划调用工具,它将在 API 响应中包含一个工具使用块。工具定义和规范应像你的整体提示一样受到提示工程的高度重视。在这个简短的附录中,我们描述了如何通过提示词工程构建你的工具。
通常有多种方式可以指定相同的操作。例如,您可以通过编写差异(diff)或重写整个文件来指定文件编辑。对于结构化输出,您可以在 Markdown 或 JSON 中返回代码。在软件工程中,这些差异是表面上的,可以无损地从一种形式转换为另一种形式。然而,某些格式对于LLM来说比其他格式更难编写。编写差异需要在新代码编写之前知道块头中有多少行正在更改。在 JSON 中编写代码(与 Markdown 相比)需要对换行符和引号进行额外的转义。
我们关于如何确定工具的使用格式的建议如下:
- 给模型足够的标记来“思考”,以免它把自己逼入死角。
- 保持格式接近模型在互联网文本中自然所见的内容。
- 确保没有格式化上的“开销”(formatting "overhead"),例如必须准确计算数千行代码,或对其编写的任何代码进行字符串转义。
你可以将如何构建一个比较好的人机交互界面(human-computer interfaces,HCI)时的思考方式作为构建比较好的工具的指导经验,例如:
- 设身处地为模型着想。根据描述和参数,使用这个工具是否显而易见,还是需要仔细思考?如果是后者,那么对模型来说可能也是如此。一个好的工具定义通常包括使用示例、边缘情况、输入格式要求以及与其他工具的明确界限。
- 如何更改参数名称或描述以使内容更加清晰?可以将其视为为团队中的初级开发者编写一份优秀的文档字符串。在使用许多类似工具时,这一点尤为重要。
- 测试一下模型会如何使用您的工具:在Claude 的工作台 中运行示例输入可以方便你查看模型犯了哪些错误,并进行迭代。
- 防错(poka-yoke)你的工具。改变参数,使其更难以出错。
在构建我们的SWE-bench代理时,我们实际上花了更多时间优化工具,而不是是整体提示。例如,我们发现当代理移出根目录后,模型在使用相对文件路径的工具时会出错。为了解决这个问题,我们将工具改为始终要求绝对文件路径——我们发现模型使用这种方法时毫无瑕疵。
【END】