跳转到内容

运行智能体

智能体本身不会执行任何操作——您需要使用 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

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

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

输入可以是字符串(会被视为一条用户消息)、输入项列表,或者在构建人机协作智能体时使用的 RunState 对象。

附加选项包括:

选项默认值说明
streamfalse如果为 true,调用将返回 StreamedRunResult,并在模型产生事件时发出这些事件。
context传递给每个工具 / 护栏 / 交接的上下文对象。详见上下文管理指南
maxTurns10安全限制——达到时会抛出 MaxTurnsExceededError
signal用于取消的 AbortSignal
session会话持久化实现。参见会话指南
sessionInputCallback用于合并会话历史与新输入的自定义逻辑;在模型调用前运行。参见会话
callModelInputFilter在调用模型之前,用于编辑模型输入(条目 + 可选 instructions)的 hook。参见模型输入过滤器
toolErrorFormatter用于自定义返回给模型的工具审批拒绝消息的 hook。参见工具错误格式化器
reasoningItemIdPolicy控制在将先前运行条目重新转换为模型输入时,是否保留或省略 reasoning-item 的 id。参见Reasoning item ID 策略
tracing每次运行的追踪配置覆盖项(例如导出 API key)。
errorHandlers支持的运行时错误处理器(当前为 maxTurns)。参见错误处理器
conversationId复用服务器端对话(仅适用于 OpenAI Responses API + Conversations API)。
previousResponseId在不创建对话的情况下,从上一次 Responses API 调用继续(仅适用于 OpenAI Responses API)。

流式传输允许您在 LLM 运行时额外接收流式事件。流开始后,StreamedRunResult 将包含关于本次运行的完整信息,包括所有新产生的输出。您可以使用 for await 循环迭代这些流式事件。更多信息请参见流式传输指南

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

字段类型用途
modelstring | Model为本次运行中的所有智能体强制指定特定模型。
modelProviderModelProvider解析模型名称——默认为 OpenAI provider。
modelSettingsModelSettings全局调优参数,会覆盖每个智能体各自的设置。详见模型指南,其中还包括选择启用的重试配置。
handoffInputFilterHandoffInputFilter在执行交接时变更输入项(如果交接本身尚未定义过滤器)。
inputGuardrailsInputGuardrail[]应用于初始用户输入的护栏。
outputGuardrailsOutputGuardrail[]应用于最终输出的护栏。
tracingDisabledboolean完全禁用 OpenAI 追踪。
traceIncludeSensitiveDataboolean在仍然发出 span 的同时,排除追踪中的 LLM/工具输入与输出。
workflowNamestring显示在 Traces 仪表板中——有助于对相关运行进行分组。
traceId / groupIdstring手动指定 trace 或 group ID,而不是让 SDK 自动生成。
traceMetadataRecord<string, string>附加到每个 span 的任意元数据。
tracingTracingConfig每次运行的追踪覆盖项(例如导出 API key)。
sessionInputCallbackSessionInputCallback此 runner 上所有运行的默认历史合并策略。
callModelInputFilterCallModelInputFilter在每次模型调用前编辑模型输入的全局 hook。
toolErrorFormatterToolErrorFormatter用于自定义返回给模型的工具审批拒绝消息的全局 hook。
reasoningItemIdPolicyReasoningItemIdPolicy在将生成的条目重放到后续模型调用时,保留或省略 reasoning-item id 的默认策略。

将状态带入下一轮的常见方式有四种:

策略状态存储位置最适合下一轮传递的内容
result.history您应用的内存小型聊天循环、完全手动控制、任意 providerresult.history
session您的存储 + SDK持久聊天状态、可恢复运行、自定义存储同一个 session 实例(或基于存储的实例)
conversationIdOpenAI Conversations API跨 worker/服务共享的服务器端状态同一个 conversationId 加上仅新的用户轮次
previousResponseId仅 OpenAI Responses API不创建对话时,最简单的服务器管理式延续result.lastResponseId 加上仅新的用户轮次

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

每次调用 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 提供了两种复用服务器端状态的方式:

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 将每个请求串联起来。这样无需创建完整的对话资源,也能在多轮之间保持上下文。

使用 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 在调用模型之前编辑模型输入。这个 hook 会接收当前智能体、上下文以及合并后的输入项(包括存在时的会话历史)。返回更新后的 input 数组和可选的 instructions,以便隐藏敏感数据、丢弃旧消息或注入额外的系统指导。

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

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

SDK 会在调用过滤器之前复制已准备好的轮次输入。如果您同时使用了 session,那么被过滤后的副本就是最终被持久化的内容,因此这里执行的脱敏或截断也会反映在存储的会话历史中。

使用 conversationIdpreviousResponseId 时,该 hook 会作用于为下一次 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.

该格式化器当前会在 approval_rejected 事件中运行,并接收:

  • kind(当前始终为 'approval_rejected'
  • toolType'function''computer''shell''apply_patch'
  • toolName
  • callId
  • defaultMessage(SDK 的后备消息,当前为 Tool execution was not approved.
  • runContext

返回一个字符串以覆盖该消息,或返回 undefined 以保留 SDK 默认值。如果格式化器抛出异常(或返回非字符串值),SDK 会记录一条警告,并回退到默认审批拒绝消息。

使用 reasoningItemIdPolicy 来控制:当 SDK 将先前生成的运行条目重新转换为 AgentInputItem[] 以供后续模型输入时,reasoning item 是否保留其 id 字段。

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

  • 同一次运行中的后续模型调用(例如工具执行之后);
  • 后续轮次中,将生成条目复用为输入/历史记录;
  • 从保存的 RunState 恢复运行;
  • 派生的结果视图,例如 result.history / result.output(它们是模型输入形状的数组)。
  • 'preserve'(默认)会保留 reasoning item ID。
  • 'omit' 会在这些 reasoning item 被重新作为输入发送前去掉其 id 字段。
  • 非 reasoning item 不受影响。

以下内容不会因此改变:

  • 原始模型响应(result.rawResponses);
  • 运行条目(result.newItems);
  • provider 返回的当前轮模型输出。

换句话说,该策略作用于 SDK 如何基于先前生成的条目构建下一次输入

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

reasoningItemIdPolicy 会先于 callModelInputFilter 应用。如果您需要自定义行为,callModelInputFilter 仍然可以在模型调用前检查准备好的输入,并手动重新引入或移除 reasoning ID。

当您希望被重放的 reasoning item 以不带 ID 的方式标准化时,请使用 'omit'(例如,为了让转发/重放的模型输入更简单,或满足您应用流程中的集成要求)。

如果您的后端/provider 会因重放的 reasoning item 而拒绝请求并返回校验错误(例如,后续输入中与 reasoning item ID 有关的 HTTP 400 错误),这也是一个有用的故障排除选项。在这类情况下,使用 'omit' 去除重放的 reasoning ID,可以避免发送那些会被后端视为新请求中的无效 ID。

如果您希望 SDK 在重放输入中保留 reasoning item ID,并且您的集成能够接受它们,请继续使用 'preserve'

使用 errorHandlers 将支持的运行时错误转换为最终输出,而不是直接抛出异常。当前仅支持 maxTurns

  • errorHandlers.maxTurns 仅处理最大轮次错误。
  • errorHandlers.default 用作受支持类型的后备处理器。
  • 处理器会接收 { error, context, runData },并可返回 { finalOutput, includeInHistory? }

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 管理的持久化记忆。
  • 工具:了解运行循环中使用的能力。
  • 模型:了解 provider 配置和 Responses 传输。
  • 添加护栏追踪,以满足生产就绪需求。