OpenAI 服务端控制

2026-11-06 12:40 更新

Go服务端控制示例需要把call ID传给应用自己的服务端会话更新函数;它不意味着客户端已经自动配置了所有后端控制。 选择所用 API,查看各自的连接步骤和会话事件。

从服务器控制 GPT-Live 会话

服务器需要接收对话事件、执行私有工具或更新对话时,将它附加到现有 GPT-Live WebRTC 或 SIP 会话。这条第二连接称为旁路 WebSocket。两条连接共享一个会话,主要音频继续由 WebRTC 或 SIP 承载。

旁路传递事件和命令,工具执行、授权检查和业务规则由应用提供。API 密钥和工具凭证保存在服务器。

决定是否需要旁路

浏览器字幕和本地界面更新使用 WebRTC 数据通道。转录处理在服务器运行时,例如防护检查、情感分析或推测性工具调用,使用旁路。服务器可直接接收事件并引导同一会话,浏览器音频仍走 WebRTC。示例见响应转录片段。

后端已通过主 WebSocket传音频时,就使用该连接收事件、发命令。

Responses 委派也能不使用旁路:浏览器可把数据通道上的函数调用转发到已认证后端执行,OpenAI 托管工具则经委派后端运行,无需应用工具执行器。

附加到既有会话

  1. 保存待控制会话的 ID。WebRTC 或 SIP 呼出使用 POST /v1/live/sessions JSON 响应的 session.id;SIP 呼入先接受来电,再用 webhook 的 data.session_id。ID 与应用用户和对话记录一起保存。
  2. 从服务器连接以下 WebSocket URL,原样代入保存的 ID。使用创建或接受会话的项目认证,通过 Authorization: Bearer $OPENAI_API_KEY 认证,并包含创建时所需的相同连接头。

来源示例 1(text):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。

   wss://api.openai.com/v1/live/sessions/{session_id}/attach

  1. 在附加连接上收事件、发命令。会话已在运行,不要再次发送 session.start。

会话 ID 连同前缀原样使用,并验证应用已获授权访问该会话。

观察事件与发送命令

任务 事件或命令
跟踪对话 接收双方转录增量、委派事件与嵌套 Responses 事件。
更新后端配置 session.update 更新当前委派模式支持的设置;前端模型、音频等启动设置保持不变。
提供上下文 指令用 session.instructions.append;静默上下文用 session.thinking.append;可播报更新用 session.commentary.append。
返回工具结果 Responses 委派发送 response.item.create,再用 response.create 继续后端。
控制麦克风输入 session.input_audio.mute、session.input_audio.unmute;静音输入不停止助手输出。
结束会话 发送 session.close,收到 session.closed 后再断开。

命令遵循与主连接相同的验证与委派规则。上下文追加中,一般会话信息用 delegation_id: null,非空 ID 必须标识已有客户端委派。配置、函数执行和追加示例见委派与工具。

浏览器会话的麦克风和扬声器保留在协商好的 WebRTC 音轨上,旁路用于事件与控制,播放状态由播放器追踪。

接收镜像音频

主连接传实时媒体时,旁路还能收到随后输入与输出音频的副本:

事件 音频字段 时间信息
session.input_audio.append audio 无时间戳。
session.output_audio.delta delta start_ms、end_ms 表示输出在会话时间线的范围。

无论主传输的格式是什么,两者均为 Base64 编码、24 kHz、单声道 PCM16LE 原始音频,均无 event_id。镜像输入是输入静音前收到的音频,不确认模型实际消费了这些采样。镜像输出范围可能因丢帧有空档,也不表示来电者听到音频的时机。

麦克风音频只通过主连接发送,旁路用于接收镜像音频。

为每项操作指定唯一负责人

明确由浏览器还是后端处理各操作。如果两条连接都收到函数调用,只执行一次。上下文更新及后端继续请求也采用相同归属规则。

后端需要从开始观察对话时应尽早附加。应用保存转录与工具状态,包括附加前已收集的历史。

旁路附加后,浏览器仍可能收到事件。敏感工具凭证和授权决定留在后端,只返回对话所需上下文。

应用对话防护规则

使用服务器主 WebSocket 或旁路监控对话,依据应用政策检查请求。检查、阻止相关动作及触发后的纠正指令均由应用负责。

