跳转到内容

构建语音智能体

某些传输层(如默认的 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 返回 nullsession.mute() 会抛出异常,因此在 WebSocket 设置下,你应在自己这边暂停采集并停止调用 sendAudio(),直到需要重新开启麦克风。

你可以在构造时向 RealtimeSession 传入额外选项,或在调用 connect(...) 时进行配置。

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',
config: {
outputModalities: ['text', 'audio'],
audio: {
input: {
format: 'pcm16',
transcription: {
model: 'gpt-4o-mini-transcribe',
},
},
output: {
format: 'pcm16',
},
},
},
});

这些传输层允许你传入任意与 session 匹配的参数。

建议使用较新的配置结构:outputModalitiesaudio.inputaudio.output。为了向后兼容,诸如 modalitiesinputAudioFormatoutputAudioFormatinputAudioTranscriptionturnDetection 等旧的顶层字段仍会被规范化,但新代码应使用此处展示的嵌套 audio 结构。

对于新增且在 RealtimeSessionConfig 中没有匹配参数的项,你可以使用 providerData。传入 providerData 的内容会直接作为 session 对象的一部分传递。

可在构造时设置的其他 RealtimeSession 选项:

选项类型目的
contextTContext额外的本地上下文,会与会话上下文合并。
historyStoreAudioboolean在本地历史快照中存储音频数据(默认禁用)。
outputGuardrailsRealtimeOutputGuardrail[]会话的输出护栏(参见 Guardrails)。
outputGuardrailSettingsRealtimeOutputGuardrailSettings护栏检查的频率与行为。
tracingDisabledboolean禁用该会话的追踪。
groupIdstring跨会话或后端运行对追踪进行分组。
traceMetadataRecord<string, any>附加到会话追踪的自定义元数据。
workflowNamestring追踪工作流的友好名称。
automaticallyTriggerResponseForMcpToolCallsbooleanMCP 工具调用完成后自动触发模型响应(默认:true)。
toolErrorFormatterToolErrorFormatter自定义返回给模型的工具审批拒绝消息。

connect(...) 选项:

选项类型目的
apiKeystring | (() => string | Promise<string>)此连接使用的 API key(或惰性加载器)。
modelOpenAIRealtimeModels | string传输连接的可选模型覆盖。
urlstring可选的自定义 Realtime 端点 URL。
callIdstring附加到现有的由 SIP 发起的通话/会话。

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

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],
});

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

此外,这意味着在交接时不能更改 voicemodel。你也只能连接到其他实时智能体(Realtime Agents)。如果你需要使用不同的模型,例如 gpt-5-mini 这样的推理模型,可以使用通过工具进行委派

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

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 相同的环境中运行。这意味着如果你在浏览器中运行会话,工具也会在浏览器中执行。如果需要执行敏感操作,请在工具内向你的后端发起 HTTP 请求。

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

当前过滤后的 MCP 工具列表也可通过 session.availableMcpTools 获取。该属性和 mcp_tools_changed 事件仅反映活动智能体上启用的托管 MCP 服务器,并应用了智能体配置中的 allowed_tools 过滤器。

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

如果函数工具应在完成后不立即触发另一次模型响应,请从 @openai/agents/realtime 返回 backgroundResult(output)。这会将工具输出发送回会话,同时将是否触发响应的控制权保留在你手中。

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

除了智能体调用某个工具时使用的参数外,你还可以访问由 Realtime Session 跟踪的当前会话历史快照。如果你需要基于会话当前状态执行更复杂的操作,或计划通过工具进行委派,这将非常有用。

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 ?? [];
// making your call to process the refund request
},
});

如果你在定义工具时设置了 needsApproval: true,智能体会在执行工具前发出 tool_approval_requested 事件。

通过监听该事件,你可以向用户显示一个 UI 以批准或拒绝工具调用。

import { session } from './agent';
session.on('tool_approval_requested', (_context, _agent, request) => {
// show a UI to the user to approve or reject the tool call
// you can use the `session.approve(...)` or `session.reject(...)` methods to approve or reject the tool call
session.approve(request.approvalItem); // or session.reject(request.rawItem);
});

护栏提供了一种方式来监控智能体的发言是否违反了一组规则,并立即切断响应。这些护栏检查基于智能体响应的转录进行,因此需要启用模型的文本输出(默认启用)。

你提供的护栏会在模型响应返回时异步运行,允许你基于预定义的分类触发器切断响应,例如“提到特定的禁词”。

当护栏被触发时,会话会发出 guardrail_tripped 事件。该事件还会提供一个包含触发护栏的 itemIddetails 对象。

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 对象。

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
},
});

Realtime 会话会自动检测用户何时在说话,并使用 Realtime API 的内置语音活动检测模式触发新的轮次。

你可以在会话配置中通过传入 audio.input.turnDetection 来更改语音活动检测模式。

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

修改轮次检测设置有助于校准不必要的打断以及处理静音。查看Realtime API 文档以了解不同设置的更多细节

使用内置语音活动检测时,打断智能体说话会自动触发智能体根据所说内容检测并更新其上下文。它还会发出 audio_interrupted 事件。这可用于立即停止所有音频播放(仅适用于 WebSocket 连接)。

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

如果你想手动打断,例如在 UI 中提供一个“停止”按钮,你可以手动调用 interrupt()

import { session } from './agent';
session.interrupt();
// this will still trigger the `audio_interrupted` event for you
// to cut off the audio playback when using WebSockets

无论采用哪种方式,Realtime 会话都会同时处理对智能体生成的打断,截断其对已对用户所说内容的认知,并更新历史。

如果你使用 WebRTC 连接智能体,它还会清除音频输出。若使用 WebSocket,则需要自行停止已排队播放的音频。

如果你想向智能体发送文本输入,可以使用 RealtimeSessionsendMessage 方法。

当你希望用户既能通过语音也能通过文本与智能体交互,或为对话提供额外上下文时,这会很有用。

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

RealtimeSession 会在 history 属性中自动管理会话历史:

你可以使用它向客户渲染历史或对其执行其他操作。由于该历史会在对话过程中不断变化,你可以监听 history_updated 事件。

如果你想修改历史,例如完全移除一条消息或更新其转录,可以使用 updateHistory 方法。

import { RealtimeSession, RealtimeAgent } from '@openai/agents/realtime';
const agent = new RealtimeAgent({
name: 'Assistant',
});
const session = new RealtimeSession(agent, {
model: 'gpt-realtime',
});
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. 历史中的文本输出需要启用转录与文本模态
  3. 因打断而被截断的响应没有转录

Delegation through tools

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

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 actions 实现。

// 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-mini',
outputType: z.object({
reasong: 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);
}