《Building effective agents》-建立高效的 Agent

内容纲要

前言

2024 年 12 月 20 日
文章来源:Building effective agents

过去的一年里,我们与来自各行各业、构建大型语言模型(LLM)Agent 的数十个团队进行了合作。我们发现,那些最成功的实现通常并没有使用复杂的框架或专门的库,而是基于简单、可组合的模式来构建。

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


什么是 Agent?

对于“Agent”这一术语,有多种定义。部分客户将其定义为完全自主、在长时间里独立运行并利用各种工具完成复杂任务的系统。另一些则将其描述为遵循预定义工作流的更具限制性的实现。在 Anthropic,我们将这些不同形式统称为Agent 化系统(agentic systems),但会在架构上区分工作流(workflows)Agent

  • 工作流(Workflows):LLM 和工具通过预先定义的代码路径进行编排。
  • Agent:LLM 动态地管理自己的流程和工具使用方式,并决定如何完成任务。

在下文中,我们会详细介绍这两种 Agent 化系统。在附录 1(“Agents in Practice”)中,我们还将描述两个领域,这些领域的客户使用此类系统并从中获得了显著价值。


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

在使用 LLM 构建应用时,我们建议尽量找到最简单的解决方案,只有在必要时才增加复杂度。这也意味着,有时你可能根本无需构建 Agent 化系统。Agent 化系统通常会牺牲一定的延迟和成本,来换取更好的任务性能,你需要评估这种取舍是否符合需求。

在更复杂的场景下,工作流能为定义明确的任务提供可预测、稳定的性能,而当需要大规模灵活性以及依赖模型决策时,Agent 则是更好选择。不过,对于很多应用来说,只需要结合检索(retrieval)或上下文示例来优化单次 LLM 调用往往就已足够。


何时以及如何使用框架

目前有不少框架可简化 Agent 化系统的实现,包括:

  • 来自 LangChain 的 LangGraph
  • Amazon Bedrock 的 AI Agent 框架
  • Rivet,一个拖拽式的图形界面 LLM 工作流构建工具;
  • Vellum,另一个用于构建和测试复杂工作流的图形化工具。

这些框架能够简化标准底层任务(如调用 LLM、定义和解析工具,以及将调用串联起来),使你可以更快地上手。但它们往往也会带来额外的抽象层,可能隐藏底层的提示词与响应,从而增加调试难度。同时,这些框架也会让人倾向于在不必要的情况下添加复杂度。

我们建议开发者先尝试直接使用 LLM API:许多模式只需几行代码就能实现。如果你使用框架,一定要理解其底层代码。对底层机制的错误假设是客户出错的常见原因之一。

可参见我们的 cookbook 以获得一些示例实现。


构建模块、工作流与 Agent

在本节里,我们将探讨在生产环境中常见的 Agent 化系统模式。从基础的“增强版 LLM”开始,逐步拓展到更复杂的组合式工作流,再到自主 Agent。


基础构建模块:增强版 LLM

Agent 化系统的核心构建模块是增强版 LLM(augmented LLM),即在 LLM 基础上添加检索、工具和记忆等功能。现有的模型通常能主动使用这些增强功能,例如生成查询语句、选择合适的工具,以及确定要保留哪些信息。

img
增强版 LLM

在实现时,我们建议重点关注以下两点:

  1. 根据具体使用场景定制这些功能;
  2. 为 LLM 提供便捷、文档完善的接口。

实现这些增强功能的方式有很多,其中一种方法是使用我们最新发布的 Model Context Protocol,该协议允许开发者通过一个简单的客户端实现来接入第三方工具的不断扩展生态系统。

在本文后续内容中,我们默认每次 LLM 调用都能使用这些增强功能。


工作流模式:Prompt chaining

Prompt chaining 将一个任务分解为若干连续步骤,每一次 LLM 调用都处理上一个步骤的输出。你可以在任意中间步骤添加编程检查(见下图中“gate”)以确保流程未偏离预期。

img
Prompt chaining 工作流

适用场景:当任务可以被轻松且明确地分解为固定子任务时,该工作流非常适合。它的主要目的是牺牲一定的延迟来获得更高的准确度,每次 LLM 调用仅需处理简化后的子任务。

示例

  • 先生成一段营销文案,再将其翻译成不同语言。
  • 先撰写文档大纲,检查该大纲是否满足特定标准,然后再基于该大纲写出完整文档。

工作流模式:Routing

Routing(路由)对输入进行分类,并将其指向专门的后续任务。该模式可以实现关注点分离,并通过在不同分类下使用更具针对性的 Prompt 来提高性能。如果不进行路由,针对某一类型输入的优化反而可能会影响对其他类型输入的处理。

img
Routing 工作流

适用场景:当任务很复杂且可以分成不同类别,而每一类别都可用更具针对性的方式来处理,且分类本身(无论是由 LLM 还是传统模型/算法来完成)也足够可靠时,Routing 模式尤其有效。

