[全双工Agent架构]:完成完整语音助手OpenSpec计划,包含WebRTC音频底座、长期记忆和Tool Router设计

This commit is contained in:
mkbk
2026-06-18 21:30:25 +08:00
parent 6153cd2826
commit 696ed2c30e
5 changed files with 1311 additions and 0 deletions
@@ -0,0 +1,516 @@
# OpenSpec:完整全双工 Agent 语音助手架构
## 功能目标
### 完整业务价值
当前 Owner 语音桌宠已经从最初的“唤醒词 -> VAD -> STT -> LLM -> TTS -> 播放”演进到可真实运行的 `run-live`,并具备本地唤醒、实时字幕、噪音过滤、连续追问、结束提示音和初步播报打断规划。但现有核心仍然是 turn-based 语音桌宠:用户先唤醒,系统听完一段话,识别完再思考,回复播放完再决定是否继续。这个模型能完成普通问答,但距离“小爱同学式持续对话”和完整 Agent 仍有明显差距。
本变更的业务目标是把现有语音桌宠规划升级为完整全双工 Agent 语音助手:麦克风持续输入、播放端持续输出、WebRTC APM 提供 AEC/NS/AGC 音频底座、Streaming STT 实时生成用户话语、LLM 流式回复按句进入 Streaming TTS、用户在 AI 讲话时可以自然打断,长期记忆和 Tool Router 让助手不只聊天,还能在安全边界内检索记忆、执行只读系统任务、调用 Open Interpreter 外部后端和 Playwright 浏览器自动化。
本阶段只产出 OpenSpec 审核材料,不改运行代码、不安装依赖、不下载模型、不接入工具执行、不把本地未跟踪的 `openinterpreter/` 复制进 Owner 代码,也不提交该目录。`openinterpreter/` 在本变更中只被定义为外部 CLI/子进程工具后端候选。
### 目标用户场景
1. 用户在 macOS 上启动语音助手后,不需要按住说话;麦克风常驻监听,系统通过本地唤醒或持续 VAD/STT 管理输入。
2. 用户听 AI 回复时可以直接插话;系统在 200 ms 目标延迟内停止 TTS 播放、取消当前 LLM/TTS 任务,并把用户新话语作为下一轮输入。
3. AI 自己的扬声器声音不会被误当成用户输入;WebRTC APM 的 AEC 使用播放 reference 音频消除回声,NS 降低环境噪声,AGC 统一麦克风音量。
4. 用户可以连续追问,不必每轮都说“小杰小杰”;Conversation Manager 根据状态机和上下文判断当前是继续听、执行工具、播报结果还是恢复待机。
5. 助手可长期记住非敏感偏好、事实、项目摘要和任务摘要;重启后仍可通过 FAISS+SQLite 召回相关记忆,但敏感信息默认不自动保存。
6. 用户要求“整理下载目录”“查一下项目文件”“打开网页抓取内容”时,Tool Router 先做安全分类,低风险只读工具可执行,高风险写操作、删除、上传、交易、权限变更必须要求确认,第一版不自动做 GUI 点击/键盘控制。
7. 后续桌宠 GUI 可订阅同一事件流展示听、想、说、工具执行、被打断、等待确认等状态,而不是重新实现 pipeline 逻辑。
### 量化成功指标 KPI
1. 打断延迟:在可控音频测试环境中,`speaking` 状态下检测到有效用户语音后,进入 `interrupted` 并停止播放的 P95 延迟目标小于 200 ms。
2. 回声抑制:播放 reference 音频注入 AEC 后,纯助手回放不触发用户 VAD/STT,不产生有效 `barge_in_detected`
3. 实时识别:用户开始说话后,Streaming STT 首个稳定 partial transcript 的 P95 目标小于 800 ms。
4. 端到端首音:LLM 首个可播报句子产生后,Streaming TTS 首个可播放 PCM chunk 的 P95 目标小于 1000 ms。
5. 流式播报:LLM 不等待完整回复;中文回复按句切分进入 TTS,第一句可播放后立即播放。
6. 取消可靠性:用户打断时,当前 LLM stream、TTS synthesis、playback、工具执行候选任务均收到 cancellation token;被取消内容不得继续写入上下文。
7. 记忆召回准确性:保存的偏好/事实/项目摘要在相关查询中可被 Top-K 检索召回;关闭记忆时不得读写 SQLite/FAISS。
8. 工具安全:未授权工具、越权路径、写操作、删除操作、账号/上传/交易类任务必须被拒绝或进入确认流程;自动化测试覆盖非法工具拒绝、防循环、超时、输出截断。
9. 兼容性:现有 turn-based `run-live` 语义在迁移期不得被破坏;新全双工入口可分阶段实现,允许先以 feature flag 启用。
10. OpenSpec 完整性:`openspec validate add-full-duplex-agent-voice-assistant --strict``openspec validate --all --strict` 必须通过。
### 预期影响
1. 主规范 `voice-pet-pipeline` 将从“Python 桌宠语音 pipeline”升级为“全双工 Agent 语音助手 pipeline”,覆盖音频底座、并发模型、记忆、工具和安全策略。
2. 后续实现将新增 WebRTC APM provider、全双工音频环形缓冲、Streaming STT/TTS provider、Conversation Manager、MemoryManager、ToolRouter、Open Interpreter adapter、Playwright browser adapter。
3. 现有 GTCRN 降噪、主说话人音色门控和本地 KWS 保留为 fallback 或局部能力,不再作为完整全双工回声消除的主方案。
4. 当前进程内临时上下文保留,但长期记忆成为独立层;临时会话历史和长期记忆必须有清楚边界。
5. 工具执行引入新的安全面,必须规划确认策略、权限边界、路径限制、超时、输出截断、审计日志和敏感信息脱敏。
6. 第一版电脑控制不做 GUI 点击/键盘/屏幕控制;只预留 `ComputerControlProvider`。Codex Computer Use 只参考安全确认策略,不复制私有或捆绑实现。
### 对现有问题的系统性总结
1. 性能问题:现有 turn-based pipeline 在录音结束后才进入 final STT 和 LLM,用户感知延迟集中爆发;全双工架构要求持续识别和流式回复。
2. 打断问题:当前打断规划仍围绕播放 chunk 检查或后台监听补丁,缺少全局 cancellation graph;用户插话无法统一取消 LLM、TTS、播放和工具任务。
3. 回声问题:现有音色门控只能降低误触发,无法从音频底座消除扬声器回放;没有 AEC reference,后续 VAD/STT 容易被 AI 自己声音污染。
4. 噪音问题:GTCRN 只覆盖正式问题采集阶段;全双工持续监听需要系统级 NS/AGC,避免背景噪声导致持续 STT 和打断误判。
5. 架构问题:现有 pipeline 仍以“轮次”为主,状态机缺少 `tool_running``interrupted``recovering` 等 Agent 必需状态。
6. 记忆问题:当前上下文只存在于本次进程内;无法记住长期偏好、项目背景和任务摘要,也没有隐私分类和删除策略。
7. 工具问题:当前 LLM 只能生成自然语言;没有结构化工具协议、工具路由、安全策略、执行预算、防循环机制和工具结果回注。
8. Open Interpreter 边界问题:本地 `openinterpreter/` 是未跟踪外部仓库,不能复制进 Owner;需要把它规划为可选外部 CLI 后端并限制风险。
9. UI/UX 问题:后续桌宠如果直接绑定 runtime 方法,会继续重复逻辑;需要统一事件总线承载状态、字幕、音频、打断、工具和确认请求。
10. 安全问题:长期记忆和工具执行都会扩大数据面与操作面;必须默认最小权限、敏感内容不自动保存、高风险操作确认、日志脱敏。
## 详细需求
### 功能需求
1. 系统 SHALL 新增完整全双工 Agent 语音助手架构规划,保留现有 turn-based 能力作为迁移期兼容路径。
2. 系统 SHALL 规划 `ContinuousAudioRuntime`,负责麦克风持续输入、扬声器播放 reference、音频环形缓冲、状态机事件和任务取消。
3. 系统 SHALL 规划 `WebRtcAudioProcessingStage`,默认 `OWNER_AUDIO_APM_PROVIDER=webrtc`,开启 AEC、NS、AGC。
4. 播放链路 SHALL 把 TTS PCM/render audio 提供给 AEC reference,麦克风 capture 音频经过 APM 后再进入 VAD、STT 和打断检测。
5. 系统 SHALL 规划全双工状态机:`idle``listening``thinking``speaking``interrupted``tool_running``recovering`
6. 任意状态下检测到有效用户说话 SHALL 能进入 `interrupted`,但 `tool_running` 的中断语义必须区分可取消工具和不可安全取消工具。
7. 系统 SHALL 规划 Silero VAD 或等价本地 VAD 作为打断触发的主要人声检测层;打断检测先判断“有人开始说话”,不依赖已识别出完整文本。
8. 打断目标 SHALL 是在 `OWNER_BARGE_IN_TARGET_LATENCY_MS=200` 内停止播放和取消当前回复。
9. 系统 SHALL 规划 Streaming STT provider;开发默认候选为 `faster-whisper`,产品候选为 `SenseVoice`,保留现有 `sherpa-onnx` 兼容 adapter。
10. Streaming STT SHALL 输出 partial transcript、stable partial transcript 和 final transcript;只有 final transcript 或明确提交的 stable transcript 可进入 LLM。
11. 系统 SHALL 规划 Streaming TTS provider;目标候选为 `CosyVoice`,支持句子级和流式 PCM 输出。
12. LLM SHALL 流式输出 tokenSentence Segmenter SHALL 在检测到完整中文/英文句子或安全停顿时,把文本片段送入 TTS,不等待整段回复完成。
13. TTS 播放 SHALL 支持中途停止;停止后未完整播出的 assistant 文本不得写入短期上下文或长期记忆。
14. 系统 SHALL 规划 `ConversationManager`,负责短期会话历史、长期记忆召回、工具调用闭环和状态推进。
15. 系统 SHALL 规划 `MemoryManager`,默认 `OWNER_MEMORY_PROVIDER=faiss_sqlite`SQLite 存文本和元数据,FAISS 存向量索引。
16. 记忆类型 SHALL 至少包含 `preference``fact``project``task_summary`
17. 每轮用户输入进入 LLM 前 SHALL 根据当前用户文本、会话摘要和任务上下文检索 Top-K 长期记忆,并以明确的 memory context 注入 LLM。
18. 敏感内容 SHALL 默认不自动保存;记忆写入必须经过分类器、安全策略或用户明确指令。
19. 系统 SHALL 规划 `ToolRouter` 和结构化工具调用协议,工具请求包含 name、arguments、risk_level、requires_confirmation、timeout_ms、budget、cancellation_policy。
20. 第一版工具 SHALL 以安全工具为主:`memory.search``memory.save``shell.readonly``openinterpreter.run``browser.playwright`
21. `shell.readonly` SHALL 限制为只读命令和允许目录,禁止删除、写文件、修改权限、网络上传、安装依赖等高风险行为。
22. `openinterpreter.run` SHALL 作为外部 CLI/子进程 adapter,默认只允许低风险、受限目录、超时和输出截断任务。
23. `browser.playwright` SHALL 作为浏览器自动化 adapter,默认只允许可审计、非支付、非账号敏感的浏览和提取流程。
24. 第一版 SHALL NOT 实现 GUI 点击/键盘/屏幕控制;只在规范中预留 `ComputerControlProvider`,后续可基于 macOS Accessibility、Playwright、trycua 等公共能力实现。
25. Codex Computer Use 能力 SHALL 仅作为安全确认策略参考,不复制私有实现、捆绑脚本或内部协议。
26. 系统 SHALL 规划工具结果回注:工具输出进入 Tool Result Message,经过脱敏和长度限制后返回 LLM;工具失败进入可恢复错误路径。
27. 系统 SHALL 规划防循环策略:单轮最大工具调用次数、最大总耗时、最大输出字节、重复工具调用检测。
28. 系统 SHALL 规划安全确认策略:写文件、删除、上传、交易、账号、权限、联网提交、安装依赖、执行任意代码等高风险操作必须确认,第一版默认拒绝自动执行。
29. 系统 SHALL 规划桌宠/终端共用事件总线,事件覆盖 audio、vad、stt、llm、tts、playback、memory、tool、confirmation、interruption、recovery。
30. 本阶段 SHALL 只创建 OpenSpec 文档;不得创建或修改 `src/``tests/``scripts/``pyproject.toml``.env`、模型文件、音频资产或运行时代码。
### 非功能需求
1. 性能优化:全双工音频处理必须以固定帧长和环形缓冲为基础,避免 Python 线程阻塞导致播放卡顿或输入积压。
2. 延迟目标:VAD frame interval 默认不超过 20 ms;播放停止 chunk 默认不超过 30 ms;整体打断目标小于 200 ms。
3. 稳定性:所有 provider 必须支持超时、取消、关闭和资源释放;异常必须进入 `recovering`,不得让后台线程泄漏。
4. 可扩展性:APM、VAD、STT、TTS、LLM、Memory、Tool adapter 都必须是可替换 provider,不能把具体模型硬编码进状态机。
5. UI/UX:终端和未来 GUI 不直接调用 provider,只消费事件;显示文案、字幕、工具确认和桌宠动画均由事件驱动。
6. 安全:API key、Authorization header、原始音频、声纹特征、工具敏感输出不得写入日志或长期记忆。
7. 隐私:默认不保存原始麦克风音频;长期记忆只保存文本摘要和必要元数据;用户必须能关闭记忆。
8. 兼容性:现有 `.env` 中 LLM 配置继续可用;新增配置必须有默认值和迁移说明。
9. 可测试性:每个并发 stage 必须可用 fake provider 和虚拟时钟测试;端到端模拟不依赖真实麦克风、扬声器或外部工具。
10. 可观测性:事件必须携带 turn/session id、stage、时间戳、latency、error code 和脱敏 payload,便于定位卡顿、误触发和工具风险。
### 边缘案例
1. APM 初始化失败:系统必须报告 `AUDIO_APM_UNAVAILABLE`,可按配置降级到现有 GTCRN/音色门控 fallback 或拒绝进入全双工模式。
2. AEC reference 丢失:播放中没有 reference 音频时,系统必须降低打断置信度或临时禁用高风险 barge-in,避免 AI 自己声音触发。
3. 麦克风权限缺失:启动失败并提示设备检查,不进入假监听状态。
4. 用户在 AI 说第一个字前打断:取消 LLM/TTS stream,未播报文本不写入上下文。
5. 用户在工具执行中打断:可取消工具立即取消;不可安全取消工具进入“正在收尾/等待结果”状态,并向用户播报或显示限制。
6. LLM 已发起工具调用但用户打断:未执行工具调用应取消;已执行且低风险的只读工具结果可丢弃或标记为 stale。
7. 记忆库损坏:FAISS 或 SQLite 不可用时,系统可禁用长期记忆并继续基本语音对话,但必须报告结构化错误。
8. 记忆召回命中敏感内容:默认不注入 LLM,除非用户明确要求并通过安全策略。
9. Open Interpreter 路径缺失:`openinterpreter.run` adapter 标记不可用,不影响其他工具。
10. 工具输出过长:按配置截断并附 `truncated=true` 元数据,不把完整大输出塞进 LLM。
11. Playwright 未安装或浏览器不可用:工具返回可恢复错误,不影响语音主循环。
12. TTS 输出卡顿:播放队列应能反压 TTS 合成;卡顿事件必须可观测,不能阻塞麦克风监听线程。
13. Streaming STT partial 抖动:只显示稳定 partialfinal transcript 才进入对话。
14. 网络 LLM 慢或断线:取消和超时必须生效;恢复后回到 listening 或 idle。
15. 多人同时说话:第一版不承诺身份鉴权,只要求 AEC 后的人声触发和用户体验合理;多人区分列为需人工澄清。
### 输入输出规格
新增或规划配置:
1. `OWNER_ASSISTANT_MODE=full_duplex_agent`
2. `OWNER_AUDIO_APM_PROVIDER=webrtc`
3. `OWNER_AUDIO_AEC_ENABLED=1`
4. `OWNER_AUDIO_NS_ENABLED=1`
5. `OWNER_AUDIO_AGC_ENABLED=1`
6. `OWNER_AUDIO_FRAME_MS=20`
7. `OWNER_AUDIO_RING_BUFFER_MS=3000`
8. `OWNER_VAD_PROVIDER=silero`
9. `OWNER_INTERRUPT_ENABLED=1`
10. `OWNER_INTERRUPT_TARGET_LATENCY_MS=200`
11. `OWNER_STREAMING_STT_PROVIDER=faster_whisper`
12. `OWNER_STREAMING_STT_PRODUCT_CANDIDATE=sensevoice`
13. `OWNER_STREAMING_TTS_PROVIDER=cosyvoice`
14. `OWNER_LLM_STREAMING_ENABLED=1`
15. `OWNER_MEMORY_ENABLED=1`
16. `OWNER_MEMORY_PROVIDER=faiss_sqlite`
17. `OWNER_MEMORY_TOP_K=5`
18. `OWNER_MEMORY_AUTO_SAVE_SENSITIVE=0`
19. `OWNER_TOOL_ROUTER_ENABLED=1`
20. `OWNER_TOOL_MAX_CALLS_PER_TURN=5`
21. `OWNER_TOOL_TIMEOUT_MS=30000`
22. `OWNER_OPENINTERPRETER_ENABLED=0`
23. `OWNER_OPENINTERPRETER_COMMAND=openinterpreter`
24. `OWNER_BROWSER_PLAYWRIGHT_ENABLED=0`
25. `OWNER_COMPUTER_CONTROL_ENABLED=0`
核心事件输出:
1. `audio_capture_started`
2. `audio_apm_started`
3. `listening_started`
4. `speech_started`
5. `stt_partial`
6. `stt_final`
7. `llm_stream_started`
8. `llm_sentence_ready`
9. `tts_chunk_ready`
10. `playback_started`
11. `interrupt_detected`
12. `playback_cancelled`
13. `llm_cancelled`
14. `memory_retrieved`
15. `tool_call_requested`
16. `tool_confirmation_required`
17. `tool_call_started`
18. `tool_call_finished`
19. `tool_call_rejected`
20. `session_recovered`
### 数据验证规则
1. Provider 配置必须在启动前校验,不允许未知 provider 静默回退。
2. APM 输出帧采样率、声道数、帧长必须与 VAD/STT 输入一致;不一致必须显式 resample 或报错。
3. Streaming STT partial 不得进入长期记忆;final transcript 必须经过空文本、重复文本、敏感内容和最小置信度检查。
4. TTS 播报文本必须经过现有 TTS sanitizeremoji、表情包和 Markdown 图片不得进入语音。
5. 长期记忆写入必须包含 type、text、source_turn_id、created_at、sensitivity、embedding_model、checksum。
6. FAISS index 和 SQLite metadata 必须可一致性检查;缺失或 checksum 不匹配时不得返回伪造记忆。
7. Tool Router arguments 必须按工具 schema 校验;未知字段、路径越界、命令注入风险必须拒绝。
8. 工具结果进入 LLM 前必须截断、脱敏,并标注工具名、耗时、退出码和是否截断。
9. 所有 cancellation token 必须可幂等触发,多次取消不得抛出未处理异常。
### 需人工澄清
1. 全双工入口是替换 `run-live`,还是新增 `run-agent-live` 并保留 `run-live` 为稳定 turn-based 入口。
2. WebRTC APM Python 绑定优先选择哪个包或本地封装,是否允许引入需要系统编译的依赖。
3. 产品阶段是否确定采用 SenseVoice 和 CosyVoice,还是只在规范中保留候选。
4. 长期记忆是否需要用户可视化管理、删除、导出和禁用命令。
5. Open Interpreter CLI 的实际本机命令、工作目录、沙箱策略和是否允许写操作需要人工确认。
6. Playwright 浏览器工具是否允许使用用户当前 Chrome 登录态,还是只允许独立 browser context。
7. 多人说话场景是否需要主人声纹注册;本变更默认不做身份鉴权。
8. 桌宠 GUI 和电脑控制是否必须同期开工;本变更建议第一版先做音频全双工、记忆和安全工具。
## 设计方案
### 文字版全新架构图
```text
Microphone
-> Capture Ring Buffer
-> WebRtcAudioProcessingStage(AEC + NS + AGC, render reference from Speaker)
-> SileroVadStage
-> InterruptDetector
-> StreamingSttStage(faster-whisper dev / SenseVoice candidate / sherpa fallback)
-> ConversationManager
-> ShortTermSessionContext
-> MemoryManager(SQLite metadata + FAISS vectors)
-> LlmStage(streaming OpenAI-compatible provider)
-> ToolRouter(memory.search/save, shell.readonly, openinterpreter.run, browser.playwright)
-> ResponseStream
-> SentenceSegmenter
-> StreamingTtsStage(CosyVoice candidate / local fallback)
-> Playback Ring Buffer
-> Speaker
-> Render Reference back to WebRtcAudioProcessingStage
```
### 数据流
1. `ContinuousAudioRuntime` 启动后初始化 capture device、playback device、APM、VAD、STT、TTS、LLM、Memory、ToolRouter。
2. 麦克风音频以固定 20 ms 帧写入 capture ring buffer;播放 PCM 以相同时间轴写入 render reference buffer。
3. `WebRtcAudioProcessingStage` 使用 render reference 对 capture frame 执行 AEC,再执行 NS/AGC。
4. APM 后音频同时进入 VAD、Streaming STT 和 Interrupt Detector。
5. `listening` 状态下,VAD/STT 产生用户输入;final transcript 进入 Conversation Manager。
6. Conversation Manager 召回短期上下文和长期记忆,生成 LLM streaming request。
7. LLM delta 进入 Sentence Segmenter;完整句子进入 Streaming TTSTTS chunk 立即入 playback queue。
8. 播放开始后,播放 PCM 仍持续进入 render reference,使 AEC 能抑制助手回声。
9. 如果用户讲话,Interrupt Detector 发出 `interrupt_detected`Cancellation Graph 同时取消 LLM stream、未完成 TTS、播放队列和可取消工具。
10. Tool Router 在 LLM 请求工具时执行 schema 校验、安全分类、确认策略、执行、结果脱敏和回注。
11. 任何 stage 失败进入 `recovering`,释放后台任务和音频资源后回到 `listening``idle`
### 接口定义
```text
AudioFrame:
samples: float32 PCM
sample_rate: int
channels: int
timestamp_monotonic_ms: int
frame_id: str
```
```text
WebRtcAudioProcessingStage.process_capture(frame: AudioFrame) -> AudioFrame
WebRtcAudioProcessingStage.process_render(frame: AudioFrame) -> None
WebRtcAudioProcessingStage.reset_stream() -> None
Errors:
AUDIO_APM_UNAVAILABLE
AUDIO_APM_FORMAT_MISMATCH
AUDIO_APM_PROCESS_FAILED
```
```text
StreamingSttProvider.start_session(session_id: str) -> StreamingSttSession
StreamingSttSession.accept_audio(frame: AudioFrame) -> list[TranscriptEvent]
StreamingSttSession.finish() -> TranscriptFinal
StreamingSttSession.cancel(reason: str) -> None
```
```text
StreamingTtsProvider.start_stream(voice: str, sample_rate: int) -> StreamingTtsSession
StreamingTtsSession.accept_text(text: str) -> list[AudioFrame]
StreamingTtsSession.flush() -> list[AudioFrame]
StreamingTtsSession.cancel(reason: str) -> None
```
```text
MemoryManager.search(query: str, *, top_k: int, filters: dict) -> list[MemoryRecord]
MemoryManager.save(record: MemoryRecordInput) -> MemoryRecord
MemoryManager.delete(memory_id: str) -> None
MemoryManager.health_check() -> MemoryHealth
```
```text
ToolRouter.route(call: ToolCallRequest, context: ToolContext) -> ToolDecision
ToolRouter.execute(decision: ToolDecision, cancellation: CancellationToken) -> ToolResult
ToolDecision:
action: execute | reject | require_confirmation
risk_level: low | medium | high | forbidden
reason: str
```
### 状态机
```text
idle
-> listening
listening
-> thinking on final user transcript
-> interrupted on explicit cancel or new speech over assistant residue
-> recovering on audio/STT failure
thinking
-> speaking on first playable TTS chunk
-> tool_running on approved tool call
-> interrupted on user speech
-> recovering on LLM failure
speaking
-> listening on reply finished and no follow-up/tool pending
-> interrupted on valid user speech
-> recovering on TTS/playback failure
tool_running
-> thinking on tool result returned to LLM
-> interrupted on cancellable tool interrupted
-> recovering on tool failure
interrupted
-> listening after current tasks cancelled and buffered user audio retained
recovering
-> listening after cleanup when runtime can continue
-> idle when required provider unavailable
```
### 关键算法
1. AEC reference 对齐:播放 PCM 写入 render buffer 时记录 monotonic timestampcapture frame 处理前取最近 reference window,丢帧或漂移超过阈值时发 `audio_reference_drift`
2. 打断检测:APM 后音频先经 VAD 判断人声起点;连续人声超过最小阈值后结合 STT stable partial 或能量/频谱置信度触发 interrupt;纯 render echo 在 AEC 后应低于阈值。
3. 句子切分:LLM delta 累积到中文句号、问号、感叹号、英文终止标点或最大等待阈值时切句;代码块、URL、数字小数点不得误切。
4. 取消传播:每轮创建 root cancellation tokenLLM、TTS、playback、tool 子任务注册 child token;用户打断时 root token 广播,所有 stage 幂等收尾。
5. 长期记忆召回:用户 final transcript 生成 embeddingFAISS 取 Top-NSQLite 取 metadata,按类型、敏感度、最近使用、相似度重排后注入 LLM。
6. 记忆写入:Conversation Manager 在回合结束后提取候选记忆,按敏感分类和用户意图决定是否保存;敏感或不确定默认不保存。
7. 工具路由:LLM tool call 先过 schema,再做风险分类和权限判断;低风险可执行,高风险进入确认,禁止类直接拒绝。
### 数据库/状态管理变更
1. SQLite 表 `memories``id``type``text``summary``metadata_json``sensitivity``source_turn_id``created_at``updated_at``last_used_at``embedding_id``checksum`
2. SQLite 表 `tool_audit_logs`:记录工具名、风险、确认状态、耗时、退出码、截断标记和脱敏摘要,不保存密钥。
3. FAISS index 文件保存 embedding vectorsSQLite 保存 index 版本和 embedding model,启动时做一致性检查。
4. 短期会话上下文仍在内存中,进程退出丢弃;长期记忆独立存储,可按配置禁用。
### UI 组件重构方案
1. 终端 reporter 只订阅事件,不直接读取 pipeline 内部状态。
2. 未来桌宠 GUI 使用同一事件流展示 `listening``thinking``speaking``interrupted``tool_running``recovering`
3. 工具确认必须作为事件暴露,终端可先实现文本确认,GUI 后续实现按钮确认。
4. 实时字幕分为 partial、stable partial、final 三种显示层级,避免把抖动 partial 当作最终用户输入。
### 依赖影响分析
1. WebRTC APM:新增依赖风险最高,需确认 Python/macOS 可用绑定或自建 native wrapper。
2. Silero VAD:新增本地模型依赖,需评估 ONNX Runtime 或 torch 路线。
3. Faster Whisper:开发体验好,但模型体积和 Metal/CPU 性能需评估。
4. SenseVoice:中文效果强,产品候选;需确认 license、模型大小、macOS 部署成本。
5. CosyVoice:TTS 效果强,依赖较重;第一阶段可先保留现有本地 TTS fallback。
6. FAISSmacOS 安装和 wheel 兼容性需评估;必要时提供 sqlite-only 或 numpy fallback。
7. Playwright:浏览器自动化依赖和浏览器安装体积需评估;第一版默认关闭。
8. Open Interpreter:作为外部 CLI 后端,不作为 Owner 包内依赖;路径缺失时 adapter 不可用。
## 风险与权衡
| 风险 | 概率 | 影响 | 缓解措施 |
| --- | --- | --- | --- |
| WebRTC APM Python/macOS 绑定不可用或编译复杂 | 高 | 高 | OpenSpec 中把 provider 抽象出来;先验证 fake APM 和最小 native 方案;保留 GTCRN/音色门控 fallback。 |
| AEC reference 与 capture 时钟不同步 | 中 | 高 | 使用 monotonic timestamp、ring buffer drift 监控、reference gap 事件和回声测试 fixture。 |
| 全双工并发导致线程泄漏或播放卡顿 | 中 | 高 | 所有 stage 必须支持 cancellation token、bounded queue、backpressure 和统一 shutdown。 |
| Streaming TTS 依赖过重导致落地慢 | 中 | 中 | 第一阶段先按句分段合成,CosyVoice 作为目标 provider,保留现有 Mac TTS fallback。 |
| Faster Whisper/SenseVoice 模型性能不足 | 中 | 中 | 规范要求 provider 可替换,测试记录 partial/final latency,产品候选不在第一阶段强绑定。 |
| 长期记忆保存敏感信息 | 中 | 高 | 默认敏感不自动保存;记忆写入前分类;用户可关闭;日志和记忆脱敏。 |
| Tool Router 执行危险操作 | 中 | 高 | 默认安全工具优先,高风险确认,禁止类拒绝,目录限制,超时,输出截断,审计日志。 |
| Open Interpreter 外部后端越权 | 中 | 高 | 默认关闭;只允许受限目录、低风险任务;写操作必须确认或拒绝;不复制外部仓库进 Owner。 |
| Playwright 使用登录态带来账号风险 | 中 | 高 | 第一版默认独立 context;涉及账号、支付、购买、提交必须确认或拒绝。 |
| 用户期望立即实现完整 GUI 控制 | 中 | 中 | 本变更明确第一版不做 GUI 点击/键盘/屏幕控制,只预留公共 provider。 |
| FAISS 与 SQLite 一致性损坏 | 低 | 中 | 启动 health check、checksum、index rebuild 任务和 sqlite-only 降级。 |
| LLM 工具循环 | 中 | 中 | 单轮最大工具次数、重复调用检测、总耗时预算和可恢复拒绝。 |
| 不确定是否继续对话造成体验不稳 | 中 | 中 | Conversation Manager 规则优先,LLM 分类兜底,不确定默认 listening/idle 策略需人工确认。 |
| OpenSpec scope 过大导致实现周期过长 | 高 | 中 | 实施计划分阶段:先音频全双工,再流式 STT/TTS,再记忆和工具,再电脑控制。 |
## 任务分解
> 本节定义 proposal 里的主要功能组或里程碑阶段。后续 `tasks.md` 必须把每组拆成不超过 1 小时的原子任务,并在完成每个大模块后按 Git 提交规范立即提交。
### 1. OpenSpec 与边界冻结
目标:只产出 `add-full-duplex-agent-voice-assistant` 文档,不改代码、不装依赖、不下载模型、不提交 `openinterpreter/`
验收:OpenSpec 变更目录包含 proposal、design、tasks、spec delta;严格校验通过。
### 2. WebRTC APM 与全双工音频底座规划
目标:定义 capture/render ring buffer、AEC reference、NS、AGC、音频格式、时钟对齐和 fallback 策略。
验收:spec 包含 APM SHALL 要求、fake reference 测试场景和回声不触发 VAD/STT 场景。
### 3. 全双工状态机、事件总线与取消机制规划
目标:定义 `idle/listening/thinking/speaking/interrupted/tool_running/recovering` 状态机、事件模型、cancellation graph 和 recovery。
验收:design 包含状态转移表和取消传播;tasks 包含状态机、事件顺序和错误恢复测试。
### 4. Streaming STT 与低延迟打断规划
目标:定义 Streaming STT provider、Silero VAD、partial/final transcript、打断检测和 200 ms 目标。
验收:spec 包含 partial/final 行为、speaking 中用户说话进入 interrupted、纯回声不打断。
### 5. Streaming TTS 与响应流规划
目标:定义 LLM token stream、句子切分、Streaming TTS、播放队列、可中断播放和上下文写入边界。
验收:spec 包含不等待完整回复、未播完文本不写上下文、TTS 卡顿可观测。
### 6. 长期记忆规划
目标:定义 MemoryManager、FAISS+SQLite、记忆类型、召回、写入安全、禁用和删除策略。
验收:spec 包含保存、检索、重启召回、关闭记忆不读写、敏感内容不自动保存。
### 7. Tool Router 与安全工具规划
目标:定义结构化工具协议、安全路由、工具预算、防循环、工具结果回注,以及 `memory.search/save``shell.readonly``openinterpreter.run``browser.playwright`
验收:spec 包含合法工具执行、非法工具拒绝、确认策略、Open Interpreter 缺失处理和 Playwright 安全边界。
### 8. Open Interpreter 与电脑控制边界规划
目标:明确 `openinterpreter/` 是外部未跟踪后端候选,不复制进 Owner;第一版不做 GUI 控制,只预留 `ComputerControlProvider`
验收:proposal/design/tasks 均写明边界;git 提交不包含 `openinterpreter/`
### 9. 测试、性能、安全和验收规划
目标:定义 fake APM、VAD、STT、TTS、memory、tool、Open Interpreter、Playwright、端到端模拟、性能指标和安全验证。
验收:tasks 中每项有前置条件、优先级、验收标准和测试要点;validation 命令明确。
## Spec Deltas
### Capabilities
#### New Capabilities
本变更不创建独立新 capability 文件。原因:用户明确要求 delta 文件路径为 `specs/voice-pet-pipeline/spec.md`,且全双工音频、长期记忆和 Tool Router 都作为语音助手 pipeline 的能力升级纳入同一现有 capability。
#### Modified Capabilities
- `voice-pet-pipeline`:从 turn-based Python 桌宠语音 pipeline 扩展为完整全双工 Agent 语音助手 pipeline,新增 WebRTC APM、持续监听、Streaming STT/TTS、低延迟打断、长期记忆、Tool Router、Open Interpreter 外部后端适配和安全工具执行要求。
### 与现有 `openspec/specs/voice-pet-pipeline/spec.md` 的精确差异
1. 修改 `Local microphone and speaker transport`:从本机麦克风/扬声器 transport 扩展为 capture/render reference 双向音频流,播放音频必须提供给 AEC。
2. 修改 `VAD speech endpoint detection`:从 turn-based 端点检测扩展为持续 VAD、打断检测和全双工 listening。
3. 修改 `Local STT transcription`:从 captured segment final STT 扩展为 Streaming STT partial/stable/final。
4. 修改 `Cloud LLM streaming reply`:从流式 LLM 输出扩展为 token-to-sentence-to-TTS response stream,并要求可取消。
5. 修改 `Local TTS synthesis and playback`:从整段或分句播放扩展为 Streaming TTS、PCM chunk 播放和中途停止。
6. 修改 `Pipeline state machine`:新增 `listening``tool_running``recovering` 等全双工 Agent 状态,明确 `speaking -> interrupted`
7. 修改 `Audio feedback suppression`:从播放期间抑制输入扩展为 AEC + VAD + interrupt,允许用户有效打断。
8. 修改 `Conversation context management`:保留进程内上下文,同时新增长期记忆召回的边界。
9. 修改 `Security and privacy`:新增长期记忆、工具执行、Open Interpreter 和浏览器自动化安全要求。
10. 修改 `Performance targets`:新增打断延迟、Streaming STT 首字、TTS 首 chunk、APM 帧处理等指标。
11. 新增 requirement`WebRTC audio processing foundation`
12. 新增 requirement`Full-duplex agent state machine`
13. 新增 requirement`Streaming STT and realtime transcript`
14. 新增 requirement`Low-latency interruption and cancellation`
15. 新增 requirement`Streaming response and TTS playback`
16. 新增 requirement`Long-term memory with FAISS and SQLite`
17. 新增 requirement`Tool Router and structured tool execution`
18. 新增 requirement`Open Interpreter external adapter`
19. 新增 requirement`Browser automation tool boundary`
20. 新增 requirement`Computer control reservation`
### 推翻重做的理由
1. turn-based VAD/STT/TTS 无法自然支持“AI 讲话时用户插话”,只能不断添加补丁。
2. 音色门控不能替代 AEC;没有 render reference 的系统无法稳定区分助手回放和真实用户。
3. 只靠 final STT 会让用户等待过久;完整助手需要持续识别和实时字幕。
4. 没有 Tool Router 和 MemoryManager 的语音助手只能聊天,不能完成 Agent 任务。
5. 工具和记忆如果后补,会难以补齐安全边界;必须在 OpenSpec 阶段先定义。
## 实施计划
### 分阶段优先级顺序
1. 阶段 A:OpenSpec 文档和边界冻结。
2. 阶段 B:全双工音频底座和 fake APM 测试。
3. 阶段 C:状态机、事件总线、取消机制和模拟端到端。
4. 阶段 DStreaming STT、VAD 打断和回声抑制测试。
5. 阶段 EStreaming LLM/TTS、句子切分和可中断播放。
6. 阶段 F:长期记忆 FAISS+SQLite、记忆召回和隐私策略。
7. 阶段 GTool Router、安全工具、Open Interpreter adapter 和 Playwright adapter。
8. 阶段 H:文档、性能验收、安全审计和迁移收尾。
### 里程碑估时
| 里程碑 | 乐观 | 最可能 | 悲观 |
| --- | ---: | ---: | ---: |
| A OpenSpec 文档 | 0.5 天 | 1 天 | 1.5 天 |
| B 音频底座 | 2 天 | 4 天 | 8 天 |
| C 状态机与取消 | 2 天 | 3 天 | 6 天 |
| D Streaming STT 与打断 | 3 天 | 5 天 | 10 天 |
| E Streaming TTS | 3 天 | 5 天 | 10 天 |
| F 长期记忆 | 2 天 | 4 天 | 8 天 |
| G Tool Router | 3 天 | 6 天 | 12 天 |
| H 验收与收尾 | 2 天 | 4 天 | 8 天 |
总耗时预估:乐观 17.5 天,最可能 32 天,悲观 63.5 天。
### 数据/状态迁移策略
1. 现有进程内上下文不迁移为长期记忆,避免未经确认的历史被自动保存。
2. 新长期记忆库首次启动为空;用户明确要求保存或分类器确认低敏偏好后才写入。
3. 现有 `.env` 继续可用,新增变量采用默认值;全双工模式可通过 feature flag 启用。
4. 现有 turn-based `run-live` 在迁移期保留,直到全双工验收稳定后再决定是否替换默认入口。
5. `openinterpreter/` 外部仓库不纳入 Owner 迁移;只记录 adapter 配置和安全策略。
## Git 提交规范
1. 每完成一个大模块,必须立即执行构建或相应验证,然后执行 git commit。
2. 大模块定义为 proposal “任务分解”中的主要功能组或实施计划中的里程碑阶段。
3. 提交信息必须使用中文,格式为:`[模块名]:完成[具体功能描述],包含[关键变更]`
4. 提交前必须保证本模块验证通过,避免任何未提交的中间状态。
5. 本 OpenSpec-only 阶段完成后提交信息固定为:`[全双工Agent架构]:完成完整语音助手OpenSpec计划,包含WebRTC音频底座、长期记忆和Tool Router设计`
6. 本次提交只允许包含 `openspec/changes/add-full-duplex-agent-voice-assistant/` 下的规划文档,不得提交 `openinterpreter/`、模型文件、依赖锁文件、`.env` 或运行代码。