英文文档原文详见 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 对象作为上下文。contextRunner.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 等。stroutput_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 个选项:
.run()
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
然后,运行程序运行一个循环:
final_output
max_turns
注意
LLM 输出是否被视为“最终输出”的规则是,它生成所需类型的文本输出,并且没有工具调用。
流式处理允许您在 LLM 运行时额外接收流式处理事件。流完成后,RunResultStreaming 将包含有关运行的完整信息,包括生成的所有新输出。您可以调用流式处理事件。在流媒体指南中阅读更多内容。.stream_events()
该参数允许您为代理运行配置一些全局设置:run_config
model
temperaturetop_p
workflow_name
调用任何 run 方法都可能导致一个或多个代理运行 (因此运行一个或多个 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 中。作为概述:
max_turns
调用方法时,会得到一个:Runner.run
runrun_sync
run_streamed
这两个函数都继承自 RunResultBase,这是最有用信息所在的位置。
final_output 属性包含最后一个运行的代理的最终输出。这是:
stroutput_type
last_agent.output_type
注意
final_output
的类型为 。由于交接,我们无法静态键入 this。如果发生切换,则意味着任何 Agent 都可能是最后一个 Agent,因此我们无法静态地知道可能的输出类型集。Any
您可以使用 result.to_input_list() 将结果转换为输入列表,该列表将您提供的原始输入连接到代理运行期间生成的项目。这样可以方便地获取一个代理运行的输出并将其传递到另一个运行中,或者在循环中运行它并每次都附加新的用户输入。
last_agent 属性包含最后一个运行的代理程序。根据您的应用程序,这通常对用户下次输入内容很有用。例如,如果您有一个将手交给特定语言的代理的一线会审代理,则可以存储最后一个代理,并在下次用户向代理发送消息时重复使用它。
new_items 属性包含在运行期间生成的新项。这些项是 RunItems。运行项包装 LLM 生成的 raw 项。
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.createdresponse.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 中有三类工具:
OpenAI 在使用 OpenAIResponsesModel 时提供了一些内置工具:
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 的模块来提取函数签名,并使用 griffe 来解析文档字符串和创建架构。inspectpydantic
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,
)
如前所述,我们会自动解析函数签名以提取工具的架构,并解析文档字符串以提取工具和单个参数的描述。关于这一点的一些说明:
inspect
griffegooglesphinxnumpyfunction_tooluse_docstring_infoFalse
架构提取的代码位于 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_toolfailure_error_function
default_tool_error_function
NoneModelBehaviorErrorUserError
如果要手动创建对象,则必须处理函数内部的错误。FunctionToolon_invoke_tool
交接允许代理将任务委派给另一个代理。这在不同代理专注于不同领域的情况下特别有用。例如,客户支持应用程序可能有代理,每个代理专门处理订单状态、退款、常见问题解答等任务。
移交表示为 LLM 的工具。因此,如果移交给名为 的代理,则该工具将称为 。Refund Agenttransfer_to_refund_agent
所有代理都有一个 handoffs 参数,该参数可以直接接受 Handoff,也可以接受自定义 Handoff 的对象。AgentHandoff
您可以使用 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>.""",
)