示例

  • 将客户服务请求根据类型(一般问题、退款请求、技术支持等)路由到不同的后续处理流程、Prompt 和工具。
  • 根据问题难度对输入进行区分:将简单或常见问题路由给较小的模型(如 Claude 3.5 Haiku),而将棘手或不常见的问题路由给更强大的模型(如 Claude 3.5 Sonnet)以优化成本和速度。

工作流模式:Parallelization

在某些场景中,LLM 可以同时处理不同方面的任务,最后再通过编程方式合并结果,这种工作流称为 Parallelization(并行化)。其主要有两种典型形式:

  • Sectioning:将一个任务拆分为多个彼此独立的子任务并行执行。
  • Voting:对同一个任务多次调用模型以获取不同的视角或输出。

img
Parallelization 工作流

适用场景:当子任务可以并行处理以加快速度,或当需要多个视角/尝试才能获取更高置信度的结果时,并行化是一个好方法。对于多个面向复杂维度的任务,将每个维度拆分成独立的 LLM 调用通常会得到更好的结果,让每次调用都能专注于单独的方面。

示例

  • Sectioning:

    • 实现防护措施(guardrails)时,一个模型实例主要处理用户请求,另一个则专门进行不当内容或非法请求的筛查。与单次调用同时处理内容生产和安全审查相比,这种分离通常性能更好。
    • 在自动化评估(evals)中,用多个 LLM 调用分别评估模型表现的不同方面。
  • Voting:

    • 复查一段代码是否存在漏洞时,用不同 Prompt 多次调用模型检测,只要有调用认为存在问题就会触发相应标记。
    • 评估内容是否违规时,让多个 Prompt 分别进行不同角度的评估,或要求满足特定投票阈值,以在误报和漏报之间取得平衡。

工作流模式:Orchestrator-workers

在 Orchestrator-workers 工作流中,由一个中心 LLM 动态拆解任务,并将其分配给若干 Worker LLM,同时再将这些 Worker 的结果综合起来。

img
Orchestrator-workers 工作流

适用场景:非常复杂且难以预先知道需要哪些子任务时,这种工作流会更合适(例如代码场景,可能需要编辑哪些文件、编辑多少文件,都取决于具体任务)。虽然在结构上与并行化类似,但与并行化的区别在于它的灵活性——子任务并非预定义,而是由 orchestrator 根据输入动态决定。

示例

  • 实现一个代码功能时,需要根据具体需求修改多个文件,而每个文件怎么改都高度依赖需求本身。
  • 搜索任务需要从不同来源收集和分析信息,orchestrator 可以按情况分配搜索和分析子任务。

工作流模式:Evaluator-optimizer

Evaluator-optimizer 工作流中,一个 LLM 负责生成初步响应,另一个则对其进行评价和反馈,两者通过循环多次迭代来优化结果。

img
Evaluator-optimizer 工作流

适用场景:当有明确的评估标准,且反复迭代(在有人类能提出改进意见时响应会更好)确实能改善结果时,该模式非常有效。可以类比人类写作:每次写完一稿,再根据审阅者反馈不断完善。

示例

  • 文学翻译:有时初始译文并不能完全涵盖原文细微的情感或文化差异,而另一个 LLM 作为评审者可能提出改进建议。
  • 复杂的搜索任务:需要多轮检索和分析才能全面收集信息,评审者会判断是否还有必要进行更多搜索。

Agent

随着 LLM 在理解复杂输入、推理与规划、可靠使用工具以及自我纠错等能力方面的不断进步,Agent 正在逐渐进入实际生产环境。Agent 在开始任务前通常会先得到来自人类用户的指令或经过交互式讨论,一旦确定目标就会独立规划并执行,可能在执行过程中再次向用户寻求更多信息或判断。在执行时,Agent 至关重要的一点是:每一步都能从环境中获取“真实信息”(例如工具调用结果或代码运行结果)来评估进度。Agent 可以在检查点或遇到阻塞时暂停,以征求人类反馈。任务完成后通常会终止,也可能设置最大迭代次数等停止条件来保证安全可控。

Agent 能处理复杂任务,但往往实现起来却相对直接——通常就是 LLM 在循环过程中根据环境反馈来调用工具。因此,清晰且合理地设计工具及其文档至关重要。我们在附录 2(“Prompt Engineering your Tools”)中会进一步讨论工具设计的最佳实践。

img
自主 Agent

何时使用 Agent:当无法预测完成任务所需的步骤数,或无法硬编码固定流程时,Agent 会更具优势。LLM 可能需要进行多轮操作,需对它的决策有一定信任度。对于可信环境中需要规模化处理的开放式任务,Agent 的自主性能够带来帮助。

但 Agent 由于自主迭代会带来更高的成本和潜在错误的叠加风险。我们建议在隔离环境中进行大量测试,并结合适当的安全防护措施。

示例

以下是我们自身实践中的一些示例:

img
Coding Agent 的高层流程


混合与定制这些模式

以上这些模块和工作流并非一成不变的规范,而是一些在实际应用中经常出现的模式。开发者可以结合自己需求,对其进行拆分、混合和调整。与任何 LLM 功能一样,成功的关键仍然是测量性能并持续迭代。值得再强调一遍:只有当复杂度能够切实提升成果时,才应该添加复杂度。


