chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 智能体
|
||||
|
||||
智能体是应用中的核心构建块。智能体是一个大语言模型(LLM),配置了指令、工具以及任务转移、安全防护措施和structured outputs等可选运行时行为。
|
||||
|
||||
当你需要定义或自定义单个普通 `Agent` 时,请使用本页面。如果你正在确定多个智能体应如何协作,请阅读[智能体编排](multi_agent.md)。如果智能体需要在隔离的工作区中运行,并使用由清单定义的文件和沙箱原生能力,请阅读[沙箱智能体概念](sandbox/guide.md)。
|
||||
|
||||
对于OpenAI模型,SDK默认使用 Responses API,但这里的区别在于编排方式:`Agent` 与 `Runner` 相结合后,SDK可以为你管理轮次、工具、安全防护措施、任务转移和会话。如果你希望自行控制该循环,请直接使用 Responses API。
|
||||
|
||||
## 后续指南选择
|
||||
|
||||
将本页面作为智能体定义的入口。根据你接下来需要做出的决策,前往相应的关联指南。
|
||||
|
||||
| 如果你想要…… | 接下来阅读 |
|
||||
| --- | --- |
|
||||
| 选择模型或提供商配置 | [模型](models/index.md) |
|
||||
| 为智能体添加能力 | [工具](tools.md) |
|
||||
| 让智能体在真实代码仓库、文档集合或隔离工作区中运行 | [沙箱智能体快速入门](sandbox_agents.md) |
|
||||
| 在管理器式编排与任务转移之间进行选择 | [智能体编排](multi_agent.md) |
|
||||
| 配置任务转移行为 | [任务转移](handoffs.md) |
|
||||
| 运行轮次、以流式传输事件或管理对话状态 | [运行智能体](running_agents.md) |
|
||||
| 检查最终输出、运行项或可恢复状态 | [结果](results.md) |
|
||||
| 共享本地依赖项和运行时状态 | [上下文管理](context.md) |
|
||||
|
||||
## 基本配置
|
||||
|
||||
智能体最常用的属性包括:
|
||||
|
||||
| 属性 | 必需 | 描述 |
|
||||
| --- | --- | --- |
|
||||
| `name` | 是 | 易于理解的智能体名称。 |
|
||||
| `instructions` | 否 | 系统提示词或动态指令回调。强烈建议设置。请参阅[动态指令](#dynamic-instructions)。 |
|
||||
| `prompt` | 否 | OpenAI Responses API 提示词配置。接受静态提示词对象或函数。请参阅[提示词模板](#prompt-templates)。 |
|
||||
| `handoff_description` | 否 | 当此智能体作为任务转移目标提供时显示的简短描述。 |
|
||||
| `handoffs` | 否 | 将对话委派给专业智能体。请参阅[任务转移](handoffs.md)。 |
|
||||
| `model` | 否 | 要使用的LLM。请参阅[模型](models/index.md)。 |
|
||||
| `model_settings` | 否 | 模型调优参数,例如 `temperature`、`top_p` 和 `tool_choice`。 |
|
||||
| `tools` | 否 | 智能体可以调用的工具。请参阅[工具](tools.md)。 |
|
||||
| `mcp_servers` | 否 | 面向智能体、由MCP支持的工具。请参阅[MCP指南](mcp.md)。 |
|
||||
| `mcp_config` | 否 | 微调MCP工具的准备方式,例如严格架构转换和MCP故障格式化。请参阅[MCP指南](mcp.md#agent-level-mcp-configuration)。 |
|
||||
| `input_guardrails` | 否 | 在此智能体链的首个用户输入上运行的安全防护措施。请参阅[安全防护措施](guardrails.md)。 |
|
||||
| `output_guardrails` | 否 | 在此智能体的最终输出上运行的安全防护措施。请参阅[安全防护措施](guardrails.md)。 |
|
||||
| `output_type` | 否 | 使用结构化输出类型,而非纯文本。请参阅[输出类型](#output-types)。 |
|
||||
| `hooks` | 否 | 智能体范围内的生命周期回调。请参阅[生命周期事件(钩子)](#lifecycle-events-hooks)。 |
|
||||
| `tool_use_behavior` | 否 | 控制工具结果是返回模型继续处理,还是结束运行。请参阅[工具使用行为](#tool-use-behavior)。 |
|
||||
| `reset_tool_choice` | 否 | 在工具调用后重置 `tool_choice`(默认值:`True`),以避免工具使用循环。请参阅[强制使用工具](#forcing-tool-use)。 |
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings, function_tool
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Haiku agent",
|
||||
instructions="Always respond in haiku form",
|
||||
model="gpt-5-nano",
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
本节中的所有内容都适用于 `Agent`。`SandboxAgent` 以相同理念为基础,并添加了 `default_manifest`、`base_instructions`、`capabilities` 和 `run_as`,用于工作区范围内的运行。请参阅[沙箱智能体概念](sandbox/guide.md)。
|
||||
|
||||
## 提示词模板
|
||||
|
||||
你可以通过设置 `prompt` 来引用在OpenAI平台中创建的提示词模板。该功能适用于使用 Responses API 的OpenAI模型。
|
||||
|
||||
要使用此功能,请:
|
||||
|
||||
1. 前往 https://platform.openai.com/playground/prompts
|
||||
2. 创建一个新的提示词变量 `poem_style`。
|
||||
3. 创建一个包含以下内容的系统提示词:
|
||||
|
||||
```
|
||||
Write a poem in {{poem_style}}
|
||||
```
|
||||
|
||||
4. 使用 `--prompt-id` 标志运行代码示例。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="Prompted assistant",
|
||||
prompt={
|
||||
"id": "pmpt_123",
|
||||
"version": "1",
|
||||
"variables": {"poem_style": "haiku"},
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
你也可以在运行时动态生成提示词:
|
||||
|
||||
```python
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agents import Agent, GenerateDynamicPromptData, Runner
|
||||
|
||||
@dataclass
|
||||
class PromptContext:
|
||||
prompt_id: str
|
||||
poem_style: str
|
||||
|
||||
|
||||
async def build_prompt(data: GenerateDynamicPromptData):
|
||||
ctx: PromptContext = data.context.context
|
||||
return {
|
||||
"id": ctx.prompt_id,
|
||||
"version": "1",
|
||||
"variables": {"poem_style": ctx.poem_style},
|
||||
}
|
||||
|
||||
|
||||
agent = Agent(name="Prompted assistant", prompt=build_prompt)
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Say hello",
|
||||
context=PromptContext(prompt_id="pmpt_123", poem_style="limerick"),
|
||||
)
|
||||
```
|
||||
|
||||
## 上下文
|
||||
|
||||
智能体的 `context` 类型是泛型。上下文是一种依赖注入工具:它是你创建并传递给 `Runner.run()` 的对象,会传递给每个智能体、工具、任务转移等,并作为智能体运行所需依赖项和状态的汇集容器。你可以将任何 Python 对象作为上下文提供。
|
||||
|
||||
请阅读[上下文指南](context.md),了解完整的 `RunContextWrapper` 接口、共享用量追踪、嵌套的 `tool_input` 以及序列化注意事项。
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class UserContext:
|
||||
name: str
|
||||
uid: str
|
||||
is_pro_user: bool
|
||||
|
||||
async def fetch_purchases() -> list[Purchase]:
|
||||
return ...
|
||||
|
||||
agent = Agent[UserContext](
|
||||
...,
|
||||
)
|
||||
```
|
||||
|
||||
## 输出类型
|
||||
|
||||
默认情况下,智能体生成纯文本(即 `str`)输出。如果你希望智能体生成特定类型的输出,可以使用 `output_type` 参数。常见选择是使用 [Pydantic](https://docs.pydantic.dev/) 对象,但我们支持任何能够封装在 Pydantic [TypeAdapter](https://docs.pydantic.dev/latest/api/type_adapter/) 中的类型,例如数据类、列表、TypedDict 等。
|
||||
|
||||
```python
|
||||
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,
|
||||
)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
传入 `output_type` 时,即表示让模型使用[structured outputs](https://platform.openai.com/docs/guides/structured-outputs),而不是常规的纯文本响应。
|
||||
|
||||
## 多智能体系统设计模式
|
||||
|
||||
多智能体系统有很多设计方式,但我们通常会看到两种具有广泛适用性的模式:
|
||||
|
||||
1. 管理器(agents as tools):中央管理器/编排器将专业子智能体作为工具调用,并保留对话的控制权。
|
||||
2. 任务转移:对等智能体将控制权转移给专业智能体,由其接管对话。这是一种去中心化模式。
|
||||
|
||||
有关更多详细信息,请参阅[智能体构建实用指南](https://cdn.openai.com/business-guides-and-resources/a-practical-guide-to-building-agents.pdf)。
|
||||
|
||||
### 管理器(agents as tools)
|
||||
|
||||
`customer_facing_agent` 负责处理所有用户交互,并调用作为工具提供的专业子智能体。请在[工具](tools.md#agents-as-tools)文档中了解更多信息。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
booking_agent = Agent(...)
|
||||
refund_agent = Agent(...)
|
||||
|
||||
customer_facing_agent = Agent(
|
||||
name="Customer-facing agent",
|
||||
instructions=(
|
||||
"Handle all direct user communication. "
|
||||
"Call the relevant tools when specialized expertise is needed."
|
||||
),
|
||||
tools=[
|
||||
booking_agent.as_tool(
|
||||
tool_name="booking_expert",
|
||||
tool_description="Handles booking questions and requests.",
|
||||
),
|
||||
refund_agent.as_tool(
|
||||
tool_name="refund_expert",
|
||||
tool_description="Handles refund questions and requests.",
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
### 任务转移
|
||||
|
||||
任务转移是智能体可以委派任务的子智能体。发生任务转移时,被委派的智能体会收到对话历史记录并接管对话。此模式支持模块化的专业智能体,使其能够专注并擅长单一任务。请在[任务转移](handoffs.md)文档中了解更多信息。
|
||||
|
||||
```python
|
||||
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, hand off to the booking agent. "
|
||||
"If they ask about refunds, hand off to the refund agent."
|
||||
),
|
||||
handoffs=[booking_agent, refund_agent],
|
||||
)
|
||||
```
|
||||
|
||||
## 动态指令
|
||||
|
||||
在大多数情况下,你可以在创建智能体时提供指令。不过,你也可以通过函数提供动态指令。该函数将接收智能体和上下文,并且必须返回提示词。普通函数和 `async` 函数均受支持。
|
||||
|
||||
```python
|
||||
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,
|
||||
)
|
||||
```
|
||||
|
||||
## 生命周期事件(钩子)
|
||||
|
||||
有时,你可能需要观察智能体的生命周期。例如,你可能希望在特定事件发生时记录事件日志、预取数据或记录用量。
|
||||
|
||||
钩子分为两个作用域:
|
||||
|
||||
- [`RunHooks`][agents.lifecycle.RunHooks] 观察整个 `Runner.run(...)` 调用,包括向其他智能体的任务转移。
|
||||
- [`AgentHooks`][agents.lifecycle.AgentHooks] 通过 `agent.hooks` 附加到特定智能体实例。
|
||||
|
||||
回调上下文也会根据事件而变化:
|
||||
|
||||
- 智能体启动/结束钩子接收 [`AgentHookContext`][agents.run_context.AgentHookContext],该对象会封装你的原始上下文,并携带共享的运行用量状态。
|
||||
- LLM、工具和任务转移钩子接收 [`RunContextWrapper`][agents.run_context.RunContextWrapper]。
|
||||
|
||||
典型的钩子触发时机:
|
||||
|
||||
- `on_agent_start` / `on_agent_end`:特定智能体开始或完成最终输出的生成时。
|
||||
- `on_llm_start` / `on_llm_end`:紧邻每次模型调用的前后。
|
||||
- `on_tool_start` / `on_tool_end`:每次本地工具调用的前后。对于工具调用,钩子的 `context` 通常是 `ToolContext`,因此你可以检查 `tool_call_id` 等工具调用元数据。
|
||||
- `on_handoff`:控制权从一个智能体转移到另一个智能体时。
|
||||
|
||||
如果你需要为整个工作流设置单一观察器,请使用 `RunHooks`;如果某个智能体需要自定义副作用,请使用 `AgentHooks`。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunHooks, Runner
|
||||
|
||||
|
||||
class LoggingHooks(RunHooks):
|
||||
async def on_agent_start(self, context, agent):
|
||||
print(f"Starting {agent.name}")
|
||||
|
||||
async def on_llm_end(self, context, agent, response):
|
||||
print(f"{agent.name} produced {len(response.output)} output items")
|
||||
|
||||
async def on_agent_end(self, context, agent, output):
|
||||
print(f"{agent.name} finished with usage: {context.usage}")
|
||||
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
result = await Runner.run(agent, "Explain quines", hooks=LoggingHooks())
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
有关完整的回调接口,请参阅[生命周期 API 参考](ref/lifecycle.md)。
|
||||
|
||||
## 安全防护措施
|
||||
|
||||
安全防护措施允许你在智能体运行的同时并行检查/验证用户输入,并在智能体生成输出后检查/验证该输出。例如,你可以筛查用户输入和智能体输出是否相关。请在[安全防护措施](guardrails.md)文档中了解更多信息。
|
||||
|
||||
## 智能体的克隆/复制
|
||||
|
||||
通过对智能体使用 `clone()` 方法,你可以复制一个智能体,并可选择更改所需的任意属性。
|
||||
|
||||
```python
|
||||
pirate_agent = Agent(
|
||||
name="Pirate",
|
||||
instructions="Write like a pirate",
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
robot_agent = pirate_agent.clone(
|
||||
name="Robot",
|
||||
instructions="Write like a robot",
|
||||
)
|
||||
```
|
||||
|
||||
## 强制使用工具
|
||||
|
||||
提供工具列表并不意味着LLM一定会使用工具。你可以通过设置 [`ModelSettings.tool_choice`][agents.model_settings.ModelSettings.tool_choice] 来强制使用工具。有效值包括:
|
||||
|
||||
1. `auto`,允许LLM自行决定是否使用工具。
|
||||
2. `required`,要求LLM使用工具(但可以智能地决定使用哪个工具)。
|
||||
3. `none`,要求LLM_不_使用工具。
|
||||
4. 设置特定字符串,例如 `my_tool`,要求LLM使用该特定工具。
|
||||
|
||||
使用 OpenAI Responses 工具搜索时,具名工具选择的限制更多:不能通过 `tool_choice` 指定裸命名空间名称或仅延迟加载的工具,并且 `tool_choice="tool_search"` 不会指定 [`ToolSearchTool`][agents.tool.ToolSearchTool]。在这些情况下,建议使用 `auto` 或 `required`。有关 Responses 的特定限制,请参阅[托管工具搜索](tools.md#hosted-tool-search)。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, ModelSettings
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
model_settings=ModelSettings(tool_choice="get_weather")
|
||||
)
|
||||
```
|
||||
|
||||
## 工具使用行为
|
||||
|
||||
`Agent` 配置中的 `tool_use_behavior` 参数控制如何处理工具输出:
|
||||
|
||||
- `"run_llm_again"`:默认值。运行工具后,由LLM处理结果并生成最终响应。
|
||||
- `"stop_on_first_tool"`:将第一次工具调用的输出用作最终响应,不再由LLM进一步处理。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, ModelSettings
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
tool_use_behavior="stop_on_first_tool"
|
||||
)
|
||||
```
|
||||
|
||||
- `StopAtTools(stop_at_tool_names=[...])`:如果调用了任一指定工具,则停止运行,并将其输出用作最终响应。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool
|
||||
from agents.agent import StopAtTools
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
@function_tool
|
||||
def sum_numbers(a: int, b: int) -> int:
|
||||
"""Adds two numbers."""
|
||||
return a + b
|
||||
|
||||
agent = Agent(
|
||||
name="Stop At Stock Agent",
|
||||
instructions="Get weather or sum numbers.",
|
||||
tools=[get_weather, sum_numbers],
|
||||
tool_use_behavior=StopAtTools(stop_at_tool_names=["get_weather"])
|
||||
)
|
||||
```
|
||||
|
||||
- `ToolsToFinalOutputFunction`:处理工具结果并决定是停止还是继续由LLM处理的自定义函数。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool, FunctionToolResult, RunContextWrapper
|
||||
from agents.agent import ToolsToFinalOutputResult
|
||||
from typing import List, Any
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Returns weather info for the specified city."""
|
||||
return f"The weather in {city} is sunny"
|
||||
|
||||
def custom_tool_handler(
|
||||
context: RunContextWrapper[Any],
|
||||
tool_results: List[FunctionToolResult]
|
||||
) -> ToolsToFinalOutputResult:
|
||||
"""Processes tool results to decide final output."""
|
||||
for result in tool_results:
|
||||
if result.output and "sunny" in result.output:
|
||||
return ToolsToFinalOutputResult(
|
||||
is_final_output=True,
|
||||
final_output=f"Final weather: {result.output}"
|
||||
)
|
||||
return ToolsToFinalOutputResult(
|
||||
is_final_output=False,
|
||||
final_output=None
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="Retrieve weather details.",
|
||||
tools=[get_weather],
|
||||
tool_use_behavior=custom_tool_handler
|
||||
)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
为防止无限循环,框架会在工具调用后自动将 `tool_choice` 重置为“auto”。可以通过 [`agent.reset_tool_choice`][agents.agent.Agent.reset_tool_choice] 配置此行为。之所以会出现无限循环,是因为工具结果会发送给LLM,而LLM随后会因 `tool_choice` 再次生成工具调用,如此无限重复。
|
||||
@@ -0,0 +1,205 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 配置
|
||||
|
||||
本页涵盖 SDK 范围的默认设置,这些设置通常在应用启动时设置一次,例如默认 OpenAI 密钥或客户端、默认 OpenAI API 形态、追踪导出默认设置以及日志行为。
|
||||
|
||||
这些默认设置仍适用于基于沙盒的工作流,但沙盒工作区、沙盒客户端和会话复用需单独配置。
|
||||
|
||||
如果你需要改为配置特定智能体或运行,请从以下内容开始:
|
||||
|
||||
- [智能体](agents.md),了解普通 `Agent` 上的 instructions、tools、输出类型、任务转移和安全防护措施。
|
||||
- [运行智能体](running_agents.md),了解 `RunConfig`、会话和对话状态选项。
|
||||
- [沙盒智能体](sandbox/guide.md),了解 `SandboxRunConfig`、清单、能力以及特定于沙盒客户端的工作区设置。
|
||||
- [模型](models/index.md),了解模型选择和提供方配置。
|
||||
- [追踪](tracing.md),了解每次运行的追踪元数据和自定义追踪进程。
|
||||
|
||||
## API 密钥和客户端
|
||||
|
||||
默认情况下,SDK 使用 `OPENAI_API_KEY` 环境变量进行 LLM 请求和追踪。密钥会在 SDK 首次创建 OpenAI 客户端时解析(惰性初始化),因此请在第一次模型调用之前设置该环境变量。如果无法在应用启动前设置该环境变量,可以使用 [set_default_openai_key()][agents.set_default_openai_key] 函数来设置密钥。
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_key
|
||||
|
||||
set_default_openai_key("sk-...")
|
||||
```
|
||||
|
||||
或者,也可以配置要使用的 OpenAI 客户端。默认情况下,SDK 会创建一个 `AsyncOpenAI` 实例,并使用环境变量中的 API 密钥或上面设置的默认密钥。可以使用 [set_default_openai_client()][agents.set_default_openai_client] 函数更改此行为。
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
from agents import set_default_openai_client
|
||||
|
||||
custom_client = AsyncOpenAI(base_url="...", api_key="...")
|
||||
set_default_openai_client(custom_client)
|
||||
```
|
||||
|
||||
如果你偏好基于环境变量的端点配置,默认 OpenAI 提供方也会读取 `OPENAI_BASE_URL`。启用 Responses websocket 传输时,它还会读取 `OPENAI_WEBSOCKET_BASE_URL`,用于 websocket `/responses` 端点。
|
||||
|
||||
```bash
|
||||
export OPENAI_BASE_URL="https://your-openai-compatible-endpoint.example/v1"
|
||||
export OPENAI_WEBSOCKET_BASE_URL="wss://your-openai-compatible-endpoint.example/v1"
|
||||
```
|
||||
|
||||
最后,也可以自定义所使用的 OpenAI API。默认情况下,我们使用 OpenAI Responses API。可以使用 [set_default_openai_api()][agents.set_default_openai_api] 函数覆盖此设置,改用 Chat Completions API。
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_api
|
||||
|
||||
set_default_openai_api("chat_completions")
|
||||
```
|
||||
|
||||
## OpenAI 提供方默认设置
|
||||
|
||||
由 OpenAI 支持的提供方在解析模型名称时也会读取 SDK 范围的默认设置。使用 [`set_default_openai_responses_transport()`][agents.set_default_openai_responses_transport] 可让 OpenAI Responses 模型默认使用 websocket 传输:
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_responses_transport
|
||||
|
||||
set_default_openai_responses_transport("websocket")
|
||||
```
|
||||
|
||||
这会影响由默认 OpenAI 提供方解析的 OpenAI Responses 模型。有关提供方级别设置、连接复用、keepalive 选项以及自定义 websocket 端点,请参见 [Responses WebSocket 传输](models/index.md#responses-websocket-transport)。
|
||||
|
||||
如果你的 OpenAI 设置需要提供方级别的智能体注册元数据,请在启动时配置一次默认 harness ID:
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_harness
|
||||
|
||||
set_default_openai_harness("your-harness-id")
|
||||
```
|
||||
|
||||
你也可以传入完整的注册对象:
|
||||
|
||||
```python
|
||||
from agents import OpenAIAgentRegistrationConfig, set_default_openai_agent_registration
|
||||
|
||||
set_default_openai_agent_registration(
|
||||
OpenAIAgentRegistrationConfig(harness_id="your-harness-id")
|
||||
)
|
||||
```
|
||||
|
||||
如果未设置 SDK 默认值,由 OpenAI 支持的提供方会回退使用 `OPENAI_AGENT_HARNESS_ID` 环境变量。配置 harness ID 后,SDK 会将其作为 `agent_harness_id` 添加到追踪元数据中,除非 `RunConfig.trace_metadata` 中已存在该键。
|
||||
|
||||
## 追踪
|
||||
|
||||
追踪默认启用。默认情况下,它使用与上一节中的模型请求相同的 OpenAI API 密钥(即环境变量中的密钥或你设置的默认密钥)。可以使用 [`set_tracing_export_api_key`][agents.set_tracing_export_api_key] 函数专门设置用于追踪的 API 密钥。
|
||||
|
||||
```python
|
||||
from agents import set_tracing_export_api_key
|
||||
|
||||
set_tracing_export_api_key("sk-...")
|
||||
```
|
||||
|
||||
如果模型流量使用一个密钥或客户端,但追踪应使用另一个 OpenAI 密钥,请在设置默认密钥或客户端时传入 `use_for_tracing=False`,然后单独配置追踪。如果不使用自定义客户端,同样的模式也适用于 [`set_default_openai_key()`][agents.set_default_openai_key]。
|
||||
|
||||
```python
|
||||
from openai import AsyncOpenAI
|
||||
from agents import (
|
||||
set_default_openai_client,
|
||||
set_tracing_export_api_key,
|
||||
)
|
||||
|
||||
custom_client = AsyncOpenAI(base_url="https://your-openai-compatible-endpoint.example/v1", api_key="provider-key")
|
||||
set_default_openai_client(custom_client, use_for_tracing=False)
|
||||
|
||||
set_tracing_export_api_key("sk-tracing")
|
||||
```
|
||||
|
||||
如果在使用默认导出器时需要将追踪归因到特定组织或项目,请在应用启动前设置这些环境变量:
|
||||
|
||||
```bash
|
||||
export OPENAI_ORG_ID="org_..."
|
||||
export OPENAI_PROJECT_ID="proj_..."
|
||||
```
|
||||
|
||||
也可以为每次运行设置追踪 API 密钥,而不更改全局导出器。
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(tracing={"api_key": "sk-tracing-123"}),
|
||||
)
|
||||
```
|
||||
|
||||
还可以使用 [`set_tracing_disabled()`][agents.set_tracing_disabled] 函数完全禁用追踪。
|
||||
|
||||
```python
|
||||
from agents import set_tracing_disabled
|
||||
|
||||
set_tracing_disabled(True)
|
||||
```
|
||||
|
||||
如果想保持追踪启用,但从追踪载荷中排除可能敏感的输入/输出,请将 [`RunConfig.trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data] 设置为 `False`:
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(trace_include_sensitive_data=False),
|
||||
)
|
||||
```
|
||||
|
||||
也可以在应用启动前设置此环境变量,从而无需改代码即可更改默认值:
|
||||
|
||||
```bash
|
||||
export OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA=0
|
||||
```
|
||||
|
||||
有关完整的追踪控制,请参见[追踪指南](tracing.md)。
|
||||
|
||||
## 调试日志
|
||||
|
||||
SDK 定义了两个 Python 日志记录器(`openai.agents` 和 `openai.agents.tracing`),并且默认不附加处理程序。日志遵循应用的 Python 日志配置。
|
||||
|
||||
要启用详细日志,请使用 [`enable_verbose_stdout_logging()`][agents.enable_verbose_stdout_logging] 函数。
|
||||
|
||||
```python
|
||||
from agents import enable_verbose_stdout_logging
|
||||
|
||||
enable_verbose_stdout_logging()
|
||||
```
|
||||
|
||||
或者,可以通过添加处理程序、过滤器、格式化器等来自定义日志。更多信息可参见 [Python 日志指南](https://docs.python.org/3/howto/logging.html)。
|
||||
|
||||
```python
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger("openai.agents") # or openai.agents.tracing for the Tracing logger
|
||||
|
||||
# To make all logs show up
|
||||
logger.setLevel(logging.DEBUG)
|
||||
# To make info and above show up
|
||||
logger.setLevel(logging.INFO)
|
||||
# To make warning and above show up
|
||||
logger.setLevel(logging.WARNING)
|
||||
# etc
|
||||
|
||||
# You can customize this as needed, but this will output to `stderr` by default
|
||||
logger.addHandler(logging.StreamHandler())
|
||||
```
|
||||
|
||||
### 日志中的敏感数据
|
||||
|
||||
某些日志可能包含敏感数据(例如用户数据)。
|
||||
|
||||
默认情况下,SDK **不会** 记录 LLM 输入/输出或工具输入/输出。这些保护由以下设置控制:
|
||||
|
||||
```bash
|
||||
OPENAI_AGENTS_DONT_LOG_MODEL_DATA=1
|
||||
OPENAI_AGENTS_DONT_LOG_TOOL_DATA=1
|
||||
```
|
||||
|
||||
如果需要临时包含这些数据以进行调试,请在应用启动前将任一变量设置为 `0`(或 `false`):
|
||||
|
||||
```bash
|
||||
export OPENAI_AGENTS_DONT_LOG_MODEL_DATA=0
|
||||
export OPENAI_AGENTS_DONT_LOG_TOOL_DATA=0
|
||||
```
|
||||
@@ -0,0 +1,148 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 上下文管理
|
||||
|
||||
上下文是一个含义丰富的术语。你可能关心的上下文主要有两类:
|
||||
|
||||
1. 代码本地可用的上下文:这是工具函数运行时、`on_handoff` 等回调中、生命周期钩子中等可能需要的数据和依赖项。
|
||||
2. LLM 可用的上下文:这是 LLM 在生成响应时看到的数据。
|
||||
|
||||
## 本地上下文
|
||||
|
||||
这通过 [`RunContextWrapper`][agents.run_context.RunContextWrapper] 类及其中的 [`context`][agents.run_context.RunContextWrapper.context] 属性来表示。它的工作方式如下:
|
||||
|
||||
1. 你创建任意所需的 Python 对象。常见模式是使用 dataclass 或 Pydantic 对象。
|
||||
2. 你将该对象传递给各种 run 方法(例如 `Runner.run(..., context=whatever)`)。
|
||||
3. 你的所有工具调用、生命周期钩子等都会收到一个包装对象 `RunContextWrapper[T]`,其中 `T` 表示你的上下文对象类型,你可以通过 `wrapper.context` 访问它。
|
||||
|
||||
对于一些运行时特定的回调,SDK 可能会传递 `RunContextWrapper[T]` 的更专用子类。例如,工具调用生命周期钩子通常会接收 `ToolContext`,它还会公开工具调用元数据,例如 `tool_call_id`、`tool_name` 和 `tool_arguments`。
|
||||
|
||||
需要注意的**最重要**事项:对于给定的智能体运行,其每个智能体、工具函数、生命周期等都必须使用相同的上下文_类型_。
|
||||
|
||||
你可以将上下文用于以下用途:
|
||||
|
||||
- 运行所需的上下文数据(例如用户名/uid 或关于用户的其他信息)
|
||||
- 依赖项(例如日志记录器对象、数据获取器等)
|
||||
- 辅助函数
|
||||
|
||||
!!! danger "注意"
|
||||
|
||||
上下文对象**不会**发送给 LLM。它纯粹是一个本地对象,你可以从中读取、向其写入,并调用其方法。
|
||||
|
||||
在单次运行中,派生包装器共享相同的底层应用上下文、审批状态和用量跟踪。嵌套的 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 运行可能会附加不同的 `tool_input`,但默认情况下不会获得应用状态的隔离副本。
|
||||
|
||||
### `RunContextWrapper` 公开的内容
|
||||
|
||||
[`RunContextWrapper`][agents.run_context.RunContextWrapper] 是围绕你应用定义的上下文对象的包装器。实践中你最常使用的是:
|
||||
|
||||
- [`wrapper.context`][agents.run_context.RunContextWrapper.context]:用于你自己的可变应用状态和依赖项。
|
||||
- [`wrapper.usage`][agents.run_context.RunContextWrapper.usage]:用于当前运行中聚合的请求和 token 用量。
|
||||
- [`wrapper.tool_input`][agents.run_context.RunContextWrapper.tool_input]:用于当前运行在 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 内执行时的结构化输入。
|
||||
- [`wrapper.approve_tool(...)`][agents.run_context.RunContextWrapper.approve_tool] / [`wrapper.reject_tool(...)`][agents.run_context.RunContextWrapper.reject_tool]:当你需要以编程方式更新审批状态时使用。
|
||||
|
||||
只有 `wrapper.context` 是你应用定义的对象。其他字段都是由 SDK 管理的运行时元数据。
|
||||
|
||||
如果你之后为了人工介入或持久化作业工作流而序列化 [`RunState`][agents.run_state.RunState],这些运行时元数据会随状态一起保存。如果你打算持久化或传输序列化状态,请避免在 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context] 中放入密钥。
|
||||
|
||||
会话状态是另一个单独的问题。根据你希望如何延续多轮对话,可以使用 `result.to_input_list()`、`session`、`conversation_id` 或 `previous_response_id`。有关该决策,请参见[结果](results.md)、[运行智能体](running_agents.md)和[会话](sessions/index.md)。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from dataclasses import dataclass
|
||||
|
||||
from agents import Agent, RunContextWrapper, Runner, function_tool
|
||||
|
||||
@dataclass
|
||||
class UserInfo: # (1)!
|
||||
name: str
|
||||
uid: int
|
||||
|
||||
@function_tool
|
||||
async def fetch_user_age(wrapper: RunContextWrapper[UserInfo]) -> str: # (2)!
|
||||
"""Fetch the age of the user. Call this function to get user's age information."""
|
||||
return f"The user {wrapper.context.name} is 47 years old"
|
||||
|
||||
async def main():
|
||||
user_info = UserInfo(name="John", uid=123)
|
||||
|
||||
agent = Agent[UserInfo]( # (3)!
|
||||
name="Assistant",
|
||||
tools=[fetch_user_age],
|
||||
)
|
||||
|
||||
result = await Runner.run( # (4)!
|
||||
starting_agent=agent,
|
||||
input="What is the age of the user?",
|
||||
context=user_info,
|
||||
)
|
||||
|
||||
print(result.final_output) # (5)!
|
||||
# The user John is 47 years old.
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
1. 这是上下文对象。这里我们使用了 dataclass,但你可以使用任意类型。
|
||||
2. 这是一个工具。你可以看到它接收 `RunContextWrapper[UserInfo]`。工具实现会从上下文中读取信息。
|
||||
3. 我们用泛型 `UserInfo` 标记该智能体,这样类型检查器就能捕获错误(例如,如果我们尝试传入一个接收不同上下文类型的工具)。
|
||||
4. 上下文会传递给 `run` 函数。
|
||||
5. 智能体会正确调用该工具并获得年龄。
|
||||
|
||||
---
|
||||
|
||||
### 高级:`ToolContext`
|
||||
|
||||
在某些情况下,你可能希望访问有关正在执行的工具的额外元数据,例如其名称、调用 ID 或原始参数字符串。
|
||||
为此,你可以使用 [`ToolContext`][agents.tool_context.ToolContext] 类,它扩展了 `RunContextWrapper`。
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from pydantic import BaseModel, Field
|
||||
from agents import Agent, Runner, function_tool
|
||||
from agents.tool_context import ToolContext
|
||||
|
||||
class WeatherContext(BaseModel):
|
||||
user_id: str
|
||||
|
||||
class Weather(BaseModel):
|
||||
city: str = Field(description="The city name")
|
||||
temperature_range: str = Field(description="The temperature range in Celsius")
|
||||
conditions: str = Field(description="The weather conditions")
|
||||
|
||||
@function_tool
|
||||
def get_weather(ctx: ToolContext[WeatherContext], city: Annotated[str, "The city to get the weather for"]) -> Weather:
|
||||
print(f"[debug] Tool context: (name: {ctx.tool_name}, call_id: {ctx.tool_call_id}, args: {ctx.tool_arguments})")
|
||||
return Weather(city=city, temperature_range="14-20C", conditions="Sunny with wind.")
|
||||
|
||||
agent = Agent(
|
||||
name="Weather Agent",
|
||||
instructions="You are a helpful agent that can tell the weather of a given city.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
`ToolContext` 提供与 `RunContextWrapper` 相同的 `.context` 属性,
|
||||
此外还提供当前工具调用特有的额外字段:
|
||||
|
||||
- `tool_name` – 被调用工具的名称
|
||||
- `tool_call_id` – 此工具调用的唯一标识符
|
||||
- `tool_arguments` – 传递给工具的原始参数字符串
|
||||
- `tool_namespace` – 工具调用的 Responses 命名空间,当工具通过 `tool_namespace()` 或其他带命名空间的表面加载时可用
|
||||
- `qualified_tool_name` – 当有可用命名空间时,带命名空间限定的工具名称
|
||||
|
||||
当你在执行期间需要工具级元数据时,请使用 `ToolContext`。
|
||||
对于智能体与工具之间的一般上下文共享,`RunContextWrapper` 仍然足够。由于 `ToolContext` 扩展了 `RunContextWrapper`,当嵌套的 `Agent.as_tool()` 运行提供了结构化输入时,它也可以公开 `.tool_input`。
|
||||
|
||||
---
|
||||
|
||||
## 智能体/LLM 上下文
|
||||
|
||||
调用 LLM 时,它**唯一**能看到的数据来自对话历史。这意味着,如果你想让一些新数据可供 LLM 使用,就必须以能让这些数据出现在该历史中的方式来提供。有几种方式可以做到这一点:
|
||||
|
||||
1. 你可以将其添加到智能体的 `instructions` 中。这也称为“系统提示词”或“开发者消息”。系统提示词可以是静态字符串,也可以是接收上下文并输出字符串的动态函数。对于始终有用的信息(例如用户姓名或当前日期),这是一种常见策略。
|
||||
2. 在调用 `Runner.run` 函数时将其添加到 `input` 中。这类似于 `instructions` 策略,但允许你使用在[指令链](https://cdn.openai.com/spec/model-spec-2024-05-08.html#follow-the-chain-of-command)中层级较低的消息。
|
||||
3. 通过工具调用公开它。这对于_按需_上下文很有用——LLM 决定何时需要某些数据,并可以调用工具来获取这些数据。
|
||||
4. 使用检索或网络检索。这些是特殊工具,能够从文件或数据库中获取相关数据(检索),或从网络获取相关数据(网络检索)。这对于将响应“锚定”在相关上下文数据中很有用。
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 代码示例
|
||||
|
||||
请查看[代码仓库](https://github.com/openai/openai-agents-python/tree/main/examples)的 examples 部分,了解 SDK 的各种示例实现。这些示例分为多个目录,展示了不同的模式和功能。
|
||||
|
||||
## 目录
|
||||
|
||||
- **[agent_patterns](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns):**此目录中的示例展示了常见的智能体设计模式,例如
|
||||
|
||||
- 确定性工作流
|
||||
- Agents as tools
|
||||
- 具有流式事件的Agents as tools(`examples/agent_patterns/agents_as_tools_streaming.py`)
|
||||
- 具有结构化输入参数的Agents as tools(`examples/agent_patterns/agents_as_tools_structured.py`)
|
||||
- 并行执行智能体
|
||||
- 按条件使用工具
|
||||
- 以不同的行为强制使用工具(`examples/agent_patterns/forcing_tool_use.py`)
|
||||
- 输入/输出安全防护措施
|
||||
- 由 LLM 充当评判者
|
||||
- 路由
|
||||
- 流式安全防护措施
|
||||
- 采用工具审批和状态序列化的人机协同(`examples/agent_patterns/human_in_the_loop.py`)
|
||||
- 采用流式传输的人机协同(`examples/agent_patterns/human_in_the_loop_stream.py`)
|
||||
- 审批流程的自定义拒绝消息(`examples/agent_patterns/human_in_the_loop_custom_rejection.py`)
|
||||
|
||||
- **[basic](https://github.com/openai/openai-agents-python/tree/main/examples/basic):**这些示例展示了 SDK 的基础功能,例如
|
||||
|
||||
- Hello world 示例(默认模型、GPT-5、开放权重模型)
|
||||
- 智能体生命周期管理
|
||||
- 运行钩子和智能体钩子的生命周期示例(`examples/basic/lifecycle_example.py`)
|
||||
- 动态系统提示词
|
||||
- 基本工具使用方式(`examples/basic/tools.py`)
|
||||
- 工具输入/输出安全防护措施(`examples/basic/tool_guardrails.py`)
|
||||
- 图像工具输出(`examples/basic/image_tool_output.py`)
|
||||
- 流式输出(文本、项目、函数调用参数)
|
||||
- 使用跨轮次共享会话辅助程序的 Responses WebSocket 传输(`examples/basic/stream_ws.py`)
|
||||
- 提示词模板
|
||||
- 文件处理(本地和远程、图像和 PDF)
|
||||
- 使用量追踪
|
||||
- 由 Runner 管理的重试设置(`examples/basic/retry.py`)
|
||||
- 通过第三方适配器进行由 Runner 管理的重试(`examples/basic/retry_litellm.py`)
|
||||
- 非严格输出类型
|
||||
- 前一个响应 ID 的使用方式
|
||||
|
||||
- **[customer_service](https://github.com/openai/openai-agents-python/tree/main/examples/customer_service):**航空公司客户服务系统示例。
|
||||
|
||||
- **[financial_research_agent](https://github.com/openai/openai-agents-python/tree/main/examples/financial_research_agent):**金融研究智能体,展示了使用智能体和工具进行金融数据分析的结构化研究工作流。
|
||||
|
||||
- **[handoffs](https://github.com/openai/openai-agents-python/tree/main/examples/handoffs):**包含消息筛选的智能体任务转移实用示例,包括:
|
||||
|
||||
- 消息筛选器示例(`examples/handoffs/message_filter.py`)
|
||||
- 采用流式传输的消息筛选器(`examples/handoffs/message_filter_streaming.py`)
|
||||
|
||||
- **[hosted_mcp](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp):**展示如何将托管式 MCP(Model Context Protocol)与 OpenAI Responses API 配合使用的示例,包括:
|
||||
|
||||
- 无需审批的简单托管式 MCP(`examples/hosted_mcp/simple.py`)
|
||||
- Google Calendar 等 MCP 连接器(`examples/hosted_mcp/connectors.py`)
|
||||
- 采用基于中断审批的人机协同(`examples/hosted_mcp/human_in_the_loop.py`)
|
||||
- MCP 工具调用的审批时回调(`examples/hosted_mcp/on_approval.py`)
|
||||
|
||||
- **[mcp](https://github.com/openai/openai-agents-python/tree/main/examples/mcp):**了解如何使用 MCP(Model Context Protocol)构建智能体,包括:
|
||||
|
||||
- 文件系统示例
|
||||
- Git 示例
|
||||
- MCP 提示词服务示例
|
||||
- SSE(服务器发送事件)示例
|
||||
- SSE 远程服务连接(`examples/mcp/sse_remote_example`)
|
||||
- 可流式传输的 HTTP 示例
|
||||
- 可流式传输的 HTTP 远程连接(`examples/mcp/streamable_http_remote_example`)
|
||||
- 用于可流式传输 HTTP 的自定义 HTTP 客户端工厂(`examples/mcp/streamablehttp_custom_client_example`)
|
||||
- 使用 `MCPUtil.get_all_function_tools` 预取所有 MCP 工具(`examples/mcp/get_all_mcp_tools_example`)
|
||||
- 搭配 FastAPI 使用 MCPServerManager(`examples/mcp/manager_example`)
|
||||
- MCP 工具筛选(`examples/mcp/tool_filter_example`)
|
||||
|
||||
- **[memory](https://github.com/openai/openai-agents-python/tree/main/examples/memory):**不同智能体记忆实现的示例,包括:
|
||||
|
||||
- SQLite 会话存储
|
||||
- 高级 SQLite 会话存储
|
||||
- Redis 会话存储
|
||||
- SQLAlchemy 会话存储
|
||||
- Dapr 状态存储会话存储
|
||||
- 加密会话存储
|
||||
- OpenAI Conversations 会话存储
|
||||
- Responses 压缩会话存储
|
||||
- 使用 `ModelSettings(store=False)` 的无状态 Responses 压缩(`examples/memory/compaction_session_stateless_example.py`)
|
||||
- 基于文件的会话存储(`examples/memory/file_session.py`)
|
||||
- 采用人机协同的基于文件会话(`examples/memory/file_hitl_example.py`)
|
||||
- 采用人机协同的 SQLite 内存会话(`examples/memory/memory_session_hitl_example.py`)
|
||||
- 采用人机协同的 OpenAI Conversations 会话(`examples/memory/openai_session_hitl_example.py`)
|
||||
- 跨会话的 HITL 审批/拒绝场景(`examples/memory/hitl_session_scenario.py`)
|
||||
|
||||
- **[model_providers](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers):**探索如何将非 OpenAI 模型与 SDK 配合使用,包括自定义提供商和第三方适配器。
|
||||
|
||||
- **[realtime](https://github.com/openai/openai-agents-python/tree/main/examples/realtime):**展示如何使用 SDK 构建实时体验的示例,包括:
|
||||
|
||||
- 使用结构化文本和图像消息的 Web 应用模式
|
||||
- 命令行音频循环和播放处理
|
||||
- 通过 WebSocket 集成 Twilio Media Streams
|
||||
- 使用 Realtime Calls API 附加流程集成 Twilio SIP
|
||||
|
||||
- **[reasoning_content](https://github.com/openai/openai-agents-python/tree/main/examples/reasoning_content):**展示如何处理推理内容的示例,包括:
|
||||
|
||||
- 通过 Runner API 处理推理内容,包括流式和非流式方式(`examples/reasoning_content/runner_example.py`)
|
||||
- 通过 OpenRouter 使用 OSS 模型处理推理内容(`examples/reasoning_content/gpt_oss_stream.py`)
|
||||
- 基本推理内容示例(`examples/reasoning_content/main.py`)
|
||||
|
||||
- **[research_bot](https://github.com/openai/openai-agents-python/tree/main/examples/research_bot):**简单的深度研究复刻版本,展示了复杂的多智能体研究工作流。
|
||||
|
||||
- **[sandbox](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox):**在隔离工作区中运行智能体的示例,包括:
|
||||
|
||||
- 基本沙箱智能体设置(`examples/sandbox/basic.py`)
|
||||
- Unix 本地和 Docker 沙箱生命周期示例
|
||||
- 由沙箱支持的任务转移(`examples/sandbox/handoffs.py`)
|
||||
- 沙箱记忆和快照恢复(`examples/sandbox/memory.py`)
|
||||
- 作为工具公开的沙箱智能体(`examples/sandbox/sandbox_agents_as_tools.py`)
|
||||
|
||||
- **[tools](https://github.com/openai/openai-agents-python/tree/main/examples/tools):**了解如何实现由OpenAI托管的工具和实验性 Codex 工具,例如:
|
||||
|
||||
- 网络检索和带筛选条件的网络检索
|
||||
- 文件检索
|
||||
- Code interpreter
|
||||
- 具备文件编辑和审批功能的补丁应用工具(`examples/tools/apply_patch.py`)
|
||||
- 具有审批回调的 Shell 工具执行(`examples/tools/shell.py`)
|
||||
- 采用基于中断的人机协同审批的 Shell 工具(`examples/tools/shell_human_in_the_loop.py`)
|
||||
- 具有内联技能的托管容器 Shell(`examples/tools/container_shell_inline_skill.py`)
|
||||
- 具有技能引用的托管容器 Shell(`examples/tools/container_shell_skill_reference.py`)
|
||||
- 具有本地技能的本地 Shell(`examples/tools/local_shell_skill.py`)
|
||||
- 使用命名空间和延迟工具的工具搜索(`examples/tools/tool_search.py`)
|
||||
- 计算机操作
|
||||
- 图像生成
|
||||
- 实验性 Codex 工具工作流(`examples/tools/codex.py`)
|
||||
- 实验性 Codex 同线程工作流(`examples/tools/codex_same_thread.py`)
|
||||
|
||||
- **[voice](https://github.com/openai/openai-agents-python/tree/main/examples/voice):**查看使用我们的 TTS 和 STT 模型构建语音智能体的示例,包括流式语音示例。
|
||||
@@ -0,0 +1,234 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 安全防护措施
|
||||
|
||||
安全防护措施使你能够对用户输入和智能体输出进行检查和验证。例如,假设你有一个智能体使用非常智能(因而较慢/昂贵)的模型来帮助处理客户请求。你不会希望恶意用户要求模型帮他们做数学作业。因此,你可以使用快速/便宜的模型运行安全防护措施。如果安全防护措施检测到恶意使用,它可以立即引发错误,并阻止昂贵的模型运行,从而节省时间和成本(**当使用阻塞式安全防护措施时;对于并行安全防护措施,昂贵的模型可能已经在安全防护措施完成之前开始运行。详见下方“执行模式”**)。
|
||||
|
||||
安全防护措施有两种:
|
||||
|
||||
1. 输入安全防护措施会在初始用户输入上运行
|
||||
2. 输出安全防护措施会在最终智能体输出上运行
|
||||
|
||||
## 工作流边界
|
||||
|
||||
安全防护措施会附加到智能体和工具上,但它们并不会全都在工作流中的同一位置运行:
|
||||
|
||||
- **输入安全防护措施**仅对链中的第一个智能体运行。
|
||||
- **输出安全防护措施**仅对生成最终输出的智能体运行。
|
||||
- **工具安全防护措施**会在每次自定义函数工具调用时运行,其中输入安全防护措施在执行前运行,输出安全防护措施在执行后运行。
|
||||
|
||||
如果你的工作流包含管理者、任务转移或委托的专家,并且需要对每次自定义函数工具调用进行检查,请使用工具安全防护措施,而不要只依赖智能体级别的输入/输出安全防护措施。
|
||||
|
||||
## 输入安全防护措施
|
||||
|
||||
输入安全防护措施分 3 步运行:
|
||||
|
||||
1. 首先,安全防护措施会接收传递给智能体的相同输入。
|
||||
2. 接下来,安全防护措施函数会运行并生成 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput],该输出随后会包装为 [`InputGuardrailResult`][agents.guardrail.InputGuardrailResult]
|
||||
3. 最后,我们检查 [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered] 是否为 true。如果为 true,则会引发 [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered] 异常,以便你可以适当地回应用户或处理该异常。
|
||||
|
||||
!!! Note
|
||||
|
||||
输入安全防护措施旨在针对用户输入运行,因此只有当智能体是*第一个*智能体时,该智能体的安全防护措施才会运行。你可能会疑惑,为什么 `guardrails` 属性在智能体上,而不是传递给 `Runner.run`?这是因为安全防护措施往往与实际的智能体相关——你会为不同的智能体运行不同的安全防护措施,因此将代码放在一起有助于可读性。
|
||||
|
||||
### 执行模式
|
||||
|
||||
输入安全防护措施支持两种执行模式:
|
||||
|
||||
- **并行执行**(默认,`run_in_parallel=True`):安全防护措施会与智能体的执行并发运行。这能提供最佳延迟,因为二者会同时启动。不过,如果安全防护措施失败,智能体在被取消之前可能已经消耗了 token 并执行了工具。
|
||||
|
||||
- **阻塞执行**(`run_in_parallel=False`):安全防护措施会在智能体启动*之前*运行并完成。如果安全防护措施的警戒线被触发,智能体就永远不会执行,从而避免 token 消耗和工具执行。这非常适合成本优化,以及你希望避免工具调用可能产生副作用的场景。
|
||||
|
||||
## 输出安全防护措施
|
||||
|
||||
输出安全防护措施分 3 步运行:
|
||||
|
||||
1. 首先,安全防护措施会接收智能体生成的输出。
|
||||
2. 接下来,安全防护措施函数会运行并生成 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput],该输出随后会包装为 [`OutputGuardrailResult`][agents.guardrail.OutputGuardrailResult]
|
||||
3. 最后,我们检查 [`.tripwire_triggered`][agents.guardrail.GuardrailFunctionOutput.tripwire_triggered] 是否为 true。如果为 true,则会引发 [`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered] 异常,以便你可以适当地回应用户或处理该异常。
|
||||
|
||||
!!! Note
|
||||
|
||||
输出安全防护措施旨在针对最终智能体输出运行,因此只有当智能体是*最后一个*智能体时,该智能体的安全防护措施才会运行。与输入安全防护措施类似,我们这样做是因为安全防护措施往往与实际的智能体相关——你会为不同的智能体运行不同的安全防护措施,因此将代码放在一起有助于可读性。
|
||||
|
||||
输出安全防护措施总是在智能体完成后运行,因此它们不支持 `run_in_parallel` 参数。
|
||||
|
||||
## 工具安全防护措施
|
||||
|
||||
工具安全防护措施会封装**工具调用**,并允许你在执行前后验证或阻止工具调用。它们配置在工具本身上,并在每次调用该工具时运行。
|
||||
|
||||
- 输入工具安全防护措施会在工具执行前运行,可以跳过调用、用一条消息替换输出,或触发警戒线。
|
||||
- 输出工具安全防护措施会在工具执行后运行,可以替换输出或触发警戒线。
|
||||
- 如果某个函数工具需要审批,输入工具安全防护措施通常会在审批之后、执行之前立即运行。当你希望这些输入检查在发出待审批中断之前运行时,请将 [`RunConfig.tool_execution`][agents.run.RunConfig.tool_execution] 设置为 [`ToolExecutionConfig(pre_approval_tool_input_guardrails=True)`][agents.run.ToolExecutionConfig]。通过此预审批检查的调用,在审批之后、工具执行之前仍会再次接受检查。
|
||||
- 工具安全防护措施仅适用于使用 [`function_tool`][agents.tool.function_tool] 创建的函数工具。任务转移会经过 SDK 的任务转移管道,而不是常规函数工具管道,因此工具安全防护措施不适用于任务转移调用本身。托管工具(`WebSearchTool`、`FileSearchTool`、`HostedMCPTool`、`CodeInterpreterTool`、`ImageGenerationTool`)和内置执行工具(`ComputerTool`、`ShellTool`、`ApplyPatchTool`、`LocalShellTool`)也不使用此安全防护措施管道,并且 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 目前不直接暴露工具安全防护措施选项。
|
||||
|
||||
详情请参阅下面的代码片段。
|
||||
|
||||
## 警戒线
|
||||
|
||||
如果输入或输出未通过安全防护措施,安全防护措施可以通过警戒线发出信号。一旦我们发现某个安全防护措施触发了警戒线,就会立即引发 `{Input,Output}GuardrailTripwireTriggered` 异常并停止智能体执行。
|
||||
|
||||
## 安全防护措施的实现
|
||||
|
||||
你需要提供一个接收输入并返回 [`GuardrailFunctionOutput`][agents.guardrail.GuardrailFunctionOutput] 的函数。在这个示例中,我们会通过在底层运行一个智能体来实现这一点。
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from agents import (
|
||||
Agent,
|
||||
GuardrailFunctionOutput,
|
||||
InputGuardrailTripwireTriggered,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
TResponseInputItem,
|
||||
input_guardrail,
|
||||
)
|
||||
|
||||
class MathHomeworkOutput(BaseModel):
|
||||
is_math_homework: bool
|
||||
reasoning: str
|
||||
|
||||
guardrail_agent = Agent( # (1)!
|
||||
name="Guardrail check",
|
||||
instructions="Check if the user is asking you to do their math homework.",
|
||||
output_type=MathHomeworkOutput,
|
||||
)
|
||||
|
||||
|
||||
@input_guardrail
|
||||
async def math_guardrail( # (2)!
|
||||
ctx: RunContextWrapper[None], agent: Agent, input: str | list[TResponseInputItem]
|
||||
) -> GuardrailFunctionOutput:
|
||||
result = await Runner.run(guardrail_agent, input, context=ctx.context)
|
||||
|
||||
return GuardrailFunctionOutput(
|
||||
output_info=result.final_output, # (3)!
|
||||
tripwire_triggered=result.final_output.is_math_homework,
|
||||
)
|
||||
|
||||
|
||||
agent = Agent( # (4)!
|
||||
name="Customer support agent",
|
||||
instructions="You are a customer support agent. You help customers with their questions.",
|
||||
input_guardrails=[math_guardrail],
|
||||
)
|
||||
|
||||
async def main():
|
||||
# This should trip the guardrail
|
||||
try:
|
||||
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
|
||||
print("Guardrail didn't trip - this is unexpected")
|
||||
|
||||
except InputGuardrailTripwireTriggered:
|
||||
print("Math homework guardrail tripped")
|
||||
```
|
||||
|
||||
1. 我们将在安全防护措施函数中使用这个智能体。
|
||||
2. 这是安全防护措施函数,它接收智能体的输入/上下文并返回结果。
|
||||
3. 我们可以在安全防护措施结果中包含额外信息。
|
||||
4. 这是定义工作流的实际智能体。
|
||||
|
||||
输出安全防护措施类似。
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
from agents import (
|
||||
Agent,
|
||||
GuardrailFunctionOutput,
|
||||
OutputGuardrailTripwireTriggered,
|
||||
RunContextWrapper,
|
||||
Runner,
|
||||
output_guardrail,
|
||||
)
|
||||
class MessageOutput(BaseModel): # (1)!
|
||||
response: str
|
||||
|
||||
class MathOutput(BaseModel): # (2)!
|
||||
reasoning: str
|
||||
is_math: bool
|
||||
|
||||
guardrail_agent = Agent(
|
||||
name="Guardrail check",
|
||||
instructions="Check if the output includes any math.",
|
||||
output_type=MathOutput,
|
||||
)
|
||||
|
||||
@output_guardrail
|
||||
async def math_guardrail( # (3)!
|
||||
ctx: RunContextWrapper, agent: Agent, output: MessageOutput
|
||||
) -> GuardrailFunctionOutput:
|
||||
result = await Runner.run(guardrail_agent, output.response, context=ctx.context)
|
||||
|
||||
return GuardrailFunctionOutput(
|
||||
output_info=result.final_output,
|
||||
tripwire_triggered=result.final_output.is_math,
|
||||
)
|
||||
|
||||
agent = Agent( # (4)!
|
||||
name="Customer support agent",
|
||||
instructions="You are a customer support agent. You help customers with their questions.",
|
||||
output_guardrails=[math_guardrail],
|
||||
output_type=MessageOutput,
|
||||
)
|
||||
|
||||
async def main():
|
||||
# This should trip the guardrail
|
||||
try:
|
||||
await Runner.run(agent, "Hello, can you help me solve for x: 2x + 3 = 11?")
|
||||
print("Guardrail didn't trip - this is unexpected")
|
||||
|
||||
except OutputGuardrailTripwireTriggered:
|
||||
print("Math output guardrail tripped")
|
||||
```
|
||||
|
||||
1. 这是实际智能体的输出类型。
|
||||
2. 这是安全防护措施的输出类型。
|
||||
3. 这是安全防护措施函数,它接收智能体的输出并返回结果。
|
||||
4. 这是定义工作流的实际智能体。
|
||||
|
||||
最后,以下是工具安全防护措施的示例。
|
||||
|
||||
```python
|
||||
import json
|
||||
from agents import (
|
||||
Agent,
|
||||
Runner,
|
||||
ToolGuardrailFunctionOutput,
|
||||
function_tool,
|
||||
tool_input_guardrail,
|
||||
tool_output_guardrail,
|
||||
)
|
||||
|
||||
@tool_input_guardrail
|
||||
def block_secrets(data):
|
||||
args = json.loads(data.context.tool_arguments or "{}")
|
||||
if "sk-" in json.dumps(args):
|
||||
return ToolGuardrailFunctionOutput.reject_content(
|
||||
"Remove secrets before calling this tool."
|
||||
)
|
||||
return ToolGuardrailFunctionOutput.allow()
|
||||
|
||||
|
||||
@tool_output_guardrail
|
||||
def redact_output(data):
|
||||
text = str(data.output or "")
|
||||
if "sk-" in text:
|
||||
return ToolGuardrailFunctionOutput.reject_content("Output contained sensitive data.")
|
||||
return ToolGuardrailFunctionOutput.allow()
|
||||
|
||||
|
||||
@function_tool(
|
||||
tool_input_guardrails=[block_secrets],
|
||||
tool_output_guardrails=[redact_output],
|
||||
)
|
||||
def classify_text(text: str) -> str:
|
||||
"""Classify text for internal routing."""
|
||||
return f"length:{len(text)}"
|
||||
|
||||
|
||||
agent = Agent(name="Classifier", tools=[classify_text])
|
||||
result = Runner.run_sync(agent, "hello world")
|
||||
print(result.final_output)
|
||||
```
|
||||
@@ -0,0 +1,156 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 任务转移
|
||||
|
||||
任务转移允许一个智能体将任务委派给另一个智能体。这在不同智能体专精于不同领域的场景中特别有用。例如,一个客户支持应用可能有多个智能体,分别专门处理订单状态、退款、常见问题等任务。
|
||||
|
||||
对 LLM 而言,任务转移表示为工具。因此,如果有一个任务转移到名为 `Refund Agent` 的智能体,对应的工具会被命名为 `transfer_to_refund_agent`。
|
||||
|
||||
## 任务转移的创建
|
||||
|
||||
所有智能体都有一个 [`handoffs`][agents.agent.Agent.handoffs] 参数,它既可以直接接收一个 `Agent`,也可以接收一个用于自定义任务转移的 `Handoff` 对象。
|
||||
|
||||
如果传入普通的 `Agent` 实例,它们的 [`handoff_description`][agents.agent.Agent.handoff_description](如果已设置)会附加到默认工具描述之后。可用它来提示模型何时应选择该任务转移,而无需编写完整的 `handoff()` 对象。
|
||||
|
||||
你可以使用 Agents SDK 提供的 [`handoff()`][agents.handoffs.handoff] 函数来创建任务转移。该函数允许你指定要转移到的智能体,并可选择指定覆盖项和输入过滤器。
|
||||
|
||||
### 基本用法
|
||||
|
||||
下面是创建一个简单任务转移的方法:
|
||||
|
||||
```python
|
||||
from agents import Agent, handoff
|
||||
|
||||
billing_agent = Agent(name="Billing agent")
|
||||
refund_agent = Agent(name="Refund agent")
|
||||
|
||||
# (1)!
|
||||
triage_agent = Agent(name="Triage agent", handoffs=[billing_agent, handoff(refund_agent)])
|
||||
```
|
||||
|
||||
1. 你可以直接使用智能体(如 `billing_agent`),也可以使用 `handoff()` 函数。
|
||||
|
||||
### 通过 `handoff()` 函数进行的任务转移自定义
|
||||
|
||||
[`handoff()`][agents.handoffs.handoff] 函数允许你自定义相关内容。
|
||||
|
||||
- `agent`: 这是任务将被转移到的智能体。
|
||||
- `tool_name_override`: 默认情况下会使用 `Handoff.default_tool_name()` 函数,它会解析为 `transfer_to_<agent_name>`。你可以覆盖它。
|
||||
- `tool_description_override`: 覆盖来自 `Handoff.default_tool_description()` 的默认工具描述
|
||||
- `on_handoff`: 在任务转移被调用时执行的回调函数。这对于在确认任务转移被调用后立即启动某些数据获取等操作很有用。该函数会接收智能体上下文,也可以选择接收 LLM 生成的输入。输入数据由 `input_type` 参数控制。
|
||||
- `input_type`: 任务转移工具调用参数的 schema。设置后,解析后的负载会传递给 `on_handoff`。
|
||||
- `input_filter`: 它允许你过滤下一个智能体接收到的输入。更多信息见下文。
|
||||
- `is_enabled`: 任务转移是否启用。它可以是一个布尔值,也可以是返回布尔值的函数,从而允许你在运行时动态启用或禁用任务转移。
|
||||
- `nest_handoff_history`: 对 RunConfig 级别 `nest_handoff_history` 设置的可选单次调用覆盖。如果为 `None`,则改用当前活动运行配置中定义的值。
|
||||
|
||||
[`handoff()`][agents.handoffs.handoff] 辅助函数始终会将控制权转移给你传入的特定 `agent`。如果有多个可能的目标,请为每个目标注册一个任务转移,并让模型在它们之间选择。仅当你自己的任务转移代码必须在调用时决定返回哪个智能体时,才使用自定义 [`Handoff`][agents.handoffs.Handoff]。
|
||||
|
||||
```python
|
||||
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”的任务转移。你可能希望模型提供一个原因,以便你记录它。
|
||||
|
||||
```python
|
||||
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_type` 描述任务转移工具调用本身的参数。SDK 会将该 schema 作为任务转移工具的 `parameters` 暴露给模型,在本地验证返回的 JSON,并将解析后的值传递给 `on_handoff`。
|
||||
|
||||
它不会替换下一个智能体的主输入,也不会选择不同的目标。[`handoff()`][agents.handoffs.handoff] 辅助函数仍然会转移到你包装的特定智能体,并且接收方智能体仍会看到对话历史,除非你通过 [`input_filter`][agents.handoffs.Handoff.input_filter] 或嵌套任务转移历史设置对其进行更改。
|
||||
|
||||
`input_type` 也与 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context] 分离。请将 `input_type` 用于模型在任务转移时决定的元数据,而不是用于你本地已有的应用状态或依赖项。
|
||||
|
||||
### `input_type` 的使用场景
|
||||
|
||||
当任务转移需要一小段模型生成的元数据时,请使用 `input_type`,例如 `reason`、`language`、`priority` 或 `summary`。例如,分诊智能体可以通过 `{ "reason": "duplicate_charge", "priority": "high" }` 转移给退款智能体,`on_handoff` 可以在退款智能体接管之前记录或持久化这些元数据。
|
||||
|
||||
当目标不同时,请选择其他机制:
|
||||
|
||||
- 将现有的应用状态和依赖项放入 [`RunContextWrapper.context`][agents.run_context.RunContextWrapper.context]。参见[上下文指南](context.md)。
|
||||
- 如果你想更改接收方智能体看到的历史,请使用 [`input_filter`][agents.handoffs.Handoff.input_filter]、[`RunConfig.nest_handoff_history`][agents.run.RunConfig.nest_handoff_history] 或 [`RunConfig.handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]。
|
||||
- 如果有多个可能的专家智能体,请为每个目标注册一个任务转移。`input_type` 可以向所选任务转移添加元数据,但不会在不同目标之间进行分派。
|
||||
- 如果你希望为嵌套专家提供结构化输入而不转移对话,请优先使用 [`Agent.as_tool(parameters=...)`][agents.agent.Agent.as_tool]。参见[工具](tools.md#structured-input-for-tool-agents)。
|
||||
|
||||
## 输入过滤器
|
||||
|
||||
当发生任务转移时,就像新的智能体接管了对话,并且能够看到之前的完整对话历史。如果你想更改这一点,可以设置 [`input_filter`][agents.handoffs.Handoff.input_filter]。输入过滤器是一个函数,它通过 [`HandoffInputData`][agents.handoffs.HandoffInputData] 接收现有输入,并且必须返回一个新的 `HandoffInputData`。
|
||||
|
||||
[`HandoffInputData`][agents.handoffs.HandoffInputData] 包括:
|
||||
|
||||
- `input_history`: `Runner.run(...)` 启动前的输入历史。
|
||||
- `pre_handoff_items`: 在调用任务转移的智能体轮次之前生成的项目。
|
||||
- `new_items`: 当前轮次期间生成的项目,包括任务转移调用和任务转移输出项目。
|
||||
- `input_items`: 可选项目,用于转发给下一个智能体以替代 `new_items`,允许你过滤模型输入,同时保持 `new_items` 完整以用于会话历史。
|
||||
- `run_context`: 调用任务转移时处于活动状态的 [`RunContextWrapper`][agents.run_context.RunContextWrapper]。
|
||||
|
||||
嵌套任务转移作为可选择启用的 beta 功能提供,在我们稳定它们之前默认处于禁用状态。当你启用 [`RunConfig.nest_handoff_history`][agents.run.RunConfig.nest_handoff_history] 时,运行器会将先前的转录折叠为一条助手摘要消息,并将其包装在 `<CONVERSATION HISTORY>` 块中;当同一次运行中发生多次任务转移时,该块会持续追加新的轮次。你可以通过 [`RunConfig.handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper] 提供自己的映射函数,以替换生成的消息,而无需编写完整的 `input_filter`。只有当任务转移和运行都没有提供显式 `input_filter` 时,该选择启用项才会生效,因此已经自定义负载的现有代码(包括此仓库中的代码示例)会保持当前行为而无需更改。你可以通过向 [`handoff(...)`][agents.handoffs.handoff] 传递 `nest_handoff_history=True` 或 `False` 来覆盖单个任务转移的嵌套行为,这会设置 [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history]。如果你只需要更改生成摘要的包装文本,请在运行智能体之前调用 [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers](并可选择调用 [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers])。
|
||||
|
||||
如果任务转移和活动的 [`RunConfig.handoff_input_filter`][agents.run.RunConfig.handoff_input_filter] 都定义了过滤器,则对于该特定任务转移,逐任务转移的 [`input_filter`][agents.handoffs.Handoff.input_filter] 优先。
|
||||
|
||||
!!! note
|
||||
|
||||
任务转移会保持在单次运行内。输入安全防护措施仍然只应用于链中的第一个智能体,输出安全防护措施只应用于生成最终输出的智能体。当你需要围绕工作流中每个自定义函数工具调用进行检查时,请使用工具安全防护措施。
|
||||
|
||||
有一些常见模式(例如从历史中移除所有工具调用)已经在 [`agents.extensions.handoff_filters`][] 中为你实现。
|
||||
|
||||
```python
|
||||
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, # (1)!
|
||||
)
|
||||
```
|
||||
|
||||
1. 当调用 `FAQ agent` 时,这会自动从历史中移除所有工具。
|
||||
|
||||
## 推荐提示词
|
||||
|
||||
为确保 LLM 正确理解任务转移,我们建议在你的智能体中包含有关任务转移的信息。我们在 [`agents.extensions.handoff_prompt.RECOMMENDED_PROMPT_PREFIX`][] 中提供了建议的前缀,或者你可以调用 [`agents.extensions.handoff_prompt.prompt_with_handoff_instructions`][] 来自动将推荐数据添加到你的提示词中。
|
||||
|
||||
```python
|
||||
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>.""",
|
||||
)
|
||||
```
|
||||
@@ -0,0 +1,201 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 人在环路
|
||||
|
||||
使用人在环路(HITL)流程暂停智能体执行,直到有人批准或拒绝敏感的工具调用。工具会声明自身何时需要审批,运行结果会以中断的形式呈现待处理审批,而 `RunState` 可让你在做出决策后序列化并恢复运行。
|
||||
|
||||
该审批入口覆盖整个运行,而不局限于当前顶层智能体。无论工具属于当前智能体、通过任务转移到达的智能体,还是嵌套的 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 执行,均适用同一模式。在嵌套 `Agent.as_tool()` 的情况下,中断仍会在外层运行中呈现,因此你需要在外层 `RunState` 上批准或拒绝它,并恢复原始的顶层运行。
|
||||
|
||||
使用 `Agent.as_tool()` 时,审批可能发生在两个不同层级:智能体工具本身可以通过 `Agent.as_tool(..., needs_approval=...)` 要求审批,而嵌套智能体内部的工具也可以在嵌套运行开始后再发起自己的审批。两者都通过同一个外层运行中断流程处理。
|
||||
|
||||
本页重点介绍通过 `interruptions` 进行的手动审批流程。如果你的应用能够在代码中做出决策,某些工具类型也支持程序化审批回调,使运行无需暂停即可继续。
|
||||
|
||||
## 需审批工具的标记
|
||||
|
||||
将 `needs_approval` 设置为 `True` 可始终要求审批,或提供一个异步函数按每次调用做出决定。该可调用对象会接收运行上下文、解析后的工具参数以及工具调用 ID。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool(needs_approval=True)
|
||||
async def cancel_order(order_id: int) -> str:
|
||||
return f"Cancelled order {order_id}"
|
||||
|
||||
|
||||
async def requires_review(_ctx, params, _call_id) -> bool:
|
||||
return "refund" in params.get("subject", "").lower()
|
||||
|
||||
|
||||
@function_tool(needs_approval=requires_review)
|
||||
async def send_email(subject: str, body: str) -> str:
|
||||
return f"Sent '{subject}'"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Support agent",
|
||||
instructions="Handle tickets and ask for approval when needed.",
|
||||
tools=[cancel_order, send_email],
|
||||
)
|
||||
```
|
||||
|
||||
`needs_approval` 可用于 [`function_tool`][agents.tool.function_tool]、[`Agent.as_tool`][agents.agent.Agent.as_tool]、[`ShellTool`][agents.tool.ShellTool] 和 [`ApplyPatchTool`][agents.tool.ApplyPatchTool]。本地 MCP 服务也支持通过 [`MCPServerStdio`][agents.mcp.server.MCPServerStdio]、[`MCPServerSse`][agents.mcp.server.MCPServerSse] 和 [`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp] 上的 `require_approval` 进行审批。托管 MCP 服务通过 [`HostedMCPTool`][agents.tool.HostedMCPTool] 支持审批,可配合 `tool_config={"require_approval": "always"}` 以及可选的 `on_approval_request` 回调使用。如果你想在不呈现中断的情况下自动批准或自动拒绝,Shell 和 apply_patch 工具可接受 `on_approval` 回调。
|
||||
|
||||
## 审批流程机制
|
||||
|
||||
1. 当模型发出工具调用时,运行器会评估其审批规则(`needs_approval`、`require_approval` 或托管 MCP 的等价机制)。
|
||||
2. 如果该工具调用的审批决策已经存储在 [`RunContextWrapper`][agents.run_context.RunContextWrapper] 中,运行器会无需提示而继续。逐调用审批的作用域限定在特定调用 ID;传入 `always_approve=True` 或 `always_reject=True` 可在该运行剩余期间,对该工具未来的调用持久化相同决策。
|
||||
3. 否则,执行会暂停,并且 `RunResult.interruptions`(或 `RunResultStreaming.interruptions`)会包含 [`ToolApprovalItem`][agents.items.ToolApprovalItem] 条目,其中包含 `agent.name`、`tool_name` 和 `arguments` 等详细信息。这也包括任务转移后或嵌套 `Agent.as_tool()` 执行中发起的审批。
|
||||
4. 使用 `result.to_state()` 将结果转换为 `RunState`,调用 `state.approve(...)` 或 `state.reject(...)`,然后使用 `Runner.run(agent, state)` 或 `Runner.run_streamed(agent, state)` 恢复,其中 `agent` 是该运行的原始顶层智能体。
|
||||
5. 恢复后的运行会从暂停处继续,并会在需要新的审批时重新进入此流程。
|
||||
|
||||
使用 `always_approve=True` 或 `always_reject=True` 创建的持久决策会存储在运行状态中,因此当你稍后恢复同一个已暂停运行时,它们会在 `state.to_string()` / `RunState.from_string(...)` 和 `state.to_json()` / `RunState.from_json(...)` 的序列化/反序列化之后仍然有效。
|
||||
|
||||
你不需要在同一轮处理里解决所有待处理审批。`interruptions` 可以包含普通工具调用、托管 MCP 审批以及嵌套 `Agent.as_tool()` 审批的混合项。如果你只批准或拒绝其中一部分条目后再次运行,已处理的调用可以继续,而未处理的调用会继续留在 `interruptions` 中并使运行再次暂停。
|
||||
|
||||
## 自定义拒绝消息
|
||||
|
||||
默认情况下,被拒绝的工具调用会将 SDK 的标准拒绝文本返回到运行中。你可以在两个层级自定义该消息:
|
||||
|
||||
- 运行范围回退:设置 [`RunConfig.tool_error_formatter`][agents.run.RunConfig.tool_error_formatter],以控制整个运行中审批拒绝时默认对模型可见的消息。
|
||||
- 逐调用覆盖:当你希望某个特定被拒绝的工具调用呈现不同消息时,向 `state.reject(...)` 传入 `rejection_message=...`。
|
||||
|
||||
如果两者都提供,逐调用的 `rejection_message` 优先于运行范围格式化器。
|
||||
|
||||
```python
|
||||
from agents import RunConfig, ToolErrorFormatterArgs
|
||||
|
||||
|
||||
def format_rejection(args: ToolErrorFormatterArgs[None]) -> str | None:
|
||||
if args.kind != "approval_rejected":
|
||||
return None
|
||||
return "Publish action was canceled because approval was rejected."
|
||||
|
||||
|
||||
run_config = RunConfig(tool_error_formatter=format_rejection)
|
||||
|
||||
# Later, while resolving a specific interruption:
|
||||
state.reject(
|
||||
interruption,
|
||||
rejection_message="Publish action was canceled because the reviewer denied approval.",
|
||||
)
|
||||
```
|
||||
|
||||
请参阅 [`examples/agent_patterns/human_in_the_loop_custom_rejection.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/human_in_the_loop_custom_rejection.py),其中提供了同时展示这两个层级的完整示例。
|
||||
|
||||
## 自动审批决策
|
||||
|
||||
手动 `interruptions` 是最通用的模式,但并不是唯一模式:
|
||||
|
||||
- 本地 [`ShellTool`][agents.tool.ShellTool] 和 [`ApplyPatchTool`][agents.tool.ApplyPatchTool] 可以使用 `on_approval` 在代码中立即批准或拒绝。
|
||||
- [`HostedMCPTool`][agents.tool.HostedMCPTool] 可以将 `tool_config={"require_approval": "always"}` 与 `on_approval_request` 结合使用,以实现同类程序化决策。
|
||||
- 普通 [`function_tool`][agents.tool.function_tool] 工具和 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 使用本页的手动中断流程。
|
||||
|
||||
当这些回调返回决策时,运行会继续,而不会暂停等待人工响应。对于 Realtime 和语音会话 API,请参阅 [Realtime 指南](realtime/guide.md) 中的审批流程。
|
||||
|
||||
## 流式传输与会话
|
||||
|
||||
同一个中断流程也适用于流式传输运行。流式运行暂停后,持续消费 [`RunResultStreaming.stream_events()`][agents.result.RunResultStreaming.stream_events],直到迭代器结束,检查 [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions],处理它们,并在你希望恢复后的输出继续流式传输时使用 [`Runner.run_streamed(...)`][agents.run.Runner.run_streamed] 恢复。请参阅 [流式传输](streaming.md),了解此模式的流式版本。
|
||||
|
||||
如果你还使用会话,在从 `RunState` 恢复时请继续传入同一个会话实例,或传入另一个指向同一后端存储的会话对象。恢复后的轮次会追加到同一份已存储的对话历史中。有关会话生命周期的详细信息,请参阅 [会话](sessions/index.md)。
|
||||
|
||||
## 示例:暂停、批准与恢复
|
||||
|
||||
下面的代码片段与 JavaScript HITL 指南相对应:它会在工具需要审批时暂停,将状态持久化到磁盘,重新加载它,并在收集决策后恢复。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
from agents import Agent, Runner, RunState, function_tool
|
||||
|
||||
|
||||
async def needs_oakland_approval(_ctx, params, _call_id) -> bool:
|
||||
return "Oakland" in params.get("city", "")
|
||||
|
||||
|
||||
@function_tool(needs_approval=needs_oakland_approval)
|
||||
async def get_temperature(city: str) -> str:
|
||||
return f"The temperature in {city} is 20° Celsius"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Weather assistant",
|
||||
instructions="Answer weather questions with the provided tools.",
|
||||
tools=[get_temperature],
|
||||
)
|
||||
|
||||
STATE_PATH = Path(".cache/hitl_state.json")
|
||||
|
||||
|
||||
def prompt_approval(tool_name: str, arguments: str | None) -> bool:
|
||||
answer = input(f"Approve {tool_name} with {arguments}? [y/N]: ").strip().lower()
|
||||
return answer in {"y", "yes"}
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
result = await Runner.run(agent, "What is the temperature in Oakland?")
|
||||
|
||||
while result.interruptions:
|
||||
# Persist the paused state.
|
||||
state = result.to_state()
|
||||
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
STATE_PATH.write_text(state.to_string())
|
||||
|
||||
# Load the state later (could be a different process).
|
||||
stored = json.loads(STATE_PATH.read_text())
|
||||
state = await RunState.from_json(agent, stored)
|
||||
|
||||
for interruption in result.interruptions:
|
||||
approved = await asyncio.get_running_loop().run_in_executor(
|
||||
None, prompt_approval, interruption.name or "unknown_tool", interruption.arguments
|
||||
)
|
||||
if approved:
|
||||
state.approve(interruption, always_approve=False)
|
||||
else:
|
||||
state.reject(interruption)
|
||||
|
||||
result = await Runner.run(agent, state)
|
||||
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
在此示例中,`prompt_approval` 是同步的,因为它使用 `input()`,并通过 `run_in_executor(...)` 执行。如果你的审批来源本身已经是异步的(例如 HTTP 请求或异步数据库查询),则可以改用 `async def` 函数并直接 `await` 它。
|
||||
|
||||
若要在等待审批期间流式传输输出,请调用 `Runner.run_streamed`,消费 `result.stream_events()` 直到完成,然后按照上面展示的相同 `result.to_state()` 和恢复步骤操作。
|
||||
|
||||
## 仓库模式与代码示例
|
||||
|
||||
- **流式传输审批**: `examples/agent_patterns/human_in_the_loop_stream.py` 展示如何消费完 `stream_events()`,然后在使用 `Runner.run_streamed(agent, state)` 恢复之前批准待处理的工具调用。
|
||||
- **自定义拒绝文本**: `examples/agent_patterns/human_in_the_loop_custom_rejection.py` 展示在审批被拒绝时,如何将运行级别的 `tool_error_formatter` 与逐调用的 `rejection_message` 覆盖结合使用。
|
||||
- **作为工具的智能体审批**: 当委派的智能体任务需要审核时,`Agent.as_tool(..., needs_approval=...)` 会应用同一个中断流程。嵌套中断仍会在外层运行中呈现,因此应恢复原始顶层智能体,而不是嵌套智能体。
|
||||
- **本地 shell 和 apply_patch 工具**: `ShellTool` 和 `ApplyPatchTool` 也支持 `needs_approval`。使用 `state.approve(interruption, always_approve=True)` 或 `state.reject(..., always_reject=True)` 缓存该决策以供未来调用使用。对于自动决策,请提供 `on_approval`(见 `examples/tools/shell.py`);对于手动决策,请处理中断(见 `examples/tools/shell_human_in_the_loop.py`)。托管 shell 环境不支持 `needs_approval` 或 `on_approval`;请参阅[工具指南](tools.md)。
|
||||
- **本地 MCP 服务**: 使用 `MCPServerStdio` / `MCPServerSse` / `MCPServerStreamableHttp` 上的 `require_approval` 为 MCP 工具调用设置审批门禁(见 `examples/mcp/get_all_mcp_tools_example/main.py` 和 `examples/mcp/tool_filter_example/main.py`)。
|
||||
- **托管 MCP 服务**: 在 `HostedMCPTool` 上将 `require_approval` 设置为 `"always"` 以强制使用 HITL,并可选择提供 `on_approval_request` 来自动批准或拒绝(见 `examples/hosted_mcp/human_in_the_loop.py` 和 `examples/hosted_mcp/on_approval.py`)。对受信任的服务使用 `"never"`(`examples/hosted_mcp/simple.py`)。
|
||||
- **会话与记忆**: 向 `Runner.run` 传入会话,使审批和对话历史能够跨多轮保留。SQLite 和 OpenAI Conversations 会话变体位于 `examples/memory/memory_session_hitl_example.py` 和 `examples/memory/openai_session_hitl_example.py`。
|
||||
- **Realtime 智能体**: Realtime 演示公开了 WebSocket 消息,可在 `RealtimeSession` 上通过 `approve_tool_call` / `reject_tool_call` 批准或拒绝工具调用(服务端处理程序见 `examples/realtime/app/server.py`,API 接口见 [Realtime 指南](realtime/guide.md#tool-approvals))。
|
||||
|
||||
## 长时间审批
|
||||
|
||||
`RunState` 被设计为可持久化。使用 `state.to_json()` 或 `state.to_string()` 将待处理工作存储在数据库或队列中,并稍后使用 `RunState.from_json(...)` 或 `RunState.from_string(...)` 重新创建它。
|
||||
|
||||
有用的序列化选项:
|
||||
|
||||
- `context_serializer`:自定义非映射上下文对象的序列化方式。
|
||||
- `context_deserializer`:在使用 `RunState.from_json(...)` 或 `RunState.from_string(...)` 加载状态时,重建非映射上下文对象。
|
||||
- `strict_context=True`:除非上下文本身已经是映射,或你提供了相应的序列化器/反序列化器,否则序列化或反序列化会失败。
|
||||
- `context_override`:加载状态时替换已序列化的上下文。这在你不想还原原始上下文对象时很有用,但它不会从已经序列化的载荷中移除该上下文。
|
||||
- `include_tracing_api_key=True`:当你需要恢复后的工作继续使用相同凭据导出追踪时,在序列化的追踪载荷中包含追踪 API 密钥。
|
||||
|
||||
序列化后的运行状态包括你的应用上下文,以及 SDK 管理的运行时元数据,例如审批、用量、序列化的 `tool_input`、嵌套的智能体作为工具的恢复信息、追踪元数据,以及由服务管理的对话设置。如果你计划存储或传输序列化状态,请将 `RunContextWrapper.context` 视为持久化数据,并避免在那里放置密钥等敏感信息,除非你有意让它们随状态一起传递。
|
||||
|
||||
## 待处理任务的版本控制
|
||||
|
||||
如果审批可能搁置一段时间,请在序列化状态旁同时存储智能体定义或 SDK 的版本标记。然后,你可以将反序列化路由到匹配的代码路径,以避免模型、提示词或工具定义发生变化时的不兼容。
|
||||
@@ -0,0 +1,101 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# OpenAI Agents SDK
|
||||
|
||||
[OpenAI Agents SDK](https://github.com/openai/openai-agents-python)让你能够使用轻量、易用且抽象很少的包来构建智能体式 AI 应用。它是我们此前智能体实验项目[Swarm](https://github.com/openai/swarm/tree/main)的生产就绪升级版。Agents SDK包含一组非常小的基本组件:
|
||||
|
||||
- **智能体**,即配备指令和工具的 LLM
|
||||
- **Agents as tools / 任务转移**,允许智能体将特定任务委派给其他智能体
|
||||
- **安全防护措施**,支持对智能体输入和输出进行验证
|
||||
|
||||
结合 Python,这些基本组件足以表达工具与智能体之间的复杂关系,并让你无需陡峭的学习曲线即可构建真实世界的应用。此外,SDK 内置**追踪**,可用于可视化和调试你的智能体式流程,也可用于评估这些流程,甚至为你的应用微调模型。
|
||||
|
||||
## 使用 Agents SDK 的理由
|
||||
|
||||
SDK 有两个核心设计原则:
|
||||
|
||||
1. 功能足够实用,但基本组件足够少,便于快速学习。
|
||||
2. 开箱即用,同时可以精确自定义运行方式。
|
||||
|
||||
以下是 SDK 的主要功能:
|
||||
|
||||
- **智能体循环**:内置智能体循环,可处理工具调用、将结果发送回 LLM,并持续运行直到任务完成。
|
||||
- **Python 优先**:使用内置语言特性来编排和串联智能体,而无需学习新的抽象。
|
||||
- **Agents as tools / 任务转移**:一种强大的机制,用于在多个智能体之间协调和委派工作。
|
||||
- **沙盒智能体**:在真实隔离工作区中运行专家智能体,支持由清单定义的文件、沙盒客户端选择以及可恢复的沙盒会话。
|
||||
- **安全防护措施**:与智能体执行并行运行输入验证和安全检查,并在检查未通过时快速失败。
|
||||
- **工具调用**:将任何 Python 函数转换为工具,自动生成 schema,并通过 Pydantic 进行验证。
|
||||
- **MCP 服务工具调用**:内置 MCP 服务工具集成,其工作方式与工具调用相同。
|
||||
- **会话**:用于在智能体循环中维护工作上下文的持久记忆层。
|
||||
- **人在回路**:内置机制,用于在智能体运行过程中引入人工参与。
|
||||
- **追踪**:内置追踪,用于可视化、调试和监控工作流,并支持 OpenAI 的评估、微调和蒸馏工具套件。
|
||||
- **实时智能体**:使用 `gpt-realtime-2.1`、自动中断检测、上下文管理、安全防护措施等构建强大的语音智能体。
|
||||
|
||||
## Agents SDK 与 Responses API 的选择
|
||||
|
||||
SDK 默认将 Responses API 用于 OpenAI模型,但它在模型调用之上增加了更高层的运行时。
|
||||
|
||||
在以下情况下直接使用 Responses API:
|
||||
|
||||
- 你希望自行掌控循环、工具分派和状态处理
|
||||
- 你的工作流生命周期较短,且主要目标是返回模型响应
|
||||
|
||||
在以下情况下使用 Agents SDK:
|
||||
|
||||
- 你希望由运行时管理轮次、工具执行、安全防护措施、任务转移或会话
|
||||
- 你的智能体需要生成产物,或跨多个协调步骤运行
|
||||
- 你需要通过[沙盒智能体](sandbox_agents.md)获得真实工作区或可恢复执行
|
||||
|
||||
你不必在全局范围内二选一。许多应用会使用 SDK 处理托管工作流,并在较底层路径中直接调用 Responses API。
|
||||
|
||||
## 安装
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
## Hello world 示例
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
agent = Agent(name="Assistant", instructions="You are a helpful assistant")
|
||||
|
||||
result = Runner.run_sync(agent, "Write a haiku about recursion in programming.")
|
||||
print(result.final_output)
|
||||
|
||||
# Code within the code,
|
||||
# Functions calling themselves,
|
||||
# Infinite loop's dance.
|
||||
```
|
||||
|
||||
_(如果运行此示例,请确保设置 `OPENAI_API_KEY` 环境变量)_
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
## 入门起点
|
||||
|
||||
- 通过[快速入门](quickstart.md)构建你的第一个基于文本的智能体。
|
||||
- 然后在[运行智能体](running_agents.md#choose-a-memory-strategy)中决定如何在多个轮次之间保留状态。
|
||||
- 如果任务依赖真实文件、代码仓库或每个智能体隔离的工作区状态,请阅读[沙盒智能体快速入门](sandbox_agents.md)。
|
||||
- 如果你正在任务转移和管理器式编排之间做选择,请阅读[智能体编排](multi_agent.md)。
|
||||
|
||||
## 路径选择
|
||||
|
||||
当你知道想完成的工作,但不知道应查看哪个页面时,请使用此表。
|
||||
|
||||
| 目标 | 从这里开始 |
|
||||
| --- | --- |
|
||||
| 构建第一个文本智能体,并查看一次完整运行 | [快速入门](quickstart.md) |
|
||||
| 添加工具调用、托管工具或 Agents as tools | [工具](tools.md) |
|
||||
| 在真实隔离工作区中运行编码、审查或文档智能体 | [沙盒智能体快速入门](sandbox_agents.md)和[沙盒客户端](sandbox/clients.md) |
|
||||
| 在任务转移和管理器式编排之间做选择 | [智能体编排](multi_agent.md) |
|
||||
| 在多个轮次之间保留记忆 | [运行智能体](running_agents.md#choose-a-memory-strategy)和[会话](sessions/index.md) |
|
||||
| 使用 OpenAI模型、websocket 传输或非 OpenAI提供商 | [模型](models/index.md) |
|
||||
| 查看输出、运行项、中断和恢复状态 | [结果](results.md) |
|
||||
| 使用 `gpt-realtime-2.1` 构建低延迟语音智能体 | [实时智能体快速入门](realtime/quickstart.md)和[实时传输](realtime/transport.md) |
|
||||
| 构建语音转文本 / 智能体 / 文本转语音流水线 | [语音流水线快速入门](voice/quickstart.md) |
|
||||
+467
@@ -0,0 +1,467 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# Model context protocol (MCP)
|
||||
|
||||
[Model context protocol](https://modelcontextprotocol.io/introduction)(MCP)标准化了应用如何向语言模型公开工具和
|
||||
上下文。来自官方文档:
|
||||
|
||||
> MCP是一种开放协议,标准化了应用向LLM提供上下文的方式。可以把MCP想象成AI
|
||||
> 应用的USB-C端口。正如USB-C提供了一种标准化方式,用于将你的设备连接到各种外设和配件,MCP
|
||||
> 也提供了一种标准化方式,用于将AI模型连接到不同的数据源和工具。
|
||||
|
||||
Agents Python SDK支持多种MCP传输方式。这使你可以复用现有的MCP服务,或构建自己的服务,向智能体公开由文件系统、HTTP或连接器支持的工具。
|
||||
|
||||
## MCP集成方案选择
|
||||
|
||||
在将MCP服务接入智能体之前,请先决定工具调用应在哪里执行,以及你可以访问哪些传输方式。下表总结了Python SDK支持的选项。
|
||||
|
||||
| 你的需求 | 推荐选项 |
|
||||
| ------------------------------------------------------------------------------------ | ----------------------------------------------------- |
|
||||
| 让OpenAI的Responses API代表模型调用可公开访问的MCP服务| 通过[`HostedMCPTool`][agents.tool.HostedMCPTool]使用**托管MCP服务工具** |
|
||||
| 连接到你在本地或远程运行的Streamable HTTP服务 | 通过[`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]使用**Streamable HTTP MCP服务** |
|
||||
| 与实现HTTP with Server-Sent Events的服务通信 | 通过[`MCPServerSse`][agents.mcp.server.MCPServerSse]使用**HTTP with SSE MCP服务** |
|
||||
| 启动本地进程并通过stdin/stdout通信 | 通过[`MCPServerStdio`][agents.mcp.server.MCPServerStdio]使用**stdio MCP服务** |
|
||||
|
||||
下面各节会逐一介绍每个选项、如何配置它,以及何时优先选择某种传输方式。
|
||||
|
||||
## 智能体级MCP配置
|
||||
|
||||
除了选择传输方式,你还可以通过设置`Agent.mcp_config`来调整MCP工具的准备方式。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
mcp_servers=[server],
|
||||
mcp_config={
|
||||
# Try to convert MCP tool schemas to strict JSON schema.
|
||||
"convert_schemas_to_strict": True,
|
||||
# If None, MCP tool failures are raised as exceptions instead of
|
||||
# returning model-visible error text.
|
||||
"failure_error_function": None,
|
||||
# Prefix local MCP tool names with their server name.
|
||||
"include_server_in_tool_names": True,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `convert_schemas_to_strict`是尽力而为的。如果某个schema无法转换,则使用原始schema。
|
||||
- `failure_error_function`控制MCP工具调用失败如何呈现给模型。
|
||||
- 当未设置`failure_error_function`时,SDK会使用默认的工具错误格式化器。
|
||||
- 服务级`failure_error_function`会覆盖该服务的`Agent.mcp_config["failure_error_function"]`。
|
||||
- `include_server_in_tool_names`是可选启用项。启用后,每个本地MCP工具都会以确定性的、带服务前缀的名称公开给模型,这有助于在多个MCP服务发布同名工具时避免冲突。生成的名称是ASCII安全的,会保持在工具调用名称长度限制内,并避免与同一智能体上的现有本地工具调用和已启用的任务转移名称冲突。SDK仍会在原服务上调用原始的MCP工具名称。
|
||||
|
||||
## 跨传输方式的通用模式
|
||||
|
||||
选择传输方式后,大多数集成都需要做出相同的后续决策:
|
||||
|
||||
- 如何仅公开工具的一个子集([工具筛选](#tool-filtering))。
|
||||
- 服务是否还提供可复用的提示词([提示词](#prompts))。
|
||||
- 是否应缓存`list_tools()`([缓存](#caching))。
|
||||
- MCP活动如何显示在追踪中([追踪](#tracing))。
|
||||
|
||||
对于本地MCP服务(`MCPServerStdio`、`MCPServerSse`、`MCPServerStreamableHttp`),审批策略和每次调用的`_meta`载荷也是通用概念。Streamable HTTP一节展示了最完整的示例,同样的模式也适用于其他本地传输方式。
|
||||
|
||||
## 1. 托管MCP服务工具
|
||||
|
||||
托管工具会把整个工具往返过程推送到OpenAI基础设施中执行。你的代码无需列出并调用工具,[`HostedMCPTool`][agents.tool.HostedMCPTool]会将服务标签(以及可选的连接器元数据)转发给Responses API。模型会列出远程服务的工具并调用它们,而无需额外回调到你的Python进程。托管工具目前适用于支持Responses API托管MCP集成的OpenAI模型。
|
||||
|
||||
### 基础托管MCP工具
|
||||
|
||||
通过向智能体的`tools`列表添加[`HostedMCPTool`][agents.tool.HostedMCPTool]来创建托管工具。`tool_config`
|
||||
字典与发送给REST API的JSON保持一致:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, HostedMCPTool, Runner
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the DeepWiki hosted MCP server to inspect openai/openai-agents-python.",
|
||||
tools=[
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "deepwiki",
|
||||
"server_url": "https://mcp.deepwiki.com/mcp",
|
||||
"require_approval": "never",
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Which language is the repository openai/openai-agents-python written in?",
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
托管服务会自动公开其工具;你无需将其添加到`mcp_servers`。
|
||||
|
||||
如果你希望托管工具搜索延迟加载托管MCP服务,请设置`tool_config["defer_loading"] = True`并将[`ToolSearchTool`][agents.tool.ToolSearchTool]添加到智能体。这仅在OpenAI Responses模型上受支持。有关完整的工具搜索设置和限制,请参阅[工具](tools.md#hosted-tool-search)。
|
||||
|
||||
### 托管MCP结果的流式传输
|
||||
|
||||
托管工具支持结果流式传输,方式与工具调用完全相同。使用`Runner.run_streamed`在模型仍在工作时
|
||||
消费增量MCP输出:
|
||||
|
||||
```python
|
||||
result = Runner.run_streamed(agent, "Summarise this repository's top languages")
|
||||
async for event in result.stream_events():
|
||||
if event.type == "run_item_stream_event":
|
||||
print(f"Received: {event.item}")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
### 可选审批流程
|
||||
|
||||
如果某个服务可能执行敏感操作,你可以要求每次工具执行前都经过人工或程序化审批。在`tool_config`中使用单一策略(`"always"`、`"never"`)或将工具名称映射到策略的字典来配置`require_approval`。要在Python中做出决定,请提供`on_approval_request`回调。
|
||||
|
||||
```python
|
||||
from agents import MCPToolApprovalFunctionResult, MCPToolApprovalRequest
|
||||
|
||||
SAFE_TOOLS = {"read_wiki_structure", "read_wiki_contents", "ask_question"}
|
||||
|
||||
def approve_tool(request: MCPToolApprovalRequest) -> MCPToolApprovalFunctionResult:
|
||||
if request.data.name in SAFE_TOOLS:
|
||||
return {"approve": True}
|
||||
return {"approve": False, "reason": "Escalate to a human reviewer"}
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
tools=[
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "deepwiki",
|
||||
"server_url": "https://mcp.deepwiki.com/mcp",
|
||||
"require_approval": "always",
|
||||
},
|
||||
on_approval_request=approve_tool,
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
该回调可以是同步或异步的,并会在模型需要审批数据才能继续运行时被调用。
|
||||
|
||||
### 连接器支持的托管服务
|
||||
|
||||
托管MCP还支持OpenAI连接器。无需指定`server_url`,而是提供`connector_id`和访问令牌。Responses API会处理身份验证,托管服务会公开连接器的工具。
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
HostedMCPTool(
|
||||
tool_config={
|
||||
"type": "mcp",
|
||||
"server_label": "google_calendar",
|
||||
"connector_id": "connector_googlecalendar",
|
||||
"authorization": os.environ["GOOGLE_CALENDAR_AUTHORIZATION"],
|
||||
"require_approval": "never",
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
完整可运行的托管工具代码示例——包括流式传输、审批和连接器——位于[`examples/hosted_mcp`](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp)。
|
||||
|
||||
## 2. Streamable HTTP MCP服务
|
||||
|
||||
当你想自行管理网络连接时,请使用[`MCPServerStreamableHttp`][agents.mcp.server.MCPServerStreamableHttp]。当你控制传输方式,或希望在自己的基础设施中运行服务并保持低延迟时,Streamable HTTP服务是理想选择。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import os
|
||||
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerStreamableHttp
|
||||
from agents.model_settings import ModelSettings
|
||||
|
||||
async def main() -> None:
|
||||
token = os.environ["MCP_SERVER_TOKEN"]
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Streamable HTTP Python Server",
|
||||
params={
|
||||
"url": "http://localhost:8000/mcp",
|
||||
"headers": {"Authorization": f"Bearer {token}"},
|
||||
"timeout": 10,
|
||||
},
|
||||
cache_tools_list=True,
|
||||
max_retry_attempts=3,
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the MCP tools to answer the questions.",
|
||||
mcp_servers=[server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Add 7 and 22.")
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
构造函数接受其他选项:
|
||||
|
||||
- `client_session_timeout_seconds`控制HTTP读取超时。
|
||||
- `use_structured_content`控制是否优先使用`tool_result.structured_content`而不是文本输出。
|
||||
- `max_retry_attempts`和`retry_backoff_seconds_base`为`list_tools()`和`call_tool()`添加自动重试。
|
||||
- `tool_filter`允许你仅公开工具的一个子集(参见[工具筛选](#tool-filtering))。
|
||||
- `require_approval`为本地MCP工具启用人在回路审批策略。
|
||||
- `failure_error_function`自定义模型可见的MCP工具失败消息;将其设置为`None`则改为抛出错误。
|
||||
- `tool_meta_resolver`会在`call_tool()`之前注入每次调用的MCP`_meta`载荷。
|
||||
|
||||
### 本地MCP服务的审批策略
|
||||
|
||||
`MCPServerStdio`、`MCPServerSse`和`MCPServerStreamableHttp`都接受`require_approval`。
|
||||
|
||||
支持的形式:
|
||||
|
||||
- 对所有工具使用`"always"`或`"never"`。
|
||||
- `True` / `False`(等同于always/never)。
|
||||
- 按工具的映射,例如`{"delete_file": "always", "read_file": "never"}`。
|
||||
- 分组对象:`{"always": {"tool_names": [...]}, "never": {"tool_names": [...]}}`。
|
||||
|
||||
```python
|
||||
async with MCPServerStreamableHttp(
|
||||
name="Filesystem MCP",
|
||||
params={"url": "http://localhost:8000/mcp"},
|
||||
require_approval={"always": {"tool_names": ["delete_file"]}},
|
||||
) as server:
|
||||
...
|
||||
```
|
||||
|
||||
有关完整的暂停/恢复流程,请参阅[人在回路](human_in_the_loop.md)和`examples/mcp/get_all_mcp_tools_example/main.py`。
|
||||
|
||||
### 使用`tool_meta_resolver`的每次调用元数据
|
||||
|
||||
当你的MCP服务期望在`_meta`中接收请求元数据(例如租户ID或追踪上下文)时,请使用`tool_meta_resolver`。下面的示例假定你将一个`dict`作为`context`传递给`Runner.run(...)`。
|
||||
|
||||
```python
|
||||
from agents.mcp import MCPServerStreamableHttp, MCPToolMetaContext
|
||||
|
||||
|
||||
def resolve_meta(context: MCPToolMetaContext) -> dict[str, str] | None:
|
||||
run_context_data = context.run_context.context or {}
|
||||
tenant_id = run_context_data.get("tenant_id")
|
||||
if tenant_id is None:
|
||||
return None
|
||||
return {"tenant_id": str(tenant_id), "source": "agents-sdk"}
|
||||
|
||||
|
||||
server = MCPServerStreamableHttp(
|
||||
name="Metadata-aware MCP",
|
||||
params={"url": "http://localhost:8000/mcp"},
|
||||
tool_meta_resolver=resolve_meta,
|
||||
)
|
||||
```
|
||||
|
||||
如果你的运行上下文是Pydantic模型、dataclass或自定义类,请改用属性访问读取租户ID。
|
||||
|
||||
### MCP工具输出:文本和图像
|
||||
|
||||
当MCP工具返回图像内容时,SDK会自动将其映射为图像工具输出条目。混合的文本/图像响应会作为输出项列表转发,因此智能体可以像消费常规工具调用的图像输出一样消费MCP图像结果。
|
||||
|
||||
## 3. HTTP with SSE MCP服务
|
||||
|
||||
!!! warning
|
||||
|
||||
MCP项目已弃用Server-Sent Events传输。对于新的集成,请优先选择Streamable HTTP或stdio;仅为旧服务保留SSE。
|
||||
|
||||
如果MCP服务实现了HTTP with SSE传输,请实例化[`MCPServerSse`][agents.mcp.server.MCPServerSse]。除传输方式外,API与Streamable HTTP服务相同。
|
||||
|
||||
```python
|
||||
|
||||
from agents import Agent, Runner
|
||||
from agents.model_settings import ModelSettings
|
||||
from agents.mcp import MCPServerSse
|
||||
|
||||
workspace_id = "demo-workspace"
|
||||
|
||||
async with MCPServerSse(
|
||||
name="SSE Python Server",
|
||||
params={
|
||||
"url": "http://localhost:8000/sse",
|
||||
"headers": {"X-Workspace": workspace_id},
|
||||
},
|
||||
cache_tools_list=True,
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
mcp_servers=[server],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 4. stdio MCP服务
|
||||
|
||||
对于作为本地子进程运行的MCP服务,请使用[`MCPServerStdio`][agents.mcp.server.MCPServerStdio]。SDK会启动该进程、保持管道打开,并在上下文管理器退出时自动关闭它们。此选项适合快速概念验证,或服务仅公开命令行入口点的情况。
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerStdio
|
||||
|
||||
current_dir = Path(__file__).parent
|
||||
samples_dir = current_dir / "sample_files"
|
||||
|
||||
async with MCPServerStdio(
|
||||
name="Filesystem Server via npx",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
) as server:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use the files in the sample directory to answer questions.",
|
||||
mcp_servers=[server],
|
||||
)
|
||||
result = await Runner.run(agent, "List the files available to you.")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 5. MCP服务管理器
|
||||
|
||||
当你有多个MCP服务时,请使用`MCPServerManager`预先连接它们,并向你的智能体公开已连接的子集。有关构造函数选项和重新连接行为,请参阅[MCPServerManager API参考](ref/mcp/manager.md)。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.mcp import MCPServerManager, MCPServerStreamableHttp
|
||||
|
||||
servers = [
|
||||
MCPServerStreamableHttp(name="calendar", params={"url": "http://localhost:8000/mcp"}),
|
||||
MCPServerStreamableHttp(name="docs", params={"url": "http://localhost:8001/mcp"}),
|
||||
]
|
||||
|
||||
async with MCPServerManager(servers) as manager:
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Use MCP tools when they help.",
|
||||
mcp_servers=manager.active_servers,
|
||||
)
|
||||
result = await Runner.run(agent, "Which MCP tools are available?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
关键行为:
|
||||
|
||||
- 当`drop_failed_servers=True`(默认值)时,`active_servers`仅包含成功连接的服务。
|
||||
- 失败会记录在`failed_servers`和`errors`中。
|
||||
- 设置`strict=True`会在首次连接失败时抛出错误。
|
||||
- 调用`reconnect(failed_only=True)`以重试失败的服务,或调用`reconnect(failed_only=False)`以重启所有服务。
|
||||
- 使用`connect_timeout_seconds`、`cleanup_timeout_seconds`和`connect_in_parallel`来调整生命周期行为。
|
||||
|
||||
## 通用服务能力
|
||||
|
||||
以下各节适用于各种MCP服务传输方式(确切API范围取决于服务类)。
|
||||
|
||||
## 工具筛选
|
||||
|
||||
每个MCP服务都支持工具筛选器,以便你仅公开智能体所需的函数。筛选可以在构造时进行,也可以在每次运行时动态进行。
|
||||
|
||||
### 静态工具筛选
|
||||
|
||||
使用[`create_static_tool_filter`][agents.mcp.create_static_tool_filter]配置简单的允许/阻止列表:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.mcp import MCPServerStdio, create_static_tool_filter
|
||||
|
||||
samples_dir = Path("/path/to/files")
|
||||
|
||||
filesystem_server = MCPServerStdio(
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
tool_filter=create_static_tool_filter(allowed_tool_names=["read_file", "write_file"]),
|
||||
)
|
||||
```
|
||||
|
||||
当同时提供`allowed_tool_names`和`blocked_tool_names`时,SDK会先应用允许列表,然后从剩余集合中移除所有被阻止的工具。
|
||||
|
||||
### 动态工具筛选
|
||||
|
||||
对于更复杂的逻辑,请传入一个接收[`ToolFilterContext`][agents.mcp.ToolFilterContext]的可调用对象。该可调用对象可以是同步或异步的;当工具应被公开时返回`True`。
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.mcp import MCPServerStdio, ToolFilterContext
|
||||
|
||||
samples_dir = Path("/path/to/files")
|
||||
|
||||
async def context_aware_filter(context: ToolFilterContext, tool) -> bool:
|
||||
if context.agent.name == "Code Reviewer" and tool.name.startswith("danger_"):
|
||||
return False
|
||||
return True
|
||||
|
||||
async with MCPServerStdio(
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", str(samples_dir)],
|
||||
},
|
||||
tool_filter=context_aware_filter,
|
||||
) as server:
|
||||
...
|
||||
```
|
||||
|
||||
筛选上下文会公开当前活动的`run_context`、请求这些工具的`agent`以及`server_name`。
|
||||
|
||||
## 提示词
|
||||
|
||||
MCP服务还可以提供用于动态生成智能体指令的提示词。支持提示词的服务会公开两个
|
||||
方法:
|
||||
|
||||
- `list_prompts()`枚举可用的提示词模板。
|
||||
- `get_prompt(name, arguments)`获取一个具体提示词,可选带有参数。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
prompt_result = await server.get_prompt(
|
||||
"generate_code_review_instructions",
|
||||
{"focus": "security vulnerabilities", "language": "python"},
|
||||
)
|
||||
instructions = prompt_result.messages[0].content.text
|
||||
|
||||
agent = Agent(
|
||||
name="Code Reviewer",
|
||||
instructions=instructions,
|
||||
mcp_servers=[server],
|
||||
)
|
||||
```
|
||||
|
||||
## 缓存
|
||||
|
||||
每次智能体运行都会在每个MCP服务上调用`list_tools()`。远程服务可能引入明显延迟,因此所有MCP服务类都公开了`cache_tools_list`选项。仅当你确信工具定义不会频繁变化时,才将其设置为`True`。要在之后强制获取新列表,请在服务实例上调用`invalidate_tools_cache()`。
|
||||
|
||||
## 追踪
|
||||
|
||||
[追踪](./tracing.md)会自动捕获MCP活动,包括:
|
||||
|
||||
1. 调用MCP服务以列出工具。
|
||||
2. 工具调用中的MCP相关信息。
|
||||
|
||||

|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Model Context Protocol](https://modelcontextprotocol.io/) – 规范和设计指南。
|
||||
- [examples/mcp](https://github.com/openai/openai-agents-python/tree/main/examples/mcp) – 可运行的stdio、SSE和Streamable HTTP示例代码。
|
||||
- [examples/hosted_mcp](https://github.com/openai/openai-agents-python/tree/main/examples/hosted_mcp) – 完整的托管MCP演示,包括审批和连接器。
|
||||
@@ -0,0 +1,674 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 模型
|
||||
|
||||
Agents SDK 原生支持两种类型的OpenAI模型:
|
||||
|
||||
- **推荐**:[`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel],它使用新的 [Responses API](https://platform.openai.com/docs/api-reference/responses) 调用OpenAI API。
|
||||
- [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel],它使用 [Chat Completions API](https://platform.openai.com/docs/api-reference/chat) 调用OpenAI API。
|
||||
|
||||
## 模型配置方案选择
|
||||
|
||||
从最符合你配置需求的简单方案开始:
|
||||
|
||||
| 如果你想要…… | 推荐方案 | 更多信息 |
|
||||
| --- | --- | --- |
|
||||
| 仅使用OpenAI模型 | 使用默认OpenAI提供方和 Responses 模型路径 | [OpenAI模型](#openai-models) |
|
||||
| 通过 WebSocket 传输使用OpenAI Responses API | 保持使用 Responses 模型路径并启用 WebSocket 传输 | [Responses WebSocket 传输](#responses-websocket-transport) |
|
||||
| 使用OpenAI托管的子智能体 | 使用实验性的托管式多智能体模型 | [托管式多智能体](#hosted-multi-agent-experimental) |
|
||||
| 使用一个非OpenAI提供方 | 从内置提供方集成点开始 | [非OpenAI模型](#non-openai-models) |
|
||||
| 在不同智能体间混用模型或提供方 | 按运行或按智能体选择提供方,并检查功能差异 | [在一个工作流中混用模型](#mixing-models-in-one-workflow)和[跨提供方混用模型](#mixing-models-across-providers) |
|
||||
| 调整高级OpenAI Responses 请求设置 | 在OpenAI Responses 路径上使用 `ModelSettings` | [高级OpenAI Responses 设置](#advanced-openai-responses-settings) |
|
||||
| 使用第三方适配器进行非OpenAI或混合提供方路由 | 比较受支持的测试版适配器,并验证你计划上线的提供方路径 | [第三方适配器](#third-party-adapters) |
|
||||
|
||||
## OpenAI模型
|
||||
|
||||
对于大多数仅使用OpenAI的应用,推荐使用字符串模型名称和默认OpenAI提供方,并继续使用 Responses 模型路径。
|
||||
|
||||
初始化 `Agent` 时如果没有指定模型,将使用默认模型。目前默认模型是 [`gpt-5.4-mini`](https://developers.openai.com/api/docs/models/gpt-5.4-mini),并配置 `reasoning.effort="none"` 和 `verbosity="low"`,适用于低延迟智能体工作流。如果你拥有访问权限,我们建议将智能体设置为 `gpt-5.6-sol` 以获得更高质量,同时继续显式设置 `model_settings`。
|
||||
|
||||
如果要切换到 `gpt-5.6-sol` 等其他模型,可以通过两种方式配置智能体。
|
||||
|
||||
### 默认模型
|
||||
|
||||
首先,如果想让所有未设置自定义模型的智能体始终使用某个特定模型,请在运行智能体之前设置 `OPENAI_DEFAULT_MODEL` 环境变量。
|
||||
|
||||
```bash
|
||||
export OPENAI_DEFAULT_MODEL=gpt-5.6-sol
|
||||
python3 my_awesome_agent.py
|
||||
```
|
||||
|
||||
其次,可以通过 `RunConfig` 为一次运行设置默认模型。如果没有为智能体设置模型,则将使用此次运行的模型。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="You're a helpful agent.",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model="gpt-5.6-sol"),
|
||||
)
|
||||
```
|
||||
|
||||
#### GPT-5 模型
|
||||
|
||||
以这种方式使用 `gpt-5.6-sol` 等任何 GPT-5 模型时,SDK 会应用默认的 `ModelSettings`,其中设置了适合大多数用例的最佳选项。要调整默认模型的推理强度,请传入自己的 `ModelSettings`:
|
||||
|
||||
```python
|
||||
from openai.types.shared import Reasoning
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
my_agent = Agent(
|
||||
name="My Agent",
|
||||
instructions="You're a helpful agent.",
|
||||
# If OPENAI_DEFAULT_MODEL=gpt-5.6-sol is set, passing only model_settings works.
|
||||
# It's also fine to pass a GPT-5 model name explicitly:
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(reasoning=Reasoning(effort="high"), verbosity="low")
|
||||
)
|
||||
```
|
||||
|
||||
为了降低延迟,建议在 GPT-5 模型中使用 `reasoning.effort="none"`。
|
||||
|
||||
GPT-5.6 还通过现有的 `reasoning` 设置支持推理模式、持久化推理上下文和 `"max"` 强度级别。这些控制项可在 Responses API 路径上使用:
|
||||
|
||||
```python
|
||||
from openai.types.shared import Reasoning
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
agent = Agent(
|
||||
name="Deep research agent",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
reasoning=Reasoning(
|
||||
mode="pro",
|
||||
effort="max",
|
||||
context="all_turns",
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`reasoning.mode` 和 `reasoning.context` 是 Responses 独有的设置。Chat Completions 仅使用 `reasoning.effort`,支持的强度级别取决于模型和 API 接口。请使用 Responses API 设置 GPT-5.6 的 `"max"` 强度。Chat Completions 适配器会忽略模式和上下文并发出警告;在OpenAI提供方上设置 `strict_feature_validation=True` 可将该警告转为错误。
|
||||
|
||||
使用 `context="all_turns"` 时,请通过 `previous_response_id`、服务端对话或重放之前的推理项来保留对话。对于无状态的 `store=False` 调用,请在响应中包含 `reasoning.encrypted_content`,并在下一次请求中重放这些推理项。
|
||||
|
||||
#### ComputerTool 模型选择
|
||||
|
||||
如果智能体包含 [`ComputerTool`][agents.tool.ComputerTool],实际 Responses 请求上的有效模型将决定 SDK 发送哪种计算机工具载荷。显式的 `gpt-5.5` 请求使用正式发布的内置 `computer` 工具,而显式的 `computer-use-preview` 请求继续使用旧版 `computer_use_preview` 载荷。
|
||||
|
||||
由提示词管理的调用是主要例外。如果提示词模板控制模型,且 SDK 在请求中省略 `model`,SDK 会默认使用兼容预览版的计算机载荷,从而避免猜测提示词固定的是哪个模型。要在该流程中继续使用正式发布路径,可以在请求中显式设置 `model="gpt-5.5"`,或通过 `ModelSettings(tool_choice="computer")` 或 `ModelSettings(tool_choice="computer_use")` 强制使用正式发布版选择器。
|
||||
|
||||
注册 [`ComputerTool`][agents.tool.ComputerTool] 后,`tool_choice="computer"`、`"computer_use"` 和 `"computer_use_preview"` 会被规范化为与有效请求模型匹配的内置选择器。如果未注册 `ComputerTool`,这些字符串仍会像普通函数名称一样工作。
|
||||
|
||||
兼容预览版的请求必须预先序列化 `environment` 和显示尺寸,因此使用 [`ComputerProvider`][agents.tool.ComputerProvider] 工厂、由提示词管理的流程应传入具体的 `Computer` 或 `AsyncComputer` 实例,或者在发送请求前强制使用正式发布版选择器。有关完整迁移详情,请参阅[工具](../tools.md#computertool-and-the-responses-computer-tool)。
|
||||
|
||||
#### 非 GPT-5 模型
|
||||
|
||||
如果传入非 GPT-5 模型名称且未提供自定义 `model_settings`,SDK 将恢复为与任何模型兼容的通用 `ModelSettings`。
|
||||
|
||||
### Responses 独有的工具搜索功能
|
||||
|
||||
以下工具功能仅受OpenAI Responses 模型支持:
|
||||
|
||||
- [`ToolSearchTool`][agents.tool.ToolSearchTool]
|
||||
- [`tool_namespace()`][agents.tool.tool_namespace]
|
||||
- `@function_tool(defer_loading=True)` 和其他延迟加载的 Responses 工具接口
|
||||
|
||||
Chat Completions 模型和非 Responses 后端会拒绝这些功能。使用延迟加载工具时,请将 `ToolSearchTool()` 添加到智能体,并让模型通过 `auto` 或 `required` 工具选择来加载工具,而不是强制使用单独的命名空间名称或仅限延迟加载的函数名称。有关配置详情和当前限制,请参阅[工具](../tools.md#hosted-tool-search)。
|
||||
|
||||
### Responses WebSocket 传输
|
||||
|
||||
默认情况下,OpenAI Responses API 请求使用 HTTP 传输。使用由OpenAI支持的模型时,可以选择启用 WebSocket 传输。
|
||||
|
||||
#### 基础配置
|
||||
|
||||
```python
|
||||
from agents import set_default_openai_responses_transport
|
||||
|
||||
set_default_openai_responses_transport("websocket")
|
||||
```
|
||||
|
||||
这会影响由默认OpenAI提供方解析的OpenAI Responses 模型,包括 `"gpt-5.6-sol"` 等字符串模型名称。
|
||||
|
||||
SDK 将模型名称解析为模型实例时,会进行传输方式选择。如果传入具体的 [`Model`][agents.models.interface.Model] 对象,其传输方式已经固定:[`OpenAIResponsesWSModel`][agents.models.openai_responses.OpenAIResponsesWSModel] 使用 WebSocket,[`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 使用 HTTP,而 [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel] 继续使用 Chat Completions。如果传入 `RunConfig(model_provider=...)`,则由该提供方而非全局默认提供方控制传输方式选择。
|
||||
|
||||
#### 提供方级或运行级配置
|
||||
|
||||
也可以按提供方或按运行配置 WebSocket 传输:
|
||||
|
||||
```python
|
||||
from agents import Agent, OpenAIProvider, RunConfig, Runner
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses_websocket=True,
|
||||
# Optional; if omitted, OPENAI_WEBSOCKET_BASE_URL is used when set.
|
||||
websocket_base_url="wss://your-proxy.example/v1",
|
||||
# Optional low-level websocket keepalive settings.
|
||||
responses_websocket_options={"ping_interval": 20.0, "ping_timeout": 60.0},
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
由OpenAI支持的提供方还接受可选的智能体注册配置。这是一个高级选项,适用于OpenAI配置需要提供方级注册元数据(例如运行框架 ID)的情况。
|
||||
|
||||
```python
|
||||
from agents import (
|
||||
Agent,
|
||||
OpenAIAgentRegistrationConfig,
|
||||
OpenAIProvider,
|
||||
RunConfig,
|
||||
Runner,
|
||||
)
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses_websocket=True,
|
||||
agent_registration=OpenAIAgentRegistrationConfig(harness_id="your-harness-id"),
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
#### 使用 `MultiProvider` 的高级路由
|
||||
|
||||
如果需要基于前缀的模型路由,例如在一次运行中混用 `openai/...` 和 `any-llm/...` 模型名称,请使用 [`MultiProvider`][agents.MultiProvider],并在其中设置 `openai_use_responses_websocket=True`。
|
||||
|
||||
`MultiProvider` 保留两个历史默认行为:
|
||||
|
||||
- `openai/...` 被视为OpenAI提供方的别名,因此 `openai/gpt-4.1` 会作为模型 `gpt-4.1` 进行路由。
|
||||
- 未知前缀会引发 `UserError`,而不是直接透传。
|
||||
|
||||
当OpenAI提供方指向需要字面命名空间模型 ID 的OpenAI兼容端点时,请显式启用透传行为。在启用 WebSocket 的配置中,也请在 `MultiProvider` 上保留 `openai_use_responses_websocket=True`:
|
||||
|
||||
```python
|
||||
from agents import Agent, MultiProvider, RunConfig, Runner
|
||||
|
||||
provider = MultiProvider(
|
||||
openai_base_url="https://openrouter.ai/api/v1",
|
||||
openai_api_key="...",
|
||||
openai_use_responses_websocket=True,
|
||||
openai_prefix_mode="model_id",
|
||||
unknown_prefix_mode="model_id",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Be concise.",
|
||||
model="openai/gpt-4.1",
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
后端需要字面的 `openai/...` 字符串时,请使用 `openai_prefix_mode="model_id"`。后端需要其他命名空间模型 ID(例如 `openrouter/openai/gpt-4.1-mini`)时,请使用 `unknown_prefix_mode="model_id"`。这些选项也适用于 WebSocket 传输以外的 `MultiProvider`;此示例继续启用 WebSocket,因为它属于本节所述的传输配置。相同选项也可用于 [`responses_websocket_session()`][agents.responses_websocket_session]。
|
||||
|
||||
如果通过 `MultiProvider` 进行路由时需要相同的提供方级注册元数据,请传入 `openai_agent_registration=OpenAIAgentRegistrationConfig(...)`,它将被转发给底层OpenAI提供方。
|
||||
|
||||
如果使用自定义OpenAI兼容端点或代理,WebSocket 传输还要求提供兼容的 WebSocket `/responses` 端点。在这些配置中,可能需要显式设置 `websocket_base_url`。
|
||||
|
||||
#### 注意事项
|
||||
|
||||
- 这是通过 WebSocket 传输使用的 Responses API,而不是 [Realtime API](../realtime/guide.md)。它不适用于 Chat Completions 或非OpenAI提供方,除非它们支持 Responses WebSocket `/responses` 端点。
|
||||
- 如果环境中尚未安装 `websockets` 软件包,请安装它。
|
||||
- 启用 WebSocket 传输后,可以直接使用 [`Runner.run_streamed()`][agents.run.Runner.run_streamed]。对于希望跨轮次以及嵌套的智能体即工具调用复用同一 WebSocket 连接的多轮工作流,建议使用 [`responses_websocket_session()`][agents.responses_websocket_session] 辅助函数。请参阅[运行智能体](../running_agents.md)指南和 [`examples/basic/stream_ws.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/stream_ws.py)。
|
||||
- 对于长时间推理轮次或存在延迟突增的网络,请使用 `responses_websocket_options` 自定义 WebSocket 保活行为。增大 `ping_timeout` 可容忍延迟的 pong 帧,或者设置 `ping_timeout=None`,以在保持 ping 启用的同时禁用心跳超时。如果可靠性比 WebSocket 延迟更重要,请优先使用 HTTP/SSE 传输。
|
||||
- 默认情况下,SDK 会禁用传入消息大小限制(`max_size=None`)。对于位于代理后方或内存受限容器中的长时间运行智能体进程,请设置 `responses_websocket_options={"max_size": 8 * 1024 * 1024}`,以限制每条消息的内存用量。
|
||||
|
||||
### 托管式多智能体(实验性)
|
||||
|
||||
OpenAI Responses API 托管式多智能体测试版允许 GPT-5.6 根模型创建和协调服务端托管的子智能体。Agents SDK 可以继续使用常规 `Runner`:托管式编排在服务上进行,而开发者定义的工具调用则在你的应用中执行。
|
||||
|
||||
此集成具有实验性,并使用 Responses WebSocket 传输,以便通过 `response.inject` 将本地函数输出返回给活跃的托管智能体。它要求使用 `openai[realtime]>=2.45.0`,其中包括公开 `client.beta.responses.connect` 的测试版构建。该接口和测试版项目架构可能会在正式发布前发生变化。
|
||||
|
||||
#### 模型配置
|
||||
|
||||
从实验性模块导入模型,并将其分配给 SDK `Agent`:
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
from agents.extensions.experimental.hosted_multi_agent import OpenAIHostedMultiAgentModel
|
||||
|
||||
agent = Agent(
|
||||
name="Research coordinator",
|
||||
instructions="Delegate independent research tasks, then synthesize the findings.",
|
||||
model=OpenAIHostedMultiAgentModel(model="gpt-5.6-sol", config={"max_concurrent_subagents": 3}),
|
||||
)
|
||||
```
|
||||
|
||||
构造 `OpenAIHostedMultiAgentModel` 会启用 `multi_agent.enabled`,并发送 `OpenAI-Beta: responses_multi_agent=v1` WebSocket 标头。除非提供 `openai_client`,否则该模型使用默认OpenAI客户端。如果省略 `max_concurrent_subagents`,则使用服务默认值。
|
||||
|
||||
#### 本地工具调用
|
||||
|
||||
所有托管智能体共享为请求配置的模型和工具。Responses API 决定由哪个托管智能体调用函数。常规 SDK Runner 在本地执行函数,并将具有相同调用 ID 的 `function_call_output` 注入活跃的 WebSocket 响应,使服务能够恢复原始托管调用方。函数执行仍会经过 Runner 的常规安全防护措施、钩子和失败转换。不支持 SDK 工具审批中断:任何 `needs_approval` 设置不为 `False` 的工具调用都会在请求发送前被拒绝。
|
||||
|
||||
当工具需要感知调用方的日志记录或授权时,请使用 `get_hosted_agent_metadata()`:
|
||||
|
||||
```python
|
||||
from typing import Any
|
||||
|
||||
from agents import function_tool
|
||||
from agents.extensions.experimental.hosted_multi_agent import get_hosted_agent_metadata
|
||||
from agents.tool_context import ToolContext
|
||||
|
||||
@function_tool
|
||||
def lookup_document(ctx: ToolContext[Any], section: str) -> str:
|
||||
metadata = get_hosted_agent_metadata(ctx)
|
||||
caller = metadata.agent_name if metadata else "unknown"
|
||||
print(f"tool caller: {caller}; call ID: {ctx.tool_call_id}")
|
||||
return f"Contents for {section}"
|
||||
```
|
||||
|
||||
托管智能体名称是观测性元数据,而不是本地路由机制。请使用 SDK 提供的调用 ID 路由输出。对于会产生副作用的工具,请将该调用 ID 用作幂等键,并在工具执行之前或期间通过应用代码实施任何必要的授权;请勿将 `needs_approval` 与此模型结合使用。工具参数和输出会跨越 Responses API 边界。
|
||||
|
||||
#### 输出与流式传输行为
|
||||
|
||||
只有归属于 `/root` 且阶段为 `final_answer` 的消息才会成为普通最终消息。实验性适配器会从高级 `RunResult` 中过滤子智能体消息和托管式编排记录;SDK 绝不会将这些记录作为本地函数执行。
|
||||
|
||||
原始流式传输会继续公开测试版 Responses 事件,包括托管输出项和 `response.inject.created` 确认。函数调用就绪时,适配器会将一个活跃的提供方响应划分为 SDK 可见的逻辑模型轮次,然后在 Runner 生成输出后恢复同一个提供方响应。请将 `get_hosted_agent_metadata()` 与原始托管项或 `ToolContext` 一起使用,以检查归属信息。
|
||||
|
||||
#### 与 SDK 编排的关系
|
||||
|
||||
托管式多智能体独立于 SDK 任务转移和 agents-as-tools:
|
||||
|
||||
- 托管式多智能体在OpenAI服务上创建子智能体。你的应用不会创建或调度这些子智能体。
|
||||
- SDK 任务转移会更改当前活跃的本地 SDK `Agent`。使用此实验性模型时,任务转移会被拒绝,因为每个托管智能体都会收到相同的任务转移工具,从而造成所有权冲突。
|
||||
- Agents-as-tools 仍然可用,但使用它们会产生嵌套的客户端和服务端编排。请审慎评估额外的延迟、成本和工具暴露。
|
||||
|
||||
#### 当前限制
|
||||
|
||||
实验性模型会拒绝 `reasoning.summary`、`max_tool_calls` 以及调用方提供的 `multi_agent` 或 `betas` 覆盖值。该测试版不支持 Responses `/compact` 端点,但可以使用显式的 `context_management.compact_threshold`,因为服务会自动独立压缩每个托管智能体的上下文。
|
||||
|
||||
一个 `OpenAIHostedMultiAgentModel` 实例最多同时拥有一个活跃的托管响应。如果在等待本地函数输出时放弃运行,请调用 `await model.close()` 以释放其 WebSocket。目前不支持在其他进程或事件循环中恢复进行中的托管响应。
|
||||
|
||||
有关底层 Responses API 测试版行为,请参阅 [OpenAI多智能体指南](https://developers.openai.com/api/docs/guides/tools-multi-agent)。有关非流式传输和流式传输 SDK 用法,请参阅 [`examples/agent_patterns/hosted_multi_agent_beta.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/hosted_multi_agent_beta.py)。
|
||||
|
||||
## 非OpenAI模型
|
||||
|
||||
如果需要非OpenAI提供方,请从 SDK 的内置提供方集成点开始。在许多配置中,无需添加第三方适配器即可满足需求。每种模式的代码示例位于 [examples/model_providers](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)。
|
||||
|
||||
### 非OpenAI提供方的集成方式
|
||||
|
||||
| 方式 | 适用场景 | 作用域 |
|
||||
| --- | --- | --- |
|
||||
| [`set_default_openai_client`][agents.set_default_openai_client] | 一个OpenAI兼容端点应成为大多数或所有智能体的默认端点 | 全局默认 |
|
||||
| [`ModelProvider`][agents.models.interface.ModelProvider] | 一个自定义提供方应适用于单次运行 | 按运行 |
|
||||
| [`Agent.model`][agents.agent.Agent.model] | 不同智能体需要不同的提供方或具体模型对象 | 按智能体 |
|
||||
| 第三方适配器 | 需要由适配器管理的提供方覆盖或内置路径无法提供的路由 | 请参阅[第三方适配器](#third-party-adapters) |
|
||||
|
||||
可以通过以下内置路径集成其他 LLM 提供方:
|
||||
|
||||
1. [`set_default_openai_client`][agents.set_default_openai_client] 适用于希望在全局范围使用 `AsyncOpenAI` 实例作为 LLM 客户端的情况。此方式适用于 LLM 提供方具有OpenAI兼容 API 端点,并且你可以设置 `base_url` 和 `api_key` 的情况。可配置的代码示例请参阅 [examples/model_providers/custom_example_global.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_global.py)。
|
||||
2. [`ModelProvider`][agents.models.interface.ModelProvider] 作用于 `Runner.run` 层级。借助它,你可以指定“为本次运行中的所有智能体使用自定义模型提供方”。可配置的代码示例请参阅 [examples/model_providers/custom_example_provider.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_provider.py)。
|
||||
3. [`Agent.model`][agents.agent.Agent.model] 允许在特定 Agent 实例上指定模型。这样可以为不同智能体混合搭配不同的提供方。可配置的代码示例请参阅 [examples/model_providers/custom_example_agent.py](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/custom_example_agent.py)。
|
||||
|
||||
如果没有来自 `platform.openai.com` 的 API 密钥,建议通过 `set_tracing_disabled()` 禁用追踪,或者设置[其他追踪进程](../tracing.md)。
|
||||
|
||||
``` python
|
||||
from agents import Agent, AsyncOpenAI, OpenAIChatCompletionsModel, set_tracing_disabled
|
||||
|
||||
set_tracing_disabled(disabled=True)
|
||||
|
||||
client = AsyncOpenAI(api_key="Api_Key", base_url="Base URL of Provider")
|
||||
model = OpenAIChatCompletionsModel(model="Model_Name", openai_client=client)
|
||||
|
||||
agent= Agent(name="Helping Agent", instructions="You are a Helping Agent", model=model)
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
在这些代码示例中,我们使用 Chat Completions API/模型,因为许多 LLM 提供方仍不支持 Responses API。如果你的 LLM 提供方支持它,建议使用 Responses。
|
||||
|
||||
## 在一个工作流中混用模型
|
||||
|
||||
在单个工作流中,你可能希望为每个智能体使用不同的模型。例如,可以使用更小、更快的模型进行分流,同时使用更大、能力更强的模型处理复杂任务。配置 [`Agent`][agents.Agent] 时,可以通过以下任一方式选择特定模型:
|
||||
|
||||
1. 传入模型名称。
|
||||
2. 传入任意模型名称以及能够将该名称映射到 Model 实例的 [`ModelProvider`][agents.models.interface.ModelProvider]。
|
||||
3. 直接提供 [`Model`][agents.models.interface.Model] 实现。
|
||||
|
||||
!!! note
|
||||
|
||||
虽然 SDK 同时支持 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 和 [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel] 两种形式,但建议每个工作流只使用一种模型形式,因为两种形式支持的功能和工具集合不同。如果工作流需要混用模型形式,请确保正在使用的所有功能都受两者支持。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, AsyncOpenAI, OpenAIChatCompletionsModel
|
||||
import asyncio
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish agent",
|
||||
instructions="You only speak Spanish.",
|
||||
model="gpt-5-mini", # (1)!
|
||||
)
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model=OpenAIChatCompletionsModel( # (2)!
|
||||
model="gpt-5-nano",
|
||||
openai_client=AsyncOpenAI()
|
||||
),
|
||||
)
|
||||
|
||||
triage_agent = Agent(
|
||||
name="Triage agent",
|
||||
instructions="Handoff to the appropriate agent based on the language of the request.",
|
||||
handoffs=[spanish_agent, english_agent],
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(triage_agent, input="Hola, ¿cómo estás?")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
1. 直接设置OpenAI模型的名称。
|
||||
2. 提供 [`Model`][agents.models.interface.Model] 实现。
|
||||
|
||||
如果要进一步配置智能体使用的模型,可以传入 [`ModelSettings`][agents.models.interface.ModelSettings],其中提供 temperature 等可选模型配置参数。
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model="gpt-4.1",
|
||||
model_settings=ModelSettings(temperature=0.1),
|
||||
)
|
||||
```
|
||||
|
||||
## 高级OpenAI Responses 设置
|
||||
|
||||
使用OpenAI Responses 路径且需要更多控制时,请从 `ModelSettings` 开始。
|
||||
|
||||
### 常用高级 `ModelSettings` 选项
|
||||
|
||||
使用OpenAI Responses API 时,多个请求字段已经有对应的直接 `ModelSettings` 字段,因此无需通过 `extra_args` 传递它们。
|
||||
|
||||
- `parallel_tool_calls`:允许或禁止在同一轮中进行多次工具调用。
|
||||
- `truncation`:设置为 `"auto"`,可让 Responses API 在上下文即将溢出时丢弃最早的对话项,而不是使请求失败。
|
||||
- `store`:控制生成的响应是否存储在服务端以供后续检索。这对于依赖响应 ID 的后续工作流以及在 `store=False` 时可能需要回退到本地输入的会话压缩流程很重要。
|
||||
- `context_management`:配置服务端上下文处理,例如使用 `compact_threshold` 进行 Responses 压缩。
|
||||
- `prompt_cache_retention`:为较早的模型系列配置延长保留时间,例如
|
||||
设置为 `"24h"`。
|
||||
- `prompt_cache_options`:选择隐式或显式提示词缓存;对于 GPT-5.6,还可配置 `"30m"` 缓存 TTL。
|
||||
- `response_include`:请求更丰富的响应载荷,例如 `web_search_call.action.sources`、`file_search_call.results` 或 `reasoning.encrypted_content`。
|
||||
- `top_logprobs`:请求输出文本的热门候选 token 对数概率。SDK 还会自动添加 `message.output_text.logprobs`。
|
||||
- `retry`:选择启用由 Runner 管理的模型调用重试设置。请参阅 [Runner 管理的重试](#runner-managed-retries)。
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
research_agent = Agent(
|
||||
name="Research agent",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
parallel_tool_calls=False,
|
||||
truncation="auto",
|
||||
store=True,
|
||||
context_management=[{"type": "compaction", "compact_threshold": 200000}],
|
||||
prompt_cache_options={"mode": "explicit", "ttl": "30m"},
|
||||
response_include=["web_search_call.action.sources"],
|
||||
top_logprobs=5,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
使用显式提示词缓存时,请在可复用前缀结束的内容部分添加断点。同一个 `ModelSettings.prompt_cache_options` 字段会透传到 Responses 和 Chat Completions 请求,而 Chat Completions 转换器会保留文本、图像、音频和文件内容部分的断点。
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
|
||||
result = await Runner.run(
|
||||
research_agent,
|
||||
[
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "Reusable background material...",
|
||||
"prompt_cache_breakpoint": {"mode": "explicit"},
|
||||
},
|
||||
{
|
||||
"type": "input_text",
|
||||
"text": "Analyze the latest question.",
|
||||
},
|
||||
],
|
||||
}
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
`prompt_cache_retention` 仍可用于采用旧版
|
||||
保留控制的较早模型系列。请勿同时使用直接 `ModelSettings` 字段和
|
||||
`extra_args` 中的同名键。
|
||||
|
||||
设置 `store=False` 时,Responses API 不会保留该响应以供后续服务端检索。这对于无状态或零数据保留类型的流程很有用,但也意味着原本会复用响应 ID 的功能必须改为依赖本地管理的状态。例如,当最后一个响应未存储时,[`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession] 会将其默认的 `"auto"` 压缩路径切换为基于输入的压缩。请参阅[会话指南](../sessions/index.md#openai-responses-compaction-sessions)。
|
||||
|
||||
服务端压缩不同于 [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession]。`context_management=[{"type": "compaction", "compact_threshold": ...}]` 会随每个 Responses API 请求一起发送;当渲染后的上下文超过阈值时,API 可在响应中生成压缩项。`OpenAIResponsesCompactionSession` 则会在轮次之间调用独立的 `responses.compact` 端点,并重写本地会话历史记录。
|
||||
|
||||
### `extra_args` 传递
|
||||
|
||||
当需要 SDK 尚未直接在顶层公开的提供方特定或较新的请求字段时,请使用 `extra_args`。
|
||||
|
||||
此外,使用OpenAI的 Responses API 时,[还有一些其他可选参数](https://platform.openai.com/docs/api-reference/responses/create),例如 `user`、`service_tier` 等。如果它们在顶层不可用,也可以通过 `extra_args` 传递。请勿同时通过直接 `ModelSettings` 字段设置相同的请求字段。
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelSettings
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
model="gpt-4.1",
|
||||
model_settings=ModelSettings(
|
||||
temperature=0.1,
|
||||
extra_args={"service_tier": "flex", "user": "user_12345"},
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
## Runner 管理的重试
|
||||
|
||||
重试仅在运行时生效,并且需要主动启用。除非设置 `ModelSettings(retry=...)` 且重试策略决定重试,否则 SDK 不会重试常规模型请求。
|
||||
|
||||
```python
|
||||
from agents import Agent, ModelRetrySettings, ModelSettings, retry_policies
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
model="gpt-5.6-sol",
|
||||
model_settings=ModelSettings(
|
||||
retry=ModelRetrySettings(
|
||||
max_retries=4,
|
||||
backoff={
|
||||
"initial_delay": 0.5,
|
||||
"max_delay": 5.0,
|
||||
"multiplier": 2.0,
|
||||
"jitter": True,
|
||||
},
|
||||
policy=retry_policies.any(
|
||||
retry_policies.provider_suggested(),
|
||||
retry_policies.retry_after(),
|
||||
retry_policies.network_error(),
|
||||
retry_policies.http_status([408, 409, 429, 500, 502, 503, 504]),
|
||||
),
|
||||
)
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`ModelRetrySettings` 包含三个字段:
|
||||
|
||||
<div class="field-table" markdown="1">
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `max_retries` | `int | None` | 初始请求之后允许的重试次数。 |
|
||||
| `backoff` | `ModelRetryBackoffSettings | dict | None` | 策略决定重试但未返回显式延迟时使用的默认延迟策略。`backoff.max_delay` 仅限制此处计算出的退避延迟,不限制策略返回的显式延迟或 Retry-After 提示。 |
|
||||
| `policy` | `RetryPolicy | None` | 决定是否重试的回调。此字段仅在运行时生效,不会被序列化。 |
|
||||
|
||||
</div>
|
||||
|
||||
重试策略会接收一个 [`RetryPolicyContext`][agents.retry.RetryPolicyContext],其中包含:
|
||||
|
||||
- `attempt` 和 `max_retries`,便于根据尝试次数做出决策。
|
||||
- `stream`,便于区分流式传输与非流式传输行为。
|
||||
- `error`,用于检查原始错误。
|
||||
- `normalized` 事实,例如 `status_code`、`retry_after`、`error_code`、`is_network_error`、`is_timeout` 和 `is_abort`。
|
||||
- `provider_advice`,用于底层模型适配器可以提供重试建议的情况。
|
||||
|
||||
策略可以返回:
|
||||
|
||||
- `True` / `False`,用于简单的重试决策。
|
||||
- [`RetryDecision`][agents.retry.RetryDecision],用于覆盖延迟或附加诊断原因。
|
||||
|
||||
SDK 在 `retry_policies` 上导出了现成的辅助函数:
|
||||
|
||||
| 辅助函数 | 行为 |
|
||||
| --- | --- |
|
||||
| `retry_policies.never()` | 始终不重试。 |
|
||||
| `retry_policies.provider_suggested()` | 在提供方提供重试建议时遵循该建议。 |
|
||||
| `retry_policies.network_error()` | 匹配暂时性传输失败和超时失败。 |
|
||||
| `retry_policies.http_status([...])` | 匹配指定的 HTTP 状态码。 |
|
||||
| `retry_policies.retry_after()` | 仅在存在 Retry-After 提示时重试,并使用该延迟。此辅助函数将 Retry-After 值视为显式策略延迟,因此 `backoff.max_delay` 不会限制它。 |
|
||||
| `retry_policies.any(...)` | 任意嵌套策略决定重试时进行重试。 |
|
||||
| `retry_policies.all(...)` | 仅在所有嵌套策略都决定重试时进行重试。 |
|
||||
|
||||
组合策略时,`provider_suggested()` 是最安全的首选基础组件,因为当提供方能够区分相关情况时,它会保留提供方的否决意见和重放安全性批准。
|
||||
|
||||
##### 安全边界
|
||||
|
||||
某些失败永远不会自动重试:
|
||||
|
||||
- 中止错误。
|
||||
- 提供方建议将重放标记为不安全的请求。
|
||||
- 已开始产生输出且重放会导致不安全的流式传输运行。
|
||||
|
||||
使用 `previous_response_id` 或 `conversation_id` 的有状态后续请求也会受到更保守的处理。对于这些请求,`network_error()` 或 `http_status([500])` 等非提供方判断条件本身并不足够。重试策略应包含提供方对重放安全性的批准,通常通过 `retry_policies.provider_suggested()` 实现。
|
||||
|
||||
##### Runner 与智能体的合并行为
|
||||
|
||||
运行级和智能体级 `ModelSettings` 之间会对 `retry` 进行深度合并:
|
||||
|
||||
- 智能体可以仅覆盖 `retry.max_retries`,同时继承 Runner 的 `policy`。
|
||||
- 智能体可以仅覆盖 `retry.backoff` 的一部分,同时保留 Runner 中同级的其他退避字段。
|
||||
- `policy` 仅在运行时生效,因此序列化后的 `ModelSettings` 会保留 `max_retries` 和 `backoff`,但省略回调本身。
|
||||
|
||||
更完整的代码示例请参阅 [`examples/basic/retry.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/retry.py) 和[由适配器支持的重试示例](https://github.com/openai/openai-agents-python/tree/main/examples/basic/retry_litellm.py)。
|
||||
|
||||
## 非OpenAI提供方故障排查
|
||||
|
||||
### 追踪客户端错误 401
|
||||
|
||||
如果遇到与追踪有关的错误,这是因为追踪数据会上传到OpenAI服务,而你没有OpenAI API 密钥。可以通过以下三种方式解决:
|
||||
|
||||
1. 完全禁用追踪:[`set_tracing_disabled(True)`][agents.set_tracing_disabled]。
|
||||
2. 为追踪设置OpenAI密钥:[`set_tracing_export_api_key(...)`][agents.set_tracing_export_api_key]。此 API 密钥仅用于上传追踪数据,并且必须来自 [platform.openai.com](https://platform.openai.com/)。
|
||||
3. 使用非OpenAI追踪进程。请参阅[追踪文档](../tracing.md#custom-tracing-processors)。
|
||||
|
||||
### Responses API 支持
|
||||
|
||||
SDK 默认使用 Responses API,但许多其他 LLM 提供方仍不支持它。因此,你可能会看到 404 或类似问题。可以通过以下两种方式解决:
|
||||
|
||||
1. 调用 [`set_default_openai_api("chat_completions")`][agents.set_default_openai_api]。如果通过环境变量设置 `OPENAI_API_KEY` 和 `OPENAI_BASE_URL`,此方法即可生效。
|
||||
2. 使用 [`OpenAIChatCompletionsModel`][agents.models.openai_chatcompletions.OpenAIChatCompletionsModel]。代码示例请参阅[此处](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/)。
|
||||
|
||||
### Chat Completions 兼容性选项
|
||||
|
||||
通过 Chat Completions 进行路由时,SDK 会静默丢弃 Chat Completions 无法发送的 Responses 独有字段,从而保持兼容性,例如 `previous_response_id`、`conversation_id`、提示词或并非纯文本的工具输出。如果希望在开发期间遇到这些不匹配时快速失败,请在OpenAI提供方上启用严格功能验证:
|
||||
|
||||
```python
|
||||
from agents import Agent, OpenAIProvider, RunConfig, Runner
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses=False,
|
||||
strict_feature_validation=True,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
run_config=RunConfig(model_provider=provider),
|
||||
)
|
||||
```
|
||||
|
||||
如果使用 [`MultiProvider`][agents.MultiProvider],请改为传入 `openai_strict_feature_validation=True`。
|
||||
|
||||
一些OpenAI兼容的 Chat Completions 提供方会以分块形式流式传输工具调用增量,而这些分块不够可靠,无法供 SDK 进行增量处理。在这种情况下,请启用流式传输工具调用缓冲,让 SDK 仅在提供方流结束后生成工具调用:
|
||||
|
||||
```python
|
||||
from agents import OpenAIProvider
|
||||
|
||||
provider = OpenAIProvider(
|
||||
use_responses=False,
|
||||
buffer_streamed_tool_calls=True,
|
||||
)
|
||||
```
|
||||
|
||||
对于 [`MultiProvider`][agents.MultiProvider],请使用 `openai_buffer_streamed_tool_calls=True`。
|
||||
|
||||
### structured outputs 支持
|
||||
|
||||
一些模型提供方不支持 [structured outputs](https://platform.openai.com/docs/guides/structured-outputs)。这有时会导致类似如下的错误:
|
||||
|
||||
```
|
||||
|
||||
BadRequestError: Error code: 400 - {'error': {'message': "'response_format.type' : value is not one of the allowed values ['text','json_object']", 'type': 'invalid_request_error'}}
|
||||
|
||||
```
|
||||
|
||||
这是某些模型提供方的局限:它们支持 JSON 输出,但不允许指定输出所使用的 `json_schema`。我们正在修复此问题,但建议依赖支持 JSON Schema 输出的提供方,否则应用经常会因格式错误的 JSON 而中断。
|
||||
|
||||
## 跨提供方混用模型
|
||||
|
||||
你需要了解不同模型提供方之间的功能差异,否则可能遇到错误。例如,OpenAI支持 structured outputs、多模态输入以及托管式文件检索和网络检索,但许多其他提供方并不支持这些功能。请注意以下限制:
|
||||
|
||||
- 不要向无法理解 `tools` 的提供方发送不受支持的 `tools`
|
||||
- 调用纯文本模型前,请过滤掉多模态输入
|
||||
- 请注意,不支持结构化 JSON 输出的提供方偶尔会生成无效 JSON。
|
||||
|
||||
## 第三方适配器
|
||||
|
||||
仅当 SDK 的内置提供方集成点无法满足需求时,才应使用第三方适配器。如果只通过此 SDK 使用OpenAI模型,请优先使用内置 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 路径,而不是 Any-LLM 或 LiteLLM。第三方适配器适用于需要将OpenAI模型与非OpenAI提供方结合使用,或需要由适配器管理的提供方覆盖或内置路径无法提供的路由。适配器会在 SDK 和上游模型提供方之间增加一层兼容层,因此功能支持和请求语义可能因提供方而异。目前,SDK 以尽力支持的测试版适配器集成形式提供 Any-LLM 和 LiteLLM。
|
||||
|
||||
### Any-LLM
|
||||
|
||||
Any-LLM 支持以尽力支持的测试版形式提供,适用于需要由 Any-LLM 管理的提供方覆盖或路由的情况。
|
||||
|
||||
根据上游提供方路径,Any-LLM 可能使用 Responses API、Chat Completions 兼容 API 或提供方特定的兼容层。
|
||||
|
||||
如果需要 Any-LLM,请安装 `openai-agents[any-llm]`,然后从 [`examples/model_providers/any_llm_auto.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/any_llm_auto.py) 或 [`examples/model_providers/any_llm_provider.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/any_llm_provider.py) 开始。可以将 `any-llm/...` 模型名称与 [`MultiProvider`][agents.MultiProvider] 一起使用、直接实例化 `AnyLLMModel`,或在运行作用域使用 `AnyLLMProvider`。如果需要显式固定模型接口,请在构造 `AnyLLMModel` 时传入 `api="responses"` 或 `api="chat_completions"`。
|
||||
|
||||
Any-LLM 仍然是第三方适配器层,因此提供方依赖项和能力缺口由上游 Any-LLM 而非 SDK 定义。当上游提供方返回用量指标时,这些指标会自动传播,但流式传输 Chat Completions 后端可能需要设置 `ModelSettings(include_usage=True)` 才会生成用量数据块。如果依赖 structured outputs、工具调用、用量报告或 Responses 特定行为,请验证计划部署的具体提供方后端。
|
||||
|
||||
### LiteLLM
|
||||
|
||||
LiteLLM 支持以尽力支持的测试版形式提供,适用于需要 LiteLLM 特定提供方覆盖或路由的情况。
|
||||
|
||||
如果需要 LiteLLM,请安装 `openai-agents[litellm]`,然后从 [`examples/model_providers/litellm_auto.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/litellm_auto.py) 或 [`examples/model_providers/litellm_provider.py`](https://github.com/openai/openai-agents-python/tree/main/examples/model_providers/litellm_provider.py) 开始。可以使用 `litellm/...` 模型名称,或直接实例化 [`LitellmModel`][agents.extensions.models.litellm_model.LitellmModel]。
|
||||
|
||||
一些由 LiteLLM 支持的提供方默认不会填充 SDK 用量指标。如果需要用量报告,请传入 `ModelSettings(include_usage=True)`;如果依赖 structured outputs、工具调用、用量报告或适配器特定的路由行为,请验证计划部署的具体提供方后端。
|
||||
@@ -0,0 +1,13 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# LiteLLM
|
||||
|
||||
<script>
|
||||
window.location.replace("../#third-party-adapters");
|
||||
</script>
|
||||
|
||||
此页面已移至[模型中的第三方适配器部分](index.md#third-party-adapters)。
|
||||
|
||||
如果没有自动重定向,请使用上面的链接。
|
||||
@@ -0,0 +1,64 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 智能体编排
|
||||
|
||||
编排指的是应用中智能体的流程:哪些智能体会运行、以什么顺序运行,以及它们如何决定接下来发生什么?编排智能体主要有两种方式:
|
||||
|
||||
1. 让 LLM 做决策:利用 LLM 的智能进行规划、推理,并据此决定要采取哪些步骤。
|
||||
2. 通过代码编排:通过你的代码来确定智能体的流程。
|
||||
|
||||
你也可以混合搭配这些模式。它们各有取舍,如下所述。
|
||||
|
||||
## 通过 LLM 编排
|
||||
|
||||
智能体是配备了 instructions、tools 和任务转移的 LLM。这意味着,面对开放式任务时,LLM 可以自主规划如何处理任务:使用工具执行操作并获取数据,并使用任务转移将任务委派给子智能体。例如,一个研究智能体可以配备如下工具:
|
||||
|
||||
- 网络检索,用于在线查找信息
|
||||
- 文件检索与检索,用于搜索专有数据和连接
|
||||
- 计算机操作,用于在计算机上执行操作
|
||||
- 代码执行,用于进行数据分析
|
||||
- 任务转移,用于转交给擅长规划、报告撰写等工作的专门智能体。
|
||||
|
||||
### 核心 SDK 模式
|
||||
|
||||
在 Python SDK 中,最常见的两种编排模式是:
|
||||
|
||||
| 模式 | 工作方式 | 最适合的场景 |
|
||||
| --- | --- | --- |
|
||||
| Agents as tools | 管理器智能体保持对对话的控制,并通过 `Agent.as_tool()` 调用专门智能体。 | 你希望由一个智能体负责最终答案、组合多个专门智能体的输出,或在一个地方统一执行共享的安全防护措施。 |
|
||||
| 任务转移 | 分诊智能体将对话路由到专门智能体,而该专门智能体会在本轮剩余过程中成为活跃智能体。 | 你希望专门智能体直接响应、保持提示词聚焦,或在不由管理器叙述结果的情况下切换 instructions。 |
|
||||
|
||||
当专门智能体应协助完成一个有边界的子任务、但不应接管面向用户的对话时,请使用**agents as tools**。当路由本身是工作流的一部分,并且你希望被选中的专门智能体负责交互的下一部分时,请使用**任务转移**。
|
||||
|
||||
你也可以将两者结合使用。分诊智能体可以任务转移给专门智能体,而该专门智能体仍然可以将其他智能体作为工具来调用,以处理范围较窄的子任务。
|
||||
|
||||
当任务是开放式的,并且你希望依赖 LLM 的智能时,这种模式非常适合。这里最重要的策略是:
|
||||
|
||||
1. 投入精力编写好的提示词。明确说明有哪些工具可用、如何使用它们,以及必须在哪些参数范围内运行。
|
||||
2. 监控你的应用并不断迭代。观察问题出现在哪里,并迭代你的提示词。
|
||||
3. 允许智能体自省并改进。例如,让它在循环中运行并自我评议;或者提供错误消息,让它改进。
|
||||
4. 使用在某一项任务上表现出色的专门智能体,而不是期望一个通用智能体擅长所有事情。
|
||||
5. 投入使用[评估](https://platform.openai.com/docs/guides/evals)。这可以帮助你训练智能体,使其改进并更擅长完成任务。
|
||||
|
||||
如果你想了解这种编排方式背后的核心 SDK 基础组件,请从[工具](tools.md)、[任务转移](handoffs.md)和[运行智能体](running_agents.md)开始。
|
||||
|
||||
## 通过代码编排
|
||||
|
||||
虽然通过 LLM 编排非常强大,但从速度、成本和性能角度来看,通过代码编排可以让任务更具确定性和可预测性。这里的常见模式包括:
|
||||
|
||||
- 使用 [structured outputs](https://platform.openai.com/docs/guides/structured-outputs) 生成格式良好的数据,供你的代码检查。例如,你可以要求智能体将任务归类到几个目录中,然后基于目录选择下一个智能体。
|
||||
- 通过将一个智能体的输出转换为下一个智能体的输入来串联多个智能体。你可以将撰写博客文章这样的任务分解为一系列步骤——做研究、写大纲、撰写博客文章、进行评议,然后改进它。
|
||||
- 将执行任务的智能体放在 `while` 循环中运行,并配合一个负责评估和提供反馈的智能体,直到评估者认为输出满足某些标准。
|
||||
- 并行运行多个智能体,例如通过 Python 的 `asyncio.gather` 等基础组件实现。当你有多个彼此不依赖的任务时,这有助于提升速度。
|
||||
|
||||
我们在 [`examples/agent_patterns`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns) 中提供了许多代码示例。
|
||||
|
||||
## 相关指南
|
||||
|
||||
- [智能体](agents.md):组合模式和智能体配置。
|
||||
- [工具](tools.md#agents-as-tools):`Agent.as_tool()` 和管理器式编排。
|
||||
- [任务转移](handoffs.md):专门智能体之间的委派。
|
||||
- [运行智能体](running_agents.md):每次运行的编排控制和对话状态。
|
||||
- [快速入门](quickstart.md):一个最小化的端到端任务转移示例。
|
||||
@@ -0,0 +1,225 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 快速入门
|
||||
|
||||
## 项目与虚拟环境的创建
|
||||
|
||||
你只需要执行一次。
|
||||
|
||||
```bash
|
||||
mkdir my_project
|
||||
cd my_project
|
||||
python -m venv .venv
|
||||
```
|
||||
|
||||
### 虚拟环境的激活
|
||||
|
||||
每次启动新的终端会话时都需要执行此操作。
|
||||
|
||||
在 macOS 或 Linux 上:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
在 Windows 上:
|
||||
|
||||
```cmd
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
### Agents SDK 的安装
|
||||
|
||||
```bash
|
||||
pip install openai-agents # or `uv add openai-agents`, etc
|
||||
```
|
||||
|
||||
### OpenAI API 密钥的设置
|
||||
|
||||
如果你还没有密钥,请按照[这些说明](https://platform.openai.com/docs/quickstart#create-and-export-an-api-key)创建 OpenAI API 密钥。
|
||||
|
||||
这些命令会为当前终端会话设置密钥。
|
||||
|
||||
在 macOS 或 Linux 上:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY=sk-...
|
||||
```
|
||||
|
||||
在 Windows PowerShell 上:
|
||||
|
||||
```powershell
|
||||
$env:OPENAI_API_KEY = "sk-..."
|
||||
```
|
||||
|
||||
在 Windows 命令提示符上:
|
||||
|
||||
```cmd
|
||||
set "OPENAI_API_KEY=sk-..."
|
||||
```
|
||||
|
||||
## 首个智能体的创建
|
||||
|
||||
智能体由 instructions、名称以及特定模型等可选配置定义。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
```
|
||||
|
||||
## 首个智能体的运行
|
||||
|
||||
使用 [`Runner`][agents.run.Runner] 执行智能体,并获取返回的 [`RunResult`][agents.result.RunResult]。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(agent, "When did the Roman Empire fall?")
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
对于第二轮对话,你可以将 `result.to_input_list()` 传回 `Runner.run(...)`,附加一个[会话](sessions/index.md),或者使用 `conversation_id` / `previous_response_id` 复用 OpenAI 服务端管理的状态。[运行智能体](running_agents.md)指南会比较这些方法。
|
||||
|
||||
可按以下经验法则选择:
|
||||
|
||||
| 如果你想要... | 从...开始 |
|
||||
| --- | --- |
|
||||
| 完全手动控制和与提供商无关的历史记录 | `result.to_input_list()` |
|
||||
| 由 SDK 为你加载和保存历史记录 | [`session=...`](sessions/index.md) |
|
||||
| 由 OpenAI 管理的服务端延续 | `previous_response_id` 或 `conversation_id` |
|
||||
|
||||
有关权衡取舍和确切行为,请参阅[运行智能体](running_agents.md#choose-a-memory-strategy)。
|
||||
|
||||
当任务主要存在于提示词、工具和对话状态中时,使用普通的 `Agent` 加 `Runner`。如果智能体需要在隔离的工作区中检查或修改真实文件,请转到[沙盒智能体快速入门](sandbox_agents.md)。
|
||||
|
||||
## 智能体工具的提供
|
||||
|
||||
你可以为智能体提供工具,用于查找信息或执行操作。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def history_fun_fact() -> str:
|
||||
"""Return a short history fact."""
|
||||
return "Sharks are older than trees."
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="History Tutor",
|
||||
instructions="Answer history questions clearly. Use history_fun_fact when it helps.",
|
||||
tools=[history_fun_fact],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Tell me something surprising about ancient life on Earth.",
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 更多智能体的添加
|
||||
|
||||
在选择多智能体模式之前,请决定最终答案应由谁负责:
|
||||
|
||||
- **任务转移**:专家智能体会接管该轮对话中的相应部分。
|
||||
- **Agents as tools**:编排者保持控制,并将专家智能体作为工具调用。
|
||||
|
||||
本快速入门继续使用**任务转移**,因为这是最短的入门示例。有关管理者式模式,请参阅[智能体编排](multi_agent.md)和[工具:agents as tools](tools.md#agents-as-tools)。
|
||||
|
||||
其他智能体也可以用同样的方式定义。`handoff_description` 会为路由智能体提供有关何时委派的额外上下文。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
|
||||
history_tutor_agent = Agent(
|
||||
name="History Tutor",
|
||||
handoff_description="Specialist agent for historical questions",
|
||||
instructions="You answer history questions clearly and concisely.",
|
||||
)
|
||||
|
||||
math_tutor_agent = Agent(
|
||||
name="Math Tutor",
|
||||
handoff_description="Specialist agent for math questions",
|
||||
instructions="You explain math step by step and include worked examples.",
|
||||
)
|
||||
```
|
||||
|
||||
## 任务转移的定义
|
||||
|
||||
在智能体上,你可以定义一组可选的外部任务转移选项,供它在解决任务时选择。
|
||||
|
||||
```python
|
||||
triage_agent = Agent(
|
||||
name="Triage Agent",
|
||||
instructions="Route each homework question to the right specialist.",
|
||||
handoffs=[history_tutor_agent, math_tutor_agent],
|
||||
)
|
||||
```
|
||||
|
||||
## 智能体编排的运行
|
||||
|
||||
运行器会处理各个智能体的执行、所有任务转移以及所有工具调用。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Runner
|
||||
|
||||
|
||||
async def main():
|
||||
result = await Runner.run(
|
||||
triage_agent,
|
||||
"Who was the first president of the United States?",
|
||||
)
|
||||
print(result.final_output)
|
||||
print(f"Answered by: {result.last_agent.name}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 参考代码示例
|
||||
|
||||
仓库包含相同核心模式的完整脚本:
|
||||
|
||||
- [`examples/basic/hello_world.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/hello_world.py) 用于首次运行。
|
||||
- [`examples/basic/tools.py`](https://github.com/openai/openai-agents-python/tree/main/examples/basic/tools.py) 用于工具调用。
|
||||
- [`examples/agent_patterns/routing.py`](https://github.com/openai/openai-agents-python/tree/main/examples/agent_patterns/routing.py) 用于多智能体路由。
|
||||
|
||||
## 追踪的查看
|
||||
|
||||
若要回顾智能体运行期间发生的情况,请前往 [OpenAI Dashboard 中的追踪查看器](https://platform.openai.com/traces),查看智能体运行的追踪。
|
||||
|
||||
## 后续步骤
|
||||
|
||||
了解如何构建更复杂的智能体式流程:
|
||||
|
||||
- 了解如何配置[智能体](agents.md)。
|
||||
- 了解[运行智能体](running_agents.md)和[会话](sessions/index.md)。
|
||||
- 如果工作应在真实工作区内进行,请了解[沙盒智能体](sandbox_agents.md)。
|
||||
- 了解[工具](tools.md)、[安全防护措施](guardrails.md)和[模型](models/index.md)。
|
||||
@@ -0,0 +1,348 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 实时智能体指南
|
||||
|
||||
本指南说明OpenAI Agents SDK的实时层如何映射到OpenAI Realtime API,以及 Python SDK 在其之上添加了哪些额外行为。
|
||||
|
||||
!!! note "从这里开始"
|
||||
|
||||
如果你想使用默认的 Python 路径,请先阅读[快速入门](quickstart.md)。如果你正在决定应用应使用服务端 WebSocket 还是 SIP,请阅读[实时传输](transport.md)。浏览器 WebRTC 传输不属于 Python SDK。
|
||||
|
||||
## 概览
|
||||
|
||||
实时智能体会与Realtime API保持一个长连接,以便模型可以增量处理文本和音频、流式传输音频输出、调用工具,并处理中断,而无需在每一轮都重新发起请求。
|
||||
|
||||
主要 SDK 组件包括:
|
||||
|
||||
- **RealtimeAgent**:一个实时专家智能体的 instructions、tools、输出安全防护措施和任务转移
|
||||
- **RealtimeRunner**:会话工厂,用于将起始智能体连接到实时传输
|
||||
- **RealtimeSession**:实时会话,用于发送输入、接收事件、跟踪历史记录并执行工具
|
||||
- **RealtimeModel**:传输抽象层。默认使用OpenAI的服务端 WebSocket 实现。
|
||||
|
||||
## 会话生命周期
|
||||
|
||||
典型的实时会话如下:
|
||||
|
||||
1. 创建一个或多个 `RealtimeAgent`。
|
||||
2. 使用起始智能体创建一个 `RealtimeRunner`。
|
||||
3. 调用 `await runner.run()` 获取一个 `RealtimeSession`。
|
||||
4. 使用 `async with session:` 或 `await session.enter()` 进入会话。
|
||||
5. 使用 `send_message()` 或 `send_audio()` 发送用户输入。
|
||||
6. 迭代会话事件,直到对话结束。
|
||||
|
||||
与纯文本运行不同,`runner.run()` 不会立即生成最终结果。它会返回一个实时会话对象,该对象会将本地历史记录、后台工具执行、安全防护措施状态以及活动智能体配置与传输层保持同步。
|
||||
|
||||
默认情况下,`RealtimeRunner` 使用 `OpenAIRealtimeWebSocketModel`,因此默认的 Python 路径是到Realtime API的服务端 WebSocket 连接。如果你传入不同的 `RealtimeModel`,相同的会话生命周期和智能体功能仍然适用,而连接机制可以发生变化。
|
||||
|
||||
## 智能体和会话配置
|
||||
|
||||
`RealtimeAgent` 的范围有意比常规 `Agent` 类型更窄:
|
||||
|
||||
- 模型选择在会话层级配置,而不是按智能体配置。
|
||||
- 不支持structured outputs。
|
||||
- 可以配置音色,但在会话已经生成过语音音频后就不能再更改。
|
||||
- instructions、工具调用、任务转移、钩子和输出安全防护措施仍然都可用。
|
||||
|
||||
`RealtimeSessionModelSettings` 同时支持较新的嵌套式 `audio` 配置和较旧的扁平别名。新代码建议优先使用嵌套形式,并为新的实时智能体从 `gpt-realtime-2.1` 开始:
|
||||
|
||||
```python
|
||||
runner = RealtimeRunner(
|
||||
starting_agent=agent,
|
||||
config={
|
||||
"model_settings": {
|
||||
"model_name": "gpt-realtime-2.1",
|
||||
"audio": {
|
||||
"input": {
|
||||
"format": "pcm16",
|
||||
"transcription": {"model": "gpt-4o-mini-transcribe"},
|
||||
"turn_detection": {"type": "semantic_vad", "interrupt_response": True},
|
||||
},
|
||||
"output": {"format": "pcm16", "voice": "ash"},
|
||||
},
|
||||
"tool_choice": "auto",
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
有用的会话级设置包括:
|
||||
|
||||
- `audio.input.format`、`audio.output.format`
|
||||
- `audio.input.transcription`
|
||||
- `audio.input.noise_reduction`
|
||||
- `audio.input.turn_detection`
|
||||
- `audio.output.voice`、`audio.output.speed`
|
||||
- `output_modalities`
|
||||
- `tool_choice`
|
||||
- `prompt`
|
||||
- `tracing`
|
||||
|
||||
`RealtimeRunner(config=...)` 上有用的运行级设置包括:
|
||||
|
||||
- `async_tool_calls`
|
||||
- `output_guardrails`
|
||||
- `guardrails_settings.debounce_text_length`
|
||||
- `tool_error_formatter`
|
||||
- `tracing_disabled`
|
||||
|
||||
有关完整的类型化接口,请参阅 [`RealtimeRunConfig`][agents.realtime.config.RealtimeRunConfig] 和 [`RealtimeSessionModelSettings`][agents.realtime.config.RealtimeSessionModelSettings]。
|
||||
|
||||
## 输入与输出
|
||||
|
||||
### 文本和结构化用户消息
|
||||
|
||||
使用 [`session.send_message()`][agents.realtime.session.RealtimeSession.send_message] 发送纯文本或结构化实时消息。
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeUserInputMessage
|
||||
|
||||
await session.send_message("Summarize what we discussed so far.")
|
||||
|
||||
message: RealtimeUserInputMessage = {
|
||||
"type": "message",
|
||||
"role": "user",
|
||||
"content": [
|
||||
{"type": "input_text", "text": "Describe this image."},
|
||||
{"type": "input_image", "image_url": image_data_url, "detail": "high"},
|
||||
],
|
||||
}
|
||||
await session.send_message(message)
|
||||
```
|
||||
|
||||
结构化消息是在实时对话中包含图像输入的主要方式。[`examples/realtime/app/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app/server.py) 中的示例 Web 演示就是以这种方式转发 `input_image` 消息。
|
||||
|
||||
### 音频输入
|
||||
|
||||
使用 [`session.send_audio()`][agents.realtime.session.RealtimeSession.send_audio] 流式传输原始音频字节:
|
||||
|
||||
```python
|
||||
await session.send_audio(audio_bytes)
|
||||
```
|
||||
|
||||
如果禁用了服务端轮次检测,你需要自行标记轮次边界。高层便捷方法是:
|
||||
|
||||
```python
|
||||
await session.send_audio(audio_bytes, commit=True)
|
||||
```
|
||||
|
||||
如果需要更低层级的控制,也可以通过底层模型传输发送原始客户端事件,例如 `input_audio_buffer.commit`。
|
||||
|
||||
### 手动响应控制
|
||||
|
||||
`session.send_message()` 使用高层路径发送用户输入,并为你启动响应。原始音频缓冲并**不会**在所有配置中自动执行同样操作。
|
||||
|
||||
在Realtime API层面,手动轮次控制意味着用原始 `session.update` 清除 `turn_detection`,然后自行发送 `input_audio_buffer.commit` 和 `response.create`。
|
||||
|
||||
如果你正在手动管理轮次,可以通过模型传输发送原始客户端事件:
|
||||
|
||||
```python
|
||||
from agents.realtime.model_inputs import RealtimeModelSendRawMessage
|
||||
|
||||
await session.model.send_event(
|
||||
RealtimeModelSendRawMessage(
|
||||
message={
|
||||
"type": "response.create",
|
||||
}
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
此模式适用于以下场景:
|
||||
|
||||
- `turn_detection` 已禁用,并且你想自行决定模型应在何时响应
|
||||
- 你想在触发响应之前检查用户输入或对其进行门控
|
||||
- 你需要为带外响应使用自定义提示词
|
||||
|
||||
[`examples/realtime/twilio_sip/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip/server.py) 中的 SIP 示例使用原始 `response.create` 来强制发送开场问候。
|
||||
|
||||
## 事件、历史记录和中断
|
||||
|
||||
`RealtimeSession` 会发出更高层级的 SDK 事件,同时在需要时仍会转发原始模型事件。
|
||||
|
||||
重要的会话事件包括:
|
||||
|
||||
- `audio`、`audio_end`、`audio_interrupted`
|
||||
- `agent_start`、`agent_end`
|
||||
- `tool_start`、`tool_end`、`tool_approval_required`
|
||||
- `handoff`
|
||||
- `history_added`、`history_updated`
|
||||
- `guardrail_tripped`
|
||||
- `input_audio_timeout_triggered`
|
||||
- `error`
|
||||
- `raw_model_event`
|
||||
|
||||
对 UI 状态最有用的事件通常是 `history_added` 和 `history_updated`。它们会以 `RealtimeItem` 对象形式公开会话的本地历史记录,包括用户消息、助手消息和工具调用。
|
||||
|
||||
### 中断和播放跟踪
|
||||
|
||||
当用户打断助手时,会话会发出 `audio_interrupted` 并更新历史记录,使服务端对话与用户实际听到的内容保持一致。
|
||||
|
||||
在低延迟本地播放中,默认播放跟踪器通常已经足够。在远程或延迟播放场景中,尤其是电话通信,请使用 [`RealtimePlaybackTracker`][agents.realtime.model.RealtimePlaybackTracker],以便中断截断基于实际播放进度,而不是假设所有已生成音频都已经被听到。
|
||||
|
||||
[`examples/realtime/twilio/twilio_handler.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio/twilio_handler.py) 中的 Twilio 示例展示了此模式。
|
||||
|
||||
## 工具、审批、任务转移和安全防护措施
|
||||
|
||||
### 工具调用
|
||||
|
||||
实时智能体支持在实时对话期间使用工具调用:
|
||||
|
||||
```python
|
||||
from agents import function_tool
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get current weather for a city."""
|
||||
return f"The weather in {city} is sunny, 72F."
|
||||
|
||||
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="You can answer weather questions.",
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
### 工具审批
|
||||
|
||||
工具调用可以要求在执行前获得人工审批。发生这种情况时,会话会发出 `tool_approval_required`,并暂停工具运行,直到你调用 `approve_tool_call()` 或 `reject_tool_call()`。
|
||||
|
||||
如果该工具还具有输入安全防护措施,这些安全防护措施会在审批通过后、执行前立即运行。若要在发出审批事件之前运行它们,请使用 `RealtimeRunner(..., config={"tool_execution": {"pre_approval_tool_input_guardrails": True}})` 创建 runner。通过此预审批检查的调用,在审批后、执行前仍会再次检查。
|
||||
|
||||
```python
|
||||
async for event in session:
|
||||
if event.type == "tool_approval_required":
|
||||
await session.approve_tool_call(event.call_id)
|
||||
```
|
||||
|
||||
有关具体的服务端审批循环,请参阅 [`examples/realtime/app/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app/server.py)。人在环路文档也会在[人在环路](../human_in_the_loop.md)中回指此流程。
|
||||
|
||||
### 任务转移
|
||||
|
||||
实时任务转移让一个智能体可以将实时对话转移给另一位专家:
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeAgent, realtime_handoff
|
||||
|
||||
billing_agent = RealtimeAgent(
|
||||
name="Billing Support",
|
||||
instructions="You specialize in billing issues.",
|
||||
)
|
||||
|
||||
main_agent = RealtimeAgent(
|
||||
name="Customer Service",
|
||||
instructions="Triage the request and hand off when needed.",
|
||||
handoffs=[
|
||||
realtime_handoff(
|
||||
billing_agent,
|
||||
tool_description_override="Transfer to billing support",
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
裸 `RealtimeAgent` 任务转移会被自动包装,而 `realtime_handoff(...)` 可用于自定义名称、描述、验证、回调和可用性。实时任务转移不支持常规任务转移的 `input_filter`。
|
||||
|
||||
### 安全防护措施
|
||||
|
||||
实时智能体支持针对智能体响应的输出安全防护措施,以及针对工具调用的输入安全防护措施。输出安全防护措施在经过防抖的转写累积文本上运行,而不是在每个部分 token 上运行,并且它们会发出 `guardrail_tripped`,而不是抛出异常。
|
||||
|
||||
```python
|
||||
from agents.guardrail import GuardrailFunctionOutput, OutputGuardrail
|
||||
|
||||
|
||||
def sensitive_data_check(context, agent, output):
|
||||
return GuardrailFunctionOutput(
|
||||
tripwire_triggered="password" in output,
|
||||
output_info=None,
|
||||
)
|
||||
|
||||
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="...",
|
||||
output_guardrails=[OutputGuardrail(guardrail_function=sensitive_data_check)],
|
||||
)
|
||||
```
|
||||
|
||||
当实时输出安全防护措施触发时,会话会中断活动响应,强制执行 `response.cancel`,发出 `guardrail_tripped`,并发送一条后续用户消息,指明被触发的安全防护措施,以便模型生成替代响应。你的音频播放器仍应监听 `audio_interrupted` 并立即停止本地播放,因为安全防护措施是在经过防抖的转写文本上运行的,触发条件触发时可能已有部分音频被缓冲。
|
||||
|
||||
## SIP 和电话通信
|
||||
|
||||
Python SDK 通过 [`OpenAIRealtimeSIPModel`][agents.realtime.openai_realtime.OpenAIRealtimeSIPModel] 提供一等的 SIP 附加流程。
|
||||
|
||||
当呼叫通过 Realtime Calls API 到达,并且你想将智能体会话附加到生成的 `call_id` 时,请使用它:
|
||||
|
||||
```python
|
||||
from agents.realtime import RealtimeRunner
|
||||
from agents.realtime.openai_realtime import OpenAIRealtimeSIPModel
|
||||
|
||||
runner = RealtimeRunner(starting_agent=agent, model=OpenAIRealtimeSIPModel())
|
||||
|
||||
async with await runner.run(
|
||||
model_config={
|
||||
"call_id": call_id_from_webhook,
|
||||
}
|
||||
) as session:
|
||||
async for event in session:
|
||||
...
|
||||
```
|
||||
|
||||
如果你需要先接受呼叫,并希望接受载荷与从智能体派生出的会话配置相匹配,请使用 `OpenAIRealtimeSIPModel.build_initial_session_payload(...)`。完整流程展示在 [`examples/realtime/twilio_sip/server.py`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip/server.py) 中。
|
||||
|
||||
## 低层访问和自定义端点
|
||||
|
||||
你可以通过 `session.model` 访问底层传输对象。
|
||||
|
||||
在以下情况下使用它:
|
||||
|
||||
- 通过 `session.model.add_listener(...)` 使用自定义监听器
|
||||
- 使用原始客户端事件,例如 `response.create` 或 `session.update`
|
||||
- 通过 `model_config` 处理自定义 `url`、`headers` 或 `api_key`
|
||||
- 将 `call_id` 附加到现有实时通话
|
||||
|
||||
`RealtimeModelConfig` 支持:
|
||||
|
||||
- `api_key`
|
||||
- `url`
|
||||
- `headers`
|
||||
- `initial_model_settings`
|
||||
- `playback_tracker`
|
||||
- `call_id`
|
||||
|
||||
本仓库随附的 `call_id` 示例是 SIP。更广泛的Realtime API也会在某些服务端控制流中使用 `call_id`,但这里没有将这些流程打包为 Python 代码示例。
|
||||
|
||||
连接到Azure OpenAI时,请传入 GA Realtime 端点 URL 和显式 headers。例如:
|
||||
|
||||
```python
|
||||
session = await runner.run(
|
||||
model_config={
|
||||
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
|
||||
"headers": {"api-key": "<your-azure-api-key>"},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
对于基于 token 的身份验证,请在 `headers` 中使用 bearer token:
|
||||
|
||||
```python
|
||||
session = await runner.run(
|
||||
model_config={
|
||||
"url": "wss://<your-resource>.openai.azure.com/openai/v1/realtime?model=<deployment-name>",
|
||||
"headers": {"authorization": f"Bearer {token}"},
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
如果传入 `headers`,SDK 不会自动添加 `Authorization`。在实时智能体中避免使用旧版 beta 路径(`/openai/realtime?api-version=...`)。
|
||||
|
||||
## 更多阅读
|
||||
|
||||
- [实时传输](transport.md)
|
||||
- [快速入门](quickstart.md)
|
||||
- [OpenAI Realtime 对话](https://developers.openai.com/api/docs/guides/realtime-conversations/)
|
||||
- [OpenAI Realtime 服务端控制](https://developers.openai.com/api/docs/guides/realtime-server-controls/)
|
||||
- [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime)
|
||||
@@ -0,0 +1,158 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 快速入门
|
||||
|
||||
Python SDK 中的实时智能体是基于 WebSocket 传输之上的 OpenAI Realtime API 构建的服务端低延迟智能体。
|
||||
|
||||
!!! note "Python SDK 边界"
|
||||
|
||||
Python SDK **不**提供浏览器 WebRTC 传输。本页仅介绍由 Python 管理、通过服务端 WebSocket 进行的实时会话。使用此 SDK 进行服务端编排、工具、审批以及电话集成。另请参阅[实时传输](transport.md)。
|
||||
|
||||
## 前提条件
|
||||
|
||||
- Python 3.10 或更高版本
|
||||
- OpenAI API 密钥
|
||||
- 对 OpenAI Agents SDK 有基本了解
|
||||
|
||||
## 安装
|
||||
|
||||
如果尚未安装,请安装 OpenAI Agents SDK:
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
## 服务端实时会话创建
|
||||
|
||||
### 1. 实时组件导入
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents.realtime import RealtimeAgent, RealtimeRunner
|
||||
```
|
||||
|
||||
### 2. 起始智能体定义
|
||||
|
||||
```python
|
||||
agent = RealtimeAgent(
|
||||
name="Assistant",
|
||||
instructions="You are a helpful voice assistant. Keep responses short and conversational.",
|
||||
)
|
||||
```
|
||||
|
||||
### 3. runner 配置
|
||||
|
||||
对于新代码,优先使用嵌套的 `audio.input` / `audio.output` 会话设置结构。对于新的实时智能体,请从 `gpt-realtime-2.1` 开始。
|
||||
|
||||
```python
|
||||
runner = RealtimeRunner(
|
||||
starting_agent=agent,
|
||||
config={
|
||||
"model_settings": {
|
||||
"model_name": "gpt-realtime-2.1",
|
||||
"audio": {
|
||||
"input": {
|
||||
"format": "pcm16",
|
||||
"transcription": {"model": "gpt-4o-mini-transcribe"},
|
||||
"turn_detection": {
|
||||
"type": "semantic_vad",
|
||||
"interrupt_response": True,
|
||||
},
|
||||
},
|
||||
"output": {
|
||||
"format": "pcm16",
|
||||
"voice": "ash",
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### 4. 会话启动与输入发送
|
||||
|
||||
`runner.run()` 返回一个 `RealtimeSession`。当你进入会话上下文时,连接会被打开。
|
||||
|
||||
```python
|
||||
async def main() -> None:
|
||||
session = await runner.run()
|
||||
|
||||
async with session:
|
||||
await session.send_message("Say hello in one short sentence.")
|
||||
|
||||
async for event in session:
|
||||
if event.type == "audio":
|
||||
# Forward or play event.audio.data.
|
||||
pass
|
||||
elif event.type == "history_added":
|
||||
print(event.item)
|
||||
elif event.type == "agent_end":
|
||||
# One assistant turn finished.
|
||||
break
|
||||
elif event.type == "error":
|
||||
print(f"Error: {event.error}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`session.send_message()` 接受纯字符串或结构化实时消息。对于原始音频块,请使用 [`session.send_audio()`][agents.realtime.session.RealtimeSession.send_audio]。
|
||||
|
||||
## 本快速入门未包含的内容
|
||||
|
||||
- 麦克风采集和扬声器播放代码。请参阅 [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime) 中的实时代码示例。
|
||||
- SIP / 电话附加流程。请参阅[实时传输](transport.md)和 [SIP 部分](guide.md#sip-and-telephony)。
|
||||
|
||||
## 关键设置
|
||||
|
||||
基本会话运行后,大多数人接下来会用到的设置包括:
|
||||
|
||||
- `model_name`
|
||||
- `audio.input.format`、`audio.output.format`
|
||||
- `audio.input.transcription`
|
||||
- `audio.input.noise_reduction`
|
||||
- `audio.input.turn_detection` 用于自动轮次检测
|
||||
- `audio.output.voice`
|
||||
- `tool_choice`、`prompt`、`tracing`
|
||||
- `async_tool_calls`、`tool_execution.pre_approval_tool_input_guardrails`、`guardrails_settings.debounce_text_length`、`tool_error_formatter`
|
||||
|
||||
较旧的扁平别名(如 `input_audio_format`、`output_audio_format`、`input_audio_transcription` 和 `turn_detection`)仍然可用,但新代码首选嵌套的 `audio` 设置。
|
||||
|
||||
对于手动轮次控制,请使用原始的 `session.update` / `input_audio_buffer.commit` / `response.create` 流程,具体如[实时智能体指南](guide.md#manual-response-control)中所述。
|
||||
|
||||
完整模式请参阅 [`RealtimeRunConfig`][agents.realtime.config.RealtimeRunConfig] 和 [`RealtimeSessionModelSettings`][agents.realtime.config.RealtimeSessionModelSettings]。
|
||||
|
||||
## 连接选项
|
||||
|
||||
在环境中设置你的 API 密钥:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
或在启动会话时直接传入:
|
||||
|
||||
```python
|
||||
session = await runner.run(model_config={"api_key": "your-api-key"})
|
||||
```
|
||||
|
||||
`model_config` 还支持:
|
||||
|
||||
- `url`:自定义 WebSocket 端点
|
||||
- `headers`:自定义请求头
|
||||
- `call_id`:附加到现有实时通话。在此仓库中,已记录的附加流程是 SIP。
|
||||
- `playback_tracker`:报告用户实际听到的音频量
|
||||
|
||||
如果你显式传入 `headers`,SDK 将**不会**为你注入 `Authorization` 标头。
|
||||
|
||||
连接到 Azure OpenAI 时,请在 `model_config["url"]` 中传入 GA Realtime 端点 URL,并显式传入 headers。对于实时智能体,请避免使用旧版 beta 路径(`/openai/realtime?api-version=...`)。详情请参阅[实时智能体指南](guide.md#low-level-access-and-custom-endpoints)。
|
||||
|
||||
## 后续步骤
|
||||
|
||||
- 阅读[实时传输](transport.md),以在服务端 WebSocket 和 SIP 之间做出选择。
|
||||
- 阅读[实时智能体指南](guide.md),了解生命周期、结构化输入、审批、任务转移、安全防护措施和底层控制。
|
||||
- 浏览 [`examples/realtime`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime) 中的代码示例。
|
||||
@@ -0,0 +1,76 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 实时传输
|
||||
|
||||
使用本页判断实时智能体如何适配你的 Python 应用程序。
|
||||
|
||||
!!! note "Python SDK 边界"
|
||||
|
||||
Python SDK **不**包含浏览器 WebRTC 传输。本页仅讨论 Python SDK 的传输选择:服务端 WebSocket 和 SIP 接入流程。浏览器 WebRTC 是一个单独的平台主题,记录在官方 [Realtime API with WebRTC](https://developers.openai.com/api/docs/guides/realtime-webrtc/) 指南中。
|
||||
|
||||
## 决策指南
|
||||
|
||||
| 目标 | 从这里开始 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| 构建由服务端管理的实时应用 | [快速入门](quickstart.md) | 默认的 Python 路径是由 `RealtimeRunner` 管理的服务端 WebSocket 会话。 |
|
||||
| 了解应选择哪种传输和部署形态 | 本页 | 在确定传输或部署形态之前使用本页。 |
|
||||
| 将智能体接入电话或 SIP 通话 | [实时指南](guide.md) 和 [`examples/realtime/twilio_sip`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip) | 该仓库提供了由 `call_id` 驱动的 SIP 接入流程。 |
|
||||
|
||||
## 默认 Python 路径:服务端 WebSocket
|
||||
|
||||
除非你传入自定义 `RealtimeModel`,否则 `RealtimeRunner` 会使用 `OpenAIRealtimeWebSocketModel`。
|
||||
|
||||
这意味着标准 Python 拓扑如下:
|
||||
|
||||
1. 你的 Python 服务创建一个 `RealtimeRunner`。
|
||||
2. `await runner.run()` 返回一个 `RealtimeSession`。
|
||||
3. 进入会话并发送文本、结构化消息或音频。
|
||||
4. 消费 `RealtimeSessionEvent` 项,并将音频或转录文本转发到你的应用程序。
|
||||
|
||||
这是核心演示应用、CLI 示例和 Twilio Media Streams 示例所使用的拓扑:
|
||||
|
||||
- [`examples/realtime/app`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/app)
|
||||
- [`examples/realtime/cli`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/cli)
|
||||
- [`examples/realtime/twilio`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio)
|
||||
|
||||
当你的服务负责音频管道、工具执行、审批流程和历史记录处理时,请使用此路径。
|
||||
|
||||
## 电话路径:SIP 接入
|
||||
|
||||
对于此仓库中记录的电话流程,Python SDK 会通过 `call_id` 接入现有的实时通话。
|
||||
|
||||
该拓扑如下:
|
||||
|
||||
1. OpenAI 向你的服务发送一个 webhook,例如 `realtime.call.incoming`。
|
||||
2. 你的服务通过 Realtime Calls API 接受该通话。
|
||||
3. 你的 Python 服务启动一个 `RealtimeRunner(..., model=OpenAIRealtimeSIPModel())`。
|
||||
4. 会话使用 `model_config={"call_id": ...}` 连接,然后像任何其他实时会话一样处理事件。
|
||||
|
||||
这是 [`examples/realtime/twilio_sip`](https://github.com/openai/openai-agents-python/tree/main/examples/realtime/twilio_sip) 中展示的拓扑。
|
||||
|
||||
更广泛的 Realtime API 也会在一些服务端控制模式中使用 `call_id`,但此仓库提供的接入示例是 SIP。
|
||||
|
||||
## 本 SDK 范围之外的浏览器 WebRTC
|
||||
|
||||
如果你的应用的主要客户端是使用 Realtime WebRTC 的浏览器:
|
||||
|
||||
- 将其视为不在此仓库的 Python SDK 文档范围内。
|
||||
- 使用官方 [Realtime API with WebRTC](https://developers.openai.com/api/docs/guides/realtime-webrtc/) 和 [实时对话](https://developers.openai.com/api/docs/guides/realtime-conversations/)文档,了解客户端流程和事件模型。
|
||||
- 如果你需要在浏览器 WebRTC 客户端之上使用旁路服务端连接,请使用官方 [Realtime server-side controls](https://developers.openai.com/api/docs/guides/realtime-server-controls/) 指南。
|
||||
- 不要期望此仓库提供浏览器端 `RTCPeerConnection` 抽象或现成的浏览器 WebRTC 示例。
|
||||
|
||||
此仓库目前也未提供浏览器 WebRTC 加 Python 旁路服务端示例。
|
||||
|
||||
## 自定义端点和接入点
|
||||
|
||||
[`RealtimeModelConfig`][agents.realtime.model.RealtimeModelConfig] 中的传输配置界面允许你调整默认路径:
|
||||
|
||||
- `url`:覆盖 WebSocket 端点
|
||||
- `headers`:提供显式标头,例如 Azure 身份验证标头
|
||||
- `api_key`:直接传入 API key,或通过回调传入
|
||||
- `call_id`:接入现有实时通话。在此仓库中,记录的示例是 SIP。
|
||||
- `playback_tracker`:报告实际播放进度,以便处理中断
|
||||
|
||||
选择拓扑后,请参阅[实时智能体指南](guide.md),了解详细生命周期和能力界面。
|
||||
@@ -0,0 +1,186 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 发布流程/变更日志
|
||||
|
||||
本项目采用略作修改的语义化版本控制,版本格式为 `0.Y.Z`。开头的 `0` 表示 SDK 仍在快速演进。各组成部分按以下规则递增:
|
||||
|
||||
## 次版本(`Y`)
|
||||
|
||||
对于任何未标记为 beta 的公共接口,如果存在**破坏性变更**,我们将递增次版本号 `Y`。例如,从 `0.0.x` 升级到 `0.1.x` 时可能包含破坏性变更。
|
||||
|
||||
如果不希望遇到破坏性变更,建议在项目中将版本锁定为 `0.0.x`。
|
||||
|
||||
## 补丁版本(`Z`)
|
||||
|
||||
对于非破坏性变更,我们将递增 `Z`:
|
||||
|
||||
- Bug 修复
|
||||
- 新功能
|
||||
- 私有接口变更
|
||||
- beta 功能更新
|
||||
|
||||
## 破坏性变更日志
|
||||
|
||||
### 0.18.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更。递增次版本号仅用于更新 Realtime智能体的默认模型。
|
||||
|
||||
亮点:
|
||||
|
||||
- Realtime智能体现在使用 `gpt-realtime-2.1` 作为默认模型,因此新的 Realtime 配置无需额外设置即可使用最新推荐模型。
|
||||
|
||||
### 0.17.0
|
||||
|
||||
在此版本中,沙箱本地源实体化会将 `LocalFile.src` 和 `LocalDir.src` 限制在实体化的 `base_dir` 内,除非源路径包含在 `Manifest.extra_path_grants` 中。应用清单时,`base_dir` 是 SDK 进程的当前工作目录;相对本地源路径从该目录解析,而绝对本地源路径必须已位于该目录内或显式授权的路径下。这修复了本地产物边界问题,但可能影响有意将该基础目录之外受信任的主机文件或目录复制到沙箱工作区的应用程序。
|
||||
|
||||
如需迁移,请使用 `SandboxPathGrant` 在清单级别授权受信任的主机根目录;如果沙箱只需读取这些文件,最好将其设为只读:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.sandbox import Manifest, SandboxPathGrant
|
||||
from agents.sandbox.entries import Dir, LocalDir
|
||||
|
||||
# This is an absolute host path outside the SDK process base_dir.
|
||||
TRUSTED_DOCS_ROOT = Path("/opt/my-app/docs")
|
||||
|
||||
manifest = Manifest(
|
||||
extra_path_grants=(
|
||||
# This host root is outside the SDK process base_dir, so the manifest must grant it.
|
||||
SandboxPathGrant(path=str(TRUSTED_DOCS_ROOT), read_only=True),
|
||||
),
|
||||
entries={
|
||||
# No grant is needed for local sources that stay under the SDK process base_dir.
|
||||
"fixtures": LocalDir(src=Path("fixtures"), description="Local test fixtures."),
|
||||
# This entry reads from the granted host root and copies it into the sandbox workspace.
|
||||
"docs": LocalDir(src=TRUSTED_DOCS_ROOT, description="Trusted local documents."),
|
||||
# Dir creates a sandbox workspace directory; it does not read from the host filesystem.
|
||||
"output": Dir(description="Generated artifacts."),
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
请将 `extra_path_grants` 视为受信任的应用程序配置。除非应用程序已批准相关主机路径,否则不要根据模型输出或其他不受信任的清单输入填充授权项。
|
||||
|
||||
### 0.16.0
|
||||
|
||||
在此版本中,SDK 默认模型已从 `gpt-4.1` 更改为 `gpt-5.4-mini`。这会影响未显式设置模型的智能体和运行。由于新的默认模型是 GPT-5 模型,隐式默认模型设置现在包含 GPT-5 的默认值,例如 `reasoning.effort="none"` 和 `verbosity="low"`。
|
||||
|
||||
如果需要保留之前的默认模型行为,请在智能体或运行配置中显式设置模型,或者设置 `OPENAI_DEFAULT_MODEL` 环境变量:
|
||||
|
||||
```python
|
||||
agent = Agent(name="Assistant", model="gpt-4.1")
|
||||
```
|
||||
|
||||
亮点:
|
||||
|
||||
- `Runner.run`、`Runner.run_sync` 和 `Runner.run_streamed` 现在接受 `max_turns=None`,以禁用轮次限制。
|
||||
- 在本地、Docker 和由提供商支持的沙箱实现中,沙箱工作区填充现在会拒绝包含指向归档根目录之外的符号链接(包括绝对符号链接目标)的 tar 归档。
|
||||
|
||||
### 0.15.0
|
||||
|
||||
在此版本中,模型拒绝现在会显式呈现为 `ModelRefusalError`,而不再被视为空文本输出;对于 structured outputs,也不会再导致运行循环持续重试,直至出现 `MaxTurnsExceeded`。
|
||||
|
||||
这会影响此前预期仅包含拒绝的模型响应以 `final_output == ""` 完成的代码。若要在不引发异常的情况下处理拒绝,请提供 `model_refusal` 运行错误处理程序:
|
||||
|
||||
```python
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
input,
|
||||
error_handlers={"model_refusal": lambda data: data.error.refusal},
|
||||
)
|
||||
```
|
||||
|
||||
对于使用 structured outputs 的智能体,处理程序可以返回与智能体输出模式匹配的值,SDK 将像验证其他运行错误处理程序的最终输出一样对其进行验证。
|
||||
|
||||
### 0.14.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更,但新增了一个重要的 beta 功能领域:沙箱智能体,以及在本地、容器化和托管环境中使用它们所需的运行时、后端和文档支持。
|
||||
|
||||
亮点:
|
||||
|
||||
- 新增以 `SandboxAgent`、`Manifest` 和 `SandboxRunConfig` 为核心的 beta 沙箱运行时接口,使智能体能够在持久化的隔离工作区中处理文件、目录、Git 仓库、挂载、快照,并支持恢复。
|
||||
- 新增通过 `UnixLocalSandboxClient` 和 `DockerSandboxClient` 支持本地及容器化开发的沙箱执行后端,并通过可选扩展集成 Blaxel、Cloudflare、Daytona、E2B、Modal、Runloop 和 Vercel 等托管提供商。
|
||||
- 新增沙箱记忆支持,使后续运行可以复用先前运行中的经验,并支持渐进式披露、多轮分组、可配置的隔离边界,以及包含 S3 后端工作流的持久化记忆代码示例。
|
||||
- 新增更广泛的工作区和恢复模型,包括本地及合成工作区条目、用于 S3/R2/GCS/Azure Blob Storage/S3 Files 的远程存储挂载、可移植快照,以及通过 `RunState`、`SandboxSessionState` 或已保存快照实现的恢复流程。
|
||||
- 在 `examples/sandbox/` 下新增大量沙箱代码示例和教程,涵盖结合技能、任务转移和记忆的编码任务、特定提供商的配置,以及代码审查、数据室问答和网站克隆等端到端工作流。
|
||||
- 扩展核心运行时和追踪技术栈,新增沙箱感知的会话准备、能力绑定、状态序列化、统一追踪、提示词缓存键默认值,以及更安全的敏感 MCP 输出脱敏。
|
||||
|
||||
### 0.13.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更,但包含一项值得关注的 Realtime 默认设置更新、新的 MCP 功能以及运行时稳定性修复。
|
||||
|
||||
亮点:
|
||||
|
||||
- 默认 websocket Realtime 模型现在是 `gpt-realtime-1.5`,因此新的 Realtime智能体配置无需额外设置即可使用更新的模型。
|
||||
- `MCPServer` 现在公开 `list_resources()`、`list_resource_templates()` 和 `read_resource()`,而 `MCPServerStreamableHttp` 现在公开 `session_id`,从而使可流式传输的 HTTP 会话能够在重新连接或无状态工作进程之间恢复。
|
||||
- Chat Completions集成现在可以通过 `should_replay_reasoning_content` 选择启用推理内容重放,从而改善 LiteLLM/DeepSeek 等适配器中特定于提供商的推理/工具调用连续性。
|
||||
- 修复多个运行时和会话边界情况,包括 `SQLAlchemySession` 中并发的首次写入、移除推理内容后包含孤立助手消息 ID 的压缩请求、`remove_all_tools()` 遗留 MCP/推理项,以及工具调用批量执行器中的竞态条件。
|
||||
|
||||
### 0.12.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更。有关主要新增功能,请查看[发布说明](https://github.com/openai/openai-agents-python/releases/tag/v0.12.0)。
|
||||
|
||||
### 0.11.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更。有关主要新增功能,请查看[发布说明](https://github.com/openai/openai-agents-python/releases/tag/v0.11.0)。
|
||||
|
||||
### 0.10.0
|
||||
|
||||
此次次版本发布**没有**引入破坏性变更,但为 OpenAI Responses用户新增了一个重要功能领域:Responses API 的 websocket 传输支持。
|
||||
|
||||
亮点:
|
||||
|
||||
- 新增对 OpenAI Responses模型的 websocket 传输支持(需选择启用;HTTP 仍为默认传输方式)。
|
||||
- 新增 `responses_websocket_session()` 辅助函数 / `ResponsesWebSocketSession`,用于在多轮运行中复用支持 websocket 的共享提供商和 `RunConfig`。
|
||||
- 新增 websocket 流式传输代码示例(`examples/basic/stream_ws.py`),涵盖流式传输、工具、审批和后续轮次。
|
||||
|
||||
### 0.9.0
|
||||
|
||||
在此版本中,不再支持 Python 3.9,因为该主要版本已于三个月前终止生命周期。请升级到更新的运行时版本。
|
||||
|
||||
此外,`Agent#as_tool()` 方法返回值的类型提示已从 `Tool` 收窄为 `FunctionTool`。此变更通常不会导致破坏性问题,但如果代码依赖更宽泛的联合类型,可能需要进行一些调整。
|
||||
|
||||
### 0.8.0
|
||||
|
||||
在此版本中,两项运行时行为变更可能需要迁移:
|
||||
|
||||
- 包装**同步** Python 可调用对象的工具调用现在通过 `asyncio.to_thread(...)` 在工作线程上执行,而不再在事件循环线程上运行。如果工具逻辑依赖线程局部状态或具有线程亲和性的资源,请迁移到异步工具实现,或在工具代码中显式指定线程亲和性。
|
||||
- 本地 MCP 工具的故障处理现在可配置,默认行为可以返回模型可见的错误输出,而不是使整个运行失败。如果依赖快速失败语义,请设置 `mcp_config={"failure_error_function": None}`。服务级别的 `failure_error_function` 值会覆盖智能体级别的设置,因此请在每个具有显式处理程序的本地 MCP服务上设置 `failure_error_function=None`。
|
||||
|
||||
### 0.7.0
|
||||
|
||||
在此版本中,有几项行为变更可能会影响现有应用程序:
|
||||
|
||||
- 嵌套任务转移历史记录现在需要**选择启用**(默认禁用)。如果依赖 v0.6.x 默认的嵌套行为,请显式设置 `RunConfig(nest_handoff_history=True)`。
|
||||
- `gpt-5.1` / `gpt-5.2` 的默认 `reasoning.effort` 已从 SDK 默认设置所配置的 `"low"` 更改为 `"none"`。如果提示词或质量/成本配置依赖 `"low"`,请在 `model_settings` 中显式设置该值。
|
||||
|
||||
### 0.6.0
|
||||
|
||||
在此版本中,默认任务转移历史记录现在会打包为一条助手消息,而不再公开原始的用户/助手轮次,从而为下游智能体提供简洁且可预测的回顾
|
||||
- 现有的单消息任务转移记录现在默认在 `<CONVERSATION HISTORY>` 块之前以“作为上下文,以下是用户与前一个智能体之间截至目前的对话:”开头,从而为下游智能体提供带有清晰标签的回顾
|
||||
|
||||
### 0.5.0
|
||||
|
||||
此版本未引入任何可见的破坏性变更,但包含新功能和几项重要的底层更新:
|
||||
|
||||
- 新增对 `RealtimeRunner` 处理 [SIP 协议连接](https://platform.openai.com/docs/guides/realtime-sip)的支持
|
||||
- 为兼容 Python 3.14,对 `Runner#run_sync` 的内部逻辑进行了重大修订
|
||||
|
||||
### 0.4.0
|
||||
|
||||
在此版本中,不再支持 [openai](https://pypi.org/project/openai/) 软件包的 v1.x 版本。请将 openai v2.x 与此 SDK 配合使用。
|
||||
|
||||
### 0.3.0
|
||||
|
||||
在此版本中,Realtime API支持迁移到 gpt-realtime 模型及其 API 接口(GA 版本)。
|
||||
|
||||
### 0.2.0
|
||||
|
||||
在此版本中,一些过去接受 `Agent` 作为参数的位置现在改为接受 `AgentBase`。例如,MCP服务中的 `list_tools()` 调用。这仅是类型变更,仍会收到 `Agent` 对象。更新时,只需将 `Agent` 替换为 `AgentBase` 以修复类型错误。
|
||||
|
||||
### 0.1.0
|
||||
|
||||
在此版本中,[`MCPServer.list_tools()`][agents.mcp.server.MCPServer] 新增两个参数:`run_context` 和 `agent`。需要将这些参数添加到所有继承 `MCPServer` 的类中。
|
||||
@@ -0,0 +1,24 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# REPL 实用工具
|
||||
|
||||
SDK 提供了 `run_demo_loop`,用于直接在终端中快速、交互式地测试智能体的行为。
|
||||
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, run_demo_loop
|
||||
|
||||
async def main() -> None:
|
||||
agent = Agent(name="Assistant", instructions="You are a helpful assistant.")
|
||||
await run_demo_loop(agent)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`run_demo_loop` 会循环提示用户输入,并在多轮之间保留对话历史。默认情况下,它会在模型输出生成时进行流式传输。当你运行上面的示例时,run_demo_loop 会启动一个交互式聊天会话。它会持续请求你的输入,记住多轮之间的完整对话历史(这样你的智能体就知道之前讨论过什么),并在智能体响应生成时自动实时地将其流式传输给你。
|
||||
|
||||
要结束此聊天会话,只需输入 `quit` 或 `exit`(然后按 Enter),或使用 `Ctrl-D` 键盘快捷键。
|
||||
@@ -0,0 +1,165 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 结果
|
||||
|
||||
调用 `Runner.run` 方法时,你会收到以下两种结果类型之一:
|
||||
|
||||
- 来自 `Runner.run(...)` 或 `Runner.run_sync(...)` 的 [`RunResult`][agents.result.RunResult]
|
||||
- 来自 `Runner.run_streamed(...)` 的 [`RunResultStreaming`][agents.result.RunResultStreaming]
|
||||
|
||||
二者都继承自 [`RunResultBase`][agents.result.RunResultBase],后者公开了共享的结果接口,例如 `final_output`、`new_items`、`last_agent`、`raw_responses` 和 `to_state()`。
|
||||
|
||||
`RunResultStreaming` 增加了流式传输专用控制项,例如 [`stream_events()`][agents.result.RunResultStreaming.stream_events]、[`current_agent`][agents.result.RunResultStreaming.current_agent]、[`is_complete`][agents.result.RunResultStreaming.is_complete] 和 [`cancel(...)`][agents.result.RunResultStreaming.cancel]。
|
||||
|
||||
## 合适的结果接口
|
||||
|
||||
大多数应用只需要少数几个结果属性或辅助方法:
|
||||
|
||||
| 如果你需要... | 使用 |
|
||||
| --- | --- |
|
||||
| 展示给用户的最终答案 | `final_output` |
|
||||
| 可用于重放的下一轮输入列表,包含完整本地转录记录 | `to_input_list()` |
|
||||
| 包含智能体、工具、任务转移和审批元数据的丰富运行条目 | `new_items` |
|
||||
| 通常应处理下一轮用户输入的智能体 | `last_agent` |
|
||||
| 使用 `previous_response_id` 进行 OpenAI Responses API 链接 | `last_response_id` |
|
||||
| 待处理审批和可恢复快照 | `interruptions` 和 `to_state()` |
|
||||
| 当前嵌套 `Agent.as_tool()` 调用的元数据 | `agent_tool_invocation` |
|
||||
| 原始模型调用或安全防护措施诊断 | `raw_responses` 和安全防护措施结果数组 |
|
||||
|
||||
## 最终输出
|
||||
|
||||
[`final_output`][agents.result.RunResultBase.final_output] 属性包含最后运行的智能体的最终输出。它可能是:
|
||||
|
||||
- 一个 `str`,如果最后一个智能体未定义 `output_type`
|
||||
- `last_agent.output_type` 类型的对象,如果最后一个智能体定义了输出类型
|
||||
- `None`,如果运行在产生最终输出之前停止,例如因审批中断而暂停
|
||||
|
||||
!!! note
|
||||
|
||||
`final_output` 的类型为 `Any`。任务转移可能会改变哪个智能体结束运行,因此 SDK 无法静态得知所有可能的输出类型。
|
||||
|
||||
在流式传输模式下,`final_output` 会保持为 `None`,直到流处理完成。有关逐事件流程,请参阅[流式传输](streaming.md)。
|
||||
|
||||
## 输入、下一轮历史记录和新条目
|
||||
|
||||
这些接口回答的是不同问题:
|
||||
|
||||
| 属性或辅助方法 | 包含的内容 | 最适合 |
|
||||
| --- | --- | --- |
|
||||
| [`input`][agents.result.RunResultBase.input] | 此运行片段的基础输入。如果任务转移输入过滤器重写了历史记录,这里会反映运行继续时所使用的过滤后输入。 | 审计此运行实际使用了什么作为输入 |
|
||||
| [`to_input_list()`][agents.result.RunResultBase.to_input_list] | 运行的输入条目视图。默认 `mode="preserve_all"` 会保留来自 `new_items` 的完整转换后历史记录;`mode="normalized"` 会在任务转移过滤重写模型历史记录时优先使用规范续接输入。 | 手动聊天循环、客户端管理的对话状态,以及普通条目历史记录检查 |
|
||||
| [`new_items`][agents.result.RunResultBase.new_items] | 包含智能体、工具、任务转移和审批元数据的丰富 [`RunItem`][agents.items.RunItem] 包装器。 | 日志、UI、审计和调试 |
|
||||
| [`raw_responses`][agents.result.RunResultBase.raw_responses] | 运行中每次模型调用产生的原始 [`ModelResponse`][agents.items.ModelResponse] 对象。 | 提供方级诊断或原始响应检查 |
|
||||
|
||||
实践中:
|
||||
|
||||
- 当你想要运行的普通输入条目视图时,使用 `to_input_list()`。
|
||||
- 当你希望在任务转移过滤或嵌套任务转移历史记录重写后,为下一次 `Runner.run(..., input=...)` 调用获得规范本地输入时,使用 `to_input_list(mode="normalized")`。
|
||||
- 当你希望 SDK 为你加载和保存历史记录时,使用 [`session=...`](sessions/index.md)。
|
||||
- 如果你使用带有 `conversation_id` 或 `previous_response_id` 的 OpenAI服务管理状态,通常只传递新的用户输入,并复用已存储的 ID,而不是重新发送 `to_input_list()`。
|
||||
- 当你需要用于日志、UI 或审计的完整转换后历史记录时,使用默认的 `to_input_list()` 模式或 `new_items`。
|
||||
|
||||
与 JavaScript SDK 不同,Python 不会公开一个单独的 `output` 属性来仅表示模型形态的增量。当你需要 SDK 元数据时,使用 `new_items`;当你需要原始模型载荷时,检查 `raw_responses`。
|
||||
|
||||
计算机工具重放遵循原始 Responses 载荷结构。预览模型的 `computer_call` 条目会保留单个 `action`,而 `gpt-5.5` 计算机调用可以保留批处理的 `actions[]`。[`to_input_list()`][agents.result.RunResultBase.to_input_list] 和 [`RunState`][agents.run_state.RunState] 会保留模型生成的任一结构,因此手动重放、暂停/恢复流程和已存储的转录记录都能继续适用于预览版和 GA 计算机工具调用。本地执行结果仍会以 `computer_call_output` 条目的形式出现在 `new_items` 中。
|
||||
|
||||
### 新条目
|
||||
|
||||
[`new_items`][agents.result.RunResultBase.new_items] 为你提供运行期间所发生事件的最丰富视图。常见条目类型包括:
|
||||
|
||||
- 用于助手消息的 [`MessageOutputItem`][agents.items.MessageOutputItem]
|
||||
- 用于推理条目的 [`ReasoningItem`][agents.items.ReasoningItem]
|
||||
- 用于 Responses 工具搜索请求和已加载工具搜索结果的 [`ToolSearchCallItem`][agents.items.ToolSearchCallItem] 与 [`ToolSearchOutputItem`][agents.items.ToolSearchOutputItem]
|
||||
- 用于工具调用及其结果的 [`ToolCallItem`][agents.items.ToolCallItem] 与 [`ToolCallOutputItem`][agents.items.ToolCallOutputItem]
|
||||
- 用于因审批而暂停的工具调用的 [`ToolApprovalItem`][agents.items.ToolApprovalItem]
|
||||
- 用于任务转移请求和已完成转移的 [`HandoffCallItem`][agents.items.HandoffCallItem] 与 [`HandoffOutputItem`][agents.items.HandoffOutputItem]
|
||||
|
||||
每当你需要智能体关联、工具输出、任务转移边界或审批边界时,应选择 `new_items` 而不是 `to_input_list()`。
|
||||
|
||||
使用托管工具搜索时,检查 `ToolSearchCallItem.raw_item` 可查看模型发出的搜索请求,检查 `ToolSearchOutputItem.raw_item` 可查看该轮次加载了哪些命名空间、函数或托管 MCP 服务。
|
||||
|
||||
## 对话的继续或恢复
|
||||
|
||||
### 下一轮智能体
|
||||
|
||||
[`last_agent`][agents.result.RunResultBase.last_agent] 包含最后运行的智能体。在任务转移之后,这通常是下一轮用户输入最适合复用的智能体。
|
||||
|
||||
在流式传输模式下,[`RunResultStreaming.current_agent`][agents.result.RunResultStreaming.current_agent] 会随着运行进展而更新,因此你可以在流结束前观察任务转移。
|
||||
|
||||
### 中断和运行状态
|
||||
|
||||
如果工具需要审批,待处理审批会通过 [`RunResult.interruptions`][agents.result.RunResult.interruptions] 或 [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions] 暴露。这可以包括由直接工具引发、由任务转移后到达的工具引发,或由嵌套 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 运行引发的审批。
|
||||
|
||||
调用 [`to_state()`][agents.result.RunResult.to_state] 可捕获可恢复的 [`RunState`][agents.run_state.RunState],批准或拒绝待处理条目,然后使用 `Runner.run(...)` 或 `Runner.run_streamed(...)` 恢复。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Use tools when needed.")
|
||||
result = await Runner.run(agent, "Delete temp files that are no longer needed.")
|
||||
|
||||
if result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
state.approve(interruption)
|
||||
result = await Runner.run(agent, state)
|
||||
```
|
||||
|
||||
对于流式传输运行,请先完成对 [`stream_events()`][agents.result.RunResultStreaming.stream_events] 的消费,然后检查 `result.interruptions` 并从 `result.to_state()` 恢复。有关完整审批流程,请参阅[人在回路](human_in_the_loop.md)。
|
||||
|
||||
### 服务管理的续接
|
||||
|
||||
[`last_response_id`][agents.result.RunResultBase.last_response_id] 是运行中最新的模型响应 ID。当你想继续 OpenAI Responses API 链时,在下一轮将它作为 `previous_response_id` 传回。
|
||||
|
||||
如果你已经使用 `to_input_list()`、`session` 或 `conversation_id` 继续对话,通常不需要 `last_response_id`。如果需要多步运行中的每个模型响应,请改为检查 `raw_responses`。
|
||||
|
||||
## 智能体作为工具的元数据
|
||||
|
||||
当结果来自嵌套的 [`Agent.as_tool()`][agents.agent.Agent.as_tool] 运行时,[`agent_tool_invocation`][agents.result.RunResultBase.agent_tool_invocation] 会公开关于外层工具调用的不可变元数据:
|
||||
|
||||
- `tool_name`
|
||||
- `tool_call_id`
|
||||
- `tool_arguments`
|
||||
|
||||
对于普通顶层运行,`agent_tool_invocation` 为 `None`。
|
||||
|
||||
这在 `custom_output_extractor` 内尤其有用,因为在对嵌套结果进行后处理时,你可能需要外层工具名称、调用 ID 或原始参数。有关周围的 `Agent.as_tool()` 模式,请参阅[工具](tools.md)。
|
||||
|
||||
如果你还需要该嵌套运行的已解析结构化输入,请读取 `context_wrapper.tool_input`。这是 [`RunState`][agents.run_state.RunState] 用于以通用方式序列化嵌套工具输入的字段,而 `agent_tool_invocation` 是当前嵌套调用的实时结果访问器。
|
||||
|
||||
## 流式传输生命周期和诊断
|
||||
|
||||
[`RunResultStreaming`][agents.result.RunResultStreaming] 继承上述相同结果接口,但增加了流式传输专用控制项:
|
||||
|
||||
- [`stream_events()`][agents.result.RunResultStreaming.stream_events] 用于消费语义流事件
|
||||
- [`current_agent`][agents.result.RunResultStreaming.current_agent] 用于在运行过程中跟踪活跃智能体
|
||||
- [`is_complete`][agents.result.RunResultStreaming.is_complete] 用于查看流式传输运行是否已完全结束
|
||||
- [`cancel(...)`][agents.result.RunResultStreaming.cancel] 用于立即停止运行,或在当前轮次结束后停止运行
|
||||
|
||||
持续消费 `stream_events()`,直到异步迭代器结束。只有该迭代器结束后,流式传输运行才算完成;并且在最后一个可见 token 到达后,`final_output`、`interruptions`、`raw_responses` 等汇总属性以及会话持久化副作用可能仍在收尾。
|
||||
|
||||
如果调用 `cancel()`,请继续消费 `stream_events()`,以便取消和清理能够正确完成。
|
||||
|
||||
Python 不会公开单独的流式 `completed` promise 或 `error` 属性。终止性流式传输失败会通过 `stream_events()` 抛出异常来呈现,而 `is_complete` 反映运行是否已到达其终止状态。
|
||||
|
||||
### 原始响应
|
||||
|
||||
[`raw_responses`][agents.result.RunResultBase.raw_responses] 包含运行期间收集的原始模型响应。多步运行可能会生成多个响应,例如跨任务转移或重复的模型/工具/模型循环。
|
||||
|
||||
[`last_response_id`][agents.result.RunResultBase.last_response_id] 只是 `raw_responses` 最后一项中的 ID。
|
||||
|
||||
### 安全防护措施结果
|
||||
|
||||
智能体级安全防护措施通过 [`input_guardrail_results`][agents.result.RunResultBase.input_guardrail_results] 和 [`output_guardrail_results`][agents.result.RunResultBase.output_guardrail_results] 暴露。
|
||||
|
||||
工具安全防护措施则分别通过 [`tool_input_guardrail_results`][agents.result.RunResultBase.tool_input_guardrail_results] 和 [`tool_output_guardrail_results`][agents.result.RunResultBase.tool_output_guardrail_results] 暴露。
|
||||
|
||||
这些数组会在运行过程中累积,因此它们适用于记录决策、存储额外的安全防护措施元数据,或调试运行为何被阻止。
|
||||
|
||||
### 上下文和用量
|
||||
|
||||
[`context_wrapper`][agents.result.RunResultBase.context_wrapper] 会公开你的应用上下文,以及由 SDK 管理的运行时元数据,例如审批、用量和嵌套 `tool_input`。
|
||||
|
||||
用量在 `context_wrapper.usage` 上跟踪。对于流式传输运行,用量总计可能会滞后,直到流的最终分块处理完毕。有关完整的包装器结构和持久化注意事项,请参阅[上下文管理](context.md)。
|
||||
@@ -0,0 +1,596 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 运行智能体
|
||||
|
||||
你可以通过 [`Runner`][agents.run.Runner] 类运行智能体。你有 3 种选择:
|
||||
|
||||
1. [`Runner.run()`][agents.run.Runner.run]:异步运行并返回 [`RunResult`][agents.result.RunResult]。
|
||||
2. [`Runner.run_sync()`][agents.run.Runner.run_sync]:同步方法,其内部只是运行 `.run()`。
|
||||
3. [`Runner.run_streamed()`][agents.run.Runner.run_streamed]:异步运行并返回 [`RunResultStreaming`][agents.result.RunResultStreaming]。它以流式传输模式调用 LLM,并在收到事件时将其流式传输给你。
|
||||
|
||||
```python
|
||||
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
|
||||
```
|
||||
|
||||
请在[结果指南](results.md)中了解更多信息。
|
||||
|
||||
## Runner 生命周期与配置
|
||||
|
||||
### 智能体循环
|
||||
|
||||
使用 `Runner` 中的运行方法时,你需要传入一个起始智能体和输入。输入可以是:
|
||||
|
||||
- 字符串(被视为用户消息),
|
||||
- OpenAI Responses API 格式的输入项列表,或
|
||||
- 恢复已中断的运行时使用的 [`RunState`][agents.run_state.RunState]。
|
||||
|
||||
随后,运行器将执行循环:
|
||||
|
||||
1. 我们使用当前输入为当前智能体调用 LLM。
|
||||
2. LLM 生成输出。
|
||||
1. 如果 LLM 返回 `final_output`,循环结束并返回结果。
|
||||
2. 如果 LLM 执行任务转移,我们会更新当前智能体和输入,然后重新运行循环。
|
||||
3. 如果 LLM 生成工具调用,我们会运行这些工具调用、追加结果,然后重新运行循环。
|
||||
3. 如果超过传入的 `max_turns`,我们会引发 [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded] 异常。传入 `max_turns=None` 可禁用此轮次限制。
|
||||
|
||||
!!! note
|
||||
|
||||
判断 LLM 输出是否被视为“最终输出”的规则是:它生成了具有所需类型的文本输出,并且没有工具调用。
|
||||
|
||||
### 流式传输
|
||||
|
||||
流式传输让你可以在 LLM 运行时额外接收流式事件。流结束后,[`RunResultStreaming`][agents.result.RunResultStreaming] 将包含此次运行的完整信息,包括生成的所有新输出。你可以调用 `.stream_events()` 获取流式事件。请在[流式传输指南](streaming.md)中了解更多信息。
|
||||
|
||||
#### Responses WebSocket 传输(可选辅助工具)
|
||||
|
||||
如果启用 OpenAI Responses WebSocket 传输,你仍然可以继续使用常规的 `Runner` API。建议使用 WebSocket 会话辅助工具来复用连接,但这并非必需。
|
||||
|
||||
这是通过 WebSocket 传输使用 Responses API,而不是 [Realtime API](realtime/guide.md)。
|
||||
|
||||
有关传输方式的选择规则,以及使用具体模型对象或自定义提供商时的注意事项,请参阅[模型](models/index.md#responses-websocket-transport)。
|
||||
|
||||
##### 模式 1:不使用会话辅助工具(可用)
|
||||
|
||||
如果你只想使用 WebSocket 传输,并且不需要 SDK 为你管理共享提供商或会话,请使用此方式。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, Runner, set_default_openai_responses_transport
|
||||
|
||||
|
||||
async def main():
|
||||
set_default_openai_responses_transport("websocket")
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
result = Runner.run_streamed(agent, "Summarize recursion in one sentence.")
|
||||
|
||||
async for event in result.stream_events():
|
||||
if event.type == "raw_response_event":
|
||||
continue
|
||||
print(event.type)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
此模式适用于单次运行。如果你重复调用 `Runner.run()` / `Runner.run_streamed()`,除非手动复用同一个 `RunConfig` / 提供商实例,否则每次运行都可能重新连接。
|
||||
|
||||
##### 模式 2:使用 `responses_websocket_session()`(建议用于多轮复用)
|
||||
|
||||
如果你希望在多次运行中共享支持 WebSocket 的提供商和 `RunConfig`,请使用 [`responses_websocket_session()`][agents.responses_websocket_session](包括继承同一 `run_config` 的嵌套“智能体作为工具”调用)。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
|
||||
from agents import Agent, responses_websocket_session
|
||||
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
|
||||
async with responses_websocket_session(
|
||||
responses_websocket_options={"ping_interval": 20.0, "ping_timeout": 60.0},
|
||||
) as ws:
|
||||
first = ws.run_streamed(agent, "Say hello in one short sentence.")
|
||||
async for _event in first.stream_events():
|
||||
pass
|
||||
|
||||
second = ws.run_streamed(
|
||||
agent,
|
||||
"Now say goodbye.",
|
||||
previous_response_id=first.last_response_id,
|
||||
)
|
||||
async for _event in second.stream_events():
|
||||
pass
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
请在上下文退出前完成流式结果的消费。如果在 WebSocket 请求仍在进行时退出上下文,可能会强制关闭共享连接。
|
||||
|
||||
如果较长的推理轮次触发 WebSocket keepalive 超时,请增大 `ping_timeout`,或设置 `ping_timeout=None` 以禁用心跳超时。对于可靠性比 WebSocket 延迟更重要的运行,请使用 HTTP/SSE 传输。
|
||||
|
||||
### 运行配置
|
||||
|
||||
`run_config` 参数让你可以为智能体运行配置一些全局设置:
|
||||
|
||||
#### 常用运行配置目录
|
||||
|
||||
使用 `RunConfig` 可以覆盖单次运行的行为,而无需更改每个智能体的定义。
|
||||
|
||||
##### 模型、提供商和会话默认值
|
||||
|
||||
- [`model`][agents.run.RunConfig.model]:允许设置要使用的全局 LLM 模型,而不考虑每个智能体的 `model` 设置。
|
||||
- [`model_provider`][agents.run.RunConfig.model_provider]:用于查找模型名称的模型提供商,默认为 OpenAI。
|
||||
- [`model_settings`][agents.run.RunConfig.model_settings]:覆盖智能体特定的设置。例如,你可以设置全局 `temperature` 或 `top_p`。
|
||||
- [`session_settings`][agents.run.RunConfig.session_settings]:在运行期间检索历史记录时,覆盖会话级默认值(例如 `SessionSettings(limit=...)`)。
|
||||
- [`session_input_callback`][agents.run.RunConfig.session_input_callback]:使用 Sessions 时,自定义每轮开始前将新用户输入与会话历史记录合并的方式。该回调可以是同步或异步的。
|
||||
|
||||
##### 安全防护措施、任务转移和模型输入调整
|
||||
|
||||
- [`input_guardrails`][agents.run.RunConfig.input_guardrails]、[`output_guardrails`][agents.run.RunConfig.output_guardrails]:要包含在所有运行中的输入或输出安全防护措施列表。
|
||||
- [`handoff_input_filter`][agents.run.RunConfig.handoff_input_filter]:应用于所有任务转移的全局输入过滤器,前提是该任务转移尚未设置过滤器。输入过滤器允许你编辑发送给新智能体的输入。更多详情请参阅 [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] 的文档。
|
||||
- [`nest_handoff_history`][agents.run.RunConfig.nest_handoff_history]:选择启用的测试版功能,在调用下一个智能体之前,将此前的对话记录折叠为一条助手消息。为了在稳定嵌套任务转移功能期间保持兼容,该功能默认禁用;设置为 `True` 可启用,保留为 `False` 则会原样传递原始对话记录。未传入 `RunConfig` 时,所有 [Runner 方法][agents.run.Runner]都会自动创建一个,因此快速入门和代码示例会保持默认关闭状态,并且任何显式的 [`Handoff.input_filter`][agents.handoffs.Handoff.input_filter] 回调仍会覆盖此设置。单个任务转移可以通过 [`Handoff.nest_handoff_history`][agents.handoffs.Handoff.nest_handoff_history] 覆盖此设置。
|
||||
- [`handoff_history_mapper`][agents.run.RunConfig.handoff_history_mapper]:可选的可调用对象。当你选择启用 `nest_handoff_history` 时,它会接收规范化后的对话记录(历史记录 + 任务转移项)。它必须返回要转发给下一个智能体的准确输入项列表,使你无需编写完整的任务转移过滤器即可替换内置摘要。
|
||||
- [`call_model_input_filter`][agents.run.RunConfig.call_model_input_filter]:在调用模型之前立即编辑已完整准备的模型输入(instructions 和输入项)的钩子,例如裁剪历史记录或注入系统提示词。
|
||||
- [`reasoning_item_id_policy`][agents.run.RunConfig.reasoning_item_id_policy]:控制运行器将之前的输出转换为下一轮模型输入时,是保留还是省略推理项 ID。
|
||||
|
||||
##### 追踪与可观测性
|
||||
|
||||
- [`tracing_disabled`][agents.run.RunConfig.tracing_disabled]:允许你为整个运行禁用[追踪](tracing.md)。
|
||||
- [`tracing`][agents.run.RunConfig.tracing]:传入 [`TracingConfig`][agents.tracing.TracingConfig],以覆盖追踪导出设置,例如每次运行的追踪 API 密钥。
|
||||
- [`trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data]:配置追踪是否包含潜在的敏感数据,例如 LLM 和工具调用的输入/输出。
|
||||
- [`workflow_name`][agents.run.RunConfig.workflow_name]、[`trace_id`][agents.run.RunConfig.trace_id]、[`group_id`][agents.run.RunConfig.group_id]:设置此次运行的追踪工作流名称、追踪 ID 和追踪组 ID。我们建议至少设置 `workflow_name`。组 ID 是一个可选字段,可用于关联多次运行中的追踪。
|
||||
- [`trace_metadata`][agents.run.RunConfig.trace_metadata]:要包含在所有追踪中的元数据。
|
||||
|
||||
##### 工具执行、审批和工具错误行为
|
||||
|
||||
- [`tool_execution`][agents.run.RunConfig.tool_execution]:配置 SDK 端对本地工具调用的执行行为,例如限制同时运行的工具调用数量。
|
||||
- [`tool_not_found_behavior`][agents.run.RunConfig.tool_not_found_behavior]:配置运行器如何处理模型发出的、无法解析的工具调用。默认行为会引发 `ModelBehaviorError`;你也可以选择改为返回模型可见的错误输出。
|
||||
- [`tool_error_formatter`][agents.run.RunConfig.tool_error_formatter]:自定义模型可见的工具错误消息,例如审批被拒绝和选择启用的“工具未找到”输出。
|
||||
|
||||
嵌套任务转移是一项可选择启用的测试版功能。传入 `RunConfig(nest_handoff_history=True)` 可启用折叠对话记录行为,也可以设置 `handoff(..., nest_handoff_history=True)`,仅为特定任务转移启用该行为。如果希望保留原始对话记录(默认行为),请不要设置该标志,或者提供一个按照你的具体需求转发对话的 `handoff_input_filter`(或 `handoff_history_mapper`)。如果只想更改生成摘要时使用的包装文本,而不编写自定义映射器,请调用 [`set_conversation_history_wrappers`][agents.handoffs.set_conversation_history_wrappers](并可调用 [`reset_conversation_history_wrappers`][agents.handoffs.reset_conversation_history_wrappers] 恢复默认设置)。
|
||||
|
||||
#### 运行配置详情
|
||||
|
||||
##### `tool_execution`
|
||||
|
||||
如果你想配置 SDK 端对本地工具调用的行为,例如限制单次运行中的本地工具调用并发数,请使用 `tool_execution`。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, ToolExecutionConfig
|
||||
|
||||
agent = Agent(name="Assistant", tools=[...])
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Run the required tool calls.",
|
||||
run_config=RunConfig(
|
||||
tool_execution=ToolExecutionConfig(
|
||||
max_function_tool_concurrency=2,
|
||||
pre_approval_tool_input_guardrails=True,
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`max_function_tool_concurrency=None` 会保留默认行为:当模型在一轮中发出多个工具调用时,SDK 会启动所有已发出的本地工具调用。将其设置为整数值,可限制这些本地工具调用同时运行的数量。
|
||||
|
||||
这与提供商端的 [`ModelSettings.parallel_tool_calls`][agents.model_settings.ModelSettings.parallel_tool_calls] 不同。`parallel_tool_calls` 控制是否允许模型在单个响应中发出多个工具调用。`tool_execution.max_function_tool_concurrency` 控制模型发出本地工具调用后,SDK 如何执行这些调用。
|
||||
|
||||
`pre_approval_tool_input_guardrails=False` 会保留默认审批流程:如果工具调用需要审批,运行会先暂停,工具输入安全防护措施仅在审批通过后、执行前立即运行。如果希望工具输入安全防护措施在发出待审批中断之前运行,请将其设置为 `True`。通过此次审批前检查的调用仍会在审批后再次运行相同的输入安全防护措施,以便在执行前重新验证时效性检查。
|
||||
|
||||
##### `tool_not_found_behavior`
|
||||
|
||||
默认情况下,如果模型发出的工具调用与当前智能体可用的任何工具调用都不匹配,运行器会引发 `ModelBehaviorError`。
|
||||
|
||||
如果希望运行仍可恢复,请设置 `tool_not_found_behavior="return_error_to_model"`。在此模式下,SDK 会为无法解析的工具调用追加一个 `function_call_output`,然后再次运行模型,使模型能够选择可用工具,或者在不使用该工具的情况下作答。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner
|
||||
|
||||
agent = Agent(name="Assistant", tools=[...])
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Handle this request with the available tools.",
|
||||
run_config=RunConfig(tool_not_found_behavior="return_error_to_model"),
|
||||
)
|
||||
```
|
||||
|
||||
此选项目前仅适用于无法解析的工具调用。其他无效工具载荷仍会沿用现有的错误处理行为。
|
||||
|
||||
##### `tool_error_formatter`
|
||||
|
||||
使用 `tool_error_formatter` 可以自定义 SDK 创建模型可见的工具错误输出时返回给模型的消息。
|
||||
|
||||
格式化器接收 [`ToolErrorFormatterArgs`][agents.run_config.ToolErrorFormatterArgs],其中包含:
|
||||
|
||||
- `kind`:错误目录,例如 `"approval_rejected"` 或 `"tool_not_found"`。
|
||||
- `tool_type`:工具运行时(`"function"`、`"computer"`、`"shell"`、`"apply_patch"` 或 `"custom"`)。
|
||||
- `tool_name`:工具名称。
|
||||
- `call_id`:工具调用 ID。
|
||||
- `default_message`:SDK 默认的模型可见消息。
|
||||
- `run_context`:当前活动运行上下文的包装器。
|
||||
|
||||
返回字符串可替换该消息;返回 `None` 则使用 SDK 默认消息。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, ToolErrorFormatterArgs
|
||||
|
||||
|
||||
def format_rejection(args: ToolErrorFormatterArgs[None]) -> str | None:
|
||||
if args.kind == "approval_rejected":
|
||||
return (
|
||||
f"Tool call '{args.tool_name}' was rejected by a human reviewer. "
|
||||
"Ask for confirmation or propose a safer alternative."
|
||||
)
|
||||
if args.kind == "tool_not_found":
|
||||
return f"Tool '{args.tool_name}' is not available. Choose one of the listed tools."
|
||||
return None
|
||||
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Please delete the production database.",
|
||||
run_config=RunConfig(tool_error_formatter=format_rejection),
|
||||
)
|
||||
```
|
||||
|
||||
##### `reasoning_item_id_policy`
|
||||
|
||||
`reasoning_item_id_policy` 控制运行器向后传递历史记录时,如何将推理项转换为下一轮模型输入(例如使用 `RunResult.to_input_list()` 或由会话支持的运行时)。
|
||||
|
||||
- `None` 或 `"preserve"`(默认):保留推理项 ID。
|
||||
- `"omit"`:从生成的下一轮输入中移除推理项 ID。
|
||||
|
||||
`"omit"` 主要作为一种选择启用的缓解措施,用于处理一类 Responses API 400 错误:推理项附带 `id` 发送,但缺少必需的后续项(例如 `Item 'rs_...' of type 'reasoning' was provided without its required following item.`)。
|
||||
|
||||
这种情况可能发生在多轮智能体运行中:SDK 根据之前的输出构造后续输入(包括会话持久化、由服务管理的对话增量、流式/非流式后续轮次以及恢复路径),推理项 ID 被保留,但提供商要求该 ID 必须始终与其对应的后续项配对。
|
||||
|
||||
设置 `reasoning_item_id_policy="omit"` 会保留推理内容,但移除推理项的 `id`,从而避免 SDK 生成的后续输入触发该 API 不变量约束。
|
||||
|
||||
适用范围说明:
|
||||
|
||||
- 此设置仅会更改 SDK 构建后续输入时生成或转发的推理项。
|
||||
- 它不会改写用户提供的初始输入项。
|
||||
- 应用此策略后,`call_model_input_filter` 仍可有意重新引入推理 ID。
|
||||
|
||||
## 状态与对话管理
|
||||
|
||||
### 记忆策略的选择
|
||||
|
||||
将状态带入下一轮通常有四种方式:
|
||||
|
||||
| 策略 | 状态存储位置 | 最适合 | 下一轮传入的内容 |
|
||||
| --- | --- | --- | --- |
|
||||
| `result.to_input_list()` | 你的应用内存 | 小型聊天循环、完全手动控制、任何提供商 | `result.to_input_list()` 返回的列表以及下一条用户消息 |
|
||||
| `session` | 你的存储与 SDK | 持久化聊天状态、可恢复运行、自定义存储 | 同一个 `session` 实例,或指向同一存储的另一个实例 |
|
||||
| `conversation_id` | OpenAI Conversations API | 希望在多个工作进程或服务之间共享的具名服务端对话 | 同一个 `conversation_id`,以及仅包含新的用户轮次 |
|
||||
| `previous_response_id` | OpenAI Responses API | 无需创建对话资源的轻量级服务端延续 | `result.last_response_id`,以及仅包含新的用户轮次 |
|
||||
|
||||
`result.to_input_list()` 和 `session` 由客户端管理。`conversation_id` 和 `previous_response_id` 由 OpenAI 管理,并且仅适用于使用 OpenAI Responses API 的情况。在大多数应用中,应为每个对话选择一种持久化策略。混合使用客户端管理的历史记录与 OpenAI 管理的状态可能会导致上下文重复,除非你有意协调这两个层级。
|
||||
|
||||
!!! note
|
||||
|
||||
同一次运行中,会话持久化不能与服务端管理的对话设置
|
||||
(`conversation_id`、`previous_response_id` 或 `auto_previous_response_id`)
|
||||
结合使用。每次调用请选择一种方式。
|
||||
|
||||
### 对话/聊天线程
|
||||
|
||||
调用任意运行方法都可能导致一个或多个智能体运行(因此会进行一次或多次 LLM 调用),但这在聊天对话中表示一个逻辑轮次。例如:
|
||||
|
||||
1. 用户轮次:用户输入文本
|
||||
2. 运行器运行:第一个智能体调用 LLM、运行工具、将任务转移给第二个智能体;第二个智能体运行更多工具,随后生成输出。
|
||||
|
||||
智能体运行结束后,你可以选择向用户展示哪些内容。例如,可以向用户展示智能体生成的每个新项目,也可以只展示最终输出。无论选择哪种方式,用户之后都可能提出后续问题,此时你可以再次调用运行方法。
|
||||
|
||||
#### 手动对话管理
|
||||
|
||||
你可以使用 [`RunResultBase.to_input_list()`][agents.result.RunResultBase.to_input_list] 方法获取下一轮输入,从而手动管理对话历史记录:
|
||||
|
||||
```python
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
thread_id = "thread_123" # Example thread ID
|
||||
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 = result.to_input_list() + [{"role": "user", "content": "What state is it in?"}]
|
||||
result = await Runner.run(agent, new_input)
|
||||
print(result.final_output)
|
||||
# California
|
||||
```
|
||||
|
||||
#### 使用会话自动管理对话
|
||||
|
||||
若要采用更简单的方式,可以使用 [Sessions](sessions/index.md) 自动处理对话历史记录,而无需手动调用 `.to_input_list()`:
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
# Create session instance
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
thread_id = "thread_123" # Example thread ID
|
||||
with trace(workflow_name="Conversation", group_id=thread_id):
|
||||
# First turn
|
||||
result = await Runner.run(agent, "What city is the Golden Gate Bridge in?", session=session)
|
||||
print(result.final_output)
|
||||
# San Francisco
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(agent, "What state is it in?", session=session)
|
||||
print(result.final_output)
|
||||
# California
|
||||
```
|
||||
|
||||
Sessions 会自动:
|
||||
|
||||
- 在每次运行前检索对话历史记录
|
||||
- 在每次运行后存储新消息
|
||||
- 为不同的会话 ID 维护彼此独立的对话
|
||||
|
||||
更多详情请参阅 [Sessions 文档](sessions/index.md)。
|
||||
|
||||
|
||||
#### 服务端管理的对话
|
||||
|
||||
除了在本地使用 `to_input_list()` 或 `Sessions` 处理对话状态,你也可以让 OpenAI 对话状态功能在服务端管理对话状态。这样无需手动重新发送所有历史消息,即可保留对话历史记录。使用下述任一服务端管理方式时,每次请求仅传入新一轮的输入,并复用已保存的 ID。更多详情请参阅 [OpenAI 对话状态指南](https://platform.openai.com/docs/guides/conversation-state?api-mode=responses)。
|
||||
|
||||
OpenAI 提供两种跨轮次追踪状态的方式:
|
||||
|
||||
##### 1. 使用 `conversation_id`
|
||||
|
||||
首先使用 OpenAI Conversations API 创建一个对话,然后在后续每次调用中复用其 ID:
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
client = AsyncOpenAI()
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
# Create a server-managed conversation
|
||||
conversation = await client.conversations.create()
|
||||
conv_id = conversation.id
|
||||
|
||||
while True:
|
||||
user_input = input("You: ")
|
||||
result = await Runner.run(agent, user_input, conversation_id=conv_id)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
```
|
||||
|
||||
##### 2. 使用 `previous_response_id`
|
||||
|
||||
另一种选择是**响应链式连接**,其中每一轮都会显式关联上一轮的响应 ID。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Assistant", instructions="Reply very concisely.")
|
||||
|
||||
previous_response_id = None
|
||||
|
||||
while True:
|
||||
user_input = input("You: ")
|
||||
|
||||
# Setting auto_previous_response_id=True enables response chaining automatically
|
||||
# for the first turn, even when there's no actual previous response ID yet.
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
user_input,
|
||||
previous_response_id=previous_response_id,
|
||||
auto_previous_response_id=True,
|
||||
)
|
||||
previous_response_id = result.last_response_id
|
||||
print(f"Assistant: {result.final_output}")
|
||||
```
|
||||
|
||||
如果运行因等待审批而暂停,并且你从 [`RunState`][agents.run_state.RunState] 恢复运行,SDK 会保留已保存的 `conversation_id` / `previous_response_id` / `auto_previous_response_id` 设置,使恢复后的轮次继续使用同一个由服务端管理的对话。
|
||||
|
||||
`conversation_id` 和 `previous_response_id` 互斥。如果你需要一个可跨系统共享的具名对话资源,请使用 `conversation_id`。如果你需要在轮次之间使用最轻量的 Responses API 延续基本组件,请使用 `previous_response_id`。
|
||||
|
||||
!!! note
|
||||
|
||||
SDK 会使用退避机制自动重试 `conversation_locked` 错误。在由服务端管理的
|
||||
对话运行中,SDK 会在重试前回退内部对话追踪器的输入,以便能够干净地重新发送
|
||||
相同的已准备项目。
|
||||
|
||||
在基于本地会话的运行中(不能与 `conversation_id`、
|
||||
`previous_response_id` 或 `auto_previous_response_id` 结合使用),SDK 还会尽力
|
||||
回滚最近持久化的输入项,以减少重试后出现的重复历史记录条目。
|
||||
|
||||
即使你没有配置 `ModelSettings.retry`,也会进行此兼容性重试。有关
|
||||
更广泛、可选择启用的模型请求重试行为,请参阅[由 Runner 管理的重试](models/index.md#runner-managed-retries)。
|
||||
|
||||
## 钩子与自定义
|
||||
|
||||
### 模型调用输入过滤器
|
||||
|
||||
使用 `call_model_input_filter` 可以在模型调用之前编辑模型输入。该钩子会接收当前智能体、上下文和合并后的输入项(包括存在会话时的会话历史记录),并返回新的 `ModelInputData`。
|
||||
|
||||
返回值必须是 [`ModelInputData`][agents.run.ModelInputData] 对象。其 `input` 字段为必填项,并且必须是输入项列表。返回任何其他结构都会引发 `UserError`。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, RunConfig
|
||||
from agents.run import CallModelData, ModelInputData
|
||||
|
||||
def drop_old_messages(data: CallModelData[None]) -> ModelInputData:
|
||||
# Keep only the last 5 items and preserve existing instructions.
|
||||
trimmed = data.model_data.input[-5:]
|
||||
return ModelInputData(input=trimmed, instructions=data.model_data.instructions)
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Answer concisely.")
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Explain quines",
|
||||
run_config=RunConfig(call_model_input_filter=drop_old_messages),
|
||||
)
|
||||
```
|
||||
|
||||
运行器会将已准备输入列表的副本传给该钩子,因此你可以裁剪、替换或重新排序该列表,而无需就地修改调用方的原始列表。
|
||||
|
||||
如果你正在使用会话,`call_model_input_filter` 会在会话历史记录加载完毕并与当前轮次合并后运行。如果希望自定义更早的合并步骤本身,请使用 [`session_input_callback`][agents.run.RunConfig.session_input_callback]。
|
||||
|
||||
如果你通过 `conversation_id`、`previous_response_id` 或 `auto_previous_response_id` 使用 OpenAI 服务端管理的对话状态,该钩子会针对下一次 Responses API 调用的已准备载荷运行。该载荷可能已经仅表示新一轮的增量,而不是完整重放此前的历史记录。只有你返回的项目才会被标记为已针对该服务端管理的延续发送。
|
||||
|
||||
可以通过 `run_config` 为每次运行设置该钩子,以遮盖敏感数据、裁剪过长的历史记录或注入额外的系统指导。
|
||||
|
||||
## 错误与恢复
|
||||
|
||||
### 错误处理程序
|
||||
|
||||
所有 `Runner` 入口点都接受 `error_handlers`,这是一个以错误类型为键的字典。支持的键包括 `"max_turns"`、`"model_refusal"` 和 `"invalid_final_output"`。如果你希望返回受控的最终输出,而不是让运行以相应错误结束,请使用这些键。
|
||||
|
||||
```python
|
||||
from agents import (
|
||||
Agent,
|
||||
RunErrorHandlerInput,
|
||||
RunErrorHandlerResult,
|
||||
Runner,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant", instructions="Be concise.")
|
||||
|
||||
|
||||
def on_max_turns(_data: RunErrorHandlerInput[None]) -> RunErrorHandlerResult:
|
||||
return RunErrorHandlerResult(
|
||||
final_output="I couldn't finish within the turn limit. Please narrow the request.",
|
||||
include_in_history=False,
|
||||
)
|
||||
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Analyze this long transcript",
|
||||
max_turns=3,
|
||||
error_handlers={"max_turns": on_max_turns},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
当模型消息无法通过智能体结构化 `output_type` 的验证,或者模型未返回结构化最终消息时,请使用 `"invalid_final_output"`。处理程序可以返回应用特定的后备值,SDK 会根据同一个 `output_type` 对其进行验证。它不会重试模型调用,也不会重放任何工具副作用。返回 `None` 表示拒绝恢复。如果没有后备值,非空验证失败仍会引发 `ModelBehaviorError`,而空的结构化响应会保留现有的下一轮行为。
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, ModelBehaviorError, RunErrorHandlerInput, Runner
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
ingredients: list[str]
|
||||
recovered_from_invalid_output: bool = False
|
||||
|
||||
|
||||
def on_invalid_final_output(data: RunErrorHandlerInput[None]) -> Recipe:
|
||||
assert isinstance(data.error, ModelBehaviorError)
|
||||
return Recipe(ingredients=[], recovered_from_invalid_output=True)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Recipe assistant",
|
||||
instructions="Return a structured recipe.",
|
||||
output_type=Recipe,
|
||||
)
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Plan tonight's dinner.",
|
||||
error_handlers={"invalid_final_output": on_invalid_final_output},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
如果不希望将后备输出追加到对话历史记录,请设置 `include_in_history=False`。
|
||||
|
||||
如果希望模型拒绝时生成应用特定的后备值,而不是让运行以 `ModelRefusalError` 结束,请使用 `"model_refusal"`。
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel
|
||||
|
||||
from agents import Agent, ModelRefusalError, RunErrorHandlerInput, Runner
|
||||
|
||||
|
||||
class Recipe(BaseModel):
|
||||
ingredients: list[str]
|
||||
refusal_reason: str | None = None
|
||||
|
||||
|
||||
def on_model_refusal(data: RunErrorHandlerInput[None]) -> Recipe:
|
||||
assert isinstance(data.error, ModelRefusalError)
|
||||
return Recipe(ingredients=[], refusal_reason=data.error.refusal)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Recipe assistant",
|
||||
instructions="Return a structured recipe.",
|
||||
output_type=Recipe,
|
||||
)
|
||||
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"Make me something unsafe.",
|
||||
error_handlers={"model_refusal": on_model_refusal},
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
## 持久执行集成与人在回路
|
||||
|
||||
对于工具审批的暂停/恢复模式,请先参阅专门的[人在回路指南](human_in_the_loop.md)。以下集成适用于持久编排,即运行可能经历长时间等待、重试或进程重启的情况。
|
||||
|
||||
### Dapr
|
||||
|
||||
你可以使用 Agents SDK 的 [Dapr](https://dapr.io) Diagrid 集成来运行持久、长时间运行的智能体,这些智能体支持人在回路,并能自动从故障中恢复。Dapr 是一个供应商中立的 [CNCF](https://cncf.io) 工作流编排器。请从[这里](https://docs.diagrid.io/getting-started/quickstarts/ai-agents/?agentframework=openai)开始使用 Dapr 和 OpenAI智能体。
|
||||
|
||||
### Temporal
|
||||
|
||||
你可以使用 Agents SDK 的 [Temporal](https://temporal.io/) 集成来运行持久、长时间运行的工作流,包括人在回路任务。你可以[在此视频中](https://www.youtube.com/watch?v=fFBZqzT4DD8)观看 Temporal 与 Agents SDK 协同完成长时间运行任务的演示,并[在此查看文档](https://github.com/temporalio/sdk-python/tree/main/temporalio/contrib/openai_agents)。
|
||||
|
||||
### Restate
|
||||
|
||||
你可以使用 Agents SDK 的 [Restate](https://restate.dev/) 集成来运行轻量、持久的智能体,包括人工审批、任务转移和会话管理。该集成依赖 Restate 的单二进制运行时,并支持将智能体作为进程/容器或无服务函数运行。更多详情请阅读[概述](https://www.restate.dev/blog/durable-orchestration-for-ai-agents-with-restate-and-openai-sdk)或查看[文档](https://docs.restate.dev/ai)。
|
||||
|
||||
### DBOS
|
||||
|
||||
你可以使用 Agents SDK 的 [DBOS](https://dbos.dev/) 集成来运行可靠的智能体,使其能够在故障和重启时保留进度。它支持长时间运行的智能体、人在回路工作流和任务转移,也同时支持同步和异步方法。该集成只需要 SQLite 或 Postgres 数据库。更多详情请查看集成[代码仓库](https://github.com/dbos-inc/dbos-openai-agents)和[文档](https://docs.dbos.dev/integrations/openai-agents)。
|
||||
|
||||
## 异常
|
||||
|
||||
SDK 会在某些情况下引发异常。完整列表请参阅 [`agents.exceptions`][]。概述如下:
|
||||
|
||||
- [`AgentsException`][agents.exceptions.AgentsException]:这是 SDK 内引发的所有异常的基类。它是一种通用类型,所有其他特定异常都派生自该类型。
|
||||
- [`MaxTurnsExceeded`][agents.exceptions.MaxTurnsExceeded]:当智能体运行超过传给 `Runner.run`、`Runner.run_sync` 或 `Runner.run_streamed` 方法的 `max_turns` 限制时,会引发此异常。它表示智能体无法在指定的交互轮次数内完成任务。设置 `max_turns=None` 可禁用该限制。
|
||||
- [`ModelBehaviorError`][agents.exceptions.ModelBehaviorError]:当底层模型(LLM)生成意外或无效的输出时,会发生此异常。其中可能包括:
|
||||
- 格式错误的 JSON:模型为工具调用或直接输出提供了格式错误的 JSON 结构,尤其是在定义了特定 `output_type` 时。
|
||||
- 意外的工具相关故障:模型未能按预期方式使用工具。
|
||||
- [`ToolTimeoutError`][agents.exceptions.ToolTimeoutError]:当工具调用超过其配置的超时时间,并且该工具使用 `timeout_behavior="raise_exception"` 时,会引发此异常。
|
||||
- [`UserError`][agents.exceptions.UserError]:当你(使用 SDK 编写代码的人)在使用 SDK 时出错,会引发此异常。这通常是由代码实现不正确、配置无效或误用 SDK API 导致的。
|
||||
- [`InputGuardrailTripwireTriggered`][agents.exceptions.InputGuardrailTripwireTriggered]、[`OutputGuardrailTripwireTriggered`][agents.exceptions.OutputGuardrailTripwireTriggered]:分别在满足输入安全防护措施或输出安全防护措施的触发条件时引发。输入安全防护措施会在处理前检查传入消息,而输出安全防护措施会在交付前检查智能体的最终响应。
|
||||
@@ -0,0 +1,141 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 沙盒客户端
|
||||
|
||||
使用本页选择沙盒任务应在哪里运行。大多数情况下,`SandboxAgent`定义保持不变,而沙盒客户端和客户端特定选项会在[`SandboxRunConfig`][agents.run_config.SandboxRunConfig]中变化。
|
||||
|
||||
!!! warning "Beta 功能"
|
||||
|
||||
沙盒智能体处于 Beta 阶段。在正式发布前,API 的细节、默认值和支持的功能可能会发生变化;未来也会陆续提供更高级的功能。
|
||||
|
||||
## 决策指南
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 目标 | 起点 | 原因 |
|
||||
| --- | --- | --- |
|
||||
| 在 macOS 或 Linux 上进行最快的本地迭代 | `UnixLocalSandboxClient` | 无需额外安装,便于进行简单的本地文件系统开发。 |
|
||||
| 基本容器隔离 | `DockerSandboxClient` | 使用特定镜像在 Docker 中运行任务。 |
|
||||
| 托管执行或生产风格隔离 | 一个托管沙盒客户端 | 将工作区边界移动到由提供商管理的环境中。 |
|
||||
|
||||
</div>
|
||||
|
||||
## 本地客户端
|
||||
|
||||
对于大多数用户,请从以下两个沙盒客户端之一开始:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 客户端 | 安装 | 选择场景 | 代码示例 |
|
||||
| --- | --- | --- | --- |
|
||||
| `UnixLocalSandboxClient` | 无 | 在 macOS 或 Linux 上进行最快的本地迭代。适合作为本地开发的默认选择。 | [Unix-local 入门示例](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/unix_local_runner.py) |
|
||||
| `DockerSandboxClient` | `openai-agents[docker]` | 你需要容器隔离,或需要特定镜像来保持本地环境一致性。 | [Docker 入门示例](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py) |
|
||||
|
||||
</div>
|
||||
|
||||
Unix-local 是开始基于本地文件系统进行开发的最简单方式。当你需要更强的环境隔离或生产风格的一致性时,再迁移到 Docker 或托管提供商。
|
||||
|
||||
要从 Unix-local 切换到 Docker,请保持智能体定义不变,只更改运行配置:
|
||||
|
||||
```python
|
||||
from docker import from_env as docker_from_env
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.docker import DockerSandboxClient, DockerSandboxClientOptions
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=DockerSandboxClient(docker_from_env()),
|
||||
options=DockerSandboxClientOptions(image="python:3.14-slim"),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当你需要容器隔离或镜像一致性时使用此方式。参见[examples/sandbox/docker/docker_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py)。
|
||||
|
||||
## 挂载与远程存储
|
||||
|
||||
挂载条目描述要暴露哪些存储;挂载策略描述沙盒后端如何附加该存储。请从`agents.sandbox.entries`导入内置挂载条目和通用策略。托管提供商策略可从`agents.extensions.sandbox`或提供商特定的扩展包获得。
|
||||
|
||||
常见挂载选项:
|
||||
|
||||
- `mount_path`: 存储在沙盒中的显示位置。相对路径会在清单根目录下解析;绝对路径按原样使用。
|
||||
- `read_only`: 默认值为`True`。仅当沙盒需要写回挂载的存储时,才设置为`False`。
|
||||
- `mount_strategy`: 必填。使用同时匹配挂载条目和沙盒后端的策略。
|
||||
|
||||
挂载会被视为临时工作区条目。快照和持久化流程会分离或跳过已挂载路径,而不是将已挂载的远程存储复制到保存的工作区中。
|
||||
|
||||
通用本地/容器策略:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 策略或模式 | 适用场景 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `InContainerMountStrategy(pattern=RcloneMountPattern(...))` | 沙盒镜像可以运行`rclone`。 | 支持 S3、GCS、R2、Azure Blob 和 Box。`RcloneMountPattern`可以在`fuse`模式或`nfs`模式下运行。 |
|
||||
| `InContainerMountStrategy(pattern=MountpointMountPattern(...))` | 镜像包含`mount-s3`,并且你需要 Mountpoint 风格的 S3 或 S3 兼容访问。 | 支持`S3Mount`和`GCSMount`。 |
|
||||
| `InContainerMountStrategy(pattern=FuseMountPattern(...))` | 镜像包含`blobfuse2`并支持 FUSE。 | 支持`AzureBlobMount`。 |
|
||||
| `InContainerMountStrategy(pattern=S3FilesMountPattern(...))` | 镜像包含`mount.s3files`,并且可以访问现有的 S3 Files 挂载目标。 | 支持`S3FilesMount`。 |
|
||||
| `DockerVolumeMountStrategy(driver=...)` | Docker 应在容器启动前附加由卷驱动支持的挂载。 | 仅限 Docker。S3、GCS、R2、Azure Blob 和 Box 支持`rclone`;S3 和 GCS 还支持`mountpoint`。 |
|
||||
|
||||
</div>
|
||||
|
||||
## 支持的托管平台
|
||||
|
||||
当你需要托管环境时,通常可以沿用相同的`SandboxAgent`定义,只在[`SandboxRunConfig`][agents.run_config.SandboxRunConfig]中更改沙盒客户端。
|
||||
|
||||
如果你使用的是已发布的 SDK,而不是此仓库的检出版本,请通过匹配的软件包 extra 安装沙盒客户端依赖。
|
||||
|
||||
有关提供商特定的设置说明,以及仓库中已提交的扩展代码示例链接,请参见[examples/sandbox/extensions/README.md](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/README.md)。
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 客户端 | 安装 | 代码示例 |
|
||||
| --- | --- | --- |
|
||||
| `BlaxelSandboxClient` | `openai-agents[blaxel]` | [Blaxel 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/blaxel_runner.py) |
|
||||
| `CloudflareSandboxClient` | `openai-agents[cloudflare]` | [Cloudflare 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/cloudflare_runner.py) |
|
||||
| `DaytonaSandboxClient` | `openai-agents[daytona]` | [Daytona 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/daytona/daytona_runner.py) |
|
||||
| `E2BSandboxClient` | `openai-agents[e2b]` | [E2B 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/e2b_runner.py) |
|
||||
| `ModalSandboxClient` | `openai-agents[modal]` | [Modal 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/modal_runner.py) |
|
||||
| `RunloopSandboxClient` | `openai-agents[runloop]` | [Runloop 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/runloop/runner.py) |
|
||||
| `VercelSandboxClient` | `openai-agents[vercel]` | [Vercel 运行器](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/vercel_runner.py) |
|
||||
|
||||
</div>
|
||||
|
||||
托管沙盒客户端会提供特定于提供商的挂载策略。请选择最适合你的存储提供商的后端和挂载策略:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 后端 | 挂载说明 |
|
||||
| --- | --- |
|
||||
| Docker | 支持将`S3Mount`、`GCSMount`、`R2Mount`、`AzureBlobMount`、`BoxMount`和`S3FilesMount`与`InContainerMountStrategy`、`DockerVolumeMountStrategy`等本地策略配合使用。 |
|
||||
| `ModalSandboxClient` | 支持在`S3Mount`、`R2Mount`和经过 HMAC 认证的`GCSMount`上使用`ModalCloudBucketMountStrategy`进行 Modal 云存储桶挂载。你可以使用内联凭据或命名的 Modal Secret。 |
|
||||
| `CloudflareSandboxClient` | 支持在`S3Mount`、`R2Mount`和经过 HMAC 认证的`GCSMount`上使用`CloudflareBucketMountStrategy`进行 Cloudflare 存储桶挂载。 |
|
||||
| `BlaxelSandboxClient` | 支持在`S3Mount`、`R2Mount`和`GCSMount`上使用`BlaxelCloudBucketMountStrategy`进行云存储桶挂载。还支持使用来自`agents.extensions.sandbox.blaxel`的`BlaxelDriveMount`和`BlaxelDriveMountStrategy`实现持久化 Blaxel Drives。 |
|
||||
| `DaytonaSandboxClient` | 支持通过`DaytonaCloudBucketMountStrategy`进行由 rclone 支持的云存储挂载;可将其与`S3Mount`、`GCSMount`、`R2Mount`、`AzureBlobMount`和`BoxMount`配合使用。 |
|
||||
| `E2BSandboxClient` | 支持通过`E2BCloudBucketMountStrategy`进行由 rclone 支持的云存储挂载;可将其与`S3Mount`、`GCSMount`、`R2Mount`、`AzureBlobMount`和`BoxMount`配合使用。 |
|
||||
| `RunloopSandboxClient` | 支持通过`RunloopCloudBucketMountStrategy`进行由 rclone 支持的云存储挂载;可将其与`S3Mount`、`GCSMount`、`R2Mount`、`AzureBlobMount`和`BoxMount`配合使用。 |
|
||||
| `VercelSandboxClient` | 目前未暴露特定于托管环境的挂载策略。请改用清单文件、仓库或其他工作区输入。 |
|
||||
|
||||
</div>
|
||||
|
||||
下表总结了每个后端可以直接挂载哪些远程存储条目。
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 后端 | AWS S3 | Cloudflare R2 | GCS | Azure Blob Storage | Box | S3 Files |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| Docker | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| `ModalSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `CloudflareSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `BlaxelSandboxClient` | ✓ | ✓ | ✓ | - | - | - |
|
||||
| `DaytonaSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `E2BSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `RunloopSandboxClient` | ✓ | ✓ | ✓ | ✓ | ✓ | - |
|
||||
| `VercelSandboxClient` | - | - | - | - | - | - |
|
||||
|
||||
</div>
|
||||
|
||||
如需更多可运行代码示例,请浏览[examples/sandbox/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox),了解本地、代码编写、记忆、任务转移和智能体组合模式;并浏览[examples/sandbox/extensions/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox/extensions),了解托管沙盒客户端。
|
||||
@@ -0,0 +1,861 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 概念
|
||||
|
||||
!!! warning "Beta 功能"
|
||||
|
||||
沙盒智能体目前处于 Beta 阶段。在正式发布前,API 细节、默认值和支持的功能可能会发生变化,未来也将提供更多高级功能。
|
||||
|
||||
现代智能体在能够操作文件系统中的真实文件时效果最佳。**沙盒智能体**可以使用专用工具和 shell 命令搜索及处理大型文档集、编辑文件、生成产物并运行命令。沙盒为模型提供了一个持久工作区,智能体可以在其中代您完成工作。Agents SDK 中的沙盒智能体可帮助您轻松运行与沙盒环境配套的智能体,便于将正确的文件放入文件系统并编排沙盒,从而轻松地大规模启动、停止和恢复任务。
|
||||
|
||||
您可以围绕智能体所需的数据定义工作区。工作区可以从 GitHub 仓库、本地文件和目录、合成任务文件、S3 或 Azure Blob Storage 等远程文件系统,以及您提供的其他沙盒输入开始构建。
|
||||
|
||||
<div class="sandbox-harness-image" markdown="1">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
`SandboxAgent` 仍然是一个 `Agent`。它保留了常规的智能体接口,例如 `instructions`、`prompt`、`tools`、`handoffs`、`mcp_servers`、`model_settings`、`output_type`、安全防护措施和钩子,并且仍通过常规的 `Runner` API 运行。变化之处在于执行边界:
|
||||
|
||||
- `SandboxAgent` 定义智能体本身:包括常规智能体配置,以及 `default_manifest`、`base_instructions`、`run_as` 等沙盒专用默认值,还有文件系统工具、shell 访问、技能、记忆或压缩等能力。
|
||||
- `Manifest` 声明全新沙盒工作区所需的初始内容和布局,包括文件、仓库、挂载和环境。
|
||||
- 沙盒会话是命令运行和文件发生变化的实时隔离环境。
|
||||
- [`SandboxRunConfig`][agents.run_config.SandboxRunConfig] 决定一次运行如何获得该沙盒会话,例如直接注入会话、根据序列化的沙盒会话状态重新连接,或通过沙盒客户端创建全新的沙盒会话。
|
||||
- 保存的沙盒状态和快照允许后续运行重新连接到之前的工作,或使用保存的内容初始化全新的沙盒会话。
|
||||
|
||||
`Manifest` 是全新会话的工作区契约,而不是每个实时沙盒全部状态的唯一事实来源。一次运行的有效工作区也可能来自复用的沙盒会话、序列化的沙盒会话状态,或运行时选择的快照。
|
||||
|
||||
在本页中,“沙盒会话”是指由沙盒客户端管理的实时执行环境。它不同于[会话](../sessions/index.md)中介绍的 SDK 对话式 [`Session`][agents.memory.session.Session] 接口。
|
||||
|
||||
外层运行时仍负责审批、追踪、任务转移和恢复记录。沙盒会话负责命令、文件变更和环境隔离。这种职责划分是该模型的核心组成部分。
|
||||
|
||||
### 组件协作方式
|
||||
|
||||
一次沙盒运行会将智能体定义与每次运行的沙盒配置结合起来。运行器会准备智能体,将其绑定到实时沙盒会话,并可保存状态供后续运行使用。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
agent["SandboxAgent<br/><small>full Agent + sandbox defaults</small>"]
|
||||
config["SandboxRunConfig<br/><small>client / session / resume inputs</small>"]
|
||||
runner["Runner<br/><small>prepare instructions<br/>bind capability tools</small>"]
|
||||
sandbox["sandbox session<br/><small>workspace where commands run<br/>and files change</small>"]
|
||||
saved["saved state / snapshot<br/><small>for resume or fresh-start later</small>"]
|
||||
|
||||
agent --> runner
|
||||
config --> runner
|
||||
runner --> sandbox
|
||||
sandbox --> saved
|
||||
```
|
||||
|
||||
沙盒专用默认值保留在 `SandboxAgent` 上。每次运行的沙盒会话选项则保留在 `SandboxRunConfig` 中。
|
||||
|
||||
可以将生命周期分为三个阶段:
|
||||
|
||||
1. 使用 `SandboxAgent`、`Manifest` 和能力定义智能体及全新工作区契约。
|
||||
2. 通过向 `Runner` 提供可注入、恢复或创建沙盒会话的 `SandboxRunConfig` 来执行一次运行。
|
||||
3. 稍后从运行器管理的 `RunState`、显式沙盒 `session_state` 或保存的工作区快照继续运行。
|
||||
|
||||
如果 shell 访问只是偶尔使用的工具,请从[工具指南](../tools.md)中的托管 shell 开始。当工作区隔离、沙盒客户端选择或沙盒会话恢复行为属于整体设计的一部分时,再使用沙盒智能体。
|
||||
|
||||
## 适用场景
|
||||
|
||||
沙盒智能体非常适合以工作区为中心的工作流,例如:
|
||||
|
||||
- 编码和调试,例如针对 GitHub 仓库中的问题报告编排自动修复并运行针对性测试
|
||||
- 文档处理和编辑,例如从用户的财务文档中提取信息并创建填写完成的税务表单草稿
|
||||
- 基于文件的审查或分析,例如在回答前检查入职资料包、生成的报告或产物包
|
||||
- 隔离式多智能体模式,例如为每个审查智能体或编码子智能体提供独立工作区
|
||||
- 多步骤工作区任务,例如在一次运行中修复错误,稍后再添加回归测试,或从快照或沙盒会话状态恢复
|
||||
|
||||
如果不需要访问文件或持续存在的文件系统,请继续使用 `Agent`。如果 shell 访问只是偶尔使用的能力,请添加托管 shell;如果工作区边界本身就是功能的一部分,请使用沙盒智能体。
|
||||
|
||||
## 沙盒客户端的选择
|
||||
|
||||
本地开发可从 `UnixLocalSandboxClient` 开始。当需要容器隔离或镜像一致性时,请改用 `DockerSandboxClient`。当需要由提供商管理执行时,请改用托管提供商。
|
||||
|
||||
大多数情况下,`SandboxAgent` 定义保持不变,只需在 [`SandboxRunConfig`][agents.run_config.SandboxRunConfig] 中更改沙盒客户端及其选项。有关本地、Docker、托管和远程挂载选项,请参阅[沙盒客户端](clients.md)。
|
||||
|
||||
## 核心组件
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 层级 | 主要 SDK 组件 | 解答的问题 |
|
||||
| --- | --- | --- |
|
||||
| 智能体定义 | `SandboxAgent`、`Manifest`、能力 | 将运行哪个智能体,以及它应从什么样的全新会话工作区契约开始? |
|
||||
| 沙盒执行 | `SandboxRunConfig`、沙盒客户端和实时沙盒会话 | 此次运行如何获得实时沙盒会话,以及工作在哪里执行? |
|
||||
| 保存的沙盒状态 | `RunState` 沙盒有效负载、`session_state` 和快照 | 此工作流如何重新连接到之前的沙盒工作,或根据保存的内容初始化全新的沙盒会话? |
|
||||
|
||||
</div>
|
||||
|
||||
主要 SDK 组件与这些层级的对应关系如下:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 组件 | 负责的内容 | 应提出的问题 |
|
||||
| --- | --- | --- |
|
||||
| [`SandboxAgent`][agents.sandbox.sandbox_agent.SandboxAgent] | 智能体定义 | 此智能体应执行什么任务,以及哪些默认值应随其一同使用? |
|
||||
| [`Manifest`][agents.sandbox.manifest.Manifest] | 全新会话的工作区文件和文件夹 | 运行开始时,文件系统中应存在哪些文件和文件夹? |
|
||||
| [`Capability`][agents.sandbox.capabilities.capability.Capability] | 沙盒原生行为 | 哪些工具、指令片段或运行时行为应附加到此智能体? |
|
||||
| [`SandboxRunConfig`][agents.run_config.SandboxRunConfig] | 每次运行的沙盒客户端和沙盒会话来源 | 此次运行应注入、恢复还是创建沙盒会话? |
|
||||
| [`RunState`][agents.run_state.RunState] | 由运行器管理的已保存沙盒状态 | 我是否正在恢复之前由运行器管理的工作流,并自动延续其沙盒状态? |
|
||||
| [`SandboxRunConfig.session_state`][agents.run_config.SandboxRunConfig.session_state] | 显式序列化的沙盒会话状态 | 我是否希望从已在 `RunState` 外部序列化的沙盒状态恢复? |
|
||||
| [`SandboxRunConfig.snapshot`][agents.run_config.SandboxRunConfig.snapshot] | 用于全新沙盒会话的已保存工作区内容 | 新的沙盒会话是否应从保存的文件和产物开始? |
|
||||
|
||||
</div>
|
||||
|
||||
实用的设计顺序如下:
|
||||
|
||||
1. 使用 `Manifest` 定义全新会话工作区契约。
|
||||
2. 使用 `SandboxAgent` 定义智能体。
|
||||
3. 添加内置或自定义能力。
|
||||
4. 在 `RunConfig(sandbox=SandboxRunConfig(...))` 中决定每次运行应如何获取沙盒会话。
|
||||
|
||||
## 沙盒运行的准备过程
|
||||
|
||||
运行时,运行器会将该定义转换为由具体沙盒支持的运行:
|
||||
|
||||
1. 它会从 `SandboxRunConfig` 解析沙盒会话。如果传入 `session=...`,则复用该实时沙盒会话。否则,它会使用 `client=...` 创建或恢复会话。
|
||||
2. 它会确定此次运行的有效工作区输入。如果此次运行注入或恢复了沙盒会话,则以现有沙盒状态为准。否则,运行器将从一次性清单覆盖项或 `agent.default_manifest` 开始。这正是仅凭 `Manifest` 无法定义每次运行最终实时工作区的原因。
|
||||
3. 它会让各项能力处理生成的清单。通过这种方式,能力可以在最终智能体准备完成前添加文件、挂载或其他工作区范围的行为。
|
||||
4. 它会按固定顺序构建最终指令:SDK 的默认沙盒提示词;如果显式覆盖,则使用 `base_instructions`;随后依次加入 `instructions`、能力指令片段、所有远程挂载策略文本,最后加入渲染后的文件系统树。
|
||||
5. 它会将能力工具绑定到实时沙盒会话,并通过常规 `Runner` API 运行准备好的智能体。
|
||||
|
||||
沙盒不会改变一轮交互的含义。一轮仍然是一个模型步骤,而不是一条 shell 命令或一次沙盒操作。沙盒侧操作与轮次之间没有固定的 1:1 对应关系:有些工作可能始终位于沙盒执行层内,而其他操作会返回工具结果、审批或其他需要额外模型步骤的状态。作为实用原则,只有在沙盒工作完成后,智能体运行时需要模型再次响应时,才会消耗新的一轮。
|
||||
|
||||
这些准备步骤说明了为什么在设计 `SandboxAgent` 时,`default_manifest`、`instructions`、`base_instructions`、`capabilities` 和 `run_as` 是需要重点考虑的沙盒专用选项。
|
||||
|
||||
## `SandboxAgent` 选项
|
||||
|
||||
除常规 `Agent` 字段外,还提供以下沙盒专用选项:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 选项 | 最佳用途 |
|
||||
| --- | --- |
|
||||
| `default_manifest` | 由运行器创建的全新沙盒会话所使用的默认工作区。 |
|
||||
| `instructions` | 追加在 SDK 沙盒提示词后的额外角色、工作流和成功标准。 |
|
||||
| `base_instructions` | 用于替换 SDK 沙盒提示词的高级逃生舱机制。 |
|
||||
| `capabilities` | 应随此智能体一同使用的沙盒原生工具和行为。 |
|
||||
| `run_as` | 面向模型的沙盒工具所使用的用户身份,例如 shell 命令、文件读取和补丁操作。 |
|
||||
|
||||
</div>
|
||||
|
||||
沙盒客户端选择、沙盒会话复用、清单覆盖和快照选择应放在 [`SandboxRunConfig`][agents.run_config.SandboxRunConfig] 中,而不是智能体上。
|
||||
|
||||
### `default_manifest`
|
||||
|
||||
`default_manifest` 是运行器为此智能体创建全新沙盒会话时使用的默认 [`Manifest`][agents.sandbox.manifest.Manifest]。它适用于智能体通常应以其为起点的文件、仓库、辅助材料、输出目录和挂载。
|
||||
|
||||
这只是默认值。一次运行可以使用 `SandboxRunConfig(manifest=...)` 覆盖它,而复用或恢复的沙盒会话会保留其现有工作区状态。
|
||||
|
||||
### `instructions` 和 `base_instructions`
|
||||
|
||||
使用 `instructions` 设置应在不同提示词之间保持不变的简短规则。在 `SandboxAgent` 中,这些指令会追加到 SDK 的沙盒基础提示词之后,因此您可以保留内置沙盒指导,并添加自己的角色、工作流和成功标准。
|
||||
|
||||
仅当希望替换 SDK 的沙盒基础提示词时,才使用 `base_instructions`。大多数智能体不应设置它。
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 放置位置 | 用途 | 示例 |
|
||||
| --- | --- | --- |
|
||||
| `instructions` | 智能体的稳定角色、工作流规则和成功标准。 | “检查入职文档,然后进行任务转移。”、“将最终文件写入 `output/`。” |
|
||||
| `base_instructions` | 完整替换 SDK 的沙盒基础提示词。 | 自定义底层沙盒包装器提示词。 |
|
||||
| 用户提示词 | 此次运行的一次性请求。 | “总结此工作区。” |
|
||||
| 清单中的工作区文件 | 更长的任务规范、仓库本地指令或范围受限的参考材料。 | `repo/task.md`、文档包、样本资料包。 |
|
||||
|
||||
</div>
|
||||
|
||||
`instructions` 的良好用法包括:
|
||||
|
||||
- [examples/sandbox/unix_local_pty.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/unix_local_pty.py) 会在 PTY 状态很重要时,让智能体始终在同一个交互式进程中运行。
|
||||
- [examples/sandbox/handoffs.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/handoffs.py) 禁止沙盒审查智能体在检查后直接回答用户。
|
||||
- [examples/sandbox/tax_prep.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/tax_prep.py) 要求最终填写完成的文件必须实际写入 `output/`。
|
||||
- [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py) 固定确切的验证命令,并明确补丁路径相对于工作区根目录。
|
||||
|
||||
应避免将用户的一次性任务复制到 `instructions`,避免嵌入应放在清单中的长篇参考材料,避免重复内置能力已注入的工具文档,也不要混入模型在运行时不需要的本地安装说明。
|
||||
|
||||
如果省略 `instructions`,SDK 仍会包含默认沙盒提示词。这对于底层包装器已经足够,但大多数面向用户的智能体仍应提供明确的 `instructions`。
|
||||
|
||||
### `capabilities`
|
||||
|
||||
能力会将沙盒原生行为附加到 `SandboxAgent`。它们可以在运行开始前调整工作区、追加沙盒专用指令、公开绑定到实时沙盒会话的工具,以及调整该智能体的模型行为或输入处理。
|
||||
|
||||
内置能力包括:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 能力 | 添加时机 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `Shell` | 智能体需要 shell 访问。 | 添加 `exec_command`;当沙盒客户端支持 PTY 交互时,还会添加 `write_stdin`。 |
|
||||
| `Filesystem` | 智能体需要编辑文件或检查本地图像。 | 添加 `apply_patch` 和 `view_image`;补丁路径相对于工作区根目录。 |
|
||||
| `Skills` | 希望在沙盒中发现并具现化技能。 | 应优先使用此能力,而不是手动挂载 `.agents` 或 `.agents/skills`;`Skills` 会为您索引技能并将其具现化到沙盒中。 |
|
||||
| `Memory` | 后续运行应读取或生成记忆产物。 | 需要 `Shell`;实时更新还需要 `Filesystem`。 |
|
||||
| `Compaction` | 长时间运行的流程需要在压缩项后裁剪上下文。 | 调整模型采样和输入处理。 |
|
||||
|
||||
</div>
|
||||
|
||||
默认情况下,`SandboxAgent.capabilities` 使用 `Capabilities.default()`,其中包括 `Filesystem()`、`Shell()` 和 `Compaction()`。如果传入 `capabilities=[...]`,该列表会替换默认值,因此请将仍需使用的默认能力包含在内。
|
||||
|
||||
对于技能,请根据所需的具现化方式选择来源:
|
||||
|
||||
- `Skills(lazy_from=LocalDirLazySkillSource(...))` 是较大本地技能目录的良好默认选择,因为模型可以先发现索引,然后仅加载所需内容。
|
||||
- `LocalDirLazySkillSource(source=LocalDir(src=...))` 从 SDK 进程运行所在的文件系统读取内容。请传入原始主机侧技能目录,而不是仅存在于沙盒镜像或工作区内的路径。
|
||||
- `Skills(from_=LocalDir(src=...))` 更适合希望预先暂存的小型本地技能包。
|
||||
- 当技能本身应来自仓库时,`Skills(from_=GitRepo(repo=..., ref=...))` 更合适。
|
||||
|
||||
`LocalDir.src` 是 SDK 主机上的源路径。`skills_path` 是沙盒工作区内的相对目标路径,在调用 `load_skill` 时,技能会被暂存到该位置。
|
||||
|
||||
如果技能已位于磁盘上的 `.agents/skills/<name>/SKILL.md` 等路径下,请将 `LocalDir(...)` 指向该源根目录,并仍使用 `Skills(...)` 将其公开。除非现有工作区契约依赖其他沙盒内布局,否则请保留默认的 `skills_path=".agents"`。
|
||||
|
||||
当内置能力能够满足需求时,应优先使用内置能力。仅当需要内置能力未涵盖的沙盒专用工具或指令接口时,才编写自定义能力。
|
||||
|
||||
## 概念
|
||||
|
||||
### 清单
|
||||
|
||||
[`Manifest`][agents.sandbox.manifest.Manifest] 描述全新沙盒会话的工作区。它可以设置工作区 `root`、声明文件和目录、复制本地文件、克隆 Git 仓库、附加远程存储挂载、设置环境变量、定义用户或组,以及授予对工作区外特定绝对路径的访问权限。
|
||||
|
||||
清单条目路径相对于工作区。它们不能是绝对路径,也不能使用 `..` 逃逸工作区,因此工作区契约可以在本地、Docker 和托管客户端之间保持可移植性。
|
||||
|
||||
使用清单条目提供智能体开始工作前所需的材料:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 清单条目 | 用途 |
|
||||
| --- | --- |
|
||||
| `File`、`Dir` | 小型合成输入、辅助文件或输出目录。 |
|
||||
| `LocalFile`、`LocalDir` | 应具现化到沙盒中的主机文件或目录。 |
|
||||
| `GitRepo` | 应提取到工作区中的仓库。 |
|
||||
| `S3Mount`、`GCSMount`、`R2Mount`、`AzureBlobMount`、`BoxMount`、`S3FilesMount` 等挂载 | 应显示在沙盒内的外部存储。 |
|
||||
|
||||
</div>
|
||||
|
||||
`Dir` 会根据合成子项在沙盒工作区内创建目录,或创建一个输出位置;它不会从主机文件系统读取内容。如果需要将现有主机目录复制到沙盒工作区,请使用 `LocalDir`。
|
||||
|
||||
默认情况下,`LocalFile.src` 和 `LocalDir.src` 相对于 SDK 进程的工作目录进行解析。除非源路径已包含在 `extra_path_grants` 中,否则它必须位于该基础目录下。这样可以让本地源材料的具现化与沙盒清单的其他部分保持在同一个主机路径信任边界内。
|
||||
|
||||
挂载条目描述要公开哪些存储;挂载策略则描述沙盒后端如何附加这些存储。有关挂载选项和提供商支持,请参阅[沙盒客户端](clients.md#mounts-and-remote-storage)。
|
||||
|
||||
良好的清单设计通常意味着保持工作区契约精简,将较长的任务步骤放入 `repo/task.md` 等工作区文件,并在指令中使用工作区相对路径,例如 `repo/task.md` 或 `output/report.md`。如果智能体使用 `Filesystem` 能力的 `apply_patch` 工具编辑文件,请记住,补丁路径相对于沙盒工作区根目录,而不是 shell 的 `workdir`。
|
||||
|
||||
仅当智能体需要工作区外的具体绝对路径,或清单需要复制 SDK 进程工作目录外的可信本地源时,才使用 `extra_path_grants`。示例包括:用于临时工具输出的 `/tmp`、用作只读运行时的 `/opt/toolchain`,或应具现化到沙盒中的已生成技能目录。授权适用于本地源具现化、SDK 文件 API,以及后端可以执行文件系统策略的 shell 执行:
|
||||
|
||||
```python
|
||||
from agents.sandbox import Manifest, SandboxPathGrant
|
||||
|
||||
manifest = Manifest(
|
||||
extra_path_grants=(
|
||||
SandboxPathGrant(path="/tmp"),
|
||||
SandboxPathGrant(path="/opt/toolchain", read_only=True),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
应将包含 `extra_path_grants` 的清单视为可信配置。除非应用已经批准这些主机路径,否则不要从模型输出或其他不可信有效负载中加载授权。
|
||||
|
||||
快照和 `persist_workspace()` 仍然只包含工作区根目录。额外授权的路径属于运行时访问权限,而不是持久工作区状态。
|
||||
|
||||
### 权限
|
||||
|
||||
`Permissions` 控制清单条目的文件系统权限。它作用于沙盒具现化的文件,而不是模型权限、审批策略或 API 凭据。
|
||||
|
||||
默认情况下,清单条目的所有者拥有读取、写入和执行权限,组和其他用户拥有读取和执行权限。当暂存文件应为私有、只读或可执行时,请覆盖此设置:
|
||||
|
||||
```python
|
||||
from agents.sandbox import FileMode, Permissions
|
||||
from agents.sandbox.entries import File
|
||||
|
||||
private_notes = File(
|
||||
content=b"internal notes",
|
||||
permissions=Permissions(
|
||||
owner=FileMode.READ | FileMode.WRITE,
|
||||
group=FileMode.NONE,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
`Permissions` 会分别存储所有者、组和其他用户的权限位,以及该条目是否为目录。您可以直接构建它,使用 `Permissions.from_str(...)` 从模式字符串解析,或使用 `Permissions.from_mode(...)` 从操作系统模式派生。
|
||||
|
||||
用户是可以在沙盒中执行工作的身份。当希望某个身份存在于沙盒中时,请向清单添加 `User`;当 shell 命令、文件读取和补丁等面向模型的沙盒工具应以该用户身份运行时,请设置 `SandboxAgent.run_as`。如果 `run_as` 指向清单中尚不存在的用户,运行器会自动将其添加到有效清单中。
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import FileMode, Manifest, Permissions, SandboxAgent, SandboxRunConfig, User
|
||||
from agents.sandbox.entries import Dir, LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
analyst = User(name="analyst")
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Dataroom analyst",
|
||||
instructions="Review the files in `dataroom/` and write findings to `output/`.",
|
||||
default_manifest=Manifest(
|
||||
# Declare the sandbox user so manifest entries can grant access to it.
|
||||
users=[analyst],
|
||||
entries={
|
||||
"dataroom": LocalDir(
|
||||
src="./dataroom",
|
||||
# Let the analyst traverse and read the mounted dataroom, but not edit it.
|
||||
group=analyst,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.READ | FileMode.EXEC,
|
||||
group=FileMode.READ | FileMode.EXEC,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
),
|
||||
"output": Dir(
|
||||
# Give the analyst a writable scratch/output directory for artifacts.
|
||||
group=analyst,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.NONE,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
# Run model-facing sandbox actions as this user, so those permissions apply.
|
||||
run_as=analyst,
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Summarize the contracts and call out renewal dates.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
如果还需要文件级共享规则,请将用户与清单组及条目的 `group` 元数据结合使用。`run_as` 用户控制由谁执行沙盒原生操作;`Permissions` 则控制沙盒完成工作区具现化后,该用户可以读取、写入或执行哪些文件。
|
||||
|
||||
### SnapshotSpec
|
||||
|
||||
`SnapshotSpec` 指定全新沙盒会话应从何处恢复保存的工作区内容,以及应将内容持久化回何处。它是沙盒工作区的快照策略,而 `session_state` 是用于恢复特定沙盒后端的序列化连接状态。
|
||||
|
||||
对于本地持久快照,请使用 `LocalSnapshotSpec`;当应用提供远程快照客户端时,请使用 `RemoteSnapshotSpec`。当无法设置本地快照时,会使用空操作快照作为后备;当高级调用方不需要工作区快照持久化时,也可以显式使用空操作快照。
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshots")),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
当运行器创建全新沙盒会话时,沙盒客户端会为该会话构建一个快照实例。启动时,如果快照可恢复,沙盒会在运行继续前恢复保存的工作区内容。清理时,由运行器拥有的沙盒会话会归档工作区,并通过快照将其持久化回去。
|
||||
|
||||
如果省略 `snapshot`,运行时会在可行时尝试使用默认本地快照位置。如果无法设置,则回退到空操作快照。挂载路径和临时路径不会作为持久工作区内容复制到快照中。
|
||||
|
||||
### 沙盒生命周期
|
||||
|
||||
生命周期有两种模式:**SDK 所有**和**开发者所有**。
|
||||
|
||||
<div class="sandbox-lifecycle-diagram" markdown="1">
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App
|
||||
participant Runner
|
||||
participant Client
|
||||
participant Sandbox
|
||||
|
||||
App->>Runner: Runner.run(..., SandboxRunConfig(client=...))
|
||||
Runner->>Client: create or resume sandbox
|
||||
Client-->>Runner: sandbox session
|
||||
Runner->>Sandbox: start, run tools
|
||||
Runner->>Sandbox: stop and persist snapshot
|
||||
Runner->>Client: delete runner-owned resources
|
||||
|
||||
App->>Client: create(...)
|
||||
Client-->>App: sandbox session
|
||||
App->>Sandbox: async with sandbox
|
||||
App->>Runner: Runner.run(..., SandboxRunConfig(session=sandbox))
|
||||
Runner->>Sandbox: run tools
|
||||
App->>Sandbox: cleanup on context exit / aclose()
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
当沙盒只需在一次运行期间存在时,请使用 SDK 所有的生命周期。传入 `client`、可选的 `manifest`、可选的 `snapshot` 和客户端 `options`;运行器会创建或恢复沙盒、启动沙盒、运行智能体、持久化由快照支持的工作区状态、关闭沙盒,并让客户端清理由运行器拥有的资源。
|
||||
|
||||
```python
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Inspect the workspace and summarize what changed.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当您希望提前创建沙盒、在多次运行间复用同一个实时沙盒、在运行后检查文件、通过自己创建的沙盒进行流式传输,或精确决定清理时机时,请使用开发者所有的生命周期。传入 `session=...` 会让运行器使用该实时沙盒,但不会代您关闭它。
|
||||
|
||||
```python
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
run_config = RunConfig(sandbox=SandboxRunConfig(session=sandbox))
|
||||
await Runner.run(agent, "Analyze the files.", run_config=run_config)
|
||||
await Runner.run(agent, "Write the final report.", run_config=run_config)
|
||||
```
|
||||
|
||||
通常应使用上下文管理器:它在进入时启动沙盒,并在退出时运行会话清理生命周期。如果应用无法使用上下文管理器,请直接调用生命周期方法:
|
||||
|
||||
```python
|
||||
sandbox = await client.create(
|
||||
manifest=agent.default_manifest,
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshots")),
|
||||
)
|
||||
try:
|
||||
await sandbox.start()
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Analyze the files.",
|
||||
run_config=RunConfig(sandbox=SandboxRunConfig(session=sandbox)),
|
||||
)
|
||||
# Persist a checkpoint of the live workspace before doing more work.
|
||||
# `aclose()` also calls `stop()`, so this is only needed for an explicit mid-lifecycle save.
|
||||
await sandbox.stop()
|
||||
finally:
|
||||
await sandbox.aclose()
|
||||
```
|
||||
|
||||
`stop()` 只会持久化由快照支持的工作区内容;它不会销毁沙盒。`aclose()` 是完整的会话清理路径:它会运行停止前钩子、调用 `stop()`、关闭沙盒资源并关闭会话范围的依赖项。
|
||||
|
||||
## `SandboxRunConfig` 选项
|
||||
|
||||
[`SandboxRunConfig`][agents.run_config.SandboxRunConfig] 保存每次运行的选项,用于决定沙盒会话的来源,以及应如何初始化全新会话。
|
||||
|
||||
### 沙盒来源
|
||||
|
||||
以下选项决定运行器应复用、恢复还是创建沙盒会话:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 选项 | 使用时机 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `client` | 希望运行器代您创建、恢复和清理沙盒会话。 | 除非提供实时沙盒 `session`,否则为必需项。 |
|
||||
| `session` | 已经自行创建了实时沙盒会话。 | 调用方拥有生命周期;运行器复用该实时沙盒会话。 |
|
||||
| `session_state` | 拥有序列化的沙盒会话状态,但没有实时沙盒会话对象。 | 需要 `client`;运行器将根据该显式状态,以拥有会话的方式进行恢复。 |
|
||||
|
||||
</div>
|
||||
|
||||
实际使用中,运行器会按以下顺序解析沙盒会话:
|
||||
|
||||
1. 如果注入 `run_config.sandbox.session`,则直接复用该实时沙盒会话。
|
||||
2. 否则,如果此次运行正在从 `RunState` 恢复,则恢复其中存储的沙盒会话状态。
|
||||
3. 否则,如果传入 `run_config.sandbox.session_state`,运行器将根据该显式序列化的沙盒会话状态进行恢复。
|
||||
4. 否则,运行器会创建全新的沙盒会话。对于该全新会话,如果提供了 `run_config.sandbox.manifest`,则使用它;否则使用 `agent.default_manifest`。
|
||||
|
||||
### 全新会话输入
|
||||
|
||||
以下选项仅在运行器创建全新沙盒会话时有效:
|
||||
|
||||
<div class="sandbox-nowrap-first-column-table" markdown="1">
|
||||
|
||||
| 选项 | 使用时机 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `manifest` | 希望一次性覆盖全新会话的工作区。 | 省略时回退到 `agent.default_manifest`。 |
|
||||
| `snapshot` | 全新沙盒会话应由快照初始化。 | 适用于类似恢复的流程或远程快照客户端。 |
|
||||
| `options` | 沙盒客户端需要创建时选项。 | 常用于 Docker 镜像、Modal 应用名称、E2B 模板、超时和类似的客户端专用设置。 |
|
||||
|
||||
</div>
|
||||
|
||||
### 具现化控制
|
||||
|
||||
`concurrency_limits` 控制可并行运行的沙盒具现化工作量。当大型清单或本地目录复制需要更严格的资源控制时,请使用 `SandboxConcurrencyLimits(manifest_entries=..., local_dir_files=...)`。将任一值设置为 `None` 可禁用对应限制。
|
||||
|
||||
`archive_limits` 控制 SDK 侧对归档提取的资源检查。设置 `archive_limits=SandboxArchiveLimits()` 可启用 SDK 默认阈值;当归档需要更严格的资源控制时,也可以传入 `SandboxArchiveLimits(max_input_bytes=..., max_extracted_bytes=..., max_members=...)` 等显式值。保留 `archive_limits=None` 可维持默认行为,即不设置 SDK 归档资源限制;也可以将单个字段设置为 `None`,仅禁用对应限制。
|
||||
|
||||
需要注意以下几点:
|
||||
|
||||
- 全新会话:`manifest=` 和 `snapshot=` 仅在运行器创建全新沙盒会话时生效。
|
||||
- 恢复与快照:`session_state=` 会重新连接到之前序列化的沙盒状态,而 `snapshot=` 会根据保存的工作区内容初始化新的沙盒会话。
|
||||
- 客户端专用选项:`options=` 取决于沙盒客户端;Docker 和许多托管客户端都需要该选项。
|
||||
- 注入的实时会话:如果传入正在运行的沙盒 `session`,由能力驱动的清单更新可以添加兼容的非挂载条目。但它们不能更改 `manifest.root`、`manifest.environment`、`manifest.users` 或 `manifest.groups`;不能移除现有条目;不能替换条目类型;也不能添加或更改挂载条目。
|
||||
- 运行器 API:`SandboxAgent` 仍使用常规的 `Runner.run()`、`Runner.run_sync()` 和 `Runner.run_streamed()` API 执行。
|
||||
|
||||
## 完整示例:编码任务
|
||||
|
||||
以下编码风格示例是一个良好的默认起点:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agents import ModelSettings, Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.capabilities import (
|
||||
Capabilities,
|
||||
LocalDirLazySkillSource,
|
||||
Skills,
|
||||
)
|
||||
from agents.sandbox.entries import LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||
HOST_REPO_DIR = EXAMPLE_DIR / "repo"
|
||||
HOST_SKILLS_DIR = EXAMPLE_DIR / "skills"
|
||||
TARGET_TEST_CMD = "sh tests/test_credit_note.sh"
|
||||
|
||||
|
||||
def build_agent(model: str) -> SandboxAgent[None]:
|
||||
return SandboxAgent(
|
||||
name="Sandbox engineer",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Inspect the repo, make the smallest correct change, run the most relevant checks, "
|
||||
"and summarize the file changes and risks. "
|
||||
"Read `repo/task.md` before editing files. Stay grounded in the repository, preserve "
|
||||
"existing behavior, and mention the exact verification command you ran. "
|
||||
"Use the `$credit-note-fixer` skill before editing files. If the repo lives under "
|
||||
"`repo/`, remember that `apply_patch` paths stay relative to the sandbox workspace "
|
||||
"root, so edits still target `repo/...`."
|
||||
),
|
||||
# Put repos and task files in the manifest.
|
||||
default_manifest=Manifest(
|
||||
entries={
|
||||
"repo": LocalDir(src=HOST_REPO_DIR),
|
||||
}
|
||||
),
|
||||
capabilities=Capabilities.default() + [
|
||||
Skills(
|
||||
lazy_from=LocalDirLazySkillSource(
|
||||
# This is a host path read by the SDK process.
|
||||
# Requested skills are copied into `skills_path` in the sandbox.
|
||||
source=LocalDir(src=HOST_SKILLS_DIR),
|
||||
)
|
||||
),
|
||||
],
|
||||
model_settings=ModelSettings(tool_choice="required"),
|
||||
)
|
||||
|
||||
|
||||
async def main(model: str, prompt: str) -> None:
|
||||
result = await Runner.run(
|
||||
build_agent(model),
|
||||
prompt,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
workflow_name="Sandbox coding example",
|
||||
),
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(
|
||||
main(
|
||||
model="gpt-5.6-sol",
|
||||
prompt=(
|
||||
"Open `repo/task.md`, use the `$credit-note-fixer` skill, fix the bug, "
|
||||
f"run `{TARGET_TEST_CMD}`, and summarize the change."
|
||||
),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
请参阅 [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)。它使用一个基于 shell 的微型仓库,因此可以在 Unix 本地运行中以确定性方式验证该示例。您的实际任务仓库当然可以使用 Python、JavaScript 或任何其他语言。
|
||||
|
||||
## 常见模式
|
||||
|
||||
请从上面的完整示例开始。很多情况下,您可以保持同一个 `SandboxAgent` 不变,只更改沙盒客户端、沙盒会话来源或工作区来源。
|
||||
|
||||
### 沙盒客户端切换
|
||||
|
||||
保持智能体定义不变,仅更改运行配置。当需要容器隔离或镜像一致性时,请使用 Docker;当需要由提供商管理执行时,请使用托管提供商。有关代码示例和提供商选项,请参阅[沙盒客户端](clients.md)。
|
||||
|
||||
### 工作区覆盖
|
||||
|
||||
保持智能体定义不变,仅替换全新会话清单:
|
||||
|
||||
```python
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxRunConfig
|
||||
from agents.sandbox.entries import GitRepo
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
manifest=Manifest(
|
||||
entries={
|
||||
"repo": GitRepo(repo="openai/openai-agents-python", ref="main"),
|
||||
}
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当同一个智能体角色需要针对不同仓库、资料包或任务包运行,而无需重新构建智能体时,请使用此模式。上面经过验证的编码示例使用 `default_manifest` 而非一次性覆盖,但展示了相同模式。
|
||||
|
||||
### 沙盒会话注入
|
||||
|
||||
当需要显式生命周期控制、运行后检查或输出复制时,请注入实时沙盒会话:
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
client = UnixLocalSandboxClient()
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
prompt,
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当希望在运行后检查工作区,或通过已启动的沙盒会话进行流式传输时,请使用此模式。请参阅 [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py) 和 [examples/sandbox/docker/docker_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docker/docker_runner.py)。
|
||||
|
||||
### 会话状态恢复
|
||||
|
||||
如果已在 `RunState` 外部序列化沙盒状态,可以让运行器根据该状态重新连接:
|
||||
|
||||
```python
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
|
||||
serialized = load_saved_payload()
|
||||
restored_state = client.deserialize_session_state(serialized)
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=client,
|
||||
session_state=restored_state,
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当沙盒状态存储在您自己的存储系统或作业系统中,并希望 `Runner` 直接从中恢复时,请使用此模式。有关序列化和反序列化流程,请参阅 [examples/sandbox/extensions/blaxel_runner.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/extensions/blaxel_runner.py)。
|
||||
|
||||
### 快照初始化
|
||||
|
||||
使用保存的文件和产物初始化新沙盒:
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=UnixLocalSandboxClient(),
|
||||
snapshot=LocalSnapshotSpec(base_path=Path("/tmp/my-sandbox-snapshot")),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当全新运行应从保存的工作区内容开始,而不是仅从 `agent.default_manifest` 开始时,请使用此模式。有关本地快照流程,请参阅 [examples/sandbox/memory.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory.py);有关远程快照客户端,请参阅 [examples/sandbox/sandbox_agent_with_remote_snapshot.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agent_with_remote_snapshot.py)。
|
||||
|
||||
### Git 技能加载
|
||||
|
||||
将本地技能源替换为由仓库支持的技能源:
|
||||
|
||||
```python
|
||||
from agents.sandbox.capabilities import Capabilities, Skills
|
||||
from agents.sandbox.entries import GitRepo
|
||||
|
||||
capabilities = Capabilities.default() + [
|
||||
Skills(from_=GitRepo(repo="sdcoffey/tax-prep-skills", ref="main")),
|
||||
]
|
||||
```
|
||||
|
||||
当技能包有自己的发布周期,或应在多个沙盒间共享时,请使用此模式。请参阅 [examples/sandbox/tax_prep.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/tax_prep.py)。
|
||||
|
||||
### 工具公开
|
||||
|
||||
工具智能体既可以获得自己的沙盒边界,也可以复用父级运行中的实时沙盒。复用适合快速、只读的探索智能体:它可以检查父智能体正在使用的确切工作区,而无需承担创建、填充或快照另一个沙盒的开销。
|
||||
|
||||
```python
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import FileMode, Manifest, Permissions, SandboxAgent, SandboxRunConfig, User
|
||||
from agents.sandbox.entries import Dir, File
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
coordinator = User(name="coordinator")
|
||||
explorer = User(name="explorer")
|
||||
|
||||
manifest = Manifest(
|
||||
users=[coordinator, explorer],
|
||||
entries={
|
||||
"pricing_packet": Dir(
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.READ | FileMode.EXEC,
|
||||
directory=True,
|
||||
),
|
||||
children={
|
||||
"pricing.md": File(
|
||||
content=b"Pricing packet contents...",
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.READ,
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
"work": Dir(
|
||||
group=coordinator,
|
||||
permissions=Permissions(
|
||||
owner=FileMode.ALL,
|
||||
group=FileMode.ALL,
|
||||
other=FileMode.NONE,
|
||||
directory=True,
|
||||
),
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
pricing_explorer = SandboxAgent(
|
||||
name="Pricing Explorer",
|
||||
instructions="Read `pricing_packet/` and summarize commercial risk. Do not edit files.",
|
||||
run_as=explorer,
|
||||
)
|
||||
|
||||
client = UnixLocalSandboxClient()
|
||||
sandbox = await client.create(manifest=manifest)
|
||||
|
||||
async with sandbox:
|
||||
shared_run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
)
|
||||
|
||||
orchestrator = SandboxAgent(
|
||||
name="Revenue Operations Coordinator",
|
||||
instructions="Coordinate the review and write final notes to `work/`.",
|
||||
run_as=coordinator,
|
||||
tools=[
|
||||
pricing_explorer.as_tool(
|
||||
tool_name="review_pricing_packet",
|
||||
tool_description="Inspect the pricing packet and summarize commercial risk.",
|
||||
run_config=shared_run_config,
|
||||
max_turns=2,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
orchestrator,
|
||||
"Review the pricing packet, then write final notes to `work/summary.md`.",
|
||||
run_config=shared_run_config,
|
||||
)
|
||||
```
|
||||
|
||||
此处,父智能体以 `coordinator` 身份运行,探索工具智能体则在同一个实时沙盒会话中以 `explorer` 身份运行。`pricing_packet/` 条目允许 `other` 用户读取,因此探索智能体可以快速检查这些条目,但没有写入权限位。`work/` 目录仅对协调智能体的用户和组可用,因此父智能体可以写入最终产物,而探索智能体保持只读状态。
|
||||
|
||||
当工具智能体需要真正隔离时,请为其提供独立的沙盒 `RunConfig`:
|
||||
|
||||
```python
|
||||
from docker import from_env as docker_from_env
|
||||
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.sandboxes.docker import DockerSandboxClient, DockerSandboxClientOptions
|
||||
|
||||
rollout_agent = SandboxAgent(
|
||||
name="Rollout Reviewer",
|
||||
instructions="Inspect the rollout packet and summarize implementation risk.",
|
||||
)
|
||||
|
||||
rollout_agent.as_tool(
|
||||
tool_name="review_rollout_risk",
|
||||
tool_description="Inspect the rollout packet and summarize implementation risk.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(
|
||||
client=DockerSandboxClient(docker_from_env()),
|
||||
options=DockerSandboxClientOptions(image="python:3.14-slim"),
|
||||
),
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
当工具智能体应自由修改内容、运行不可信命令或使用不同的后端或镜像时,请使用独立沙盒。请参阅 [examples/sandbox/sandbox_agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agents_as_tools.py)。
|
||||
|
||||
### 本地工具与 MCP 组合
|
||||
|
||||
在保留沙盒工作区的同时,仍可在同一个智能体上使用普通工具:
|
||||
|
||||
```python
|
||||
from agents.sandbox import SandboxAgent
|
||||
from agents.sandbox.capabilities import Shell
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Workspace reviewer",
|
||||
instructions="Inspect the workspace and call host tools when needed.",
|
||||
tools=[get_discount_approval_path],
|
||||
mcp_servers=[server],
|
||||
capabilities=[Shell()],
|
||||
)
|
||||
```
|
||||
|
||||
当工作区检查只是智能体工作的一部分时,请使用此模式。请参阅 [examples/sandbox/sandbox_agent_with_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agent_with_tools.py)。
|
||||
|
||||
## 记忆
|
||||
|
||||
当未来的沙盒智能体运行应从之前的运行中学习时,请使用 `Memory` 能力。记忆不同于 SDK 的对话式 `Session` 记忆:它会将经验提炼为沙盒工作区中的文件,后续运行便可读取这些文件。
|
||||
|
||||
有关设置、读取和生成行为、多轮对话及布局隔离,请参阅[智能体记忆](memory.md)。
|
||||
|
||||
## 组合模式
|
||||
|
||||
明确单智能体模式后,下一个设计问题是沙盒边界在更大系统中应处于什么位置。
|
||||
|
||||
沙盒智能体仍可与 SDK 的其他部分组合:
|
||||
|
||||
- [任务转移](../handoffs.md):将文档密集型工作从非沙盒接收智能体转移给沙盒审查智能体。
|
||||
- [Agents as tools](../tools.md#agents-as-tools):将多个沙盒智能体公开为工具,通常通过在每次 `Agent.as_tool(...)` 调用中传入 `run_config=RunConfig(sandbox=SandboxRunConfig(...))`,使每个工具拥有自己的沙盒边界。
|
||||
- [MCP](../mcp.md) 和普通工具调用:沙盒能力可以与 `mcp_servers` 和普通 Python 工具共存。
|
||||
- [运行智能体](../running_agents.md):沙盒运行仍使用常规 `Runner` API。
|
||||
|
||||
以下两种模式尤其常见:
|
||||
|
||||
- 非沙盒智能体只在工作流中需要工作区隔离的部分,将任务转移给沙盒智能体
|
||||
- 编排智能体将多个沙盒智能体公开为工具,通常为每次 `Agent.as_tool(...)` 调用分别提供沙盒 `RunConfig`,使每个工具拥有独立的隔离工作区
|
||||
|
||||
### 轮次与沙盒运行
|
||||
|
||||
分别说明任务转移和智能体工具调用会更清晰。
|
||||
|
||||
使用任务转移时,仍然只有一个顶层运行和一个顶层轮次循环。活动智能体会发生变化,但运行不会变为嵌套运行。如果非沙盒接收智能体将任务转移给沙盒审查智能体,则同一次运行中的下一次模型调用会为沙盒智能体进行准备,并由该沙盒智能体执行下一轮。换言之,任务转移会改变同一次运行中下一轮的负责智能体。请参阅 [examples/sandbox/handoffs.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/handoffs.py)。
|
||||
|
||||
使用 `Agent.as_tool(...)` 时,关系有所不同。外层编排智能体使用外层运行的一轮来决定调用工具,而该工具调用会为沙盒智能体启动嵌套运行。嵌套运行拥有自己的轮次循环、`max_turns`、审批,通常也拥有自己的沙盒 `RunConfig`。它可能在一个嵌套轮次内完成,也可能需要多个轮次。从外层编排智能体的角度看,所有这些工作仍位于一次工具调用之后,因此嵌套轮次不会增加外层运行的轮次计数器。请参阅 [examples/sandbox/sandbox_agents_as_tools.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/sandbox_agents_as_tools.py)。
|
||||
|
||||
审批行为遵循相同的职责划分:
|
||||
|
||||
- 使用任务转移时,审批仍位于同一个顶层运行中,因为沙盒智能体现在是该运行中的活动智能体
|
||||
- 使用 `Agent.as_tool(...)` 时,沙盒工具智能体内部触发的审批仍会显示在外层运行中,但它们来自保存的嵌套运行状态,并在外层运行恢复时恢复嵌套沙盒运行
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [快速入门](../sandbox_agents.md):运行第一个沙盒智能体。
|
||||
- [沙盒客户端](clients.md):选择本地、Docker、托管和挂载选项。
|
||||
- [智能体记忆](memory.md):保留并复用之前沙盒运行中的经验。
|
||||
- [examples/sandbox/](https://github.com/openai/openai-agents-python/tree/main/examples/sandbox):可运行的本地、编码、记忆、任务转移和智能体组合模式。
|
||||
@@ -0,0 +1,189 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 智能体记忆
|
||||
|
||||
记忆让未来的沙盒智能体运行能够从先前运行中学习。它与 SDK 的对话式 [`Session`](../sessions/index.md) 记忆分开,后者用于存储消息历史。记忆会将先前运行中的经验提炼为沙盒工作区中的文件。
|
||||
|
||||
!!! warning "Beta 功能"
|
||||
|
||||
沙盒智能体处于 Beta 阶段。在正式可用之前,API、默认值和支持能力的细节可能会发生变化,并且随着时间推移会提供更高级的功能。
|
||||
|
||||
记忆可以为未来运行降低三类成本:
|
||||
|
||||
1. 智能体成本:如果智能体花了很长时间才完成某个工作流,下一次运行应该需要更少探索。这可以减少 token 使用量和完成时间。
|
||||
2. 用户成本:如果用户纠正了智能体,或表达了偏好,未来运行可以记住这些反馈。这可以减少人工干预。
|
||||
3. 上下文成本:如果智能体之前完成过一项任务,而用户想在该任务基础上继续推进,用户就不需要找到之前的线程或重新输入全部上下文。这会让任务描述更短。
|
||||
|
||||
请参阅 [examples/sandbox/memory.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory.py),其中包含一个完整的两次运行代码示例:修复 bug、生成记忆、恢复快照,并在后续验证器运行中使用该记忆。请参阅 [examples/sandbox/memory_multi_agent_multiturn.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/memory_multi_agent_multiturn.py),了解一个多轮、多智能体示例,其中包含独立的记忆布局。
|
||||
|
||||
## 记忆启用
|
||||
|
||||
将 `Memory()` 作为一项能力添加到沙盒智能体。
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
|
||||
from agents.sandbox import LocalSnapshotSpec, SandboxAgent
|
||||
from agents.sandbox.capabilities import Filesystem, Memory, Shell
|
||||
|
||||
agent = SandboxAgent(
|
||||
name="Memory-enabled reviewer",
|
||||
instructions="Inspect the workspace and preserve useful lessons for follow-up runs.",
|
||||
capabilities=[Memory(), Filesystem(), Shell()],
|
||||
)
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="sandbox-memory-example-") as snapshot_dir:
|
||||
sandbox = await client.create(
|
||||
manifest=manifest,
|
||||
snapshot=LocalSnapshotSpec(base_path=Path(snapshot_dir)),
|
||||
)
|
||||
```
|
||||
|
||||
如果启用了读取,`Memory()` 需要 `Shell()`,这样当注入的摘要不足够时,智能体就可以读取和搜索记忆文件。当启用实时记忆更新时(默认启用),它还需要 `Filesystem()`,这样如果智能体发现记忆已过时,或用户要求它更新记忆,智能体就可以更新 `memories/MEMORY.md`。
|
||||
|
||||
默认情况下,记忆产物存储在沙盒工作区的 `memories/` 下。要在后续运行中复用它们,请通过保持同一个实时沙盒会话,或从持久化的会话状态或快照恢复,来保留并复用整个已配置的记忆目录;全新的空沙盒会从空记忆开始。
|
||||
|
||||
`Memory()` 同时启用读取记忆和生成记忆。对于应该读取记忆但不应生成新记忆的智能体,请使用 `Memory(generate=None)`:例如,内部智能体、子智能体、检查器,或运行不会增加太多信号的一次性工具智能体。当运行应该为之后生成记忆,但用户不希望该运行受现有记忆影响时,请使用 `Memory(read=None)`。
|
||||
|
||||
## 记忆读取
|
||||
|
||||
记忆读取采用渐进式披露。在运行开始时,SDK 会将一份小型摘要(`memory_summary.md`)注入到智能体的开发者提示词中,其中包含通常有用的提示、用户偏好和可用记忆。这会为智能体提供足够的上下文,以判断先前工作是否可能相关。
|
||||
|
||||
当先前工作看起来相关时,智能体会在已配置的记忆索引(`memories_dir` 下的 `MEMORY.md`)中搜索当前任务的关键词。只有当任务需要更多细节时,它才会打开已配置的 `rollout_summaries/` 目录下对应的先前 rollout 摘要。
|
||||
|
||||
记忆可能会过时。智能体会被指示仅将记忆作为指导,并信任当前环境。默认情况下,记忆读取启用 `live_update`,因此如果智能体发现记忆已过时,它可以在同一次运行中更新已配置的 `MEMORY.md`。当智能体应读取记忆但不应在运行期间修改记忆时,请禁用实时更新,例如该运行对延迟敏感时。
|
||||
|
||||
## 记忆生成
|
||||
|
||||
运行结束后,沙盒运行时会将该运行片段追加到一个对话文件中。累积的对话文件会在沙盒会话关闭时被处理。
|
||||
|
||||
记忆生成分为两个阶段:
|
||||
|
||||
1. 阶段 1:对话提取。生成记忆的模型会处理一个累积的对话文件,并生成对话摘要。system、developer 和 reasoning 内容会被省略。如果对话过长,它会被截断以适配上下文窗口,同时保留开头和结尾。它还会生成原始记忆摘录:来自对话的紧凑笔记,供阶段 2 进行整合。
|
||||
2. 阶段 2:布局整合。整合智能体会读取某个记忆布局的原始记忆,在需要更多证据时打开对话摘要,并将模式提取到 `MEMORY.md` 和 `memory_summary.md` 中。
|
||||
|
||||
默认工作区布局如下:
|
||||
|
||||
```text
|
||||
workspace/
|
||||
├── sessions/
|
||||
│ └── <rollout-id>.jsonl
|
||||
└── memories/
|
||||
├── memory_summary.md
|
||||
├── MEMORY.md
|
||||
├── raw_memories.md (intermediate)
|
||||
├── phase_two_selection.json (intermediate)
|
||||
├── raw_memories/ (intermediate)
|
||||
│ └── <rollout-id>.md
|
||||
├── rollout_summaries/
|
||||
│ └── <rollout-id>_<slug>.md
|
||||
└── skills/
|
||||
```
|
||||
|
||||
你可以使用 `MemoryGenerateConfig` 配置记忆生成:
|
||||
|
||||
```python
|
||||
from agents.sandbox import MemoryGenerateConfig
|
||||
from agents.sandbox.capabilities import Memory
|
||||
|
||||
memory = Memory(
|
||||
generate=MemoryGenerateConfig(
|
||||
max_raw_memories_for_consolidation=128,
|
||||
extra_prompt="Pay extra attention to what made the customer more satisfied or annoyed",
|
||||
),
|
||||
)
|
||||
```
|
||||
|
||||
使用 `extra_prompt` 告诉记忆生成器哪些信号对你的用例最重要,例如 GTM 智能体所需的客户和公司详细信息。
|
||||
|
||||
如果最近的原始记忆超过 `max_raw_memories_for_consolidation`(默认为 256),阶段 2 只保留来自最新对话的记忆,并移除较旧的记忆。新近程度基于对话上次更新的时间。这种遗忘机制有助于让记忆反映最新环境。
|
||||
|
||||
## 多轮对话
|
||||
|
||||
对于多轮沙盒聊天,请将常规 SDK `Session` 与同一个实时沙盒会话一起使用:
|
||||
|
||||
```python
|
||||
from agents import Runner, SQLiteSession
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import SandboxRunConfig
|
||||
|
||||
conversation_session = SQLiteSession("gtm-q2-pipeline-review")
|
||||
sandbox = await client.create(manifest=agent.default_manifest)
|
||||
|
||||
async with sandbox:
|
||||
run_config = RunConfig(
|
||||
sandbox=SandboxRunConfig(session=sandbox),
|
||||
workflow_name="GTM memory example",
|
||||
)
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Analyze data/leads.csv and identify one promising GTM segment.",
|
||||
session=conversation_session,
|
||||
run_config=run_config,
|
||||
)
|
||||
await Runner.run(
|
||||
agent,
|
||||
"Using that analysis, write a short outreach hypothesis.",
|
||||
session=conversation_session,
|
||||
run_config=run_config,
|
||||
)
|
||||
```
|
||||
|
||||
两次运行都会追加到同一个记忆对话文件,因为它们传入了同一个 SDK 对话会话(`session=conversation_session`),因此共享同一个 `session.session_id`。这不同于沙盒(`sandbox`),后者标识实时工作区,不会被用作记忆对话 ID。阶段 1 会在沙盒会话关闭时看到累积的对话,因此它可以从整个交流中提取记忆,而不是从两个孤立的轮次中提取。
|
||||
|
||||
如果你希望多个 `Runner.run(...)` 调用成为同一个记忆对话,请在这些调用之间传入一个稳定标识符。当记忆将某次运行与一个对话关联时,它会按以下顺序解析:
|
||||
|
||||
1. `conversation_id`,当你将其传给 `Runner.run(...)` 时
|
||||
2. `session.session_id`,当你传入 SDK `Session`(例如 `SQLiteSession`)时
|
||||
3. `RunConfig.group_id`,当上述两者都不存在时
|
||||
4. 生成的每次运行 ID,当不存在稳定标识符时
|
||||
|
||||
## 用于隔离不同智能体记忆的不同布局
|
||||
|
||||
记忆隔离基于 `MemoryLayoutConfig`,而不是智能体名称。具有相同布局和相同记忆对话 ID 的智能体会共享一个记忆对话和一份整合后的记忆。具有不同布局的智能体会保留独立的 rollout 文件、原始记忆、`MEMORY.md` 和 `memory_summary.md`,即使它们共享同一个沙盒工作区也是如此。
|
||||
|
||||
当多个智能体共享一个沙盒但不应共享记忆时,请使用独立布局:
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
from agents.sandbox import MemoryLayoutConfig, SandboxAgent
|
||||
from agents.sandbox.capabilities import Filesystem, Memory, Shell
|
||||
|
||||
gtm_agent = SandboxAgent(
|
||||
name="GTM reviewer",
|
||||
instructions="Analyze GTM workspace data and write concise recommendations.",
|
||||
capabilities=[
|
||||
Memory(
|
||||
layout=MemoryLayoutConfig(
|
||||
memories_dir="memories/gtm",
|
||||
sessions_dir="sessions/gtm",
|
||||
)
|
||||
),
|
||||
Filesystem(),
|
||||
Shell(),
|
||||
],
|
||||
)
|
||||
|
||||
engineering_agent = SandboxAgent(
|
||||
name="Engineering reviewer",
|
||||
instructions="Inspect engineering workspaces and summarize fixes and risks.",
|
||||
capabilities=[
|
||||
Memory(
|
||||
layout=MemoryLayoutConfig(
|
||||
memories_dir="memories/engineering",
|
||||
sessions_dir="sessions/engineering",
|
||||
)
|
||||
),
|
||||
Filesystem(),
|
||||
Shell(),
|
||||
],
|
||||
)
|
||||
|
||||
gtm_session = SQLiteSession("gtm-q2-pipeline-review")
|
||||
engineering_session = SQLiteSession("eng-invoice-test-fix")
|
||||
```
|
||||
|
||||
这可以防止 GTM 分析被整合到工程缺陷修复记忆中,反之亦然。
|
||||
@@ -0,0 +1,117 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 快速入门
|
||||
|
||||
!!! warning "测试版功能"
|
||||
|
||||
沙箱智能体目前处于测试阶段。在正式发布之前,API 细节、默认值和支持的功能可能会发生变化,并且未来将逐步提供更高级的功能。
|
||||
|
||||
现代智能体能够在文件系统中操作真实文件时,往往能发挥最佳效果。Agents SDK 中的**沙箱智能体**为模型提供持久化工作区,使其能够检索大型文档集、编辑文件、运行命令、生成产物,并从已保存的沙箱状态继续工作。
|
||||
|
||||
SDK 为你提供这一执行框架,无需自行整合文件暂存、文件系统工具、shell 访问、沙箱生命周期、快照以及特定于提供商的适配代码。你可以继续使用常规的 `Agent` 和 `Runner` 流程,然后为工作区添加 `Manifest`,为沙箱原生工具添加 capabilities,并使用 `SandboxRunConfig` 指定工作运行的位置。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- Python 3.10 或更高版本
|
||||
- 基本熟悉 OpenAI Agents SDK
|
||||
- 沙箱客户端。对于本地开发,请从 `UnixLocalSandboxClient` 开始。
|
||||
|
||||
## 安装
|
||||
|
||||
如果尚未安装 SDK:
|
||||
|
||||
```bash
|
||||
pip install openai-agents
|
||||
```
|
||||
|
||||
对于由 Docker 支持的沙箱:
|
||||
|
||||
```bash
|
||||
pip install "openai-agents[docker]"
|
||||
```
|
||||
|
||||
## 本地沙箱智能体的创建
|
||||
|
||||
此代码示例将本地仓库存放到 `repo/` 下,延迟加载本地技能,并允许运行器为本次运行创建 Unix 本地沙箱会话。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from pathlib import Path
|
||||
|
||||
from agents import Runner
|
||||
from agents.run import RunConfig
|
||||
from agents.sandbox import Manifest, SandboxAgent, SandboxRunConfig
|
||||
from agents.sandbox.capabilities import Capabilities, LocalDirLazySkillSource, Skills
|
||||
from agents.sandbox.entries import LocalDir
|
||||
from agents.sandbox.sandboxes.unix_local import UnixLocalSandboxClient
|
||||
|
||||
EXAMPLE_DIR = Path(__file__).resolve().parent
|
||||
HOST_REPO_DIR = EXAMPLE_DIR / "repo"
|
||||
HOST_SKILLS_DIR = EXAMPLE_DIR / "skills"
|
||||
|
||||
|
||||
def build_agent(model: str) -> SandboxAgent[None]:
|
||||
return SandboxAgent(
|
||||
name="Sandbox engineer",
|
||||
model=model,
|
||||
instructions=(
|
||||
"Read `repo/task.md` before editing files. Stay grounded in the repository, preserve "
|
||||
"existing behavior, and mention the exact verification command you ran. "
|
||||
"If you edit files with apply_patch, paths are relative to the sandbox workspace root."
|
||||
),
|
||||
default_manifest=Manifest(
|
||||
entries={
|
||||
"repo": LocalDir(src=HOST_REPO_DIR),
|
||||
}
|
||||
),
|
||||
capabilities=Capabilities.default() + [
|
||||
Skills(
|
||||
lazy_from=LocalDirLazySkillSource(
|
||||
# This is a host path read by the SDK process.
|
||||
# Requested skills are copied into `skills_path` in the sandbox.
|
||||
source=LocalDir(src=HOST_SKILLS_DIR),
|
||||
)
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
result = await Runner.run(
|
||||
build_agent("gpt-5.6-sol"),
|
||||
"Open `repo/task.md`, fix the issue, run the targeted test, and summarize the change.",
|
||||
run_config=RunConfig(
|
||||
sandbox=SandboxRunConfig(client=UnixLocalSandboxClient()),
|
||||
workflow_name="Sandbox coding example",
|
||||
),
|
||||
)
|
||||
print(result.final_output)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
请参阅 [examples/sandbox/docs/coding_task.py](https://github.com/openai/openai-agents-python/blob/main/examples/sandbox/docs/coding_task.py)。它使用一个基于 shell 的小型仓库,因此可以在多次 Unix 本地运行中以确定性方式验证该代码示例。
|
||||
|
||||
## 关键选项
|
||||
|
||||
基本运行正常后,大多数人接下来会使用以下选项:
|
||||
|
||||
- `default_manifest`:全新沙箱会话所使用的文件、仓库、目录和挂载项
|
||||
- `instructions`:应适用于所有提示词的简短工作流规则
|
||||
- `base_instructions`:用于替换 SDK 沙箱提示词的高级应急选项
|
||||
- `capabilities`:沙箱原生工具,例如文件系统编辑/图像检查、shell、技能、记忆和压缩
|
||||
- `run_as`:面向模型的工具所使用的沙箱用户身份
|
||||
- `SandboxRunConfig.client`:沙箱后端
|
||||
- `SandboxRunConfig.session`、`session_state` 或 `snapshot`:后续运行如何重新连接到先前的工作
|
||||
|
||||
## 后续步骤
|
||||
|
||||
- [概念](sandbox/guide.md):了解清单、capabilities、权限、快照、运行配置和组合模式。
|
||||
- [沙箱客户端](sandbox/clients.md):选择 Unix 本地、Docker、托管提供商和挂载策略。
|
||||
- [智能体记忆](sandbox/memory.md):保留并复用以往沙箱运行中获得的经验。
|
||||
|
||||
如果 shell 访问只是偶尔使用的工具,请先从[工具指南](tools.md)中的托管 shell 开始。如果工作区隔离、沙箱客户端选择或沙箱会话恢复行为属于设计的一部分,请使用沙箱智能体。
|
||||
@@ -0,0 +1,460 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 会话
|
||||
|
||||
Agents SDK 提供内置的会话内存,可在多个智能体运行之间自动维护对话历史,无需在回合之间手动处理 `.to_input_list()`。
|
||||
|
||||
会话为特定会话存储对话历史,使智能体无需显式的手动内存管理即可保持上下文。这对于构建聊天应用或多轮对话尤为有用,你可以让智能体记住之前的交互。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance with a session ID
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
# First turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
|
||||
# Also works with synchronous runner
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"What's the population?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "Approximately 39 million"
|
||||
```
|
||||
|
||||
## 工作原理
|
||||
|
||||
当启用会话内存时:
|
||||
|
||||
1. **每次运行前**:运行器会自动检索该会话的对话历史,并将其预置到输入项之前。
|
||||
2. **每次运行后**:在运行期间生成的所有新条目(用户输入、助手响应、工具调用等)都会自动存储到会话中。
|
||||
3. **上下文保留**:使用相同会话的后续运行将包含完整对话历史,使智能体能够保持上下文。
|
||||
|
||||
这消除了在运行之间手动调用 `.to_input_list()` 并管理对话状态的需要。
|
||||
|
||||
## 内存操作
|
||||
|
||||
### 基础操作
|
||||
|
||||
会话支持多种用于管理对话历史的操作:
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Get all items in a session
|
||||
items = await session.get_items()
|
||||
|
||||
# Add new items to a session
|
||||
new_items = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"}
|
||||
]
|
||||
await session.add_items(new_items)
|
||||
|
||||
# Remove and return the most recent item
|
||||
last_item = await session.pop_item()
|
||||
print(last_item) # {"role": "assistant", "content": "Hi there!"}
|
||||
|
||||
# Clear all items from a session
|
||||
await session.clear_session()
|
||||
```
|
||||
|
||||
### 使用 pop_item 进行更正
|
||||
|
||||
当你想要撤销或修改对话中的最后一个条目时,`pop_item` 方法特别有用:
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("correction_example")
|
||||
|
||||
# Initial conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 2?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
|
||||
# User wants to correct their question
|
||||
assistant_item = await session.pop_item() # Remove agent's response
|
||||
user_item = await session.pop_item() # Remove user's question
|
||||
|
||||
# Ask a corrected question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 3?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
```
|
||||
|
||||
## 内存选项
|
||||
|
||||
### 无内存(默认)
|
||||
|
||||
```python
|
||||
# Default behavior - no session memory
|
||||
result = await Runner.run(agent, "Hello")
|
||||
```
|
||||
|
||||
### OpenAI Conversations API 内存
|
||||
|
||||
使用 [OpenAI Conversations API](https://platform.openai.com/docs/api-reference/conversations/create) 来持久化
|
||||
[conversation state](https://platform.openai.com/docs/guides/conversation-state?api-mode=responses#using-the-conversations-api),无需管理你自己的数据库。当你已经依赖由 OpenAI 托管的基础设施来存储对话历史时,这将很有帮助。
|
||||
|
||||
```python
|
||||
from agents import OpenAIConversationsSession
|
||||
|
||||
session = OpenAIConversationsSession()
|
||||
|
||||
# Optionally resume a previous conversation by passing a conversation ID
|
||||
# session = OpenAIConversationsSession(conversation_id="conv_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session,
|
||||
)
|
||||
```
|
||||
|
||||
### SQLite 内存
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
# In-memory database (lost when process ends)
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Persistent file-based database
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Use the session
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
### 多会话
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Different sessions maintain separate conversation histories
|
||||
session_1 = SQLiteSession("user_123", "conversations.db")
|
||||
session_2 = SQLiteSession("user_456", "conversations.db")
|
||||
|
||||
result1 = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session_1
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session_2
|
||||
)
|
||||
```
|
||||
|
||||
### 由 SQLAlchemy 驱动的会话
|
||||
|
||||
对于更高级的用例,你可以使用由 SQLAlchemy 驱动的会话后端。这样就可以使用任何 SQLAlchemy 支持的数据库(PostgreSQL、MySQL、SQLite 等)来进行会话存储。
|
||||
|
||||
**示例 1:使用 `from_url` 搭配内存型 SQLite**
|
||||
|
||||
这是最简单的入门方式,适合开发和测试。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True, # Auto-create tables for the demo
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**示例 2:使用现有的 SQLAlchemy 引擎**
|
||||
|
||||
在生产应用中,你很可能已经拥有一个 SQLAlchemy 的 `AsyncEngine` 实例。你可以将其直接传递给会话。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory.sqlalchemy_session import SQLAlchemySession
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
async def main():
|
||||
# In your application, you would use your existing engine
|
||||
engine = create_async_engine("sqlite+aiosqlite:///conversations.db")
|
||||
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession(
|
||||
"user-456",
|
||||
engine=engine,
|
||||
create_tables=True, # Auto-create tables for the demo
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 加密会话
|
||||
|
||||
对于需要对静态对话数据进行加密的应用,你可以使用 `EncryptedSession` 来包装任意会话后端,实现透明加密和基于 TTL 的自动过期。这需要 `encrypt` 可选依赖:`pip install openai-agents[encrypt]`。
|
||||
|
||||
`EncryptedSession` 使用基于每个会话的密钥派生(HKDF)的 Fernet 加密,并支持旧消息的自动过期。当条目超过 TTL 时,它们在检索期间会被静默跳过。
|
||||
|
||||
**示例:为 SQLAlchemy 会话数据加密**
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
# Create underlying session (works with any SessionABC implementation)
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
session_id="user-123",
|
||||
url="postgresql+asyncpg://app:secret@db.example.com/agents",
|
||||
create_tables=True,
|
||||
)
|
||||
|
||||
# Wrap with encryption and TTL-based expiration
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-encryption-key", # Use a secure key from your secrets management
|
||||
ttl=600, # 10 minutes - items older than this are silently skipped
|
||||
)
|
||||
|
||||
agent = Agent("Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
**关键特性:**
|
||||
|
||||
- **透明加密**:在存储前自动加密所有会话条目,并在检索时解密
|
||||
- **按会话派生密钥**:使用会话 ID 作为盐的 HKDF 来派生唯一加密密钥
|
||||
- **基于 TTL 的过期**:根据可配置的生存时间(默认:10 分钟)自动使旧消息过期
|
||||
- **灵活的密钥输入**:接受 Fernet 密钥或原始字符串作为加密密钥
|
||||
- **可包装任意会话**:适用于 SQLite、SQLAlchemy 或自定义会话实现
|
||||
|
||||
!!! warning "重要的安全注意事项"
|
||||
|
||||
- 安全存储你的加密密钥(如环境变量、密钥管理服务)
|
||||
- 过期令牌根据应用服务的系统时钟被拒绝——请确保所有服务均通过 NTP 同步时间,以避免因时钟漂移导致的误拒
|
||||
- 底层会话仍存储加密数据,因此你依然可以掌控你的数据库基础设施
|
||||
|
||||
|
||||
## 自定义内存实现
|
||||
|
||||
你可以通过创建遵循 [`Session`][agents.memory.session.Session] 协议的类来实现你自己的会话内存:
|
||||
|
||||
```python
|
||||
from agents.memory.session import SessionABC
|
||||
from agents.items import TResponseInputItem
|
||||
from typing import List
|
||||
|
||||
class MyCustomSession(SessionABC):
|
||||
"""Custom session implementation following the Session protocol."""
|
||||
|
||||
def __init__(self, session_id: str):
|
||||
self.session_id = session_id
|
||||
# Your initialization here
|
||||
|
||||
async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]:
|
||||
"""Retrieve conversation history for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def add_items(self, items: List[TResponseInputItem]) -> None:
|
||||
"""Store new items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def pop_item(self) -> TResponseInputItem | None:
|
||||
"""Remove and return the most recent item from this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def clear_session(self) -> None:
|
||||
"""Clear all items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
# Use your custom session
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=MyCustomSession("my_session")
|
||||
)
|
||||
```
|
||||
|
||||
## 会话管理
|
||||
|
||||
### 会话 ID 命名
|
||||
|
||||
使用有意义的会话 ID 来帮助组织对话:
|
||||
|
||||
- 基于用户:`"user_12345"`
|
||||
- 基于线程:`"thread_abc123"`
|
||||
- 基于上下文:`"support_ticket_456"`
|
||||
|
||||
### 内存持久化
|
||||
|
||||
- 临时会话使用内存型 SQLite(`SQLiteSession("session_id")`)
|
||||
- 持久化会话使用基于文件的 SQLite(`SQLiteSession("session_id", "path/to/db.sqlite")`)
|
||||
- 生产系统且已有数据库时,使用由 SQLAlchemy 驱动的会话(`SQLAlchemySession("session_id", engine=engine, create_tables=True)`),支持 SQLAlchemy 支持的数据库
|
||||
- 当你希望将历史存储在 OpenAI Conversations API 中时,使用 OpenAI 托管的存储(`OpenAIConversationsSession()`)
|
||||
- 使用加密会话(`EncryptedSession(session_id, underlying_session, encryption_key)`)为任意会话提供透明加密与基于 TTL 的过期
|
||||
- 针对其他生产系统(Redis、Django 等)考虑实现自定义会话后端,以满足更高级的用例
|
||||
|
||||
### 会话管理
|
||||
|
||||
```python
|
||||
# Clear a session when conversation should start fresh
|
||||
await session.clear_session()
|
||||
|
||||
# Different agents can share the same session
|
||||
support_agent = Agent(name="Support")
|
||||
billing_agent = Agent(name="Billing")
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Both agents will see the same conversation history
|
||||
result1 = await Runner.run(
|
||||
support_agent,
|
||||
"Help me with my account",
|
||||
session=session
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
billing_agent,
|
||||
"What are my charges?",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
以下是展示会话内存实际效果的完整示例:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
|
||||
async def main():
|
||||
# Create an agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance that will persist across runs
|
||||
session = SQLiteSession("conversation_123", "conversation_history.db")
|
||||
|
||||
print("=== Sessions Example ===")
|
||||
print("The agent will remember previous messages automatically.\n")
|
||||
|
||||
# First turn
|
||||
print("First turn:")
|
||||
print("User: What city is the Golden Gate Bridge in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Second turn - the agent will remember the previous conversation
|
||||
print("Second turn:")
|
||||
print("User: What state is it in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Third turn - continuing the conversation
|
||||
print("Third turn:")
|
||||
print("User: What's the population of that state?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's the population of that state?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
print("=== Conversation Complete ===")
|
||||
print("Notice how the agent remembered the context from previous turns!")
|
||||
print("Sessions automatically handles conversation history.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
详细的 API 文档请参阅:
|
||||
|
||||
- [`Session`][agents.memory.Session] - 协议接口
|
||||
- [`SQLiteSession`][agents.memory.SQLiteSession] - SQLite 实现
|
||||
- [`OpenAIConversationsSession`](ref/memory/openai_conversations_session.md) - OpenAI Conversations API 实现
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - 由 SQLAlchemy 驱动的实现
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - 具有 TTL 的加密会话封装器
|
||||
@@ -0,0 +1,307 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 高级 SQLite 会话
|
||||
|
||||
`AdvancedSQLiteSession` 是基础 `SQLiteSession` 的增强版本,提供高级对话管理能力,包括对话分支、详细的使用情况分析以及结构化对话查询。
|
||||
|
||||
## 功能
|
||||
|
||||
- **对话分支**:从任意用户消息创建替代对话路径
|
||||
- **使用情况追踪**:按轮次提供详细的 token 使用情况分析,并包含完整的 JSON 明细
|
||||
- **结构化查询**:按轮次获取对话、工具使用统计等
|
||||
- **分支管理**:独立的分支切换与管理
|
||||
- **消息结构元数据**:跟踪消息类型、工具使用情况和对话流程
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create an advanced session
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="conversation_123",
|
||||
db_path="conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# First conversation turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# IMPORTANT: Store usage data
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Continue conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
await session.store_run_usage(result)
|
||||
```
|
||||
|
||||
## 初始化
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Basic initialization
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="my_conversation",
|
||||
create_tables=True # Auto-create advanced tables
|
||||
)
|
||||
|
||||
# With persistent storage
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="user_123",
|
||||
db_path="path/to/conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# With custom logger
|
||||
import logging
|
||||
logger = logging.getLogger("my_app")
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="session_456",
|
||||
create_tables=True,
|
||||
logger=logger
|
||||
)
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
- `session_id` (str):对话会话的唯一标识符
|
||||
- `db_path` (str | Path):SQLite 数据库文件路径。默认为 `:memory:`,用于内存存储
|
||||
- `create_tables` (bool):是否自动创建高级表。默认为 `False`
|
||||
- `logger` (logging.Logger | None):用于会话的自定义日志记录器。默认为模块日志记录器
|
||||
|
||||
## 使用情况追踪
|
||||
|
||||
AdvancedSQLiteSession 通过按对话轮次存储 token 使用情况数据,提供详细的使用情况分析。**这完全依赖于在每次智能体运行后调用 `store_run_usage` 方法。**
|
||||
|
||||
### 使用情况数据存储
|
||||
|
||||
```python
|
||||
# After each agent run, store the usage data
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# This stores:
|
||||
# - Total tokens used
|
||||
# - Input/output token breakdown
|
||||
# - Request count
|
||||
# - Detailed JSON token information (if available)
|
||||
```
|
||||
|
||||
### 使用情况统计检索
|
||||
|
||||
```python
|
||||
# Get session-level usage (all branches)
|
||||
session_usage = await session.get_session_usage()
|
||||
if session_usage:
|
||||
print(f"Total requests: {session_usage['requests']}")
|
||||
print(f"Total tokens: {session_usage['total_tokens']}")
|
||||
print(f"Input tokens: {session_usage['input_tokens']}")
|
||||
print(f"Output tokens: {session_usage['output_tokens']}")
|
||||
print(f"Total turns: {session_usage['total_turns']}")
|
||||
|
||||
# Get usage for specific branch
|
||||
branch_usage = await session.get_session_usage(branch_id="main")
|
||||
|
||||
# Get usage by turn
|
||||
turn_usage = await session.get_turn_usage()
|
||||
for turn_data in turn_usage:
|
||||
print(f"Turn {turn_data['user_turn_number']}: {turn_data['total_tokens']} tokens")
|
||||
if turn_data['input_tokens_details']:
|
||||
print(f" Input details: {turn_data['input_tokens_details']}")
|
||||
if turn_data['output_tokens_details']:
|
||||
print(f" Output details: {turn_data['output_tokens_details']}")
|
||||
|
||||
# Get usage for specific turn
|
||||
turn_2_usage = await session.get_turn_usage(user_turn_number=2)
|
||||
```
|
||||
|
||||
## 对话分支
|
||||
|
||||
AdvancedSQLiteSession 的关键功能之一是能够从任意用户消息创建对话分支,从而让你探索替代的对话路径。
|
||||
|
||||
### 分支创建
|
||||
|
||||
```python
|
||||
# Get available turns for branching
|
||||
turns = await session.get_conversation_turns()
|
||||
for turn in turns:
|
||||
print(f"Turn {turn['turn']}: {turn['content']}")
|
||||
print(f"Can branch: {turn['can_branch']}")
|
||||
|
||||
# Create a branch from turn 2
|
||||
branch_id = await session.create_branch_from_turn(2)
|
||||
print(f"Created branch: {branch_id}")
|
||||
|
||||
# Create a branch with custom name
|
||||
branch_id = await session.create_branch_from_turn(
|
||||
2,
|
||||
branch_name="alternative_path"
|
||||
)
|
||||
|
||||
# Create branch by searching for content
|
||||
branch_id = await session.create_branch_from_content(
|
||||
"weather",
|
||||
branch_name="weather_focus"
|
||||
)
|
||||
```
|
||||
|
||||
### 分支管理
|
||||
|
||||
```python
|
||||
# List all branches
|
||||
branches = await session.list_branches()
|
||||
for branch in branches:
|
||||
current = " (current)" if branch["is_current"] else ""
|
||||
print(f"{branch['branch_id']}: {branch['user_turns']} turns, {branch['message_count']} messages{current}")
|
||||
|
||||
# Switch between branches
|
||||
await session.switch_to_branch("main")
|
||||
await session.switch_to_branch(branch_id)
|
||||
|
||||
# Delete a branch
|
||||
await session.delete_branch(branch_id, force=True) # force=True allows deleting current branch
|
||||
```
|
||||
|
||||
### 分支工作流示例
|
||||
|
||||
```python
|
||||
# Original conversation
|
||||
result = await Runner.run(agent, "What's the capital of France?", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
result = await Runner.run(agent, "What's the weather like there?", session=session)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Create branch from turn 2 (weather question)
|
||||
branch_id = await session.create_branch_from_turn(2, "weather_focus")
|
||||
|
||||
# Continue in new branch with different question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What are the main tourist attractions in Paris?",
|
||||
session=session
|
||||
)
|
||||
await session.store_run_usage(result)
|
||||
|
||||
# Switch back to main branch
|
||||
await session.switch_to_branch("main")
|
||||
|
||||
# Continue original conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"How expensive is it to visit?",
|
||||
session=session
|
||||
)
|
||||
await session.store_run_usage(result)
|
||||
```
|
||||
|
||||
## 结构化查询
|
||||
|
||||
AdvancedSQLiteSession 提供了多种方法,用于分析对话结构和内容。
|
||||
|
||||
### 对话分析
|
||||
|
||||
```python
|
||||
# Get conversation organized by turns
|
||||
conversation_by_turns = await session.get_conversation_by_turns()
|
||||
for turn_num, items in conversation_by_turns.items():
|
||||
print(f"Turn {turn_num}: {len(items)} items")
|
||||
for item in items:
|
||||
if item["tool_name"]:
|
||||
print(f" - {item['type']} (tool: {item['tool_name']})")
|
||||
else:
|
||||
print(f" - {item['type']}")
|
||||
|
||||
# Get tool usage statistics
|
||||
tool_usage = await session.get_tool_usage()
|
||||
for tool_name, count, turn in tool_usage:
|
||||
print(f"{tool_name}: used {count} times in turn {turn}")
|
||||
|
||||
# Find turns by content
|
||||
matching_turns = await session.find_turns_by_content("weather")
|
||||
for turn in matching_turns:
|
||||
print(f"Turn {turn['turn']}: {turn['content']}")
|
||||
```
|
||||
|
||||
### 消息结构
|
||||
|
||||
会话会自动跟踪消息结构,包括:
|
||||
|
||||
- 消息类型(用户、assistant、tool_call 等)
|
||||
- 工具调用的工具名称
|
||||
- 轮次编号和序列号
|
||||
- 分支关联
|
||||
- 时间戳
|
||||
|
||||
## 数据库架构
|
||||
|
||||
AdvancedSQLiteSession 在基础 SQLite 架构之上扩展了两个额外的表:
|
||||
|
||||
### message_structure 表
|
||||
|
||||
```sql
|
||||
CREATE TABLE message_structure (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
message_id INTEGER NOT NULL,
|
||||
branch_id TEXT NOT NULL DEFAULT 'main',
|
||||
message_type TEXT NOT NULL,
|
||||
sequence_number INTEGER NOT NULL,
|
||||
user_turn_number INTEGER,
|
||||
branch_turn_number INTEGER,
|
||||
tool_name TEXT,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (message_id) REFERENCES agent_messages(id) ON DELETE CASCADE
|
||||
);
|
||||
```
|
||||
|
||||
### turn_usage 表
|
||||
|
||||
```sql
|
||||
CREATE TABLE turn_usage (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
session_id TEXT NOT NULL,
|
||||
branch_id TEXT NOT NULL DEFAULT 'main',
|
||||
user_turn_number INTEGER NOT NULL,
|
||||
requests INTEGER DEFAULT 0,
|
||||
input_tokens INTEGER DEFAULT 0,
|
||||
output_tokens INTEGER DEFAULT 0,
|
||||
total_tokens INTEGER DEFAULT 0,
|
||||
input_tokens_details JSON,
|
||||
output_tokens_details JSON,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE,
|
||||
UNIQUE(session_id, branch_id, user_turn_number)
|
||||
);
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
查看[完整示例](https://github.com/openai/openai-agents-python/tree/main/examples/memory/advanced_sqlite_session_example.py),全面了解所有功能。
|
||||
|
||||
|
||||
## API 参考
|
||||
|
||||
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - 主类
|
||||
- [`Session`][agents.memory.session.Session] - 基础会话协议
|
||||
@@ -0,0 +1,179 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 加密会话
|
||||
|
||||
`EncryptedSession` 为任何会话实现提供透明加密,通过自动过期旧条目来保护对话数据。
|
||||
|
||||
## 功能
|
||||
|
||||
- **透明加密**:使用 Fernet 加密包装任何会话
|
||||
- **每会话密钥**:使用 HKDF 密钥派生,为每个会话生成唯一加密
|
||||
- **自动过期**:TTL 过期时会静默跳过旧条目
|
||||
- **即插即用替代方案**:适用于任何现有会话实现
|
||||
|
||||
## 安装
|
||||
|
||||
加密会话需要 `encrypt` extra:
|
||||
|
||||
```bash
|
||||
pip install openai-agents[encrypt]
|
||||
```
|
||||
|
||||
## 快速入门
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
|
||||
# Create underlying session
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Wrap with encryption
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-secret-key-here",
|
||||
ttl=600 # 10 minutes
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 配置
|
||||
|
||||
### 加密密钥
|
||||
|
||||
加密密钥可以是 Fernet 密钥,也可以是任意字符串:
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession
|
||||
|
||||
# Using a Fernet key (base64-encoded)
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-fernet-key-here",
|
||||
ttl=600
|
||||
)
|
||||
|
||||
# Using a raw string (will be derived to a key)
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="my-secret-password",
|
||||
ttl=600
|
||||
)
|
||||
```
|
||||
|
||||
### TTL(存活时间)
|
||||
|
||||
设置加密条目的有效时长:
|
||||
|
||||
```python
|
||||
# Items expire after 1 hour
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="secret",
|
||||
ttl=3600 # 1 hour in seconds
|
||||
)
|
||||
|
||||
# Items expire after 1 day
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="secret",
|
||||
ttl=86400 # 24 hours in seconds
|
||||
)
|
||||
```
|
||||
|
||||
## 与不同会话类型的搭配使用
|
||||
|
||||
### 与 SQLite 会话搭配使用
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
from agents.extensions.memory import EncryptedSession
|
||||
|
||||
# Create encrypted SQLite session
|
||||
underlying = SQLiteSession("user-123", "conversations.db")
|
||||
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying,
|
||||
encryption_key="secret-key"
|
||||
)
|
||||
```
|
||||
|
||||
### 与 SQLAlchemy 会话搭配使用
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
# Create encrypted SQLAlchemy session
|
||||
underlying = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="postgresql+asyncpg://user:pass@localhost/db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
session = EncryptedSession(
|
||||
session_id="user-123",
|
||||
underlying_session=underlying,
|
||||
encryption_key="secret-key"
|
||||
)
|
||||
```
|
||||
|
||||
!!! warning "高级会话功能"
|
||||
|
||||
将 `EncryptedSession` 与 `AdvancedSQLiteSession` 等高级会话实现一起使用时,请注意:
|
||||
|
||||
- 像 `find_turns_by_content()` 这样的方法无法有效工作,因为消息内容已加密
|
||||
- 基于内容的搜索会在加密数据上运行,因此效果受限
|
||||
|
||||
|
||||
|
||||
## 密钥派生
|
||||
|
||||
EncryptedSession 使用 HKDF(基于 HMAC 的密钥派生函数)为每个会话派生唯一的加密密钥:
|
||||
|
||||
- **主密钥**:你提供的加密密钥
|
||||
- **会话盐值**:会话 ID
|
||||
- **信息字符串**:`"agents.session-store.hkdf.v1"`
|
||||
- **输出**:32 字节 Fernet 密钥
|
||||
|
||||
这可以确保:
|
||||
- 每个会话都有唯一的加密密钥
|
||||
- 没有主密钥就无法派生密钥
|
||||
- 不同会话之间的会话数据无法相互解密
|
||||
|
||||
## 自动过期
|
||||
|
||||
当条目超过 TTL 时,检索过程中会自动跳过它们:
|
||||
|
||||
```python
|
||||
# Items older than TTL are silently ignored
|
||||
items = await session.get_items() # Only returns non-expired items
|
||||
|
||||
# Expired items don't affect session behavior
|
||||
result = await Runner.run(agent, "Continue conversation", session=session)
|
||||
```
|
||||
|
||||
## API 参考
|
||||
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - 主类
|
||||
- [`Session`][agents.memory.session.Session] - 基础会话协议
|
||||
@@ -0,0 +1,711 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 会话
|
||||
|
||||
Agents SDK 提供内置会话记忆,用于在多次智能体运行之间自动维护对话历史记录,从而无需在各轮之间手动处理 `.to_input_list()`。
|
||||
|
||||
会话会存储特定会话的对话历史记录,使智能体能够维护上下文,而无需显式的手动记忆管理。这对于构建聊天应用或多轮对话尤其有用,因为你希望智能体记住之前的交互。
|
||||
|
||||
当你希望 SDK 为你管理客户端侧记忆时,请使用会话。会话不能在同一次运行中与 `conversation_id`、`previous_response_id` 或 `auto_previous_response_id` 结合使用。如果你希望改用由 OpenAI 服务端管理的延续机制,请选择其中一种机制,而不是在其上叠加会话。
|
||||
|
||||
## 快速开始
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance with a session ID
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
# First turn
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Second turn - agent automatically remembers previous context
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
|
||||
# Also works with synchronous runner
|
||||
result = Runner.run_sync(
|
||||
agent,
|
||||
"What's the population?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "Approximately 39 million"
|
||||
```
|
||||
|
||||
## 使用同一会话恢复中断的运行
|
||||
|
||||
如果某次运行因等待批准而暂停,请使用同一个会话实例(或另一个指向同一后端存储的会话实例)来恢复它,以便恢复后的轮次继续使用同一份已存储的对话历史记录。
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "Delete temporary files that are no longer needed.", session=session)
|
||||
|
||||
if result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
state.approve(interruption)
|
||||
result = await Runner.run(agent, state, session=session)
|
||||
```
|
||||
|
||||
## 核心会话行为
|
||||
|
||||
启用会话记忆后:
|
||||
|
||||
1. **每次运行之前**:运行器会自动检索该会话的对话历史记录,并将其前置到输入项中。
|
||||
2. **每次运行之后**:运行期间生成的所有新项(用户输入、助手响应、工具调用等)都会自动存储到会话中。
|
||||
3. **上下文保留**:同一会话的每次后续运行都会包含完整的对话历史记录,使智能体能够维护上下文。
|
||||
|
||||
这消除了手动调用 `.to_input_list()` 并在运行之间管理对话状态的需要。
|
||||
|
||||
## 历史记录与新输入的合并控制
|
||||
|
||||
当你传入会话时,运行器通常会按如下方式准备模型输入:
|
||||
|
||||
1. 会话历史记录(从 `session.get_items(...)` 检索)
|
||||
2. 新轮次输入
|
||||
|
||||
使用 [`RunConfig.session_input_callback`][agents.run.RunConfig.session_input_callback] 在模型调用之前自定义该合并步骤。回调会接收两个列表:
|
||||
|
||||
- `history`:检索到的会话历史记录(已规范化为输入项格式)
|
||||
- `new_input`:当前轮次的新输入项
|
||||
|
||||
返回应发送给模型的最终输入项列表。
|
||||
|
||||
回调接收的是这两个列表的副本,因此你可以安全地修改它们。返回的列表会控制该轮次的模型输入,但 SDK 仍然只会持久化属于新轮次的项。因此,对旧历史记录重新排序或过滤,并不会导致旧会话项被再次作为新输入保存。
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, SQLiteSession
|
||||
|
||||
|
||||
def keep_recent_history(history, new_input):
|
||||
# Keep only the last 10 history items, then append the new turn.
|
||||
return history[-10:] + new_input
|
||||
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Continue from the latest updates only.",
|
||||
session=session,
|
||||
run_config=RunConfig(session_input_callback=keep_recent_history),
|
||||
)
|
||||
```
|
||||
|
||||
当你需要自定义裁剪、重新排序或选择性纳入历史记录,同时不改变会话存储项的方式时,请使用此功能。如果你需要在模型调用前立即进行更靠后的最终处理步骤,请使用[运行智能体指南](../running_agents.md)中的 [`call_model_input_filter`][agents.run.RunConfig.call_model_input_filter]。
|
||||
|
||||
## 检索历史记录的限制
|
||||
|
||||
使用 [`SessionSettings`][agents.memory.SessionSettings] 控制每次运行前获取多少历史记录。
|
||||
|
||||
- `SessionSettings(limit=None)`(默认):检索所有可用的会话项
|
||||
- `SessionSettings(limit=N)`:仅检索最近的 `N` 个项
|
||||
|
||||
你可以通过 [`RunConfig.session_settings`][agents.run.RunConfig.session_settings] 按运行应用此设置:
|
||||
|
||||
```python
|
||||
from agents import Agent, RunConfig, Runner, SessionSettings, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("conversation_123")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Summarize our recent discussion.",
|
||||
session=session,
|
||||
run_config=RunConfig(session_settings=SessionSettings(limit=50)),
|
||||
)
|
||||
```
|
||||
|
||||
如果你的会话实现公开了默认会话设置,`RunConfig.session_settings` 会为该次运行覆盖任何非 `None` 的值。这对于长对话很有用:你可以在不改变会话默认行为的情况下限制检索大小。
|
||||
|
||||
## 记忆操作
|
||||
|
||||
### 基本操作
|
||||
|
||||
会话支持用于管理对话历史记录的多种操作:
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Get all items in a session
|
||||
items = await session.get_items()
|
||||
|
||||
# Add new items to a session
|
||||
new_items = [
|
||||
{"role": "user", "content": "Hello"},
|
||||
{"role": "assistant", "content": "Hi there!"}
|
||||
]
|
||||
await session.add_items(new_items)
|
||||
|
||||
# Remove and return the most recent item
|
||||
last_item = await session.pop_item()
|
||||
print(last_item) # {"role": "assistant", "content": "Hi there!"}
|
||||
|
||||
# Clear all items from a session
|
||||
await session.clear_session()
|
||||
```
|
||||
|
||||
### 使用 pop_item 进行修正
|
||||
|
||||
当你想撤销或修改对话中的最后一项时,`pop_item` 方法尤其有用:
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = SQLiteSession("correction_example")
|
||||
|
||||
# Initial conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 2?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
|
||||
# User wants to correct their question
|
||||
assistant_item = await session.pop_item() # Remove agent's response
|
||||
user_item = await session.pop_item() # Remove user's question
|
||||
|
||||
# Ask a corrected question
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's 2 + 3?",
|
||||
session=session
|
||||
)
|
||||
print(f"Agent: {result.final_output}")
|
||||
```
|
||||
|
||||
## 内置会话实现
|
||||
|
||||
SDK 为不同用例提供了多个会话实现:
|
||||
|
||||
### 内置会话实现的选择
|
||||
|
||||
在阅读下方详细示例之前,使用此表选择一个起点。
|
||||
|
||||
| 会话类型 | 适用场景 | 备注 |
|
||||
| --- | --- | --- |
|
||||
| `SQLiteSession` | 本地开发和简单应用 | 内置、轻量,可基于文件或内存 |
|
||||
| `AsyncSQLiteSession` | 使用 `aiosqlite` 的异步 SQLite | 支持异步驱动的扩展后端 |
|
||||
| `RedisSession` | 跨多个工作进程/服务的共享记忆 | 适合低延迟分布式部署 |
|
||||
| `SQLAlchemySession` | 使用现有数据库的生产应用 | 适用于 SQLAlchemy 支持的数据库 |
|
||||
| `MongoDBSession` | 已使用 MongoDB 或需要多进程存储的应用 | 异步 pymongo;通过原子序列计数器保证顺序 |
|
||||
| `DaprSession` | 带有 Dapr sidecar 的云原生部署 | 支持多种状态存储,以及 TTL 和一致性控制 |
|
||||
| `OpenAIConversationsSession` | OpenAI 中由服务端管理的存储 | 基于 OpenAI Conversations API 的历史记录 |
|
||||
| `OpenAIResponsesCompactionSession` | 带有自动压缩的长对话 | 另一个会话后端的包装器 |
|
||||
| `AdvancedSQLiteSession` | SQLite 加分支/分析 | 功能集较重;请参阅专门页面 |
|
||||
| `EncryptedSession` | 基于另一个会话的加密 + TTL | 包装器;请先选择底层后端 |
|
||||
|
||||
一些实现有包含更多详细信息的专门页面;这些页面已在其小节中以内联链接形式给出。
|
||||
|
||||
如果你正在为 ChatKit 实现 Python 服务,请使用 `chatkit.store.Store` 实现来持久化 ChatKit 的线程和项。Agents SDK 会话(如 `SQLAlchemySession`)会管理 SDK 侧的对话历史记录,但它们不能直接替代 ChatKit 的 store。请参阅 [`chatkit-python` 关于实现 ChatKit 数据存储的指南](https://github.com/openai/chatkit-python/blob/main/docs/guides/respond-to-user-message.md#implement-your-chatkit-data-store)。
|
||||
|
||||
### OpenAI Conversations API 会话
|
||||
|
||||
通过 `OpenAIConversationsSession` 使用 [OpenAI 的 Conversations API](https://platform.openai.com/docs/api-reference/conversations)。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, OpenAIConversationsSession
|
||||
|
||||
# Create agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a new conversation
|
||||
session = OpenAIConversationsSession()
|
||||
|
||||
# Optionally resume a previous conversation by passing a conversation ID
|
||||
# session = OpenAIConversationsSession(conversation_id="conv_123")
|
||||
|
||||
# Start conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "San Francisco"
|
||||
|
||||
# Continue the conversation
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(result.final_output) # "California"
|
||||
```
|
||||
|
||||
### OpenAI Responses 压缩会话
|
||||
|
||||
使用 `OpenAIResponsesCompactionSession` 通过 Responses API(`responses.compact`)压缩已存储的对话历史记录。它会包装一个底层会话,并可根据 `should_trigger_compaction` 在每轮之后自动压缩。不要用它包装 `OpenAIConversationsSession`;这两个功能以不同方式管理历史记录。
|
||||
|
||||
#### 典型用法(自动压缩)
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
from agents.memory import OpenAIResponsesCompactionSession
|
||||
|
||||
underlying = SQLiteSession("conversation_123")
|
||||
session = OpenAIResponsesCompactionSession(
|
||||
session_id="conversation_123",
|
||||
underlying_session=underlying,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
默认情况下,一旦达到候选阈值,压缩会在每轮之后运行。
|
||||
|
||||
当你已经使用 Responses API 响应 ID 串联各轮时,`compaction_mode="previous_response_id"` 效果最好。`compaction_mode="input"` 则会基于当前会话项重建压缩请求,这在响应链不可用,或你希望以会话内容作为事实来源时很有用。默认的 `"auto"` 会选择可用的最安全选项。
|
||||
|
||||
如果你的智能体使用 `ModelSettings(store=False)` 运行,Responses API 不会保留最后一个响应以供之后查找。在这种无状态设置中,默认的 `"auto"` 模式会退回到基于输入的压缩,而不是依赖 `previous_response_id`。有关完整示例,请参阅 [`examples/memory/compaction_session_stateless_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/compaction_session_stateless_example.py)。
|
||||
|
||||
#### 自动压缩对流式传输的阻塞
|
||||
|
||||
压缩会清空并重写会话历史记录,因此 SDK 会等待压缩完成后才将该运行视为完成。在流式传输模式下,这意味着如果压缩开销较大,在最后一个输出 token 之后,`run.stream_events()` 可能仍会保持打开数秒。
|
||||
|
||||
如果你希望低延迟流式传输或快速轮次切换,请禁用自动压缩,并在轮次之间(或空闲期间)自行调用 `run_compaction()`。你可以根据自己的标准决定何时强制压缩。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
from agents.memory import OpenAIResponsesCompactionSession
|
||||
|
||||
underlying = SQLiteSession("conversation_123")
|
||||
session = OpenAIResponsesCompactionSession(
|
||||
session_id="conversation_123",
|
||||
underlying_session=underlying,
|
||||
# Disable triggering the auto compaction
|
||||
should_trigger_compaction=lambda _: False,
|
||||
)
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
|
||||
# Decide when to compact (e.g., on idle, every N turns, or size thresholds).
|
||||
await session.run_compaction({"force": True})
|
||||
```
|
||||
|
||||
### SQLite 会话
|
||||
|
||||
使用 SQLite 的默认轻量级会话实现:
|
||||
|
||||
```python
|
||||
from agents import SQLiteSession
|
||||
|
||||
# In-memory database (lost when process ends)
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Persistent file-based database
|
||||
session = SQLiteSession("user_123", "conversations.db")
|
||||
|
||||
# Use the session
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
### 异步 SQLite 会话
|
||||
|
||||
当你希望使用由 `aiosqlite` 支持的 SQLite 持久化时,请使用 `AsyncSQLiteSession`。
|
||||
|
||||
```bash
|
||||
pip install aiosqlite
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import AsyncSQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = AsyncSQLiteSession("user_123", db_path="conversations.db")
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
### Redis 会话
|
||||
|
||||
使用 `RedisSession` 在多个工作进程或服务之间共享会话记忆。
|
||||
|
||||
```bash
|
||||
pip install openai-agents[redis]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import RedisSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
session = RedisSession.from_url(
|
||||
"user_123",
|
||||
url="redis://localhost:6379/0",
|
||||
)
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
### SQLAlchemy 会话
|
||||
|
||||
使用任何 SQLAlchemy 支持的数据库实现的生产就绪型 Agents SDK 会话持久化:
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
|
||||
# Using database URL
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user_123",
|
||||
url="postgresql+asyncpg://user:pass@localhost/db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Using existing engine
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
||||
session = SQLAlchemySession("user_123", engine=engine, create_tables=True)
|
||||
```
|
||||
|
||||
请参阅 [SQLAlchemy 会话](sqlalchemy_session.md)了解详细文档。
|
||||
|
||||
### Dapr 会话
|
||||
|
||||
当你已经运行 Dapr sidecar,或希望在不更改智能体代码的情况下,让会话存储能够在不同状态存储后端之间迁移时,请使用 `DaprSession`。
|
||||
|
||||
```bash
|
||||
pip install openai-agents[dapr]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import DaprSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
async with DaprSession.from_address(
|
||||
"user_123",
|
||||
state_store_name="statestore",
|
||||
dapr_address="localhost:50001",
|
||||
) as session:
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
备注:
|
||||
|
||||
- `from_address(...)` 会为你创建并拥有 Dapr 客户端。如果你的应用已经管理了一个客户端,请直接使用 `dapr_client=...` 构造 `DaprSession(...)`。
|
||||
- 当底层状态存储支持 TTL 时,传入 `ttl=...` 可让它自动使旧会话数据过期。
|
||||
- 当你需要更强的写后读保证时,传入 `consistency=DAPR_CONSISTENCY_STRONG`。
|
||||
- Dapr Python SDK 还会检查 HTTP sidecar 端点。在本地开发中,启动 Dapr 时除了 `dapr_address` 中使用的 gRPC 端口外,还应使用 `--dapr-http-port 3500`。
|
||||
- 请参阅 [`examples/memory/dapr_session_example.py`](https://github.com/openai/openai-agents-python/tree/main/examples/memory/dapr_session_example.py)获取完整设置演练,包括本地组件和故障排查。
|
||||
|
||||
|
||||
### MongoDB 会话
|
||||
|
||||
对于已使用 MongoDB 或需要可水平扩展的多进程会话存储的应用,请使用 `MongoDBSession`。
|
||||
|
||||
```bash
|
||||
pip install openai-agents[mongodb]
|
||||
```
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import MongoDBSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Create from URI — owns the client and closes it when session.close() is called
|
||||
session = MongoDBSession.from_uri(
|
||||
"user-123",
|
||||
uri="mongodb://localhost:27017",
|
||||
database="agents",
|
||||
)
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
await session.close()
|
||||
```
|
||||
|
||||
备注:
|
||||
|
||||
- `from_uri(...)` 会创建并拥有 `AsyncMongoClient`,并在 `session.close()` 时关闭它。如果你的应用已经管理了一个客户端,请直接使用 `client=...` 构造 `MongoDBSession(...)`;在这种情况下,`session.close()` 不执行任何操作,生命周期由调用方管理。
|
||||
- 通过向 `from_uri(...)` 传入 `mongodb+srv://user:password@cluster.example.mongodb.net` URI,即可连接到 [MongoDB Atlas](https://www.mongodb.com/products/platform),无需其他更改。
|
||||
- 会使用两个集合,且二者名称都可通过 `sessions_collection=`(默认 `agent_sessions`)和 `messages_collection=`(默认 `agent_messages`)配置。首次使用时会自动创建索引。每个消息文档都带有一个单调递增的 `seq` 计数器,可在并发写入者和进程之间保持顺序。
|
||||
- 在首次运行之前,使用 `await session.ping()` 验证连接性。
|
||||
|
||||
### 高级 SQLite 会话
|
||||
|
||||
增强型 SQLite 会话,支持对话分支、用量分析和结构化查询:
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import AdvancedSQLiteSession
|
||||
|
||||
# Create with advanced features
|
||||
session = AdvancedSQLiteSession(
|
||||
session_id="user_123",
|
||||
db_path="conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Automatic usage tracking
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
await session.store_run_usage(result) # Track token usage
|
||||
|
||||
# Conversation branching
|
||||
await session.create_branch_from_turn(2) # Branch from turn 2
|
||||
```
|
||||
|
||||
请参阅 [高级 SQLite 会话](advanced_sqlite_session.md)了解详细文档。
|
||||
|
||||
### 加密会话
|
||||
|
||||
用于任何会话实现的透明加密包装器:
|
||||
|
||||
```python
|
||||
from agents.extensions.memory import EncryptedSession, SQLAlchemySession
|
||||
|
||||
# Create underlying session
|
||||
underlying_session = SQLAlchemySession.from_url(
|
||||
"user_123",
|
||||
url="sqlite+aiosqlite:///conversations.db",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
# Wrap with encryption and TTL
|
||||
session = EncryptedSession(
|
||||
session_id="user_123",
|
||||
underlying_session=underlying_session,
|
||||
encryption_key="your-secret-key",
|
||||
ttl=600 # 10 minutes
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
```
|
||||
|
||||
请参阅 [加密会话](encrypted_session.md)了解详细文档。
|
||||
|
||||
### 其他会话类型
|
||||
|
||||
还有一些其他内置选项。请参阅 `examples/memory/` 以及 `extensions/memory/` 下的源代码。
|
||||
|
||||
## 操作模式
|
||||
|
||||
### 会话 ID 命名
|
||||
|
||||
使用有意义的会话 ID 来帮助你组织对话:
|
||||
|
||||
- 基于用户:`"user_12345"`
|
||||
- 基于线程:`"thread_abc123"`
|
||||
- 基于上下文:`"support_ticket_456"`
|
||||
|
||||
### 记忆持久化
|
||||
|
||||
- 使用内存 SQLite(`SQLiteSession("session_id")`)处理临时对话
|
||||
- 使用基于文件的 SQLite(`SQLiteSession("session_id", "path/to/db.sqlite")`)处理持久对话
|
||||
- 当你需要基于 `aiosqlite` 的实现时,使用异步 SQLite(`AsyncSQLiteSession("session_id", db_path="...")`)
|
||||
- 使用基于 Redis 的会话(`RedisSession.from_url("session_id", url="redis://...")`)实现共享的低延迟会话记忆
|
||||
- 对于使用 SQLAlchemy 支持的现有数据库的生产系统,使用基于 SQLAlchemy 的会话(`SQLAlchemySession("session_id", engine=engine, create_tables=True)`)
|
||||
- 对于已使用 MongoDB 或需要多进程、可水平扩展会话存储的应用,使用 MongoDB 会话(`MongoDBSession.from_uri("session_id", uri="mongodb://localhost:27017")`)
|
||||
- 对于支持 30+ 数据库后端,并内置遥测、追踪和数据隔离的生产级云原生部署,使用 Dapr 状态存储会话(`DaprSession.from_address("session_id", state_store_name="statestore", dapr_address="localhost:50001")`)
|
||||
- 当你希望将历史记录存储在 OpenAI Conversations API 中时,使用 OpenAI 托管存储(`OpenAIConversationsSession()`)
|
||||
- 使用加密会话(`EncryptedSession(session_id, underlying_session, encryption_key)`)为任何会话包装透明加密和基于 TTL 的过期机制
|
||||
- 对于更高级的用例,可以考虑为其他生产系统(例如 Django)实现自定义会话后端
|
||||
|
||||
### 多个会话
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
agent = Agent(name="Assistant")
|
||||
|
||||
# Different sessions maintain separate conversation histories
|
||||
session_1 = SQLiteSession("user_123", "conversations.db")
|
||||
session_2 = SQLiteSession("user_456", "conversations.db")
|
||||
|
||||
result1 = await Runner.run(
|
||||
agent,
|
||||
"Help me with my account",
|
||||
session=session_1
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
agent,
|
||||
"What are my charges?",
|
||||
session=session_2
|
||||
)
|
||||
```
|
||||
|
||||
### 会话共享
|
||||
|
||||
```python
|
||||
# Different agents can share the same session
|
||||
support_agent = Agent(name="Support")
|
||||
billing_agent = Agent(name="Billing")
|
||||
session = SQLiteSession("user_123")
|
||||
|
||||
# Both agents will see the same conversation history
|
||||
result1 = await Runner.run(
|
||||
support_agent,
|
||||
"Help me with my account",
|
||||
session=session
|
||||
)
|
||||
result2 = await Runner.run(
|
||||
billing_agent,
|
||||
"What are my charges?",
|
||||
session=session
|
||||
)
|
||||
```
|
||||
|
||||
## 完整示例
|
||||
|
||||
下面是展示会话记忆实际效果的完整示例:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, SQLiteSession
|
||||
|
||||
|
||||
async def main():
|
||||
# Create an agent
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions="Reply very concisely.",
|
||||
)
|
||||
|
||||
# Create a session instance that will persist across runs
|
||||
session = SQLiteSession("conversation_123", "conversation_history.db")
|
||||
|
||||
print("=== Sessions Example ===")
|
||||
print("The agent will remember previous messages automatically.\n")
|
||||
|
||||
# First turn
|
||||
print("First turn:")
|
||||
print("User: What city is the Golden Gate Bridge in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What city is the Golden Gate Bridge in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Second turn - the agent will remember the previous conversation
|
||||
print("Second turn:")
|
||||
print("User: What state is it in?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What state is it in?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
# Third turn - continuing the conversation
|
||||
print("Third turn:")
|
||||
print("User: What's the population of that state?")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"What's the population of that state?",
|
||||
session=session
|
||||
)
|
||||
print(f"Assistant: {result.final_output}")
|
||||
print()
|
||||
|
||||
print("=== Conversation Complete ===")
|
||||
print("Notice how the agent remembered the context from previous turns!")
|
||||
print("Sessions automatically handles conversation history.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
## 自定义会话实现
|
||||
|
||||
你可以创建一个遵循 [`Session`][agents.memory.session.Session] 协议的类来实现自己的会话记忆:
|
||||
|
||||
```python
|
||||
from agents.memory.session import SessionABC
|
||||
from agents.items import TResponseInputItem
|
||||
from typing import List
|
||||
|
||||
class MyCustomSession(SessionABC):
|
||||
"""Custom session implementation following the Session protocol."""
|
||||
|
||||
def __init__(self, session_id: str):
|
||||
self.session_id = session_id
|
||||
# Your initialization here
|
||||
|
||||
async def get_items(self, limit: int | None = None) -> List[TResponseInputItem]:
|
||||
"""Retrieve conversation history for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def add_items(self, items: List[TResponseInputItem]) -> None:
|
||||
"""Store new items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def pop_item(self) -> TResponseInputItem | None:
|
||||
"""Remove and return the most recent item from this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
async def clear_session(self) -> None:
|
||||
"""Clear all items for this session."""
|
||||
# Your implementation here
|
||||
pass
|
||||
|
||||
# Use your custom session
|
||||
agent = Agent(name="Assistant")
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Hello",
|
||||
session=MyCustomSession("my_session")
|
||||
)
|
||||
```
|
||||
|
||||
## 社区会话实现
|
||||
|
||||
社区已开发出其他会话实现:
|
||||
|
||||
| 包 | 描述 |
|
||||
|---------|-------------|
|
||||
| [openai-django-sessions](https://pypi.org/project/openai-django-sessions/) | 基于 Django ORM 的会话,适用于任何 Django 支持的数据库(PostgreSQL、MySQL、SQLite 等) |
|
||||
|
||||
如果你构建了一个会话实现,欢迎提交文档 PR,将它添加到这里!
|
||||
|
||||
## API 参考
|
||||
|
||||
有关详细 API 文档,请参阅:
|
||||
|
||||
- [`Session`][agents.memory.session.Session] - 协议接口
|
||||
- [`OpenAIConversationsSession`][agents.memory.OpenAIConversationsSession] - OpenAI Conversations API 实现
|
||||
- [`OpenAIResponsesCompactionSession`][agents.memory.openai_responses_compaction_session.OpenAIResponsesCompactionSession] - Responses API 压缩包装器
|
||||
- [`SQLiteSession`][agents.memory.sqlite_session.SQLiteSession] - 基础 SQLite 实现
|
||||
- [`AsyncSQLiteSession`][agents.extensions.memory.async_sqlite_session.AsyncSQLiteSession] - 基于 `aiosqlite` 的异步 SQLite 实现
|
||||
- [`RedisSession`][agents.extensions.memory.redis_session.RedisSession] - 基于 Redis 的会话实现
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - 基于 SQLAlchemy 的实现
|
||||
- [`MongoDBSession`][agents.extensions.memory.mongodb_session.MongoDBSession] - 基于 MongoDB 的会话实现
|
||||
- [`DaprSession`][agents.extensions.memory.dapr_session.DaprSession] - Dapr 状态存储实现
|
||||
- [`AdvancedSQLiteSession`][agents.extensions.memory.advanced_sqlite_session.AdvancedSQLiteSession] - 支持分支和分析的增强型 SQLite
|
||||
- [`EncryptedSession`][agents.extensions.memory.encrypt_session.EncryptedSession] - 用于任何会话的加密包装器
|
||||
@@ -0,0 +1,80 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# SQLAlchemy 会话
|
||||
|
||||
`SQLAlchemySession` 使用 SQLAlchemy 提供生产就绪的会话实现,使你可以使用 SQLAlchemy 支持的任何数据库(PostgreSQL、MySQL、SQLite 等)进行会话存储。
|
||||
|
||||
## 安装
|
||||
|
||||
SQLAlchemy 会话需要 `sqlalchemy` 额外依赖:
|
||||
|
||||
```bash
|
||||
pip install openai-agents[sqlalchemy]
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
### 使用数据库 URL
|
||||
|
||||
最简单的入门方式:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
|
||||
async def main():
|
||||
agent = Agent("Assistant")
|
||||
|
||||
# Create session using database URL
|
||||
session = SQLAlchemySession.from_url(
|
||||
"user-123",
|
||||
url="sqlite+aiosqlite:///:memory:",
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
### 使用现有引擎
|
||||
|
||||
适用于已有 SQLAlchemy 引擎的应用:
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner
|
||||
from agents.extensions.memory import SQLAlchemySession
|
||||
from sqlalchemy.ext.asyncio import create_async_engine
|
||||
|
||||
async def main():
|
||||
# Create your database engine
|
||||
engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
|
||||
|
||||
agent = Agent("Assistant")
|
||||
session = SQLAlchemySession(
|
||||
"user-456",
|
||||
engine=engine,
|
||||
create_tables=True
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Hello", session=session)
|
||||
print(result.final_output)
|
||||
|
||||
# Clean up
|
||||
await engine.dispose()
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
|
||||
## API 参考
|
||||
|
||||
- [`SQLAlchemySession`][agents.extensions.memory.sqlalchemy_session.SQLAlchemySession] - 主类
|
||||
- [`Session`][agents.memory.session.Session] - 基础会话协议
|
||||
@@ -0,0 +1,145 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 流式传输
|
||||
|
||||
流式传输让你能够订阅智能体运行过程中的更新。这对于向最终用户展示进度更新和部分响应非常有用。
|
||||
|
||||
若要进行流式传输,可以调用 [`Runner.run_streamed()`][agents.run.Runner.run_streamed],它会返回一个 [`RunResultStreaming`][agents.result.RunResultStreaming]。调用 `result.stream_events()` 会得到一个由 [`StreamEvent`][agents.stream_events.StreamEvent] 对象组成的异步流,这些对象将在下文介绍。
|
||||
|
||||
请持续消费 `result.stream_events()`,直到异步迭代器结束。只有当迭代器结束时,一次流式运行才算完成;会话持久化、审批记账或历史压缩等后处理可能会在最后一个可见 token 到达后才完成。当循环退出时,`result.is_complete` 会反映最终运行状态。
|
||||
|
||||
## 原始响应事件
|
||||
|
||||
[`RawResponsesStreamEvent`][agents.stream_events.RawResponsesStreamEvent] 是直接从 LLM 传递过来的原始事件。它们采用 OpenAI Responses API 格式,这意味着每个事件都有一个类型(例如 `response.created`、`response.output_text.delta` 等)和数据。如果你希望在响应消息生成后立即将其流式传输给用户,这些事件会很有用。
|
||||
|
||||
计算机工具原始事件会保留与已存储结果相同的 Preview 与 GA 区分。Preview 流会流式传输带有一个 `action` 的 `computer_call` 项,而 `gpt-5.5` 可以流式传输带有批量 `actions[]` 的 `computer_call` 项。更高层级的 [`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent] 表面不会为此添加特殊的仅限计算机的事件名称:两种形态仍然都会以 `tool_called` 的形式呈现,截图结果则会以 `tool_output` 的形式返回,并包装一个 `computer_call_output` 项。
|
||||
|
||||
例如,这将逐个 token 输出 LLM 生成的文本。
|
||||
|
||||
```python
|
||||
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())
|
||||
```
|
||||
|
||||
## 流式传输与审批
|
||||
|
||||
流式传输与会暂停以等待工具审批的运行兼容。如果某个工具需要审批,`result.stream_events()` 会结束,待处理的审批会通过 [`RunResultStreaming.interruptions`][agents.result.RunResultStreaming.interruptions] 暴露。使用 `result.to_state()` 将结果转换为 [`RunState`][agents.run_state.RunState],批准或拒绝该中断,然后使用 `Runner.run_streamed(...)` 恢复运行。
|
||||
|
||||
```python
|
||||
result = Runner.run_streamed(agent, "Delete temporary files if they are no longer needed.")
|
||||
async for _event in result.stream_events():
|
||||
pass
|
||||
|
||||
if result.interruptions:
|
||||
state = result.to_state()
|
||||
for interruption in result.interruptions:
|
||||
state.approve(interruption)
|
||||
result = Runner.run_streamed(agent, state)
|
||||
async for _event in result.stream_events():
|
||||
pass
|
||||
```
|
||||
|
||||
有关完整的暂停/恢复演练,请参阅 [human-in-the-loop 指南](human_in_the_loop.md)。
|
||||
|
||||
## 当前轮次后的流式传输取消
|
||||
|
||||
如果需要在中途停止一次流式运行,请调用 [`result.cancel()`][agents.result.RunResultStreaming.cancel]。默认情况下,这会立即停止运行。若要让当前轮次在停止前干净地完成,请改为调用 `result.cancel(mode="after_turn")`。
|
||||
|
||||
在 `result.stream_events()` 完成之前,流式运行并未完成。在最后一个可见 token 之后,SDK 可能仍在持久化会话项、最终确定审批状态或压缩历史。
|
||||
|
||||
如果你正在从 [`result.to_input_list(mode="normalized")`][agents.result.RunResultBase.to_input_list] 手动继续,并且 `cancel(mode="after_turn")` 在一次工具轮次后停止,请通过使用该规范化输入重新运行 `result.last_agent` 来继续那个未完成的轮次,而不是立即追加一个新的用户轮次。
|
||||
- 如果流式运行因工具审批而停止,不要将其视为一个新轮次。请先完全消费流,检查 `result.interruptions`,然后改为从 `result.to_state()` 恢复。
|
||||
- 使用 [`RunConfig.session_input_callback`][agents.run.RunConfig.session_input_callback] 来自定义在下一次模型调用之前,如何合并检索到的会话历史与新的用户输入。如果你在那里重写新轮次项,那么被重写的版本就是该轮次会持久化的内容。
|
||||
|
||||
## 运行项事件与智能体事件
|
||||
|
||||
[`RunItemStreamEvent`][agents.stream_events.RunItemStreamEvent] 是更高层级的事件。它们会在某个项完全生成后通知你。这使你可以按“消息已生成”“工具已运行”等级别向用户推送进度更新,而不是按每个 token 推送。类似地,[`AgentUpdatedStreamEvent`][agents.stream_events.AgentUpdatedStreamEvent] 会在当前智能体发生变化时(例如由于任务转移)向你提供更新。
|
||||
|
||||
### 运行项事件名称
|
||||
|
||||
`RunItemStreamEvent.name` 使用一组固定的语义事件名称:
|
||||
|
||||
- `message_output_created`
|
||||
- `handoff_requested`
|
||||
- `handoff_occured`
|
||||
- `tool_called`
|
||||
- `tool_search_called`
|
||||
- `tool_search_output_created`
|
||||
- `tool_output`
|
||||
- `reasoning_item_created`
|
||||
- `mcp_approval_requested`
|
||||
- `mcp_approval_response`
|
||||
- `mcp_list_tools`
|
||||
|
||||
`handoff_occured` 为了向后兼容而有意拼写错误。
|
||||
|
||||
当你使用托管工具搜索时,模型发出工具搜索请求时会发出 `tool_search_called`,Responses API 返回已加载的子集时会发出 `tool_search_output_created`。
|
||||
|
||||
例如,这将忽略原始事件,并将更新流式传输给用户。
|
||||
|
||||
```python
|
||||
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())
|
||||
```
|
||||
@@ -0,0 +1,834 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 工具
|
||||
|
||||
工具让智能体能够执行操作,例如获取数据、运行代码、调用外部 API,甚至操作计算机。SDK 支持五个目录:
|
||||
|
||||
- 由OpenAI托管的工具:与模型一起在OpenAI服务上运行。
|
||||
- 本地/运行时执行工具:`ComputerTool` 和 `ApplyPatchTool` 始终在你的环境中运行,而 `ShellTool` 可以在本地或托管容器中运行。
|
||||
- Function calling:将任意 Python 函数封装为工具。
|
||||
- Agents as tools:将智能体公开为可调用工具,而无需执行完整的任务转移。
|
||||
- 实验性功能:Codex 工具:通过工具调用运行限定于工作区的 Codex 任务。
|
||||
|
||||
## 工具类型选择
|
||||
|
||||
将本页面作为目录使用,然后跳转到与你所控制的运行时相匹配的部分。
|
||||
|
||||
| 如果你想要…… | 从这里开始 |
|
||||
| --- | --- |
|
||||
| 使用由OpenAI管理的工具(网络检索、文件检索、Code Interpreter、托管式MCP、图像生成) | [托管工具](#hosted-tools) |
|
||||
| 使用工具搜索将大型工具集合延迟到运行时加载 | [托管工具搜索](#hosted-tool-search) |
|
||||
| 在你自己的进程或环境中运行工具 | [本地运行时工具](#local-runtime-tools) |
|
||||
| 将 Python 函数封装为工具 | [工具调用](#function-tools) |
|
||||
| 让一个智能体调用另一个智能体而不执行任务转移 | [Agents as tools](#agents-as-tools) |
|
||||
| 从智能体运行限定于工作区的 Codex 任务 | [实验性功能:Codex 工具](#experimental-codex-tool) |
|
||||
|
||||
## 托管工具
|
||||
|
||||
使用 [`OpenAIResponsesModel`][agents.models.openai_responses.OpenAIResponsesModel] 时,OpenAI提供了一些内置工具:
|
||||
|
||||
- [`WebSearchTool`][agents.tool.WebSearchTool] 让智能体能够检索网络。
|
||||
- [`FileSearchTool`][agents.tool.FileSearchTool] 支持从你的OpenAI向量存储中检索信息。
|
||||
- [`CodeInterpreterTool`][agents.tool.CodeInterpreterTool] 让LLM能够在沙箱环境中执行代码。
|
||||
- [`HostedMCPTool`][agents.tool.HostedMCPTool] 将远程MCP服务的工具公开给模型。
|
||||
- [`ImageGenerationTool`][agents.tool.ImageGenerationTool] 根据提示词生成图像。
|
||||
- [`ToolSearchTool`][agents.tool.ToolSearchTool] 让模型能够按需加载延迟加载的工具、命名空间或托管式MCP服务。
|
||||
|
||||
高级托管搜索选项:
|
||||
|
||||
- 除了 `vector_store_ids` 和 `max_num_results`,`FileSearchTool` 还支持 `filters`、`ranking_options` 和 `include_search_results`。
|
||||
- `WebSearchTool` 支持 `filters`、`user_location` 和 `search_context_size`。
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
### 托管工具搜索
|
||||
|
||||
工具搜索让 OpenAI Responses 模型可以将大型工具集合延迟到运行时加载,使模型仅加载当前轮次所需的子集。当你拥有大量工具调用、命名空间组或托管式MCP服务,并且希望在不预先公开每个工具的情况下减少工具模式所占的 token 时,此功能非常有用。
|
||||
|
||||
如果构建智能体时已经知道候选工具,请从托管工具搜索开始。如果你的应用需要动态决定加载哪些内容,Responses API 也支持由客户端执行的工具搜索,但标准 `Runner` 不会自动执行该模式。
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
|
||||
from agents import Agent, Runner, ToolSearchTool, function_tool, tool_namespace
|
||||
|
||||
|
||||
@function_tool(defer_loading=True)
|
||||
def get_customer_profile(
|
||||
customer_id: Annotated[str, "The customer ID to look up."],
|
||||
) -> str:
|
||||
"""Fetch a CRM customer profile."""
|
||||
return f"profile for {customer_id}"
|
||||
|
||||
|
||||
@function_tool(defer_loading=True)
|
||||
def list_open_orders(
|
||||
customer_id: Annotated[str, "The customer ID to look up."],
|
||||
) -> str:
|
||||
"""List open orders for a customer."""
|
||||
return f"open orders for {customer_id}"
|
||||
|
||||
|
||||
crm_tools = tool_namespace(
|
||||
name="crm",
|
||||
description="CRM tools for customer lookups.",
|
||||
tools=[get_customer_profile, list_open_orders],
|
||||
)
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Operations assistant",
|
||||
model="gpt-5.6-sol",
|
||||
instructions="Load the crm namespace before using CRM tools.",
|
||||
tools=[*crm_tools, ToolSearchTool()],
|
||||
)
|
||||
|
||||
result = await Runner.run(agent, "Look up customer_42 and list their open orders.")
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 托管工具搜索仅适用于 OpenAI Responses 模型。当前 Python SDK 的支持依赖于 `openai>=2.25.0`。
|
||||
- 在智能体上配置延迟加载的工具集合时,只添加一个 `ToolSearchTool()`。
|
||||
- 可搜索的工具集合包括 `@function_tool(defer_loading=True)`、`tool_namespace(name=..., description=..., tools=[...])` 和 `HostedMCPTool(tool_config={..., "defer_loading": True})`。
|
||||
- 延迟加载的工具调用必须与 `ToolSearchTool()` 配合使用。仅包含命名空间的设置也可以使用 `ToolSearchTool()`,让模型按需加载正确的工具组。
|
||||
- `tool_namespace()` 将多个 `FunctionTool` 实例归入一个具有共享名称和描述的命名空间。当你有许多相关工具(例如 `crm`、`billing` 或 `shipping`)时,这通常是最佳选择。
|
||||
- OpenAI的官方最佳实践指南是[尽可能使用命名空间](https://developers.openai.com/api/docs/guides/tools-tool-search#use-namespaces-where-possible)。
|
||||
- 如果可能,优先使用命名空间或托管式MCP服务,而不是许多单独延迟加载的函数。它们通常能为模型提供更好的高层搜索界面,并节省更多 token。
|
||||
- 命名空间可以混合包含立即可用和延迟加载的工具。未设置 `defer_loading=True` 的工具仍可立即调用,而同一命名空间中的延迟工具会通过工具搜索加载。
|
||||
- 根据经验,每个命名空间应保持相对精简,最好少于 10 个函数。
|
||||
- 具名 `tool_choice` 无法指定单独的命名空间名称或仅支持延迟加载的工具。应优先使用 `auto`、`required` 或实际的顶层可调用工具名称。
|
||||
- `ToolSearchTool(execution="client")` 用于手动编排 Responses。如果模型发出由客户端执行的 `tool_search_call`,标准 `Runner` 会引发异常,而不会替你执行。
|
||||
- 工具搜索活动会显示在 [`RunResult.new_items`](results.md#new-items) 和 [`RunItemStreamEvent`](streaming.md#run-item-event-names) 中,并使用专门的条目类型和事件类型。
|
||||
- 有关涵盖命名空间加载和顶层延迟工具的完整可运行代码示例,请参阅 `examples/tools/tool_search.py`。
|
||||
- 官方平台指南:[工具搜索](https://developers.openai.com/api/docs/guides/tools-tool-search)。
|
||||
|
||||
### 托管容器 Shell 与技能
|
||||
|
||||
`ShellTool` 还支持在OpenAI托管的容器中执行。当你希望模型在托管容器中而不是本地运行时中执行 Shell 命令时,请使用此模式。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, ShellTool, ShellToolSkillReference
|
||||
|
||||
csv_skill: ShellToolSkillReference = {
|
||||
"type": "skill_reference",
|
||||
"skill_id": "skill_698bbe879adc81918725cbc69dcae7960bc5613dadaed377",
|
||||
"version": "1",
|
||||
}
|
||||
|
||||
agent = Agent(
|
||||
name="Container shell agent",
|
||||
model="gpt-5.6-sol",
|
||||
instructions="Use the mounted skill when helpful.",
|
||||
tools=[
|
||||
ShellTool(
|
||||
environment={
|
||||
"type": "container_auto",
|
||||
"network_policy": {"type": "disabled"},
|
||||
"skills": [csv_skill],
|
||||
}
|
||||
)
|
||||
],
|
||||
)
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
"Use the configured skill to analyze CSV files in /mnt/data and summarize totals by region.",
|
||||
)
|
||||
print(result.final_output)
|
||||
```
|
||||
|
||||
若要在后续运行中复用现有容器,请设置 `environment={"type": "container_reference", "container_id": "cntr_..."}`。
|
||||
|
||||
注意事项:
|
||||
|
||||
- 托管 Shell 通过 Responses API 的 Shell 工具提供。
|
||||
- `container_auto` 为请求创建容器;`container_reference` 复用现有容器。
|
||||
- `container_auto` 还可以包含 `file_ids` 和 `memory_limit`。
|
||||
- `environment.skills` 接受技能引用和内联技能包。
|
||||
- 使用托管环境时,不要在 `ShellTool` 上设置 `executor`、`needs_approval` 或 `on_approval`。
|
||||
- `network_policy` 支持 `disabled` 和 `allowlist` 模式。
|
||||
- 在允许列表模式下,`network_policy.domain_secrets` 可以按名称注入限定于特定域名的密钥。
|
||||
- 有关完整代码示例,请参阅 `examples/tools/container_shell_skill_reference.py` 和 `examples/tools/container_shell_inline_skill.py`。
|
||||
- OpenAI平台指南:[Shell](https://platform.openai.com/docs/guides/tools-shell)和[技能](https://platform.openai.com/docs/guides/tools-skills)。
|
||||
|
||||
## 本地运行时工具
|
||||
|
||||
本地运行时工具在模型响应本身之外执行。模型仍会决定何时调用它们,但实际工作由你的应用或配置的执行环境完成。
|
||||
|
||||
`ComputerTool` 和 `ApplyPatchTool` 始终需要由你提供本地实现。`ShellTool` 横跨两种模式:如果需要托管执行,请使用上面的托管容器配置;如果希望命令在你自己的进程中运行,请使用下面的本地运行时配置。
|
||||
|
||||
本地运行时工具要求你提供实现:
|
||||
|
||||
- [`ComputerTool`][agents.tool.ComputerTool]:实现 [`Computer`][agents.computer.Computer] 或 [`AsyncComputer`][agents.computer.AsyncComputer] 接口,以启用 GUI/浏览器自动化。
|
||||
- [`ShellTool`][agents.tool.ShellTool]:同时用于本地执行和托管容器执行的最新 Shell 工具。
|
||||
- [`LocalShellTool`][agents.tool.LocalShellTool]:旧版本地 Shell 集成。
|
||||
- [`ApplyPatchTool`][agents.tool.ApplyPatchTool]:实现 [`ApplyPatchEditor`][agents.editor.ApplyPatchEditor],以便在本地应用差异。
|
||||
- 使用 `ShellTool(environment={"type": "local", "skills": [...]})` 可以提供本地 Shell 技能。
|
||||
|
||||
### ComputerTool 与 Responses 计算机操作工具
|
||||
|
||||
`ComputerTool` 仍然是本地执行框架:你需要提供 [`Computer`][agents.computer.Computer] 或 [`AsyncComputer`][agents.computer.AsyncComputer] 实现,SDK 会将该执行框架映射到 OpenAI Responses API 的计算机操作界面。
|
||||
|
||||
对于显式的 [`gpt-5.5`](https://developers.openai.com/api/docs/models/gpt-5.5) 请求,SDK 会发送正式版内置工具载荷 `{"type": "computer"}`。较旧的 `computer-use-preview` 模型会继续使用预览版载荷 `{"type": "computer_use_preview", "environment": ..., "display_width": ..., "display_height": ...}`。这与OpenAI[计算机操作指南](https://developers.openai.com/api/docs/guides/tools-computer-use/)中描述的平台迁移一致:
|
||||
|
||||
- 模型:`computer-use-preview` -> `gpt-5.5`
|
||||
- 工具选择器:`computer_use_preview` -> `computer`
|
||||
- 计算机调用结构:每个 `computer_call` 一个 `action` -> `computer_call` 上批量的 `actions[]`
|
||||
- 截断:预览版路径要求设置 `ModelSettings(truncation="auto")` -> 正式版路径不要求
|
||||
|
||||
SDK 会根据实际 Responses 请求中的有效模型选择相应的传输格式。如果你使用提示词模板,并且由于模型由提示词指定而使请求省略了 `model`,SDK 会保留兼容预览版的计算机操作载荷,除非你明确保留 `model="gpt-5.5"`,或通过 `ModelSettings(tool_choice="computer")` 或 `ModelSettings(tool_choice="computer_use")` 强制使用正式版选择器。
|
||||
|
||||
存在 [`ComputerTool`][agents.tool.ComputerTool] 时,`tool_choice="computer"`、`"computer_use"` 和 `"computer_use_preview"` 都会被接受,并规范化为与有效请求模型匹配的内置选择器。如果不存在 `ComputerTool`,这些字符串仍会像普通函数名称一样处理。
|
||||
|
||||
当 `ComputerTool` 由 [`ComputerProvider`][agents.tool.ComputerProvider] 工厂支持时,这一区别十分重要。正式版 `computer` 载荷在序列化时不需要 `environment` 或尺寸,因此工厂尚未解析也没有问题。兼容预览版的序列化仍需要已解析的 `Computer` 或 `AsyncComputer` 实例,以便 SDK 可以发送 `environment`、`display_width` 和 `display_height`。
|
||||
|
||||
在运行时,两条路径仍使用相同的本地执行框架。预览版响应会发出包含单个 `action` 的 `computer_call` 条目;`gpt-5.5` 可以发出批量的 `actions[]`,SDK 会按顺序执行这些操作,然后生成 `computer_call_output` 截图条目。有关基于 Playwright 的可运行执行框架,请参阅 `examples/tools/computer_use.py`。
|
||||
|
||||
```python
|
||||
from agents import Agent, ApplyPatchTool, ShellTool
|
||||
from agents.computer import AsyncComputer
|
||||
from agents.editor import ApplyPatchResult, ApplyPatchOperation, ApplyPatchEditor
|
||||
|
||||
|
||||
class NoopComputer(AsyncComputer):
|
||||
environment = "browser"
|
||||
dimensions = (1024, 768)
|
||||
async def screenshot(self): return ""
|
||||
async def click(self, x, y, button): ...
|
||||
async def double_click(self, x, y): ...
|
||||
async def scroll(self, x, y, scroll_x, scroll_y): ...
|
||||
async def type(self, text): ...
|
||||
async def wait(self): ...
|
||||
async def move(self, x, y): ...
|
||||
async def keypress(self, keys): ...
|
||||
async def drag(self, path): ...
|
||||
|
||||
|
||||
class NoopEditor(ApplyPatchEditor):
|
||||
async def create_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
async def update_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
async def delete_file(self, op: ApplyPatchOperation): return ApplyPatchResult(status="completed")
|
||||
|
||||
|
||||
async def run_shell(request):
|
||||
return "shell output"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Local tools agent",
|
||||
tools=[
|
||||
ShellTool(executor=run_shell),
|
||||
ApplyPatchTool(editor=NoopEditor()),
|
||||
# ComputerTool expects a Computer/AsyncComputer implementation; omitted here for brevity.
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
## 工具调用
|
||||
|
||||
你可以将任意 Python 函数用作工具。Agents SDK 会自动设置该工具:
|
||||
|
||||
- 工具名称将是 Python 函数的名称(你也可以自行提供名称)
|
||||
- 工具描述将取自函数的文档字符串(你也可以自行提供描述)
|
||||
- 函数输入的模式会根据函数参数自动创建
|
||||
- 除非禁用,否则每个输入的描述都取自函数的文档字符串
|
||||
|
||||
我们使用 Python 的 `inspect` 模块提取函数签名,同时使用 [`griffe`](https://mkdocstrings.github.io/griffe/) 解析文档字符串,并使用 `pydantic` 创建模式。
|
||||
|
||||
使用 OpenAI Responses 模型时,`@function_tool(defer_loading=True)` 会隐藏工具调用,直到 `ToolSearchTool()` 将其加载。你还可以使用 [`tool_namespace()`][agents.tool.tool_namespace] 对相关工具调用进行分组。有关完整设置和限制,请参阅[托管工具搜索](#hosted-tool-search)。
|
||||
|
||||
```python
|
||||
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 # (1)!
|
||||
async def fetch_weather(location: Location) -> str:
|
||||
# (2)!
|
||||
"""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") # (3)!
|
||||
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], # (4)!
|
||||
)
|
||||
|
||||
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()
|
||||
|
||||
```
|
||||
|
||||
1. 你可以使用任意 Python 类型作为函数参数,并且函数可以是同步或异步函数。
|
||||
2. 如果存在文档字符串,则会使用它来获取函数描述和参数描述。
|
||||
3. 函数可以选择接收 `context`(必须是第一个参数)。你还可以设置覆盖项,例如工具名称、描述、要使用的文档字符串样式等。
|
||||
4. 你可以将装饰后的函数传入工具列表。
|
||||
|
||||
??? note "展开以查看输出"
|
||||
|
||||
```
|
||||
fetch_weather
|
||||
Fetch the weather for a given location.
|
||||
{
|
||||
"$defs": {
|
||||
"Location": {
|
||||
"properties": {
|
||||
"lat": {
|
||||
"title": "Lat",
|
||||
"type": "number"
|
||||
},
|
||||
"long": {
|
||||
"title": "Long",
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"lat",
|
||||
"long"
|
||||
],
|
||||
"title": "Location",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"location": {
|
||||
"$ref": "#/$defs/Location",
|
||||
"description": "The location to fetch the weather for."
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"location"
|
||||
],
|
||||
"title": "fetch_weather_args",
|
||||
"type": "object"
|
||||
}
|
||||
|
||||
fetch_data
|
||||
Read the contents of a file.
|
||||
{
|
||||
"properties": {
|
||||
"path": {
|
||||
"description": "The path to the file to read.",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
},
|
||||
"directory": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "The directory to read the file from.",
|
||||
"title": "Directory"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"path"
|
||||
],
|
||||
"title": "fetch_data_args",
|
||||
"type": "object"
|
||||
}
|
||||
```
|
||||
|
||||
### 从工具调用返回图像或文件
|
||||
|
||||
除了返回文本输出,你还可以将一个或多个图像或文件作为工具调用的输出返回。为此,你可以返回以下任意内容:
|
||||
|
||||
- 图像:[`ToolOutputImage`][agents.tool.ToolOutputImage](或 TypedDict 版本 [`ToolOutputImageDict`][agents.tool.ToolOutputImageDict])
|
||||
- 文件:[`ToolOutputFileContent`][agents.tool.ToolOutputFileContent](或 TypedDict 版本 [`ToolOutputFileContentDict`][agents.tool.ToolOutputFileContentDict])
|
||||
- 文本:字符串、可转换为字符串的对象,或 [`ToolOutputText`][agents.tool.ToolOutputText](或 TypedDict 版本 [`ToolOutputTextDict`][agents.tool.ToolOutputTextDict])
|
||||
|
||||
### 自定义工具调用
|
||||
|
||||
有时,你可能不想将 Python 函数用作工具。如果愿意,你可以直接创建 [`FunctionTool`][agents.tool.FunctionTool]。你需要提供:
|
||||
|
||||
- `name`
|
||||
- `description`
|
||||
- `params_json_schema`,即参数的 JSON 模式
|
||||
- `on_invoke_tool`,这是一个异步函数,接收 [`ToolContext`][agents.tool_context.ToolContext] 和采用 JSON 字符串形式的参数,并返回工具输出(例如文本、结构化工具输出对象或输出列表)。
|
||||
|
||||
```python
|
||||
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,
|
||||
)
|
||||
```
|
||||
|
||||
### 参数与文档字符串的自动解析
|
||||
|
||||
如前所述,我们会自动解析函数签名以提取工具模式,并解析文档字符串以提取工具及各个参数的描述。相关注意事项如下:
|
||||
|
||||
1. 签名解析通过 `inspect` 模块完成。我们使用类型注解理解参数类型,并动态构建 Pydantic 模型来表示整体模式。它支持大多数类型,包括 Python 基本类型、Pydantic 模型、TypedDict 等。
|
||||
2. 我们使用 `griffe` 解析文档字符串。支持的文档字符串格式包括 `google`、`sphinx` 和 `numpy`。我们会尝试自动检测文档字符串格式,但这只能尽力而为,你可以在调用 `function_tool` 时显式设置格式。也可以将 `use_docstring_info` 设置为 `False`,以禁用文档字符串解析。
|
||||
|
||||
模式提取代码位于 [`agents.function_schema`][]。
|
||||
|
||||
### 使用 Pydantic Field 约束和描述参数
|
||||
|
||||
你可以使用 Pydantic 的 [`Field`](https://docs.pydantic.dev/latest/concepts/fields/) 为工具参数添加约束(例如数字的最小值/最大值、字符串的长度或模式)和描述。与 Pydantic 一样,支持两种形式:基于默认值的形式(`arg: int = Field(..., ge=1)`)和 `Annotated` 形式(`arg: Annotated[int, Field(..., ge=1)]`)。生成的 JSON 模式和验证都会包含这些约束。
|
||||
|
||||
```python
|
||||
from typing import Annotated
|
||||
from pydantic import Field
|
||||
from agents import function_tool
|
||||
|
||||
# Default-based form
|
||||
@function_tool
|
||||
def score_a(score: int = Field(..., ge=0, le=100, description="Score from 0 to 100")) -> str:
|
||||
return f"Score recorded: {score}"
|
||||
|
||||
# Annotated form
|
||||
@function_tool
|
||||
def score_b(score: Annotated[int, Field(..., ge=0, le=100, description="Score from 0 to 100")]) -> str:
|
||||
return f"Score recorded: {score}"
|
||||
```
|
||||
|
||||
### 工具调用超时
|
||||
|
||||
你可以使用 `@function_tool(timeout=...)` 为异步工具调用设置单次调用超时。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, function_tool
|
||||
|
||||
|
||||
@function_tool(timeout=2.0)
|
||||
async def slow_lookup(query: str) -> str:
|
||||
await asyncio.sleep(10)
|
||||
return f"Result for {query}"
|
||||
|
||||
|
||||
agent = Agent(
|
||||
name="Timeout demo",
|
||||
instructions="Use tools when helpful.",
|
||||
tools=[slow_lookup],
|
||||
)
|
||||
```
|
||||
|
||||
达到超时时间时,默认行为是 `timeout_behavior="error_as_result"`,它会发送一条模型可见的超时消息(例如 `Tool 'slow_lookup' timed out after 2 seconds.`)。
|
||||
|
||||
你可以控制超时处理方式:
|
||||
|
||||
- `timeout_behavior="error_as_result"`(默认):向模型返回超时消息,使其能够恢复。
|
||||
- `timeout_behavior="raise_exception"`:引发 [`ToolTimeoutError`][agents.exceptions.ToolTimeoutError] 并使运行失败。
|
||||
- `timeout_error_function=...`:使用 `error_as_result` 时自定义超时消息。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, Runner, ToolTimeoutError, function_tool
|
||||
|
||||
|
||||
@function_tool(timeout=1.5, timeout_behavior="raise_exception")
|
||||
async def slow_tool() -> str:
|
||||
await asyncio.sleep(5)
|
||||
return "done"
|
||||
|
||||
|
||||
agent = Agent(name="Timeout hard-fail", tools=[slow_tool])
|
||||
|
||||
try:
|
||||
await Runner.run(agent, "Run the tool")
|
||||
except ToolTimeoutError as e:
|
||||
print(f"{e.tool_name} timed out in {e.timeout_seconds} seconds")
|
||||
```
|
||||
|
||||
!!! note
|
||||
|
||||
仅异步 `@function_tool` 处理程序支持超时配置。
|
||||
|
||||
### 工具调用中的错误处理
|
||||
|
||||
通过 `@function_tool` 创建工具调用时,你可以传入 `failure_error_function`。如果工具调用崩溃,该函数会向LLM提供错误响应。
|
||||
|
||||
- 默认情况下(即不传入任何内容时),它会运行 `default_tool_error_function`,告知LLM发生了错误。
|
||||
- 如果传入你自己的错误函数,则会改为运行该函数,并将响应发送给LLM。
|
||||
- 如果显式传入 `None`,则会重新引发任何工具调用错误,供你处理。如果模型生成了无效 JSON,这可能是 `ModelBehaviorError`;如果你的代码崩溃,则可能是 `UserError` 等。
|
||||
|
||||
```python
|
||||
from agents import function_tool, RunContextWrapper
|
||||
from typing import Any
|
||||
|
||||
def my_custom_error_function(context: RunContextWrapper[Any], error: Exception) -> str:
|
||||
"""A custom function to provide a user-friendly error message."""
|
||||
print(f"A tool call failed with the following error: {error}")
|
||||
return "An internal server error occurred. Please try again later."
|
||||
|
||||
@function_tool(failure_error_function=my_custom_error_function)
|
||||
def get_user_profile(user_id: str) -> str:
|
||||
"""Fetches a user profile from a mock API.
|
||||
This function demonstrates a 'flaky' or failing API call.
|
||||
"""
|
||||
if user_id == "user_123":
|
||||
return "User profile for user_123 successfully retrieved."
|
||||
else:
|
||||
raise ValueError(f"Could not retrieve profile for user_id: {user_id}. API returned an error.")
|
||||
|
||||
```
|
||||
|
||||
如果你手动创建 `FunctionTool` 对象,则必须在 `on_invoke_tool` 函数内处理错误。
|
||||
|
||||
## Agents as tools
|
||||
|
||||
在某些工作流中,你可能希望由一个中央智能体编排由多个专用智能体组成的网络,而不是转移控制权。你可以通过将智能体建模为工具来实现这一点。
|
||||
|
||||
```python
|
||||
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)
|
||||
```
|
||||
|
||||
### 工具智能体自定义
|
||||
|
||||
`agent.as_tool` 函数是一种便捷方法,可轻松将智能体转换为工具。它支持常见的运行时选项,例如 `max_turns`、`run_config`、`hooks`、`previous_response_id`、`conversation_id`、`session` 和 `needs_approval`。它还通过 `parameters`、`input_builder` 和 `include_input_schema` 支持结构化输入。
|
||||
|
||||
状态选项用于配置由工具调用启动的嵌套智能体运行;父运行的对话状态不会自动继承。若要在父运行和嵌套运行之间共享由客户端管理的历史记录,请显式向两者传入相同的 `session`。与 `Runner.run` 一样,请为嵌套运行选择一种状态策略:由客户端管理的 `session`,或通过 `previous_response_id` 或 `conversation_id` 在服务端管理的延续。
|
||||
|
||||
```python
|
||||
@function_tool
|
||||
async def run_my_agent() -> str:
|
||||
"""A tool that runs the agent with custom configs"""
|
||||
|
||||
agent = Agent(name="My agent", instructions="...")
|
||||
|
||||
result = await Runner.run(
|
||||
agent,
|
||||
input="...",
|
||||
max_turns=5,
|
||||
run_config=...
|
||||
)
|
||||
|
||||
return str(result.final_output)
|
||||
```
|
||||
|
||||
### 工具智能体的结构化输入
|
||||
|
||||
默认情况下,`Agent.as_tool()` 需要单个字符串输入(`{"input": "..."}`),但你可以通过传入 `parameters`(Pydantic 模型或数据类类型)公开结构化模式。
|
||||
|
||||
其他选项:
|
||||
|
||||
- `include_input_schema=True` 在生成的嵌套输入中包含完整的 JSON Schema。
|
||||
- `input_builder=...` 让你可以完全自定义如何将结构化工具参数转换为嵌套智能体输入。
|
||||
- `RunContextWrapper.tool_input` 包含嵌套运行上下文中已解析的结构化载荷。
|
||||
|
||||
```python
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class TranslationInput(BaseModel):
|
||||
text: str = Field(description="Text to translate.")
|
||||
source: str = Field(description="Source language.")
|
||||
target: str = Field(description="Target language.")
|
||||
|
||||
|
||||
translator_tool = translator_agent.as_tool(
|
||||
tool_name="translate_text",
|
||||
tool_description="Translate text between languages.",
|
||||
parameters=TranslationInput,
|
||||
include_input_schema=True,
|
||||
)
|
||||
```
|
||||
|
||||
有关完整的可运行代码示例,请参阅 `examples/agent_patterns/agents_as_tools_structured.py`。
|
||||
|
||||
### 工具智能体的审批关卡
|
||||
|
||||
`Agent.as_tool(..., needs_approval=...)` 使用与 `function_tool` 相同的审批流程。如果需要审批,运行会暂停,待处理条目会出现在 `result.interruptions` 中;然后使用 `result.to_state()`,并在调用 `state.approve(...)` 或 `state.reject(...)` 后恢复运行。有关完整的暂停/恢复模式,请参阅[人工介入指南](human_in_the_loop.md)。
|
||||
|
||||
### 自定义输出提取
|
||||
|
||||
在某些情况下,你可能希望先修改工具智能体的输出,再将其返回给中央智能体。以下情况可能适合这样做:
|
||||
|
||||
- 从子智能体的聊天历史记录中提取特定信息(例如 JSON 载荷)。
|
||||
- 转换或重新格式化智能体的最终答案(例如将 Markdown 转换为纯文本或 CSV)。
|
||||
- 验证输出,或在智能体响应缺失或格式错误时提供回退值。
|
||||
|
||||
你可以通过向 `as_tool` 方法提供 `custom_output_extractor` 参数来实现:
|
||||
|
||||
```python
|
||||
async def extract_json_payload(run_result: RunResult) -> str:
|
||||
# Scan the agent’s outputs in reverse order until we find a JSON-like message from a tool call.
|
||||
for item in reversed(run_result.new_items):
|
||||
if isinstance(item, ToolCallOutputItem) and item.output.strip().startswith("{"):
|
||||
return item.output.strip()
|
||||
# Fallback to an empty JSON object if nothing was found
|
||||
return "{}"
|
||||
|
||||
|
||||
json_tool = data_agent.as_tool(
|
||||
tool_name="get_data_json",
|
||||
tool_description="Run the data agent and return only its JSON payload",
|
||||
custom_output_extractor=extract_json_payload,
|
||||
)
|
||||
```
|
||||
|
||||
在自定义提取器中,嵌套的 [`RunResult`][agents.result.RunResult] 还会公开 [`agent_tool_invocation`][agents.result.RunResultBase.agent_tool_invocation]。当你在后处理嵌套结果时需要外层工具名称、调用 ID 或原始参数,此属性非常有用。请参阅[结果指南](results.md#agent-as-tool-metadata)。
|
||||
|
||||
### 嵌套智能体运行的流式传输
|
||||
|
||||
向 `as_tool` 传入 `on_stream` 回调,即可监听嵌套智能体发出的流式事件,同时在流结束后仍返回其最终输出。
|
||||
|
||||
```python
|
||||
from agents import AgentToolStreamEvent
|
||||
|
||||
|
||||
async def handle_stream(event: AgentToolStreamEvent) -> None:
|
||||
# Inspect the underlying StreamEvent along with agent metadata.
|
||||
print(f"[stream] {event['agent'].name} :: {event['event'].type}")
|
||||
|
||||
|
||||
billing_agent_tool = billing_agent.as_tool(
|
||||
tool_name="billing_helper",
|
||||
tool_description="Answer billing questions.",
|
||||
on_stream=handle_stream, # Can be sync or async.
|
||||
)
|
||||
```
|
||||
|
||||
预期行为:
|
||||
|
||||
- 事件类型与 `StreamEvent["type"]` 一致:`raw_response_event`、`run_item_stream_event`、`agent_updated_stream_event`。
|
||||
- 提供 `on_stream` 会自动以流式传输模式运行嵌套智能体,并在返回最终输出前耗尽整个流。
|
||||
- 处理程序可以是同步或异步的;每个事件都会按照到达顺序传递。
|
||||
- 通过模型工具调用来调用工具时会提供 `tool_call`;直接调用时,它可能为 `None`。
|
||||
- 有关完整的可运行代码示例,请参阅 `examples/agent_patterns/agents_as_tools_streaming.py`。
|
||||
|
||||
### 工具的条件启用
|
||||
|
||||
你可以使用 `is_enabled` 参数,在运行时有条件地启用或禁用智能体工具。这样,你就可以根据上下文、用户偏好或运行时条件,动态筛选LLM可用的工具。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from agents import Agent, AgentBase, Runner, RunContextWrapper
|
||||
from pydantic import BaseModel
|
||||
|
||||
class LanguageContext(BaseModel):
|
||||
language_preference: str = "french_spanish"
|
||||
|
||||
def french_enabled(ctx: RunContextWrapper[LanguageContext], agent: AgentBase) -> bool:
|
||||
"""Enable French for French+Spanish preference."""
|
||||
return ctx.context.language_preference == "french_spanish"
|
||||
|
||||
# Create specialized agents
|
||||
spanish_agent = Agent(
|
||||
name="spanish_agent",
|
||||
instructions="You respond in Spanish. Always reply to the user's question in Spanish.",
|
||||
)
|
||||
|
||||
french_agent = Agent(
|
||||
name="french_agent",
|
||||
instructions="You respond in French. Always reply to the user's question in French.",
|
||||
)
|
||||
|
||||
# Create orchestrator with conditional tools
|
||||
orchestrator = Agent(
|
||||
name="orchestrator",
|
||||
instructions=(
|
||||
"You are a multilingual assistant. You use the tools given to you to respond to users. "
|
||||
"You must call ALL available tools to provide responses in different languages. "
|
||||
"You never respond in languages yourself, you always use the provided tools."
|
||||
),
|
||||
tools=[
|
||||
spanish_agent.as_tool(
|
||||
tool_name="respond_spanish",
|
||||
tool_description="Respond to the user's question in Spanish",
|
||||
is_enabled=True, # Always enabled
|
||||
),
|
||||
french_agent.as_tool(
|
||||
tool_name="respond_french",
|
||||
tool_description="Respond to the user's question in French",
|
||||
is_enabled=french_enabled,
|
||||
),
|
||||
],
|
||||
)
|
||||
|
||||
async def main():
|
||||
context = RunContextWrapper(LanguageContext(language_preference="french_spanish"))
|
||||
result = await Runner.run(orchestrator, "How are you?", context=context.context)
|
||||
print(result.final_output)
|
||||
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
`is_enabled` 参数接受:
|
||||
|
||||
- **布尔值**:`True`(始终启用)或 `False`(始终禁用)
|
||||
- **可调用函数**:接收 `(context, agent)` 并返回布尔值的函数
|
||||
- **异步函数**:用于复杂条件逻辑的异步函数
|
||||
|
||||
禁用的工具会在运行时对LLM完全隐藏,因此适用于:
|
||||
|
||||
- 根据用户权限控制功能
|
||||
- 特定环境的工具可用性(开发环境与生产环境)
|
||||
- 对不同工具配置进行 A/B 测试
|
||||
- 根据运行时状态动态筛选工具
|
||||
|
||||
## 实验性功能:Codex 工具
|
||||
|
||||
`codex_tool` 封装了 Codex CLI,使智能体能够在工具调用期间运行限定于工作区的任务(Shell、文件编辑、MCP工具)。此功能处于实验阶段,可能会发生变化。
|
||||
|
||||
当你希望主智能体将范围明确的工作区任务委派给 Codex,同时不退出当前运行时,请使用此工具。默认工具名称为 `codex`。如果设置自定义名称,则该名称必须是 `codex` 或以 `codex_` 开头。当智能体包含多个 Codex 工具时,每个工具必须使用唯一名称。
|
||||
|
||||
```python
|
||||
from agents import Agent
|
||||
from agents.extensions.experimental.codex import ThreadOptions, TurnOptions, codex_tool
|
||||
|
||||
agent = Agent(
|
||||
name="Codex Agent",
|
||||
instructions="Use the codex tool to inspect the workspace and answer the question.",
|
||||
tools=[
|
||||
codex_tool(
|
||||
sandbox_mode="workspace-write",
|
||||
working_directory="/path/to/repo",
|
||||
default_thread_options=ThreadOptions(
|
||||
model="gpt-5.5",
|
||||
model_reasoning_effort="low",
|
||||
network_access_enabled=True,
|
||||
web_search_mode="disabled",
|
||||
approval_policy="never",
|
||||
),
|
||||
default_turn_options=TurnOptions(
|
||||
idle_timeout_seconds=60,
|
||||
),
|
||||
persist_session=True,
|
||||
)
|
||||
],
|
||||
)
|
||||
```
|
||||
|
||||
请从以下选项组开始:
|
||||
|
||||
- 执行范围:`sandbox_mode` 和 `working_directory` 定义 Codex 可以在哪里操作。请将两者配合使用;如果工作目录不在 Git 仓库内,请设置 `skip_git_repo_check=True`。
|
||||
- 线程默认值:`default_thread_options=ThreadOptions(...)` 用于配置模型、推理强度、审批策略、其他目录、网络访问和网络检索模式。应优先使用 `web_search_mode`,而不是旧版的 `web_search_enabled`。
|
||||
- 轮次默认值:`default_turn_options=TurnOptions(...)` 用于配置每轮行为,例如 `idle_timeout_seconds` 和可选的取消 `signal`。
|
||||
- 工具输入/输出:工具调用必须至少包含一个 `inputs` 条目,其格式为 `{ "type": "text", "text": ... }` 或 `{ "type": "local_image", "path": ... }`。`output_schema` 可用于要求 Codex 提供结构化响应。
|
||||
|
||||
线程复用和持久化是独立的控制项:
|
||||
|
||||
- `persist_session=True` 会让对同一工具实例的重复调用复用一个 Codex 线程。
|
||||
- `use_run_context_thread_id=True` 会在运行上下文中存储并复用线程 ID,适用于共享同一可变上下文对象的多次运行。
|
||||
- 线程 ID 的优先级依次为:每次调用的 `thread_id`、运行上下文线程 ID(如果启用),然后是已配置的 `thread_id` 选项。
|
||||
- 对于 `name="codex"`,默认运行上下文键为 `codex_thread_id`;对于 `name="codex_<suffix>"`,则为 `codex_thread_id_<suffix>`。可以使用 `run_context_thread_id_key` 覆盖它。
|
||||
|
||||
运行时配置:
|
||||
|
||||
- 身份验证:设置 `CODEX_API_KEY`(首选)或 `OPENAI_API_KEY`,或传入 `codex_options={"api_key": "..."}`。
|
||||
- 运行时:`codex_options.base_url` 会覆盖 CLI 的基础 URL。
|
||||
- 二进制文件解析:设置 `codex_options.codex_path_override`(或 `CODEX_PATH`)以固定 CLI 路径。否则,SDK 会先从 `PATH` 中解析 `codex`,然后回退到捆绑的供应商二进制文件。
|
||||
- 环境:`codex_options.env` 完全控制子进程环境。提供该选项时,子进程不会继承 `os.environ`。
|
||||
- 流限制:`codex_options.codex_subprocess_stream_limit_bytes`(或 `OPENAI_AGENTS_CODEX_SUBPROCESS_STREAM_LIMIT_BYTES`)控制 stdout/stderr 读取器限制。有效范围为 `65536` 到 `67108864`;默认值为 `8388608`。
|
||||
- 流式传输:`on_stream` 接收线程/轮次生命周期事件和条目事件(`reasoning`、`command_execution`、`mcp_tool_call`、`file_change`、`web_search`、`todo_list` 和 `error` 条目更新)。
|
||||
- 输出:结果包括 `response`、`usage` 和 `thread_id`;使用量会添加到 `RunContextWrapper.usage`。
|
||||
|
||||
参考资料:
|
||||
|
||||
- [Codex 工具 API 参考](ref/extensions/experimental/codex/codex_tool.md)
|
||||
- [ThreadOptions 参考](ref/extensions/experimental/codex/thread_options.md)
|
||||
- [TurnOptions 参考](ref/extensions/experimental/codex/turn_options.md)
|
||||
- 有关完整的可运行代码示例,请参阅 `examples/tools/codex.py` 和 `examples/tools/codex_same_thread.py`。
|
||||
@@ -0,0 +1,223 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 追踪
|
||||
|
||||
Agents SDK内置了追踪功能,会收集智能体运行期间事件的完整记录:LLM生成、工具调用、任务转移、安全防护措施,甚至包括发生的自定义事件。使用[Traces 控制面板](https://platform.openai.com/traces),你可以在开发期间和生产环境中调试、可视化并监控你的工作流。
|
||||
|
||||
!!!note
|
||||
|
||||
追踪默认启用。你可以通过三种常见方式将其禁用:
|
||||
|
||||
1. 你可以通过设置环境变量 `OPENAI_AGENTS_DISABLE_TRACING=1` 全局禁用追踪
|
||||
2. 你可以在代码中使用 [`set_tracing_disabled(True)`][agents.set_tracing_disabled] 全局禁用追踪
|
||||
3. 你可以将 [`agents.run.RunConfig.tracing_disabled`][] 设置为 `True`,为单次运行禁用追踪
|
||||
|
||||
***对于使用OpenAI的 API 且遵循零数据保留(ZDR)政策的组织,追踪不可用。***
|
||||
|
||||
## 追踪与 Span
|
||||
|
||||
- **追踪**表示一个“工作流”的单次端到端操作。它们由 Span 组成。追踪具有以下属性:
|
||||
- `workflow_name`:这是逻辑工作流或应用。例如“代码生成”或“客户服务”。
|
||||
- `trace_id`:追踪的唯一 ID。如果你不传入,则会自动生成。格式必须为 `trace_<32_alphanumeric>`。
|
||||
- `group_id`:可选的组 ID,用于关联来自同一对话的多个追踪。例如,你可以使用聊天线程 ID。
|
||||
- `disabled`:如果为 True,则不会记录该追踪。
|
||||
- `metadata`:追踪的可选元数据。
|
||||
- **Span**表示具有开始时间和结束时间的操作。Span 具有:
|
||||
- `started_at` 和 `ended_at` 时间戳。
|
||||
- `trace_id`,用于表示它们所属的追踪
|
||||
- `parent_id`,指向此 Span 的父级 Span(如果有)
|
||||
- `span_data`,即关于该 Span 的信息。例如,`AgentSpanData` 包含有关智能体的信息,`GenerationSpanData` 包含有关LLM生成的信息,等等。
|
||||
|
||||
## 默认追踪
|
||||
|
||||
默认情况下,SDK会追踪以下内容:
|
||||
|
||||
- 整个 `Runner.{run, run_sync, run_streamed}()` 会被封装在 `trace()` 中。
|
||||
- 每次智能体运行时,都会被封装在 `agent_span()` 中
|
||||
- LLM生成会被封装在 `generation_span()` 中
|
||||
- 每个工具调用都会被封装在 `function_span()` 中
|
||||
- 安全防护措施会被封装在 `guardrail_span()` 中
|
||||
- 任务转移会被封装在 `handoff_span()` 中
|
||||
- 音频输入(语音转文本)会被封装在 `transcription_span()` 中
|
||||
- 音频输出(文本转语音)会被封装在 `speech_span()` 中
|
||||
- 相关音频 Span 可以作为子级归属于 `speech_group_span()`
|
||||
|
||||
默认情况下,追踪命名为“Agent workflow”。如果使用 `trace`,你可以设置此名称;也可以使用 [`RunConfig`][agents.run.RunConfig] 配置名称和其他属性。
|
||||
|
||||
此外,你还可以设置[自定义追踪进程](#custom-tracing-processors),将追踪推送到其他目标(作为替代目标或第二目标)。
|
||||
|
||||
## 长时间运行的 worker 与即时导出
|
||||
|
||||
默认的 [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor] 会每隔几秒在后台导出追踪,或者当内存队列达到其大小触发阈值时更早导出,并且还会在进程退出时执行最终刷新。在 Celery、RQ、Dramatiq 或 FastAPI 后台任务等长时间运行的 worker 中,这意味着追踪通常无需任何额外代码就会自动导出,但它们可能不会在每个任务完成后立即出现在 Traces 控制面板中。
|
||||
|
||||
如果你需要在一个工作单元结束时保证即时交付,请在追踪上下文退出后调用 [`flush_traces()`][agents.tracing.flush_traces]。
|
||||
|
||||
```python
|
||||
from agents import Runner, flush_traces, trace
|
||||
|
||||
|
||||
@celery_app.task
|
||||
def run_agent_task(prompt: str):
|
||||
try:
|
||||
with trace("celery_task"):
|
||||
result = Runner.run_sync(agent, prompt)
|
||||
return result.final_output
|
||||
finally:
|
||||
flush_traces()
|
||||
```
|
||||
|
||||
```python
|
||||
from fastapi import BackgroundTasks, FastAPI
|
||||
from agents import Runner, flush_traces, trace
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
def process_in_background(prompt: str) -> None:
|
||||
try:
|
||||
with trace("background_job"):
|
||||
Runner.run_sync(agent, prompt)
|
||||
finally:
|
||||
flush_traces()
|
||||
|
||||
|
||||
@app.post("/run")
|
||||
async def run(prompt: str, background_tasks: BackgroundTasks):
|
||||
background_tasks.add_task(process_in_background, prompt)
|
||||
return {"status": "queued"}
|
||||
```
|
||||
|
||||
[`flush_traces()`][agents.tracing.flush_traces] 会阻塞,直到当前已缓冲的追踪和 Span 都被导出;因此请在 `trace()` 关闭后调用它,以避免刷新尚未构建完成的追踪。当默认导出延迟可以接受时,你可以跳过此调用。
|
||||
|
||||
## 更高层级的追踪
|
||||
|
||||
有时,你可能希望多次调用 `run()` 都属于同一个追踪。你可以通过将整个代码封装在一个 `trace()` 中来实现。
|
||||
|
||||
```python
|
||||
from agents import Agent, Runner, trace
|
||||
|
||||
async def main():
|
||||
agent = Agent(name="Joke generator", instructions="Tell funny jokes.")
|
||||
|
||||
with trace("Joke workflow"): # (1)!
|
||||
first_result = await Runner.run(agent, "Tell me a joke")
|
||||
second_result = await Runner.run(agent, f"Rate this joke: {first_result.final_output}")
|
||||
print(f"Joke: {first_result.final_output}")
|
||||
print(f"Rating: {second_result.final_output}")
|
||||
```
|
||||
|
||||
1. 由于对 `Runner.run` 的两次调用都封装在 `with trace()` 中,因此各次运行将成为整体追踪的一部分,而不是创建两个追踪。
|
||||
|
||||
## 追踪创建
|
||||
|
||||
你可以使用 [`trace()`][agents.tracing.trace] 函数创建追踪。追踪需要启动和结束。你有两种方式可以做到这一点:
|
||||
|
||||
1. **推荐**:将追踪用作上下文管理器,即 `with trace(...) as my_trace`。这会在正确的时间自动开始和结束追踪。
|
||||
2. 你也可以手动调用 [`trace.start()`][agents.tracing.Trace.start] 和 [`trace.finish()`][agents.tracing.Trace.finish]。
|
||||
|
||||
当前追踪通过 Python [`contextvar`](https://docs.python.org/3/library/contextvars.html) 进行跟踪。这意味着它可自动适配并发。如果你手动开始/结束追踪,则需要将 `mark_as_current` 和 `reset_current` 传递给 `start()`/`finish()`,以更新当前追踪。
|
||||
|
||||
## Span 创建
|
||||
|
||||
你可以使用各种 [`*_span()`][agents.tracing.create] 方法来创建 Span。通常,你不需要手动创建 Span。可使用 [`custom_span()`][agents.tracing.custom_span] 函数来跟踪自定义 Span 信息。
|
||||
|
||||
Span 会自动成为当前追踪的一部分,并嵌套在最近的当前 Span 之下;当前 Span 通过 Python [`contextvar`](https://docs.python.org/3/library/contextvars.html) 跟踪。
|
||||
|
||||
## 敏感数据
|
||||
|
||||
某些 Span 可能会捕获潜在敏感数据。
|
||||
|
||||
`generation_span()` 会存储LLM生成的输入/输出,`function_span()` 会存储函数调用的输入/输出。这些内容可能包含敏感数据,因此你可以通过 [`RunConfig.trace_include_sensitive_data`][agents.run.RunConfig.trace_include_sensitive_data] 禁用对此类数据的捕获。
|
||||
|
||||
同样,默认情况下,音频 Span 会包含输入和输出音频的 base64 编码 PCM 数据。你可以通过配置 [`VoicePipelineConfig.trace_include_sensitive_audio_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_audio_data] 来禁用对此音频数据的捕获。
|
||||
|
||||
默认情况下,`trace_include_sensitive_data` 为 `True`。你可以在运行应用之前将 `OPENAI_AGENTS_TRACE_INCLUDE_SENSITIVE_DATA` 环境变量导出为 `true/1` 或 `false/0`,以在不修改代码的情况下设置默认值。
|
||||
|
||||
## 自定义追踪进程
|
||||
|
||||
追踪的高层架构如下:
|
||||
|
||||
- 初始化时,我们会创建一个全局 [`TraceProvider`][agents.tracing.setup.TraceProvider],负责创建追踪。
|
||||
- 我们使用 [`BatchTraceProcessor`][agents.tracing.processors.BatchTraceProcessor] 配置 `TraceProvider`,它会将追踪/Span 分批发送到 [`BackendSpanExporter`][agents.tracing.processors.BackendSpanExporter],后者会将 Span 和追踪分批导出到OpenAI后端。
|
||||
|
||||
要自定义此默认设置,以将追踪发送到替代后端或额外后端,或修改导出器行为,你有两个选择:
|
||||
|
||||
1. [`add_trace_processor()`][agents.tracing.add_trace_processor] 允许你添加一个**额外的**追踪进程,该进程会在追踪和 Span 准备就绪时接收它们。这样你就可以在将追踪发送到OpenAI后端之外,执行自己的处理。
|
||||
2. [`set_trace_processors()`][agents.tracing.set_trace_processors] 允许你使用自己的追踪进程**替换**默认进程。这意味着,除非你包含一个执行此操作的 `TracingProcessor`,否则追踪不会发送到OpenAI后端。
|
||||
|
||||
|
||||
## 非OpenAI模型追踪
|
||||
|
||||
你可以将 OpenAI API 密钥与非OpenAI模型一起使用,以在 OpenAI Traces 控制面板中启用免费追踪,而无需禁用追踪。有关适配器选择和设置注意事项,请参阅 Models 指南中的[第三方适配器](models/index.md#third-party-adapters)部分。
|
||||
|
||||
```python
|
||||
import os
|
||||
from agents import set_tracing_export_api_key, Agent, Runner
|
||||
from agents.extensions.models.any_llm_model import AnyLLMModel
|
||||
|
||||
tracing_api_key = os.environ["OPENAI_API_KEY"]
|
||||
set_tracing_export_api_key(tracing_api_key)
|
||||
|
||||
model = AnyLLMModel(
|
||||
model="your-provider/your-model-name",
|
||||
api_key="your-api-key",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
model=model,
|
||||
)
|
||||
```
|
||||
|
||||
如果你只需要为单次运行使用不同的追踪密钥,请通过 `RunConfig` 传入,而不是更改全局导出器。
|
||||
|
||||
```python
|
||||
from agents import Runner, RunConfig
|
||||
|
||||
await Runner.run(
|
||||
agent,
|
||||
input="Hello",
|
||||
run_config=RunConfig(tracing={"api_key": "sk-tracing-123"}),
|
||||
)
|
||||
```
|
||||
|
||||
## 补充说明
|
||||
- 在 OpenAI Traces 控制面板查看免费追踪。
|
||||
|
||||
|
||||
## 生态系统集成
|
||||
|
||||
以下社区和供应商集成支持 OpenAI Agents SDK的追踪接口面。
|
||||
|
||||
### 外部追踪进程列表
|
||||
|
||||
- [Weights & Biases](https://weave-docs.wandb.ai/guides/integrations/openai_agents)
|
||||
- [Arize-Phoenix](https://docs.arize.com/phoenix/tracing/integrations-tracing/openai-agents-sdk)
|
||||
- [Future AGI](https://docs.futureagi.com/future-agi/products/observability/auto-instrumentation/openai_agents)
|
||||
- [MLflow(自托管/OSS)](https://mlflow.org/docs/latest/tracing/integrations/openai-agent)
|
||||
- [MLflow(Databricks 托管)](https://docs.databricks.com/aws/en/mlflow/mlflow-tracing#-automatic-tracing)
|
||||
- [Braintrust](https://braintrust.dev/docs/guides/traces/integrations#openai-agents-sdk)
|
||||
- [Pydantic Logfire](https://logfire.pydantic.dev/docs/integrations/llms/openai/#openai-agents)
|
||||
- [AgentOps](https://docs.agentops.ai/v1/integrations/agentssdk)
|
||||
- [Scorecard](https://docs.scorecard.io/docs/documentation/features/tracing#openai-agents-sdk-integration)
|
||||
- [Respan](https://respan.ai/docs/integrations/tracing/openai-agents-sdk)
|
||||
- [LangSmith](https://docs.smith.langchain.com/observability/how_to_guides/trace_with_openai_agents_sdk)
|
||||
- [Maxim AI](https://www.getmaxim.ai/docs/observe/integrations/openai-agents-sdk)
|
||||
- [Comet Opik](https://www.comet.com/docs/opik/tracing/integrations/openai_agents)
|
||||
- [Langfuse](https://langfuse.com/docs/integrations/openaiagentssdk/openai-agents)
|
||||
- [Langtrace](https://docs.langtrace.ai/supported-integrations/llm-frameworks/openai-agents-sdk)
|
||||
- [Okahu-Monocle](https://github.com/monocle2ai/monocle)
|
||||
- [Galileo](https://v2docs.galileo.ai/integrations/openai-agent-integration#openai-agent-integration)
|
||||
- [Portkey AI](https://portkey.ai/docs/integrations/agents/openai-agents)
|
||||
- [LangDB AI](https://docs.langdb.ai/getting-started/working-with-agent-frameworks/working-with-openai-agents-sdk)
|
||||
- [Agenta](https://docs.agenta.ai/observability/integrations/openai-agents)
|
||||
- [PostHog](https://posthog.com/docs/llm-analytics/installation/openai-agents)
|
||||
- [Traccia](https://traccia.ai/docs/integrations/openai-agents)
|
||||
- [PromptLayer](https://docs.promptlayer.com/languages/integrations#openai-agents-sdk)
|
||||
- [HoneyHive](https://docs.honeyhive.ai/v2/integrations/openai-agents)
|
||||
- [Asqav](https://www.asqav.com/docs/integrations#openai-agents)
|
||||
- [Datadog](https://docs.datadoghq.com/llm_observability/instrumentation/auto_instrumentation/?tab=python#openai-agents)
|
||||
- [Latitude](https://docs.latitude.so/telemetry/frameworks/openai-agents)
|
||||
- [DProvenanceKit](https://dprovenance.dev/openai-agents/)
|
||||
@@ -0,0 +1,90 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 使用量
|
||||
|
||||
Agents SDK会自动追踪每次运行的token使用量。您可以从运行上下文中访问它,并用它来监控成本、强制执行限制或记录分析数据。
|
||||
|
||||
## 追踪内容
|
||||
|
||||
- **requests**: 发起的LLM API调用次数
|
||||
- **input_tokens**: 发送的输入token总数
|
||||
- **output_tokens**: 接收的输出token总数
|
||||
- **total_tokens**: 输入 + 输出
|
||||
- **request_usage_entries**: 每个请求的使用量明细列表
|
||||
- **details**:
|
||||
- `input_tokens_details.cached_tokens`
|
||||
- `output_tokens_details.reasoning_tokens`
|
||||
|
||||
## 运行中的使用量访问
|
||||
|
||||
在`Runner.run(...)`之后,通过`result.context_wrapper.usage`访问使用量。
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
usage = result.context_wrapper.usage
|
||||
|
||||
print("Requests:", usage.requests)
|
||||
print("Input tokens:", usage.input_tokens)
|
||||
print("Output tokens:", usage.output_tokens)
|
||||
print("Total tokens:", usage.total_tokens)
|
||||
```
|
||||
|
||||
使用量会在运行期间的所有模型调用中汇总(包括工具调用和任务转移)。
|
||||
|
||||
### 第三方适配器的使用量启用
|
||||
|
||||
使用量报告会因第三方适配器和提供商后端而异。如果您依赖由适配器支持的模型,并且需要准确的`result.context_wrapper.usage`值:
|
||||
|
||||
- 使用`AnyLLMModel`时,当上游提供商返回使用量数据时,使用量会自动传递。对于流式传输的Chat Completions后端,可能需要设置`ModelSettings(include_usage=True)`后才会发出使用量数据块。
|
||||
- 使用`LitellmModel`时,某些提供商后端默认不报告使用量,因此通常需要`ModelSettings(include_usage=True)`。
|
||||
|
||||
请查看模型指南中[第三方适配器](models/index.md#third-party-adapters)部分的适配器特定说明,并验证您计划部署的具体提供商后端。
|
||||
|
||||
## 按请求的使用量追踪
|
||||
|
||||
SDK会在`request_usage_entries`中自动追踪每个API请求的使用量,可用于详细的成本计算和监控上下文窗口消耗。
|
||||
|
||||
```python
|
||||
result = await Runner.run(agent, "What's the weather in Tokyo?")
|
||||
|
||||
for i, request in enumerate(result.context_wrapper.usage.request_usage_entries):
|
||||
print(f"Request {i + 1}: {request.input_tokens} in, {request.output_tokens} out")
|
||||
```
|
||||
|
||||
## 会话中的使用量访问
|
||||
|
||||
当使用`Session`(例如`SQLiteSession`)时,每次调用`Runner.run(...)`都会返回该特定运行的使用量。会话会维护用于上下文的对话历史,但每次运行的使用量都是独立的。
|
||||
|
||||
```python
|
||||
session = SQLiteSession("my_conversation")
|
||||
|
||||
first = await Runner.run(agent, "Hi!", session=session)
|
||||
print(first.context_wrapper.usage.total_tokens) # Usage for first run
|
||||
|
||||
second = await Runner.run(agent, "Can you elaborate?", session=session)
|
||||
print(second.context_wrapper.usage.total_tokens) # Usage for second run
|
||||
```
|
||||
|
||||
请注意,尽管会话会在运行之间保留对话上下文,但每次`Runner.run()`调用返回的使用量指标仅代表该特定执行。在会话中,之前的消息可能会作为输入重新提供给每次运行,这会影响后续轮次中的输入token数量。
|
||||
|
||||
## 钩子中的使用量访问
|
||||
|
||||
如果您使用`RunHooks`,传递给每个钩子的`context`对象都包含`usage`。这使您能够在关键生命周期时刻记录使用量。
|
||||
|
||||
```python
|
||||
class MyHooks(RunHooks):
|
||||
async def on_agent_end(self, context: RunContextWrapper, agent: Agent, output: Any) -> None:
|
||||
u = context.usage
|
||||
print(f"{agent.name} → {u.requests} requests, {u.total_tokens} total tokens")
|
||||
```
|
||||
|
||||
## API参考
|
||||
|
||||
有关详细的API文档,请参阅:
|
||||
|
||||
- [`Usage`][agents.usage.Usage] - 使用量追踪数据结构
|
||||
- [`RequestUsage`][agents.usage.RequestUsage] - 每个请求的使用量详情
|
||||
- [`RunContextWrapper`][agents.run.RunContextWrapper] - 从运行上下文访问使用量
|
||||
- [`RunHooks`][agents.run.RunHooks] - 接入使用量追踪生命周期
|
||||
@@ -0,0 +1,107 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 智能体可视化
|
||||
|
||||
智能体可视化允许你使用 **Graphviz** 生成智能体及其关系的结构化图形表示。这有助于理解智能体、工具和任务转移在应用程序中的交互方式。
|
||||
|
||||
## 安装
|
||||
|
||||
安装可选的 `viz` 依赖组:
|
||||
|
||||
```bash
|
||||
pip install "openai-agents[viz]"
|
||||
```
|
||||
|
||||
## 图的生成
|
||||
|
||||
你可以使用 `draw_graph` 函数生成智能体可视化图。此函数会创建一个有向图,其中:
|
||||
|
||||
- **智能体**表示为黄色方框。
|
||||
- **MCP 服务**表示为灰色方框。
|
||||
- **工具**表示为绿色椭圆。
|
||||
- **任务转移**表示为从一个智能体指向另一个智能体的有向边。
|
||||
|
||||
### 用法示例
|
||||
|
||||
```python
|
||||
import os
|
||||
|
||||
from agents import Agent, function_tool
|
||||
from agents.mcp.server import MCPServerStdio
|
||||
from agents.extensions.visualization import draw_graph
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
return f"The weather in {city} is sunny."
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish agent",
|
||||
instructions="You only speak Spanish.",
|
||||
)
|
||||
|
||||
english_agent = Agent(
|
||||
name="English agent",
|
||||
instructions="You only speak English",
|
||||
)
|
||||
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
samples_dir = os.path.join(current_dir, "sample_files")
|
||||
mcp_server = MCPServerStdio(
|
||||
name="Filesystem Server, via npx",
|
||||
params={
|
||||
"command": "npx",
|
||||
"args": ["-y", "@modelcontextprotocol/server-filesystem", samples_dir],
|
||||
},
|
||||
)
|
||||
|
||||
triage_agent = Agent(
|
||||
name="Triage agent",
|
||||
instructions="Handoff to the appropriate agent based on the language of the request.",
|
||||
handoffs=[spanish_agent, english_agent],
|
||||
tools=[get_weather],
|
||||
mcp_servers=[mcp_server],
|
||||
)
|
||||
|
||||
draw_graph(triage_agent)
|
||||
```
|
||||
|
||||

|
||||
|
||||
这会生成一张图,用于直观表示**分诊智能体**的结构,以及它与子智能体和工具的连接关系。
|
||||
|
||||
|
||||
## 可视化结果解析
|
||||
|
||||
生成的图包括:
|
||||
|
||||
- 一个**起始节点**(`__start__`),表示入口点。
|
||||
- 以黄色填充的**矩形**表示智能体。
|
||||
- 以绿色填充的**椭圆**表示工具。
|
||||
- 以灰色填充的**矩形**表示 MCP 服务。
|
||||
- 表示交互的有向边:
|
||||
- **实线箭头**表示智能体到智能体的任务转移。
|
||||
- **点线箭头**表示工具调用。
|
||||
- **虚线箭头**表示 MCP 服务调用。
|
||||
- 一个**结束节点**(`__end__`),表示执行终止的位置。
|
||||
|
||||
**注意:**MCP 服务会在较新版本的 `agents` 包中渲染(已在 **v0.2.8** 中验证)。如果你在可视化中没有看到 MCP 方框,请升级到最新版本。
|
||||
|
||||
## 图的自定义
|
||||
|
||||
### 图的显示
|
||||
默认情况下,`draw_graph` 会内联显示图。要在单独的窗口中显示图,请编写以下内容:
|
||||
|
||||
```python
|
||||
draw_graph(triage_agent).view()
|
||||
```
|
||||
|
||||
### 图的保存
|
||||
默认情况下,`draw_graph` 会内联显示图。要将其保存为文件,请指定文件名:
|
||||
|
||||
```python
|
||||
draw_graph(triage_agent, filename="agent_graph")
|
||||
```
|
||||
|
||||
这将在工作目录中生成 `agent_graph.png`。
|
||||
@@ -0,0 +1,81 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 管道和工作流
|
||||
|
||||
[`VoicePipeline`][agents.voice.pipeline.VoicePipeline] 是一个类,可让你轻松将智能体式工作流转换为语音应用。你传入要运行的工作流,管道会负责转写输入音频、检测音频何时结束、在合适的时机调用你的工作流,并将工作流输出转换回音频。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
%% Input
|
||||
A["🎤 Audio Input"]
|
||||
|
||||
%% Voice Pipeline
|
||||
subgraph Voice_Pipeline [Voice Pipeline]
|
||||
direction TB
|
||||
B["Transcribe (speech-to-text)"]
|
||||
C["Your Code"]:::highlight
|
||||
D["Text-to-speech"]
|
||||
B --> C --> D
|
||||
end
|
||||
|
||||
%% Output
|
||||
E["🎧 Audio Output"]
|
||||
|
||||
%% Flow
|
||||
A --> Voice_Pipeline
|
||||
Voice_Pipeline --> E
|
||||
|
||||
%% Custom styling
|
||||
classDef highlight fill:#ffcc66,stroke:#333,stroke-width:1px,font-weight:700;
|
||||
|
||||
```
|
||||
|
||||
## 管道配置
|
||||
|
||||
创建管道时,你可以设置以下几项:
|
||||
|
||||
1. [`workflow`][agents.voice.workflow.VoiceWorkflowBase],即每当有新的音频被转写时运行的代码。
|
||||
2. 使用的 [`speech-to-text`][agents.voice.model.STTModel] 和 [`text-to-speech`][agents.voice.model.TTSModel] 模型
|
||||
3. [`config`][agents.voice.pipeline_config.VoicePipelineConfig],可用于配置以下内容:
|
||||
- 模型提供方,可将模型名称映射到模型
|
||||
- 追踪,包括是否禁用追踪、是否上传音频文件、工作流名称、追踪 ID 等。
|
||||
- TTS 和 STT 模型的设置,例如提示词、语言以及使用的数据类型。
|
||||
|
||||
## 管道运行
|
||||
|
||||
你可以通过 [`run()`][agents.voice.pipeline.VoicePipeline.run] 方法运行管道,该方法允许你以两种形式传入音频输入:
|
||||
|
||||
1. 当你已有完整的音频输入,并且只想为其生成结果时,可以使用 [`AudioInput`][agents.voice.input.AudioInput]。这适用于不需要检测说话者何时说完的场景;例如,你有预录音频,或者在按键通话应用中,用户何时说完是明确的。
|
||||
2. 当你可能需要检测用户何时说完时,可以使用 [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput]。它允许你在检测到音频片段时将其推送进去,语音管道会通过一个称为“活动检测”的过程,在合适的时机自动运行智能体工作流。
|
||||
|
||||
## 结果
|
||||
|
||||
语音管道运行的结果是 [`StreamedAudioResult`][agents.voice.result.StreamedAudioResult]。这是一个对象,可让你在事件发生时以流式方式传输这些事件。[`VoiceStreamEvent`][agents.voice.events.VoiceStreamEvent] 有几种类型,包括:
|
||||
|
||||
1. [`VoiceStreamEventAudio`][agents.voice.events.VoiceStreamEventAudio],其中包含一个音频片段。
|
||||
2. [`VoiceStreamEventLifecycle`][agents.voice.events.VoiceStreamEventLifecycle],用于告知你轮次开始或结束等生命周期事件。
|
||||
3. [`VoiceStreamEventError`][agents.voice.events.VoiceStreamEventError],这是一种错误事件。
|
||||
|
||||
```python
|
||||
|
||||
result = await pipeline.run(input)
|
||||
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
# play audio
|
||||
pass
|
||||
elif event.type == "voice_stream_event_lifecycle":
|
||||
# lifecycle
|
||||
pass
|
||||
elif event.type == "voice_stream_event_error":
|
||||
# error
|
||||
pass
|
||||
```
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 中断
|
||||
|
||||
Agents SDK 目前没有为 [`StreamedAudioInput`][agents.voice.input.StreamedAudioInput] 提供任何内置的中断处理。相反,每个检测到的轮次都会触发你的工作流单独运行一次。如果你想在应用内处理中断,可以监听 [`VoiceStreamEventLifecycle`][agents.voice.events.VoiceStreamEventLifecycle] 事件。`turn_started` 表示已转写出新的轮次,且处理即将开始。`turn_ended` 会在相应轮次的所有音频都分发完毕后触发。你可以使用这些事件,在模型开始一个轮次时将说话者的麦克风静音,并在该轮次的所有相关音频都刷新完后取消静音。
|
||||
@@ -0,0 +1,198 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 快速入门
|
||||
|
||||
## 前置条件
|
||||
|
||||
请确保已按照 Agents SDK 的基础[快速入门说明](../quickstart.md)完成操作,并设置好虚拟环境。然后,从 SDK 安装可选的语音依赖项:
|
||||
|
||||
```bash
|
||||
pip install 'openai-agents[voice]'
|
||||
```
|
||||
|
||||
## 概念
|
||||
|
||||
需要了解的核心概念是 [`VoicePipeline`][agents.voice.pipeline.VoicePipeline],它包含以下三个步骤:
|
||||
|
||||
1. 运行语音转文本模型,将音频转换为文本。
|
||||
2. 运行你的代码(通常是智能体工作流)以生成结果。
|
||||
3. 运行文本转语音模型,将结果文本转换回音频。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
%% Input
|
||||
A["🎤 Audio Input"]
|
||||
|
||||
%% Voice Pipeline
|
||||
subgraph Voice_Pipeline [Voice Pipeline]
|
||||
direction TB
|
||||
B["Transcribe (speech-to-text)"]
|
||||
C["Your Code"]:::highlight
|
||||
D["Text-to-speech"]
|
||||
B --> C --> D
|
||||
end
|
||||
|
||||
%% Output
|
||||
E["🎧 Audio Output"]
|
||||
|
||||
%% Flow
|
||||
A --> Voice_Pipeline
|
||||
Voice_Pipeline --> E
|
||||
|
||||
%% Custom styling
|
||||
classDef highlight fill:#ffcc66,stroke:#333,stroke-width:1px,font-weight:700;
|
||||
|
||||
```
|
||||
|
||||
## 智能体
|
||||
|
||||
首先,我们来设置一些智能体。如果你曾使用此 SDK 构建过智能体,这部分应该会很熟悉。我们将使用两个智能体、一次任务转移和一个工具。
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
function_tool,
|
||||
)
|
||||
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
|
||||
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a given city."""
|
||||
print(f"[debug] get_weather called with city: {city}")
|
||||
choices = ["sunny", "cloudy", "rainy", "snowy"]
|
||||
return f"The weather in {city} is {random.choice(choices)}."
|
||||
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish",
|
||||
handoff_description="A Spanish-speaking agent.",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. Speak in Spanish.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. If the user speaks in Spanish, hand off to the Spanish agent.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
handoffs=[spanish_agent],
|
||||
tools=[get_weather],
|
||||
)
|
||||
```
|
||||
|
||||
## 语音管线
|
||||
|
||||
我们将设置一个简单的语音管线,并使用 [`SingleAgentVoiceWorkflow`][agents.voice.workflow.SingleAgentVoiceWorkflow] 作为工作流。
|
||||
|
||||
```python
|
||||
from agents.voice import SingleAgentVoiceWorkflow, VoicePipeline
|
||||
pipeline = VoicePipeline(workflow=SingleAgentVoiceWorkflow(agent))
|
||||
```
|
||||
|
||||
## 管线运行
|
||||
|
||||
```python
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
from agents.voice import AudioInput
|
||||
|
||||
# For simplicity, we'll just create 3 seconds of silence
|
||||
# In reality, you'd get microphone data
|
||||
buffer = np.zeros(24000 * 3, dtype=np.int16)
|
||||
audio_input = AudioInput(buffer=buffer)
|
||||
|
||||
result = await pipeline.run(audio_input)
|
||||
|
||||
# Create an audio player using `sounddevice`
|
||||
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
|
||||
player.start()
|
||||
|
||||
# Play the audio stream as it comes in
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
player.write(event.data)
|
||||
|
||||
```
|
||||
|
||||
## 完整整合
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import random
|
||||
|
||||
import numpy as np
|
||||
import sounddevice as sd
|
||||
|
||||
from agents import (
|
||||
Agent,
|
||||
function_tool,
|
||||
set_tracing_disabled,
|
||||
)
|
||||
from agents.voice import (
|
||||
AudioInput,
|
||||
SingleAgentVoiceWorkflow,
|
||||
VoicePipeline,
|
||||
)
|
||||
from agents.extensions.handoff_prompt import prompt_with_handoff_instructions
|
||||
|
||||
|
||||
@function_tool
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get the weather for a given city."""
|
||||
print(f"[debug] get_weather called with city: {city}")
|
||||
choices = ["sunny", "cloudy", "rainy", "snowy"]
|
||||
return f"The weather in {city} is {random.choice(choices)}."
|
||||
|
||||
|
||||
spanish_agent = Agent(
|
||||
name="Spanish",
|
||||
handoff_description="A Spanish-speaking agent.",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. Speak in Spanish.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
)
|
||||
|
||||
agent = Agent(
|
||||
name="Assistant",
|
||||
instructions=prompt_with_handoff_instructions(
|
||||
"You're speaking to a human, so be polite and concise. If the user speaks in Spanish, hand off to the Spanish agent.",
|
||||
),
|
||||
model="gpt-5.6-sol",
|
||||
handoffs=[spanish_agent],
|
||||
tools=[get_weather],
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
pipeline = VoicePipeline(workflow=SingleAgentVoiceWorkflow(agent))
|
||||
buffer = np.zeros(24000 * 3, dtype=np.int16)
|
||||
audio_input = AudioInput(buffer=buffer)
|
||||
|
||||
result = await pipeline.run(audio_input)
|
||||
|
||||
# Create an audio player using `sounddevice`
|
||||
player = sd.OutputStream(samplerate=24000, channels=1, dtype=np.int16)
|
||||
player.start()
|
||||
|
||||
# Play the audio stream as it comes in
|
||||
async for event in result.stream():
|
||||
if event.type == "voice_stream_event_audio":
|
||||
player.write(event.data)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
运行此示例后,智能体就会与你进行语音交流!请查看 [examples/voice/static](https://github.com/openai/openai-agents-python/tree/main/examples/voice/static) 中的示例,了解如何亲自与智能体进行语音交流。
|
||||
@@ -0,0 +1,18 @@
|
||||
---
|
||||
search:
|
||||
exclude: true
|
||||
---
|
||||
# 追踪
|
||||
|
||||
就像[智能体会被追踪](../tracing.md)一样,语音管线也会被自动追踪。
|
||||
|
||||
你可以阅读上面的追踪文档以了解基本的追踪信息;此外,你还可以通过[`VoicePipelineConfig`][agents.voice.pipeline_config.VoicePipelineConfig]配置管线的追踪。
|
||||
|
||||
与追踪相关的关键字段包括:
|
||||
|
||||
- [`tracing_disabled`][agents.voice.pipeline_config.VoicePipelineConfig.tracing_disabled]:控制是否禁用追踪。默认情况下,追踪处于启用状态。
|
||||
- [`trace_include_sensitive_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_data]:控制追踪是否包含可能敏感的数据,例如音频转录文本。这专门针对语音管线,不适用于你的工作流内部发生的任何事情。
|
||||
- [`trace_include_sensitive_audio_data`][agents.voice.pipeline_config.VoicePipelineConfig.trace_include_sensitive_audio_data]:控制追踪是否包含音频数据。
|
||||
- [`workflow_name`][agents.voice.pipeline_config.VoicePipelineConfig.workflow_name]:追踪工作流的名称。
|
||||
- [`group_id`][agents.voice.pipeline_config.VoicePipelineConfig.group_id]:追踪的`group_id`,可用于关联多个追踪。
|
||||
- [`trace_metadata`][agents.voice.pipeline_config.VoicePipelineConfig.trace_metadata]:要包含在追踪中的额外元数据。
|
||||
Reference in New Issue
Block a user