Files
Owner/openspec/changes/complete-full-duplex-agent-runtime/proposal.md
T

184 lines
10 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.
# 完整全双工 Agent 语音助手运行时
## 功能目标
当前 `run-agent-live` 已能进入无唤醒监听并在 TTS 播放阶段尝试后台打断,但真实架构仍是轮次式:先录完整句话,再 final STT,再 LLM,再 TTS 播放。播放期打断依赖临时 `AsyncBargeInMonitor` 从同一个 Transport 队列抢帧,无法保证麦克风输入、VAD、STT、回声抑制和播放取消之间的并发边界稳定。用户实际反馈“完全没有打断”,说明继续调参数不能解决根因。
本变更目标是把 `run-agent-live` 替换为完整全双工 Agent 运行时。目标用户是 macOS 本地桌面语音助手使用者,核心场景是用户可以直接说话、助手边生成边播报、用户随时插话打断、系统取消当前回复并立即处理新问题,同时保留长期记忆和安全工具调用能力。
量化成功指标:
1. 打断延迟:在自测 fixture 中,用户语音开始到播放停止的 P95 小于 200 ms。
2. 回声隔离:纯助手回放 reference 不触发用户打断、不产生有效用户 transcript。
3. 帧分发正确性:AudioHub 多消费者订阅时,VAD、STT、interrupt detector 不互相抢帧。
4. 响应首音:LLM 输出首个可播报句子后,TTS 首个 PCM chunk 在 1000 ms 内进入播放队列。
5. 自测覆盖:`agent-self-test --profile full-duplex --turns 3` 能无真人完成 STT、LLM、TTS、barge-in、memory、tool router 验收。
6. 安全边界:工具调用默认执行低风险工具,高风险工具进入确认或拒绝;日志不泄露 `.env` 密钥、raw PCM 或敏感记忆。
## 详细需求
功能需求:
1. `run-agent-live` SHALL 使用新的 `FullDuplexAgentRuntime`,不得继续调用 `VoiceAssistantPipeline.run_agent_turn()`
2. `run-live` SHALL 保持旧 wake-word turn-based 稳定入口。
3. 系统 SHALL 新增 `AudioHub`,作为唯一麦克风输入拥有者,内部维护 20 ms、16 kHz、mono、int16 capture ring buffer。
4. VAD、STT、interrupt detector 和诊断录制 SHALL 从 AudioHub 获取独立订阅,不得直接抢读 Transport 队列。
5. 系统 SHALL 新增 render ring buffer,播放 PCM 写入 speaker 的同时写入 AEC reference。
6. `run-agent-live` SHALL 初始化 WebRTC APM;当 `OWNER_AUDIO_APM_PROVIDER=webrtc``OWNER_AUDIO_APM_REQUIRED=1` 时,真实 provider 不可用必须启动失败,不得静默回退为伪全双工。
7. fake APM SHALL 只用于测试、自测和显式 `OWNER_AUDIO_APM_PROVIDER=fake`
8. 持续 VAD SHALL 在 listening/thinking/speaking/tool_running 阶段持续判断用户是否开始说话。
9. Streaming STT SHALL 输出 partial、stable partial、final 三层事件;final 或明确提交 stable transcript 才能进入 LLM。
10. 打断 SHALL 不依赖 partial STTVAD + APM 后有效人声即可触发 cancellation。
11. LLM、TTS、播放队列、可取消工具 SHALL 共享 cancellation graph;打断时全部取消。
12. LLM SHALL 默认启用 streaming。
13. TTS SHALL 使用可分块 PCM providerCosyVoice 为目标 providermacOS `say` 只做 fallback。
14. 播放 SHALL 每 20-30 ms chunk 检查 cancellation,并把 render chunk 写入 APM reference。
15. Conversation Manager SHALL 统一短期上下文、长期记忆、LLM、tool call、TTS 和打断恢复。
16. MemoryManager SHALL 支持 SQLite 文本/元数据和 FAISS 向量索引;默认启用但敏感内容不自动保存。
17. ToolRouter SHALL 接入 LLM tool call loop,第一批工具包括 `memory.search``memory.save``shell.readonly``openinterpreter.run``browser.playwright`
18. Open Interpreter、Playwright 默认关闭;显式启用后仍受风险分类、目录限制、超时、输出截断和确认策略约束。
19. README 和 `.env.example` SHALL 统一说明 `run-agent-live` 是完整全双工入口,`run-live` 是旧入口。
20. 新增 `agent-self-test``audio-self-test`,作为无人值守验收命令。
非功能需求:
1. 音频回调不得执行阻塞 STT、LLM、TTS 或工具逻辑。
2. Capture/render ring buffer 必须有容量上限,溢出丢弃旧帧并发诊断事件。
3. 所有后台线程/任务必须可关闭;测试后不得残留线程。
4. 事件 payload 必须脱敏,不输出 API key、Authorization、raw PCM、完整工具敏感输出。
5. 无 APM、无 STT 模型、无 TTS provider、无设备权限时必须给结构化错误。
6.`simulate-live``real-live-check``run-live` 不得被新 runtime 破坏。
边缘案例:
1. 用户在 LLM 尚未开始播放时插话:取消 thinking,保留新用户音频,重启当前输入。
2. 用户在 TTS 合成中插话:取消 TTS,未播文本不进上下文。
3. 用户和助手声音重叠:APM 后用户声有效时触发打断;纯回声不得触发。
4. 工具运行中插话:可取消工具取消;不可安全取消工具进入确认/等待结果后恢复。
5. STT final 空文本:不调用 LLM,回 listening。
6. Memory index 损坏:禁用长期记忆并发 health error,不影响基本对话。
## 设计方案
文字架构图:
```text
Microphone
-> AudioHub raw capture ring
-> WebRtcAudioProcessingStage(AEC/NS/AGC, render reference)
-> processed capture ring
-> Continuous VAD
-> Streaming STT
-> ConversationManager
-> MemoryManager(SQLite + FAISS)
-> LLM streaming + ToolRouter
-> SentenceSegmenter
-> Streaming TTS
-> InterruptiblePlaybackQueue
-> Speaker + render reference ring
```
主状态机:
```text
idle -> listening -> thinking -> speaking -> listening
speaking -> interrupted -> listening
thinking -> interrupted -> listening
tool_running -> interrupted/recovering/listening
any recoverable error -> recovering -> listening
```
关键接口:
1. `AudioHub.subscribe(kind: str) -> AudioSubscription`
- 参数:`kind``processed_capture``raw_capture``render_reference``debug`
- 返回:独立 cursor 的 frame reader。
- 错误:设备缺失、buffer closed、format mismatch。
2. `AudioProcessingProvider.process_capture(frame) -> AudioFrame`
- 输入:raw capture frame。
- 输出:AEC/NS/AGC 后 frame。
- 错误:`AUDIO_APM_UNAVAILABLE``AUDIO_APM_PROCESS_FAILED`
3. `StreamingSttSession.accept_frame(frame) -> list[TranscriptEvent]`
- 输出:partial/stable/final。
- 错误:模型缺失、推理失败、取消。
4. `StreamingTtsSession.accept_text(text) -> list[AudioFrame]`
- 输出:可播放 PCM chunks。
- 错误:provider 缺失、合成失败、取消。
5. `ToolRouter.route(call) -> ToolDecision`
- 输出:execute/reject/require_confirmation。
性能优化路径:
1. 音频输入只写 ring buffer,不做重计算。
2. STT 和 VAD 消费 processed capture ring,互不阻塞。
3. 播放 chunk 与 render reference 同步写入,避免打断检测依赖播放回调抢帧。
4. LLM streaming + sentence segmentation 让首句尽早进入 TTS。
5. Self-test 记录 interrupt latency、STT first partial、TTS first chunk。
UI/UX 路径:
1. 终端输出仍由 event bus 驱动。
2. 打断事件输出 `检测到用户打断``取消当前回复``继续听你说`
3. debug 模式输出 APM provider、echo suppression、interrupt latency。
4. 未来 GUI 桌宠订阅同一事件总线。
## 风险与权衡
| 风险 | 概率 | 影响 | 缓解措施 |
|---|---:|---:|---|
| macOS 上没有可用 WebRTC APM Python binding | 高 | 高 | 完整模式启动失败并说明安装要求;self-test 使用 fake APM;旧 `run-live` 保留 |
| Faster Whisper 流式能力不是真正低延迟 streaming | 中 | 中 | 先实现稳定 provider contract;允许 sherpa-onnx fallbackSenseVoice 作为后续 provider |
| CosyVoice 本地依赖重、安装慢 | 高 | 中 | provider 可配置;macOS say fallback 标记为降级,不宣称完整全双工默认 |
| AudioHub 并发复杂导致线程泄漏 | 中 | 高 | 所有后台任务使用 cancellation graph;单元测试检查 shutdown |
| AEC 质量不足仍误触发 | 中 | 高 | 增加 echo self-test、render drift 诊断、阈值调试输出 |
| FAISS/SQLite 不一致 | 中 | 中 | health-check 和 checksum;不返回未验证记忆 |
| 工具调用误执行高风险动作 | 中 | 高 | 默认关闭 Open Interpreter/Playwright;风险分类和确认策略;只读 shell allowlist |
| 旧 run-live 回归 | 低 | 高 | 保持代码路径分离;旧测试继续跑 |
## 任务分解
详见 `tasks.md`。所有任务拆到不超过 1 小时,按 OpenSpec、音频底座、持续识别与打断、流式回复、记忆工具、自测文档分阶段完成。每个阶段完成后验证并中文 commit。
## Spec Deltas
新增:
1. `Complete full-duplex agent runtime`
2. `AudioHub fanout`
3. `Required WebRTC APM for full-duplex`
4. `Always-on interruption controller`
5. `Streaming STT/TTS runtime`
6. `Conversation manager with memory and tools`
7. `Self-test commands`
修改:
1. `Live run-agent-live runtime` 从“可真实启动并播放期打断”升级为“完整全双工主入口”。
2. `WebRTC audio processing foundation` 从规划/fake provider 验证升级为运行时启动要求。
3. `Tool Router` 从模块级测试升级为 LLM tool-call 主链路要求。
删除/推翻:
1. 推翻 `AsyncBargeInMonitor` 作为主打断架构;保留为 legacy fallback 或删除。
2. 推翻 `run-agent-live` 复用 `VoiceAssistantPipeline.run_agent_turn()` 的实现边界。
3. 推翻“真实运行仍走 run-live”的 README 旧描述。
## 实施计划
1. M1 OpenSpec0.5 天。
2. M2 AudioHub + APM1-1.5 天。
3. M3 Continuous VAD/STT + interrupt1-2 天。
4. M4 Streaming response/TTS/playback1 天。
5. M5 Memory + Tool Router1-1.5 天。
6. M6 self-test/docs/final gates0.5-1 天。
乐观总耗时 5 天,最可能 7 天,悲观 10 天。没有数据迁移;长期记忆首次启用时创建 SQLite/FAISS 文件,旧短期上下文不迁移。
## Git 提交规范
每完成一个主要阶段必须立即验证并提交,提交信息格式:
`[模块名]:完成[具体功能描述],包含[关键变更]`
提交前至少运行该阶段相关单测;最终阶段运行完整门禁并保证 `git status --short` 为空。