总结

想要在 LLM 领域取得成功,并不是要构建最复杂的系统,而是要构建最符合需求的系统。请从简单的 Prompt 入手,通过全面的评估进行优化,仅当简单方案无法满足需求时,再考虑添加多步骤的 Agent 化系统。

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

  1. 让设计保持简单
  2. 保持透明度,显式展示 Agent 的规划步骤;
  3. 通过完善工具的文档与测试,仔细打造“Agent-计算机接口”(ACI)。

框架确实有助于快速入门,但随着系统进入生产环境,也不要犹豫去降低抽象层、回到更基本的实现方式。通过遵循这些原则,你就能打造既强大又可靠、可维护并能获得用户信任的 Agent。


致谢

本文由 Erik Schluntz 和 Barry Zhang 撰写。内容基于我们在 Anthropic 构建 Agent 的实践以及各位客户分享的宝贵经验,在此我们深表感谢。


附录 1:Agent 的实际应用

通过与客户的合作,我们发现有两个应用场景十分适合 AI Agent,能很好地体现前文提到的模式在实践中的价值。它们都说明了 Agent 在需要对话 + 行动、有明确成功标准、具有反馈回路并能实现有意义的人类监督的场景下能带来极大价值。


A. 客户支持

客户支持兼具熟悉的聊天机器人界面与通过工具集成带来的额外能力。它天然契合更开放式的 Agent:

  • 客服对话通常需要依赖外部信息和可执行操作;
  • 工具可用于拉取用户数据、订单历史以及知识库文章;
  • 类似退款、更新工单等操作可通过编程完成;
  • 成功与否可以通过用户是否得到满意的解决方案来衡量。

已经有多家公司基于这种模式提供基于成功量收取费用的服务,展现了对其 Agent 有效性的充分信心。


B. Coding Agent

在软件开发领域,从代码补全到自动化解决问题,LLM 的能力正不断演进。在这里,Agent 往往十分有效,因为:

  • 代码能用自动化测试来验证;
  • Agent 能根据测试结果不断迭代并改进;
  • 问题空间明确定义、结构化程度较高;
  • 可以用客观指标来衡量输出质量。

在我们的实现中,Agent 已能基于拉取请求描述来解决 SWE-bench Verified 基准测试中的真实 GitHub 问题。当然,自动化测试能帮助验证功能正确性,但要让整体解决方案与更大系统契合,人工审查仍是必要的。


附录 2:为你的工具进行 Prompt 工程

无论你在构建哪种 Agent 化系统,工具通常都会是关键的一环。工具让 Claude 能够通过在我们的 API 中指定工具的精确结构与定义,来调用外部服务和 API。当 Claude 决定调用某个工具时,它会在 API 响应中包含一个 tool use block。工具定义和规范本身也需要进行 Prompt 工程上的考量,与整个 Prompt 同等重要。在这个简短的附录中,我们会阐述如何为工具进行 Prompt 工程。

实现同一个动作通常有多种方法。例如,要对文件进行编辑,你可以让模型编写 diff,也可以让它重写整份文件;要输出结构化结果,可把代码写到 Markdown 代码块或用 JSON 输出。虽然在软件工程角度来看,这些输出形式可以互相转换,但对 LLM 来说,某些格式要更难正确生成。编写 diff 时,它需要提前知道改动行数以便写在 diff 头部;在 JSON 中写代码需要对换行和引号进行转义。诸如此类的格式复杂度都会影响模型的易用性和准确度。

因此,我们的建议是:

  • 给模型留足够的 token,让它可以“思考”并避免一不小心把输出写崩了;
  • 选用接近模型在互联网上见过的自然格式;
  • 避免太多格式上的额外负担,比如要精确维护数千行的行号,或大段转义。

一个经验法则是参考人机交互(HCI)所投入的努力,对Agent-计算机接口(ACI)也要投入同等精力。可以考虑以下做法:

  • 站在模型视角思考:工具的描述和参数是否易于理解?如果对人类开发者来说也不直观,那么对模型也不直观。写好工具定义就像为团队中新手程序员写一个优秀的注释和文档一样,要包含示例用法、边界条件、输入格式要求及与其他工具的清晰区分。
  • 通过更改参数名称或描述,让用法更清晰。想象你在为一位初级开发者编写文档,这在同时使用多个相似工具时尤其关键。
  • 测试模型对工具的使用方法:在我们的 workbench 中输入大量示例,看看模型会犯哪些错误,再对应地迭代改进。
  • 采用 Poka-yoke 思路修改工具,让出错变得更难。例如,在我们为 SWE-bench 构建 Agent 时,曾遇到模型在移动出根目录后无法使用相对路径的问题。为此,我们修改了工具,强制要求使用绝对路径。结果我们发现模型能非常顺畅地适应这种用法。

在构建 SWE-bench Agent 的过程中,我们实际上花了更多时间来优化工具,而非优化整体 Prompt。例如,之前提到的相对路径问题,改为让工具强制使用绝对路径后,模型就能完美处理了。

Leave a Comment

您的电子邮箱地址不会被公开。 必填项已用*标注

close
arrow_upward