与对话并行检查

防护规则是实时处理转录片段的一种用途。同一事件流还可并行启动推测性查询或更新界面。

  1. 监控转录。累计 session.input_transcript.delta,检查越狱尝试、敏感信息或政策违规;用 session.output_transcript.delta 检查助手无依据陈述或超出范围的回复。每次检查关联它评估的转录和应用请求。
  2. 并发检查。可用快速轻量模型边对话边评估,返回 {"triggered": true} 等小型结构结果。需要批准的操作只有检查通过后才能运行,失败或超时则继续阻止。
  3. 阻断操作。触发后在应用状态标记请求被阻止,工具执行或提交更改前检查该状态,已排队工作也一样。
  4. 停止相关工作。后端支持时取消应用作业,丢弃被阻断或已替代请求的迟到结果。Responses 委派停止执行相关自定义函数,并不再发送 response.create 续接受阻工作。这不会取消已运行的托管响应,也不会停止前端讲话。
  5. 记录并引导。记录受影响请求和委派 ID,再发送纠正指令。

片段收集见转录增量,后端结果与当前任务对齐见委派与工具。

引导对话

防护干预使用 session.instructions.append,它可打断当前语音并应用新指令。例如应用阻断请求后发送:

来源示例 2(javascript):

程序文字译释221:立即停止讲话,不继续也不执行最后请求,简短拒绝后等待。

// [中文] 示例 2:引导对话;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.instructions.append;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:connection.send;异步调用及事件接收顺序保留。
// [中文] 委派 ID 与响应、工具调用 ID 不同;追加上下文时,非空 delegation_id 须对应客户端委派,null 用于会话级上下文。
export function sendUpdate(connection) {
  connection.send({
    type: "session.instructions.append",
    event_id: "guardrail_block_17",
    delegation_id: null,
    content:
      "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.",
  });
}

来源示例 3(python):

程序文字译释222:立即停止讲话,不继续或执行最后请求。

程序文字译释223:简短拒绝,然后等待。

## [中文] 示例 3:引导对话;以下保留本语言原始示例。
## [中文] 沿原示例执行这些方法: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="guardrail_block_17",
        delegation_id=None,
        content=(
            "Stop speaking immediately. Do not continue or act on the last request. "
            "Refuse briefly, then wait."
        ),
    )

// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import (
    "context"
    "github.com/openai/openai-go/v3"
    "github.com/openai/openai-go/v3/live"
    "github.com/openai/openai-go/v3/packages/param"
)

// SendSidebandUpdate uses the server connection attached to an existing session.
func SendSidebandUpdate(ctx context.Context, connection *live.SidebandConnection) error {
    return connection.Send(ctx, live.ConnectClientEventUnionParam{OfSessionInstructionsAppend: &live.InstructionsAppendEventParam{
        EventID:      openai.String("guardrail_block_17"),
        DelegationID: param.Null[string](),
        Content:      "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.",
    }})
}

// 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("guardrail_block_17"),
        DelegationID: param.Null[string](),
        Content:      "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.",
    }})
}

// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
import com.openai.core.JsonField;
import com.openai.helpers.LiveConnection;
import com.openai.helpers.LiveSidebandConnection;
import com.openai.models.live.*;

// Use the server connection attached to an existing session.
public static void sendUpdate(LiveSidebandConnection connection) {
  connection.send(
      InstructionsAppendEvent.builder()
          .eventId("guardrail_block_17")
          .delegationId(JsonField.<String>ofNullable(null))
          .content(
              "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.")
          .build());
}

// Use a primary connection after its session has started.
public static void sendUpdate(LiveConnection connection) {
  connection.send(
      ClientEvent.ofSessionInstructionsAppend(
          InstructionsAppendEvent.builder()
              .eventId("guardrail_block_17")
              .delegationId(JsonField.<String>ofNullable(null))
              .content(
                  "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.")
              .build()));
}

## [中文] 追加由应用编写的会话指令,区分会话级更新与任务委派;不要把不可信用户文本直接升级成应用指令。
def send_update(connection)
  connection.send_event(type: "session.instructions.append", event_id: "guardrail_block_17", delegation_id: nil, content: "Stop speaking immediately. Do not continue or act on the last request. Refuse briefly, then wait.")
end

