OpenAI 管理 GPT-Live 会话

2026-11-06 12:39 更新

在连接 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 事件中,使指令从启动时就可用。

指定语言、助手应说的话,以及立即开口、随后停下来聆听的要求。来电者开始讲话前,使用应用选择的问候语言。例如开发者消息可以写为:

现在用英语问候来电者。介绍自己是客服助手,并询问有什么可以帮助的。然后停下来聆听。

  1. 将消息添加到 session.input,设置 role: "developer",并包含一个 input_text 内容部分。消息格式见用既有对话初始化会话。
  2. 等待 session.started,然后通过 session.commentary.append 发送一次相同的问候指令,使用唯一 event_id 和 delegation_id: null,如下例。保持输入音频运行,包括来电者开口前的静音:WebSocket 持续发送 session.input_audio.append,WebRTC 保持输入音轨活动。
  3. 检查输出音频和转录文本,验证助手是否问候了来电者。启动指令可以改善问候表现,但不保证模型一定发声。

来源示例 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,后续后端结果也路由到新会话。

准备可分叉的会话

  1. 创建来源会话时启用存储。在会话配置中设置 store: true。默认 false;项目必须已启用存储,数据政策也必须允许持久化。
  2. 保存来源会话 ID。从 session.started 或 WebRTC 创建响应中读取,并与应用的对话记录关联。
  3. 结束并关闭来源。完成必要后端工作,再按用量与正常关闭处理。保持连接,直到 session.closed,并处理最终收尾错误。分叉需要已完成的存储录音,保存录音会增加收尾时间。
  4. 在新连接启动分叉。按下表流程使用来源 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

关闭空闲会话并恢复

交互之间存在较长空档时,可以在空闲期间关闭语音会话,用户返回后再启动新会话。保留对话上下文和应用任务状态,让用户无需重复表达。例如,车载助手可在驾驶者再次激活语音时恢复;编程助手则可在两次语音对话之间保持后端工作进程运行。

  1. 决定关闭时机。依据音频活动、助手播放与应用交互设置由应用控制的空闲超时,给阅读、思考等预期停顿留出余地。仅在播放结束且没有待处理工作需要当前语音会话时关闭。仅凭转录事件间隔,不能认定静音。
  2. 保存状态并正常关闭。保存来源会话 ID、对话上下文和任务状态。完成必要的 Responses 工作,再按用量与正常关闭注册 session.closed 监听、发送 session.close,等待 session.closed 后释放连接。客户端委派中,应用管理的后端工作可在语音关闭后独立继续。
  3. 检测重新启动时机。提供“恢复对话”按钮、按键说话控制或应用管理的唤醒触发器。已关闭的 Live 会话无法继续聆听。若使用本地语音检测自动恢复,应保持麦克风采集,并在建立连接时缓冲开头语音,新会话就绪后再交付,避免丢失用户最初的话。
  4. 在新会话恢复上下文。若来源以 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 完成事件另行追踪。核算方式见成本优化。

正常关闭的步骤如下:

  1. 完成应用所需的被委派 Responses 工作,包括待返回的函数结果与响应续接。
  2. 在发送 session.close 前注册 session.closed 监听器。
  3. 发送 session.close,停止提交新工作。在待处理事件排空期间,保留 WebSocket 或 WebRTC 连接、数据通道及已连接的旁路接收器。
  4. 从 session.closed 读取最终 usage.seconds、reason 和会话快照,同时保留先前通过 response.event 收到的委派用量。
  5. 该事件到达后再清理传输与音频设备。如果收尾失败或超过应用设置的超时,报告收尾未完成,并释放资源。

发送 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 前失败,保留最近观察到的用量,并把最终用量标为未确认。

有已完成存储录音时,可分叉,在新会话继续;否则使用相关已保存历史创建替代会话。继续前检查后端未完成操作、恢复当前任务状态,并更新结果路由,避免先前会话的迟到结果覆盖较新的工作。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号