跳转到内容

构建实时智能体

默认的 OpenAIRealtimeWebRTC 等部分传输层会自动为您处理音频输入和输出。对于 OpenAIRealtimeWebSocket 等其他传输机制,您必须自行处理会话音频:

import {
RealtimeAgent,
RealtimeSession,
TransportLayerAudio,
} from '@openai/agents/realtime';
const agent = new RealtimeAgent({ name: 'My agent' });
const session = new RealtimeSession(agent);
const newlyRecordedAudio = new ArrayBuffer(0);
session.on('audio', (event: TransportLayerAudio) => {
// play your audio
});
// send new audio to the agent
session.sendAudio(newlyRecordedAudio);

当底层传输机制支持时,session.muted 会报告当前静音状态,session.mute(true | false) 则用于切换麦克风采集状态。OpenAIRealtimeWebSocket 未实现静音功能:session.muted 返回 null,而 session.mute() 会抛出异常。因此,在 WebSocket 配置中,您应在本地暂停采集,并停止调用 sendAudio(),直到需要重新启用麦克风。

创建 RealtimeSession 时配置会话本身,通常通过 model 选项和 config 对象进行配置。connect(...) 用于处理连接时的事项,例如凭据、端点 URL 和 SIP 呼叫关联,而不是任意会话字段。

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
config: {
outputModalities: ['audio'],
reasoning: {
effort: 'low',
},
parallelToolCalls: true,
audio: {
input: {
format: 'pcm16',
transcription: {
model: 'gpt-4o-mini-transcribe',
},
},
output: {
format: 'pcm16',
},
},
},
});

在底层,SDK 会将此配置规范化为实时 session.update 结构。如果需要使用 RealtimeSessionConfig 中没有对应属性的原始会话字段,请使用 providerData,或通过 session.transport.sendEvent(...) 发送原始 session.update

建议优先使用包含 outputModalitiesaudio.inputaudio.output 的新版 SDK 配置结构。为保持向后兼容,modalitiesinputAudioFormatoutputAudioFormatinputAudioTranscriptionturnDetection 等旧版 SDK 别名仍会被规范化,但新代码应使用此处所示的嵌套 audio 结构。

对于 gpt-realtime-2.1 等具备推理能力的实时模型,请在会话配置中设置 reasoning.effort。更高的推理强度可能会增加延迟和 token 用量。如果希望控制模型能否并行调用多个工具,还可以设置 parallelToolCalls

对于语音到语音会话,通常应选择 outputModalities: ['audio'],这样可获得音频输出及转录文本。只有需要纯文本响应时,才切换为 ['text']

对于尚未在 RealtimeSessionConfig 中提供对应参数的新参数,可以使用 providerData。传入 providerData 的任何内容都会作为原始 session 对象的一部分转发。

还可以在构造时设置以下 RealtimeSession 选项:

选项类型用途
contextTContext合并到会话上下文中的额外本地上下文。
historyStoreAudioboolean在本地历史记录快照中存储音频数据(默认禁用)。
outputGuardrailsRealtimeOutputGuardrail[]会话的输出护栏(请参阅护栏)。
outputGuardrailSettings{ debounceTextLength?: number }护栏运行频率。默认为 100;使用 -1 可仅在完整文本可用后运行一次。
tracingDisabledboolean禁用会话追踪。
groupIdstring对多个会话或后端运行的追踪进行分组。需要设置 workflowName
traceMetadataRecord<string, any>附加到会话追踪的自定义元数据。需要设置 workflowName
workflowNamestring便于识别的追踪工作流名称。
automaticallyTriggerResponseForMcpToolCallsbooleanMCP 工具调用完成时自动触发模型响应(默认值:true)。
toolErrorFormatterToolErrorFormatter自定义返回给模型的工具审批拒绝消息。
toolExecutionRealtimeToolExecutionConfig本地实时函数工具的 SDK 端执行设置。设置 preApprovalInputGuardrails: true 可在发出待审批请求之前运行输入护栏。

connect(...) 选项:

选项类型用途
apiKeystring | (() => string | Promise<string>)此连接使用的 API 密钥(或延迟加载器)。
modelOpenAIRealtimeModels | string传输层选项类型中提供的字段。对于 RealtimeSession,请在构造函数中设置模型;原始传输也可以在连接时使用模型。
urlstring可选的自定义实时端点 URL。
callIdstring关联到现有的 SIP 发起呼叫或会话。

RealtimeSession 建立在长连接实时连接之上。它会保留对话历史的本地副本、监听传输事件、运行工具和输出护栏,并使当前智能体配置与传输层保持同步。

底层 API 的行为仍然很重要:

  • 成功连接后会先触发 session.created 事件,后续配置更改则会触发 session.updated
  • 大多数会话属性都可以随时间更改,但 model 无法在对话过程中更改;voice 只能在会话生成音频输出之前更改;追踪也应预先决定,因为 Realtime API 不允许在启用追踪后对其进行修改。
  • Realtime API 目前将单个会话限制为 60 分钟。
  • 输入音频转录是异步的,因此最新话语的转录文本可能在响应生成已经开始后才到达。

在 SDK 层,await session.connect() 表示”传输层已准备就绪,可以开始对话”,但具体时机会因传输机制而异:

  • 在默认的浏览器 WebRTC 传输中,SDK 会在数据通道打开后立即发送初始 session.update,并尝试等待对应的 session.updated 事件后再完成 connect()。这样可避免在应用您的 instructions、工具和模态之前,音频就已到达服务器。如果始终未收到该确认,connect() 会在短暂超时后转为完成状态。
  • 在默认的服务器端 WebSocket 传输中,套接字打开并发送初始配置后,connect() 即会完成。因此,对应的 session.updated 事件可能在 connect() 已经完成后才到达。

如果需要了解原始事件模型,请将官方实时对话指南与本页面结合阅读。

默认情况下,实时会话使用内置语音活动检测(VAD),让 API 判断用户何时开始或停止说话,以及何时创建响应。SDK 通过 audio.input.turnDetection 公开此功能。

import { RealtimeSession } from '@openai/agents/realtime';
import { agent } from './agent';
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
config: {
audio: {
input: {
turnDetection: {
type: 'semantic_vad',
eagerness: 'medium',
createResponse: true,
interruptResponse: true,
},
},
},
},
});

两种常见模式为:

  • semantic_vad,旨在提供更自然的轮次边界;当用户听起来还没有说完时,它可以多等待一段时间。
  • server_vad,更依赖阈值,并提供 thresholdprefixPaddingMssilenceDurationMsidleTimeoutMs 等设置。

如果希望自行管理轮次边界,请将 audio.input.turnDetection 设置为 null。官方语音活动检测指南实时对话指南对底层行为进行了更详细的说明。

启用 VAD 后,用户在智能体说话时插话可以中断当前响应。在 WebSocket 传输中,SDK 会监听 input_audio_buffer.speech_started,将助手音频截断到用户实际听到的位置,并触发 audio_interrupted 事件。在 WebSocket 配置中自行管理播放时,此事件尤其有用。

import { session } from './agent';
session.on('audio_interrupted', () => {
// handle local playback interruption
});

如果希望提供手动停止按钮,请自行调用 interrupt()

import { session } from './agent';
session.interrupt();
// This still triggers `audio_interrupted` so your UI can stop playback

WebRTC 和 WebSocket 都会停止正在进行的响应,但底层机制因传输方式而异。WebRTC 会自动清除已缓冲的输出音频。在 WebSocket 配置中,您仍需自行停止本地播放;当对应的截断事件和对话事件从传输层返回时,本地历史记录才会更新。

如果希望向实时对话发送键入内容或其他结构化用户内容,请使用 sendMessage()

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
session.sendMessage('Hello, how are you?');

这适用于混合文本与语音的 UI、带外上下文注入,或为语音输入搭配明确的键入说明。

实时语音到语音会话还可以包含图像。在 SDK 中,使用 addImage() 将图像附加到当前对话。

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
const imageDataUrl = 'data:image/png;base64,...';
session.addImage(imageDataUrl, { triggerResponse: false });
session.sendMessage('Describe what is in this image.');

传入 triggerResponse: false 后,可以先将图像与后续文本或音频轮次一起批量提交,再要求模型响应。这与官方实时对话图像输入指南一致。

在较高层级的 SDK 中,sendMessage()addImage() 默认会为您触发响应。当使用原始传输事件、按键说话流程或自定义审核/验证步骤时,手动响应控制非常重要。

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
session.transport.on('*', (event) => {
// Event received from the underlying Realtime transport
});
// Send any valid client event, for example, to trigger a new response
session.transport.sendEvent({
type: 'response.create',
// ...
});

常见情况有两种:

  1. 如果使用 audio.input.turnDetection = null 完全禁用 VAD,您需要自行提交音频轮次,然后发送 response.create
  2. 如果保留 VAD,但设置 turnDetection.interruptResponse = falseturnDetection.createResponse = false,API 仍会检测轮次,但由您负责创建响应。

第二种模式适用于希望在模型响应前检查或审核用户输入的场景。它与官方关于禁用自动响应的实时对话指南一致。

与常规智能体类似,您可以使用交接将一个智能体拆分为多个智能体并在它们之间进行编排,从而提升性能并更准确地限定问题范围。

import { RealtimeAgent } from '@openai/agents/realtime';
const mathTutorAgent = new RealtimeAgent({
name: 'Math Tutor',
handoffDescription: 'Specialist agent for math questions',
instructions:
'You provide help with math problems. Explain your reasoning at each step and include examples',
});
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
handoffs: [mathTutorAgent],
});

与常规智能体不同,实时智能体的交接行为略有差异。执行交接时,进行中的会话会直接更新为新智能体的配置。因此,新智能体会自动访问当前对话历史,而且目前不会应用输入过滤器。

由于会话保持实时状态,该会话所用的模型不会在交接期间发生变化。语音更改遵循底层 Realtime API 规则:只有在会话生成音频输出之前才能生效。实时交接主要用于在同一会话中切换不同的 RealtimeAgent 配置;如果需要使用其他模型(例如 gpt-5.4 之类的推理模型),或委派给非实时后端智能体,请使用通过工具委派

与常规智能体一样,实时智能体可以调用工具来执行操作。实时功能支持函数工具(本地执行)和托管 MCP 工具(由 Realtime API 远程执行)。您可以使用与常规智能体相同的 tool() 辅助函数来定义函数工具。

仅适用于 Responses 的工具选项不会沿用到实时智能体。包含 'programmatic' 的函数工具 outputSchemaallowedCallers 值会被拒绝,托管 MCP 工具上的程序化调用方同样会被拒绝。请在实时会话中使用可直接调用的函数工具或托管 MCP 工具,或将需要程序化工具调用的工作委派给 Responses 智能体。

import { tool, RealtimeAgent } from '@openai/agents/realtime';
import { z } from 'zod';
const getWeather = tool({
name: 'get_weather',
description: 'Return the weather for a city.',
parameters: z.object({ city: z.string() }),
async execute({ city }) {
return `The weather in ${city} is sunny.`;
},
});
const weatherAgent = new RealtimeAgent({
name: 'Weather assistant',
instructions: 'Answer weather questions.',
tools: [getWeather],
});

函数工具与 RealtimeSession 在同一环境中运行。这意味着,如果会话在浏览器中运行,工具也会在浏览器中执行。如果需要执行敏感操作,请从工具内部调用后端,并让服务器执行需要特权的工作。

这样一来,浏览器端工具就可以充当通往服务器端逻辑的轻量级反向通道。例如,examples/realtime-next 在浏览器中定义了一个 refundBackchannel 工具,将请求和当前对话历史转发给服务器上的 handleRefundRequest(...)。服务器上的另一个 Runner 可以使用不同的智能体或模型评估退款,再将结果返回给语音会话。

托管 MCP 工具可以使用 hostedMcpTool 进行配置,并在远程执行。MCP 工具可用性发生变化时,会话会触发 mcp_tools_changed。若要阻止会话在 MCP 工具调用完成后自动触发模型响应,请设置 automaticallyTriggerResponseForMcpToolCalls: false

当前经过筛选的 MCP 工具列表也可通过 session.availableMcpTools 获取。该属性和 mcp_tools_changed 事件都只反映当前智能体上已启用的托管 MCP 服务器,并且已经应用智能体配置中的所有 allowed_tools 过滤器。

如果将安全服务器选择、请求头和审批视为连接前配置,就更容易理解托管 MCP 的设置方式。在 RealtimeSession.connect() 打开传输连接之前,SDK 会解析当前智能体的托管 MCP 工具定义,并将受支持的 MCP 字段包含在发送给 Realtime API 的初始会话配置中。

