AI桌面陪伴助手:方案更新至 v2.4,代码剥离到独立仓库

代码剥离
- 助手本体、TTS 服务、P0 基准脚本、ESP32 固件移到 ~/workspace/ai-companion
  (独立 git 仓库),本目录只留方案、P0 报告与硬件记录
- 因此删除 esp32_smile_test.ino / esp32_touch_eyes.ino
- 同时移除已失效的 .gitignore(原本只为挡住模型体积,代码已搬走)

方案 v2.4 重大变更:架构从"单机"改为"服务器即助手、本地为客户端"
- 起因:实测 N5095 无 AVX2,跑不动可用的语音模型
- N5095 降为客户端/外设桥,只做 USB 接入、DoA、WebRTC 客户端
- 实施阶段表加进度列(P0/P1/P3/P4a 部分完成),新增三条验收要求
- 采购清单按实际到货更新,标出屏幕形态待确认(2.8寸 vs 原定1.28寸圆屏)

技术栈(实测更新)
- LLM:DeepSeek deepseek-flash(云端,必须关思考);本机 27B 已停用
- TTS:Qwen3-TTS 1.7B(服务器 3090);vits-zh-ll 因音色不满意被替换
- STT:Zipformer int8 未变,中文识别率仍是未闭合项

新增两份 P0 报告
- P0-Qwen3TTS报告.md:TTS 换型过程与实测
- P0-DeepSeek验证报告.md:四条路径验证

两份旧报告加了状态说明(结论仍有效但选型已变),
并修正了随代码移走而失效的引用与复现命令。
This commit is contained in:
ylhan
2026-09-15 11:08:56 +08:00
parent 152b919b25
commit bdd6bfb63d
8 changed files with 762 additions and 289 deletions
@@ -0,0 +1,113 @@
# P0 验证报告 · DeepSeek API
日期:2026-09-14。机器:N5095 / Ubuntu 24.04.3 / Python 3.13.5。
脚本:`~/workspace/ai-companion/p0/verify_deepseek.py`。结果:**11/11 通过**。
复现:
```bash
set -a; . ~/.config/ai_assistant/env; set +a
cd ~/workspace/ai-companion && python3 p0/verify_deepseek.py
```
密钥存放:`~/.config/ai_assistant/env`(权限 600,不在项目目录内,不进版本管理)。
## 结论摘要
| # | 项目 | 结果 |
|---|---|---|
| 0 | 模型清单 | 仅 `deepseek-flash`、`deepseek-v4-pro` |
| 1a | 纯文本流式 | 可用 |
| 1b | 思考内容分离 | on 模式有 `reasoning_content`;off 模式无 |
| 1c | 首 token 延迟 | **P50 = 786ms,P95 = 1100ms**(N=8) |
| 2a | 流式工具调用 | 可用,参数 JSON 正确 |
| 2b | 工具结果回灌 | 可用 |
| 2c | 思考模式约束 | 开启时**必须回传 `reasoning_content`** |
| 3a | **图片输入** | **可用**,正确读出图中数字 |
| 4a | 中途断开(打断) | 无异常 |
| 4b | 超时 | 正确抛出 `TimeoutError` |
| 4c | 错误模型名 | HTTP 400,可作启动自检 |
## 逐条说明
### 1. 模型名:方案文档正确,Pipecat 默认值错误
`GET /v1/models` 实际返回:
```json
[{"id": "deepseek-flash"}, {"id": "deepseek-v4-pro"}]
```
- 方案文档 §2 记的 `deepseek-flash` **正确**。
- **Pipecat 的 `DeepSeekLLMService` 默认模型是 `deepseek-v4-flash`,该模型不存在**,直接用会 400。
实例化时必须显式覆盖为 `deepseek-flash`。
### 2. 思考模式必须显式关闭(重要)
`deepseek-flash` 默认开启思考模式。此时 API 要求**把上一轮 assistant 消息里的
`reasoning_content` 原样回传**,否则任何多轮对话与工具回灌都返回:
```
400 invalid_request_error: The `reasoning_content` in the thinking mode must be passed back to the API.
```
实测三种手工构造 assistant 消息的写法(`content=None` / `content=""` / 不带 tools)**全部 400**;
只有回传服务端返回的原始 assistant 消息才通过。
**影响**:多数框架的上下文层按 OpenAI 格式存储消息,不保留 `reasoning_content`——
一旦开启思考模式,Pipecat 的上下文管理与工具循环会持续踩这个坑。
**决定**:会话主路径传 `"thinking": {"type": "disabled"}`。两条理由:
1. 彻底消除 `reasoning_content` 回传要求,上下文层可安全裁剪消息;
2. 开启时**第一个流式 chunk 是 `reasoning_content` 而非 `content`**,若直接送 TTS 会念出思考过程。
关闭后两者合一,无需分流。
Pipecat 该服务的默认值恰好是 `DeepSeekThinkingConfig(type="disabled")`,与本结论一致。
若将来要开思考以提升回答质量,必须同时改造上下文层保证 `reasoning_content` 不丢。
### 3. 首 token 延迟
N=8,短问答,`thinking=disabled`:**P50 = 786ms,P95 = 1100ms**(min 515 / max 1100)。
对照方案 P1 验收目标"短问答结束到首段有声回复 P50≤2 秒、P95≤4 秒",
LLM 占约 0.8 秒,**余下约 1.2 秒预算**留给 STT 收尾判定 + TTS 首段合成。这个余量是否够,
取决于本地 TTS 的 RTF(待测,见下)。
注:thinking 开关对首包时间影响不大(约 700ms vs 718ms),但关闭后总耗时更短,因为不再生成思考 token。
### 4. 图片输入可用(P5 的意外好消息)
`deepseek-flash` 接受 `image_url` 的 base64 data URI,正确读出图中文字("7391")。
方案 §P5 的视觉问答不需要另找模型,按需抓拍 → 缩放 → base64 上传即可。
**未测**:视觉请求的 TTFT(预估显著长于纯文本)、大图 token 消耗、并发下的表现。
按需视觉是按次计费项,接 P5 时需单独测成本。
### 5. 工具调用链路完整
流式工具调用可用,`finish_reason="tool_calls"`,参数 JSON 正确。
回灌工具结果后模型能生成自然语言确认("客厅的灯已经打开了")。
这是 P5 米家控制的基础链路,已验证通畅。
**注意**:回灌时 `tool_call_id` 必须用服务端返回的真实值,assistant 消息必须用服务端返回的原始对象,不能手工构造。
### 6. 打断与超时
- **打断**(收到若干 chunk 后主动 close):无异常抛出,连接正常释放。对应方案"打断时取消旧回复生成"的客户端能力成立。
- **超时**:非流式长生成 + 2 秒预算,正确抛出 `TimeoutError`。
- 流式下 socket 超时不会因首包快而触发——**"打断"和"超时"必须分开实现检测**,不能只做一种。
### 7. 计费与缓存
`usage` 返回 `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens`,实测命中 256/398。
系统提示词固定可吃到缓存,对月费有利。方案 §8 要求的费用统计应记录这两个字段。
## 遗留与未验证
- 视觉请求的延迟与 token 成本未测。
- 长上下文(多轮 + 记忆注入)下的延迟退化未测。
- 并发请求未测(多用户场景下的表现)。
- 本次仅验证 API 链路,**未接入 Pipecat、未写任何管线代码**。
- 本机 `github.com` / `huggingface.co` 直连不通,需走本地 mihomo 代理 `127.0.0.1:7890`
或国内镜像。DeepSeek API 直连正常,不需要代理。