
构建高效的智能体(Building effective agents)
原文链接:Building effective agents by Anthropic https://www.anthropic.com/research/building-effective-agents
最近我在 Anthropic 官网(Claude 是由该公司推出的)看到了篇文章《Building effective agents》感觉还不错,于是我简单翻译一下:
在过去一年里,我们与多个团队合作,帮助他们在不同行业中构建大语言模型(LLM)智能体。我们发现,最成功的案例往往并没有依赖复杂的框架或专用库,而是采用了简单且易于组合的设计模式。
在这篇文章中,我们将分享与客户合作及自主构建智能体过程中的经验教训,并为开发者提供一些实用的建议,帮助他们构建高效的智能体。
什么是智能体?
“智能体”这一概念有多种定义。一些客户将智能体定义为完全自主的系统,这些系统可以在长时间内独立运行,通过使用各种工具来完成复杂任务。另一些客户则用该术语描述那些遵循预定工作流的、更具规范性的实现方式。在 Anthropic,我们将所有这些不同的形式统称为智能体系统(Agentic Systems),但在架构上,我们在 工作流(Workflow) 和 智能体(Agent) 之间做了一个重要的区分:
- 工作流是指通过预定义的代码路径(predefined code paths)来协调 LLM 和工具的系统。
- 智能体则是指 LLM 能够动态地指引自身的过程和工具使用,保持对任务完成方式的控制。
接下来,我们将详细探讨这两种类型的智能体系统。在附录 1(《智能体的实际应用》)中,我们介绍了客户在两个领域中使用这些系统所获得的特别价值。
何时(以及何时不)使用智能体
在构建基于 LLM 的应用时,我们建议尽可能选择最简单的解决方案,只有在必要时才增加复杂度。这可能意味着根本不需要构建智能体系统。智能体系统通常会牺牲延迟和成本,以换取更好的任务执行效果,因此你需要考虑这种权衡是否合理。
当确实需要增加复杂度时,工作流对于明确的任务提供了可预测性(predictability)和一致性(consistency),而在需要灵活性和基于模型的决策制定时,智能体则是更好的选择。然而,对于许多应用而言,通过检索和上下文示例来优化单次 LLM 调用通常已经足够。
何时以及如何使用框架
有许多框架可以简化智能体系统的实现,包括:
- LangChain 的 LangGraph;
- Amazon Bedrock 的 AI 智能体框架;
- Rivet,一个拖拽式图形界面 LLM 工作流构建工具;
- Vellum,另一个用于构建和测试复杂工作流的图形界面工具。
这些框架通过简化一些标准的低级任务(例如调用 LLM、定义和解析工具、链式调用等)来帮助快速入门。然而,它们往往会增加额外的抽象层,这可能使底层的提示词和响应变得不透明,从而难以调试。同时,它们还可能让人产生一种冲动,在实际只需简单设置的情况下,过度增加复杂性。
我们建议开发者首先直接使用 LLM API:许多模式只需几行代码即可实现。如果你决定使用框架,务必理解其底层代码。对于底层实现的错误假设是客户常见的错误来源。
请参阅我们的指南,了解一些示例实现。
基础构件、工作流与智能体(Blocks、Workflows、Agents)
在这一部分,我们将探讨我们在实际应用中常见的智能体系统模式。我们将从我们的基础构件——增强型 LLM 开始,逐步增加复杂度,从简单的组合性工作流到自主智能体(Autonomous agents)。
基础构件:增强型 LLM(The augmented LLM)
智能体系统的基础构件是经过增强的 LLM,通常包括检索、工具和记忆等功能。我们当前的模型能够主动使用这些能力——生成自己的搜索查询、选择合适的工具,并决定保留哪些信息。
我们建议在实现过程中重点关注两个关键方面:一是将这些功能根据你的具体应用场景进行定制,二是确保它们为你的 LLM 提供一个简洁且文档完善的接口。虽然实现这些增强功能的方式有很多种,但其中一种方法是通过我们最近发布的模型上下文协议(Model Context Protocol),该协议允许开发者通过简单的客户端实现,轻松集成日益增长的第三方工具生态系统。
在接下来的内容中,我们将假设每次 LLM 调用都可以访问这些增强功能。
工作流:提示词链式调用(Prompt chaining)
提示词链式调用将任务分解为一系列步骤,每次 LLM 调用都会处理前一步的输出。你可以在任何中间步骤添加程序化检查(参见下图中的“gate”),以确保整个过程按预期进行。
何时使用此工作流: 这个工作流非常适合那些可以轻松且清晰地分解为固定子任务的情况。其主要目标是通过将每次 LLM 调用简化为更容易处理的任务,从而牺牲延迟以换取更高的准确性。
提示词链式调用有用的示例:
- 生成营销文案,然后将其翻译成不同的语言。
- 编写文档大纲,检查大纲是否符合特定标准,然后根据大纲编写文档。
工作流:路由(Routing)
路由将输入进行分类,并将其指引到特定的后续任务。这个工作流有助于关注点分离,并构建更具针对性的提示词。如果没有这个工作流,优化某种类型的输入可能会影响其他输入的性能。
何时使用此工作流: 路由适用于那些任务复杂、可以明确区分为不同类别并且更适合单独处理的场景,同时分类可以通过 LLM 或更传统的分类模型/算法来准确完成。
路由有用的示例:
- 将不同类型的客户服务查询(如常见问题、退款请求、技术支持)分配到不同的后续流程、提示词和工具中。
- 将简单/常见问题路由到较小的模型(如 Claude 3.5 Haiku),将困难/不常见问题路由到更强大的模型(如 Claude 3.5 Sonnet),以优化成本和速度。
工作流:并行化(Parallelization)
LLM 有时可以同时处理一个任务,并通过程序化方式聚合它们的输出。并行化工作流主要有两种形式:
- 分段(Sectioning):将一个任务拆分为独立的子任务并行执行。
- 投票(Voting):多次执行相同的任务,以获得多样化的输出。
何时使用此工作流: 并行化在以下情况中非常有效:当任务被拆分成多个子任务可以并行处理以提高速度,或者当需要多个视角或尝试来获得更高置信度的结果时。对于需要考虑多个因素的复杂任务,当每个因素由独立的 LLM 调用处理时,通常能获得更好的表现,因为这样可以集中精力处理每个具体的方面。
并行化有用的示例:
- 分段(Sectioning):
- 实现防护措施,其中一个模型实例处理用户查询,另一个模型实例则筛查不当内容或请求。与让同一个 LLM 调用同时处理防护措施和核心响应相比,这种方式通常效果更好。
- 自动化评估 LLM 性能,其中每次 LLM 调用评估模型在给定提示词下的不同性能方面。
- 投票(Voting):
- 审查一段代码是否存在漏洞,多个不同的提示词同时审查并标记代码中发现的问题。
- 评估一段内容是否不当,通过多个提示词评估内容的不同方面,或要求不同的投票阈值,以平衡假阳性和假阴性。
工作流:协调器-工作者(Orchestrator-workers)
在协调器-工作者工作流中,中央 LLM 动态地将任务拆解,委派给工作者 LLM,并合成它们的结果。
何时使用此工作流: 该工作流非常适合那些无法预测所需子任务的复杂任务(例如在编码中,需要更改的文件数量以及每个文件中的更改类型通常取决于具体任务)。虽然在结构上与并行化相似,但与并行化的主要区别在于它的灵活性——子任务不是预定义的,而是由协调器根据具体输入动态确定的。
协调器-工作者模式有用的示例:
- 需要每次对多个文件进行复杂更改的编码任务。
- 涉及从多个来源收集并分析信息以寻找可能相关信息的搜索任务。
工作流:评估器-优化器(Evaluator-optimizer)
在评估器-优化器工作流中,一个 LLM 调用生成响应,而另一个 LLM 在循环中提供评估和反馈。
何时使用此工作流: 当我们有明确的评估标准,并且迭代优化能够带来可衡量的价值时,这个工作流特别有效。适用的两个标志是:首先,当一个人给出反馈时,LLM 的响应可以显著改进;其次,LLM 能够提供这样的反馈。这类似于人类写作过程中反复修改的过程,直到完成一篇精炼的文档。
评估器-优化器模式有用的示例:
- 文学翻译,翻译 LLM 可能最初无法捕捉到某些细微差别,但评估 LLM 可以提供有价值的批评意见。
- 复杂的搜索任务,要求多轮搜索和分析以收集全面的信息,在这种情况下,评估者决定是否需要进一步搜索。
智能体(Agents)
随着 LLM 在关键能力(如理解复杂输入、进行推理与规划、可靠使用工具以及从错误中恢复)方面的成熟,智能体在生产环境中逐渐出现。智能体通常从与人类用户的命令或互动讨论开始工作。一旦任务明确,智能体会独立进行规划和操作,可能会在需要进一步信息或判断时返回人类。在执行过程中,智能体需要在每一步从环境中获取“真实数据”(如工具调用结果或代码执行结果)以评估其进展。因此,智能体在关键节点或遇到障碍时需要暂停并获取人类反馈。任务通常在完成时终止,但也常包括停止条件(如最大迭代次数)以便保持控制。
智能体可以处理复杂的任务,但其实现通常相对简单。它们通常只是基于环境反馈循环使用工具的 LLM。因此,设计工具集及其文档时,必须做到清晰和周到。我们在附录 2(《工具的提示词工程》)中详细讨论了工具开发的最佳实践。
何时使用智能体: 智能体适用于那些开放性问题,在这些问题中,预测所需的步骤数量是困难或不可能的,也无法预先硬编码一个固定的路径。LLM 可能会操作多个回合,你必须在其决策过程中建立一定的信任。智能体的自主性使其非常适合在可信的环境中扩展任务。
智能体的自主性也意味着更高的成本,并且可能导致错误的积累。因此,我们建议在沙盒环境中进行广泛的测试,并设置适当的防护措施。
智能体有用的示例:
以下是我们自己实施的示例:
- 一个编码智能体,用于解决 SWE-bench 任务,该任务涉及根据任务描述对多个文件进行编辑;
- 我们的“计算机使用”参考实现,其中 Claude 使用计算机完成任务。
组合和定制这些模式
这些构建模块并非规定性的,而是开发者可以根据不同应用场景进行塑造和组合的常见模式。成功的关键,与任何 LLM 特性一样,是衡量性能并不断迭代实现。再强调一次:只有当增加复杂度能够明显改善结果时,才应考虑增加复杂性。
总结
在 LLM 领域取得成功并不是要构建最复杂的系统,而是要根据你的需求构建合适的系统。从简单的提示词开始,通过全面的评估优化它们,只有当简单的解决方案无法满足需求时,才添加多步骤的智能体系统。
在实现智能体时,我们尽量遵循三个核心原则:
- 保持智能体设计的简洁性(simplicity)。
- 优先考虑透明度(transparency),明确展示智能体的规划步骤。
- 通过详细的工具文档和测试(documentation and testing),精心设计你的智能体-计算机接口(ACI)。
框架可以帮助你快速入门,但在进入生产阶段时,不要犹豫减少抽象层次,使用基础组件进行构建。遵循这些原则,你可以创建既强大又可靠、可维护、并且被用户信任的智能体系统。
致谢
本文由 Erik Schluntz 和 Barry Zhang 编写。此工作基于我们在 Anthropic 构建智能体的经验以及客户分享的宝贵见解,对此我们深表感谢。
附录 1:智能体的实际应用
我们与客户的合作展示了两种特别有前景的 AI 智能体应用,展示了上述模式的实际价值。这两个应用都说明了智能体如何在既需要对话又需要行动的任务中发挥最大价值,这些任务具有明确的成功标准,能够启用反馈循环,并且能够整合有意义的人类监督。
A. 客户支持
客户支持将传统的聊天机器人界面与通过工具集成增强的能力结合在一起。这非常适合开放式智能体,因为:
- 支持互动自然地遵循对话流程,同时需要访问外部信息和执行操作;
- 可以集成工具来提取客户数据、订单历史和知识库文章;
- 诸如发放退款或更新工单等操作可以通过编程处理;
- 可以通过用户定义的解决方案来清晰衡量成功。
几家公司已经通过基于使用量的定价模型展示了这一方法的可行性,这些模型仅对成功解决的案例收费,显示了它们对智能体有效性的信心。
B. 编码智能体
软件开发领域展现了 LLM 特性巨大的潜力,从代码补全到自主问题解决,功能不断发展。智能体特别有效,因为:
- 代码解决方案可以通过自动化测试进行验证;
- 智能体可以利用测试结果作为反馈,迭代解决方案;
- 问题空间明确且结构化;
- 输出质量可以客观衡量。
在我们的实施中,智能体现在能够仅凭拉取请求描述,在 SWE-bench 验证基准中解决实际的 GitHub 问题(issues)。然而,尽管自动化测试有助于验证功能,但人工审查仍然至关重要,以确保解决方案符合更广泛的系统要求。
附录 2:工具的提示词工程
无论你正在构建哪种智能体系统,工具很可能会成为智能体的重要组成部分。工具使 Claude 能够通过在我们的 API 中明确指定外部服务和 API 的确切结构和定义,与这些服务和 API 进行交互。当 Claude 作出响应时,如果它计划调用某个工具,它将在 API 响应中包含一个工具使用块(Tool use block)。工具的定义和规范应该像设计整体提示词一样,得到同等程度的提示词工程关注。在这个简短的附录中,我们将介绍如何为你的工具进行提示词工程。
通常有多种方式来指定相同的操作。例如,你可以通过编写差异(diff)来指定文件编辑,或者通过重写整个文件来指定文件编辑。对于结构化输出,你可以将代码放在 Markdown 或 JSON 中返回。在软件工程中,这类差异通常是表面上的,可以无损地相互转换。然而,一些格式比其他格式更难以为 LLM 编写。编写差异需要知道在新代码写入之前,差异块的标题中有多少行发生了变化;而将代码写入 JSON(与 Markdown 相比)需要对换行符和引号进行额外的转义处理。
我们对选择工具格式的建议如下:
- 给模型足够的 tokens 让它在开始写代码之前有时间“思考”,避免写到死胡同。
- 保持格式,尽量与模型在互联网上遇到的文本格式相似。
- 确保没有格式“开销”,比如必须精确统计成千上万行代码,或转义任何它写入的代码。
一个经验法则是,考虑到人机界面(HCI)所投入的努力,计划在创建良好的智能体-计算机接口(ACI)时投入同样的努力。以下是一些实现的建议:
- 站在模型的角度思考。根据工具的描述和参数,是否能够明显理解如何使用该工具,还是需要仔细思考?如果是这样,那么模型可能也有相同的困惑。一个好的工具定义通常包括示例用法、边界情况、输入格式要求以及与其他工具的明确区分。
- 如何更改参数名称或描述以使其更加清晰?可以把这看作是为你团队中的初级开发人员编写一个优秀的文档字符串。当使用许多相似的工具时,这一点尤为重要。
- 测试模型如何使用你的工具:在我们的工作台上运行多个示例输入,看看模型犯了哪些错误,并进行迭代。
- 为你的工具设置 poka-yoke(防错机制)。调整参数,使错误变得更难发生。
在为 SWE-bench 构建智能体时,我们实际上花了更多时间优化工具,而不是整体提示词。例如,我们发现,在智能体移动出根目录后,模型在使用相对文件路径的工具时会犯错。为了解决这个问题,我们将工具更改为始终要求绝对文件路径——结果我们发现,模型完美地使用了这种方法。
- 感谢您的赞赏