在浏览器 WebRTC 应用中,这一时机尤为重要。临时客户端密钥始终在您的服务器上生成,因此,任何必须保密的托管 MCP 凭据或自定义 headers,都应作为初始 session 载荷的一部分,附加到服务器端的 POST /v1/realtime/client_secrets 请求中。不要将长期凭据放入浏览器代码,也不要打算在 connect() 启动后再添加。

在 Realtime API 层,后续的 session.update 调用仍可更改工具及其他可变会话字段;当前智能体发生变化时,SDK 本身也会发送 session.update。不过,在浏览器应用中,应将安全的托管 MCP 初始化视为服务器端的连接前事项,并确保浏览器端 RealtimeSession 配置与服务器生成密钥时使用的配置保持一致。

工具执行期间,智能体将无法处理用户的新请求。改善体验的一种方式是,让智能体在即将执行工具时提前告知用户,或说出特定短语,为工具执行争取一些时间。

如果函数工具完成后不应立即触发另一个模型响应,请从 @openai/agents/realtime 返回 backgroundResult(output)。这会将工具输出发回会话,同时仍由您控制是否触发响应。

函数工具的超时选项(timeoutMstimeoutBehaviortimeoutErrorFunction)在实时会话中的工作方式相同。使用默认的 error_as_result 时,超时消息会作为工具输出发送。使用 raise_exception 时,会话会触发包含 ToolTimeoutErrorerror 事件,并且不会为该调用发送工具输出。

除了智能体调用特定工具时传入的参数外,您还可以访问实时会话所追踪的当前对话历史快照。如果需要根据对话的当前状态执行更复杂的操作,或计划使用工具进行委派,此功能会很有用。

import {
tool,
RealtimeContextData,
RealtimeItem,
} from '@openai/agents/realtime';
import { z } from 'zod';
const parameters = z.object({
request: z.string(),
});
const refundTool = tool<typeof parameters, RealtimeContextData>({
name: 'Refund Expert',
description: 'Evaluate a refund',
parameters,
execute: async ({ request }, details) => {
// The history might not be available
const history: RealtimeItem[] = details?.context?.history ?? [];
// Call your backend to process the refund request
},
});

如果使用 needsApproval: true 定义工具,智能体会在执行工具前触发 tool_approval_requested 事件。

通过监听此事件,您可以向用户显示用于批准或拒绝工具调用的 UI。

使用 await session.approve(request.approvalItem)await session.reject(request.approvalItem) 处理请求。对于函数工具,可以传入 { alwaysApprove: true }{ alwaysReject: true },以便在会话剩余期间对重复调用复用相同决定;也可以使用 session.reject(request.approvalItem, { message: '...' }),为该次特定调用向模型返回自定义拒绝消息。托管 MCP 审批不支持持续生效的批准或拒绝;请改用托管 MCP 的 allowedTools 配置来限制这些工具。

如果没有传入针对单次调用的拒绝 message,会话会回退到 toolErrorFormatter(如已配置),然后再回退到 SDK 默认拒绝文本。

默认情况下,函数工具输入护栏会在审批后、工具执行前立即运行。如果向 new RealtimeSession(...) 传入 toolExecution: { preApprovalInputGuardrails: true },本地函数工具输入护栏也会在会话触发 tool_approval_requested 之前运行。护栏拒绝会将拒绝消息作为工具输出发回,并跳过审批事件。如果护栏允许调用,则仍会触发审批事件,并且在 session.approve(...) 之后、执行之前再次运行护栏。

import { session } from './agent';
session.on('tool_approval_requested', (_context, _agent, request) => {
// Show a UI to let the user approve or reject the tool call
// Then resolve the request with `session.approve(...)` or `session.reject(...)`
session.approve(request.approvalItem);
});

护栏可用于监控智能体所说的内容是否违反一组规则,并立即中止响应。这些检查针对智能体的响应输出流运行。在纯文本会话中,SDK 会评估输出文本增量。在音频会话中,它使用输出音频转录文本和转录增量,因此关键前提是转录文本可用,而不是存在单独的文本输出模态。

您提供的护栏会在返回模型响应时异步运行,使您能够根据预定义的分类触发条件中止响应,例如”提到了某个明确禁止使用的词语”。

护栏触发时,会话会触发 guardrail_tripped 事件。该事件还会提供一个 details 对象,其中包含触发护栏的 itemId