指令应由应用编写,不要把不可信用户文本直接复制成指令。会话级纠正使用 delegation_id: null,content 不超过 500 个词元。

用 client_event_id 将 session.instructions.appended 匹配到命令;确认在估计的上下文注入后到达。要阻止音频到达来电者,仍需下述播放控制。

请求特定口头措辞的告知声明也使用指令,示例与播放注意点见播报告知声明。

按需控制播放

需要阻止模型音频时,在客户端或媒体中继控制播放:暂时静音或丢弃输出、清除本地排队音频,再发送纠正。清理过时音频后,按应用恢复策略继续播放,指令确认不等于恢复信号。仅有旁路不能控制播放,纠正也无法收回已听到的音频。

session.input_audio.mute 只控制来电者麦克风,不静音模型输出,也不取消委派工作。

播放前检查语音

多数应用可在对话继续时监控双方转录,触发后阻断相关动作或纠正。

如必须先检查再播放,应在播放器或媒体桥缓冲音频。检查期间持续接收音频和转录,转录读取独立于播放。

  1. 收集音频及对应文字。应用实现输出 VAD,或使用媒体框架的噪声门识别候选语音片段。GPT-Live 不为此提供输出 VAD 或噪声门。等待检查片段所需的转录。
  2. 放行通过检查的音频。通过后,把原始缓冲音频加入播放队列。检查失败、转录缺失或超时则丢弃,并使用应用定义的安全回退。
  3. 处理打断。用应用生成的 ID 关联音频、转录、检查结果和播放状态。打断取消该段语音时,清空缓冲与队列,忽略迟到批准。

停顿只能标识候选片段,模型可能仍在构思答案。政策要求检查完整答案时,必须定义如何认定完成,仅靠 VAD 不能判断整段答案结束。

缓冲增加延迟。被丢弃的语音仍留在模型对话上下文中,因此要测试扣住音频或回退后如何继续。

测试干预

测试允许和阻断请求、误报、慢或失败检查、讲话期间触发、工具运行期间触发,以及取消工作的迟到结果。分别验证操作阻断、应用状态、纠正语音和实际播放。控制输出时还应测试排队音频与恢复。用语音智能体评估 Cookbook比较成功率与口头回应时间。

正常结束

工具结束、会话报告最终用量期间继续收事件。在发送 session.close 前注册 session.closed 处理器;排空待处理工作时保持 WebRTC、数据通道和旁路打开。清理前保存最终会话用量,以及 Responses 事件中收到的后端用量。最终事件前连接失败,记为收尾未完成。关闭顺序见管理会话。

Realtime API:客户端可以通过 WebRTC 或 SIP 直接连接 API,但为保持私密性、独立于客户端,工具与业务逻辑通常应放在应用服务器。

通过旁路控制通道,在服务端保存工具、业务逻辑等细节。SIP 与 WebRTC 都支持旁路。它表示同一 Realtime 会话存在用户客户端与应用服务器两条活动连接,服务器可监控、更新指令并回应工具调用。

通过 WebRTC

  1. 建立对等连接时,从 API 获取 SDP 响应配置连接。若使用指南示例,类似:

来源示例 4(javascript):

// [中文] 示例 4:通过 WebRTC;以下保留本语言原始示例。
// [中文] 本段涉及的 HTTP 端点:/v1/realtime/calls;认证和请求体保持原示例形式。
const baseUrl = "https://api.openai.com/v1/realtime/calls";
const sdpResponse = await fetch(baseUrl, {
  method: "POST",
  body: offer.sdp,
  headers: {
    Authorization: `Bearer ${EPHEMERAL_KEY}`,
    "Content-Type": "application/sdp",
  },
});

  1. 响应的 Location 头包含唯一通话 ID,可用于服务器附加同一 Realtime 会话:

来源示例 5(javascript):

// [中文] 示例 5:通过 WebRTC;以下保留本语言原始示例。
// [中文] 从建立 Realtime 通话的 SDP 响应读取 Location 头,取路径最后一段作为 call_id 并打印。
// [中文] 可选链只避免 Location 缺失时立即抛错;应用仍需确认通话创建成功且 callId 有值,再由服务端附加旁路连接。
// Location: /v1/realtime/calls/rtc_123456
const location = sdpResponse.headers.get("Location");
const callId = location?.split("/").pop();
console.log(callId);

  1. 服务器使用 wss://api.openai.com/v1/realtime?call_id=rtc_xxxxx,即可像普通 Realtime WebSocket 一样监听事件与配置会话:

来源示例 6(javascript):

程序文字译释224:已连接服务器。

程序文字译释225:今天格外友善一些!

// [中文] 示例 6:通过 WebRTC;以下保留本语言原始示例。
// [中文] 本段构造的事件类型:session.update;不要将构造载荷等同于收到成功确认。
// [中文] 沿原示例执行这些方法:ws.on、ws.send;异步调用及事件接收顺序保留。
import WebSocket from "ws";
const callId = "rtc_u1_9c6574da8b8a41a18da9308f4ad974ce";

// Connect to a WebSocket for the in-progress call
const url = "wss://api.openai.com/v1/realtime?call_id=" + callId;
const ws = new WebSocket(url, {
  headers: {
    Authorization: "Bearer " + process.env.OPENAI_API_KEY,
  },
});

ws.on("open", function open() {
  console.log("Connected to server.");

  // Send client events over the WebSocket once connected
  ws.send(
    JSON.stringify({
      type: "session.update",
      session: {
        type: "realtime",
        instructions: "Be extra nice today!",
      },
    })
  );
});

// Listen for and parse server events
ws.on("message", function incoming(message) {
  console.log(JSON.parse(message.toString()));
});

// [中文] 保留原例中客户端、输入和返回值的处理顺序;本段需结合所在小节的连接前提使用,未通过真实API调用验证。
// [中文] 本段为配置或调用片段,需结合外层程序与已建立连接使用;不作为独立完整程序宣称编译通过。
func updateSession(ctx context.Context, client openai.Client, callID string) error {
    conn, err := client.Realtime.Connect(ctx, realtime.RealtimeConnectionOptions{}, option.WithQuery("call_id", callID))
    if err != nil {
        return err
    }
    defer conn.Close()
    if err := conn.Send(ctx, realtime.RealtimeClientEventParamOfSessionUpdate(realtime.RealtimeSessionCreateRequestParam{
        Instructions: openai.String("Be extra nice today!"),
    })); err != nil {
        return err
    }
    for {
        event, err := conn.Recv(ctx)
        if err != nil {
            return err
        }
        if event.Type == "error" {
            return fmt.Errorf("Realtime API error: %s", event.OfRealtimeErrorEvent.Error.Message)
        }
        if event.Type == "session.updated" {
            if event.OfSessionUpdatedEvent.Session.OfRealtimeSessionCreateRequest.Instructions != "Be extra nice today!" {
                return fmt.Errorf("session update was not applied")
            }
            fmt.Println("session.updated")
            return nil
        }
    }
}

这样可以在服务端添加工具、监控并执行业务逻辑,无需在客户端配置。

通过 SIP

  1. 用户通过 SIP 电话连接 OpenAI。
  2. OpenAI 向应用 webhook URL 发送通知,例如:

来源示例 7(json):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。

POST https://my_website.com/webhook_endpoint
user-agent: OpenAI/1.0 (+https://platform.openai.com/docs/webhooks)
content-type: application/json
webhook-id: wh_685342e6c53c8190a1be43f081506c52 # unique id for idempotency
webhook-timestamp: 1750287078 # timestamp of delivery attempt
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4= # signature to verify authenticity from OpenAI

{
  "object": "event",
  "id": "evt_685343a1381c819085d44c354e1b330e",
  "type": "realtime.call.incoming",
  "created_at": 1750287018, // Unix timestamp
  "data": {
    "call_id": "some_unique_id",
    "sip_headers": [
      { "name": "From", "value": "sip:+142555512112@sip.example.com" },
      { "name": "To", "value": "sip:+18005551212@sip.example.com" },
      { "name": "Call-ID", "value": "03782086-4ce9-44bf-8b0d-4e303d2cc590"}
    ]
  }
}

  1. 应用服务器使用 webhook 中的 call_id,连接 wss://api.openai.com/v1/realtime?call_id={callId}。WebSocket 生命周期与 SIP 通话一致。

随后可像直接以 WebSocket 发起会话一样收发事件,包括监控通话、动态更新指令和回应工具调用。

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

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号