OpenAI 管理 GPT-Live 会话
在连接 GPT-Live后,使用会话事件添加上下文、显示转录文本并管理连接。GPT-Live 可以同时聆听和说话。应分别追踪转录文本、已经播放的音频和后端任务进度,让界面能显示助手正在说什么,以及哪些工作仍在运行。
本指南假定连接已发出 session.started。连接设置和音频流见连接指南,后端工作见委派与工具。
配置会话
在创建会话时选择模型、声音与委派模式。为模型提供对话指令和相关历史。对话增长时,GPT-Live 会自动管理上下文。
配置字段
| 设置 | 启动时配置 | 会话运行期间如何更改 |
|---|---|---|
| 模型 | 设置必填的 model。 |
更换时启动新会话。 |
| 指令 | 用 instructions 设置对话行为,最多 16,384 个词元。 |
通过 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 |
英语 | 美国南部 | 偏男性化 | 生成 |
| Nuri | nuri |
德语 | — | 偏女性化 | 自然 |
| Juni | juni |
德语 | — | 偏女性化 | 自然 |
| Flitz | flitz |
德语 | — | 偏男性化 | 自然 |
| Brise | brise |
法语 | — | 偏女性化 | 自然 |
| Sillage | sillage |
法语 | — | 偏男性化 | 自然 |
| Nira | nira |
印地语 | — | 偏女性化 | 自然 |
| Harema | harema |
日语 | — | 偏女性化 | 自然 |
| Shida | shida |
日语 | — | 偏男性化 | 自然 |
| Noeul | noeul |
韩语 | — | 偏女性化 | 自然 |
地区特征描述声音的说话风格。应使用应用需要的语言和发音测试声音。根据文字描述或获授权录音创建声音的方法见自定义声音。
WebSocket 在启动时选择 audio.format,同一会话的输入与输出音频共用该格式;要更换格式,需要启动新会话。WebRTC 在建立连接时协商音频格式,因此 WebRTC 请求中应省略 audio.format。受支持的格式和流传输详情见 WebSocket 音频格式。
更新正在运行的会话
在已经使用 Responses 委派的会话中,通过 session.update 更改 session.delegation.responses。只发送需要更改的设置;未提供的设置保留原值。可配置字段及更新流程见配置 Responses 委派。
委派模式以及 model、instructions、input、audio、store 均在启动时选择。session.update 仅用于上述受支持的 Responses 设置,其他配置字段会被拒绝。切换委派模式要创建新会话。启动时的 delegation: null 表示选择客户端委派,并非恢复默认 Responses 设置。
更新成功会发出带有结果配置的 session.updated。通过其中的 client_event_id 匹配发出的 event_id。在同一事件循环中处理被拒绝的命令。后端工作和语音输出应通过各自事件追踪。
提供历史与上下文
用启动历史恢复话题,随着对话继续追加相关上下文。可信的应用指令应与用户消息、事实性结果分开。
用既有对话初始化会话
在创建会话时,通过 session.input 包含既有文本消息。例如,向会话创建配置添加以下 input 字段:
来源示例 1(javascript):
原文 Markdown 的此示例为空;以下代码由同页所提供的 HTML 示例补回。
程序文字译释24:最近的订单需要帮助。
程序文字译释25:订单号是多少?
// [中文] 示例 1:用既有对话初始化会话;以下保留本语言原始示例。
// [中文] 原代码明确设置的模型值:gpt-live-1;各值的作用按其所在配置对象区分。
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?",
},
],
},
],
};
来源示例 2(python):
沿用程序文字译释24。
沿用程序文字译释25。
## [中文] 示例 2:用既有对话初始化会话;以下保留本语言原始示例。
## [中文] 构造启动历史:用户消息的内容用 input_text,助手历史用 output_text;保留两条消息的先后顺序。
## [中文] 这里只定义 session 配置,尚未创建连接;启动会话时才把此对象交给 SDK 或创建请求。
from openai.types.live.session_config_param import SessionConfigParam
session: SessionConfigParam = {
"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?"}],
},
],
}
// [中文] 定义会话启动配置,区分Live语音模型与委派后端设置;配置对象本身不会建立连接、执行工具或自动运行后端。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"github.com/openai/openai-go/v3/live"
)
var session = live.SessionConfigParam{Model: "gpt-live-1", Input: []live.InitialItemUnionParam{
{OfUser: &live.InitialItemUserParam{Content: []live.InitialItemUserContentParam{{Text: "I need help with my recent order."}}}},
{OfAssistant: &live.InitialItemAssistantParam{Content: []live.InitialItemAssistantContentUnionParam{{OfOutputText: &live.InitialItemAssistantContentOutputTextParam{Text: "What is the order number?"}}}}},
}}
// [中文] 定义会话启动配置,区分Live语音模型与委派后端设置;配置对象本身不会建立连接、执行工具或自动运行后端。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.models.live.*;
var session =
SessionConfig.builder()
.model("gpt-live-1")
.addInput(
InitialItem.User.builder()
.addContent(
InitialItem.User.Content.builder()
.text("I need help with my recent order.")
.build())
.build())
.addInput(
InitialItem.Assistant.builder()
.addContent(
InitialItem.Assistant.Content.ofOutputText(
InitialItem.Assistant.Content.OutputText.builder()
.text("What is the order number?")
.build()))
.build())
.build();
## [中文] 定义会话启动配置,区分Live语音模型与委派后端设置;配置对象本身不会建立连接、执行工具或自动运行后端。
require "openai"
session = OpenAI::Models::Live::SessionConfig.new(
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 个词元。每条消息包含一个文本内容部分,以及 developer、user 或 assistant 角色。开发者与用户消息使用 input_text;助手消息使用 text 或 output_text。可信应用指令放在 instructions 或开发者消息中。
启动时只提供下一次交互需要的文本历史。运行期间使用下述上下文事件追加更新。工具结果等仅供后端使用的项目,通过委派工作流发送。
理解上下文何时到达模型
模型从一开始就需要的上下文应放在 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 个词元的纯字符串 content,并必须提供 delegation_id。会话级上下文使用 null。例如,应用验证用户接受并已开始查询后,可以发送:
来源示例 3(javascript):
程序文字译释26:用户已接受条款,账户查询仍在运行。
// [中文] 示例 3:对话期间添加上下文;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.thinking.append;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
// [中文] 委派 ID 与响应、工具调用 ID 不同;追加上下文时,非空 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.",
});
}
来源示例 4(python):
程序文字译释27:用户已接受条款,账户查询仍在运行;源程序把句子拆成相邻字符串。
## [中文] 示例 4:对话期间添加上下文;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:connection.session.thinking.append;异步调用及事件接收顺序保留。
## [中文] 委派 ID 与响应、工具调用 ID 不同;追加上下文时,非空 delegation_id 须对应客户端委派,null 用于会话级上下文。
from openai.resources.live.live import AsyncLiveConnection
from openai.resources.live.sideband import AsyncSidebandConnection
async def send_update(
connection: AsyncLiveConnection | AsyncSidebandConnection,
) -> None:
await connection.session.thinking.append(
event_id="context_1",
delegation_id=None,
content=(
"The user has already accepted the terms. The account lookup is still "
"running."
),
)
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/live"
"github.com/openai/openai-go/v3/packages/param"
)
// SendUpdate requires a started Live session.
func SendUpdate(ctx context.Context, connection *live.LiveConnection) error {
return connection.Send(ctx, live.ClientEventUnionParam{OfSessionThinkingAppend: &live.ThinkingAppendEventParam{
EventID: openai.String("context_1"),
DelegationID: param.Null[string](),
Content: "The user has already accepted the terms. The account lookup is still running.",
}})
}
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.core.JsonField;
import com.openai.helpers.LiveConnection;
import com.openai.models.live.*;
public static void sendUpdate(LiveConnection connection) {
connection.send(
ClientEvent.ofSessionThinkingAppend(
ThinkingAppendEvent.builder()
.eventId("context_1")
.delegationId(JsonField.<String>ofNullable(null))
.content(
"The user has already accepted the terms. The account lookup is still running.")
.build()));
}
## [中文] 将事实或进度作为thinking上下文追加到会话;保留event_id与delegation_id的作用,不能把上下文送达确认当成已经播报。
def send_update(connection)
connection.send_event(type: "session.thinking.append", event_id: "context_1", delegation_id: nil, content: "The user has already accepted the terms. The account lookup is still running.")
end
处理 client_event_id: "context_1" 的 session.thinking.appended 或对应错误,追踪此更新。确认时机见理解上下文何时到达模型。
通过这些事件提供的任何信息,助手都有可能复述。只发送适合出现在对话中的信息,凭证与秘密留在后端。应用定义的行为使用 session.instructions.append;事实性工具结果作为上下文提供;权限和必要确认由应用代码执行。
页面导航、选中内容和其他界面变化,可参阅共享界面上下文,通过精简更新帮助 GPT-Live 理解用户的指代。
更新针对特定后端任务时,使用相关客户端委派的 ID。委派 ID 标识 Live 任务,而 Responses 响应 ID、工具调用 ID 标识其他对象,不能混用。流程见发送正确类型的更新。
应用发现问题时,通过会话主 WebSocket 或旁路 WebSocket发送简短纠正。检查、操作控制与播放处理见应用对话防护规则。
管理语音与转录文本
转录增量
监听 session.input_transcript.delta 获取用户语音文本,监听 session.output_transcript.delta 获取助手语音文本。每个事件包含一个文本片段和它在会话时间线上的区间:
来源示例 5(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "session.input_transcript.delta",
"event_id": "event_transcript_1",
"delta": "What is",
"start_ms": 1000,
"end_ms": 1200
}
按收到的原样追加各讲话者的 delta,保留空格、重复词以及 start_ms、end_ms。时间单位是从会话开始计算的毫秒。上例覆盖从 1,000 毫秒开始、到 1,200 毫秒之前的区间。这是片段时间的近似值,而非精确单词边界;应使用它组织转录,而不是使用数据包到达时间。
仅在包含文本的时间区间产生转录事件,交付节奏可能不均匀。一个片段可能只是半句话;交付空档也可能是网络延迟。转录增量没有项目 ID,也没有标记一个对话轮次结束的事件,因此由应用决定如何分组显示。
处理转录片段是可选的。可以用它更新界面、运行检查,或在对话继续时提前启动工作。轻量检查可考虑使用 gpt-5.6-luna 等小模型并设置低推理强度。示例和连接指导见响应转录片段。
用转录防护规则在讲话继续时监控对话并触发干预。若必须在播放前检查助手语音,缓冲、批准、打断与恢复处理见播放前检查语音。
转录时间与音频播放必须分开管理。WebSocket 的 session.output_audio.delta 不包含时间字段,也没有输出音频完成事件;WebRTC 则通过媒体轨道交付音频。音频处理见连接指南。
显示字幕
GPT-Live 是全双工的,来电者与助手可以同时说话。应独立更新双方字幕,使重叠讲话期间两边的文字都能继续增长。
如果应用使用聊天气泡,“我想”和“修改我的预订”可以出现在同一个来电者气泡。来电者继续讲话时,若助手说“好的”,应单独显示该回应,同时允许用户气泡继续增长。保留原始片段和时间戳,让较晚到达的文本仍能更新正确气泡。
用 session.output_transcript.delta 显示实际语音字幕,后端更新单独展示。工具执行或取消工作的决定属于应用任务逻辑,应与显示文字的分组方式分开。
控制麦克风输入
发送 session.input_audio.mute 可静音输入而不结束会话:
来源示例 6(javascript):
// [中文] 示例 6:控制麦克风输入;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.input_audio.mute;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
export function sendUpdate(connection) {
connection.send({
type: "session.input_audio.mute",
event_id: "mute_1",
});
}
来源示例 7(python):
## [中文] 示例 7:控制麦克风输入;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:connection.session.input_audio.mute;异步调用及事件接收顺序保留。
from openai.resources.live.live import AsyncLiveConnection
from openai.resources.live.sideband import AsyncSidebandConnection
async def send_update(
connection: AsyncLiveConnection | AsyncSidebandConnection,
) -> None:
await connection.session.input_audio.mute(
event_id="mute_1",
)
// [中文] 更新会话的麦克风输入控制;静音不等同于取消后端工作或停止模型已生成的语音。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/live"
)
// SendUpdate requires a started Live session.
func SendUpdate(ctx context.Context, connection *live.LiveConnection) error {
return connection.Send(ctx, live.ClientEventUnionParam{OfSessionInputAudioMute: &live.InputAudioMuteEventParam{
EventID: openai.String("mute_1"),
}})
}
// [中文] 更新会话的麦克风输入控制;静音不等同于取消后端工作或停止模型已生成的语音。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.helpers.LiveConnection;
import com.openai.models.live.*;
public static void sendUpdate(LiveConnection connection) {
connection.send(
ClientEvent.ofSessionInputAudioMute(
InputAudioMuteEvent.builder().eventId("mute_1").build()));
}
## [中文] 更新会话的麦克风输入控制;静音不等同于取消后端工作或停止模型已生成的语音。
def send_update(connection)
connection.send_event(type: "session.input_audio.mute", event_id: "mute_1")
end
等待带有 client_event_id: "mute_1" 的 session.input_audio.muted,再认定命令被接受。恢复输入时发送 session.input_audio.unmute,等待 session.input_audio.unmuted。两类命令都要处理错误。
输入静音后,会话仍在运行:模型可以继续生成语音,被委派工作也可以继续。还需停止本地录音或播放时,使用应用自身的麦克风采集与播放器控制。
在来电者说话前问候
如果希望 GPT-Live 主动开场,为获得较好效果,应在创建会话时,将问候指令放入 session.input 的开发者消息。WebSocket 中,将该消息包含在 session.start 事件中,使指令从启动时就可用。
指定语言、助手应说的话,以及立即开口、随后停下来聆听的要求。来电者开始讲话前,使用应用选择的问候语言。例如开发者消息可以写为:
现在用英语问候来电者。介绍自己是客服助手,并询问有什么可以帮助的。然后停下来聆听。
- 将消息添加到
session.input,设置role: "developer",并包含一个input_text内容部分。消息格式见用既有对话初始化会话。 - 等待
session.started,然后通过session.commentary.append发送一次相同的问候指令,使用唯一event_id和delegation_id: null,如下例。保持输入音频运行,包括来电者开口前的静音:WebSocket 持续发送session.input_audio.append,WebRTC 保持输入音轨活动。 - 检查输出音频和转录文本,验证助手是否问候了来电者。启动指令可以改善问候表现,但不保证模型一定发声。
来源示例 8(json):
程序文字译释28:现在用英语问候来电者,自我介绍为客服助手,问有什么可帮忙的,然后停下来聆听。
{
"type": "session.commentary.append",
"event_id": "greeting_1",
"delegation_id": null,
"content": "Greet the caller now in English. Introduce yourself as the support assistant and ask how you can help. Then pause and listen."
}
session.commentary.appended 只确认请求,不代表问候已说出或播放。GPT-Live 可能改写、增加措辞,或保持沉默。
需要可预测的问候播放、精确措辞或明确播放完成点时,由应用播放经过核验的录音或已生成片段,并在播放期间控制 GPT-Live 输出。用支持的各语言测试问候,包括来电者在问候期间开始讲话的情况。提示词设计见语音模型提示词。
播报告知声明
需要特定的告知声明措辞时,使用 session.instructions.append;session.commentary.append 可能改写文本。例如在 session.started 后发送:
来源示例 9(javascript):
程序文字译释29:回应来电者前,立即逐字、完整说出:“本通话可能因质量和培训目的被录音。”
// [中文] 示例 9:播报告知声明;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.instructions.append;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
// [中文] 委派 ID 与响应、工具调用 ID 不同;追加上下文时,非空 delegation_id 须对应客户端委派,null 用于会话级上下文。
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.",
});
}
来源示例 10(python):
程序文字译释30:回应来电者之前,立即完整、准确地说出以下告知声明;该指令在源程序分成相邻字符串。
程序文字译释31:声明前半:本通话可能因质量和……
程序文字译释32:声明后半:培训目的被录音。
## [中文] 示例 10:播报告知声明;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:connection.session.instructions.append;异步调用及事件接收顺序保留。
## [中文] 委派 ID 与响应、工具调用 ID 不同;追加上下文时,非空 delegation_id 须对应客户端委派,null 用于会话级上下文。
from openai.resources.live.live import AsyncLiveConnection
from openai.resources.live.sideband import AsyncSidebandConnection
async def send_update(
connection: AsyncLiveConnection | AsyncSidebandConnection,
) -> None:
await connection.session.instructions.append(
event_id="disclosure_1",
delegation_id=None,
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."
),
)
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/live"
"github.com/openai/openai-go/v3/packages/param"
)
// SendUpdate requires a started Live session.
func SendUpdate(ctx context.Context, connection *live.LiveConnection) error {
return connection.Send(ctx, live.ClientEventUnionParam{OfSessionInstructionsAppend: &live.InstructionsAppendEventParam{
EventID: openai.String("disclosure_1"),
DelegationID: param.Null[string](),
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.",
}})
}
// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.core.JsonField;
import com.openai.helpers.LiveConnection;
import com.openai.models.live.*;
public static void sendUpdate(LiveConnection connection) {
connection.send(
ClientEvent.ofSessionInstructionsAppend(
InstructionsAppendEvent.builder()
.eventId("disclosure_1")
.delegationId(JsonField.<String>ofNullable(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.")
.build()));
}
## [中文] 追加由应用编写的会话指令,区分会话级更新与任务委派;不要把不可信用户文本直接升级成应用指令。
def send_update(connection)
connection.send_event(type: "session.instructions.append", event_id: "disclosure_1", delegation_id: nil, 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.")
end
如在来电者说话前问候所述,保持输入音频运行。在对话期间发送的指令可能打断正在进行的讲话。
将声明标记为已送达前,检查生成内容和播放情况。指令确认只记录已接受;核对实际措辞要检查音频本身。需要精确措辞和明确播放完成点时,由应用播放经过验证的录音或生成片段,并在播放时控制 GPT-Live 输出。见按需控制播放。
管理较长对话
GPT-Live 自动管理长对话,并保留最初的启动指令。
默认上下文窗口容纳 128,000 个词元,包含指令、对话文本和未体现在转录中的音频词元。
GPT-Live 在后台总结较早对话。上下文使用量超过 90% 时,会在同一会话内启动替代语音引擎。替代引擎接收原始指令和最多 8,192 个词元的对话历史,其中包括近期消息,以及可用时的较早消息摘要。准备摘要不会立即改变当前引擎的上下文。
较早的对话细节可能被总结或省略。重要事实、已确认操作和当前任务状态应保存在应用中,并按需提供相关上下文。
存储与分叉会话
分叉会从保存的语音对话启动一个新会话,可用于从同一参考对话重复评估,也可让用户在先前会话结束后继续。每个分叉都有新的连接与会话 ID,以来源对话及保存的配置为起点。
从参考对话运行评估
例如,要测试智能体如何处理来电者修改订单,可先录制一次准备过程,到来电者已说明订单身份为止;在其提出修改要求之前结束并完成该会话的最终处理。每次评估都从相同参考会话分叉,接收相同的下一段来电者音频:“其实,可以改送到我的办公室吗?”
每次试验恢复相同的测试订单和应用状态,提供下一段用户输入,再评估新回复与工具操作。可重复场景,或比较受支持的 Responses 后端设置。分叉启动耗时与响应时间应分开测量。场景选择和结果衡量见 GPT-Live 评估指南。
分叉继承 GPT-Live 模型、声音和原始指令。要比较其他语音模型或启动提示词,应按新配置创建新会话。分叉 API 使用已完成的来源录音,因此参考会话应恰好结束在希望开始评估的位置。
会话结束后继续
例如,来电者挂断后再次来电,或通话中断后重新连接。如果先前会话存在已完成的存储录音,应用就可以在新连接上分叉,从保存的对话继续。
应用任务状态与来源会话 ID 一并保存。继续之前,检查未完成后端工作的状态,并向新会话提供当前结果。例如,订单更新已提交时,先确认结果,再尝试下一次更新。控制操作和旁路连接使用新会话 ID,后续后端结果也路由到新会话。
准备可分叉的会话
- 创建来源会话时启用存储。在会话配置中设置
store: true。默认false;项目必须已启用存储,数据政策也必须允许持久化。 - 保存来源会话 ID。从
session.started或 WebRTC 创建响应中读取,并与应用的对话记录关联。 - 结束并关闭来源。完成必要后端工作,再按用量与正常关闭处理。保持连接,直到
session.closed,并处理最终收尾错误。分叉需要已完成的存储录音,保存录音会增加收尾时间。 - 在新连接启动分叉。按下表流程使用来源 ID,保存新 ID,并在继续对话前完成启动。明确设置子会话的
store:以后还要从续接对话分叉时用true,无需存储试验时用false;省略则继承来源设置。
存储录音保留 30 天。启用零数据保留(ZDR)时,store 按 false 处理,不可分叉。基于分叉的评估应使用非 ZDR 组织,并为项目启用存储。
没有已完成存储录音时,用相关的已保存文本历史启动新会话。存储要求见 GPT-Live 数据控制。
例如,在来源会话的 WebSocket session.start 配置或 WebRTC 创建请求中设置:
来源示例 11(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"store": true
}
按应用使用的传输方式启动分叉:
| 传输方式 | 启动方法 |
|---|---|
| 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:
store、Responses 委派设置,以及新连接的audio.format。发送带session对象的session.start;在支持继承的设置上用{}保持原配置。 - WebRTC:
store、Responses 委派设置以及前端客户端权限。
WebSocket 分叉应为新连接设置 audio.format,或使用默认的 24 kHz PCM16。来源音频格式和前端数据通道权限不会继承。WebRTC 在连接设置时协商音频格式,应省略 audio.format;除非覆盖,WebRTC 会保留前端权限设置。
WebSocket 应等待 session.started 再发送其他命令。WebRTC 的 HTTP 请求已经启动会话,应通过协商好的连接继续,不要再发送 session.start。
启动 WebSocket 分叉
设置 OPENAI_API_KEY。示例使用应用保存的来源会话 ID,确认启动后随即关闭分叉。要继续对话,在 session.started 后按 WebSocket 连接流程收发音频。启动字段和事件见分叉 WebSocket 参考。
来源示例 12(javascript):
程序文字译释33:分叉会话已就绪。
程序文字译释34:最终用量。
程序文字译释35:收到session.closed前连接已关闭。
// [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
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;
let closeRequested = 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" });
closeRequested = true;
} else if (event.message.type === "session.closed") {
console.log("Final usage:", event.message.usage);
if (!closeRequested) {
throw new Error("Fork ended before startup completed");
}
finalized = true;
break;
}
}
}
if (!finalized) throw new Error("Connection closed before session.closed");
} finally {
ws.close();
}
}
来源示例 13(python):
沿用程序文字译释33。
沿用程序文字译释34。
沿用程序文字译释35。
## [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
from openai import OpenAI
def fork_session(source_session_id: str) -> None:
client = OpenAI()
with client.live.forks.connect(session_id=source_session_id) as connection:
connection.session.start(session={})
finalized = False
close_requested = False
for event in connection:
if event.type == "session.started":
print("Fork ready:", event.session.id)
# This startup example closes the fork after confirming it is ready.
connection.session.close()
close_requested = True
elif event.type == "session.closed":
print("Final usage:", event.usage)
if not close_requested:
raise RuntimeError("Fork ended before startup completed")
finalized = True
break
elif event.type == "error":
raise RuntimeError(event.error.message)
if not finalized:
raise RuntimeError("Connection closed before session.closed")
// [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"fmt"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/live"
"os"
"time"
)
func forkSession(ctx context.Context, sourceSessionID string) error {
client := openai.NewClient()
connection, err := client.Live.Forks.Connect(ctx, sourceSessionID, live.ForkConnectionOptions{})
if err != nil {
return err
}
defer connection.Abort()
if err := connection.Send(ctx, live.ForkClientEventUnionParam{OfSessionStart: &live.ForkSessionStartEventParam{Session: live.ForkSessionConfigParam{}}}); err != nil {
return err
}
started := false
for {
event, err := connection.Recv(ctx)
if err != nil {
return err
}
switch event.Type {
case "error":
return fmt.Errorf("Live fork error: %s", event.RawJSON())
case "session.started":
started = true
if err := connection.Send(ctx, live.ForkClientEventUnionParam{OfSessionClose: &live.SessionCloseEventParam{}}); err != nil {
return err
}
case "session.closed":
if !started {
return fmt.Errorf("fork closed before session.started")
}
fmt.Fprintln(os.Stderr, "Final session usage", event.OfSessionClosedEvent.Usage)
return nil
}
}
}
// [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OkHttpClient;
import com.openai.core.ClientOptions;
import com.openai.helpers.LiveForkConnection;
import com.openai.models.live.*;
import com.openai.models.live.forks.*;
import java.util.concurrent.TimeUnit;
public static void forkSession(String sourceSessionId) throws Exception {
try (var http = OkHttpClient.builder().build();
var connection =
LiveForkConnection.connect(
ClientOptions.builder().fromEnv().httpClient(http).build(), sourceSessionId)) {
connection.send(
ForkClientEvent.ofSessionStart(
ForkSessionStartEvent.builder()
.session(ForkSessionConfig.builder().build())
.build()));
boolean started = false;
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30);
while (true) {
long remaining = deadline - System.nanoTime();
if (remaining <= 0) throw new IllegalStateException("Incomplete fork finalization");
var event = connection.receiveAsync().get(remaining, TimeUnit.NANOSECONDS);
if (event.isErrorEvent()) throw new IllegalStateException(event.toString());
if (event.isSessionStarted()) {
started = true;
connection.send(ForkClientEvent.ofSessionClose(SessionCloseEvent.builder().build()));
} else if (event.isSessionClosed()) {
if (!started) throw new IllegalStateException("Fork closed before session.started");
System.err.println("Final session usage " + event.asSessionClosed().usage());
return;
}
}
}
}
## [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
require "openai"
require "timeout"
def fork_websocket_session(source_session_id)
client = OpenAI::Client.new
Timeout.timeout(30) do
client.live.forks.connect(source_session_id) do |connection|
connection.send_event(type: :"session.start", session: {})
state = {
started: false,
finalized: false
}
connection.each do |event|
case event
when OpenAI::Models::Live::ErrorEvent
raise "Live fork error: #{event.to_json}"
when OpenAI::Models::Live::SessionStartedEvent
state[:started] = true
connection.send_event(type: :"session.close")
when OpenAI::Models::Live::SessionClosedEvent
raise "Fork closed before session.started" unless state[:started]
warn "Final session usage #{event.usage}"
state[:finalized] = true
break
end
end
raise "Connection closed without final session usage" unless state[:finalized]
end
end
end
启动 WebRTC 分叉
在前端创建新的 SDP offer 并发送到后端。以下后端示例使用该 offer,以及应用保存的来源会话 ID:
来源示例 14(javascript):
// [中文] 示例 14:启动 WebRTC 分叉;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:client.live.sessions.fork;异步调用及事件接收顺序保留。
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));
}
来源示例 15(python):
## [中文] 示例 15:启动 WebRTC 分叉;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:client.live.sessions.fork;异步调用及事件接收顺序保留。
from openai import OpenAI
def fork_session(source_session_id: str, offer_sdp: str) -> None:
client = OpenAI()
fork = client.live.sessions.fork(
source_session_id,
transport={"type": "webrtc", "sdp": offer_sdp},
)
print(fork.model_dump_json())
// [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"encoding/json"
"github.com/openai/openai-go/v3"
"github.com/openai/openai-go/v3/live"
"github.com/openai/openai-go/v3/option"
"os"
)
func forkSession(ctx context.Context, sourceSessionID, offerSDP string) (*live.SessionForkResponse, error) {
client := openai.NewClient(option.WithMaxRetries(0))
return client.Live.Sessions.Fork(ctx, sourceSessionID, live.SessionForkParams{Transport: live.SessionForkParamsTransport{Sdp: offerSDP}})
}
// [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.live.sessions.SessionForkParams;
import com.openai.models.live.sessions.SessionForkResponse;
public static SessionForkResponse forkSession(String sourceSessionId, String offerSdp) {
var client = OpenAIOkHttpClient.builder().fromEnv().maxRetries(0).build();
try {
return client
.live()
.sessions()
.fork(
SessionForkParams.builder()
.sessionId(sourceSessionId)
.transport(SessionForkParams.Transport.builder().sdp(offerSdp).build())
.build());
} finally {
client.close();
}
}
## [中文] 从源会话创建新的分叉会话,按本语言代码处理连接和返回值;分叉使用独立会话ID,不能把源会话ID当成新会话ID。
require "openai"
def fork_session(source_session_id, offer_sdp)
client = OpenAI::Client.new(max_retries: 0)
client.live.sessions.fork(
source_session_id, transport: {
type: :webrtc,
sdp: offer_sdp
}
)
end
将响应返回前端,把 transport.sdp 作为新对等连接的 answer,并保留新的 session.id。API 密钥保留在后端。
旁路连接和会话控制使用新 ID。重试未完成操作之前,先在后端检查其结果并恢复当前任务状态。没有已完成存储录音时,用保存的历史初始化新会话。
下载录音
存储录音完成收尾后,通过 GET /v1/live/sessions/{session_id}/content 下载音频。响应为二进制立体声 WAV,左声道是输入音频,右声道是输出音频。示例使用应用保存的会话 ID,将响应流写入 recording.wav:
来源示例 16(javascript):
程序文字译释36:录音响应没有响应体。
// [中文] 示例 16:下载录音;以下保留本语言原始示例。
// [中文] 沿原示例执行这些方法:client.live.sessions.downloadRecording;异步调用及事件接收顺序保留。
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"));
}
来源示例 17(python):
## [中文] 示例 17:下载录音;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法:client.live.sessions.with_streaming_response.download_recording;异步调用及事件接收顺序保留。
from openai import OpenAI
def download_recording(session_id: str) -> None:
client = OpenAI()
with client.live.sessions.with_streaming_response.download_recording(
session_id
) as response:
response.stream_to_file("recording.wav")
// [中文] 按会话ID下载录音并写入文件;下载结果依赖服务端录音是否可用,不等同于本段已经录制音频。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
"context"
"github.com/openai/openai-go/v3"
"io"
"os"
)
func downloadRecording(ctx context.Context, sessionID string) error {
client := openai.NewClient()
response, err := client.Live.Sessions.DownloadRecording(ctx, sessionID)
if err != nil {
return err
}
defer response.Body.Close()
file, err := os.Create("recording.wav")
if err != nil {
return err
}
defer file.Close()
_, err = io.Copy(file, response.Body)
return err
}
// [中文] 按会话ID下载录音并写入文件;下载结果依赖服务端录音是否可用,不等同于本段已经录制音频。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.models.live.sessions.SessionDownloadRecordingParams;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
public static void downloadRecording(String sessionId) throws Exception {
var client = OpenAIOkHttpClient.fromEnv();
try (var response =
client
.live()
.sessions()
.downloadRecording(
SessionDownloadRecordingParams.builder().sessionId(sessionId).build());
var body = response.body()) {
Files.copy(body, Path.of("recording.wav"), StandardCopyOption.REPLACE_EXISTING);
} finally {
client.close();
}
}
## [中文] 按会话ID下载录音并写入文件;下载结果依赖服务端录音是否可用,不等同于本段已经录制音频。
require "openai"
def download_recording(session_id)
client = OpenAI::Client.new
response = client.live.sessions.download_recording(session_id)
File.open("recording.wav", "wb") do |file|
IO.copy_stream(response, file)
end
ensure
response&.close
end
关闭空闲会话并恢复
交互之间存在较长空档时,可以在空闲期间关闭语音会话,用户返回后再启动新会话。保留对话上下文和应用任务状态,让用户无需重复表达。例如,车载助手可在驾驶者再次激活语音时恢复;编程助手则可在两次语音对话之间保持后端工作进程运行。
- 决定关闭时机。依据音频活动、助手播放与应用交互设置由应用控制的空闲超时,给阅读、思考等预期停顿留出余地。仅在播放结束且没有待处理工作需要当前语音会话时关闭。仅凭转录事件间隔,不能认定静音。
- 保存状态并正常关闭。保存来源会话 ID、对话上下文和任务状态。完成必要的 Responses 工作,再按用量与正常关闭注册
session.closed监听、发送session.close,等待session.closed后释放连接。客户端委派中,应用管理的后端工作可在语音关闭后独立继续。 - 检测重新启动时机。提供“恢复对话”按钮、按键说话控制或应用管理的唤醒触发器。已关闭的 Live 会话无法继续聆听。若使用本地语音检测自动恢复,应保持麦克风采集,并在建立连接时缓冲开头语音,新会话就绪后再交付,避免丢失用户最初的话。
- 在新会话恢复上下文。若来源以
store: true创建、存储已启用且被允许、录音已成功完成,就分叉存储会话;否则使用保存的文本历史启动新会话。保存新 ID,检查未完成后端操作状态,并把后续结果路由到新会话。操作状态由应用维护,以免重启重复已完成的动作。
静音麦克风不会关闭会话。选择空闲超时时,应比较可省下的语音时长费用、会话创建费用,以及语音重新就绪所需的延迟。见语音会话费用与 WebRTC 初始化费用。
处理错误与结束会话
持续读取会话事件,直到会话完成最终收尾。区分命令被拒绝、连接失败与会话完成,应用才能采取适当恢复方式。
处理被拒绝的命令
在读取确认事件的同时处理 error 事件。如果存在 error.client_event_id,它标识失败的发出命令:
来源示例 18(json):
程序文字译释37:会话启动后不能更改委派类型。
{
"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 以秒报告累计语音时长:
来源示例 19(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
{
"type": "session.usage.updated",
"event_id": "event_usage_1",
"usage": { "seconds": 12 },
"context_window": { "usage_ratio": 0.42 }
}
以最新 usage.seconds 作为累计总量。例如先更新 12 秒、再更新 15 秒,表示累计使用 15 秒。后端词元用量应从嵌套的 Responses 完成事件另行追踪。核算方式见成本优化。
正常关闭的步骤如下:
- 完成应用所需的被委派 Responses 工作,包括待返回的函数结果与响应续接。
- 在发送
session.close前注册session.closed监听器。 - 发送
session.close,停止提交新工作。在待处理事件排空期间,保留 WebSocket 或 WebRTC 连接、数据通道及已连接的旁路接收器。 - 从
session.closed读取最终usage.seconds、reason和会话快照,同时保留先前通过response.event收到的委派用量。 - 该事件到达后再清理传输与音频设备。如果收尾失败或超过应用设置的超时,报告收尾未完成,并释放资源。
发送 session.close 会取消排队中的 Responses,并拒绝后续命令。活动中的响应可能完成,但正在等待函数结果的响应无法在关闭开始后继续。通过客户端委派由应用运行的工作,应单独决定完成还是取消。
用 session.closed 确认最终收尾并读取最后的配置快照。此事件到达前保持传输连接。如果套接字先关闭,记录收尾尚未确认;如果先收到有效 session.closed 再关闭,则保留已确认结果。
最终事件的 reason 说明会话结束原因:
| 原因 | 含义 |
|---|---|
close_requested |
应用发送了 session.close 或调用挂断端点。 |
expired |
会话达到时长限制。 |
content |
安全过滤器结束会话。 |
remote_hangup |
远端主连接正常结束。 |
connection_lost |
主连接或上游连接意外丢失。 |
即使原因是连接丢失或安全终止,收到 session.closed 也能确认最终收尾;没有该事件,最终用量就仍未确认。存储会话可能因保存录音而需要更长收尾时间,应用超时应考虑这一点。
从连接失败中恢复
HTTP 会话创建错误表示尚未到达 session.started。启动错误与运行中错误应分开处理。运行中的连接若在 session.closed 前失败,保留最近观察到的用量,并把最终用量标为未确认。
有已完成存储录音时,可分叉,在新会话继续;否则使用相关已保存历史创建替代会话。继续前检查后端未完成操作、恢复当前任务状态,并更新结果路由,避免先前会话的迟到结果覆盖较新的工作。

TRAE-AI编程


更多建议: