OpenAI 服务端控制
Go服务端控制示例需要把call ID传给应用自己的服务端会话更新函数;它不意味着客户端已经自动配置了所有后端控制。 选择所用 API,查看各自的连接步骤和会话事件。
从服务器控制 GPT-Live 会话
服务器需要接收对话事件、执行私有工具或更新对话时,将它附加到现有 GPT-Live WebRTC 或 SIP 会话。这条第二连接称为旁路 WebSocket。两条连接共享一个会话,主要音频继续由 WebRTC 或 SIP 承载。
旁路传递事件和命令,工具执行、授权检查和业务规则由应用提供。API 密钥和工具凭证保存在服务器。
决定是否需要旁路
浏览器字幕和本地界面更新使用 WebRTC 数据通道。转录处理在服务器运行时,例如防护检查、情感分析或推测性工具调用,使用旁路。服务器可直接接收事件并引导同一会话,浏览器音频仍走 WebRTC。示例见响应转录片段。
后端已通过主 WebSocket传音频时,就使用该连接收事件、发命令。
Responses 委派也能不使用旁路:浏览器可把数据通道上的函数调用转发到已认证后端执行,OpenAI 托管工具则经委派后端运行,无需应用工具执行器。
附加到既有会话
- 保存待控制会话的 ID。WebRTC 或 SIP 呼出使用
POST /v1/live/sessionsJSON 响应的session.id;SIP 呼入先接受来电,再用 webhook 的data.session_id。ID 与应用用户和对话记录一起保存。 - 从服务器连接以下 WebSocket URL,原样代入保存的 ID。使用创建或接受会话的项目认证,通过
Authorization: Bearer $OPENAI_API_KEY认证,并包含创建时所需的相同连接头。
来源示例 1(text):字段名、事件类型和示意值保留原样,含义见相邻正文;片段中的省略号不表示完整可运行程序。
wss://api.openai.com/v1/live/sessions/{session_id}/attach
- 在附加连接上收事件、发命令。会话已在运行,不要再次发送
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 或旁路监控对话,依据应用政策检查请求。检查、阻止相关动作及触发后的纠正指令均由应用负责。
与对话并行检查
防护规则是实时处理转录片段的一种用途。同一事件流还可并行启动推测性查询或更新界面。
- 监控转录。累计
session.input_transcript.delta,检查越狱尝试、敏感信息或政策违规;用session.output_transcript.delta检查助手无依据陈述或超出范围的回复。每次检查关联它评估的转录和应用请求。 - 并发检查。可用快速轻量模型边对话边评估,返回
{"triggered": true}等小型结构结果。需要批准的操作只有检查通过后才能运行,失败或超时则继续阻止。 - 阻断操作。触发后在应用状态标记请求被阻止,工具执行或提交更改前检查该状态,已排队工作也一样。
- 停止相关工作。后端支持时取消应用作业,丢弃被阻断或已替代请求的迟到结果。Responses 委派停止执行相关自定义函数,并不再发送
response.create续接受阻工作。这不会取消已运行的托管响应,也不会停止前端讲话。 - 记录并引导。记录受影响请求和委派 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 只控制来电者麦克风,不静音模型输出,也不取消委派工作。
播放前检查语音
多数应用可在对话继续时监控双方转录,触发后阻断相关动作或纠正。
如必须先检查再播放,应在播放器或媒体桥缓冲音频。检查期间持续接收音频和转录,转录读取独立于播放。
- 收集音频及对应文字。应用实现输出 VAD,或使用媒体框架的噪声门识别候选语音片段。GPT-Live 不为此提供输出 VAD 或噪声门。等待检查片段所需的转录。
- 放行通过检查的音频。通过后,把原始缓冲音频加入播放队列。检查失败、转录缺失或超时则丢弃,并使用应用定义的安全回退。
- 处理打断。用应用生成的 ID 关联音频、转录、检查结果和播放状态。打断取消该段语音时,清空缓冲与队列,忽略迟到批准。
停顿只能标识候选片段,模型可能仍在构思答案。政策要求检查完整答案时,必须定义如何认定完成,仅靠 VAD 不能判断整段答案结束。
缓冲增加延迟。被丢弃的语音仍留在模型对话上下文中,因此要测试扣住音频或回退后如何继续。
测试干预
测试允许和阻断请求、误报、慢或失败检查、讲话期间触发、工具运行期间触发,以及取消工作的迟到结果。分别验证操作阻断、应用状态、纠正语音和实际播放。控制输出时还应测试排队音频与恢复。用语音智能体评估 Cookbook比较成功率与口头回应时间。
正常结束
工具结束、会话报告最终用量期间继续收事件。在发送 session.close 前注册 session.closed 处理器;排空待处理工作时保持 WebRTC、数据通道和旁路打开。清理前保存最终会话用量,以及 Responses 事件中收到的后端用量。最终事件前连接失败,记为收尾未完成。关闭顺序见管理会话。
Realtime API:客户端可以通过 WebRTC 或 SIP 直接连接 API,但为保持私密性、独立于客户端,工具与业务逻辑通常应放在应用服务器。
通过旁路控制通道,在服务端保存工具、业务逻辑等细节。SIP 与 WebRTC 都支持旁路。它表示同一 Realtime 会话存在用户客户端与应用服务器两条活动连接,服务器可监控、更新指令并回应工具调用。
通过 WebRTC
- 建立对等连接时,从 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",
},
});
- 响应的
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);
- 服务器使用
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
- 用户通过 SIP 电话连接 OpenAI。
- 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"}
]
}
}
- 应用服务器使用 webhook 中的
call_id,连接wss://api.openai.com/v1/realtime?call_id={callId}。WebSocket 生命周期与 SIP 通话一致。
随后可像直接以 WebSocket 发起会话一样收发事件,包括监控通话、动态更新指令和回应工具调用。

TRAE-AI编程


更多建议: