连接到 GPT-Live 后,使用会话事件更新上下文、显示转写文本并管理连接的生命周期。模型可以同时听取语音和说话,因此请在您的应用中分别管理接收到的事件、音频播放和后端任务状态。
本指南假定您的连接已发出 session.started。有关连接设置和音频流式传输,请参阅连接;有关后端工作,请参阅委派与工具。
配置会话
创建会话时,选择模型、语音和委派模式。为模型提供对话指令,并包含相关历史记录。随着对话内容增加,GPT-Live 会自动管理上下文。
配置字段
| 设置 | 启动时配置 | 会话期间更改 |
|---|---|---|
| 模型 | 设置必填字段 model。 | 如需更改,请启动新会话。 |
| 指令 | 设置 instructions 以指定对话行为,最多可包含 16,384 个 Token。 | 使用 session.instructions.append 添加指令。 |
| 历史记录 | 将 input 设置为相关的历史文本消息。默认值为 []。 | 追加上下文,不要替换启动时的历史记录。 |
| 语音 | 将 audio.output.voice 设置为受支持的语音或已获授权的自定义语音。默认值为 marin。 | 如需更改,请启动新会话。 |
| 委派 | 将 delegation.type 设置为 client 或 responses。省略委派配置或将其设为 null 时,会选择客户端模式。 | 在现有模式下更新 Responses 设置。 |
| 存储 | 将 store 设置为 true,以便从该会话派生新会话。默认值为 false。 | 在启动时选择。 |
语音选项
创建会话时选择语音。将 audio.output.voice 设置为 API 名称,例如 "quartz"。GPT-Live 还提供以下语音选项:
| 语音 | API 名称 | 语言 | 地域风格 | 声音特征 | 来源 |
|---|---|---|---|---|---|
| Quartz | quartz | 英语 | 澳大利亚 | 女性化 | 生成 |
| Ripple | ripple | 英语 | 澳大利亚 | 男性化 | 自然 |
| Vesper | vesper | 英语 | 英国 | 男性化 | 自然 |
| Willow | willow | 英语 | 爱尔兰 | 女性化 | 自然 |
| Stone | stone | 英语 | 爱尔兰 | 男性化 | 自然 |
| Gleam | gleam | 英语 | 北美 | 女性化 | 自然语音 |
| Meridian | meridian | 英语 | 北美 | 男性化 | 自然语音 |
| Bossa | bossa | 葡萄牙语 | 巴西 | 女性化 | 自然语音 |
| Tempo | tempo | 葡萄牙语 | 巴西 | 男性化 | 自然语音 |
| Beacon | beacon | 英语 | 菲律宾 | 男性化 | 生成语音 |
| Delta | delta | 英语 | 美国南部 | 女性化 | 生成语音 |
| Cinder | cinder | 英语 | 美国南部 | 男性化 | 生成语音 |
地域影响描述的是音色的说话风格,并不保证口音的还原程度。如需使用您自己的录音创建经过批准的音色,请参阅自定义音色。
对于 WebSocket,请在启动时选择共用的 audio.format;会话期间无法更改此设置。对于 WebRTC,请省略此字段,因为连接会协商音频格式。有关格式和流式传输的详细信息,请参阅 WebSocket 音频格式。
更新正在运行的会话
在已使用 Responses 委派的会话中,使用 session.update 更改 session.delegation.responses。仅发送您要更改的设置;省略的设置会保留原值。有关设置和更新工作流程,请参阅配置 Responses 委派。
启动后无法更改委派模式。特别是,将 delegation 设置为 null 会选择客户端模式,并不会重置 Responses 会话。启动字段 model、instructions、input、audio 和 store 不可用于更新。未知的配置字段会被拒绝。
更新成功后会发出 session.updated,其中包含解析后的完整会话配置。如果您提供了 event_id,确认事件会将其作为 client_event_id 返回。除了确认事件,还应检查被拒绝的命令。收到接受确认仅表示配置已更新,并不代表后端任务已运行或模型已发声。
提供历史记录和上下文
使用启动时提供的历史记录继续之前的话题,并在对话进行过程中追加相关上下文。请将可信的应用指令与用户消息和事实性结果分开。
用之前的对话初始化会话
创建会话时,请在 session.input 中包含之前的文本消息。例如,将以下 input 字段添加到您的会话创建配置中:
export const session = {
model: "gpt-live-1",
input: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "I need help with my recent order.",
},
],
},
{
type: "message",
role: "assistant",
content: [
{
type: "output_text",
text: "What is the order number?",
},
],
},
],
};该列表最多接受 128 条消息,合计不超过 8,192 个 Token。支持的角色为 developer、user 和 assistant,每条消息包含一个文本部分。开发者消息和用户消息使用 input_text;助手消息使用 text 或 output_text。请将可信的应用指令放在 instructions 或开发者消息中。该列表不接受 system 角色。
请选择下一次交互所需的历史记录。input 是启动字段,不能用于在会话运行期间替换历史记录。它也并非支持 Responses 委派所使用的所有后端输入项。
了解上下文何时传入模型
创建会话时提供的完整 input 在会话启动时即可供模型使用。请将模型从一开始就需要的上下文放在此字段中。
会话运行期间,session.instructions.append、session.thinking.append 和 session.commentary.append 事件会逐步将内容传入模型。这些事件的确认会等待帧进度到达上下文注入的预计结束点。返回的 start_ms 和 end_ms 描述的是会话时间轴上的估计区间,并不表示发声或播放已完成。它们无法证明模型已处理整条更新。请勿假定模型接下来的发言会反映更新的全部内容。
如果帧进度停止,确认可能会一直处于待处理状态。关闭会话时,系统会针对待处理的追加操作报告错误。请通过 client_event_id 将每个确认事件与已发送的 event_id 匹配,并在等待期间继续处理错误。
在对话期间添加上下文
根据您希望模型如何使用更新内容来选择事件:
session.instructions.append:添加可信的应用指令,以影响模型的行为和发言。session.thinking.append:添加事实性上下文,不要求模型立即说出这些内容。session.commentary.append:提供要让模型说出的信息,模型可能会换一种说法表达。
每个事件接受最多 500 个 Token 的纯字符串 content,且必须提供 delegation_id。对于适用于整个会话的上下文,请使用 null。例如,在您的应用核实用户已同意并启动查询后,发送以下内容:
export function sendUpdate(connection) {
connection.send({
type: "session.thinking.append",
event_id: "context_1",
delegation_id: null,
content:
"The user has already accepted the terms. The account lookup is still running.",
});
}等待包含 client_event_id: "context_1" 的 session.thinking.appended,或处理错误。确认事件表示上下文已被接受,并不表示模型已发声、音频已播放或外部操作已完成。
不要求立即说出的上下文仍可能影响后续发言,不能将其视为隐私边界。请勿在这三种事件中的任何一种里包含凭据、机密信息或模型绝不能透露的文本。指令事件应用于应用编写的行为指令,不应用于不可信的工具输出。请在您的应用中落实权限控制和必要的确认流程。
对于页面导航、选择操作及其他 UI 变化,请参阅共享 UI 上下文,了解如何通过简洁的更新帮助 GPT-Live 理解用户所指的内容。
对于与后端任务相关的结果,请使用已知的客户端委派 ID,并遵循发送正确类型的更新中的说明。该 ID 不是 Responses 响应 ID,也不是工具调用 ID。
当应用检查被触发后,使用指令引导对话。您的服务器可以监控事件,并通过附加到现有会话的旁路 WebSocket 或该会话的主 WebSocket 发送这些纠正指令。有关并发检查、操作阻止和播放控制的信息,请参阅应用对话护栏。
构建对话界面
转写文本和麦克风状态的显示应独立于后端进度。收到助手文本并不能说明用户已听到了多少音频。
转写文本增量
监听 session.input_transcript.delta 以获取用户语音的转写文本,监听 session.output_transcript.delta 以获取助手语音的转写文本。每个事件都包含一个文本片段及其在会话时间线上的区间:
{
"type": "session.input_transcript.delta",
"event_id": "event_transcript_1",
"delta": "What is",
"start_ms": 1000,
"end_ms": 1200
}
按顺序为每位说话者追加片段,并保留 start_ms 和 end_ms。这两个值以毫秒为单位,表示会话时间线上的位置,区间包含起点但不包含终点。它们并非实际时钟时间戳、数据包到达时间或精确的逐词对齐时间。
只有包含转写文本的区间才会产生事件,而且网络传输可能不均匀。不要因未收到事件就推断没有人说话,也不要将单个片段视为完整的用户话轮。转写文本增量不包含条目 ID,也没有可用于确定话轮已结束的事件。
处理转写文本片段是可选的。您可以在对话继续进行时,利用这些片段更新 UI、运行检查或提前开始工作。对于轻量检查,可以考虑使用 gpt-5.6-luna 等小型模型,并将推理强度设为低。有关示例和连接指导,请参阅响应转写文本片段。
要实现对话护栏,请在用户和助手文本到达时检查已累积的文本。转写文本的传输并不会提供提前缓冲,让您在语音播放前进行审批。请参阅按需控制播放。
如果您的界面将文本按话轮分组,应允许后续调整分组。保留原始片段,允许用户和助手的时间区间重叠,并根据录制的对话调节间隔超时设置。另一位说话者的简短应答可能属于正在进行的交流。片段分组本身不得触发工具执行或取消后端工作。
将转写文本的时间信息与音频播放分开处理。WebSocket 的 session.output_audio.delta 事件不包含时间字段,也没有输出音频完成事件;WebRTC 通过其媒体轨道传输音频。有关音频处理,请参阅连接。
显示字幕
构建可在双方同时说话时持续追加文本的字幕行:
- 保留原始文本。 存储每位说话者的原始
delta、start_ms和end_ms。完全按接收时的原样拼接文本,包括空格和重复的词语。不要去除片段首尾的空白,也不要在片段之间插入空格。 - 分别更新每位说话者的字幕。 允许用户和助手的字幕行在双方同时说话时持续追加文本。中断后仍应显示助手之前的文本,并在助手恢复说话时另起一行。
- 保持字幕行稳定。 在应用中分配显示 ID,并在文本增加时保持行顺序。不要根据不断变化的文本或结束时间戳来确定行标识,也不要每收到一个片段就将对应行移到底部。
- 根据迟到的片段调整分组。 使用转写文本时间戳,将同一位说话者在时间上相近的片段分为一组。允许迟到的文本更新较早的行,并调整片段所属的分组,同时保留原始片段。这些显示分组并不代表语义完整的话轮;任何间隔阈值都由应用自行设定,并需经过测试。
- 让读者控制滚动。 当读者位于底部时,跟随新文本滚动。当读者向上滚动时,暂停自动滚动,并提供返回最新字幕的方式。
- 在状态区域显示工具进度。 使用助手转写文本事件显示语音字幕。在字幕之外显示工具活动和后端结果;收到结果并不意味着助手已经说出了该结果。
测试以下场景中的显示效果:双方同时说话、简短应答、中断、长时间停顿,以及双方文本以不同速率到达的翻译场景。
控制麦克风输入
发送 session.input_audio.mute,即可在不结束会话的情况下将输入静音:
export function sendUpdate(connection) {
connection.send({
type: "session.input_audio.mute",
event_id: "mute_1",
});
}等待收到带有 client_event_id: "mute_1" 的 session.input_audio.muted 后,才能认为命令已被接受。要恢复输入,请发送 session.input_audio.unmute 并等待 session.input_audio.unmuted。这两个命令的错误都需要处理。
将输入静音不会停止推断、委派的工作或生成的语音。如需控制麦克风采集和音频播放,请在应用中分别实现这些控制。
在来电者开口前问候
要在 session.started 之后请求问候,请执行以下操作:
- 发送一个新的
session.instructions.append,并设置delegation_id: null。其中应包含问候语、所用语言,以及明确的指令:无需等待来电者开口,立即问候,然后暂停并倾听。保留现有的启动指令。 - 等待
session.instructions.appended,并将其client_event_id与您的命令匹配。如果命令被拒绝,请先处理再继续。 - 保持输入音频持续传输,包括来电者开口前的静音。在 WebSocket 上,继续发送
session.input_audio.append;在 WebRTC 上,保持协商好的输入音频轨道处于活动状态。观察输出转写文本和音频中是否出现问候。
在来电者开口之前,使用应用指定的语言;不要根据姓名、电话号码或位置推断语言。有关提示设计,请参阅为语音模型编写提示。
如果问候需要遵循应用指令,请先通过 session.instructions.append 发送这些指令,然后使用一条简短的 session.commentary.append 提示助手开始。例如:“Begin the conversation now, following the instructions provided.”保持输入音频持续传输,包括来电者开口前的静音。
指令只是请求进行问候,并不能保证措辞完全一致或播放不被中断。API 不会发出开场完成事件,收到确认也不意味着用户已听到问候。如果音频必须逐字准确,请由应用控制播放。请针对应用支持的语言和中断场景测试问候。
播报告知声明
使用 session.instructions.append 请求按指定措辞播报告知声明。session.commentary.append 可能会改述文本。例如,在 session.started 之后发送:
export function sendUpdate(connection) {
connection.send({
type: "session.instructions.append",
event_id: "disclosure_1",
delegation_id: null,
content:
"Immediately say the following disclosure exactly and in full before responding to the caller: This call may be recorded for quality and training purposes.",
});
}按照在来电者开口前问候中的说明,保持输入音频持续传输。请慎重选择播报时机:在对话中发送指令可能会打断正在进行的语音。
这只是请求使用指定措辞,并不能保证实际播报完全一致。在将告知声明标记为已送达之前,请验证完整的语音声明和实际播放情况。session.instructions.appended 仅确认指令已被接受。如果必须准确播放音频,请通过应用播放经过验证的录音或渲染生成的音频片段,并在播放期间控制 GPT-Live 的输出。请参阅按需控制播放。
管理较长的对话
GPT-Live 会在长对话中自动管理上下文,无需配置参数。您在会话开始时提供的指令会在整个压缩过程中保留,无需重新发送。
默认上下文窗口可容纳 128,000 个 Token,包括您的指令、对话文本,以及不会出现在转写文本中的音频 Token。
GPT-Live 会在后台总结较早的对话历史。当上下文使用率超过 90% 时,它会在同一会话内启动一个替代语音引擎。替代引擎会接收您的原始指令,以及最多 8,192 个 Token 的对话历史,其中包含近期消息,以及较早消息的摘要(如果有)。准备摘要不会立即改变当前运行引擎的上下文。
较早的对话细节可能会被总结或省略。请在应用中保存重要事实、已确认的操作和当前任务状态,并在需要时提供相关上下文。
存储和派生会话
创建会话时,在会话配置中将 store 设为 true,即可保存录音供以后下载或派生会话。存储设置默认为 false,且必须为您的项目启用存储功能。下载和派生会话需要已完成并存储的录音,以及允许持久化存储的数据政策。录音会在 30 天后过期。启用零数据保留时,store 会被视为 false,且无法下载录音或派生会话。请参阅 GPT-Live 数据控制。
例如,将此字段添加到 WebSocket session.start 事件或 WebRTC 创建请求中的 session 对象:
{
"store": true
}
保存 session.started 或 WebRTC 创建响应中的源会话 ID。派生操作会根据存储的会话状态启动一个 具有新 ID 的新会话。它不会重新打开原始连接,也不会复用源会话 ID。
通过应用使用的传输方式启动派生会话:
| 传输方式 | 启动派生会话 |
|---|---|
| WebSocket | 连接到 wss://api.openai.com/v1/live/sessions/{source_session_id}/fork。 |
| WebRTC | 向 POST /v1/live/sessions/{source_session_id}/fork 发送新的 SDP offer。将返回的 transport.sdp answer 应用到新的对等连接。 |
派生会话会继承已存储的会话配置,但需遵循下文的传输规则。对于 WebSocket 派生会话,发送 session.start 时必须包含 session 对象;{} 表示不覆盖任何设置。不要提供新模型,也不要重复提供原始指令或输入。您可以覆盖 store、Responses 委派设置以及新 WebSocket 的音频格式。WebRTC 派生会话可以覆盖 store、Responses 委派设置和前端客户端权限。在派生会话时省略 store,会继承源会话的设置。
WebSocket 派生会话 不会 继承源音频格式:请显式设置 audio.format,或使用默认的 24 kHz PCM16。它还会丢弃继承的前端数据通道权限。WebRTC 派生会话会协商音频格式,并拒绝 audio.format;除非您覆盖前端权限设置,否则这些设置会保留。
等待 session.started 后再发送其他 WebSocket 命令。WebRTC 通过 HTTP 请求启动,不得在其数据通道上再次接收 session.start。
启动 WebSocket 派生会话
设置 OPENAI_API_KEY。这些示例使用应用保存的已存储源会话的 ID。它们会确认启动成功,然后关闭派生会话。要继续对话,请在 session.started 之后按照 WebSocket 连接流程发送和接收音频。有关启动字段和事件,请参阅派生会话 WebSocket 参考资料。
import OpenAI from "openai";
import { ForksWS } from "openai/resources/live/forks/ws";
async function forkSession(sourceSessionId) {
const ws = new ForksWS(new OpenAI(), { session_id: sourceSessionId });
let finalized = false;
try {
for await (const event of ws) {
if (event.type === "open") {
ws.send({ type: "session.start", session: {} });
} else if (event.type === "error") {
throw event.error;
} else if (event.type === "message") {
if (event.message.type === "session.started") {
console.log("Fork ready:", event.message.session.id);
// This startup example closes the fork after confirming it is ready.
ws.send({ type: "session.close" });
} else if (event.message.type === "session.closed") {
console.log("Final usage:", event.message.usage);
finalized = true;
break;
}
}
}
if (!finalized) throw new Error("Connection closed before session.closed");
} finally {
ws.close();
}
}启动 WebRTC 派生会话
在前端创建新的 SDP offer,并将其发送到后端。以下后端示例使用该 offer 和应用保存的已存储源会话的 ID:
import OpenAI from "openai";
async function forkSession(sourceSessionId, offerSdp) {
const client = new OpenAI();
const fork = await client.live.sessions.fork(sourceSessionId, {
transport: { type: "webrtc", sdp: offerSdp },
});
console.log(JSON.stringify(fork));
}将响应返回给前端,将 transport.sdp 作为新对等连接的应答应用到该连接,并保留新的 session.id。请将 API 密钥保存在后端。
后续的带外连接和会话控制请使用新的会话 ID。单独保存应用的任务状态:恢复对话状态并不能确认尚未完成的后端操作已经完成。重试操作前,请核实尚不确定的结果。如果没有可供派生的已存储会话,请使用已保存的历史记录初始化新会话。
下载录音
待存储的录音完成收尾后,使用 GET /v1/live/sessions/{session_id}/content 下载其音频。响应为二进制立体声 WAV,左声道为输入音频,右声道为输出音频。以下示例使用应用中保存的会话 ID,并将响应以流式方式写入 recording.wav:
import OpenAI from "openai";
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
async function downloadRecording(sessionId) {
const client = new OpenAI();
const response = await client.live.sessions.downloadRecording(sessionId);
if (!response.body) throw new Error("Recording response has no body");
await pipeline(response.body, createWriteStream("recording.wav"));
}处理错误并结束会话
持续读取会话事件,直到会话完成收尾。区分命令被拒绝、连接失败和会话已完成这几种情况,以便应用采取适当的恢复措施。
处理被拒绝的命令
读取确认消息的同时,也要读取 error 事件。如果存在 error.client_event_id,它会标识已发出但执行失败的命令:
{
"type": "error",
"event_id": "event_error",
"error": {
"type": "invalid_request_error",
"code": "immutable_field_update",
"message": "The delegation type cannot change after session startup.",
"param": "session.delegation.type",
"client_event_id": "event_update"
}
}
错误代码可能为 null,错误也可能不包含客户端事件 ID。处理这些情况时,不要假定命令已成功执行。对于不可变字段错误,请保留当前配置,或使用所需设置创建新会话。
处理内容审核
内容审核可能通过两种方式影响会话:
- 某些内容审核事件会结束会话。
- 其他事件会截断助手当前发言的剩余音频,并发出
error事件,但不会结束会话。
即使正在播放音频,也要读取 error 事件。不要假定每个内容审核错误都会关闭会话,也不要认为音频中断就意味着连接失败。让应用状态与会话生命周期保持一致,不要将被中断的语音消息标记为已完整送达。应用层的对话护栏与这种内置内容审核行为相互独立。
用量与优雅关闭
session.usage.updated 以秒为单位报告累计语音时长:
{
"type": "session.usage.updated",
"event_id": "event_usage_1",
"usage": { "seconds": 12 },
"context_window": { "usage_ratio": 0.42 }
}
这些是快照,而非可累加的增量。后端 Token 用量单独统计;请从嵌套的 Responses 完成事件中获取并保留这些数据。有关用量核算,请参阅成本优化。
要优雅关闭会话,请执行以下步骤:
- 完成应用所需的所有委派给 Responses 的工作,包括尚待返回的函数结果和后续响应处理。
- 在发送
session.close之前,先注册session.closed监听器。 - 发送
session.close,并停止向会话提交新工作。在等待剩余会话事件处理完毕期间,保持 WebSocket 或 WebRTC 连接、数据通道以及任何已连接的带外接收器处于活动状态。 - 从
session.closed中读取最终的usage.seconds、reason和会话快照。保留已通过response.event收到的委派工作用量数据。 - 收到该事件后,再清理传输连接和音频设备。如果收尾失败或超过应用设置的超时时间,请报告收尾未完成并释放资源。
发送 session.close 会取消排队中的 Responses 请求,并拒绝后续命令。正在执行的响应可以完成,但等待函数结果的响应在关闭流程开始后无法继续。对于应用通过客户端委派执行的工作,请单独决定是完成还是取消。
session.closed 事件用于确认收尾已完成;其中嵌入的会话是配置快照。仅凭套接字关闭无法确认成功,而在有效的最终事件之后收到的传输层关闭代码也不会推翻收尾已完成的事实。发送命令后立即关闭 WebRTC,可能导致最终事件无法送达。
最终事件中的 reason 说明了会话结束的原因:
| 原因 | 含义 |
|---|---|
close_requested | 您的应用发送了 session.close 或调用了挂断端点。 |
expired | 会话已达到时长上限。 |
content | 安全过滤器结束了会话。 |
remote_hangup | 远程主连接已优雅关闭。 |
connection_lost | 主连接或上游连接意外断开。 |
即使会话因连接断开或安全原因而终止,session.closed 事件仍可确认收尾已完成。如果未收到该事件,最终用量仍未确认。对于启用了存储的会话,保存录音可能使收尾耗时更长;请在设置应用超时时间时考虑存储所需的时间。
从连接失败中恢复
创建会话时发生 HTTP 错误,意味着会话尚未到达 session.started 阶段。请将启动错误与运行中会话的错误分开处理。如果正在运行的连接在 session.closed 之前失败,请保留最近一次观测到的用量,并将最终用量标记为未确认。
如果有可用的已存储会话,请从该会话派生新会话,以便从其保存的状态开始。否则,请使用已保存的相关历史记录创建替代会话。重试尚未完成的操作前,请与后端核实其状态,并屏蔽上一会话的过期结果。请显式恢复应用状态,不要假定新连接会恢复上一会话或其中尚未完成的工作。