> ## Documentation Index
> Fetch the complete documentation index at: https://docs.aihubmix.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 实时语音转录

> 通过 WebSocket 建立持久连接，边说边出字，实现低延迟的流式语音转文本

## 介绍

实时语音转录通过 WebSocket（一种在客户端与服务端之间保持长连接、可双向推送数据的协议）建立持久连接，将持续输入的音频流**边接收、边转写、边返回**，适合对延迟敏感的语音场景。

它与文件转录 [STT](/cn/api/STT) 的区别：

| 维度 | 文件转录（STT）       | 实时转录（本页）           |
| -- | --------------- | ------------------ |
| 协议 | HTTP，一次请求返回完整结果 | WebSocket，持续推送增量结果 |
| 输入 | 完整音频文件（≤25MB）   | 连续音频流（PCM 分片）      |
| 延迟 | 等整段处理完          | 说话过程中即返回文字         |
| 适用 | 录音转写、字幕生成       | 会议实时字幕、语音助手、直播听写   |

**可用模型：**

* **gpt-live-transcribe** —— 流式转录模型，支持多语言，随音频输入实时输出转写文本。

<Warning>
  **本接口面向服务端集成，浏览器无法直连。** 出于安全考虑，网关会校验并拒绝带 `Origin` 头的连接、拒绝 `openai-insecure-api-key` 子协议，密钥只接受标准 `Authorization` 头。浏览器发起的 WebSocket 会自动附带 `Origin` 头，因此会被拒绝。若需在前端做实时转录，请在你自己的服务端建立到网关的连接、再把结果转发给前端。
</Warning>

## 快速开始

### 连接端点

```text theme={null}
wss://aihubmix.com/v1/realtime?intent=transcription&model=gpt-live-transcribe
```

* `intent=transcription` —— **必填**，声明这是一条转录会话。
* `model=gpt-live-transcribe` —— **必填**，模型在连接时由 URL 参数定死，会话中不可再更改（见下方约束）。

### 鉴权

握手时通过标准 HTTP 头传入密钥：

```text theme={null}
Authorization: Bearer $AIHUBMIX_API_KEY
```

### 音频格式要求

当前仅支持一种输入格式，发送前请将音频转换为：

* **编码**：PCM16（16 位有符号整数，小端序）
* **采样率**：24000 Hz
* **声道**：单声道（mono）

即 `audio/pcm@24000`。发送非该格式（如 G.711/µ-law）会被拒绝并关闭会话。

<Note>
  **转录会话不支持语音活动检测（turn\_detection / VAD）**，必须显式设为 `null`。若省略或传非 `null` 值，模型推理厂商会以 `invalid_value` 拒绝转录。网关会对转发的配置强制把 `turn_detection` 置为 `null`，但仍建议你在客户端主动设为 `null` 以保持行为清晰。
</Note>

## 会话配置（session.update）

连接建立后，客户端先发送一帧 `session.update` 配置转录参数。若你不发送，网关会用授权模型注入一份默认配置兜底，但推荐显式配置。

```json theme={null}
{
  "type": "session.update",
  "session": {
    "type": "transcription",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "transcription": {
          "model": "gpt-live-transcribe",
          "languages": ["en", "zh"],
          "prompt": "会议录音，含产品名与英文缩写",
          "keywords": ["AiHubMix", "gpt-live-transcribe"],
          "delay": "low"
        },
        "turn_detection": null,
        "noise_reduction": { "type": "near_field" }
      }
    }
  }
}
```

### 配置参数

<ParamField body="session.type" type="string" required>
  会话类型，转录场景固定为 `transcription`。
</ParamField>

<ParamField body="session.audio.input.format" type="object" required>
  输入音频格式，固定为 `{ "type": "audio/pcm", "rate": 24000 }`。
</ParamField>

<ParamField body="session.audio.input.transcription.model" type="string" required>
  转录模型。必须与连接 URL 的 `model` 一致（`gpt-live-transcribe`）。传入其它模型视为越权，会话将被以 `1008` 关闭。
</ParamField>

