智能体开发指南

原文:https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf

介绍

大型语言模型(LLM)正变得越来越能够处理复杂的、多步骤的任务。推理、多模态和工具使用方面的进步开启了一个新的由 LLM 驱动的系统类别,称为智能体(agents)

本指南专为探索如何构建其首批智能体的产品和工程团队而设计,将众多客户部署中的见解提炼为实用且可操作的最佳实践。它包括识别有前景的用例的框架、设计智能体逻辑和编排的清晰模式,以及确保您的智能体安全、可预测且有效地运行的最佳实践。

阅读本指南后,您将拥有自信地开始构建您的第一个智能体所需的基础知识。

什么是智能体?

智能体是代表您独立完成任务的系统

工作流是为了实现用户目标而必须执行的一系列步骤,无论是解决客户服务问题、预订餐厅、提交代码更改还是生成报告。

集成 LLM 但不使用它们来控制工作流执行的应用程序——例如简单的聊天机器人、单轮 LLM 或情感分类器——不是智能体。

更具体地说,智能体拥有使其能够可靠且一致地代表用户行动的核心特征:

何时需要创建你的智能体?

构建智能体需要重新思考您的系统如何做出决策和处理复杂性。与传统的自动化不同,智能体非常适合传统确定性和基于规则的方法难以应对的工作流。

考虑支付欺诈分析的示例。传统的规则引擎就像一个清单,根据预设的标准标记交易。相比之下,LLM 智能体的功能更像是一位经验丰富的调查员,评估上下文,考虑细微的模式,即使在没有明确违反规则的情况下也能识别可疑活动。这种细致的推理能力正是使智能体能够有效管理复杂的、模棱两可的情况的原因。

在评估智能体可以在何处增加价值时,优先考虑以前难以自动化的工作流,尤其是在传统方法遇到阻力的地方:

在致力于构建智能体之前,请验证您的用例是否能清楚地满足这些标准。否则,确定性的解决方案可能就足够了。

智能体设计基础

在其最基本的形式中,一个智能体包含三个核心组件

weather_agent = Agent(
    name="Weather agent",
    instructions="You are a helpful agent who can talk to users about the weather.",
    tools=[get_weather],
)

选择你的大语言模型

不同的模型在任务复杂性、延迟和成本方面具有不同的优势和权衡。正如我们将在下一节关于编排中看到的那样,您可能需要考虑在工作流的不同任务中使用各种模型。

并非每个任务都需要最智能的模型——一个简单的检索或意图分类任务可能由一个更小、更快的模型处理,而像决定是否批准退款这样的更困难的任务可能受益于一个更强大的模型。

一种有效的方法是使用最强大的模型为每个任务构建您的智能体原型,以建立性能基线。然后,尝试换入较小的模型,看看它们是否仍然能达到可接受的结果。这样,您就不会过早地限制智能体的能力,并且可以诊断出较小的模型在哪些方面成功或失败。

总之,选择模型的原则很简单

您可以在此处找到选择 OpenAI 模型的综合指南。

定义工具

工具通过使用底层应用程序或系统的 API 来扩展您智能体的能力。对于没有 API 的遗留系统,智能体可以依赖计算机使用模型直接通过 Web 和应用程序 UI 与这些应用程序和系统交互——就像人类一样。

每个工具都应该有一个标准化的定义,从而实现工具和智能体之间灵活的多对多关系。良好记录、经过全面测试且可重用的工具可以提高可发现性,简化版本管理并防止冗余定义。

广义上讲,智能体需要三种类型的工具

类型(Type) 描述(Description) 示例(Examples)
数据(Data) 使智能体能够检索执行工作流所需的上下文和信息。 查询事务数据库或 CRM 等系统,阅读 PDF 文档或搜索网络。
行动(Action) 使智能体能够与系统交互以执行操作,例如向数据库添加新信息、更新记录或发送消息。 发送电子邮件和短信,更新 CRM 记录,将客户服务工单转交给人工。
编排(Orchestration) 智能体本身可以充当其他智能体的工具——参见编排部分中的管理器模式(Manager Pattern)。 退款智能体(Refund agent)、研究智能体(Research agent)、写作智能体(Writing agent)。
from agents import Agent, WebSearchTool, function_tool
import datetime

