← 录音测试台

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 部署在同一台服务器时,见同机部署与优化。该方案使用内部地址调用 ASR,浏览器仍访问业务站点的 HTTPS/WSS。

1. 认证与接入方式

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

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 字段不会改变识别行为。

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,例如:

{"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 头,然后发送:

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

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

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

等待服务器回复 ready,不要在 ready 前发送音频:

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

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

4.2 按序发送 PCM

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

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

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

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

4.3 停止和最终结果

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

{"type":"end"}

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

{"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 失败和断开

通常先发送:

{"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。

{"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 从环境或私有文件读取,没有写死在示例里。

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 示例:

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:

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 之后发送:

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. 当前行为边界

10. Next.js 与 ASR 同机部署

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

10.1 调用链路与地址选择

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

录完上传:浏览器 → 业务域名 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 的服务发现和容器端口规则见官方网络文档。同一物理机上的容器通常有独立网络命名空间,不能直接把容器的 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:

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 环境变量文档。

业务项目可使用 App Router Route Handler 接收 /api/asr/transcriptions,先验证登录和用户调用额度,再转发 multipart file 到 ASR。Route Handler 是公开 HTTP 入口,不能仅凭地址有 /api 就视为已认证;接入方式见 Next.js 后端代理指南。

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

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 自部署可以放在反向代理后,见官方自部署指南。

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

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

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

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

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

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 性能、容量与凭据边界

10.6 部署验收

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

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 节理解。