<ParamField body="session.audio.input.transcription.languages" type="string[]">
  预期语言列表，数组形式（如 `["en", "zh"]`）。`gpt-live-transcribe` 用**复数** `languages`，可一次声明多种语言；指定语言能提升准确性并降低延迟。取值字典见下方 [语言代码](#语言代码language-codes)。
</ParamField>

<ParamField body="session.audio.input.transcription.language" type="string">
  单数写法，单个 ISO-639-1 代码（如 `"en"`）。与 `languages` **二选一，不要同时传**（同时传会被以 `invalid_value` 拒绝）。官方对 `gpt-live-transcribe` 推荐用复数 `languages`；单数 `language` 网关也接受，便于从旧代码迁移。
</ParamField>

<ParamField body="session.audio.input.transcription.prompt" type="string">
  自由文本提示，描述录音场景（如「客服通话」「含医学术语的问诊」），帮助模型贴合语域。实测服务端会在 `session.updated` 中原样回显，已生效。
</ParamField>

<ParamField body="session.audio.input.transcription.keywords" type="string[]">
  字面提示词数组，用于产品名、缩写、专有名词等易错词（如 `["AiHubMix", "gpt-live-transcribe"]`）。它是**提示**而非强制输出；每个词单独一项，避免包含 `<`、`>`、换行符。实测已回显生效。
</ParamField>

<ParamField body="session.audio.input.transcription.delay" type="string">
  延迟 / 准确率档位，可选值 `minimal`、`low`、`medium`、`high`、`xhigh`——档位越高越准但延迟越大。**注意**：网关接受该字段（不报错），但实测未在 `session.updated` 中回显，生效性以官方文档为准、暂未由回显确认。
</ParamField>

<ParamField body="session.audio.input.turn_detection" type="null" required>
  语音活动检测。转录会话必须为 `null`。
</ParamField>

<ParamField body="session.audio.input.noise_reduction" type="object">
  可选的降噪配置，如 `{ "type": "near_field" }`（近场，适合麦克风贴近说话人）或 `{ "type": "far_field" }`（远场）。
</ParamField>

### 语言代码（language codes）

`languages` / `language` 的取值遵循以下格式，**大小写敏感、必须是下方支持的形式**，传入不支持或格式错误的代码会被 realtime API 拒绝：

| 类别               | 示例                       | 说明                          |
| ---------------- | ------------------------ | --------------------------- |
| ISO 639-1（两位）    | `en`、`zh`、`es`、`fr`、`ja` | 最常用，一门语言一个两位码               |
| 部分 ISO 639-3（三位） | `eng`、`spa`、`yue`、`cmn`  | 用于区分方言，如 `yue`=粤语、`cmn`=普通话 |
| 地区化中文            | `zh-cn`、`zh-tw`、`zh-hk`  | 语言 + 地区，细分简繁与港台用词           |

<Tip>
  用 `languages` 复数时，把最可能出现的语言排在前面。多语混说场景（如中英夹杂）可写 `["zh", "en"]`；单一语言直接写 `["en"]` 即可，比不指定更准更快。
</Tip>

## 发送音频

将 PCM16 音频切成小片（如每 100ms 一片），base64 编码后通过 `input_audio_buffer.append` 事件持续发送：

```json theme={null}
{
  "type": "input_audio_buffer.append",
  "audio": "<base64 编码的 PCM16 音频片段>"
}
```

由于转录会话不启用 VAD（语音活动检测），服务端不会自动判断一段话何时结束。发完一段音频后，需**手动**发一帧 `input_audio_buffer.commit` 标记该段结束，触发转录收尾并返回 `completed` 结果：

```json theme={null}
{ "type": "input_audio_buffer.commit" }
```

## 接收转录结果

服务端会持续推送事件，关键事件类型：

<ParamField body="session.created / session.updated" type="event">
  会话创建、配置更新的确认。
</ParamField>

<ParamField body="conversation.item.input_audio_transcription.delta" type="event">
  转录**增量**结果，字段 `delta` 是本次新增的文字片段。边说边返回，适合实时上屏。
</ParamField>

<ParamField body="conversation.item.input_audio_transcription.completed" type="event">
  一段语音转录**完成**，字段 `transcript` 是该段的完整文本。
</ParamField>

<ParamField body="error" type="event">
  错误事件，包含错误码与说明。
</ParamField>

## 完整示例

下面给出两种写法，任选其一：

* **OpenAI 官方 SDK（推荐）**：无需手写 WebSocket，把 `websocket_base_url`（SDK 的 WebSocket 基址参数）指到网关即可复用官方库。
* **原生 websockets**：不装 SDK，直接按协议收发帧，依赖最少、便于排查。

<Note>
  **为什么官方 demo 不传模型名，我们却要传？** OpenAI 的转录 intent 把模型放在 `session.update` 的 `transcription.model` 里，连接 URL 只带 `?intent=transcription`。AiHubMix 网关不同：模型名**必须**出现在握手 URL（`?model=gpt-live-transcribe`）——因为网关在 WebSocket 握手那一刻就要用它**选择模型推理商、鉴权、预留额度**，而 `session.update` 是握手完成之后才到、来不及。所以用 SDK 时要给 `connect()` 显式传 `model`（SDK 会把它拼进 URL query）；缺了它网关在**握手期就回 `400 missing_model_parameter`**——`connect()` 直接抛异常、连接根本建不起来，压根进不到 `session.update` 那一步。会话内 `transcription.model` 仍需与 URL 一致。
</Note>

<CodeGroup>
  ```python Python (OpenAI SDK) theme={null}
  # 依赖: pip install "openai[realtime]"
  import asyncio
  import base64
  from openai import AsyncOpenAI

  # 复用官方 SDK 只需三处适配:
  #   1) websocket_base_url 指向 AiHubMix 网关(而非 OpenAI 默认地址)
  #   2) connect() 显式传 model —— 见上方 Note,网关握手必需
  #   3) extra_query 带上 intent=transcription
  client = AsyncOpenAI(
      api_key="sk-***",  # 替换为你的 AiHubMix API 密钥
      websocket_base_url="wss://aihubmix.com/v1",
  )

  async def main():
      async with client.realtime.connect(
          model="gpt-live-transcribe",           # 必传: 落进握手 URL,网关据此选路+计费
          extra_query={"intent": "transcription"},
      ) as conn:
          # 1) 配置转录会话
          await conn.session.update(session={
              "type": "transcription",
              "audio": {"input": {
                  "format": {"type": "audio/pcm", "rate": 24000},
                  "transcription": {
                      "model": "gpt-live-transcribe",
                      "languages": ["en", "zh"],
                      "prompt": "会议录音,含产品名与英文缩写",
                      "keywords": ["AiHubMix", "gpt-live-transcribe"],
                  },
                  "turn_detection": None,
                  "noise_reduction": {"type": "near_field"},
              }},
          })

          # 2) 读取本地 PCM16 / 24kHz / 单声道 裸音频,分块发送
          async def send_audio():
              with open("audio_pcm16_24k.raw", "rb") as f:
                  pcm = f.read()
              chunk = 24000 * 2 // 10  # 100ms = 采样率 × 2字节 ÷ 10
              for i in range(0, len(pcm), chunk):
                  await conn.input_audio_buffer.append(
                      audio=base64.b64encode(pcm[i:i + chunk]).decode()
                  )
                  await asyncio.sleep(0.1)  # 模拟实时节奏
              # 无 VAD,音频发完后手动 commit 触发转录收尾
              await conn.input_audio_buffer.commit()

          asyncio.create_task(send_audio())

          # 3) 接收转录结果
          async for evt in conn:
              etype = getattr(evt, "type", "")
              if etype.endswith("transcription.delta"):
                  print(getattr(evt, "delta", ""), end="", flush=True)
              elif etype.endswith("transcription.completed"):
                  print("\n[完成]", getattr(evt, "transcript", ""))
                  break  # 拿到完整结果即可退出
              elif etype == "error":
                  print("\n[错误]", evt.to_dict())
                  break

  asyncio.run(main())
  ```

  ```python Python (websockets) theme={null}
  import asyncio
  import base64
  import json
  import websockets

  API_KEY = "sk-***"  # 替换为你的 AiHubMix API 密钥
  URL = (
      "wss://aihubmix.com/v1/realtime"
      "?intent=transcription&model=gpt-live-transcribe"
  )

  async def main():
      # websockets >= 13 用 additional_headers；旧版本用 extra_headers
      async with websockets.connect(
          URL, additional_headers={"Authorization": f"Bearer {API_KEY}"}
      ) as ws:
          # 1) 配置转录会话
          await ws.send(json.dumps({
              "type": "session.update",
              "session": {
                  "type": "transcription",
                  "audio": {"input": {
                      "format": {"type": "audio/pcm", "rate": 24000},
                      "transcription": {"model": "gpt-live-transcribe", "language": "en"},
                      "turn_detection": None,
                      "noise_reduction": {"type": "near_field"},
                  }},
              },
          }))

          # 2) 读取本地 PCM16 / 24kHz / 单声道 裸音频，分块发送
          async def send_audio():
              with open("audio_pcm16_24k.raw", "rb") as f:
                  pcm = f.read()
              chunk = 24000 * 2 // 10  # 100ms = 采样率 × 2字节 ÷ 10
              for i in range(0, len(pcm), chunk):
                  await ws.send(json.dumps({
                      "type": "input_audio_buffer.append",
                      "audio": base64.b64encode(pcm[i:i + chunk]).decode(),
                  }))
                  await asyncio.sleep(0.1)  # 模拟实时节奏
              # 无 VAD，音频发完后手动 commit 触发转录收尾
              await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))

          asyncio.create_task(send_audio())

          # 3) 接收转录结果
          async for msg in ws:
              evt = json.loads(msg)
              etype = evt.get("type", "")
              if etype.endswith("transcription.delta"):
                  print(evt.get("delta", ""), end="", flush=True)
              elif etype.endswith("transcription.completed"):
                  print("\n[完成]", evt.get("transcript", ""))
                  break  # 拿到完整结果即可退出
              elif etype == "error":
                  print("\n[错误]", evt.get("error"))
                  break

  asyncio.run(main())
  ```

  ```bash 连接测试 (wscat) theme={null}
  # 用 wscat 快速验证连通性与鉴权（需先 npm i -g wscat）
  wscat -c "wss://aihubmix.com/v1/realtime?intent=transcription&model=gpt-live-transcribe" \
    -H "Authorization: Bearer $AIHUBMIX_API_KEY"

  # 连接成功后，粘贴一帧 session.update 即可开始（音频需自行 base64 后用 input_audio_buffer.append 发送）
  ```
</CodeGroup>

<Tip>
  把任意音频转成本接口要求的裸 PCM 格式，可用 ffmpeg：

  ```bash theme={null}
  ffmpeg -i input.mp3 -f s16le -acodec pcm_s16le -ac 1 -ar 24000 audio_pcm16_24k.raw
  ```
</Tip>

## 运行效果（线上实测）

以下为上述示例在 `aihubmix.com` 线上环境（模型 `gpt-live-transcribe`）的真实运行结果。配置了 `languages: ["en", "zh"]` + `prompt` + `keywords` + `delay: "low"` + `noise_reduction: { "type": "near_field" }`：

```text theme={null}
# 1) 服务端回显确认（session.updated）—— languages / prompt / keywords / noise_reduction 均原样回显
session.updated  session.audio.input.transcription = {
                   "model": "gpt-live-transcribe",
                   "language": null,
                   "languages": ["en", "zh"],
                   "keywords": ["AiHubMix", "gpt-live-transcribe"],
                   "prompt": "Product demo recording, contains the brand name AiHubMix."
                 }
                 noise_reduction = { "type": "near_field" }   turn_detection = null
                 # 注意：请求里传的 delay 字段未出现在回显中

# 2) 音频（PCM16 / 24kHz / 单声道）分块发送并 commit 后，转录逐字增量返回（delta），末尾给出完整文本（completed）
Hello, this is a real-time transcription test for AIHubMix. The weather is really nice today
[完成] Hello, this is a real-time transcription test for AIHubMix. The weather is really nice today
```

<Note>
  实测中 `languages`、`prompt`、`keywords`、`noise_reduction` 均被服务端在 `session.updated` 中原样回显，说明配置已实际生效（并非仅接受不处理）。`delay` 字段网关接受但不回显，生效性以官方文档为准；`language`（单数）与 `languages`（复数）二者只能传其一。
</Note>

## 计费说明

* **单价**：`gpt-live-transcribe` 按 **\$0.017 / 分钟** 计费（以[模型详情页](https://aihubmix.com/model/gpt-live-transcribe)实时挂牌价为准）。
* 按**转录的音频时长**计费：以实际转发到转录模型的音频秒数为准，向上取整到整秒。例如转录 90 秒音频，计费 `90 ÷ 60 × $0.017 = $0.0255`。
* 计费不受网络往返或空闲等待影响，只对真正送入转录的音频计时。
* **边用边结算**：这是一条长连接，费用不是等会话结束才一次性结算，而是**在会话过程中分段实时扣除**。建立会话时会先按约 1 分钟用量做一次**额度预留**（仅作准入校验，不是真实扣费）；会话中每 20 秒滚动续期一次预留，实际费用按真实转发秒数分段扣除，会话结束后释放剩余预留。**因此账户可用余额至少要够约 1 分钟的用量，会话才能建立。**
* 在 [用量与账单](https://aihubmix.com) 的消费明细里，每条实时转录记录的**备注**会标注「每分钟单价」与「本次实收秒数」，方便逐条核对。

<Frame caption="用量与账单 Activity 中的一条 gpt-live-transcribe 实时转录计费记录，备注标注按 7 秒音频 × $0.017 / 分钟计费，实收 $0.001982，与上文口径一致。">
  <img src="https://mintcdn.com/aihubmix/NHavMnNP2PBQnyvP/public/cn/realtime-transcription-billing.png?fit=max&auto=format&n=NHavMnNP2PBQnyvP&q=85&s=ab7d655e49bfe8b29a0f63d9b9a4ad1c" alt="gpt-live-transcribe 实时转录在用量与账单 Activity 里的计费记录，备注显示按 7 秒音频、每分钟 $0.017 计费" width="3244" height="1176" data-path="public/cn/realtime-transcription-billing.png" />
</Frame>

## 限制与约束

1. **单会话时长**：一条 WebSocket 连接最长 **62 分钟**，到时服务端主动关闭；需要更长请分段重连。
2. **余额不足**：分两种情况——**建立会话时**可用余额不足以覆盖约 1 分钟的预留额度，握手直接被拒（HTTP 403），会话不会建立；**会话进行中**若余额耗尽（每 20 秒的续期校验或分段扣费后复查发现），已建立的连接会被立即关闭。
3. **仅服务端**：不支持浏览器直连（校验 `Origin` 头），请在服务端集成。
4. **模型锁定**：模型在连接 URL 定死，会话中通过 `session.update` 改模型会被拒绝并关闭会话。
5. **格式锁定**：仅支持 `audio/pcm@24000` 单声道，其它格式会被拒绝。

## 常见错误

| 场景                    | 关闭码 / 状态                           | 说明                    |
| --------------------- | ---------------------------------- | --------------------- |
| 服务未开启                 | HTTP 403 `realtime_disabled`       | 实时转录未对该环境开放           |
| 携带 `Origin` 头 / 浏览器直连 | 握手被拒                               | 改用服务端连接               |
| 会话中改模型                | `1008` `model_override_forbidden`  | 模型只能在连接 URL 指定        |
| 音频格式非 PCM             | `1008` `audio_format_unsupported`  | 转成 `audio/pcm@24000`  |
| 建连时余额不足               | HTTP 403 `insufficient_user_quota` | 余额不足以预留约 1 分钟用量，充值后重试 |
| 会话中余额耗尽               | 连接被关闭                              | 充值后重连                 |

***

更新时间：2026-09-16