@function_tool
def save_results(output: str):
    db.insert({"output": output, "timestamp": datetime.time()})
    return "File saved"

search_agent = Agent(
    name="Search agent",
    instructions="Help the user search the internet and save results if asked.",
    tools=[WebSearchTool(),save_results],
)

随着所需工具数量的增加,请考虑跨多个智能体拆分任务(参见编排)。

配置指令

高质量的指令对于任何由 LLM 驱动的应用程序都至关重要,对于智能体尤其如此。清晰的指令减少了歧义,提高了智能体的决策能力,从而实现更顺畅的工作流执行和更少的错误。

智能体指令的最佳实践

"你是一位为 LLM 智能体编写指令的专家。将以下帮助中心文档转换为一个清晰的指令集,以编号列表的形式编写。该文档将是 LLM 遵循的策略。确保没有歧义,并且指令是作为智能体的指示编写的。要转换的帮助中心文档如下:{{help_center_doc}}"

编排

在有了基础组件之后,您可以考虑编排模式,以使您的智能体能够有效地执行工作流。

虽然立即构建具有复杂架构的完全自主的智能体很诱人,但客户通常通过增量方法取得更大的成功。

一般来说,编排模式分为两类:

单智能体系统

单个智能体可以通过逐步添加工具来处理许多任务,保持复杂性可控并简化评估和维护。每个新工具都扩展了其功能,而无需过早地强制您编排多个智能体。

单智能体系统架构图
图1:单智能体系统架构

每种编排方法都需要一个"run"的概念,通常实现为一个循环,让智能体运行直到达到退出条件。常见的退出条件包括工具调用、特定的结构化输出、错误或达到最大轮数。

例如,在 Agents SDK 中,智能体使用 Runner.run() 方法启动,该方法循环遍历 LLM,直到以下任一情况发生:

Agents.run(agent, [UserMessage("What's the capital of the USA?")])

while 循环的概念是智能体运行的核心。在多智能体系统中,正如您将在下一节中看到的那样,您可以有一系列的工具调用和智能体之间的移交,但允许模型运行多个步骤直到满足退出条件。

一种在不切换到多智能体框架的情况下管理复杂性的有效策略是使用提示模板。与其为不同的用例维护大量的单独提示,不如使用一个灵活的基础提示,该提示接受策略变量。这种模板方法可以轻松适应各种上下文,显着简化维护和评估。当出现新的用例时,您可以更新变量,而不是重写整个工作流。

""" You are a call center agent. You are interacting with {{user_first_name}} who has been a member for {{user_tenure}}. The user's most common complains are about {{user_complaint_categories}}. Greet the user, thank them for being a loyal customer, and answer any questions the user may have!"""

何时考虑创建多个智能体

我们的一般建议是首先最大化单个智能体的能力。更多的智能体可以提供直观的概念分离,但会引入额外的复杂性和开销,因此通常一个带有工具的智能体就足够了。

对于许多复杂的工作流,跨多个智能体拆分提示和工具可以提高性能和可扩展性。当您的智能体无法遵循复杂的指令或始终选择错误的工具时,您可能需要进一步划分您的系统并引入更多不同的智能体。

拆分智能体的实用指南包括

多智能体系统

虽然可以为特定的工作流和需求设计无数种多智能体系统,但我们与客户的经验突出了两个广泛适用的类别:

多智能体系统可以建模为图,其中智能体表示为节点。在管理器模式中,边表示工具调用,而在去中心化模式中,边表示在智能体之间转移执行权的移交。

无论采用哪种编排模式,相同的原则都适用:保持组件的灵活性、可组合性,并由清晰、结构良好的提示驱动

管理器模式(Manager pattern)

管理器模式使中央 LLM("管理器")能够通过工具调用无缝地协调一个由专门智能体组成的网络。管理器不会丢失上下文或控制权,而是智能地在正确的时间将任务委派给正确的智能体,毫不费力地将结果合成为一个连贯的交互。这确保了平滑、统一的用户体验,并且可以随时按需使用专业功能。

此模式非常适合您只希望一个智能体控制工作流执行并可以访问用户的场景。

管理器模式架构图
图2:管理器模式架构
from agents import Agent, Runner

manager_agent = Agent(
    name="manager_agent",
    instructions=(
        "You are a translation agent. You use the tools given to you to\n"
        "translate."
    ),
    tools=[
        spanish_agent.as_tool(
            tool_name="translate_to_spanish",
            tool_description="Translate the user's message to Spanish",
        ),
        french_agent.as_tool(
            tool_name="translate_to_french",
            tool_description="Translate the user's message to French",
        ),
        italian_agent.as_tool(
            tool_name="translate_to_italian",
            tool_description="Translate the user's message to Italian",
        ),
    ],
)

async def main():
    msg = input("Translate 'hello' to Spanish, French and Italian for me!\n")
    orchestrator_output = await Runner.run(
        manager_agent,msg)
    for message in orchestrator_output.new_messages:
        print(f" - {message.content}")

## Translation step:

声明式图与非声明式图

一些框架是声明式的,要求开发人员通过由节点(智能体)和边(确定性或动态移交)组成的图预先显式地定义工作流中的每个分支、循环和条件。虽然这有利于视觉清晰度,但随着工作流变得更加动态和复杂,这种方法很快就会变得繁琐且具有挑战性,通常需要学习专门的领域特定语言。

相比之下,Agents SDK 采用了一种更灵活的代码优先方法。开发人员可以使用熟悉的编程结构直接表达工作流逻辑,而无需预先定义整个图,从而实现更动态和适应性更强的智能体编排。

去中心化模式(Decentralized pattern)

去中心化模式中,智能体可以相互"移交"工作流的执行权。移交是一种单向转移,允许一个智能体委派给另一个智能体。在 Agents SDK 中,移交是一种工具或函数。如果一个智能体调用一个移交函数,我们会立即开始执行被移交给的新智能体,同时转移最新的对话状态。

这种模式涉及使用许多地位平等的智能体,其中一个智能体可以直接将工作流的控制权移交给另一个智能体。当您不需要单个智能体维护中央控制或合成时,而是允许每个智能体接管执行并根据需要与用户交互时,这是最佳的。

去中心化模式架构图
图3:去中心化模式架构
from agents import Agent, Runner

technical_support_agent = Agent(
    name="Technical Support Agent",
    instructions=(
        "You provide expert assistance with resolving technical issues,\n"
        "system outages, or product troubleshooting."
    ),
    tools=[search_knowledge_base]
)

sales_assistant_agent = Agent(
    name="Sales Assistant Agent",
    instructions=(
        "You help enterprise clients browse the product catalog, recommend\n"
        "suitable solutions, and facilitate purchase transactions."
    ),
    tools=[initiate_purchase_order]
)

order_management_agent = Agent(
    name="Order Management Agent",
    instructions=(
        "You assist clients with inquiries regarding order tracking,\n"
        "delivery schedules, and processing returns or refunds."
    ),
    tools=[track_order_status, initiate_refund_process]
)

triage_agent = Agent(
    name="Triage Agent",
    instructions=(
        "You act as the first point of contact, assessing customer\n"
        "queries and directing them promptly to the correct specialized agent."
    ),
    handoffs=[technical_support_agent, sales_assistant_agent,
              order_management_agent],
)

async def main():
    await Runner.run(
        triage_agent,
        input("Could you please provide an update on the delivery timeline for\n"
              "our recent purchase?\n")
    )

在上面的示例中,初始用户消息被发送给 triage_agenttriage_agent 识别到输入与最近的购买有关,将调用对 order_management_agent 的移交,并将控制权转移给它。

对于诸如会话分流之类的场景,或者当您希望专门的智能体完全接管某些任务而无需原始智能体继续参与时,此模式尤其有效。您可以选择为第二个智能体配备一个返回给原始智能体的移交,使其在必要时可以再次转移控制权。

护栏

精心设计的护栏(guardrails)有助于您管理数据隐私风险(例如,防止系统提示泄露)或声誉风险(例如,强制执行符合品牌要求的模型行为)。护栏是任何基于 LLM 的部署的关键组成部分,但应与强大的身份验证和授权协议、严格的访问控制以及标准的软件安全措施相结合。

将护栏视为分层防御机制。虽然单个护栏不太可能提供足够的保护,但一起使用多个专门的护栏可以创建更具弹性的智能体。

护栏系统架构图
图4:护栏系统架构

