跳转到内容

运行智能体

智能体本身不会执行任何操作——您需要使用 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 runner
const 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 的 run 方法时,您需要传入起始智能体和输入。输入可以是字符串(视为用户消息),也可以是输入项列表,即 OpenAI Responses API 中的项。

Runner 随后会运行一个循环:

  1. 使用当前输入调用当前智能体的模型。
  2. 检查 LLM 响应。
    • 最终输出 → 返回。
    • 交接 → 切换到新的智能体,保留累积的对话历史记录,然后返回步骤 1。
    • 工具调用 → 执行工具,将结果追加到对话中,然后返回步骤 1。
  3. 达到 maxTurns 时抛出 MaxTurnsExceededError,除非 maxTurnsnull

在应用启动时创建一个 Runner,并在不同请求间复用它。该实例存储模型提供商和追踪选项等全局配置。只有在需要完全不同的设置时,才应创建另一个 Runner。对于简单脚本,您也可以调用 run(),它会在内部使用默认 Runner。

run() 方法的输入包括用于开始运行的初始智能体、此次运行的输入以及一组选项。

输入可以是字符串(视为用户消息)、输入项列表;如果您正在构建人机协作智能体,也可以是 RunState 对象。

其他选项如下:

选项默认值说明
streamfalse如果为 true,调用将返回 StreamedRunResult,并在事件从模型到达时将其发出。
context转发给每个工具、护栏和交接的上下文对象。详情请参阅上下文管理
maxTurns10安全限制——达到该限制时抛出 MaxTurnsExceededError。传入 null 可禁用此限制。
signal用于取消操作的 AbortSignal
session会话持久化实现。请参阅会话
sessionInputCallback用于合并会话历史记录和新输入的自定义逻辑;在模型调用之前运行。请参阅会话
callModelInputFilter在调用模型前编辑模型输入(输入项 + 可选 instructions)的钩子。请参阅模型调用输入过滤器
toolErrorFormatter用于自定义返回给模型的工具错误消息的钩子。请参阅工具错误格式化器
reasoningItemIdPolicy控制将之前的运行项重新转换为模型输入时,是保留还是省略推理项的 id。请参阅推理项 ID 策略
tracing针对单次运行覆盖的追踪配置。设置 includeTaskAndTurnSpans: false 可省略默认的任务/轮次 span 层级。
sandbox用于 SandboxAgent 运行的沙盒客户端、实时会话、会话状态、快照、清单覆盖或并发限制。请参阅概念
toolExecutionSDK 侧本地工具调用的执行设置。使用 toolExecution.maxFunctionToolConcurrency 限制同时运行的函数工具数量,使用 toolExecution.preApprovalInputGuardrails 在待处理的审批请求之前运行函数工具输入护栏。
toolNotFoundBehavior'raise_error'控制模型发出但无法解析的函数工具调用。使用 'return_error_to_model' 可向模型返回可见的工具错误并继续运行。
toolNameCollisionPolicy'warn'控制已启用的函数工具名称与交接名称之间的冲突。使用 'error' 可在模型请求前失败,而不是发出警告并且仅公开当前的分派优先项。
errorHandlers支持的运行时错误处理器。请参阅错误处理器
conversationId复用服务器端对话(仅限 OpenAI Responses API + Conversations API)。
previousResponseId在不创建对话的情况下,从上一次 Responses API 调用继续(仅限 OpenAI Responses API)。

流式传输还会在 LLM 运行时公开事件。StreamedRunResult 会随着运行的推进不断累积信息。执行结果的 completed Promise 得到解决后,其摘要属性将包含所有新生成的输出。您可以使用 for await 循环迭代流式事件。详情请参阅流式传输

如果您要创建自己的 Runner 实例,可以传入一个 RunConfig 对象来配置 Runner。

