英文文档原文详见 https://openai.github.io/openai-agents-python/
本文是OpenAI-agents-sdk-python使用翻译软件翻译后的中文文档/教程。分多个帖子发布,帖子的目录如下:
(2) OpenAI agents sdk, agents,运行agents,结果,流,工具,交接
————————————————
目录
代理
代理是应用程序中的核心构建块。代理是一个大型语言模型 (LLM),配置了说明和工具。
基本配置
您将配置的代理的最常见属性是:
instructions
:也称为开发者消息或系统提示符。model
:使用哪个 LLM,并且可以选择配置模型调整参数,如温度、top_p等。model_settings
tools
:代理可用于完成其任务的工具。
from agents import Agent, ModelSettings, function_tool
def get_weather(city: str) -> str:
return f"The weather in {city} is sunny"
agent = Agent(
name="Haiku agent",
instructions="Always respond in haiku form",
model="o3-mini",
tools=[function_tool(get_weather)],
)
上下文
代理在其类型上是通用的。Context 是一个依赖关系注入工具:它是您创建并传递给 的对象,该对象被传递给每个代理、工具、切换等,它充当代理运行的依赖项和状态的抓取袋。您可以提供任何 Python 对象作为上下文。context
Runner.run()
@dataclass
class UserContext:
uid: str
is_pro_user: bool
async def fetch_purchases() -> list[Purchase]:
return ...
agent = Agent[UserContext](
...,
)
输出类型
默认情况下,代理生成纯文本(即 )输出。如果您希望代理生成特定类型的输出,则可以使用 parameter 。一个常见的选择是使用 Pydantic 对象,但我们支持任何可以包装在 Pydantic TypeAdapter 中的类型 - 数据类、列表、TypedDict 等。str
output_type
from pydantic import BaseModel
from agents import Agent
class CalendarEvent(BaseModel):
name: str
date: str
participants: list[str]
agent = Agent(
name="Calendar extractor",
instructions="Extract calendar events from text",
output_type=CalendarEvent,
)
注意
当您传递 , 时,它会告诉模型使用结构化输出而不是常规的纯文本响应。output_type
交接
Handoff 是代理可以委派给的子代理。您提供交接列表,代理可以选择委派给它们(如果相关)。这是一个强大的模式,允许编排在单一任务中表现出色的模块化、专用代理。在 handoffs 文档中阅读更多内容。
from agents import Agent
booking_agent = Agent(...)
refund_agent = Agent(...)
triage_agent = Agent(
name="Triage agent",
instructions=(
"Help the user with their questions."
"If they ask about booking, handoff to the booking agent."
"If they ask about refunds, handoff to the refund agent."
),
handoffs=[booking_agent, refund_agent],
)
动态指令
在大多数情况下,您可以在创建代理时提供说明。但是,您也可以通过函数提供动态指令。该函数将接收 agent 和 context,并且必须返回 prompt。regular 和 functions 都被接受。async
def dynamic_instructions(
context: RunContextWrapper[UserContext], agent: Agent[UserContext]
) -> str:
return f"The user's name is {context.context.name}. Help them with their questions."
agent = Agent[UserContext](
name="Triage agent",
instructions=dynamic_instructions,
)
生命周期事件 (钩子)
有时,您希望观察代理的生命周期。例如,您可能希望记录事件,或在某些事件发生时预取数据。您可以使用该属性挂接到代理生命周期。将 AgentHooks 类子类化,并覆盖您感兴趣的方法。hooks
护栏
防护机制允许您对用户输入运行检查/验证,与正在运行的代理并行。例如,您可以筛选用户输入的相关性。在 护栏 文档中阅读更多内容。
克隆/复制代理
通过在代理上使用该方法,您可以复制代理,并选择性地更改所需的任何属性。clone()
pirate_agent = Agent(
name="Pirate",
instructions="Write like a pirate",
model="o3-mini",
)
robot_agent = pirate_agent.clone(
name="Robot",
instructions="Write like a robot",
)
运行代理
您可以通过 Runner 类运行代理。您有 3 个选项:
- Runner.run(),它异步运行并返回 RunResult。
- Runner.run_sync() 的 API 中,这是一种同步方法,仅在后台运行。
.run()
- Runner.run_streamed()运行异步并返回 RunResultStreaming。它在流式处理模式下调用 LLM,并在收到这些事件时将这些事件流式传输给您。
from agents import Agent, Runner
async def main():
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
result = await Runner.run(agent, "Write a haiku about recursion in programming.")
print(result.final_output)
# Code within the code,
# Functions calling themselves,
# Infinite loop's dance.
在结果指南中阅读更多内容。
代理循环
在 中使用 run 方法时,将传入启动代理和输入。输入可以是字符串(被视为用户消息),也可以是输入项列表,这些项是 OpenAI 响应 API 中的项。Runner
然后,运行程序运行一个循环:
- 我们使用当前 input 调用当前代理的 LLM。
- LLM 生成其输出。
- 如果 LLM 返回 ,则循环结束,我们返回结果。
final_output
- 如果 LLM 执行切换,我们将更新当前代理和输入,然后重新运行循环。
- 如果 LLM 生成工具调用,我们将运行这些工具调用,附加结果,然后重新运行循环。
- 如果 LLM 返回 ,则循环结束,我们返回结果。
- 如果超过传递的值,则会引发 MaxTurnsExceeded 异常。
max_turns
注意
LLM 输出是否被视为“最终输出”的规则是,它生成所需类型的文本输出,并且没有工具调用。
流
流式处理允许您在 LLM 运行时额外接收流式处理事件。流完成后,RunResultStreaming 将包含有关运行的完整信息,包括生成的所有新输出。您可以调用流式处理事件。在流媒体指南中阅读更多内容。.stream_events()
运行 config
该参数允许您为代理运行配置一些全局设置:run_config
- model:允许设置要使用的全局 LLM 模型,而不管每个 Agent 有什么。
model
- model_provider:用于查找模型名称的模型提供程序,默认为 OpenAI。
- model_settings:覆盖特定于代理的设置。例如,您可以设置全局 或 .
temperature
top_p
- input_guardrails、output_guardrails:要包含在所有运行中的输入或输出护栏的列表。
- handoff_input_filter:应用于所有切换的全局输入滤波器(如果切换还没有)。输入过滤器允许您编辑发送到新代理的输入。有关更多详细信息,请参阅 Handoff.input_filter 中的文档。
- tracing_disabled:允许您禁用整个运行的跟踪。
- trace_include_sensitive_data:配置跟踪是否包含潜在的敏感数据,例如 LLM 和工具调用输入/输出。
- workflow_name、trace_id group_id:设置运行的跟踪工作流名称、跟踪 ID 和跟踪组 ID。我们建议至少设置 .会话 ID 是一个可选字段,可让您跨多个运行链接跟踪。
workflow_name
- trace_metadata:要包含在所有跟踪上的元数据。
对话/聊天线程
调用任何 run 方法都可能导致一个或多个代理运行 (因此运行一个或多个 LLM 调用),但它表示聊天对话中的单个逻辑轮次。例如:
- User turn:用户输入文本
- 运行程序运行:第一个代理调用 LLM,运行工具,移交给第二个代理,第二个代理运行更多工具,然后生成输出。
在代理运行结束时,您可以选择向用户显示的内容。例如,您可以向用户显示代理生成的每个新项目,或者只显示最终输出。无论哪种方式,用户都可以提出后续问题,在这种情况下,您可以再次调用 run 方法。
您可以使用基 RunResultBase.to_input_list() 方法获取下一轮的输入。
async def main():
agent = Agent(name="Assistant", instructions="Reply very concisely.")
with trace(workflow_name="Conversation", group_id=thread_id):
# First turn
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?")
print(result.final_output)
# San Francisco
# Second turn
new_input = output.to_input_list() + [{"role": "user", "content": "What state is it in?"}]
result = await Runner.run(agent, new_input)
print(result.final_output)
# California
异常
在某些情况下,SDK 会引发异常。完整列表位于 agents.exceptions 中。作为概述:
- AgentsException 是 SDK 中引发的所有异常的基类。
- MaxTurnsExceeded 在运行超过传递给 run 方法的值时引发。
max_turns
- ModelBehaviorError 当模型生成无效输出(例如格式错误的 JSON 或使用不存在的工具)时引发。
- 当您(使用 SDK 编写代码的人员)使用 SDK 出错时,会引发 UserError。
- InputGuardrailTripwireTriggered,则当护栏跳闸时,将引发 OutputGuardrailTripwireTriggered。
结果
调用方法时,会得到一个:Runner.run
- RunResult(如果调用 或
run
run_sync
- RunResultStreaming(如果您调用
run_streamed
这两个函数都继承自 RunResultBase,这是最有用信息所在的位置。
最终输出
final_output 属性包含最后一个运行的代理的最终输出。这是:
- a ,如果最后一个代理没有定义的
str
output_type
- 如果代理定义了 output type ,则为 type 的对象。
last_agent.output_type
注意
final_output
的类型为 。由于交接,我们无法静态键入 this。如果发生切换,则意味着任何 Agent 都可能是最后一个 Agent,因此我们无法静态地知道可能的输出类型集。Any
下一轮的输入
您可以使用 result.to_input_list() 将结果转换为输入列表,该列表将您提供的原始输入连接到代理运行期间生成的项目。这样可以方便地获取一个代理运行的输出并将其传递到另一个运行中,或者在循环中运行它并每次都附加新的用户输入。
最后一个代理
last_agent 属性包含最后一个运行的代理程序。根据您的应用程序,这通常对用户下次输入内容很有用。例如,如果您有一个将手交给特定语言的代理的一线会审代理,则可以存储最后一个代理,并在下次用户向代理发送消息时重复使用它。
新项目
new_items 属性包含在运行期间生成的新项。这些项是 RunItems。运行项包装 LLM 生成的 raw 项。
- MessageOutputItem 表示来自 LLM 的消息。原始项目是生成的消息。
- HandoffCallItem 表示 LLM 调用了切换工具。原始项是 LLM 中的工具调用项。
- HandoffOutputItem 指示发生了切换。原始项目是对 handoff 工具调用的工具响应。您还可以从项目访问源/目标代理。
- ToolCallItem 表示 LLM 调用了一个工具。
- ToolCallOutputItem 指示调用了工具。原始项是工具响应。您还可以从项目访问工具输出。
- ReasoningItem 表示 LLM 中的推理项。原始项是生成的推理。
其他信息
护栏结果
input_guardrail_results 和 output_guardrail_results 属性包含护栏的结果(如果有)。护栏结果有时可能包含您想要记录或存储的有用信息,因此我们会为您提供这些信息。
原始响应
raw_responses 属性包含 LLM 生成的 ModelResponses。
原始输入
input 属性包含您提供给方法的原始输入。在大多数情况下,您不需要它,但如果您需要,它可用。run
流
流式处理允许您在代理运行时订阅其更新。这对于显示最终用户进度更新和部分响应非常有用。
要进行流式处理,您可以调用 Runner.run_streamed(),这将为您提供 RunResultStreaming。调用 会为您提供 StreamEvent 对象的异步流,如下所述。result.stream_events()
原始响应事件
RawResponsesStreamEvent 是直接从 LLM 传递的原始事件。它们采用 OpenAI 响应 API 格式,这意味着每个事件都有一个类型(如 、 等)和数据。如果要在生成响应消息后立即将其流式传输给用户,则这些事件非常有用。response.created
response.output_text.delta
例如,这将逐个输出 LLM 令牌生成的文本。
import asyncio
from openai.types.responses import ResponseTextDeltaEvent
from agents import Agent, Runner
async def main():
agent = Agent(
name="Joker",
instructions="You are a helpful assistant.",
)
result = Runner.run_streamed(agent, input="Please tell me 5 jokes.")
async for event in result.stream_events():
if event.type == "raw_response_event" and isinstance(event.data, ResponseTextDeltaEvent):
print(event.data.delta, end="", flush=True)
if __name__ == "__main__":
asyncio.run(main())
运行项目事件和代理事件
RunItemStreamEvent是更高级别的事件。它们会在项目已完全生成时通知您。这允许你在 “message generated” 和 “tool run” 等级别推送进度更新,而不是每个令牌。同样,当当前代理发生更改时(例如,作为移交的结果),AgentUpdatedStreamEvent 会为您提供更新。
例如,这将忽略原始事件并将更新流式传输给用户。
import asyncio
import random
from agents import Agent, ItemHelpers, Runner, function_tool
@function_tool
def how_many_jokes() -> int:
return random.randint(1, 10)
async def main():
agent = Agent(
name="Joker",
instructions="First call the `how_many_jokes` tool, then tell that many jokes.",
tools=[how_many_jokes],
)
result = Runner.run_streamed(
agent,
input="Hello",
)
print("=== Run starting ===")
async for event in result.stream_events():
# We'll ignore the raw responses event deltas
if event.type == "raw_response_event":
continue
# When the agent updates, print that
elif event.type == "agent_updated_stream_event":
print(f"Agent updated: {event.new_agent.name}")
continue
# When items are generated, print them
elif event.type == "run_item_stream_event":
if event.item.type == "tool_call_item":
print("-- Tool was called")
elif event.item.type == "tool_call_output_item":
print(f"-- Tool output: {event.item.output}")
elif event.item.type == "message_output_item":
print(f"-- Message output:\n {ItemHelpers.text_message_output(event.item)}")
else:
pass # Ignore other event types
print("=== Run complete ===")
if __name__ == "__main__":
asyncio.run(main())
工具
工具允许代理执行作:例如获取数据、运行代码、调用外部 API,甚至使用计算机。Agent SDK 中有三类工具:
- 托管工具:这些工具与 AI 模型一起在 LLM 服务器上运行。OpenAI 提供检索、网络搜索和计算机使用作为托管工具。
- 函数调用:这些允许您将任何 Python 函数用作工具。
- 代理作为工具:这允许您将代理用作工具,允许代理呼叫其他代理而无需移交给他们。
托管工具
OpenAI 在使用 OpenAIResponsesModel 时提供了一些内置工具:
- WebSearchTool 允许代理搜索 Web。
- FileSearchTool 允许从您的 OpenAI 矢量存储中检索信息。
- ComputerTool 允许自动执行计算机使用任务。
from agents import Agent, FileSearchTool, Runner, WebSearchTool
agent = Agent(
name="Assistant",
tools=[
WebSearchTool(),
FileSearchTool(
max_num_results=3,
vector_store_ids=["VECTOR_STORE_ID"],
),
],
)
async def main():
result = await Runner.run(agent, "Which coffee shop should I go to, taking into account my preferences and the weather today in SF?")
print(result.final_output)
函数工具
您可以将任何 Python 函数用作工具。代理 SDK 将自动设置该工具:
- 工具的名称将是 Python 函数的名称(或者您可以提供名称)
- 工具描述将取自函数的文档字符串(或者您可以提供描述)
- 函数 inputs 的架构是根据函数的参数自动创建的
- 除非禁用,否则每个输入的描述都取自函数的文档字符串
我们使用 Python 的模块来提取函数签名,并使用 griffe 来解析文档字符串和创建架构。inspect
pydantic
import json
from typing_extensions import TypedDict, Any
from agents import Agent, FunctionTool, RunContextWrapper, function_tool
class Location(TypedDict):
lat: float
long: float
@function_tool
async def fetch_weather(location: Location) -> str: """Fetch the weather for a given location. Args: location: The location to fetch the weather for. """ # In real life, we'd fetch the weather from a weather API return "sunny" @function_tool(name_override="fetch_data") def read_file(ctx: RunContextWrapper[Any], path: str, directory: str | None = None) -> str: """Read the contents of a file. Args: path: The path to the file to read. directory: The directory to read the file from. """ # In real life, we'd read the file from the file system return "<file contents>" agent = Agent( name="Assistant", tools=[fetch_weather, read_file], ) for tool in agent.tools: if isinstance(tool, FunctionTool): print(tool.name) print(tool.description) print(json.dumps(tool.params_json_schema, indent=2)) print()
自定义功能工具
有时,您不想将 Python 函数用作工具。如果您愿意,可以直接创建 FunctionTool。您需要提供:
name
description
params_json_schema
,这是参数的 JSON 架构on_invoke_tool
,这是一个异步函数,它以 JSON 字符串的形式接收上下文和参数,并且必须以字符串形式返回工具输出。
from typing import Any
from pydantic import BaseModel
from agents import RunContextWrapper, FunctionTool
def do_some_work(data: str) -> str:
return "done"
class FunctionArgs(BaseModel):
username: str
age: int
async def run_function(ctx: RunContextWrapper[Any], args: str) -> str:
parsed = FunctionArgs.model_validate_json(args)
return do_some_work(data=f"{parsed.username} is {parsed.age} years old")
tool = FunctionTool(
name="process_user",
description="Processes extracted user data",
params_json_schema=FunctionArgs.model_json_schema(),
on_invoke_tool=run_function,
)
自动参数和文档字符串解析
如前所述,我们会自动解析函数签名以提取工具的架构,并解析文档字符串以提取工具和单个参数的描述。关于这一点的一些说明:
- 签名解析是通过模块完成的。我们使用类型注释来理解参数的类型,并动态构建一个 Pydantic 模型来表示整个架构。它支持大多数类型,包括 Python 基元、Pydantic 模型、TypedDicts 等。
inspect
- 我们用来解析文档字符串。支持的文档字符串格式包括 和 .我们尝试自动检测文档字符串格式,但这是尽力而为,您可以在调用 .您还可以通过设置为 来禁用文档字符串解析。
griffe
google
sphinx
numpy
function_tool
use_docstring_info
False
架构提取的代码位于 agents.function_schema 中。
代理作为工具
在某些工作流中,您可能希望中央代理来编排专用代理网络,而不是移交控制权。您可以通过将代理建模为工具来实现此目的。
from agents import Agent, Runner
import asyncio
spanish_agent = Agent(
name="Spanish agent",
instructions="You translate the user's message to Spanish",
)
french_agent = Agent(
name="French agent",
instructions="You translate the user's message to French",
)
orchestrator_agent = Agent(
name="orchestrator_agent",
instructions=(
"You are a translation agent. You use the tools given to you to translate."
"If asked for multiple translations, you call the relevant tools."
),
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",
),
],
)
async def main():
result = await Runner.run(orchestrator_agent, input="Say 'Hello, how are you?' in Spanish.")
print(result.final_output)
处理函数工具中的错误
通过 创建函数工具时,可以传递 .这是一个函数,可在工具调用崩溃时向 LLM 提供错误响应。@function_tool
failure_error_function
- 默认情况下(即,如果你不传递任何内容),它会运行 a 告诉 LLM 发生了错误。
default_tool_error_function
- 如果您传递自己的 error 函数,它会运行该函数,并将响应发送到 LLM。
- 如果您显式传递 ,则将重新引发任何工具调用错误供您处理。这可能是模型生成无效的 JSON,或者您的代码崩溃等。
None
ModelBehaviorError
UserError
如果要手动创建对象,则必须处理函数内部的错误。FunctionTool
on_invoke_tool
交接
交接允许代理将任务委派给另一个代理。这在不同代理专注于不同领域的情况下特别有用。例如,客户支持应用程序可能有代理,每个代理专门处理订单状态、退款、常见问题解答等任务。
移交表示为 LLM 的工具。因此,如果移交给名为 的代理,则该工具将称为 。Refund Agent
transfer_to_refund_agent
创建切换
所有代理都有一个 handoffs 参数,该参数可以直接接受 Handoff,也可以接受自定义 Handoff 的对象。Agent
Handoff
您可以使用 Agents SDK 提供的 handoff() 函数创建切换。此功能允许您指定要移交给的代理,以及可选的覆盖和输入过滤器。
基本用法
以下是创建简单切换的方法:
from agents import Agent, handoff
billing_agent = Agent(name="Billing agent")
refund_agent = Agent(name="Refund agent")
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
通过函数自定义切换handoff()
handoff() 函数允许你自定义内容。
agent
: 这是将要交给的代理。tool_name_override
:默认情况下,使用该函数,该函数解析为 。您可以覆盖此 URL。Handoff.default_tool_name()
transfer_to_<agent_name>
tool_description_override
:覆盖默认工具描述Handoff.default_tool_description()
on_handoff
:调用 handoff 时执行的回调函数。这对于在知道正在调用切换时立即开始一些数据获取等作非常有用。此函数接收代理上下文,还可以选择接收 LLM 生成的输入。输入数据由 param 控制。input_type
input_type
:切换所需的输入类型(可选)。input_filter
:这允许您筛选下一个代理接收的输入。有关更多信息,请参见下文。
from agents import Agent, handoff, RunContextWrapper
def on_handoff(ctx: RunContextWrapper[None]):
print("Handoff called")
agent = Agent(name="My agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
tool_name_override="custom_handoff_tool",
tool_description_override="Custom description",
)
切换输入
在某些情况下,您希望 LLM 在调用切换时提供一些数据。例如,假设将工作移交给 “Escalation agent”。您可能希望提供原因,以便记录它。
from pydantic import BaseModel
from agents import Agent, handoff, RunContextWrapper
class EscalationData(BaseModel):
reason: str
async def on_handoff(ctx: RunContextWrapper[None], input_data: EscalationData):
print(f"Escalation agent called with reason: {input_data.reason}")
agent = Agent(name="Escalation agent")
handoff_obj = handoff(
agent=agent,
on_handoff=on_handoff,
input_type=EscalationData,
)
输入筛选器
当切换发生时,就好像新代理接管了对话,并可以看到之前的整个对话历史记录。如果要更改此设置,可以设置 input_filter。输入过滤器是通过 HandoffInputData 接收现有输入的函数,并且必须返回新的 .HandoffInputData
有一些常见模式(例如,从历史记录中删除所有工具调用),这些模式在 agents.extensions.handoff_filters 中为您实现
from agents import Agent, handoff
from agents.extensions import handoff_filters
agent = Agent(name="FAQ agent")
handoff_obj = handoff(
agent=agent,
input_filter=handoff_filters.remove_all_tools,
)
建议的提示
为确保 LLM 正确理解交接,我们建议在座席中包含有关交接的信息。我们在 agents.extensions.handoff_prompt 中有一个建议的前缀。RECOMMENDED_PROMPT_PREFIX,或者您可以调用 agents.extensions.handoff_prompt.prompt_with_handoff_instructions 以自动将推荐数据添加到您的提示中。
from agents import Agent
from agents.extensions.handoff_prompt import RECOMMENDED_PROMPT_PREFIX
billing_agent = Agent(
name="Billing agent",
instructions=f"""{RECOMMENDED_PROMPT_PREFIX}
<Fill in the rest of your prompt here>.""",
)