运行智能体
智能体本身不会做任何事——你需要使用 Runner 类或 run() 工具来运行它们。
当你想要执行轮次、流式接收事件或管理对话状态时,请在阅读完智能体后阅读本页。如果你还在决定如何定义智能体,请先从智能体开始。
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant',});
const result = await run( agent, 'Write a haiku about recursion in programming.',);console.log(result.finalOutput);
// Code within the code,// Functions calling themselves,// Infinite loop's dance.当你不需要自定义 runner 时,也可以使用 run() 工具,它会运行一个默认的单例 Runner 实例。
或者,你也可以创建自己的 runner 实例:
import { Agent, Runner } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'You are a helpful assistant',});
// You can pass custom configuration to the runnerconst runner = new Runner();
const result = await runner.run( agent, 'Write a haiku about recursion in programming.',);console.log(result.finalOutput);
// Code within the code,// Functions calling themselves,// Infinite loop's dance.运行智能体后,你会收到一个执行结果对象,其中包含最终输出和本次运行的完整历史。
Runner 生命周期与配置
Section titled “Runner 生命周期与配置”当你在 Runner 中使用 run 方法时,需要传入一个起始智能体和输入。输入可以是字符串(会被视为一条用户消息),也可以是输入项列表,即 OpenAI Responses API 中的 items。
随后 runner 会运行一个循环:
- 使用当前输入调用当前智能体的模型。
- 检查 LLM 响应。
- 最终输出 → 返回。
- 交接 → 切换到新智能体,保留已累计的对话历史,转到 1。
- 工具调用 → 执行工具,将结果追加到对话中,转到 1。
- 一旦达到
maxTurns,抛出MaxTurnsExceededError。
Runner 生命周期
Section titled “Runner 生命周期”在应用启动时创建一个 Runner,并在多个请求间复用它。该实例会保存全局配置,例如模型提供方和追踪选项。只有在你需要完全不同的配置时,才应创建另一个 Runner。对于简单脚本,也可以直接调用 run(),它内部会使用默认 runner。
run() 方法的输入包括:用于启动运行的初始智能体、运行输入以及一组选项。
输入可以是字符串(会被视为一条用户消息),也可以是输入项列表;如果你在构建人机协作智能体,也可以传入 RunState 对象。
额外选项如下:
| 选项 | 默认值 | 说明 |
|---|---|---|
stream | false | 若为 true,调用将返回 StreamedRunResult,并在模型返回事件时持续发出。 |
context | – | 传递给每个工具 / 护栏 / 交接的上下文对象。详见上下文管理指南。 |
maxTurns | 10 | 安全限制——达到后抛出 MaxTurnsExceededError。 |
signal | – | 用于取消的 AbortSignal。 |
session | – | 会话持久化实现。参见会话指南。 |
sessionInputCallback | – | 用于合并会话历史与新输入的自定义逻辑;在模型调用前运行。参见会话。 |
callModelInputFilter | – | 在调用模型前编辑模型输入(items + 可选 instructions)的钩子。参见调用模型输入过滤器。 |
toolErrorFormatter | – | 自定义返回给模型的工具审批拒绝消息的钩子。参见工具错误格式化器。 |
reasoningItemIdPolicy | – | 控制将先前运行项转换回模型输入时,是否保留 reasoning-item 的 id。参见Reasoning item ID 策略。 |
tracing | – | 按次运行覆盖追踪配置(例如导出 API 密钥)。 |
errorHandlers | – | 受支持运行时错误的处理器(当前为 maxTurns)。参见错误处理器。 |
conversationId | – | 复用服务端对话(仅 OpenAI Responses API + Conversations API)。 |
previousResponseId | – | 在不创建对话的情况下,从上一次 Responses API 调用继续(仅 OpenAI Responses API)。 |
流式传输允许你在 LLM 运行时额外接收流式事件。一旦流开始,StreamedRunResult 将包含本次运行的完整信息,包括所有新产生的输出。你可以使用 for await 循环遍历流式事件。更多内容见流式传输指南。
如果你要创建自己的 Runner 实例,可以传入 RunConfig 对象来配置 runner。
| 字段 | 类型 | 用途 |
|---|---|---|
model | string | Model | 为本次运行中的所有智能体强制指定模型。 |
modelProvider | ModelProvider | 解析模型名称——默认为 OpenAI provider。 |
modelSettings | ModelSettings | 覆盖每个智能体设置的全局调优参数。详见模型指南,包括可选的重试配置。 |
handoffInputFilter | HandoffInputFilter | 执行交接时修改输入项(前提是交接本身未定义)。 |
inputGuardrails | InputGuardrail[] | 应用于初始用户输入的护栏。 |
outputGuardrails | OutputGuardrail[] | 应用于最终输出的护栏。 |
tracingDisabled | boolean | 完全禁用 OpenAI 追踪。 |
traceIncludeSensitiveData | boolean | 在仍发出 span 的同时,排除追踪中的 LLM/工具输入与输出。 |
workflowName | string | 显示在 Traces 仪表盘中——用于分组相关运行。 |
traceId / groupId | string | 手动指定 trace 或 group ID,而不是由 SDK 自动生成。 |
traceMetadata | Record<string, string> | 附加到每个 span 的任意元数据。 |
tracing | TracingConfig | 按次运行覆盖追踪配置(例如导出 API 密钥)。 |
sessionInputCallback | SessionInputCallback | 此 runner 上所有运行的默认历史合并策略。 |
callModelInputFilter | CallModelInputFilter | 每次模型调用前编辑模型输入的全局钩子。 |
toolErrorFormatter | ToolErrorFormatter | 自定义返回给模型的工具审批拒绝消息的全局钩子。 |
reasoningItemIdPolicy | ReasoningItemIdPolicy | 在将已生成项回放到后续模型调用时,默认保留或省略 reasoning-item id 的策略。 |
状态与对话管理
Section titled “状态与对话管理”内存策略选择
Section titled “内存策略选择”将状态带入下一轮有四种常见方式:
| 策略 | 状态存放位置 | 最适用场景 | 下一轮传入内容 |
|---|---|---|---|
result.history | 你的应用内存 | 小型聊天循环、完全手动控制、任意 provider | result.history |
session | 你的存储 + SDK | 持久聊天状态、可恢复运行、自定义存储 | 同一个 session 实例(或基于存储的实例) |
conversationId | OpenAI Conversations API | 在多个 worker/服务间共享服务端状态 | 同一个 conversationId + 仅新的用户轮次 |
previousResponseId | 仅 OpenAI Responses API | 不创建对话时最简单的服务端托管续接 | result.lastResponseId + 仅新的用户轮次 |
result.history 和 session 由客户端管理。conversationId 和 previousResponseId 由 OpenAI 管理,且仅在使用 OpenAI Responses API 时适用。在大多数应用中,每个对话选择一种持久化策略即可。除非你有意协调这两层,否则混用客户端管理历史与服务端管理状态会导致上下文重复。
对话 / 聊天线程
Section titled “对话 / 聊天线程”在你的应用层对话中,每次调用 runner.run()(或 run() 工具)都表示一个轮次。你可以决定向终端用户展示 RunResult 中多少内容——有时只展示 finalOutput,有时展示所有生成项。
import { Agent, run } from '@openai/agents';import type { AgentInputItem } from '@openai/agents';
let thread: AgentInputItem[] = [];
const agent = new Agent({ name: 'Assistant',});
async function userSays(text: string) { const result = await run( agent, thread.concat({ role: 'user', content: text }), );
thread = result.history; // Carry over history + newly generated items return result.finalOutput;}
await userSays('What city is the Golden Gate Bridge in?');// -> "San Francisco"
await userSays('What state is it in?');// -> "California"交互版本请参见聊天示例。
服务端托管对话
Section titled “服务端托管对话”你可以让 OpenAI Responses API 为你持久化对话历史,而不是每轮都发送完整本地历史。当你在协调长对话或多个服务时,这非常有用。使用下方任一服务端托管方式时,每次请求只需传入新轮次输入。API 会为你复用先前状态。详见对话状态指南。
OpenAI 提供两种复用服务端状态的方式:
1. 使用 conversationId 管理整个对话
Section titled “1. 使用 conversationId 管理整个对话”你可以先通过 Conversations API 创建一次对话,然后在每个轮次复用其 ID。SDK 会自动仅包含新生成的项。
import { Agent, run } from '@openai/agents';import { OpenAI } from 'openai';
const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',});
async function main() { // Create a server-managed conversation: const client = new OpenAI(); const { id: conversationId } = await client.conversations.create({});
const first = await run(agent, 'What city is the Golden Gate Bridge in?', { conversationId, }); console.log(first.finalOutput); // -> "San Francisco"
const second = await run(agent, 'What state is it in?', { conversationId }); console.log(second.finalOutput); // -> "California"}
main().catch(console.error);2. 使用 previousResponseId 从上一轮继续
Section titled “2. 使用 previousResponseId 从上一轮继续”如果你本就只想从 Responses API 开始,也可以使用上一条响应返回的 ID 串联每个请求。这样无需创建完整对话资源,也能在轮次间保持上下文。
import { Agent, run } from '@openai/agents';
const agent = new Agent({ name: 'Assistant', instructions: 'Reply very concisely.',});
async function main() { const first = await run(agent, 'What city is the Golden Gate Bridge in?'); console.log(first.finalOutput); // -> "San Francisco"
const previousResponseId = first.lastResponseId; const second = await run(agent, 'What state is it in?', { previousResponseId, }); console.log(second.finalOutput); // -> "California"}
main().catch(console.error);conversationId 与 previousResponseId 互斥。若你希望拥有可跨系统共享的具名对话资源,请使用 conversationId;若你只想在响应之间使用最轻量、最低成本的 SDK 级续接原语,请使用 previousResponseId。
钩子与自定义
Section titled “钩子与自定义”调用模型输入过滤器
Section titled “调用模型输入过滤器”使用 callModelInputFilter 可在调用模型之前编辑模型输入。该钩子会接收当前智能体、上下文以及合并后的输入项(包含会话历史,如果存在)。你可以返回更新后的 input 数组和可选 instructions,用于脱敏、丢弃旧消息或注入额外系统引导。
可按次运行在 runner.run(..., { callModelInputFilter }) 中设置,也可在 Runner 配置中设为默认值(RunConfig 中的 callModelInputFilter)。
返回值必须是 ModelInputData 对象:{ input: AgentInputItem[], instructions? }。input 字段必填且必须是数组。返回其他结构会抛出 UserError。
SDK 会在调用过滤器前克隆已准备好的当前轮次输入。如果你同时使用了 session,被过滤后的克隆内容会被持久化,因此这里执行的脱敏或截断也会反映到存储的会话历史中。
在使用 conversationId 或 previousResponseId 时,该钩子会作用于下一次 Responses API 调用的已准备 payload。之前由服务端管理的上下文会由 API 恢复,因此该次调用的过滤数组可能已经只是新轮次增量,而非完整历史回放。若你需要在最终过滤步骤前改变“存储历史 + 当前轮次”的合并方式,请使用 sessionInputCallback。
工具错误格式化器
Section titled “工具错误格式化器”使用 toolErrorFormatter 可自定义当工具调用被拒绝时回传给模型的审批拒绝消息。这样你可以返回领域特定措辞(例如合规指引),而不是使用 SDK 默认消息。
该格式化器可按次运行设置(runner.run(..., { toolErrorFormatter })),也可在 RunConfig 中全局设置(new Runner(...) 里的 toolErrorFormatter)。
该格式化器是审批拒绝场景的全局兜底。如果你使用 result.state.reject(interruption, { message: '...' }) 拒绝某次特定中断,则该次调用的 message 优先级高于 toolErrorFormatter。若两者都未提供,SDK 将回退到默认拒绝文本:Tool execution was not approved.
当前格式化器会在 approval_rejected 事件触发,接收:
kind(当前始终为'approval_rejected')toolType('function'、'computer'、'shell'或'apply_patch')toolNamecallIddefaultMessage(SDK 兜底消息,当前为Tool execution was not approved.)runContext
返回字符串可覆盖消息;返回 undefined 则保留 SDK 默认值。若格式化器抛错(或返回非字符串值),SDK 会记录警告并回退到默认审批拒绝消息。
Reasoning item ID 策略
Section titled “Reasoning item ID 策略”使用 reasoningItemIdPolicy 可控制当 SDK 将之前生成的运行项转换回 AgentInputItem[] 供后续模型输入时,reasoning items 是否保留其 id 字段。
这会影响 SDK 将生成的模型项作为输入回放的场景,例如:
- 同一次运行内的后续模型调用(例如工具执行后);
- 后续轮次复用生成项作为输入/历史;
- 从保存的
RunState恢复运行; - 派生结果视图,如
result.history/result.output(它们是模型输入形态的数组)。 'preserve'(默认)保留 reasoning-item IDs。'omit'在作为输入回传前移除 reasoning items 的id字段。- 非 reasoning items 不受影响。
该策略不会改变:
- 原始模型响应(
result.rawResponses); - 运行项(
result.newItems); - provider 返回的模型当前轮次输出。
换句话说,该策略作用于 SDK 根据先前生成项构建下一次输入时。
你可以按次运行设置该策略(runner.run(..., { reasoningItemIdPolicy: 'omit' })),也可以设为 runner 默认值(new Runner({ reasoningItemIdPolicy: 'omit', ... }))。当从保存的 RunState 恢复时,除非你显式覆盖,否则会复用之前已解析的策略。
与 callModelInputFilter 的交互
Section titled “与 callModelInputFilter 的交互”reasoningItemIdPolicy 会在 callModelInputFilter 之前应用。若你需要自定义行为,仍可在 callModelInputFilter 中检查已准备输入,并在模型调用前手动重新加入或移除 reasoning IDs。
何时使用 'omit'
Section titled “何时使用 'omit'”当你希望被回放的 reasoning items 以无 ID 的规范化形式存在时,可使用 'omit'(例如让转发/回放的模型输入更简单,或满足应用流水线中的集成要求)。
如果你的后端/provider 因请求校验错误而拒绝回放的 reasoning items(例如后续输入中与 reasoning item IDs 相关的 HTTP 400 错误),这也是一个有用的排障选项。在这些情况下,使用 'omit' 去除回放的 reasoning IDs,可避免发送后端视为无效的新请求 ID。
如果你希望 SDK 在回放输入中携带 reasoning-item IDs,且你的集成能接受它们,请保留 'preserve'。
使用 errorHandlers 可将受支持的运行时错误转换为最终输出,而不是抛出异常。目前仅支持 maxTurns。
errorHandlers.maxTurns仅处理最大轮次错误。errorHandlers.default用作受支持类型的兜底处理。- 处理器会接收
{ error, context, runData },并可返回{ finalOutput, includeInHistory? }。
SDK 会抛出一小组可捕获错误:
MaxTurnsExceededError——达到maxTurns。ModelBehaviorError——模型产生了无效输出(例如格式错误的 JSON、未知工具)。InputGuardrailTripwireTriggered/OutputGuardrailTripwireTriggered——护栏违规。ToolInputGuardrailTripwireTriggered/ToolOutputGuardrailTripwireTriggered——工具护栏违规。GuardrailExecutionError——护栏执行失败。ToolTimeoutError——函数工具超过timeoutMs,且使用了timeoutBehavior: 'raise_exception'。ToolCallError——函数工具执行失败(非超时错误)。UserError——基于配置或用户输入抛出的任何错误。
这些错误都继承自基类 AgentsError,该基类可能提供 state 属性以访问当前运行状态。
下面是处理 GuardrailExecutionError 的代码示例。由于输入护栏只会在首条用户输入上运行,示例会使用原始输入和上下文重新启动运行。它还展示了如何复用已保存状态,在不再次调用模型的情况下重试输出护栏:
import { Agent, GuardrailExecutionError, InputGuardrail, InputGuardrailTripwireTriggered, OutputGuardrail, OutputGuardrailTripwireTriggered, run,} from '@openai/agents';import { z } from 'zod';
// Shared guardrail agent to avoid re-creating it on every fallback run.const guardrailAgent = new Agent({ name: 'Guardrail check', instructions: 'Check if the user is asking you to do their math homework.', outputType: z.object({ isMathHomework: z.boolean(), reasoning: z.string(), }),});
async function main() { const input = 'Hello, can you help me solve for x: 2x + 3 = 11?'; const context = { customerId: '12345' };
// Input guardrail example
const unstableInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (unstable)', execute: async () => { throw new Error('Something is wrong!'); }, };
const fallbackInputGuardrail: InputGuardrail = { name: 'Math Homework Guardrail (fallback)', execute: async ({ input, context }) => { const result = await run(guardrailAgent, input, { context }); const isMathHomework = result.finalOutput?.isMathHomework ?? /solve for x|math homework/i.test(JSON.stringify(input)); return { outputInfo: result.finalOutput, tripwireTriggered: isMathHomework, }; }, };
const agent = new Agent({ name: 'Customer support agent', instructions: 'You are a customer support agent. You help customers with their questions.', inputGuardrails: [unstableInputGuardrail], });
try { // Input guardrails only run on the first turn of a run, so retries must start a fresh run. await run(agent, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError) { console.error(`Guardrail execution failed (input): ${e}`); try { agent.inputGuardrails = [fallbackInputGuardrail]; // Retry from scratch with the original input and context. await run(agent, input, { context }); } catch (ee) { if (ee instanceof InputGuardrailTripwireTriggered) { console.log('Math homework input guardrail tripped on retry'); } else { throw ee; } } } else { throw e; } }
// Output guardrail example
const replyOutputSchema = z.object({ reply: z.string() });
const unstableOutputGuardrail: OutputGuardrail<typeof replyOutputSchema> = { name: 'Answer review (unstable)', execute: async () => { throw new Error('Output guardrail crashed.'); }, };
const fallbackOutputGuardrail: OutputGuardrail<typeof replyOutputSchema> = { name: 'Answer review (fallback)', execute: async ({ agentOutput }) => { const outputText = typeof agentOutput === 'string' ? agentOutput : (agentOutput?.reply ?? JSON.stringify(agentOutput)); const flagged = /math homework|solve for x|x =/i.test(outputText); return { outputInfo: { flaggedOutput: outputText }, tripwireTriggered: flagged, }; }, };
const agent2 = new Agent<unknown, typeof replyOutputSchema>({ name: 'Customer support agent (output check)', instructions: 'You are a customer support agent. Answer briefly.', outputType: replyOutputSchema, outputGuardrails: [unstableOutputGuardrail], });
try { await run(agent2, input, { context }); } catch (e) { if (e instanceof GuardrailExecutionError && e.state) { console.error(`Guardrail execution failed (output): ${e}`); try { agent2.outputGuardrails = [fallbackOutputGuardrail]; // Output guardrails can be retried using the saved state without another model call. await run(agent2, e.state); } catch (ee) { if (ee instanceof OutputGuardrailTripwireTriggered) { console.log('Output guardrail tripped after retry with saved state'); } else { throw ee; } } } else { throw e; } }}
main().catch(console.error);输入重试 vs 输出重试:
- 输入护栏仅在一次运行的第一条用户输入时执行,因此要重试必须使用相同输入/上下文启动全新运行——传入已保存
state不会重新触发输入护栏。 - 输出护栏在模型响应后执行,因此你可以复用
GuardrailExecutionError中保存的state来重新运行输出护栏,而无需再次调用模型。
运行上述示例后,你会看到如下输出:
Guardrail execution failed (input): Error: Input guardrail failed to complete: Error: Something is wrong!Math homework input guardrail tripped on retryGuardrail execution failed (output): Error: Output guardrail failed to complete: Error: Output guardrail crashed.Output guardrail tripped after retry with saved state