字段类型用途
modelstring | Model强制此次运行中的所有智能体使用指定模型。
modelProviderModelProvider解析模型名称——默认为 OpenAI 提供商。
modelSettingsModelSettings覆盖各智能体设置的全局调优参数。有关详情(包括需要显式启用的重试配置),请参阅模型
handoffInputFilterHandoffInputFilter执行交接时修改输入项(前提是交接本身尚未定义过滤器)。
inputGuardrailsInputGuardrail[]应用于初始用户输入的护栏。
outputGuardrailsOutputGuardrail[]应用于最终输出的护栏。
tracingDisabledboolean完全禁用 OpenAI 追踪。
traceIncludeSensitiveDataboolean从追踪中排除 LLM/工具的输入和输出,同时仍发出 span。
workflowNamestring显示在追踪控制面板中,用于对相关运行进行分组。
traceId / groupIdstring手动指定追踪 ID 或组 ID,而不是让 SDK 生成。
traceMetadataRecord<string, string>附加到每个 span 的任意元数据。
tracingTracingConfigRunner 级别的默认追踪配置,包括导出 API 密钥和 includeTaskAndTurnSpans。单次运行选项可以覆盖这些值。
sessionInputCallbackSessionInputCallback此 Runner 上所有运行采用的默认历史记录合并策略。
callModelInputFilterCallModelInputFilter在每次调用模型前编辑模型输入的全局钩子。
toolErrorFormatterToolErrorFormatter用于自定义返回给模型的工具错误消息的全局钩子。
reasoningItemIdPolicyReasoningItemIdPolicy将生成的项重新用于后续模型调用时,保留或省略推理项 id 的默认策略。
sandboxSandboxRunConfigSandboxAgent 运行的默认沙盒运行时配置。
toolExecutionToolExecutionConfigSDK 侧本地工具调用的默认执行设置。maxFunctionToolConcurrency 限制每个轮次的本地函数工具并发数;未设置或设为 null 时,将启动该轮次发出的所有函数工具调用。preApprovalInputGuardrails 用于显式启用在待处理的审批请求之前运行函数工具输入护栏。
toolNotFoundBehaviorToolNotFoundBehavior无法解析的函数工具调用的默认行为。'raise_error' 会抛出 ModelBehaviorError'return_error_to_model' 会返回模型可见的工具错误,并允许继续运行。
toolNameCollisionPolicyToolNameCollisionPolicy已启用的函数工具名称与交接名称之间的默认冲突策略。'warn' 会记录日志并公开当前的分派优先项;'error' 会在调用模型前抛出 UserError

toolExecution.maxFunctionToolConcurrency 必须是大于或等于 1 的整数。此设置仅限制 SDK 侧本地函数工具的执行,不会改变提供商侧的 modelSettings.parallelToolCalls

toolExecution.preApprovalInputGuardrails 默认处于禁用状态。设置为 true 后,需要审批的本地函数工具会在 SDK 记录待处理审批中断之前运行输入护栏。如果护栏返回 rejectContent,SDK 会将该拒绝消息作为工具输出发回,而不会请求审批。如果护栏允许此次调用,系统仍会发起审批请求;审批得到解决后,在工具执行前还会再次运行相同的输入护栏。

toolNameCollisionPolicy 仅控制已启用的函数工具与交接之间的名称冲突。针对命名空间工具、延迟工具和重复本地 MCP 工具名称的现有严格验证保持不变。

将状态带入下一轮通常有以下四种方式:

策略状态存储位置最适合下一轮传入的内容
result.history应用内存小型聊天循环、完全手动控制、任意提供商result.history
session您的存储 + SDK持久化聊天状态、可恢复运行、自定义存储同一个 session 实例(或由存储支持的实例)
conversationIdOpenAI Conversations API在多个工作进程/服务之间共享服务器端状态同一个 conversationId,外加仅包含新的用户轮次
previousResponseId仅限 OpenAI Responses API无需创建对话即可实现的最简单服务器托管续接方式result.lastResponseId,外加仅包含新的用户轮次

result.historysession 由客户端管理。conversationIdpreviousResponseId 由 OpenAI 管理,并且仅适用于使用 OpenAI Responses API 的情况。在大多数应用中,每个对话应选择一种持久化策略。除非您有意协调这两个层级,否则混用客户端管理的历史记录与服务器管理的状态可能会导致上下文重复。

沙盒智能体还增加了一层状态:实时沙盒工作区。使用常规 SDK 的 sessionconversationIdpreviousResponseId 管理对话历史记录,并使用 sandbox.sessionsandbox.sessionStateRunState 或快照管理沙盒文件系统状态。有关工作区生命周期,请参阅概念

每次调用 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"

有关交互式版本,请参阅聊天示例

您可以让 OpenAI Responses API 为您持久化对话历史记录,而不必在每一轮中发送完整的本地对话历史记录。这在协调长对话或多个服务时很有用。使用下面任一种服务器托管方式时,每次请求只需传入新轮次的输入。API 会为您复用之前的状态。有关详情,请参阅对话状态指南

