# Sonari ASR 语音识别 API(v1)

多租户语音转文字服务。自建 FunASR 引擎，已开放中文 / 英文 / 印尼语 / 菲律宾语 /
英菲自动识别 / 泰语识别路由。支持批量(文件)和实时(流式)两种识别。

- **Base URL:** `https://asr-api.sonari.dev/v1`
- **鉴权:** API Key,`Authorization: Bearer <key>`
- **公开在线文档:** `https://asr-admin.sonari.dev/api-docs`
- **Markdown 下载:** `https://asr-admin.sonari.dev/api-docs/download`
- **OpenAPI:** `https://asr-api.sonari.dev/v1/openapi.json`

> Base URL 取决于网关对外发布的位置。若不用子域名、而是挂在现有域名的路径下,
> 则为 `https://audiolab.sonari.dev/asr/v1`。

---

## 1. 鉴权

除 `GET /v1/health` 外,每个请求都要带项目 API Key:

```
Authorization: Bearer sk_live_xxxxxxxxxxxxxxxxxxxxxxxx
```

Key 按项目(租户)发放,请保存在服务端。一个 Key 对应一个租户,各自有独立的限流和
用量统计。浏览器里发起 WebSocket 时(无法自定义请求头),用查询参数传 Key:
`?token=sk_live_...`。

Key 缺失/无效 → `401`;Key 或租户被停用 → `401`。

---

## 2. 语言支持

`lang` 取值与能力:

| lang    | 语言                  | 流式实时字幕 | 质量    |
|---------|-----------------------|--------------|---------|
| `zh`    | 中文普通话            | ✅ 支持      | 生产级  |
| `en`    | 英语                  | ✅ 支持      | 生产级  |
| `id`    | 印尼语                | ✅ rolling partial | 生产级  |
| `fil`   | 菲律宾语(他加禄)      | ✅ rolling partial | 生产级  |
| `enfil` | **英语 / 菲律宾语自动识别** | ✅ rolling partial | 生产级  |
| `th`    | 泰语                  | ✅ rolling partial | 需补标准语料评测 |

`GET /v1/languages` 返回实时能力。`zh`/`en` 使用原生流式 partial；
`id`/`fil`/`enfil`/`th` 使用 rolling-window partial，并在 `commit`/`end` 后返回最终稿。

**`enfil` —— 英菲自动识别。** 传 `lang=enfil`,无需事先知道说的是哪种语言。引擎
(whisper large-v3-turbo)做语种识别(限定在英语/他加禄之间)并据此转写,**包括英菲
混说的 Taglish**(他加禄里夹大量英文词)也能一遍出。每次响应都带检测到的语言 `lang`
字段(`"en"` 或 `"fil"`)——`/file` 在 JSON body 里,WS 流在 `done` 事件里。菲律宾语音
聊天(英菲随意混说)用这个。

**非语音过滤(音乐 / 环境音)。** ASR 引擎喂音乐、背景噪声、静音会幻觉出文字(如
`"Oh, oh, oh"`、`"Thank you"`、`"so"`)。所有语言都跑了一道平衡的非语音过滤:当输入是
音乐/噪声/静音时,转写返回**空**并带 `"filtered": true`(`/file` 的 JSON、WS 的 `done`
事件都有)。请把空的 `text`/`full_text` 当作「无人说话——忽略」,不要往下游(LLM)送。
真实语音(含麦克风电平很低的)会保留。

---

## 3. 接口

### `GET /v1/health`
存活探针,免鉴权。
```json
{ "status": "ok", "service": "asr-gateway", "version": "1.0.0", "realtime": { "provider": "internal", "target_first_token_ms": 300 } }
```

### `GET /v1/languages` *(需鉴权)*
```json
{ "languages": { "id": { "streaming": true, "quality": "production", "commercial_realtime": { "mode": "internal_engine", "target_first_token_ms": 300, "blocked": false } }, "...": {} } }
```

### `POST /v1/asr/file` *(需鉴权)* —— 文件批量识别
`multipart/form-data`:

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| file | 文件 | 是   | wav / mp3 等。任意采样率(自动转 16 kHz)。 |
| lang | 字符串 | 否 | 默认 `zh`。取上表之一。 |

响应 `200`:
```json
{
  "text": "测试一下,这是识别结果。",
  "lang": "zh",
  "filtered": false,
  "sample_rate": 24000,
  "audio_seconds": 7.0,
  "elapsed_seconds": 0.1
}
```
`enfil`/`fil`/`id` 时 `lang` 字段返回实际使用的语言(`enfil` 即自动检测出的 `"en"` 或 `"fil"`)。
`filtered: true` + 空 `text` 表示该段被判为非语音(音乐/噪声/静音)已抑制。
上传上限 50 MB(可配置),超出返回 `413`。

```bash
curl -X POST https://asr-api.sonari.dev/v1/asr/file \
  -H "Authorization: Bearer sk_live_xxx" \
  -F "file=@sample.wav" -F "lang=zh"
```

### `WS /v1/asr/stream` *(需鉴权)* —— 实时流式识别
连接:`wss://asr-api.sonari.dev/v1/asr/stream`,带 `Authorization: Bearer` 请求头;
浏览器则用 `wss://asr-api.sonari.dev/v1/asr/stream?token=sk_live_xxx`。

**协议**

