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

10 KiB
Raw Blame History

完整全双工 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=webrtcOWNER_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.searchmemory.saveshell.readonlyopeninterpreter.runbrowser.playwright
  18. Open Interpreter、Playwright 默认关闭;显式启用后仍受风险分类、目录限制、超时、输出截断和确认策略约束。
  19. README 和 .env.example SHALL 统一说明 run-agent-live 是完整全双工入口,run-live 是旧入口。
  20. 新增 agent-self-testaudio-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-livereal-live-checkrun-live 不得被新 runtime 破坏。

边缘案例:

  1. 用户在 LLM 尚未开始播放时插话:取消 thinking,保留新用户音频,重启当前输入。
  2. 用户在 TTS 合成中插话:取消 TTS,未播文本不进上下文。
  3. 用户和助手声音重叠:APM 后用户声有效时触发打断;纯回声不得触发。
  4. 工具运行中插话:可取消工具取消;不可安全取消工具进入确认/等待结果后恢复。
  5. STT final 空文本:不调用 LLM,回 listening。
  6. Memory index 损坏:禁用长期记忆并发 health error,不影响基本对话。

设计方案

文字架构图:

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

主状态机:

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
    • 参数:kindprocessed_captureraw_capturerender_referencedebug
    • 返回:独立 cursor 的 frame reader。
    • 错误:设备缺失、buffer closed、format mismatch。
  2. AudioProcessingProvider.process_capture(frame) -> AudioFrame
    • 输入:raw capture frame。
    • 输出:AEC/NS/AGC 后 frame。
    • 错误:AUDIO_APM_UNAVAILABLEAUDIO_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 为空。