构建有效的代理
在过去一年里,我们与数十个团队合作,构建了多个行业的大语言模型(LLM)。一致地,最成功的实现并不使用复杂的框架或专门的库,而是使用简单的、可组合的模式。
在这篇帖子中,我们分享了与客户合作以及自己构建代理的经验,并为开发者提供了构建有效代理的实用建议。
什么是代理?
“Agent” 可以从多种角度定义。一些客户将代理定义为完全自主的系统,能够独立工作,使用各种工具完成复杂任务。另一些客户则使用“代理”一词来描述遵循预定义工作流程的更规范的实现。在 Anthropic,我们将所有这些变体归为代理系统,但通过重要的架构区别,区分了工作流和代理:
工作流是通过预定义的代码路径来协调 LLMs 和工具的系统。
相反,代理系统中的 LLMs 会动态地控制其进程和工具的使用,从而控制任务的完成方式。
下面我们将详细探讨两种代理系统。在附录 1(“实践中的代理”)中,我们描述了两个领域,其中客户在使用这些系统时发现特别有价值。
何时使用代理(以及何时不使用)
使用 LLMs 构建应用时,我们建议找到最简单的方法,必要时再增加复杂性。这可能意味着不构建代理系统。代理系统通常通过牺牲延迟和成本来提高任务性能,你需要考虑这种权衡何时合理。
当复杂性需要时,工作流为明确任务提供预测性和一致性,而代理在需要灵活性和模型驱动决策时更合适。然而,对于许多应用,优化单 LLM 调用并使用检索和上下文示例通常就足够了。
框架的使用时机和方法
有许多框架可以简化代理系统的实现,包括:
LangGraph 由 LangChain 提供;
亚马逊 Bedrock 的 AI 代理框架;
Rivet,一个拖放式 GUI LLM 工作流构建器;
Vellum,另一个用于构建和测试复杂工作流的 GUI 工具。
这些框架简化了标准低级任务,如调用 LLMs、定义和解析工具,以及链接调用。然而,它们通常会增加额外的抽象层,这可能会掩盖底层的提示和响应,使调试更难。它们还可能让人在简单设置下添加复杂性。
我们建议开发者先使用 LLM API:许多模式只需几行代码就能实现。如果使用框架,确保理解底层代码。对框架背后机制的误解是常见错误来源。
查看我们的食谱,了解一些示例实现。
构建块、工作流和代理
在这一部分,我们将探讨我们在生产环境中常见的代理系统模式。我们将从基础构建块——增强的 LLM——开始,逐步增加复杂性,从简单的组合工作流到自主代理。
构建块:增强的 LLM
代理系统的基本单元是 LLM,加上检索、工具和记忆等增强功能。我们的模型可以主动使用这些功能——生成搜索查询、选择合适的工具,并决定保留哪些信息。
我们建议重点关注两个关键方面:将这些功能定制化以适应您的特定场景,并确保它们为您的 LLM 提供一个易用、文档化的接口。虽然有许多实现方法,但通过我们最近发布的 Model Context Protocol,开发者可以轻松地与不断增长的第三方工具集成,实现简单的客户端。
接下来的帖子中,我们假设每个 LLM 调用都有这些增强功能。
流程:提示链
提示链将任务分解为一系列步骤,每个 LLM 调用处理前一步的输出。你可以在任何中间步骤添加程序性检查(见下图中的“门”),确保整个过程仍在进行中。
使用此工作流的情况:此工作流适用于任务可以轻松划分为固定子任务的情况。主要目标是通过将每个 LLM 调用视为简单任务来提高准确率,减少延迟。
提示链在以下场景中非常有用:
生成营销文案,然后翻译成另一种语言。
编写文档大纲,检查大纲是否符合标准,然后根据大纲撰写文档。
流程:路由
路由功能会分类输入并将其定向到专门的任务。这种工作流程有助于分离功能,构建更专业的提示。如果没有这个工作流程,优化一种输入可能会对其他输入产生影响。
使用此工作流的情况:路由适用于复杂任务,其中需要将任务分为不同的类别,以更好地处理这些类别,而分类可以由 LLM 或更传统的分类模型/算法准确完成。
路由在以下场景中非常有用:
将不同类型的客户服务查询(一般问题、退款请求、技术支持)分配到不同的下游流程、工具和工具中。
将常见问题转给 Claude 3.5 Haiku,将复杂或不寻常的问题转给 Claude 3.5 Sonnet,以优化成本和速度。
流程:并行处理
LLMs 可以同时处理任务,并将输出程序化汇总。这种工作流,即并行处理,有以下两种关键形式:
分块:将任务拆分成独立的子任务,这些子任务并行运行。
投票:多次运行同一任务以获得多样化的输出。
使用此工作流的时机:当任务可以并行处理以提高速度时,或者需要多个视角或尝试以获得更高置信度的结果时。对于需要考虑多个因素的复杂任务,LLMs 通常在每个考虑因素由单独的 LLM 调用时表现更好,这样可以专注于每个特定方面。
并行化在哪些场景下有用:
- 分段:
在模型中实现防护机制,一个模型处理用户查询,另一个模型筛查内容或请求。这通常比让同一个 LLM 调用同时处理防护机制和核心响应更有效。
自动化评估 LLM 性能,每个 LLM 调用评估模型在给定提示下的不同方面。
- Voting:
审查代码以发现漏洞,多个提示会检查代码,如果发现问题就会标记。
评估内容是否不适当,多个提示分别从不同方面进行评估,或需要不同的投票门槛来平衡误报和误报。
流程:协调器-工作单元
在调度器-工作流程中,LLM 会动态拆分任务,分配给 LLMs,并合成结果。
使用此工作流的情况:此工作流适用于复杂任务,无法预测具体子任务(例如编程中,需要修改的文件数量和每文件的修改方式可能因任务而异)。虽然其结构类似,但与并行化的主要区别在于其灵活性——子任务未预先定义,而是由协调器根据具体输入确定。
例如,当 orchestrator-workers 有用时:
每次更改多个文件时,生成复杂代码的产品。
搜索任务涉及从多个来源收集和分析信息,以获取可能的相关信息。
流程:评估器-优化器
在评估优化流程中,一个 LLM 调用生成响应,另一个 LLM 通过循环提供评估和反馈。
使用此工作流的时机:当有明确的评估标准时,以及当迭代改进能带来可测量的价值时,此工作流特别有效。两个好的匹配迹象是:首先,当人类反馈时,LLM 的响应可以明显改进;其次,LLM 可以提供这样的反馈。这就像人类作家在生产 polished 文档时会经历的迭代写作过程。
评估器-优化器在以下场景中非常有用:
文学翻译中,LLM 可能无法立即捕捉到的细微之处,但 LLM 可以提供有用的批评。
需要多轮搜索和分析来收集全面信息的复杂任务,评估者会决定是否需要进一步搜索。
Agents
代理正在生产环境中崭露头角,LLMs 在关键能力方面成熟——能够理解复杂输入,进行推理和规划,使用工具可靠,以及从错误中恢复。代理从人类用户处接收命令或进行交互开始工作。一旦任务明确,代理将独立规划和操作,可能需要返回人类获取进一步的信息或判断。 在执行过程中,代理需要从环境中获取“真实信息”(如工具调用结果或代码执行结果)来评估其进度。代理可以在检查点或遇到障碍时暂停等待人类反馈。任务通常在完成时终止,但也常包含停止条件(如最大迭代次数)以保持控制。
代理可以处理复杂任务,但实现通常简单。它们通常只是 LLMs 通过环境反馈循环使用工具。因此,设计工具及其文档时要清晰、周到。我们在附录 2(“Prompt Engineering your Tools”)中详细讨论了最佳实践。
何时使用代理:代理适用于开放性问题,无法预测所需步骤数,以及无法硬编码固定路径的情况。LLM 可能需要运行多轮,你需要对代理的决策有一定程度的信任。代理的自主性使其在受信任的环境中适合扩展任务。
自主性意味着更高的成本和潜在的错误累积。我们建议在沙箱环境中进行广泛的测试,并设置适当的限制。
代理在以下场景中非常有用:
以下示例来自我们的实现:
一个编码代理,用于解决 SWE-bench 任务,这些任务需要根据任务描述修改多个文件
我们的“计算机使用”参考实现,其中 Claude 使用计算机完成任务。
结合并定制这些模式
这些构建块不是强制性的。它们是开发者可以调整和组合的常见模式,适用于不同的用例。成功的关键,就像任何 LLM 功能一样,是测量性能并进行迭代。重复一下:只有当它显著提升效果时,才考虑增加复杂性。
Summary
在 LLM 项目中取得成功,不是在于构建最复杂的系统,而是在于构建适合你需求的系统。从简单的提示开始,通过全面评估优化它们,只有当简单的方法无法满足需求时,才添加多步骤代理系统。
在实现代理时,我们遵循三个核心原则:
保持代理设计的简洁。
优先确保透明度,明确展示代理的规划步骤。
通过详细的工具文档和测试,精心设计你的代理-计算机接口(ACI)。
框架可以帮你快速上手,但不要犹豫,逐步减少抽象层次,使用基础组件。遵循这些原则,你可以创建既强大又可靠、维护性高、用户信任的代理。
感谢
作者:Erik Schluntz 和 Barry Zhang。本工作借鉴了我们在 Anthropic 为代理构建过程中积累的经验,以及客户提供的宝贵见解,我们对此表示衷心感谢。
附录 1:实践中的代理
我们与客户合作发现,两个特别有前景的应用场景展示了上述模式的实际价值。这两个应用都说明,AI 代理在需要对话和行动的任务中,能带来最大的价值,有明确的成功标准,能实现反馈循环,并集成有意义的人类监督。
A. 客户支持
客户支持通过工具集成,将传统的聊天机器人界面与增强的功能相结合。这自然适合开放式的代理,因为:
支持自然地遵循对话流,同时需要访问外部信息和操作
工具可以集成以获取客户数据、订单历史和知识库文章
可以使用程序来处理退款或更新票等操作
成功可以通过用户定义的分辨率来明确衡量。
多家公司通过使用基础定价模型,只对成功解决的问题收费,展示了对代理效果的信心。
B. 编程代理
软件开发领域展现出 LLM 功能的惊人潜力,从代码补全到自主解决问题。代理特别有效,因为:
代码解决方案可以通过自动化测试来验证
代理可以通过测试结果来迭代解决方案
问题空间明确且结构化;
输出质量可以客观地衡量。
在我们的实现中,代理现在可以根据拉取请求描述解决真实的 GitHub 问题。然而,自动化测试有助于验证功能,但人工审核仍至关重要,以确保解决方案符合系统要求。
附录 2:工具的提示设计
无论你构建的代理系统使用哪种工具,工具在你的代理中都非常重要。通过在我们的 API 中指定工具的结构和定义,工具可以与外部服务和 API 交互。当 Claude 响应时,如果它计划调用工具,API 响应中会包含一个工具使用块。工具定义和规范应与您的整体提示一样,得到充分的提示工程关注。 在本附录中,我们介绍了如何进行工具的提示工程。
通常,指定同一操作的方式有多种。例如,你可以通过编写差异或重写整个文件来指定文件编辑。对于结构化输出,你可以将代码返回到 Markdown 或 JSON 中。在软件工程中,这些差异通常是可忽略的,可以互相转换。然而,有些格式对 LLM 来说要写得更难。 编写差异需要知道在新代码之前,chunk 头有多少行需要修改。在 JSON 内编写代码(与 markdown 相比)需要额外的转义处理新行和引号。
我们建议选择工具格式时,可以参考以下几点:
给模型足够的令牌,让它有足够的思考时间,避免写到死路。
保持格式与互联网上自然出现的文本相似。
确保没有格式化“额外开销”,比如需要准确统计代码行数,或者在代码中进行字符串转义。
一个通用的建议是考虑人机交互(HCI)需要投入多少努力,同时也要投入同等的努力来创建好的代理计算机交互(ACI)。以下是一些实现方法的思考:
把自己想象成模型。根据描述和参数,使用这个工具是否显而易见,还是需要仔细考虑?如果是,那么模型也可能是这样。一个好的工具定义通常包括示例使用、边缘情况、输入格式要求和明确的与其他工具的界限。
如何更改参数名称或描述,让它们更显而易见?这就像为团队中的一位初级开发者写一个优秀的文档注释。当使用多种类似工具时,这一点尤为重要。
测试模型如何使用你的工具:在我们的工作台中运行多个示例输入,看看模型犯了哪些错误,并进行迭代。
把工具归为一类,调整参数,使出错更难。
在构建 SWE-bench 代理时,我们发现优化工具比优化整个提示花费更多时间。例如,我们发现模型在代理从根目录移动后,使用相对文件路径时会出错。为了解决这个问题,我们改用总是要求绝对文件路径的方法,并发现模型使用这种方法时表现完美无缺。