37 KiB
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/子进程工具后端候选。
目标用户场景
- 用户在 macOS 上启动语音助手后,不需要按住说话;麦克风常驻监听,系统通过本地唤醒或持续 VAD/STT 管理输入。
- 用户听 AI 回复时可以直接插话;系统在 200 ms 目标延迟内停止 TTS 播放、取消当前 LLM/TTS 任务,并把用户新话语作为下一轮输入。
- AI 自己的扬声器声音不会被误当成用户输入;WebRTC APM 的 AEC 使用播放 reference 音频消除回声,NS 降低环境噪声,AGC 统一麦克风音量。
- 用户可以连续追问,不必每轮都说“小杰小杰”;Conversation Manager 根据状态机和上下文判断当前是继续听、执行工具、播报结果还是恢复待机。
- 助手可长期记住非敏感偏好、事实、项目摘要和任务摘要;重启后仍可通过 FAISS+SQLite 召回相关记忆,但敏感信息默认不自动保存。
- 用户要求“整理下载目录”“查一下项目文件”“打开网页抓取内容”时,Tool Router 先做安全分类,低风险只读工具可执行,高风险写操作、删除、上传、交易、权限变更必须要求确认,第一版不自动做 GUI 点击/键盘控制。
- 后续桌宠 GUI 可订阅同一事件流展示听、想、说、工具执行、被打断、等待确认等状态,而不是重新实现 pipeline 逻辑。
量化成功指标 KPI
- 打断延迟:在可控音频测试环境中,
speaking状态下检测到有效用户语音后,进入interrupted并停止播放的 P95 延迟目标小于 200 ms。 - 回声抑制:播放 reference 音频注入 AEC 后,纯助手回放不触发用户 VAD/STT,不产生有效
barge_in_detected。 - 实时识别:用户开始说话后,Streaming STT 首个稳定 partial transcript 的 P95 目标小于 800 ms。
- 端到端首音:LLM 首个可播报句子产生后,Streaming TTS 首个可播放 PCM chunk 的 P95 目标小于 1000 ms。
- 流式播报:LLM 不等待完整回复;中文回复按句切分进入 TTS,第一句可播放后立即播放。
- 取消可靠性:用户打断时,当前 LLM stream、TTS synthesis、playback、工具执行候选任务均收到 cancellation token;被取消内容不得继续写入上下文。
- 记忆召回准确性:保存的偏好/事实/项目摘要在相关查询中可被 Top-K 检索召回;关闭记忆时不得读写 SQLite/FAISS。
- 工具安全:未授权工具、越权路径、写操作、删除操作、账号/上传/交易类任务必须被拒绝或进入确认流程;自动化测试覆盖非法工具拒绝、防循环、超时、输出截断。
- 兼容性:现有 turn-based
run-live语义在迁移期不得被破坏;新全双工入口可分阶段实现,允许先以 feature flag 启用。 - OpenSpec 完整性:
openspec validate add-full-duplex-agent-voice-assistant --strict与openspec validate --all --strict必须通过。
预期影响
- 主规范
voice-pet-pipeline将从“Python 桌宠语音 pipeline”升级为“全双工 Agent 语音助手 pipeline”,覆盖音频底座、并发模型、记忆、工具和安全策略。 - 后续实现将新增 WebRTC APM provider、全双工音频环形缓冲、Streaming STT/TTS provider、Conversation Manager、MemoryManager、ToolRouter、Open Interpreter adapter、Playwright browser adapter。
- 现有 GTCRN 降噪、主说话人音色门控和本地 KWS 保留为 fallback 或局部能力,不再作为完整全双工回声消除的主方案。
- 当前进程内临时上下文保留,但长期记忆成为独立层;临时会话历史和长期记忆必须有清楚边界。
- 工具执行引入新的安全面,必须规划确认策略、权限边界、路径限制、超时、输出截断、审计日志和敏感信息脱敏。
- 第一版电脑控制不做 GUI 点击/键盘/屏幕控制;只预留
ComputerControlProvider。Codex Computer Use 只参考安全确认策略,不复制私有或捆绑实现。
对现有问题的系统性总结
- 性能问题:现有 turn-based pipeline 在录音结束后才进入 final STT 和 LLM,用户感知延迟集中爆发;全双工架构要求持续识别和流式回复。
- 打断问题:当前打断规划仍围绕播放 chunk 检查或后台监听补丁,缺少全局 cancellation graph;用户插话无法统一取消 LLM、TTS、播放和工具任务。
- 回声问题:现有音色门控只能降低误触发,无法从音频底座消除扬声器回放;没有 AEC reference,后续 VAD/STT 容易被 AI 自己声音污染。
- 噪音问题:GTCRN 只覆盖正式问题采集阶段;全双工持续监听需要系统级 NS/AGC,避免背景噪声导致持续 STT 和打断误判。
- 架构问题:现有 pipeline 仍以“轮次”为主,状态机缺少
tool_running、interrupted、recovering等 Agent 必需状态。 - 记忆问题:当前上下文只存在于本次进程内;无法记住长期偏好、项目背景和任务摘要,也没有隐私分类和删除策略。
- 工具问题:当前 LLM 只能生成自然语言;没有结构化工具协议、工具路由、安全策略、执行预算、防循环机制和工具结果回注。
- Open Interpreter 边界问题:本地
openinterpreter/是未跟踪外部仓库,不能复制进 Owner;需要把它规划为可选外部 CLI 后端并限制风险。 - UI/UX 问题:后续桌宠如果直接绑定 runtime 方法,会继续重复逻辑;需要统一事件总线承载状态、字幕、音频、打断、工具和确认请求。
- 安全问题:长期记忆和工具执行都会扩大数据面与操作面;必须默认最小权限、敏感内容不自动保存、高风险操作确认、日志脱敏。
详细需求
功能需求
- 系统 SHALL 新增完整全双工 Agent 语音助手架构规划,保留现有 turn-based 能力作为迁移期兼容路径。
- 系统 SHALL 规划
ContinuousAudioRuntime,负责麦克风持续输入、扬声器播放 reference、音频环形缓冲、状态机事件和任务取消。 - 系统 SHALL 规划
WebRtcAudioProcessingStage,默认OWNER_AUDIO_APM_PROVIDER=webrtc,开启 AEC、NS、AGC。 - 播放链路 SHALL 把 TTS PCM/render audio 提供给 AEC reference,麦克风 capture 音频经过 APM 后再进入 VAD、STT 和打断检测。
- 系统 SHALL 规划全双工状态机:
idle、listening、thinking、speaking、interrupted、tool_running、recovering。 - 任意状态下检测到有效用户说话 SHALL 能进入
interrupted,但tool_running的中断语义必须区分可取消工具和不可安全取消工具。 - 系统 SHALL 规划 Silero VAD 或等价本地 VAD 作为打断触发的主要人声检测层;打断检测先判断“有人开始说话”,不依赖已识别出完整文本。
- 打断目标 SHALL 是在
OWNER_BARGE_IN_TARGET_LATENCY_MS=200内停止播放和取消当前回复。 - 系统 SHALL 规划 Streaming STT provider;开发默认候选为
faster-whisper,产品候选为SenseVoice,保留现有sherpa-onnx兼容 adapter。 - Streaming STT SHALL 输出 partial transcript、stable partial transcript 和 final transcript;只有 final transcript 或明确提交的 stable transcript 可进入 LLM。
- 系统 SHALL 规划 Streaming TTS provider;目标候选为
CosyVoice,支持句子级和流式 PCM 输出。 - LLM SHALL 流式输出 token;Sentence Segmenter SHALL 在检测到完整中文/英文句子或安全停顿时,把文本片段送入 TTS,不等待整段回复完成。
- TTS 播放 SHALL 支持中途停止;停止后未完整播出的 assistant 文本不得写入短期上下文或长期记忆。
- 系统 SHALL 规划
ConversationManager,负责短期会话历史、长期记忆召回、工具调用闭环和状态推进。 - 系统 SHALL 规划
MemoryManager,默认OWNER_MEMORY_PROVIDER=faiss_sqlite,SQLite 存文本和元数据,FAISS 存向量索引。 - 记忆类型 SHALL 至少包含
preference、fact、project、task_summary。 - 每轮用户输入进入 LLM 前 SHALL 根据当前用户文本、会话摘要和任务上下文检索 Top-K 长期记忆,并以明确的 memory context 注入 LLM。
- 敏感内容 SHALL 默认不自动保存;记忆写入必须经过分类器、安全策略或用户明确指令。
- 系统 SHALL 规划
ToolRouter和结构化工具调用协议,工具请求包含 name、arguments、risk_level、requires_confirmation、timeout_ms、budget、cancellation_policy。 - 第一版工具 SHALL 以安全工具为主:
memory.search、memory.save、shell.readonly、openinterpreter.run、browser.playwright。 shell.readonlySHALL 限制为只读命令和允许目录,禁止删除、写文件、修改权限、网络上传、安装依赖等高风险行为。openinterpreter.runSHALL 作为外部 CLI/子进程 adapter,默认只允许低风险、受限目录、超时和输出截断任务。browser.playwrightSHALL 作为浏览器自动化 adapter,默认只允许可审计、非支付、非账号敏感的浏览和提取流程。- 第一版 SHALL NOT 实现 GUI 点击/键盘/屏幕控制;只在规范中预留
ComputerControlProvider,后续可基于 macOS Accessibility、Playwright、trycua 等公共能力实现。 - Codex Computer Use 能力 SHALL 仅作为安全确认策略参考,不复制私有实现、捆绑脚本或内部协议。
- 系统 SHALL 规划工具结果回注:工具输出进入 Tool Result Message,经过脱敏和长度限制后返回 LLM;工具失败进入可恢复错误路径。
- 系统 SHALL 规划防循环策略:单轮最大工具调用次数、最大总耗时、最大输出字节、重复工具调用检测。
- 系统 SHALL 规划安全确认策略:写文件、删除、上传、交易、账号、权限、联网提交、安装依赖、执行任意代码等高风险操作必须确认,第一版默认拒绝自动执行。
- 系统 SHALL 规划桌宠/终端共用事件总线,事件覆盖 audio、vad、stt、llm、tts、playback、memory、tool、confirmation、interruption、recovery。
- 本阶段 SHALL 只创建 OpenSpec 文档;不得创建或修改
src/、tests/、scripts/、pyproject.toml、.env、模型文件、音频资产或运行时代码。
非功能需求
- 性能优化:全双工音频处理必须以固定帧长和环形缓冲为基础,避免 Python 线程阻塞导致播放卡顿或输入积压。
- 延迟目标:VAD frame interval 默认不超过 20 ms;播放停止 chunk 默认不超过 30 ms;整体打断目标小于 200 ms。
- 稳定性:所有 provider 必须支持超时、取消、关闭和资源释放;异常必须进入
recovering,不得让后台线程泄漏。 - 可扩展性:APM、VAD、STT、TTS、LLM、Memory、Tool adapter 都必须是可替换 provider,不能把具体模型硬编码进状态机。
- UI/UX:终端和未来 GUI 不直接调用 provider,只消费事件;显示文案、字幕、工具确认和桌宠动画均由事件驱动。
- 安全:API key、Authorization header、原始音频、声纹特征、工具敏感输出不得写入日志或长期记忆。
- 隐私:默认不保存原始麦克风音频;长期记忆只保存文本摘要和必要元数据;用户必须能关闭记忆。
- 兼容性:现有
.env中 LLM 配置继续可用;新增配置必须有默认值和迁移说明。 - 可测试性:每个并发 stage 必须可用 fake provider 和虚拟时钟测试;端到端模拟不依赖真实麦克风、扬声器或外部工具。
- 可观测性:事件必须携带 turn/session id、stage、时间戳、latency、error code 和脱敏 payload,便于定位卡顿、误触发和工具风险。
边缘案例
- APM 初始化失败:系统必须报告
AUDIO_APM_UNAVAILABLE,可按配置降级到现有 GTCRN/音色门控 fallback 或拒绝进入全双工模式。 - AEC reference 丢失:播放中没有 reference 音频时,系统必须降低打断置信度或临时禁用高风险 barge-in,避免 AI 自己声音触发。
- 麦克风权限缺失:启动失败并提示设备检查,不进入假监听状态。
- 用户在 AI 说第一个字前打断:取消 LLM/TTS stream,未播报文本不写入上下文。
- 用户在工具执行中打断:可取消工具立即取消;不可安全取消工具进入“正在收尾/等待结果”状态,并向用户播报或显示限制。
- LLM 已发起工具调用但用户打断:未执行工具调用应取消;已执行且低风险的只读工具结果可丢弃或标记为 stale。
- 记忆库损坏:FAISS 或 SQLite 不可用时,系统可禁用长期记忆并继续基本语音对话,但必须报告结构化错误。
- 记忆召回命中敏感内容:默认不注入 LLM,除非用户明确要求并通过安全策略。
- Open Interpreter 路径缺失:
openinterpreter.runadapter 标记不可用,不影响其他工具。 - 工具输出过长:按配置截断并附
truncated=true元数据,不把完整大输出塞进 LLM。 - Playwright 未安装或浏览器不可用:工具返回可恢复错误,不影响语音主循环。
- TTS 输出卡顿:播放队列应能反压 TTS 合成;卡顿事件必须可观测,不能阻塞麦克风监听线程。
- Streaming STT partial 抖动:只显示稳定 partial;final transcript 才进入对话。
- 网络 LLM 慢或断线:取消和超时必须生效;恢复后回到 listening 或 idle。
- 多人同时说话:第一版不承诺身份鉴权,只要求 AEC 后的人声触发和用户体验合理;多人区分列为需人工澄清。
输入输出规格
新增或规划配置:
OWNER_ASSISTANT_MODE=full_duplex_agentOWNER_AUDIO_APM_PROVIDER=webrtcOWNER_AUDIO_AEC_ENABLED=1OWNER_AUDIO_NS_ENABLED=1OWNER_AUDIO_AGC_ENABLED=1OWNER_AUDIO_FRAME_MS=20OWNER_AUDIO_RING_BUFFER_MS=3000OWNER_VAD_PROVIDER=sileroOWNER_INTERRUPT_ENABLED=1OWNER_INTERRUPT_TARGET_LATENCY_MS=200OWNER_STREAMING_STT_PROVIDER=faster_whisperOWNER_STREAMING_STT_PRODUCT_CANDIDATE=sensevoiceOWNER_STREAMING_TTS_PROVIDER=cosyvoiceOWNER_LLM_STREAMING_ENABLED=1OWNER_MEMORY_ENABLED=1OWNER_MEMORY_PROVIDER=faiss_sqliteOWNER_MEMORY_TOP_K=5OWNER_MEMORY_AUTO_SAVE_SENSITIVE=0OWNER_TOOL_ROUTER_ENABLED=1OWNER_TOOL_MAX_CALLS_PER_TURN=5OWNER_TOOL_TIMEOUT_MS=30000OWNER_OPENINTERPRETER_ENABLED=0OWNER_OPENINTERPRETER_COMMAND=openinterpreterOWNER_BROWSER_PLAYWRIGHT_ENABLED=0OWNER_COMPUTER_CONTROL_ENABLED=0
核心事件输出:
audio_capture_startedaudio_apm_startedlistening_startedspeech_startedstt_partialstt_finalllm_stream_startedllm_sentence_readytts_chunk_readyplayback_startedinterrupt_detectedplayback_cancelledllm_cancelledmemory_retrievedtool_call_requestedtool_confirmation_requiredtool_call_startedtool_call_finishedtool_call_rejectedsession_recovered
数据验证规则
- Provider 配置必须在启动前校验,不允许未知 provider 静默回退。
- APM 输出帧采样率、声道数、帧长必须与 VAD/STT 输入一致;不一致必须显式 resample 或报错。
- Streaming STT partial 不得进入长期记忆;final transcript 必须经过空文本、重复文本、敏感内容和最小置信度检查。
- TTS 播报文本必须经过现有 TTS sanitizer;emoji、表情包和 Markdown 图片不得进入语音。
- 长期记忆写入必须包含 type、text、source_turn_id、created_at、sensitivity、embedding_model、checksum。
- FAISS index 和 SQLite metadata 必须可一致性检查;缺失或 checksum 不匹配时不得返回伪造记忆。
- Tool Router arguments 必须按工具 schema 校验;未知字段、路径越界、命令注入风险必须拒绝。
- 工具结果进入 LLM 前必须截断、脱敏,并标注工具名、耗时、退出码和是否截断。
- 所有 cancellation token 必须可幂等触发,多次取消不得抛出未处理异常。
需人工澄清
- 全双工入口是替换
run-live,还是新增run-agent-live并保留run-live为稳定 turn-based 入口。 - WebRTC APM Python 绑定优先选择哪个包或本地封装,是否允许引入需要系统编译的依赖。
- 产品阶段是否确定采用 SenseVoice 和 CosyVoice,还是只在规范中保留候选。
- 长期记忆是否需要用户可视化管理、删除、导出和禁用命令。
- Open Interpreter CLI 的实际本机命令、工作目录、沙箱策略和是否允许写操作需要人工确认。
- Playwright 浏览器工具是否允许使用用户当前 Chrome 登录态,还是只允许独立 browser context。
- 多人说话场景是否需要主人声纹注册;本变更默认不做身份鉴权。
- 桌宠 GUI 和电脑控制是否必须同期开工;本变更建议第一版先做音频全双工、记忆和安全工具。
设计方案
文字版全新架构图
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
数据流
ContinuousAudioRuntime启动后初始化 capture device、playback device、APM、VAD、STT、TTS、LLM、Memory、ToolRouter。- 麦克风音频以固定 20 ms 帧写入 capture ring buffer;播放 PCM 以相同时间轴写入 render reference buffer。
WebRtcAudioProcessingStage使用 render reference 对 capture frame 执行 AEC,再执行 NS/AGC。- APM 后音频同时进入 VAD、Streaming STT 和 Interrupt Detector。
listening状态下,VAD/STT 产生用户输入;final transcript 进入 Conversation Manager。- Conversation Manager 召回短期上下文和长期记忆,生成 LLM streaming request。
- LLM delta 进入 Sentence Segmenter;完整句子进入 Streaming TTS;TTS chunk 立即入 playback queue。
- 播放开始后,播放 PCM 仍持续进入 render reference,使 AEC 能抑制助手回声。
- 如果用户讲话,Interrupt Detector 发出
interrupt_detected,Cancellation Graph 同时取消 LLM stream、未完成 TTS、播放队列和可取消工具。 - Tool Router 在 LLM 请求工具时执行 schema 校验、安全分类、确认策略、执行、结果脱敏和回注。
- 任何 stage 失败进入
recovering,释放后台任务和音频资源后回到listening或idle。
接口定义
AudioFrame:
samples: float32 PCM
sample_rate: int
channels: int
timestamp_monotonic_ms: int
frame_id: str
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
StreamingSttProvider.start_session(session_id: str) -> StreamingSttSession
StreamingSttSession.accept_audio(frame: AudioFrame) -> list[TranscriptEvent]
StreamingSttSession.finish() -> TranscriptFinal
StreamingSttSession.cancel(reason: str) -> None
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
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
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
状态机
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
关键算法
- AEC reference 对齐:播放 PCM 写入 render buffer 时记录 monotonic timestamp;capture frame 处理前取最近 reference window,丢帧或漂移超过阈值时发
audio_reference_drift。 - 打断检测:APM 后音频先经 VAD 判断人声起点;连续人声超过最小阈值后结合 STT stable partial 或能量/频谱置信度触发 interrupt;纯 render echo 在 AEC 后应低于阈值。
- 句子切分:LLM delta 累积到中文句号、问号、感叹号、英文终止标点或最大等待阈值时切句;代码块、URL、数字小数点不得误切。
- 取消传播:每轮创建 root cancellation token;LLM、TTS、playback、tool 子任务注册 child token;用户打断时 root token 广播,所有 stage 幂等收尾。
- 长期记忆召回:用户 final transcript 生成 embedding,FAISS 取 Top-N,SQLite 取 metadata,按类型、敏感度、最近使用、相似度重排后注入 LLM。
- 记忆写入:Conversation Manager 在回合结束后提取候选记忆,按敏感分类和用户意图决定是否保存;敏感或不确定默认不保存。
- 工具路由:LLM tool call 先过 schema,再做风险分类和权限判断;低风险可执行,高风险进入确认,禁止类直接拒绝。
数据库/状态管理变更
- SQLite 表
memories:id、type、text、summary、metadata_json、sensitivity、source_turn_id、created_at、updated_at、last_used_at、embedding_id、checksum。 - SQLite 表
tool_audit_logs:记录工具名、风险、确认状态、耗时、退出码、截断标记和脱敏摘要,不保存密钥。 - FAISS index 文件保存 embedding vectors;SQLite 保存 index 版本和 embedding model,启动时做一致性检查。
- 短期会话上下文仍在内存中,进程退出丢弃;长期记忆独立存储,可按配置禁用。
UI 组件重构方案
- 终端 reporter 只订阅事件,不直接读取 pipeline 内部状态。
- 未来桌宠 GUI 使用同一事件流展示
listening、thinking、speaking、interrupted、tool_running、recovering。 - 工具确认必须作为事件暴露,终端可先实现文本确认,GUI 后续实现按钮确认。
- 实时字幕分为 partial、stable partial、final 三种显示层级,避免把抖动 partial 当作最终用户输入。
依赖影响分析
- WebRTC APM:新增依赖风险最高,需确认 Python/macOS 可用绑定或自建 native wrapper。
- Silero VAD:新增本地模型依赖,需评估 ONNX Runtime 或 torch 路线。
- Faster Whisper:开发体验好,但模型体积和 Metal/CPU 性能需评估。
- SenseVoice:中文效果强,产品候选;需确认 license、模型大小、macOS 部署成本。
- CosyVoice:TTS 效果强,依赖较重;第一阶段可先保留现有本地 TTS fallback。
- FAISS:macOS 安装和 wheel 兼容性需评估;必要时提供 sqlite-only 或 numpy fallback。
- Playwright:浏览器自动化依赖和浏览器安装体积需评估;第一版默认关闭。
- 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 命令明确。
10. 真实全双工运行时落地
目标:撤销“只完成骨架即完成”的边界,把 run-agent-live 接成可真人运行的全双工语音入口;保留 run-live 作为旧 turn-based 稳定入口。
验收:run-agent-live 不带 --check-config 不再返回 FULL_DUPLEX_RUNTIME_NOT_IMPLEMENTED,而是启动持续 listening;播放中检测到有效用户说话时,在软件回声抑制通过后先停止播放,再把用户打断音频接入下一轮 STT;只有助手回放 reference 时不触发打断。
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 的精确差异
- 修改
Local microphone and speaker transport:从本机麦克风/扬声器 transport 扩展为 capture/render reference 双向音频流,播放音频必须提供给 AEC。 - 修改
VAD speech endpoint detection:从 turn-based 端点检测扩展为持续 VAD、打断检测和全双工 listening。 - 修改
Local STT transcription:从 captured segment final STT 扩展为 Streaming STT partial/stable/final。 - 修改
Cloud LLM streaming reply:从流式 LLM 输出扩展为 token-to-sentence-to-TTS response stream,并要求可取消。 - 修改
Local TTS synthesis and playback:从整段或分句播放扩展为 Streaming TTS、PCM chunk 播放和中途停止。 - 修改
Pipeline state machine:新增listening、tool_running、recovering等全双工 Agent 状态,明确speaking -> interrupted。 - 修改
Audio feedback suppression:从播放期间抑制输入扩展为 AEC + VAD + interrupt,允许用户有效打断。 - 修改
Conversation context management:保留进程内上下文,同时新增长期记忆召回的边界。 - 修改
Security and privacy:新增长期记忆、工具执行、Open Interpreter 和浏览器自动化安全要求。 - 修改
Performance targets:新增打断延迟、Streaming STT 首字、TTS 首 chunk、APM 帧处理等指标。 - 新增 requirement:
WebRTC audio processing foundation。 - 新增 requirement:
Full-duplex agent state machine。 - 新增 requirement:
Streaming STT and realtime transcript。 - 新增 requirement:
Low-latency interruption and cancellation。 - 新增 requirement:
Streaming response and TTS playback。 - 新增 requirement:
Long-term memory with FAISS and SQLite。 - 新增 requirement:
Tool Router and structured tool execution。 - 新增 requirement:
Open Interpreter external adapter。 - 新增 requirement:
Browser automation tool boundary。 - 新增 requirement:
Computer control reservation。 - 新增 requirement:
Live run-agent-live runtime。 - 新增 requirement:
Software render-reference interruption gate。
推翻重做的理由
- turn-based VAD/STT/TTS 无法自然支持“AI 讲话时用户插话”,只能不断添加补丁。
- 音色门控不能替代 AEC;没有 render reference 的系统无法稳定区分助手回放和真实用户。
- 只靠 final STT 会让用户等待过久;完整助手需要持续识别和实时字幕。
- 没有 Tool Router 和 MemoryManager 的语音助手只能聊天,不能完成 Agent 任务。
- 工具和记忆如果后补,会难以补齐安全边界;必须在 OpenSpec 阶段先定义。
实施计划
分阶段优先级顺序
- 阶段 A:OpenSpec 文档和边界冻结。
- 阶段 B:全双工音频底座和 fake APM 测试。
- 阶段 C:状态机、事件总线、取消机制和模拟端到端。
- 阶段 D:Streaming STT、VAD 打断和回声抑制测试。
- 阶段 E:Streaming LLM/TTS、句子切分和可中断播放。
- 阶段 F:长期记忆 FAISS+SQLite、记忆召回和隐私策略。
- 阶段 G:Tool Router、安全工具、Open Interpreter adapter 和 Playwright adapter。
- 阶段 H:文档、性能验收、安全审计和迁移收尾。
- 阶段 I:真实
run-agent-live运行时、软件回声抑制和真人打断验收。
里程碑估时
| 里程碑 | 乐观 | 最可能 | 悲观 |
|---|---|---|---|
| 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 天 |
| I 真实全双工运行时 | 1 天 | 2 天 | 4 天 |
总耗时预估:乐观 18.5 天,最可能 34 天,悲观 67.5 天。
数据/状态迁移策略
- 现有进程内上下文不迁移为长期记忆,避免未经确认的历史被自动保存。
- 新长期记忆库首次启动为空;用户明确要求保存或分类器确认低敏偏好后才写入。
- 现有
.env继续可用,新增变量采用默认值;全双工模式可通过 feature flag 启用。 - 现有 turn-based
run-live在迁移期保留,直到全双工验收稳定后再决定是否替换默认入口。 openinterpreter/外部仓库不纳入 Owner 迁移;只记录 adapter 配置和安全策略。
Git 提交规范
- 每完成一个大模块,必须立即执行构建或相应验证,然后执行 git commit。
- 大模块定义为 proposal “任务分解”中的主要功能组或实施计划中的里程碑阶段。
- 提交信息必须使用中文,格式为:
[模块名]:完成[具体功能描述],包含[关键变更]。 - 提交前必须保证本模块验证通过,避免任何未提交的中间状态。
- 本 OpenSpec-only 阶段完成后提交信息固定为:
[全双工Agent架构]:完成完整语音助手OpenSpec计划,包含WebRTC音频底座、长期记忆和Tool Router设计。 - 本次提交只允许包含
openspec/changes/add-full-duplex-agent-voice-assistant/下的规划文档,不得提交openinterpreter/、模型文件、依赖锁文件、.env或运行代码。