# Qwen3 ASR API 调用文档

版本：1.0.0 · 更新：2026-10-13 · 服务：Qwen3-ASR-0.6B / 中文

| 用途 | 地址 |
|---|---|
| HTTP API | `https://asr.talent-map.com` |
| 流式 WebSocket | `wss://asr.talent-map.com/ws/asr` |
| 在线文档 | `https://asr.talent-map.com/api-docs` |
| OpenAPI 3.1，可导入 Postman / Apifox | `https://asr.talent-map.com/openapi.json` |
| 本文 Markdown | `https://asr.talent-map.com/api-reference.md` |
| 已接入公网 API 的录音测试页 | `https://asr.talent-map.com/` |

公网 URL **没有 `/api` 前缀**。`/api` 只用于本机 localhost 开发代理。

Next.js 全栈项目与 ASR 部署在同一台服务器时，见[同机部署与优化](#nextjs-same-host)。该方案使用内部地址调用 ASR，浏览器仍访问业务站点的 HTTPS/WSS。

## 1. 认证与接入方式

HTTP 和非浏览器 WebSocket 客户端发送：

```http
Authorization: Bearer YOUR_TOKEN
```

Token 为共享 API 密钥，当前不自动过期。不要放到 URL、查询参数或普通访问日志中。浏览器原生 WebSocket 不支持设置握手 Authorization，请在第一条 `start` JSON 内发送 `api_token`，详见流式接口。

当前测试页按用户要求已将部署 Token 写入网页配置并自动连接。这使该 Token 可被页面访问者读取；它不能再限制公网调用者身份。其他项目应明确是否也采用这种公开测试方式。若要恢复受限服务，需要更换 Token，并将凭据保留在项目后端。

推荐其他业务项目由自己的后端调用此 API，然后将文本返回前端。跨域浏览器 HTTP 目前未启用 CORS；WebSocket 的浏览器 Origin 仅允许 `https://asr.talent-map.com`、`http://localhost:8088`、`http://127.0.0.1:8088`。使用其他前端域名直连时，需要追加准确的允许 Origin / CORS 配置。服务端调用无需发送 Origin。

## 2. 输入与服务限制

| 项目 | 要求 |
|---|---|
| 采样率 | 16000 Hz |
| 声道 | 单声道 |
| 样本格式 | 有符号 16 位、小端 `pcm_s16le` |
| 流式传输 | 原始 PCM 字节，不含 WAV 头、不用 Base64 |
| 整段上传 | 包含 WAV 头的 PCM16 WAV，multipart `file` 字段 |
| 建议录音 | 每段 2–8 秒 |
| 最大录音 | 15 秒，即 240000 样本 / 480000 PCM 字节 |
| 单帧 | 2–3200 个偶数字节，建议 3200 字节 / 100ms；尾帧可短 |
| 并发 | 全服务仅一个活动识别任务；HTTP 与 WS 共用容量 |
| SDK 分块 | 当前 500ms，由服务端 ready 的 `chunk_ms` 告知 |
| 音频积压 | 等待推理的 PCM 超过两秒会报 `overloaded` |
| 等待输入 | 首条消息 / 相邻消息超过约 10 秒会超时 |
| WS 会话 | 就绪后的会话处理总时限约 40 秒 |
| 输出 | 中文全文快照，无逐字时间戳、说话人分离或翻译 |

浏览器经常实际采样 44.1kHz 或 48kHz。必须连续低通重采样到 16kHz 后再转换 PCM，不能只修改 WAV 头。仓库 `web/audio.js` 与 `web/pcm-worklet.js` 可作为采集参考。

WebM、Opus、MP3、立体声 WAV 或浮点 WAV 不属于当前接口支持范围。上传 WAV 文件体积还受 `484096` 字节上限限制，multipart 请求体受 `545536` 字节上限限制。

## 3. 整段转写

**`POST /v1/audio/transcriptions`**

`Content-Type: multipart/form-data`，必填文件字段 `file`。让 HTTP 库生成 multipart boundary，不要手动设置成 `audio/wav` 或固定 multipart Content-Type。当前不提供 `model`、`language`、`prompt`、`stream`、时间戳等参数；多余 multipart 字段不会改变识别行为。

```bash
curl --connect-timeout 5 --max-time 30 \
  -H 'Authorization: Bearer YOUR_TOKEN' \
  -F 'file=@sample.wav;type=audio/wav' \
  https://asr.talent-map.com/v1/audio/transcriptions
```

成功 `200 application/json`，例如：

```json
{"text":"今天下午我们一起去公园散步。","audio_ms":3080,"inference_ms":42.5}
```

| 字段 | 类型 | 含义 |
|---|---|---|
| `text` | string | 完整文本，可能有标点；可以为空 |
| `audio_ms` | integer | 提交的音频样本时长，毫秒，取整 |
| `inference_ms` | number | 服务端推理调用耗时，毫秒；不包含文件上传、公网往返或浏览器采集 |

示例中的文本和耗时只表示响应形状。业务延迟应从自己的请求链路计时。

## 4. 流式转写

每次连接处理一段录音。结束后服务器发送一次 `final` 并正常关闭连接；下一段重新建立 WebSocket。

### 4.1 连接和开始

连接 `wss://asr.talent-map.com/ws/asr`。后端客户端握手使用 Bearer 头，然后发送：

```json
{"type":"start","sample_rate":16000,"channels":1,"encoding":"pcm_s16le"}
```

浏览器客户端第一条消息改为：

```json
{"type":"start","sample_rate":16000,"channels":1,"encoding":"pcm_s16le","api_token":"YOUR_TOKEN"}
```

等待服务器回复 `ready`，**不要在 ready 前发送音频**：

```json
{"type":"ready","session_id":"c806544765fd4d098cb237218604c6ee","chunk_ms":500}
```

`session_id` 是排查会话问题的标识。`chunk_ms` 是模型更新分块，并不要求客户端把网络帧也设为 500ms。

### 4.2 按序发送 PCM

每 100ms 左右发送 3200 字节原始 PCM 二进制帧。文件回放也应按实时速度发送；一次性把整段音频灌入 WS 可能触发两秒积压限制。已经录好的整段音频通常使用 HTTP 接口更合适。

服务器仅在文字发生变化时推送：

```json
{"type":"partial","revision":1,"text":"今天下午"}
{"type":"partial","revision":2,"text":"今天下午我们一起去公园"}
```

`text` 是**完整文本快照**，末尾可能改写。UI 应替换上一份文字，不要拼接。`revision` 在本次会话中递增；并不代表词数。少于一个 SDK 分块的录音可能没有任何 partial，随后直接收到 final。

### 4.3 停止和最终结果

停止采集后先清空 AudioWorklet、重采样器和网络发送缓冲，把尾帧全部按序发完，再发送：

```json
{"type":"end"}
```

继续监听，不要立即关闭 WebSocket。服务器处理完所有已收到的音频，并完成 SDK 尾帧推理后发送：

```json
{"type":"final","revision":3,"text":"今天下午我们一起去公园散步。","audio_ms":3080,"finalize_ms":39.2}
```

| 字段 | 类型 | 含义 |
|---|---|---|
| `revision` | integer | 最终文本 revision，比先前 partial 大 |
| `text` | string | 最终全文快照 |
| `audio_ms` | integer | 服务端实际接收的 PCM 样本总时长 |
| `finalize_ms` | number | 服务端收到 end 到准备发送 final 的耗时，含已有音频积压与尾帧收尾；不含客户端尾帧清空和公网返回时间 |

成功后关闭码为 `1000`。一旦收到 final，业务可以提交文本；final 文本可能与最后一个 partial 相同，也可能修正。服务不会在流式结束时额外强制做一次整段识别。

### 4.4 失败和断开

通常先发送：

```json
{"type":"error","code":"busy","message":"已有识别任务，请稍后再试"}
```

协议、认证、超时或 busy 错误通常以 `1008` 关闭；推理失败为 `1011`。握手时 IP / Origin 被拒绝，或提供了错误 Authorization 请求头，可能直接以 HTTP 403 拒绝升级，此时没有 JSON error 帧。

连接在 final 前关闭时，该段未完成；不要把最近一次 partial 当成成功结果。没有恢复同一会话的接口。重新连接意味着开始新任务；可保留本地 PCM，在连接故障后由用户选择整段重试。断开后，正在执行的同步 GPU 调用会先结束再释放容量，因此极短时间内仍可能返回 busy。

## 5. 健康与就绪

**`GET /healthz`**：无需 Token，成功 `200 {"status":"alive"}`。只说明 HTTP 进程可响应，不说明模型可识别。

**`GET /readyz`**：要求 Bearer Token。

```json
{"status":"ready","busy":false,"error":null}
```

| HTTP | 状态 | 含义 |
|---|---|---|
| 200 | `ready`, `busy=false` | 模型可用，当前容量空闲 |
| 200 | `ready`, `busy=true` | 模型可用，但已有任务 |
| 503 | `loading` | 正在加载/预热，或加载失败；`error` 可能是异常类型 |

ready 查询不会预留容量；查询后发起识别仍可能遇到其他客户端抢先占用。

## 6. HTTP 错误与 WS 错误码

HTTP 业务错误一般为 `{"error":"CODE","message":"可选说明"}`；`message` 可缺省。WS 对应 `{"type":"error","code":"CODE","message":"说明"}`。

| HTTP | 错误码 | 处理建议 |
|---|---|---|
| 400 | `invalid_format` | 转成合法 16kHz 单声道 PCM16 WAV |
| 400 | `empty_audio` | 提交至少一个音频样本 |
| 400 | `audio_too_long` | 按不超过 15 秒切段 |
| 401 | `unauthorized` | 检查 Token；响应附 `WWW-Authenticate: Bearer` |
| 403 | `forbidden` | 检查代理到后端的 peer 白名单；不是语言/文件错误 |
| 408 | `timeout` | 上传在时限内没有完成 |
| 409 | `busy` | 稍后重试；当前全服务只允许一路识别 |
| 413 | `upload_too_large` | 请求体或 WAV 体积超限 |
| 422 | FastAPI `detail` 数组 | 缺少 multipart `file`，或请求类型不匹配 |
| 500 | `inference_failed` | 本次失败，保留本地音频后决定是否重试 |
| 503 | `not_ready` | 模型未就绪，等待 readyz |

WS 另有 `invalid_message`（非法 JSON/未知类型/控制消息超过1024字符）、`invalid_sequence`（重复 start/end、音频先于 start 或在 end 后）、`invalid_frame`（空帧/奇数字节/超过3200字节）、`overloaded`（两秒积压）、`timeout`（首帧、输入间隔或会话超时）。

网络代理可能返回 HTML 格式的 502/504；客户端应检查状态码和 Content-Type，不应假定所有故障都是 JSON。重试建议只针对 busy、未就绪或瞬时网络故障，采用有限次数与退避；401、格式错误和超长输入需要修正后再调用。

## 7. Python 项目接入

依赖：Python 3.11+、`httpx==0.28.1`、`websockets==16.0`。仓库有可直接运行的 `examples/python/asr_client.py`，支持整段与实时速度流式回放；Token 从环境或私有文件读取，没有写死在示例里。

```bash
python examples/python/asr_client.py --token-file /private/asr-token --mode upload sample.wav
python examples/python/asr_client.py --token-file /private/asr-token --mode stream sample.wav
```

最小 HTTP 示例：

```python
import os
import httpx

with open("sample.wav", "rb") as audio, httpx.Client(timeout=30, trust_env=False) as client:
    response = client.post(
        "https://asr.talent-map.com/v1/audio/transcriptions",
        headers={"Authorization": "Bearer " + os.environ["ASR_API_TOKEN"]},
        files={"file": ("sample.wav", audio, "audio/wav")},
    )
    response.raise_for_status()
    print(response.json()["text"])
```

## 8. JavaScript / Node.js 项目接入

Node.js 20+ 可直接使用 fetch / FormData / Blob：

```javascript
import {readFile} from 'node:fs/promises';
const form = new FormData();
form.append('file', new Blob([await readFile('sample.wav')], {type:'audio/wav'}), 'sample.wav');
const response = await fetch('https://asr.talent-map.com/v1/audio/transcriptions', {
  method:'POST', headers:{Authorization:`Bearer ${process.env.ASR_API_TOKEN}`},
  body:form, signal:AbortSignal.timeout(30000),
});
if (!response.ok) throw new Error(`ASR HTTP ${response.status}: ${await response.text()}`);
console.log((await response.json()).text);
```

浏览器已经获得 16kHz PCM 帧时，核心流式调用如下。音频必须通过真实采集/定时回放按序传入，且只在 ready 之后发送：

```javascript
const ws = new WebSocket('wss://asr.talent-map.com/ws/asr');
let ready = false;
ws.onopen = () => ws.send(JSON.stringify({
  type:'start', sample_rate:16000, channels:1, encoding:'pcm_s16le', api_token:token,
}));
ws.onmessage = ({data}) => {
  const result = JSON.parse(data);
  if (result.type === 'ready') ready = true;
  if (result.type === 'partial') showTemporaryText(result.text); // 替换快照
  if (result.type === 'final') { ready = false; saveFinalText(result.text); }
  if (result.type === 'error') { ready = false; showError(result.code); }
};
function sendPCM(arrayBuffer) {
  if (!ready || ws.readyState !== WebSocket.OPEN) throw new Error('ASR not ready');
  if (ws.bufferedAmount + arrayBuffer.byteLength > 64000) throw new Error('audio backlog');
  ws.send(arrayBuffer);
}
function finishAfterAllTailFrames() {
  if (!ready) throw new Error('ASR not ready');
  ws.send(JSON.stringify({type:'end'})); // 继续监听直到 final
}
```

`web/api-client.js` 可复用 HTTP、ready 与浏览器 WS 认证代码；`web/app.js` 是包括采集、停止尾帧和对照识别的完整示例。其他浏览器域名直连需先配置允许的 Origin / CORS，或由自己的后端代理。

## 9. 当前行为边界

- 采用用户手动结束录音，不会额外等待静音 VAD；不是永久在线的长会话服务。
- 无按用户分配的并发、自动切段、批量流式、音频时间戳或自动 Token 轮换。
- 识别结果可能修正，数字可输出为汉字，人名可能错写，纯静音也可能产生文字；业务端仍需确认文本。
- 服务不保存提交的录音到磁盘。测试页的示例 WAV 是明确生成的测试素材。
- OpenAPI 描述 HTTP 接口，WS 协议以本文为准；将 `openapi.json` 导入工具不会自动创建 WS 采集客户端。

<a id="nextjs-same-host"></a>

## 10. Next.js 与 ASR 同机部署

本节是其他业务项目的部署接入方案。下面的业务路由 `/api/asr/transcriptions`、`/asr/ws` 为建议路径，需要在业务项目或网关中实现；当前 ASR 服务没有新增这些路由。ASR 的音频格式、认证和消息协议仍按第 1–6 节执行。

### 10.1 调用链路与地址选择

“同机”指 Next.js **服务端**与 ASR 在同一台物理服务器。用户浏览器运行在用户自己的设备上，浏览器里的 `127.0.0.1` 指向用户设备，不能用它访问服务器上的 ASR。

```text
录完上传：浏览器 → 业务域名 HTTPS /api/asr/transcriptions
                    → Next.js 服务端 → 内部 HTTP /v1/audio/transcriptions

实时识别：浏览器 → 业务域名 WSS /asr/ws
                    → 验证业务登录的 WS 网关 → 内部 WS /ws/asr
```

| 部署方式 | Next.js 服务端使用的 ASR 地址 | 注意事项 |
|---|---|---|
| Next.js 与当前 systemd ASR 都直接运行在宿主机 | `http://127.0.0.1:8123`；WS 为 `ws://127.0.0.1:8123/ws/asr` | 当前 ASR peer 白名单已包含 loopback，内部调用仍需要 Token |
| 两者都在同一个 Compose 网络 | 例如 `http://asr:8123`；WS 为 `ws://asr:8123/ws/asr` | `asr` 是示例服务名；使用容器端口，按实际连接来源配置 ASR 白名单 |
| Next.js 在 Docker，ASR 仍是宿主机 systemd 服务 | `http://host.docker.internal:8123` | Linux 需配置 host-gateway；ASR 和宿主机防火墙需允许实际容器来源 |
| WS 网关在另一台机器 | 使用可达的私网地址，例如现有代理使用 `10.100.0.2:8123` | 网关的 `127.0.0.1` 指向网关自己；仅 Next.js 与 ASR 同机并不能让远程代理使用 loopback |

Docker Compose 的服务发现和容器端口规则见[官方网络文档](https://docs.docker.com/compose/how-tos/networking/)。同一物理机上的容器通常有独立网络命名空间，不能直接把容器的 `localhost` 当成宿主机。

当前 `asr.talent-map.com` 的代理在 `10.100.0.1`，上游是 `10.100.0.2:8123`。可保留这条公网链路，同时让同机 Next.js 后端走 loopback。**保留现有远程代理时，ASR 继续监听 `0.0.0.0:8123` 并用白名单和防火墙控制访问**；直接改为仅监听 `127.0.0.1` 会中断现有公网入口。

### 10.2 Next.js 后端整段上传

宿主机部署的 Next.js 配置以下服务端环境变量；容器部署只需按上表调整内部 URL：

```dotenv
ASR_INTERNAL_URL=http://127.0.0.1:8123
ASR_API_TOKEN=YOUR_TOKEN
```

通过 Next.js 运行环境或私有 `.env.local` 注入凭据。不要使用 `NEXT_PUBLIC_ASR_API_TOKEN`，也不要把凭据传入 Client Component props；`NEXT_PUBLIC_` 变量会进入浏览器构建产物。见 [Next.js 环境变量文档](https://nextjs.org/docs/app/guides/environment-variables)。

业务项目可使用 App Router Route Handler 接收 `/api/asr/transcriptions`，先验证登录和用户调用额度，再转发 multipart `file` 到 ASR。Route Handler 是公开 HTTP 入口，不能仅凭地址有 `/api` 就视为已认证；接入方式见 [Next.js 后端代理指南](https://nextjs.org/docs/app/guides/backend-for-frontend)。

下面是仅供服务端调用的 TypeScript 转写函数，可放在业务项目的 `lib/asr.ts`。它返回原始上游 Response，便于业务路由按状态码处理；它本身不实现业务登录验证。

```typescript
import 'server-only';

export async function transcribeWav(file: File): Promise<Response> {
  const base = process.env.ASR_INTERNAL_URL;
  const token = process.env.ASR_API_TOKEN;
  if (!base || !token) throw new Error('ASR server configuration missing');
  const form = new FormData();
  form.append('file', file, file.name);
  return fetch(`${base.replace(/\/$/, '')}/v1/audio/transcriptions`, {
    method: 'POST',
    headers: {Authorization: `Bearer ${token}`},
    body: form,
    cache: 'no-store',
    signal: AbortSignal.timeout(30000),
  });
}
```

Route Handler 应使用 Node.js runtime，按以下顺序处理请求：

1. 验证当前业务用户，再检查上传 Content-Type、请求体上限和读取超时；不能只依赖 Content-Length，也不能无界执行 `request.formData()`。
2. 提取 `file`，确认 WAV 文件不超过 `484096` 字节；multipart 请求体按 `545536` 字节限制。边缘代理可先限制总请求体，业务后端仍需处理缺少或伪造 Content-Length 的情况。
3. 调用上面的服务端函数。由 FormData 自动生成 multipart boundary，不手工设置 Content-Type。
4. 保留 ASR 的成功字段和错误状态语义，尤其是 `409 busy`、`413`、`503`；不要把失败包装成 `200`。内部 `401` 表示服务凭据配置问题，业务登录过期应在第一步处理。
5. 将请求超时或上游连接失败明确返回为业务网关错误，保留音频供用户决定是否重试。上游可能返回非 JSON，解析前检查状态和 Content-Type。

浏览器只上传到自身业务域名，HTTP 无需新增 ASR 跨域配置。重采样仍在浏览器采集阶段完成：16kHz、单声道、PCM16 WAV，最长 15 秒；浏览器 MediaRecorder 默认的 WebM/Opus 不能直接上传。

### 10.3 实时 WebSocket 网关

实时识别使用能持续代理 WebSocket 的 Nginx 或专用 WS 网关。普通上传 Route Handler 用于 HTTP；部署流式接口时，应明确验证宿主环境对 WebSocket Upgrade 和长连接的支持，不能把它当成普通 POST 请求处理。Next.js 自部署可以放在反向代理后，见[官方自部署指南](https://nextjs.org/docs/app/guides/self-hosting)。

推荐网关在握手阶段用业务 session/cookie 验证用户，再向 ASR 注入 `Authorization: Bearer YOUR_TOKEN`。例如 Nginx `auth_request` 可以调用业务的登录校验接口，验证通过才允许升级；该校验接口也需要业务项目实现。仅由代理自动添加 Token，并不会限制公网调用者身份。见 [Nginx 认证子请求](https://nginx.org/en/docs/http/ngx_http_auth_request_module.html)。

WS 网关需要满足以下条件：

- 上游路径为 `/ws/asr`，使用 HTTP/1.1 并传递 Upgrade / Connection；正确转发二进制 PCM 和文本控制帧。见 [Nginx WebSocket 代理](https://nginx.org/en/docs/http/websocket.html)。
- 在同一台宿主机上运行的网关可连接 `ws://127.0.0.1:8123/ws/asr`；远程网关或容器网关按 10.1 节选址。
- 业务浏览器的首条 `start` 可以省略 `api_token`，因为网关已在握手中注入服务 Token。消息顺序仍为 start → ready → PCM → end → final。
- 保持原始浏览器 Origin，并在 ASR 的 `ASR_ALLOWED_ORIGINS` 逗号分隔列表中追加准确业务 Origin，例如 `https://app.example.com`；保留现有允许项，配置生效需要重启 ASR。不要用伪造 Origin 代替业务登录鉴权。
- ASR 的 `ASR_ALLOWED_CLIENTS` 检查网关实际连接地址。当前配置按**单个 IP**匹配，不支持 CIDR；改动时还要检查宿主机防火墙。
- 按实时速度转发，保留顺序和背压；不要等整段录音结束再一次性灌入 WS。建议每 100ms 发 3200 字节，等待 `ready`，停止时清空尾帧再发送 `end`，收到 `final` 后才算成功。
- 代理读取超时应覆盖 ASR 约 40 秒的会话总时限，例如 60 秒；业务自行设置握手、输入和发送超时，不能无限积压。

同源业务域名简化浏览器接入，但 WS 仍受 ASR Origin 校验。HTTPS 页面使用 WSS；内部 loopback 使用 HTTP/WS 不需要绕行公网 TLS 入口。

### 10.4 Next.js 容器访问当前宿主机 ASR

以下为 Linux Compose 配置片段，假设业务服务名为 `web`，ASR 继续使用当前宿主机 systemd 部署：

```yaml
services:
  web:
    # 保留业务项目已有的 image/build、ports 等配置。
    extra_hosts:
      - "host.docker.internal:host-gateway"
    environment:
      ASR_INTERNAL_URL: http://host.docker.internal:8123
      ASR_API_TOKEN: ${ASR_API_TOKEN:?ASR_API_TOKEN must be set}
```

从容器实际连接后确认 ASR 看到的 peer 地址，再配置允许的单个来源 IP 和必要的防火墙规则。当前 loopback / WireGuard 白名单不会自动允许 Docker bridge 来源。若容器地址重建后变化，应使用受控网络和稳定地址或同步更新配置；不能把 `172.x` CIDR 直接写进当前只匹配单个 IP 的白名单。若 ASR 也迁入 Compose，则使用同一网络中的实际服务名，并单独配置其模型、GPU 与访问规则；本节不会自动迁移现有服务。

### 10.5 性能、容量与凭据边界

- 内部调用省去公网 DNS/TLS、远程反向代理和 WireGuard 绕行；实际收益以业务端端到端计时为准。模型推理耗时和当前 500ms SDK 分块等待不会因此消失。
- Next.js 与 ASR 保持独立常驻进程，业务构建或重启不应重新加载 GPU 模型。ASR 维持一个 Uvicorn worker 和一个推理线程；增加 worker 会各自加载模型，且不能保持当前全服务单路容量约束。
- 当前 HTTP 和 WS、所有接入项目**共用一个活动识别名额**。同机部署、多个 Next.js 实例或多个业务用户都不会增加 GPU 服务容量。优先展示 busy 并有限退避；如需排队，应限制长度与等待时间，多实例业务使用共享协调机制，不能假设各进程内存队列能统一调度。
- 业务登录、用户限频和调用统计放在 Next.js / 网关层；服务端 Token 负责上游认证。现有公开测试页按用户要求仍包含共享 Token，单独将 Next.js 凭据放在服务端不会让这把已经公开的 Token 恢复保密。若后续需要限制整个公网 ASR 的调用者，应另行调整公开测试入口和凭据策略。

### 10.6 部署验收

先从 Next.js 所在运行环境测试内部地址。下面适用于宿主机进程；容器内应替换为对应内部地址，`ASR_API_TOKEN` 通过私有运行环境注入：

```bash
curl --connect-timeout 3 --max-time 10 http://127.0.0.1:8123/healthz
curl --connect-timeout 3 --max-time 10 \
  -H "Authorization: Bearer ${ASR_API_TOKEN}" \
  http://127.0.0.1:8123/readyz
```

`readyz` 应返回 `200` 且 `status=ready`；`busy=true` 表示已有调用，不代表部署失败。完成业务项目接入后，再验证：未登录的业务请求被拒绝；合法 WAV 经业务 HTTP 路由成功转写；WSS 收到 ready / partial / final；另一并发请求按约定返回 busy；浏览器业务构建中没有注入服务端 Token。现有公网测试页的公开 Token 行为另按第 1 节理解。