| 方向 | 消息 |
|------|------|
| 客户端 → | `{"type":"start","sample_rate":16000,"lang":"zh"}`(文本) |
| 客户端 → | 裸 **PCM16 LE** 音频字节(二进制),每块 100–600 ms |
| 客户端 → | `{"type":"commit"}` 一句结束、保持连接 —— 或 |
| 客户端 → | `{"type":"end"}` 结束 |
| 服务端 → | `{"type":"ready","lang":"zh","partials":true}` |
| 服务端 → | `{"type":"partial","text":"识别中..."}`(仅 zh/en) |
| 服务端 → | `{"type":"done","full_text":"最终成稿。","lang":"fil"}`(每句一次;`enfil` 时 `lang` 为检测到的语言) |
| 服务端 → | `{"type":"error","message":"..."}` |

音频为 16-bit 有符号小端 PCM,**不带 WAV 头**。`sample_rate` ≠ 16000 时服务端自动重采样。
常见做法:客户端做语音活动检测(VAD),静默约 1 秒后发 `end`/`commit`。

关闭码:`4401` 未授权,`4429` 并发流超限。

最简浏览器示例:
```js
const ws = new WebSocket("wss://asr-api.sonari.dev/v1/asr/stream?token=sk_live_xxx");
ws.binaryType = "arraybuffer";
ws.onopen = () => ws.send(JSON.stringify({type:"start", sample_rate:16000, lang:"zh"}));
ws.onmessage = (e) => {
  const m = JSON.parse(e.data);
  if (m.type === "partial") showCaption(m.text);     // 实时字幕
  if (m.type === "done")    commitText(m.full_text);  // 最终成稿
};
// 持续发送 PCM16 LE 音频块:ws.send(int16Array.buffer)
```

### `GET /v1/usage` *(需鉴权)* —— 查询自己的用量
查询参数:`days`(1–365,默认 30)。
```json
{
  "tenant": "proj_worldparty",
  "window_days": 30,
  "requests": 1284,
  "audio_seconds": 5230.4,
  "errors": 3,
  "limits": { "rpm": 240, "max_concurrent_streams": 6 }
}
```

---

## 4. 限流与配额

每个租户:
- **每分钟请求数**(`rpm`),涵盖 `file` 请求 + WS 建连。超出 → `429`,带 `Retry-After`。
- **最大并发流**(`max_streams`)。超出 → WS 关闭码 `4429`。

用量(请求数 + 音频秒数)按租户计量,用于对账/计费。

---

## 5. 错误

所有错误都是 JSON:
```json
{ "error": { "type": "auth_error", "message": "invalid or missing API key" } }
```

| HTTP | `type`           | 触发场景 |
|------|------------------|----------|
| 400  | `bad_request`    | 不支持的 `lang`、入参非法 |
| 401  | `auth_error`     | Key 缺失 / 无效 / 被停用 |
| 413  | `too_large`      | 文件超过大小上限 |
| 429  | `rate_limited`   | 每分钟请求数超限 |
| 502  | `upstream_error` | ASR 引擎不可达 / 失败 |

(WebSocket 用关闭码 `4401` / `4429`,不用 HTTP 状态码。)

---

## 6. 接入新项目(运营侧操作)

由运营人员签发:
```bash
python manage_tenants.py add-tenant proj_acme "Acme App" --rpm 240 --max-streams 6
python manage_tenants.py add-key proj_acme --label "prod"     # 仅打印一次 Key
```
把打印出来的 `sk_live_...` 交给接入方。停用时禁用对应租户或 Key 即可。

## Voice Intelligence API v0

当前已新增 ASR 驱动的实时语音智能事件层。系统不会把未接入模型的能力伪装成已上线能力；噪声、音乐、情绪、声纹、说话人分离、抢话、反欺诈等字段会明确标记为 placeholder/planned。

已实现接口：

- `GET /v1/voice/capabilities`：能力矩阵。
- `POST /v1/voice/analyze`：正式模型 VAD、音频场景/噪声/音乐、情绪分析。
- `POST /v1/voice/event/fuse`：把 ASR/模型输出融合为 `voice_intelligence_event.v0`。
- `POST /v1/voice/asr/file`：文件 ASR + 统一语音智能事件。
- `WS /v1/voice/stream`：实时 ASR partial/final + 统一语音智能事件。
- `POST /v1/voiceprint/enroll`、`POST /v1/voiceprint/verify`：带质量门控的 WavLM 声纹 embedding，PostgreSQL 加密存储，余弦相似度验证，并写入统一语音智能事件。
- `GET /v1/voiceprint/list`、`POST /v1/voiceprint/revoke`、`POST /v1/voiceprint/delete`：声纹授权查询、撤销和删除。

设计文档：[`docs/realtime_voice_intelligence_engine_design.md`](realtime_voice_intelligence_engine_design.md)

## Voice Intelligence API v0.3

新增 P0 可运行能力：

- 正式模型 VAD：faster-whisper/Silero 输出 speech probability 和 speech/non_speech。
- 音频场景：AST AudioSet 输出噪声/音乐场景提示。
- 情绪识别：wav2vec2/SUPERB 情绪分类。
- 声纹：WavLM 512 维 speaker embedding 注册、验证、质量门控、阈值决策、查询、撤销、删除。
- 音频质量：`dbfs`、`peak`、`clipping_ratio`、`audio_quality_score`。
- 事件落库：PostgreSQL `voice_events` 表。
- `GET /v1/voice/events`：按租户、房间、会话查询最近语音事件。
- `GET /v1/voice/metrics`：查询事件计数、房间/会话数、语音请求用量。

完整生产认证仍需补更大规模标注语料，覆盖分语言 CER/WER、音乐/噪声鲁棒性、VAD 断句准确率、
情绪准确率，以及声纹线上阈值验收。