OpenAI 提供了两种复用服务器端状态的方式:

您可以使用 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 串联每个请求。这样无需创建完整的对话资源,也能在多个轮次之间保留上下文。

使用 previousResponseId 串联请求
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);

conversationIdpreviousResponseId 互斥。如果您需要一个可跨系统共享的命名对话资源,请使用 conversationId;如果您只需要从一个响应继续到下一个响应的最轻量级 SDK 基础组件,请使用 previousResponseId

使用 callModelInputFilter 可在模型调用的前一刻编辑模型输入。此钩子接收当前智能体、上下文和合并后的输入项(存在会话历史记录时也包括这些记录)。返回更新后的 input 数组和可选 instructions,以编辑敏感数据、丢弃旧消息或注入额外的系统指引。

您可以在 runner.run(..., { callModelInputFilter }) 中为单次运行设置它,也可以在 Runner 配置中将其设为默认值(即 RunConfig 中的 callModelInputFilter)。

返回值必须是 ModelInputData 对象:{ input: AgentInputItem[], instructions? }input 字段是必需的,并且必须是数组。返回任何其他结构都会抛出 UserError

SDK 会在调用过滤器之前克隆准备好的轮次输入。如果您还使用了 session,经过过滤的克隆内容会被持久化,因此此处进行的编辑或截断也会反映在存储的会话历史记录中。

如果过滤器依赖对象标识来记忆多次模型调用中的工作,请在过滤器上设置 preserveInputIdentity = true。随后,只要 SDK 准备的项仍然存在于准备好的输入中,SDK 就会保留该项的标识;不过,每次调用过滤器时传入的数组都是新的。此选项不会保留最初传给 run() 且由调用方持有的对象标识。

保留准备好的输入项标识
import { CallModelInputFilter, Runner } from '@openai/agents';
const inspectedItems = new WeakSet<object>();
const inspectInputOnce: CallModelInputFilter = ({ modelData }) => {
for (const item of modelData.input) {
if (item && typeof item === 'object' && !inspectedItems.has(item)) {
inspectedItems.add(item);
recordPreparedItem(item);
}
}
return modelData;
};
// Keep SDK-prepared item identities stable across repeated model calls so the
// WeakSet can recognize items that this filter already inspected. Do not mutate
// the items or their nested values.
inspectInputOnce.preserveInputIdentity = true;
const runner = new Runner({ callModelInputFilter: inspectInputOnce });
declare function recordPreparedItem(item: object): void;
export { runner };

此模式需要显式启用,因为 SDK 不会冻结准备好的项或其嵌套值。过滤器及其调用的每个辅助函数都必须将这些项视为不可变值,并返回替代值,而不是直接修改它们。过滤器返回后,模型请求和会话输入仍是相互分离的副本。

使用 conversationIdpreviousResponseId 时,该钩子会在下一次 Responses API 调用的准备载荷上运行。之前由服务器管理的上下文会由 API 恢复,因此该调用的过滤后数组可能已经只表示新轮次的增量,而不是对之前全部历史记录的完整重放。如果需要在最后的过滤步骤前,更改存储的历史记录与当前轮次的合并方式,请使用 sessionInputCallback

使用 toolErrorFormatter 可自定义发回给模型的工具错误消息。这样,您可以返回特定于领域的措辞(例如合规指引),而不是 SDK 的默认消息。

格式化器可以为每次运行单独设置(runner.run(..., { toolErrorFormatter })),也可以在 RunConfig 中全局设置(new Runner(...) 中的 toolErrorFormatter)。

此格式化器是审批拒绝的全局回退方案。如果您通过 result.state.reject(interruption, { message: '...' }) 拒绝特定中断,则该次调用的 message 优先于 toolErrorFormatter。如果两者都未提供,SDK 会回退到默认拒绝文本:Tool execution was not approved.

toolNotFoundBehavior: 'return_error_to_model' 将无法解析的函数工具调用转换为模型可见的工具输出时,该格式化器也会运行。在这种情况下,默认消息为 Tool '<name>' not found.

格式化器接收:

  • kind'approval_rejected''tool_not_found'
  • toolType'function''computer''shell''apply_patch'
  • toolName
  • callId
  • defaultMessage(当前错误类别对应的 SDK 回退消息)
  • runContext