import {
RealtimeOutputGuardrail,
RealtimeAgent,
RealtimeSession,
} from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
const guardrails: RealtimeOutputGuardrail[] = [
{
name: 'No mention of Dom',
async execute({ agentOutput }) {
const domInOutput = agentOutput.includes('Dom');
return {
tripwireTriggered: domInOutput,
outputInfo: { domInOutput },
};
},
},
];
const guardedSession = new RealtimeSession(agent, {
outputGuardrails: guardrails,
});

默认情况下,护栏每生成 100 个字符时运行一次,并在最终转录文本可用后再次运行。由于说出文本通常比生成转录文本耗时更长,因此护栏往往能在用户听到不安全输出之前将其中止。

如果希望修改此行为,可以向会话传入 outputGuardrailSettings 对象。

如果只想在响应结束时对完整生成的转录文本评估一次,请设置 debounceTextLength: -1

import { RealtimeAgent, RealtimeSession } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Greeter',
instructions: 'Greet the user with cheer and answer questions.',
});
const guardedSession = new RealtimeSession(agent, {
outputGuardrails: [
/*...*/
],
outputGuardrailSettings: {
debounceTextLength: 500, // run guardrail every 500 characters or set it to -1 to run it only at the end
},
});

RealtimeSession 会自动维护本地 history 快照,用于追踪用户消息、助手输出、工具调用和截断状态。您可以在 UI 中渲染它、在工具内部检查它,或在需要更正或删除项目时进行更新。

对话发生变化时,会话会触发 history_updated。如果需要请求更改历史记录,请使用 updateHistory()。它会要求传输层比较当前历史记录的差异,并发送必要的删除/创建事件;随着对应的对话事件从传输层返回,本地 session.history 视图也会更新。

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime-2.1',
});
await session.connect({ apiKey: '<client-api-key>' });
// listening to the history_updated event
session.on('history_updated', (history) => {
// returns the full history of the session
console.log(history);
});
// Option 1: explicit setting
session.updateHistory([
/* specific history */
]);
// Option 2: override based on current state like removing all agent messages
session.updateHistory((currentHistory) => {
return currentHistory.filter(
(item) => !(item.type === 'message' && item.role === 'assistant'),
);
});
  1. 目前无法在事后编辑函数工具调用。
  2. 历史记录中的助手文本取决于可用的转录文本,包括 output_audio.transcript
  3. 因中断而截断的响应不会保留最终转录文本。
  4. 最好将输入音频转录视为用户所说内容的大致参考,而不是模型如何理解音频的精确副本。

通过工具委派

通过结合对话历史和工具调用,您可以将对话委派给另一个后端智能体来执行更复杂的操作,然后将结果传回给用户。

import {
RealtimeAgent,
RealtimeContextData,
tool,
} from '@openai/agents/realtime';
import { handleRefundRequest } from './serverAgent';
import z from 'zod';
const refundSupervisorParameters = z.object({
request: z.string(),
});
const refundSupervisor = tool<
typeof refundSupervisorParameters,
RealtimeContextData
>({
name: 'escalateToRefundSupervisor',
description: 'Escalate a refund request to the refund supervisor',
parameters: refundSupervisorParameters,
execute: async ({ request }, details) => {
// This will execute on the server
return handleRefundRequest(request, details?.context?.history ?? []);
},
});
const agent = new RealtimeAgent({
name: 'Customer Support',
instructions:
'You are a customer support agent. If you receive any requests for refunds, you need to delegate to your supervisor.',
tools: [refundSupervisor],
});

随后,以下代码会在服务器上运行;本示例通过 Next.js Server Action 执行。

// This runs on the server
import 'server-only';
import { Agent, run } from '@openai/agents';
import type { RealtimeItem } from '@openai/agents/realtime';
import z from 'zod';
const agent = new Agent({
name: 'Refund Expert',
instructions:
'You are a refund expert. You are given a request to process a refund and you need to determine if the request is valid.',
model: 'gpt-5.4',
outputType: z.object({
reason: z.string(),
refundApproved: z.boolean(),
}),
});
export async function handleRefundRequest(
request: string,
history: RealtimeItem[],
) {
const input = `
The user has requested a refund.
The request is: ${request}
Current conversation history:
${JSON.stringify(history, null, 2)}
`.trim();
const result = await run(agent, input);
return JSON.stringify(result.finalOutput, null, 2);
}