跳到主要内容
版本:Dev

Realtime API

AISIX AI 网关在 GET /v1/realtime 通过 WebSocket 转发 OpenAI Realtime API。网关会在接受连接前完成客户端认证、模型别名解析、访问控制和限流策略检查,然后在客户端与服务提供方之间双向转发事件。

本指南将演示如何通过网关连接 Realtime 客户端,并说明该端点的关键会话行为。

前提条件​

开始前请准备:

  • 一个可处理代理请求的 AISIX 网关。
  • 一个可访问目标模型别名的调用方 API Key。
  • 一个由支持 OpenAI Realtime 协议的服务提供方支持的模型别名。

导出服务器端示例使用的网关连接和请求值:

# AISIX_PROXY 使用 http 或 https,且不含尾部斜杠或端点路径。
# 本地快速入门使用 http://127.0.0.1:3000。
export AISIX_PROXY="YOUR_AISIX_GATEWAY_URL"
export AISIX_API_KEY="YOUR_CALLER_API_KEY"
export AISIX_MODEL="realtime-prod"

目前支持兼容 OpenAI 的服务提供方(适配器为 openai,包括自定义 api_base 部署)和 Azure OpenAI(适配器为 azure-openai,使用 /openai/realtime 和 api-key 认证)。Gemini Live 和 Bedrock 使用不同的实时事件协议,不通过该端点提供。

连接客户端​

通过 model 查询参数选择模型。服务端客户端使用标准请求头认证:

import WebSocket from "ws";

const realtimeUrl = new URL("/v1/realtime", process.env.AISIX_PROXY);
realtimeUrl.protocol = realtimeUrl.protocol === "https:" ? "wss:" : "ws:";
realtimeUrl.searchParams.set("model", process.env.AISIX_MODEL);

const ws = new WebSocket(realtimeUrl, {
headers: { Authorization: `Bearer ${process.env.AISIX_API_KEY}` },
});

浏览器客户端无法设置 WebSocket 请求头。可以像 OpenAI 浏览器示例一样,把调用方 API Key 作为子协议项传入。网关会回显 realtime 子协议:

// 使用不含端点路径的绝对 HTTP(S) 网关 URL,例如 https://gateway.example.com
const aisixProxy = "YOUR_AISIX_GATEWAY_URL";
const aisixApiKey = "YOUR_CALLER_API_KEY";
const aisixModel = "realtime-prod";

const realtimeUrl = new URL("/v1/realtime", aisixProxy);
realtimeUrl.protocol = realtimeUrl.protocol === "https:" ? "wss:" : "ws:";
realtimeUrl.searchParams.set("model", aisixModel);

const ws = new WebSocket(realtimeUrl, [
"realtime",
`openai-insecure-api-key.${aisixApiKey}`,
]);

连接建立后,可以像直接连接服务提供方一样收发 Realtime 事件。AISIX 转发 session.update、音频缓冲区、response.create 和服务端事件等事件时不会改变其结构。

AISIX 唯一会改写的值是会话对象中的模型名称。session.created 和 session.updated 事件中的模型名称是客户端连接时使用的模型别名,而不是服务提供方自身的模型 ID。客户端把该别名通过 session.update 发回时,会在转发到上游前被转换回服务提供方的模型 ID,因此客户端可以直接回传收到的会话对象。

认证与策略​

认证、模型访问检查、客户端 IP 限制和限流会在 WebSocket 升级完成前执行。预算检查也在此时执行。任何一项检查失败都会在 HTTP 握手阶段被拒绝(401、403 或 429),客户端会看到连接建立失败。

会话期间,已配置的安全护栏会扫描双向文本事件。被阻断的事件会产生 OpenAI 风格的 error 事件,随后连接关闭。

用量跟踪​

网关会从服务提供方的 response.done 事件中提取用量信息;转录会话还会从转录完成事件中提取用量。每个会话会记录一个聚合用量事件,包括缓存 Token 数。会话总 Token 会计入基于 Token 的限流。

会话限制​

AISIX 会对两个方向的事件间隔应用空闲上限。系统依次从直接模型的 stream_timeout、该模型的 timeout,以及部署级 upstream.stream_timeout_ms 或 upstream.timeout_ms 默认值解析此上限。部署默认上限为 6000 秒。静默时间超过解析后截止时间的会话将以代码 1001 和原因 idle timeout 关闭。

要让某个模型不受部署级兜底值影响,请设置 timeout: 0,并确保该模型未设置非零 stream_timeout。部署运维人员也可以将两个上游超时默认值均设置为 0。有关完整优先级规则,请参阅超时之间的关系。上游连接失败会以代码 1011 关闭会话,参见上游连接失败。打开该上游连接本身也有时间预算:upstream.connect_timeout_ms(默认 5 秒)限制整个拨号过程,其中包括 WebSocket 升级握手,因此始终不回应的上游会在此处失败,而不会让会话一直挂着。

上游连接失败​

AISIX 先接受客户端的 WebSocket 升级,再拨号上游,因此拨号失败时,客户端会收到一个类型为 upstream_error 的 error 帧,随后是代码 1011、原因 upstream connect failed 的关闭。其余内容取决于拨号失败的方式:

拨号失败的方式帧中的 error.message记录的状态error_class冷却设置
上游以 4xx 拒绝升级。上游的文本。上游发送了 code 和 param 时,帧中也会带上。该状态upstream_statustrigger_statuses
上游以 5xx 拒绝升级。上游的文本。502upstream_statustrigger_statuses
上游以其他状态应答,例如不是升级的 200。上游的文本。502upstream_statustrigger_statuses
没有收到 HTTP 响应,例如 DNS、连接或 TLS 失败。failed to connect to the upstream realtime endpoint502transporttrigger_on_transport
拨号超过 upstream.connect_timeout_ms,或内核报告连接超时。failed to connect to the upstream realtime endpoint504timeouttrigger_on_timeout

记录的状态是用量事件、访问日志,以及请求指标 status 和 outcome 标签上的状态。记录的文本仍然写明上游实际返回的内容,例如 upstream returned HTTP 500: <text>;trigger_statuses 和 honor_retry_after 读取的是上游自己的状态和 Retry-After,因此在默认列表下,以 400 拒绝不会让模型进入冷却。

AISIX 按 HTTP 路由的方式读取上游文本,上限为 1 KiB。没有文本可引用时,帧中改用状态,例如 upstream returned 500 Internal Server Error 或 upstream returned 200 OK,且不带 code 或 param。这包括正文为空或经过压缩,以及正文停滞或被截断的情况。AISIX 在模型的流式预算内读取正文,因此停滞的正文最多会让这个帧在拨号之后再晚一个预算发出。没有预算的模型(timeout: 0 且未设置 stream_timeout)会一直等待,直到上游不再停滞或客户端离开。

azure-openai 服务提供方密钥的拒绝响应按 OpenAI 错误读取,因此 4xx 携带的是上游自己的 code,不做转换:是 DeploymentNotFound,而 chat completions 上回答的是 model_not_found。

1.8.0 及更早版本的网关在所有情况下都发送固定文本,并把每次失败的拨号记录为 502、error_class 为 transport,由 trigger_on_transport 决定冷却。

升级之后的上游拒绝​

上游可能先完成 101 升级,再拒绝会话。OpenAI 对错误的 API 密钥就是这样应答的:发送一个 error 事件,然后以代码 3000 关闭。

当上游一侧结束时只发送过 error 事件、没有其他帧,客户端只会收到上游的帧及其关闭代码和原因,不会收到网关的帧。没有 Close 帧就断开的连接仍以 1011 关闭客户端,同样没有网关的帧。

AISIX 把这样的会话记录为失败,error_class 为 upstream_in_band,文本为 upstream reported an in-band error: <message>。该事件不带 HTTP 状态,因此记录的状态从事件中读取:

error 事件记录的状态
code 为 invalid_api_key401
code 为 invalid_model 或 model_not_found404
code 为 rate_limit_exceeded 或 insufficient_quota429
type 为 invalid_request_error,code 为其他值400
code 是 4xx 范围内的数字该值
其他情况502

冷却按同一状态走 trigger_statuses。映射不到状态的事件走 trigger_on_transport。

会话从第一个不是 error 事件的帧起变为可用。在该帧之后的 error 事件不算拒绝:OpenAI 会对错误的客户端事件发送这种事件,会话保持打开。1.8.0 及更早版本的网关把被拒绝的会话记录为 200,并在上游的帧之后多给客户端发送一个 server_error 帧。

连接关闭​

AISIX 会在两个方向上完成 WebSocket 关闭握手:

  • 客户端关闭会话时,客户端会收到自己发送的关闭代码和原因,上游也会收到同样的代码和原因。
  • 上游关闭会话时,客户端会收到上游的关闭代码和原因。原因按上游发送的原样转发,截断为 123 字节。
  • 无法在 Close 帧中发送的关闭代码,例如缺少状态码或 1006,会以 1011 送达另一方。恰好为 1002、原因为 Protocol violation 的 Close 帧也会变为 1011,因为 AISIX 无法把它与 WebSocket 库替换无效代码后生成的帧区分开。原因不同的 1002 会照常转发。
  • 除正常关闭(1000)外,任何结束之前都会先发送一个类型为 server_error 的 Realtime error 事件,说明发生了什么:上游的关闭代码和原因、AISIX 无法转发的 Close 帧、不带状态码的关闭、会话期间失败的连接,或者没有 Close 帧就结束的连接。例外是上游在升级之后拒绝的会话,它自己的 error 事件已经告知了客户端。拨号失败时发送的则是上游连接失败中说明的 upstream_error 帧。

关闭过程中到达的用量仍会计入。会话在访问日志中的状态不取决于关闭代码。

网关关闭时,会像对待流式响应一样,等待打开的 Realtime 会话关闭后再退出,以便记录其用量。请按预期最长的会话时长设置网关的终止宽限期。

通过代理访问上游​

对于 wss:// 服务提供方,AISIX 通过 HTTPS_PROXY 打开上游连接;对于 ws:// 服务提供方,则通过 HTTP_PROXY,两者都回退到 ALL_PROXY,除非 NO_PROXY 覆盖该主机。代理必须是支持 CONNECT 的 HTTP 代理。不支持 SOCKS 代理:会话会以一个 error 事件和关闭代码 1011 失败,访问日志记录 502。1.6.x 及更早版本的网关始终直接连接上游。

下一步​

你已经通过网关连接了 Realtime 客户端。接下来可阅读语音与音频,了解非 Realtime 音频端点。