# AI 桌面陪伴助手 · 代码说明 **架构一句话**:服务器是助手,本地是客户端。 助手本体(会话引擎、记忆、身份、STT/TTS 调度)跑在 cadadmin-server; N5095 只做 USB 外设接入和网络桥接。理由与实测见 `P0-语音基准报告.md`。 运行方式与配置见 `部署说明.md`;总体方案见 `AI桌面陪伴助手方案.md`。 ## 目录结构 ``` app/ 助手本体(部署到服务器) ├── config.py 配置。全部可用环境变量覆盖——部署时不改代码 ├── prompts.py 提示词构造。单条 system 每轮重建 ├── engine.py 会话引擎:身份 → 记忆 → LLM → TTS 的核心回路 ├── llm/ LLM 抽象与后端 │ ├── base.py 接口 + SSE 解析 + 消息归一化 │ ├── local_vllm.py 本机 vLLM(Qwen3.8-27B,已停用) │ ├── deepseek.py DeepSeek 云 API(当前主用) │ └── __init__.py 工厂 + 降级路由 ├── memory/ 记忆 │ ├── base.py 接口。**按"可能远程"设计**——全 async、可超时、可失败 │ └── sqlite_store.py SQLite 实现 ├── identity/ 身份 │ ├── base.py 接口。人脸只提供候选,**不自动切身份** │ └── manual.py 首版实现:显式切换 + 访客兜底 ├── voice/ 语音 │ ├── stt.py sherpa-onnx 流式 Zipformer(CPU) │ ├── tts.py 本地 vits-zh-ll(sherpa-onnx) │ └── remote_tts.py Qwen3-TTS 远端服务适配(当前主用) └── tests/ 4 个测试,共 88 项断言,全部无需硬件 services/ └── tts_server.py Qwen3-TTS 服务(跑在 ~/tts-qwen,独立 venv) p0/ P0 阶段的验证与基准脚本(保留用于复现) ├── verify_deepseek.py DeepSeek 四条路径 ├── verify_local_llm.py 本机 Qwen3.8-27B └── stt_tts_bench/ STT/TTS 基准、模型下载脚本、线程扫描 ``` ## 测试 ```bash # 服务器上(完整) cd ~/ai-companion .venv/bin/python app/tests/test_llm.py # 27 项 .venv/bin/python app/tests/test_memory.py # 28 项 .venv/bin/python app/tests/test_identity.py # 17 项 .venv/bin/python app/tests/test_engine_e2e.py # 16 项,端到端 ``` 端到端测试**不需要麦克风、音箱、摄像头、ESP32**—— 这是"服务器即助手"架构的直接好处:链路可以在任何有 CPU 的地方验证, 硬件到货后再接最后一段。跑完会把合成音频写到 `out/`,可以人工试听。 ## 设计上几个刻意的选择 **接口按最坏情况设计。** `MemoryStore` 全是 async、可超时、可失败, 虽然现在背后是本地 SQLite(微秒级)。因为将来多设备共享记忆时要搬到服务器, 如果现在写成同步直连本地文件,那时要重写所有调用方。 **身份、会话、视觉追踪三者分开。** `user_id`(永久身份)、`session_id`(一次会话)、 `track_id`(画面里的一个目标)互不冒充。人脸认出来**不等于**确认对方在说话—— 所以人脸只产生"建议",不自动切换身份。做错的后果是路人经过就把你的私人记忆念出来。 **记忆查询强制带 `owner_user_id`。** 没有"按 memory_id 单独取"的口子: 猜到 id 就能读别人的记忆,是这类系统最容易犯的错。删除同理,必须同时匹配 owner。 **自动抽取的记忆是"候选",默认不参与检索。** 只有用户显式"记住"或工具调用 写入的才是"已确认"。对应方案 §8 的"模型推测不当作确认事实"。 **异步任务绑定创建时的身份。** 回复后写记忆的任务在创建时捕获 `user_id`, 不读"当前用户"——否则用户在这几百毫秒内切了身份,这条记忆会写到别人名下。 **引擎与传输解耦。** 不依赖 Pipecat、不依赖任何传输方式,只依赖四个接口。 好处:可以用文件音频端到端测试;接 WebRTC 时只需写薄适配层。 ## 踩过的坑 全部写在对应文件的注释里,这里只列索引,详见 `部署说明.md` §已知的坑: - 本机 vLLM 只接受一条 system 消息(多条 400) - 思考字段名 vLLM 用 `reasoning`、DeepSeek 用 `reasoning_content` - DeepSeek 默认开思考,多轮/工具回灌会 400;且思考会吃掉 `max_tokens` - `max_num_sentences=1` 静默截断多句输入 - VITS 非自回归,首音 = 整句合成耗时 - Qwen3-TTS 短句 RTF 反而最差(固定开销约 1 秒) - qwen-tts 与 vllm-omni 依赖互斥,必须分 venv - STT 与 TTS 的线程扩展方向相反