护栏类型

构建护栏

设置针对您已识别的用例风险的护栏,并在发现新的漏洞时添加额外的护栏。

我们发现以下启发式方法是有效的:

from agents import Agent, GuardrailFunctionOutput, InputGuardrailTripwireTriggered, RunContextWrapper, Runner, TResponseInputItem, input_guardrail, Guardrail, GuardrailTripwireTriggered
from pydantic import BaseModel
from typing import List, Optional

class ChurnDetectionOutput(BaseModel):
    is_churn_risk: bool
    reasoning: Optional[str]

churn_detection_agent = Agent(
    name="Churn Detection Agent",
    instructions="Identify if the user message indicates a potential\ncustomer churn risk.",
    output_type=ChurnDetectionOutput,
)

@input_guardrail
async def churn_detection_tripwire(
    ctx: RunContextWrapper, agent: Agent, input: List[TResponseInputItem] | None
) -> GuardrailFunctionOutput:
    result = await Runner.run(churn_detection_agent, [item.input for item in input],
                              context=ctx.context)
    return GuardrailFunctionOutput(
        output_info=result.final_output,
        tripwire_triggered=result.final_output.is_churn_risk,
    )

customer_support_agent = Agent(
    name="Customer support agent",
    instructions="You are a customer support agent. You help customers with\ntheir questions.",
    input_guardrails=[
        Guardrail(guardrail_function=churn_detection_tripwire),
    ],
)

async def main():
    ## This should be ok
    await Runner.run(customer_support_agent, "Hello!")
    print("Hello message passed")

    ## This should trip the guardrail
    try:
        await Runner.run(agent, "I think I might cancel my subscription")
    except GuardrailTripwireTriggered:
        print("Churn detection guardrail tripped")
    else:
        print("Guardrail didn't trip - this is unexpected")

Agents SDK 默认采用乐观执行,将护栏视为一等公民。在这种方法下,主要智能体主动生成输出,而护栏同时运行,如果违反约束则触发异常。

护栏可以实现为函数或智能体,以执行诸如越狱预防、相关性验证、关键词过滤、阻止列表执行或安全分类等策略。例如,上面的智能体乐观地处理一个数学问题输入,直到 math_homework_tripwire 护栏识别到违规并引发异常。

人工干预计划

人工干预是一个关键的保障措施,使您能够在不影响用户体验的情况下提高智能体的实际性能。这在部署初期尤其重要,有助于识别故障、发现边缘情况并建立健全的评估周期。

实施人工干预机制允许智能体在无法完成任务时优雅地转移控制权。在客户服务中,这意味着将问题升级给人工客服。对于编码智能体,这意味着将控制权交还给用户。

以下两个主要触发因素通常需要人工干预:

  • 超出失败阈值:设置智能体重试或操作的限制。如果智能体超出这些限制(例如,多次尝试后仍无法理解客户意图),则升级到人工干预。
  • 高风险操作:敏感、不可逆或高风险的操作应触发人工监督,直到对智能体的可靠性有足够的信心为止。示例包括取消用户订单、授权大额退款或进行付款。

结论

智能体标志着工作流自动化的新纪元,系统能够推理模糊性、跨工具执行操作并处理具有高度自主性的多步骤任务。与更简单的 LLM 应用程序不同,智能体可以端到端地执行工作流,使其非常适合涉及复杂决策、非结构化数据或脆弱的基于规则的系统的用例。

要构建可靠的智能体,请从坚实的基础开始:将强大的模型与定义明确的工具和清晰、结构化的指令配对。使用与您的复杂程度相匹配的编排模式,首先从单个智能体开始,仅在需要时才发展到多智能体系统护栏在每个阶段都至关重要,从输入过滤和工具使用到人工在环干预,有助于确保智能体在生产环境中安全且可预测地运行。

成功部署的道路并非一蹴而就。从小处着手,与真实用户进行验证,并随着时间的推移逐步增强功能。凭借正确的基础和迭代方法,智能体可以交付真正的商业价值——不仅可以自动化任务,还可以通过智能和适应性自动化整个工作流。

如果您正在为您的组织探索智能体或准备首次部署,请随时联系我们。我们的团队可以提供专业知识、指导和实践支持,以确保您的成功。