返回字符串可覆盖消息;返回 undefined 则保留 SDK 默认值。如果格式化器抛出异常(或返回非字符串值),SDK 会记录警告,并回退到当前错误类别的默认消息。

使用 reasoningItemIdPolicy 可控制 SDK 将之前生成的运行项转换回 AgentInputItem[] 以供后续模型输入时,推理项是否保留其 id 字段。

这会影响 SDK 将生成的模型项作为输入重放的场景,例如:

  • 同一次运行中的后续模型调用(例如工具执行后),
  • 将生成的项复用为输入/历史记录的后续轮次,
  • 从保存的 RunState 恢复的运行,
  • result.history / result.output 等派生执行结果视图(它们是模型输入形态的数组)。
  • 'preserve'(默认)保留推理项 ID。
  • 'omit' 在将推理项发回作为输入之前,移除其 id 字段。
  • 非推理项不受影响。

以下内容不会受到影响:

  • 原始模型响应(result.rawResponses),
  • 运行项(result.newItems),
  • 提供商返回的模型当前轮次输出。

换句话说,当 SDK 根据之前生成的项构建下一次输入时,此策略才会生效。

您可以为单次运行设置该策略(runner.run(..., { reasoningItemIdPolicy: 'omit' })),也可以将其设为 Runner 默认值(new Runner({ reasoningItemIdPolicy: 'omit', ... }))。从保存的 RunState 恢复时,除非您覆盖该策略,否则会复用之前确定的策略。

reasoningItemIdPolicy 会在 callModelInputFilter 之前应用。如果您需要自定义行为,callModelInputFilter 仍可检查准备好的输入,并在模型调用前手动重新添加或移除推理 ID。

如果您希望重放的推理项在不含 ID 的情况下进行规范化(例如,让转发或重放的模型输入更简单,或者符合应用流水线中的集成要求),请使用 'omit'

如果后端或提供商因请求验证错误而拒绝重放的推理项,这也是一个有用的故障排除选项(例如,与后续输入中的推理项 ID 相关的 HTTP 400 错误)。在这些情况下,使用 'omit' 移除重放的推理 ID,可以避免发送后端认为对新请求无效的 ID。

如果您希望 SDK 在重放的输入中继续携带推理项 ID,并且您的集成可以接受这些 ID,请保留 'preserve'

使用 errorHandlers 可将支持的运行时错误转换为最终输出,而不是抛出异常。支持的键包括 maxTurnsmodelRefusalinvalidFinalOutput

  • errorHandlers.maxTurns 仅处理最大轮次错误。
  • errorHandlers.modelRefusal 处理以 ModelRefusalError 形式公开的模型拒绝。
  • errorHandlers.invalidFinalOutput 处理因结构化最终输出缺失或未通过输出模式验证而抛出的 ModelBehaviorError 实例。该处理器会返回经过验证的回退值,而不会重试模型或重放工具副作用。
  • errorHandlers.default 用作支持的错误类别的回退处理器。
  • 处理器接收 { error, context, runData },并可返回 { finalOutput, includeInHistory? }。返回的 finalOutput 必须与当前智能体的 outputType 匹配。对于 structured outputs,SDK 会先验证回退值,然后再完成运行。为该错误返回 undefined 会保留默认行为。

SDK 会抛出以下几类可捕获的错误:

这些错误都扩展自基础 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);

输入重试与输出重试的区别:

  • 输入护栏仅对一次运行中的第一个用户输入执行,因此必须使用相同的输入和上下文开始一次新运行才能重试——传入保存的 state 不会重新触发输入护栏。
  • 输出护栏在模型响应后运行,因此您可以复用 GuardrailExecutionError 中保存的 state,在不再次调用模型的情况下重新运行输出护栏。

运行上述代码示例时,您将看到以下输出:

Guardrail execution failed (input): Error: Input guardrail failed to complete: Error: Something is wrong!
Math homework input guardrail tripped on retry
Guardrail execution failed (output): Error: Output guardrail failed to complete: Error: Output guardrail crashed.
Output guardrail tripped after retry with saved state

  • 智能体:在运行智能体之前定义它。
  • 执行结果:了解 finalOutput、运行项、中断和恢复状态。
  • 会话:了解 SDK 管理的持久化记忆。
  • 工具:了解运行循环中使用的能力。
  • 模型:了解提供商配置和 Responses 传输机制。
  • 添加护栏追踪,为生产环境做好准备。