|
|
@@ -1,5 +1,5 @@
|
|
|
# ASR Agent
|
|
|
-
|
|
|
+每次开发都要跟新本文件 这非常重要!!!要保证本文件能实时反应当前系统状态
|
|
|
## 项目架构
|
|
|
|
|
|
```
|
|
|
@@ -13,7 +13,8 @@ asr/
|
|
|
│ │ ├── qwen_engine.py # Qwen3-ASR 本地引擎
|
|
|
│ │ └── transcript_processor.py # 转录去重
|
|
|
│ ├── silero_vad.onnx # Silero VAD ONNX 模型
|
|
|
-│ ├── Dockerfile # Worker 镜像
|
|
|
+│ ├── requirements.txt # Python 依赖
|
|
|
+│ ├── Dockerfile # Dispatcher + Worker 统一镜像
|
|
|
│ └── Dockerfile.base # 基础依赖镜像
|
|
|
├── flutter_asr_client/ # Flutter 移动客户端
|
|
|
│ ├── lib/services/ # LiveKit / Settings 服务
|
|
|
@@ -30,20 +31,30 @@ asr/
|
|
|
flowchart TD
|
|
|
A[App 麦克风] -->|Opus| B[LiveKit Room]
|
|
|
B -->|auto_subscribe| C[Worker]
|
|
|
-
|
|
|
+
|
|
|
C --> D[AudioStream<br/>16kHz PCM]
|
|
|
D --> E[Silero VAD<br/>ONNX 双阈值]
|
|
|
D --> F[AudioBuffer<br/>环形缓冲]
|
|
|
-
|
|
|
+
|
|
|
E -->|语音结束| G[ASR]
|
|
|
F --> G
|
|
|
-
|
|
|
+ F --> V[声纹识别<br/>Zvec ← MFCC]
|
|
|
+
|
|
|
G -->|Mimo API / Qwen3| H[transcription]
|
|
|
- H --> I[LLM 流式 SSE<br/>Mimo v2.5 / vLLM]
|
|
|
- I -->|分段| J[TTS 并发生成<br/>Mimo API 24kHz]
|
|
|
- J -->|play_q 顺序入队| K[_player 单轨播放]
|
|
|
- K -->|AudioTrack| B
|
|
|
+ V -->|speaker_id| I[LLM 流式 SSE<br/>Mimo v2.5 / vLLM]
|
|
|
+ H --> I
|
|
|
+
|
|
|
+ V -->|加载历史| J[对话记忆<br/>JSON 持久化]
|
|
|
+ J --> I
|
|
|
+
|
|
|
+ I -->|TTS Segmenter<br/>分段| K[TTS 并发生成<br/>Mimo API 24kHz]
|
|
|
+ K -->|play_q 顺序入队| L[_player 单轨播放]
|
|
|
+ L -->|LocalAudioTrack| B
|
|
|
B -->|Opus| A
|
|
|
+
|
|
|
+ I -->|保存对话| J
|
|
|
+
|
|
|
+ H -.->|新 utterance<br/>cur_gen++ drain_queue| L
|
|
|
```
|
|
|
|
|
|
## 服务部署架构
|
|
|
@@ -54,27 +65,31 @@ flowchart TD
|
|
|
P10005[":10005"]
|
|
|
P10003[":10003"]
|
|
|
end
|
|
|
-
|
|
|
+
|
|
|
subgraph Nginx[nginx]
|
|
|
N1[HTTP Proxy]
|
|
|
N2[WebSocket Proxy]
|
|
|
end
|
|
|
-
|
|
|
- subgraph Services[内网服务]
|
|
|
+
|
|
|
+ subgraph Host[内网服务 200.200.18.11]
|
|
|
D[Dispatcher<br/>:9100]
|
|
|
+ W1[Worker 子进程 room-xxx]
|
|
|
+ W2[Worker 子进程 room-yyy]
|
|
|
LK[LiveKit Server<br/>:7880]
|
|
|
end
|
|
|
-
|
|
|
+
|
|
|
P10005 --> N1
|
|
|
P10003 --> N2
|
|
|
N1 -->|proxy_pass| D
|
|
|
N2 -->|proxy_pass| LK
|
|
|
-
|
|
|
+
|
|
|
A2[App] -->|POST /connect| P10005
|
|
|
A2 -->|WebSocket| P10003
|
|
|
-
|
|
|
- D -->|启动 Worker| W[Worker 进程]
|
|
|
- W -->|加入房间| LK
|
|
|
+
|
|
|
+ D -->|spawn_worker asyncio.Task| W1
|
|
|
+ D -->|spawn_worker asyncio.Task| W2
|
|
|
+ W1 -->|加入房间| LK
|
|
|
+ W2 -->|加入房间| LK
|
|
|
A2 -->|同一房间| LK
|
|
|
```
|
|
|
|
|
|
@@ -101,19 +116,20 @@ sequenceDiagram
|
|
|
LK->>W: participant_disconnected
|
|
|
W->>W: 检测房间空 → exit
|
|
|
W-->>LK: disconnect
|
|
|
+```
|
|
|
|
|
|
## Worker 流水线详解
|
|
|
|
|
|
| 组件 | 实现 | 说明 |
|
|
|
|------|------|------|
|
|
|
-| **VAD** | Silero VAD (ONNX) | 双阈值 0.5/0.2,5帧滑动窗口,无 ONNX 时回退能量 VAD |
|
|
|
+| **VAD** | Silero VAD (ONNX) | 双阈值 0.5/0.2,5帧滑动窗口,min_speech 0.3s / min_silence 0.4s / max_speech 8s;无 ONNX 时回退能量 VAD |
|
|
|
| **VP** | LiveKit WebRTC | AEC/ANS/AGC 由 WebRTC 处理 |
|
|
|
-| **ASR** | Mimo API / Qwen3 本地 | `ASR_PROVIDER` 环境变量切换;支持流式 SSE |
|
|
|
-| **LLM** | Mimo v2.5 / vLLM | `LLM_PROVIDER` 切换;流式 SSE + `<think>` 过滤 |
|
|
|
-| **TTS** | Mimo v2.5 API | 24kHz PCM,并行生成 + 顺序入队播放 |
|
|
|
-| **播放** | 单轨 `LocalAudioTrack` | `play_q` 队列 + `_player` 协程持续 drain |
|
|
|
-| **声纹** | scipy MFCC + Zvec | 自动注册 / 识别说话人,识别结果注入 LLM 上下文 |
|
|
|
-| **记忆** | Zvec + JSON | 声纹向量存 Zvec;对话历史存 JSON,按用户隔离 |
|
|
|
+| **ASR** | Mimo API / Qwen3 本地 | `ASR_PROVIDER` 切换;`mimo` 为流式 SSE,`qwen` 为本地 Qwen3-ASR-0.6B |
|
|
|
+| **LLM** | Mimo v2.5 / vLLM | `LLM_PROVIDER` 切换;流式 SSE + `<think>` 过滤;注入 speaker 上下文与历史记忆 |
|
|
|
+| **TTS** | Mimo v2.5 API | 24kHz PCM,按句子分段,并行生成 + `play_q` 顺序入队 |
|
|
|
+| **播放** | 单轨 `LocalAudioTrack` | 每会话复用一条 track;`_player` 协程持续 drain;新 utterance 通过 `cur_gen++` 打断旧音频 |
|
|
|
+| **声纹** | scipy MFCC + Zvec | 自动注册 / 识别说话人,识别结果注入 LLM 上下文,不暴露名字 |
|
|
|
+| **记忆** | Zvec + JSON | 声纹向量存 Zvec;对话历史存 JSON,按 `speaker_id` 隔离,保留最近 20 轮 |
|
|
|
|
|
|
## 声纹识别
|
|
|
|
|
|
@@ -123,13 +139,16 @@ flowchart LR
|
|
|
B --> C[Zvec 向量检索<br/>余弦相似度]
|
|
|
C -->|匹配 >0.65| D[返回已知用户]
|
|
|
C -->|无匹配| E[自动注册新用户]
|
|
|
- D --> F[注入 LLM 上下文]
|
|
|
- E --> F
|
|
|
- F --> G[个性化回复]
|
|
|
-
|
|
|
- B --> H[对话记忆<br/>JSON 持久化]
|
|
|
- H -->|同一说话人| I[恢复历史会话]
|
|
|
- I --> F
|
|
|
+
|
|
|
+ D --> F[加载对话历史<br/>JSON 记忆]
|
|
|
+ E --> G[注入 LLM 上下文]
|
|
|
+ F --> G
|
|
|
+
|
|
|
+ G --> H[LLM 生成<br/>个性化回复]
|
|
|
+ H --> I[保存对话<br/>JSON 持久化]
|
|
|
+
|
|
|
+ I --> J[同一说话人<br/>跨会话恢复]
|
|
|
+ J --> F
|
|
|
```
|
|
|
|
|
|
## 会话管理
|
|
|
@@ -138,18 +157,28 @@ flowchart LR
|
|
|
sequenceDiagram
|
|
|
participant A as App
|
|
|
participant D as Dispatcher
|
|
|
+ participant LK as LiveKit
|
|
|
participant W as Worker
|
|
|
-
|
|
|
+
|
|
|
Note over A,W: 首次连接
|
|
|
A->>D: POST /connect {identity}
|
|
|
- D-->>A: {room: "room-xxx", token}
|
|
|
- Note over A: 保存 room
|
|
|
-
|
|
|
+ D->>D: 创建 room-xxx
|
|
|
+ D->>W: spawn_worker(room-xxx)
|
|
|
+ D-->>A: {room: "room-xxx", url, token}
|
|
|
+ W->>LK: connect(room-xxx)
|
|
|
+ A->>LK: WebSocket connect(room-xxx)
|
|
|
+
|
|
|
Note over A,W: 暂停 → 继续
|
|
|
A->>D: POST /connect {identity, room: "room-xxx"}
|
|
|
D->>D: 检测 room-xxx 存活
|
|
|
- D-->>A: {room: "room-xxx", token}
|
|
|
+ D-->>A: {room: "room-xxx", url, token}
|
|
|
Note over A,W: 同房间 → Worker 保持 → 上下文不丢
|
|
|
+
|
|
|
+ Note over A,W: 全部离开
|
|
|
+ A->>LK: disconnect
|
|
|
+ LK->>W: participant_disconnected
|
|
|
+ W->>W: 房间空 → 退出
|
|
|
+ W->>LK: disconnect
|
|
|
```
|
|
|
|
|
|
## 三层记忆系统
|
|
|
@@ -177,9 +206,11 @@ python3 -m py_compile asr_agent/worker.py
|
|
|
python3 -m py_compile asr_agent/dispatcher.py
|
|
|
|
|
|
# 2. 上传文件到服务器
|
|
|
+sshpass -p '123456' scp asr_agent/Dockerfile ubuntu@200.200.18.11:/tmp/asr_build/Dockerfile
|
|
|
sshpass -p '123456' scp asr_agent/worker.py ubuntu@200.200.18.11:/tmp/asr_build/worker.py
|
|
|
sshpass -p '123456' scp asr_agent/dispatcher.py ubuntu@200.200.18.11:/tmp/asr_build/dispatcher.py
|
|
|
sshpass -p '123456' scp asr_agent/voiceprint.py ubuntu@200.200.18.11:/tmp/asr_build/voiceprint.py
|
|
|
+sshpass -p '123456' scp asr_agent/requirements.txt ubuntu@200.200.18.11:/tmp/asr_build/requirements.txt
|
|
|
sshpass -p '123456' scp -r asr_agent/whisper_asr ubuntu@200.200.18.11:/tmp/asr_build/
|
|
|
|
|
|
# 3. 服务器构建镜像
|
|
|
@@ -222,10 +253,18 @@ curl -s -X POST http://localhost:10005/connect -H "Content-Type: application/jso
|
|
|
| `PUBLIC_LIVEKIT_URL` | 同 LIVEKIT_URL | 返回给 App 的外网地址 |
|
|
|
| `LLM_PROVIDER` | `vllm` | `vllm` / `mimo` |
|
|
|
| `ASR_PROVIDER` | `qwen` | `qwen` / `mimo` |
|
|
|
+| `ASR_MODEL_PATH` | `Qwen/Qwen3-ASR-0.6B` | 本地 Qwen3-ASR 模型路径 |
|
|
|
| `LLM_MODEL` | `qwen3.6-35b-awq` | vLLM 模式下的模型名 |
|
|
|
| `MIMO_KEY` | — | Mimo API Key |
|
|
|
| `MIMO_API_BASE` | `https://token-plan-cn.xiaomimimo.com/v1` | Mimo API 地址 |
|
|
|
| `VAD_MODEL_PATH` | `whisper_asr/silero_vad.onnx` | Silero VAD 模型路径 |
|
|
|
+| `VAD_THRESHOLD_HIGH` | `0.5` | VAD 触发阈值 |
|
|
|
+| `VAD_THRESHOLD_LOW` | `0.2` | VAD 保持阈值 |
|
|
|
+| `VAD_WINDOW_SIZE` | `5` | VAD 滑动窗口帧数 |
|
|
|
+| `VAD_VOICE_RATIO` | `0.5` | 窗口内判定为语音的比例 |
|
|
|
+| `MIN_SPEECH_S` | `0.3` | 最短有效语音时长 |
|
|
|
+| `MIN_SILENCE_S` | `0.4` | 结束静音时长 |
|
|
|
+| `MAX_SPEECH_S` | `8.0` | 最大语音切片时长 |
|
|
|
| `VP_DB_PATH` | `/data/voiceprints` | 声纹数据库目录 |
|
|
|
| `VP_SIMILARITY_THRESHOLD` | `0.65` | 声纹匹配余弦相似度阈值 |
|
|
|
|