Files
ai-companion/部署说明.md
T
ylhan 35e107c290 助手本体:会话引擎、LLM 路由、记忆、身份、语音适配
从 Obsidian 笔记库剥离为独立仓库。规划文档与 P0 报告留在笔记库,
代码(助手本体、TTS 服务、P0 基准脚本、ESP32 固件)在此。

架构:服务器是助手,本地是客户端
- 助手本体跑在 cadadmin-server,N5095 只做 USB 外设桥与网络桥接
- 起因:实测 N5095 无 AVX2,跑不动可用的语音模型

当前技术栈
- LLM:DeepSeek deepseek-flash(云端,必须关思考)
- TTS:Qwen3-TTS 1.7B(服务器 3090,独立 HTTP 服务)
- STT:Zipformer 中英双语 int8(CPU,中文识别率未测)

实测:端到端 16/16 通过,首音延迟 1545ms
(对比:本地 LLM + vits-zh-ll 是 179ms,但音色不满意——音质与延迟
在此硬件上此消彼长)

设计要点
- MemoryStore 接口按"可能远程"设计(全 async、可超时、可失败)
- user_id / session_id / track_id 三者分开,人脸只作候选不自动切身份
- 记忆查询强制带 owner_user_id,无按 id 单独取的口子
- 自动抽取的记忆是"候选",默认不参与检索
- 异步写记忆绑定创建时的身份,不读"当前用户"
- 引擎与传输解耦,只用文件音频即可端到端测试

模型与音频产物不进版本管理(1.1GB),由 download_models.sh 恢复
2026-09-15 11:05:09 +08:00

174 lines
7.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 部署说明 · 服务器侧助手
更新:2026-09-15。状态:**已部署并跑通**,端到端测试 16/16 通过。
## 架构定位
```
cadadmin-server(192.168.0.20) N5095 ESP32-S3
助手本体 外设桥 表情/动作
├─ 会话引擎 ├─ USB 麦克风阵列 ├─ 屏幕
├─ LLM:DeepSeek 云 API ├─ USB 摄像头 ├─ 电机
├─ 记忆 SQLite ├─ DoA 读取(不过网) └─ 按键
├─ 身份裁决 ├─ 人脸检测
├─ STT:Zipformer(CPU) ├─ WebRTC 客户端
└─ TTS:Qwen3-TTS(3090) └─ 串口 → ESP32
手机 / 浏览器:额外客户端
```
**"服务器是助手,本地是客户端"**(2026-09-14 确定)。
助手本体、记忆库、语音模型全部在服务器;N5095 只做 USB 外设接入、
DoA 读取和网络桥接,不跑任何推理。
## 当前技术栈
| 环节 | 方案 | 位置 | 实测 |
|---|---|---|---|
| LLM | DeepSeek `deepseek-flash`(关思考) | 云 API | 首 token 0.8–1.2s |
| TTS | Qwen3-TTS 1.7B-CustomVoice | 3090 | RTF 0.55–0.94 |
| STT | Zipformer 中英双语 int8 | CPU 4 线程 | RTF 0.03 |
| 记忆 | SQLite | 服务器本地 | — |
| 身份 | 手动切换 + 访客兜底 | 服务器 | — |
**首音延迟约 1.5 秒**(文本输入)。方案 P1 目标是 P50≤2 秒,
加上 VAD 判定后处在 2.0–2.2 秒,**刚好卡线,没有余量**。
### 为什么 LLM 走云端而 TTS 在本地
两者都需要算力,但 3090 只有 24GB:
| 方案 | 显存 | 首音延迟 |
|---|---|---|
| 本地 Qwen3.8-27B + 本地 vits-zh-ll | 27B 占 21.5GB | **179ms**(音色不满意) |
| **DeepSeek + 本地 Qwen3-TTS** | TTS 占 8.9GB | **1545ms** |
2026-09-15 选了后者:**用约 600ms 的网络往返 + 约 900ms 的 TTS 固定开销,
换音质从"用户不满意"到"用户试听选定"**。27B 的 vLLM 已停。
这是可逆的(改 `AI_LLM_PRIMARY` 即可切回),但切回去 GPU 就不够跑 Qwen3-TTS。
## 服务器上的布局
```
~/ai-companion/ 助手主体
├── app/ 24 个 py 文件
│ ├── config.py 配置,支持环境变量覆盖
│ ├── prompts.py 单条 system 每轮重建
│ ├── engine.py 会话引擎(身份→记忆→LLM→TTS)
│ ├── llm/ 后端抽象 + vLLM + DeepSeek + 降级路由
│ ├── memory/ 接口 + SQLite 实现
│ ├── identity/ 接口 + 手动切换实现
│ ├── voice/ STT / TTS(本地 + 远端两种后端)
│ └── tests/ 4 个测试,共 88 项断言
├── models -> ~/ai_companion_bench/models 软链,未重复占用
└── .venv/ 608MB(Python 3.12)
~/tts-service/ Qwen3-TTS 模型(4.3GB)
~/tts-qwen/ Qwen3-TTS 服务
├── tts_server.py FastAPI 服务,OpenAI 兼容 /v1/audio/speech
├── samples/ 音色试听样本
└── .venv/ 独立 venv(与 vllm-omni 依赖互斥,必须分开)
~/ai_companion_bench/ P0 基准脚本与模型(STT/TTS/vits)
~/ai/qwen-serving/ 原来的 27B vLLM(已停,保留可随时启回)
```
## 配置
`~/.config/ai_assistant/env`(权限 600,含密钥)
| 变量 | 服务器值 | 说明 |
|---|---|---|
| `AI_LLM_PRIMARY` | `deepseek` | 主 LLM;改 `local` 可切回本机 vLLM |
| `AI_TTS_BACKEND` | `remote` | `remote`=Qwen3-TTS 服务;`local`=vits-zh-ll |
| `TTS_SERVICE_URL` | `http://127.0.0.1:18040` | TTS 服务地址 |
| `AI_TTS_SPEAKER` | `Serena` | 用户试听选定 |
| `AI_TTS_INSTRUCTIONS` | `用轻快活泼的语气说` | 风格指令 |
| `AI_STT_THREADS` | `4` | **N5095 上必须改成 2**(见下) |
| `AI_MODEL_DIR` | `/home/yonglin/ai-companion/models` | 两边模型位置不同 |
**两台机器的线程配置不能照抄**:N5095(4核无AVX2)STT 超过 2 线程急剧退化
(4 线程慢 3.9 倍);服务器(i9-14900K 有AVX2)给 4 线程没问题。
TTS 的线程扩展方向又和 STT 相反(越多越快)。
## 启动
```bash
# 1. TTS 服务(模型常驻显存,约 4 秒加载完)
cd ~/tts-qwen && setsid nohup .venv/bin/python tts_server.py --port 18040 \
> logs/tts_service.log 2>&1 < /dev/null &
# 2. 确认就绪
curl -s http://127.0.0.1:18040/health
# 3. 跑测试
cd ~/ai-companion && .venv/bin/python app/tests/test_engine_e2e.py
```
助手本体还没有常驻服务(没有 systemd),目前靠直接跑脚本。
## 部署代码
```bash
cd ~/workspace/ai-companion
rsync -az --delete --exclude '__pycache__' --exclude '*.pyc' \
app/ yonglin@cadadmin-server:~/ai-companion/app/
```
## 已知的坑
**都已写进代码注释,这里只列索引。**
### 接口层
1. **本机 vLLM 只接受一条 system 消息**,多条 400。记忆不能作为第二条
system 注入,必须并进同一条每轮重建。
2. **思考字段名各实现不同**:vLLM 用 `reasoning`,DeepSeek 用 `reasoning_content`。
适配层两个都认,少认一个会静默丢弃思考内容。
3. **DeepSeek 默认开思考**,此时要求把上一轮的 `reasoning_content` 原样回传,
否则多轮/工具回灌一律 400。主路径必须显式关闭。
4. **开思考会吃掉 `max_tokens` 预算**,实测 200 预算下回答只吐出两个字。
### 语音层
5. **`max_num_sentences=1` 会静默截断多句输入**(sherpa-onnx VITS),
必须自己先分句。
6. **VITS 是非自回归的**,没有"边合成边播",首音 = 整句合成耗时。
提前出声的唯一手段是按语义短句拆分。
7. **Qwen3-TTS 短句 RTF 反而最差**(0.94 vs 长句 0.55):每次调用有约 1 秒
固定开销,与文本长度无关。所以"让模型先说个短句"并不划算。
### 记忆层
8. **检索兜底要限量**:关键词全不命中时退回最近记忆,但只在记忆总数 ≤10 时
生效。不加限制就变成"每轮全量注入",不再是按需检索。
9. **"被引用的 user 必须存在"由存储层自己保证**(`_ensure_user_row`),
不能让每个调用点各自记得——漏一处就报 `FOREIGN KEY constraint failed`。
### 依赖层
10. **qwen-tts 与 vllm-omni 依赖互斥**(transformers 4.57 vs 5.14、
hf_hub 0.36 vs 1.31)。装同一个 venv 会互相破坏,实测把 vllm-omni 搞崩过。
所以 TTS 跑独立进程 + HTTP。
11. **flash-attn 装不上**:预编译 wheel 最高只支持 torch 2.9,本机是 2.14
(vLLM 0.28 拉上来的);源码编译要从 GitHub 拉依赖,而这台机器下
GitHub release 会断连。
## 未做的事
- **没有常驻服务**:TTS 服务和助手都没有 systemd 开机自启(方案 §10 要求 P2 后做)
- **没接 Pipecat 管线**:引擎与传输解耦,接 WebRTC 时写薄适配层即可
- **设备侧代码不存在**:N5095 的外设桥(USB、WebRTC 客户端、串口)尚未开始
- **STT 没换**:仍是 2023 年的 Zipformer int8,中文识别率从未测过
- **记忆库在服务器上**,而人脸特征按方案必须留在设备端——将来做
"删除我的数据"要跨两台机器协调,这个数据边界问题需要单独想
- **代码仍在 note_sync 笔记仓库里**,未拆成独立仓库
## 相关文档
- `AI桌面陪伴助手方案.md` — 总体方案
- `P0-语音基准报告.md` — STT/TTS 基准(vits 时代,含线程数结论)
- `P0-DeepSeek验证报告.md` — DeepSeek API 四条路径
- `P0-本地LLM验证报告.md` — 本机 Qwen3.8-27B(**已停用**)
- `P0-Qwen3TTS报告.md` — Qwen3-TTS 选型与部署