Files
Owner/openspec/changes/separate-wake-and-realtime-transcript/proposal.md
T

478 lines
47 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.
## 功能目标
### 完整业务价值
当前 `run-live` 已能完成真实重复语音对话,但唤醒路径仍把麦克风片段送入 STT/ASR 判断“是否包含小杰小杰”。这种做法会把一次唤醒变成一次完整语音识别请求,造成明显延迟;同时当用户连续说“小杰小杰”或环境里有回声时,唤醒片段会被混入正式用户问题,最终出现终端中“思考中:杰小杰。小杰小杰...”这类错误输入。本变更的业务价值是把“唤醒词检测”从“用户问题转写”中彻底拆开,形成独立、本地、低延迟的 wake pipeline,并让用户在终端中看到被识别出来的正式问题文本。
真人验收继续暴露出第二类问题:即使唤醒和正式 STT 已拆开,`run-live` 仍然是一段串行脚本式流程,录音端点、状态输出、错误恢复、TTS 播放和上下文推进耦合在同一个 runtime 方法里;一旦 VAD 能量兜底被背景噪声拖住,用户第一次说完后不会马上进入 STT,最终把第二次重复提问也拼进同一段音频。参考 Home Assistant Assist、Rhasspy、Wyoming、OpenVoiceOS/Mycroft 的公开架构后,本变更继续把 live runtime 重构为 stage 化语音助手 pipeline:音频输入、唤醒、应答、正式问题采集、STT、对话上下文、LLM、TTS、播放和恢复待机各自独立,通过统一事件总线向终端和后续 GUI 汇报状态。
目标用户是正在本机运行语音桌宠的使用者。用户希望桌宠像真实语音助手一样先快速识别“小杰小杰”,进入听取问题状态,再把后续问题转写显示出来并交给 LLM;用户不希望每次唤醒都等待云端 ASR,也不希望唤醒词本身污染正式对话历史。
### 目标用户场景
1. 低延迟唤醒:用户说“小杰小杰”,本地 KWS 模型在麦克风流中命中后立即输出“唤醒命中”,不调用云端 ASR 判断唤醒词。
2. 正式问题转写:用户唤醒后说“帮我记住苹果这个词”,终端必须显示“转写结果:帮我记住苹果这个词”,随后才进入“思考中”。
3. 重复对话:第一轮提问和回复完成后恢复待机,第二轮再次通过本地 KWS 唤醒,并携带本次运行进程内历史。
4. 模型诊断:`download_speech_models.py --dir models` 必须下载/准备 wake KWS、VAD、STT 所需模型;`model-check` 必须检查 wake 模型文件。
5. 故障恢复:本地 wake 模型缺失、加载失败或检测失败时,启动阶段必须清楚报错,不得静默回退到云端 ASR 唤醒。
### 量化成功指标 KPI
1. 唤醒路径零云端 ASR:自动化测试必须证明 wake 阶段不调用 `SttProvider.transcribe()`STT 只用于唤醒后的正式问题。
2. 唤醒污染清零:LLM 请求中的当前 user message 不得包含“小杰小杰”唤醒词,除非用户在正式问题里明确重复说出该词。
3. 终端可见转写:每轮正式 STT 完成后,终端必须输出一条包含用户问题文本的转写消息,且该消息出现在 LLM 请求之前。
4. 本地 wake 模型验收:`model-check --models-dir models` 必须检查 KWS tokens、encoder、decoder、joiner、keywords 文件。
5. 重复对话不回退:两轮 fake runtime 测试必须验证两次 wake、两次正式 STT、两次 LLM、两次 TTS、两次播放,并最终恢复待机。
6. 真实运行可用:在模型、设备和 `.env` 齐全时,`run-live --once` 仍可完成一轮真实语音交互。
7. Pipeline 事件顺序稳定:自动化测试必须验证成功 turn 的事件顺序至少包含 `wake_listening -> wake_detected -> ack_started -> capture_started -> speech_started -> speech_ended -> stt_started -> transcript_final -> llm_started -> tts_started -> playback_finished -> standby_resumed`
8. 主说话人端点有效:当用户只说一次“你是谁”并在后续出现背景噪声或第二次重复提问时,第一轮音色消失连续约 300 ms 后必须结束采集,不得把第二次重复提问拼进同一个 `AudioSegment`
### 预期影响
1. OpenSpec 主规范将明确 wake word detection 必须优先使用本地 KWS 模型,不再把完整 ASR 作为默认唤醒实现。
2. 模型目录将新增 wake KWS 模型和关键词文件,仍位于 ignored `models/`,不提交 Git。
3. Runtime 将新增 `wakeword` Provider 依赖,唤醒检测直接消费麦克风帧;正式问题录音和 STT 在唤醒后才开始。
4. 终端 reporter 将新增转写输出能力,用于显示正式问题文本。
5. 测试将更新旧的两轮 runtime 断言:STT 调用次数从“wake+question 每轮两次”改为“question 每轮一次”。
6. README 将说明首次准备需要下载 wake/VAD/STT 本地模型,唤醒词检测为本地模型路径。
7. Runtime 将新增 `VoiceAssistantPipeline``TurnController` 和 pipeline event bus`run-live` 通过统一 pipeline 执行,旧 `LiveVoiceRuntime` 仅作为兼容入口或测试辅助,不再承载新的 live 主流程。
8. 采集阶段将新增本轮临时主说话人端点,默认不保存声纹、不跨进程记忆、不新增长期主人注册流程。
### 对现有问题的系统性总结
1. 性能问题:旧实现每次检测唤醒都要先用 VAD 截出一段音频,再调用 STT/ASR 转文字,wake latency 被网络、模型转写和 VAD 端点共同放大。
2. 功能问题:旧实现从 wake transcript 中提取 wake word 后,可能把残留文本或重复唤醒词当成用户正文,导致 LLM 输入污染。
3. UI/UX 问题:终端只显示“思考中:xxx”,用户无法明确分辨“这是转写结果”还是“LLM 正在处理”,也看不到 STT 阶段的实际识别文本。
4. 架构问题:wake、VAD、STT 的职责边界不清晰,`LiveVoiceRuntime._wait_for_wake_and_user_text()` 同时承担唤醒识别、唤醒词剥离、问题录音和问题转写。
5. 测试问题:现有 live runtime 测试把 wake transcript 放进 `QueueSttProvider`,等于把“唤醒必须经过 STT”固化成测试事实。
6. 模型管理问题:已有 `models/` manifest 只覆盖 VAD/STT,不覆盖专用 KWS 模型和关键词表。
7. Pipeline 边界问题:live runtime 缺少 stage 级事件和 controller,终端输出、错误恢复、TTS 播放、STT 调用顺序散落在同一个类里,后续 GUI 桌宠无法复用稳定事件。
8. 端点问题:`HybridVadProvider` 用“本地 VAD 或能量阈值任一为语音”同时控制开始和结束,底噪偏高时会持续重置静音计数,导致录音结束慢。
9. 首句保留问题:真人验收显示唤醒应答后仍会感觉“第一句话没有获取到”,当前 ACK 后会执行 `flush_input -> read/drop -> flush_input`,默认额外丢弃 50 ms 麦克风输入,用户若紧跟提示开口会损失正式问题开头。
10. 实时消费问题:`SoundDeviceAudioTransport.read_frames()` 每次只返回一个队列帧,在音频回调批量积压时会增加 pipeline 对真实麦克风流的追帧成本。
11. 画像门槛问题:`PrimarySpeakerVadRecorder._profile_ready()` 把主说话人画像就绪阈值绑定到 `OWNER_VAD_MIN_DURATION_MS`,默认至少等待 250 ms 后主说话人端点才参与结束判断,短句用户会被迫等普通 VAD 静音或重复说话。
12. 实时转写问题:当前终端只在整段录音结束并完成 final STT 后显示“转写结果”,用户说话期间看不到任何文字反馈,无法判断系统是否已经听到并识别当前句子。
13. 实时字幕质量问题:真人日志中 `实时转写:家``实时转写:家确` 来自旧本地 14M streaming STT partial,最终云端 ASR 虽然较准,但用户看到的实时反馈会被单字噪声和短暂跳变污染。
14. 语音链路一致性问题:当前 `OWNER_SPEECH_PROVIDER=cloud` 时 final STT/TTS 走云端,而 partial 走本地模型;同一轮对话里 partial 和 final 来自不同模型,容易出现“实时字幕和最终转写明显冲突”的体验。
15. 降噪缺失问题:正式问题录音直接把原始麦克风帧送入 VAD、partial STT 和 final STT,背景噪声会同时影响端点、实时字幕和最终识别。
16. 本地模型落后问题:默认 STT 仍是 2023 年 14M 小模型,适合最小验收但不适合作为默认实时字幕质量基线;应升级为 sherpa-onnx 官方 2025 中文 CTC int8 模型。
17. 自测闭环问题:现有 `acceptance` 只覆盖旧单轮 pipeline,不能证明当前 `VoiceAssistantPipeline` 在“模拟麦克风 -> wake -> capture -> partial -> final -> LLM -> TTS -> standby -> 第二轮”路径上完整正常;真人测试前缺少可重复的自动调试入口。
## 详细需求
### 功能需求
1. `run-live` SHALL 使用本地 wake word Provider 检测“小杰小杰”,默认实现为 `sherpa-onnx` `KeywordSpotter` 或等价本地 KWS 模型。
2. wake 检测 SHALL 直接消费麦克风流 `AudioFrame`,不得调用云端 ASR,也不得调用正式问题 `SttProvider.transcribe()`
3. wake 命中后 SHALL 重置 VAD/录音缓冲,并进入“请说出问题/录音中”状态。
4. 正式问题的音频 SHALL 从 wake 命中后开始采集;wake 音频不得传入 LLM 上下文。
5. 正式问题 STT 完成后 SHALL 立即输出终端转写文本,例如“转写结果:帮我记住苹果这个词”。
6. LLM 请求 SHALL 只携带正式问题文本和本次运行临时历史,不携带 wake transcript 或 KWS 结果字符串。
7. `scripts/download_speech_models.py --dir models` SHALL 下载或准备 KWS 模型,生成 `models/wake/keywords.txt`
8. `model-check` SHALL 检查 wake KWS 模型关键文件和关键词文件,并尝试加载 wake provider。
9. `.env.example` SHALL 增加 wake provider 配置:`OWNER_WAKE_PROVIDER=local_kws`、wake 模型/阈值/关键词配置。
10. README SHALL 明确“唤醒使用本地模型,ASR/TTS 可按 `OWNER_SPEECH_PROVIDER` 走 cloud 或 local”。
11. `run-live` SHALL 使用统一 `VoiceAssistantPipeline` 执行 turn-based 语音助手流程,stage 之间通过明确输入输出传递,不得让 wake 音频、正式问题音频、LLM 上下文或 TTS 播放状态互相污染。
12. Pipeline SHALL 发出稳定事件:`pipeline_started``wake_listening``wake_detected``ack_started``capture_started``speech_started``speech_ended``stt_started``transcript_final``llm_started``tts_started``playback_finished``standby_resumed``stage_error`
13. 正式问题采集 SHALL 默认使用本轮临时主说话人端点:唤醒后以正式问题开头短音频建立本轮音色画像,主说话人音色连续消失达到配置时间后结束采集。
14. 主说话人端点 SHALL 不保存长期声纹、不写音频文件、不跨进程复用音色画像。
15. ACK 后输入清理 SHALL 只清除播放期间已积压的麦克风缓冲,不得在播放结束后额外读取并丢弃新的正式问题音频;默认 `OWNER_POST_PLAYBACK_DRAIN_MS` SHALL 为 `0`
16. SoundDevice 音频输入 SHALL 支持一次读取当前队列中可用的多个帧,避免 pipeline 在真实麦克风输入积压时逐帧追赶。
17. 主说话人画像就绪 SHALL 使用独立配置 `OWNER_SPEAKER_PROFILE_MIN_MS`,默认 `120` ms;该阈值不得被 `OWNER_VAD_MIN_DURATION_MS` 放大。
18. 主说话人端点在画像就绪后 SHALL 以 `OWNER_SPEAKER_ABSENT_MS` 作为主要结束条件;主说话人连续缺席达到配置值后 SHALL 结束采集,不得额外等待普通 VAD 的最小时长门槛。
19. 录音期间 SHALL 支持 partial transcript 事件;当本地 streaming STT 产生新的中间文本时,终端 SHALL 立即显示 `实时转写:<文本>`
20. partial transcript SHALL 只作为用户可见反馈,不得直接写入对话上下文;LLM 输入仍以最终 `transcript_final` 文本为准。
21.`OWNER_SPEECH_PROVIDER=cloud` 时,partial transcript SHALL 使用本地 streaming STT,避免对云端 ASR 进行高频请求。
22. 下一版默认语音链路 SHALL 改为“除 LLM 外全本地”:wake、VAD、STT、partial transcript、TTS、噪音过滤均在本机执行;LLM 仍走配置中的云端 OpenAI-compatible endpoint。
23. `.env.example` 和代码默认 SHALL 将 `OWNER_SPEECH_PROVIDER` 设为 `local`,从而默认 final STT 使用本地 sherpa-onnx 模型,TTS 使用 macOS 本地 `say/afplay` provider。
24. 模型 manifest SHALL 默认使用 sherpa-onnx 官方 2025 中文 Zipformer2 CTC int8 模型,关键文件为 `tokens.txt``model.int8.onnx`,不再以旧 14M transducer 作为默认 STT 模型。
25. 模型 manifest SHALL 新增 GTCRN/sherpa-onnx speech denoiser 模型 `denoise/gtcrn_simple.onnx``download_speech_models.py``model-check` 必须把它列为 required file。
26. Pipeline SHALL 新增 `AudioPreprocessStage`。默认 `OWNER_NOISE_FILTER_ENABLED=1` 时,唤醒后的正式问题采集必须先对音频帧降噪,再把同一份降噪后帧送入 VAD、实时 partial STT 和 final STT segment。
27. wake 阶段默认 SHALL 继续使用原始音频帧,避免 denoiser 改变 KWS 特征;仅当用户显式设置 `OWNER_WAKE_DENOISE_ENABLED=1` 时才允许对 wake 帧预处理。
28. 降噪 provider 失败 SHALL 作为结构化可恢复错误进入 `stage_error -> recovering -> standby`,不得把未经标记的半处理音频写入对话上下文。
29. partial transcript SHALL 增加稳定过滤:不得显示单个中文/英文有效字符,不得重复显示同一文本,不得把极短的瞬态跳变作为终端实时字幕输出。
30. final transcript SHALL 是唯一进入 LLM 的用户文本;降噪帧、partial 文本、denoiser metadata 和 ASR raw metadata 均不得进入 `ConversationContext`
31. 系统 SHALL 提供 `owner_voice_pet simulate-live` 命令,用模拟麦克风帧驱动当前 `VoiceAssistantPipeline`,默认完成两轮 wake-to-playback turn。
32. `simulate-live` SHALL 输出 JSON 检查项,至少包含完成轮数、失败轮数、wake/speech/STT/LLM/TTS/standby 事件计数、final transcripts、partial 噪声过滤、第二轮临时上下文和播放段数。
33. 模拟麦克风输入 SHALL 包含 wake 帧、正式问题主说话人帧、短噪声 partial、背景噪声/非主说话人帧和第二轮重复唤醒帧。
34. 模拟 transport SHALL 有边界保护:如果帧提前耗尽,命令必须结构化失败并退出,不能无限等待。
35. `simulate-live` SHALL 支持写入和回放 JSONL fixture,便于后续持续复现同一组模拟麦克风输入。
### 非功能需求
1. 性能:本地 wake 检测目标是在本地模型可用时 800 ms 内给出可见“唤醒命中”状态;wake 不受 LLM/ASR 网络延迟影响。
2. UI/UX:终端输出必须区分“待机监听唤醒”“唤醒命中”“录音中”“转写中”“转写结果”“思考中”“播放中”“恢复待机”。
3. 安全:wake/KWS 在本机执行,不上传连续麦克风流;`.env` key 和 `models/` 仍不得提交。
4. 可扩展性:wake Provider 必须是独立接口,后续可以替换为 Porcupine、CoreML、Apple Speech 或其他本地 KWS,不影响 STT/LLM/TTS。
5. 测试性:自动化测试必须能注入 fake wake provider,不依赖真实麦克风或真实 KWS 模型。
6. 架构可观测性:所有用户可见状态必须来自 pipeline event bus,终端 reporter 和后续 GUI 只消费事件,不直接嵌入 stage 逻辑。
7. 端点性能:默认配置下,主说话人音色消失后 300 ms 左右应结束采集,并进入 STT;最大录音时长仍作为兜底。
8. 实时字幕质量:本地 partial 默认不显示 1 个有效字符以内的文本,避免 `家``嗯``啊` 这类背景噪声触发可见字幕;最终字幕仍由 final STT 决定。
9. 语音隐私:除 LLM 请求文本外,正式问题原始音频、降噪音频、VAD 特征和临时音色画像均不得上传云端、不得落盘。
10. 降噪性能:GTCRN online denoiser 只能在 capture 阶段运行,目标是不明显拖慢端点;如果 denoiser 不可用,启动/模型检查必须明确失败,而不是静默回退为无降噪。
### 边缘案例
1. wake 模型缺失:`run-live` 启动前失败,错误指向 `model-check` 或下载脚本,不回退到云端 ASR。
2. wake 模型加载失败:输出结构化 `WAKE_MODEL_LOAD_FAILED`,不进入假待机。
3. 未命中唤醒词:保持待机,不调用 STT、LLM、TTS。
4. wake 命中后用户不说话:VAD no-speech timeout 后恢复待机,不调用 LLM。
5. STT 返回空文本:输出空转写/错误并恢复待机,不调用 LLM。
6. 用户正式问题中包含“小杰小杰”:只有 wake 命中后的正式录音内容可进入上下文;如果用户确实在正式问题中重复该词,允许保留。
7. 播放回声误触发:播放期间仍遵循既有音频反馈抑制要求,不能把 TTS 当作新 wake。
8. 背景噪声拖尾:用户停止说话后若仍有非主说话人或噪声,主说话人端点必须允许结束录音。
9. 音色画像不足:如果开头音频太短或能量不足,采集阶段必须回退到普通 VAD 静音端点,不能卡死。
10. 降噪模型缺失:`model-check` 必须报告 `denoise/gtcrn_simple.onnx` 缺失;`run-live` 启动时不得进入“看似可用但未降噪”的状态。
11. 降噪运行时异常:如果 GTCRN provider 在某帧处理失败,当前 turn 必须结构化报错并恢复待机;不得把可能损坏的片段送入 STT 或 LLM。
12. partial 单字误识别:本地 streaming STT 输出 `家``确``a` 等单字时,终端不得显示为 `实时转写`
13. partial 瞬态跳变:本地 streaming STT 从“你是谁”短暂跳到“加”再回到“你是谁”时,不得显示中间极短跳变。
14. CTC 模型缺旧 transducer 文件:当 manifest 类型为 `sherpa-onnx-streaming-zipformer2-ctc` 时,不应再要求 encoder/decoder/joiner 文件存在。
### 输入输出规格
新增 `.env` 输入:
1. `OWNER_WAKE_PROVIDER=local_kws`:第一版默认本地 KWS 唤醒。
2. `OWNER_WAKE_KEYWORD=小杰小杰`:唤醒词。
3. `OWNER_WAKE_KEYWORDS_FILE=models/wake/keywords.txt`KWS 关键词表路径。
4. `OWNER_WAKE_KWS_THRESHOLD=0.25`KWS 命中阈值。
5. `OWNER_WAKE_KWS_SCORE=1.0`KWS 关键词分数。
6. `OWNER_PIPELINE_MODE=live_turn_based`:第一版固定 turn-based pipeline。
7. `OWNER_ENDPOINT_MODE=primary_speaker`:默认主说话人端点;可设为 `vad` 回退普通 VAD。
8. `OWNER_SPEAKER_PROFILE_MS=600`:建立本轮临时音色画像的目标音频长度。
9. `OWNER_SPEAKER_ABSENT_MS=300`:主说话人音色连续消失多少毫秒后结束录音。
10. `OWNER_SPEAKER_SIMILARITY_THRESHOLD=0.70`:音色相似度阈值。
11. `OWNER_SPEAKER_MIN_RMS=0.012`:进入音色画像/匹配的最低能量。
12. `OWNER_CONTEXT_MODE=session_memory`:本次进程内临时上下文。
13. `OWNER_POST_PLAYBACK_DRAIN_MS=0`:ACK 或 TTS 播放完成后只 flush 已积压输入,不额外读取并丢弃新音频。
14. `OWNER_SPEAKER_PROFILE_MIN_MS=120`:主说话人画像参与端点判断的最低有效语音长度。
15. `OWNER_REALTIME_TRANSCRIPT_ENABLED=1`:启用录音期间本地 streaming STT 中间结果显示。
16. `OWNER_SPEECH_PROVIDER=local`:默认本地 STT/TTS,云端只保留 LLM。
17. `OWNER_NOISE_FILTER_ENABLED=1`:正式问题阶段默认启用本地降噪。
18. `OWNER_WAKE_DENOISE_ENABLED=0`wake 阶段默认不启用降噪。
19. `OWNER_NOISE_FILTER_PROVIDER=sherpa_onnx_gtcrn`:第一版本地降噪 provider。
终端输出:
1. 待机:`[第N轮] 待机:等待唤醒词“小杰小杰”`
2. 唤醒:`[第N轮] 唤醒命中:请说出问题`
3. 转写中:`[第N轮] 转写中:正在识别问题`
4. 转写结果:`[第N轮] 转写结果:<正式问题文本>`
5. 思考:`[第N轮] 思考中:正在生成回复`
### 数据验证规则
1. wake provider 只允许 `local_kws` 和测试注入 provider;无效配置启动失败。
2. wake keywords 文件必须存在且非空。
3. KWS 模型 tokens、encoder、decoder、joiner 文件必须存在。
4. 终端转写输出不得包含 API key 或 Authorization header。
5. LLM messages 的最后一条 user content 必须等于正式 STT 文本。
## 设计方案
### 文字版全新架构图
```text
SoundDeviceAudioTransport
-> PipelineEventBus
-> LocalWakeWordProvider(sherpa-onnx KeywordSpotter, models/wake, keywords.txt)
-> wake_hit
-> AcknowledgeStage("我在")
-> CaptureStage(raw user utterance only)
-> AudioPreprocessStage(sherpa-onnx GTCRN denoise, capture only)
-> PrimarySpeakerEndpoint + VAD(denoised frames)
-> RealtimeSttProvider(local CTC, denoised frames, stable partial filter)
-> SttProvider(local CTC by default, denoised final segment)
-> PipelineEvent(transcript_final)
-> ConversationContext(temporary process history)
-> Cloud LLM
-> Local TTS(MacSayTtsProvider)
-> Speaker
-> standby
```
### 数据流
1. 启动时加载 `.env`、wake KWS 模型、VAD、正式 STT、LLM、TTS。
2. 待机阶段持续读取麦克风帧,调用 `wakeword.detect(frame)`
3. KWS 返回 wake event 后输出“唤醒命中”,重置 VAD 和输入缓冲。
4. 进入正式问题录音,VAD 判断用户问题起止。
5. 正式问题结束后调用 STT。
6. STT 成功后调用 `reporter.transcript(user_text, final=True)`,终端立即显示转写结果。
7. Runtime 将 `user_text` 追加到临时上下文并调用 LLM。
8. TTS 播放后追加 assistant 历史并恢复待机。
9. 所有 stage 同步发出 pipeline events;终端 reporter 只把事件映射为中文文案。
10. 正式问题 capture 收到原始麦克风帧后,先调用 `AudioPreprocessor.process_frame(frame)` 得到降噪帧。
11. VAD、主说话人端点、partial streaming STT 和 final STT segment builder 必须使用同一份降噪帧,保证用户看到的 realtime partial 和最终转写来自一致音频来源。
12. wake listening 默认使用原始帧;后续仅在 `OWNER_WAKE_DENOISE_ENABLED=1` 时把同一预处理接口接到 wake 阶段。
### 接口定义
```text
WakeWordProvider.load() -> None
WakeWordProvider.detect(frame: AudioFrame) -> WakeEvent | None
WakeWordProvider.reset() -> None
```
```text
RuntimeReporter.transcript(text: str, final: bool, turn_id: int | None = None) -> None
```
```text
PipelineEvent(type: str, turn_id: int | None, state: PipelineState | None, message: str, payload: dict)
PipelineEventBus.emit(event_type: str, *, turn_id: int | None, state: PipelineState | None, message: str, payload: dict | None) -> PipelineEvent
PipelineEventBus.subscribe(listener: Callable[[PipelineEvent], None]) -> None
```
```text
TurnController.run_turn(turn_id: int) -> TurnResult
VoiceAssistantPipeline.run(once: bool = False, max_turns: int | None = None) -> RuntimeSummary
```
```text
AudioPreprocessor.load() -> None
AudioPreprocessor.reset() -> None
AudioPreprocessor.process_frame(frame: AudioFrame) -> AudioFrame
AudioPreprocessor.flush() -> list[AudioFrame]
```
```text
SherpaOnnxDenoiserPreprocessor(
models_dir: Path,
provider: "sherpa_onnx_gtcrn",
enabled: bool,
sherpa_module: object | None = None,
)
```
```text
SherpaOnnxSttProvider.load()
manifest type "sherpa-onnx-streaming-transducer" -> OnlineRecognizer.from_transducer(...)
manifest type "sherpa-onnx-streaming-zipformer2-ctc" -> OnlineRecognizer.from_zipformer2_ctc(tokens, model, ...)
```
```text
SherpaOnnxKeywordWakeWordProvider(
models_dir: Path,
keyword: str,
keywords_file: Path,
threshold: float,
score: float,
)
```
### 状态机变更
```text
standby
-> local_wake_listening
-> wake_hit
-> acknowledging
-> recording_user_utterance
-> transcribing_user_utterance
-> transcript_visible
-> thinking
-> speaking
-> standby
```
### 关键算法
1. KWS 检测:把每个 16 kHz int16 frame 转为 float32,送入 `sherpa_onnx.KeywordSpotter` stream;当 `get_result()` 返回非空关键词时立即 reset stream 并返回 `WakeEvent`
2. 关键词文件生成:下载模型后写入默认关键词表,默认内容为 `x iǎo j ié x iǎo j ié @小杰小杰`,并允许用户修改。
3. 唤醒隔离:wake 命中后清理 VAD 状态,正式问题只从后续 frames 构建 `AudioSegment`
4. 转写显示:STT 成功后先 reporter 输出,再追加上下文,再 LLM。
5. 主说话人端点:从正式问题开头的有效语音帧提取 RMS、过零率、谱质心、谱带宽、谱滚降、谱平坦度和频带能量比例,构建本轮临时画像;后续帧相似度低于阈值且连续达到 `OWNER_SPEAKER_ABSENT_MS` 后结束采集。
6. ACK 后首句保留:播放“我在”期间允许输入队列积压,播放完成后只执行一次队列 flush 清掉播放回声,不再额外读取 `post_playback_drain_ms` 毫秒并丢弃,默认值改为 0。
7. 批量读帧:真实 SoundDevice 输入在拿到首帧后立即 drain 当前队列中所有可用帧并返回给 pipeline,使 wake、capture 和 VAD 能在同一个循环内处理积压帧。
8. 快速主说话人结束:画像就绪最低语音长度由 `OWNER_SPEAKER_PROFILE_MIN_MS` 控制,默认 120 ms;一旦画像就绪,主说话人缺席计时达到 `OWNER_SPEAKER_ABSENT_MS` 即结束,不再叠加 `OWNER_VAD_MIN_DURATION_MS`
9. 实时转写显示:CaptureStage 在 `speech_started` 后把已录入的帧同时送入本地 streaming STT session;每当 partial 文本变化时发出 `transcript_partial`,终端显示 `实时转写:<文本>`;最终段落仍交给 configured STT provider 生成 `transcript_final`
10. 捕获阶段降噪:`AudioPreprocessStage` 将 int16 PCM 转为 float32,调用 `sherpa_onnx.OnlineSpeechDenoiser.run(samples, sample_rate)`,把返回的 `DenoisedAudio.samples` 转回 int16 PCM,并在 metadata 中加入 `denoised=True``noise_filter_provider=sherpa_onnx_gtcrn`
11. CTC STT 加载:manifest `stt.type=sherpa-onnx-streaming-zipformer2-ctc` 时,只校验 `tokens``model`,调用 `OnlineRecognizer.from_zipformer2_ctc`;旧 manifest 仍兼容 transducer 路径。
12. partial 稳定过滤:实时字幕会先去除空白和标点,计算有效字符数;有效字符数小于 2 的文本直接忽略;若新文本比上一次已显示文本短且不构成稳定前缀推进,也忽略,避免把瞬时噪声显示给用户。
### 数据库/状态管理变更
无数据库变更。新增 wake provider 内部 stream 状态,Runtime 创建时加载,`reset()` 在命中或恢复待机时重置。临时对话历史仍只保存在当前进程内。
### UI 组件重构方案
第一版仍是终端 UI。重构点是将“思考中:<用户文本>”调整为两条语义明确的输出:
1. `转写结果:<用户文本>` 表示 STT 输出。
2. `思考中:正在生成回复` 表示 LLM 阶段。
### 依赖影响分析
1. 继续使用已有 `sherpa-onnx` Python 包。
2. 新增 KWS 模型约 15 MB,下载到 ignored `models/wake/`
3. 不新增 Python 运行依赖。
4. `model-check` 变严格:缺 wake 模型会失败。
5. 默认 STT 模型升级为 2025 中文 CTC int8,模型体积和加载时间高于旧 14M 小模型,但换来更稳定的本地 partial/final 一致性。
6. 新增 GTCRN denoiser 单文件模型,下载到 ignored `models/denoise/gtcrn_simple.onnx`
7. 继续使用已安装的 `sherpa-onnx` 包;不新增 Python 依赖,不接入云 ASR/TTS 作为默认路径。
## 风险与权衡
| 风险 | 概率 | 影响 | 缓解措施 |
| --- | --- | --- | --- |
| KWS 关键词拼音格式不匹配模型 | 中 | 高 | 默认写入 sherpa 示例格式;保留 keywords 文件可编辑;测试下载后用真实模型加载;README 标明关键词文件位置 |
| KWS 模型误唤醒或漏唤醒 | 中 | 中 | 暴露 threshold/score 配置;保留状态输出;后续可替换 KWS Provider |
| 唤醒应答期间用户抢说被缓冲清理吞掉 | 中 | 高 | 终端提示顺序改为“唤醒命中 -> 应答中 -> 请说出问题 -> 录音中”,用户只在应答完成后收到提问提示;默认 `OWNER_POST_PLAYBACK_DRAIN_MS=0`,不再额外读取并丢弃播放后的新音频 |
| 本地 VAD 对真实麦克风音量过保守 | 中 | 高 | 默认 VAD provider 改为 `hybrid`,本地模型判断和能量阈值兜底任一命中即认为有语音;保留 `local``energy` 可配置回退 |
| 能量兜底让录音无法及时结束 | 高 | 高 | 将能量兜底限制为“开始录音辅助”,结束录音优先使用主说话人音色消失和本地 VAD 静音 |
| ACK 后额外丢弃音频截断首句 | 高 | 高 | 默认 `OWNER_POST_PLAYBACK_DRAIN_MS=0`;播放结束后只 flush 已积压输入;新增首句保留回归测试 |
| 主说话人画像等待过久 | 高 | 中 | 新增 `OWNER_SPEAKER_PROFILE_MIN_MS=120`;画像就绪后主说话人缺席结束不再等待普通 VAD 最小时长 |
| 麦克风帧队列积压导致状态滞后 | 中 | 中 | `SoundDeviceAudioTransport.read_frames()` 批量返回已积压帧;新增批量读帧测试 |
| partial STT 误识别影响 LLM | 中 | 中 | partial 只显示给用户,不进入 `ConversationContext`;最终 LLM 输入仍以 final STT 为准 |
| 本地 streaming STT 增加 CPU 占用 | 中 | 中 | 只在 capture started 且用户语音已开始后 feed;重复 partial 文本不重复输出;提供 `OWNER_REALTIME_TRANSCRIPT_ENABLED=0` 回退 |
| 手写音色特征不等于严格声纹识别 | 中 | 中 | 明确第一版为本轮临时主说话人端点;不承诺长期主人识别;保留后续接入 speaker embedding 模型的接口空间 |
| Pipeline 重构影响现有 CLI/测试 | 中 | 高 | 保留 `run-live` 命令和兼容类名;新增事件顺序测试、两轮回归测试和错误恢复测试 |
| 本地 KWS 增加启动加载时间 | 低 | 中 | 模型约 15 MB,启动加载一次;不在每轮重复加载 |
| 正式问题和唤醒词连在同一句导致问题前半段丢失 | 中 | 中 | 第一版交互明确为先唤醒再提问;后续可做 pre-roll buffer,但不得把 wake 音频直接进 LLM |
| 终端转写不是逐字流式 | 中 | 低 | 第一版至少在 LLM 前即时显示最终 STT 文本;后续可接入本地 streaming STT partial |
| 模型下载网络失败 | 中 | 中 | 下载脚本保留重试;`model-check` 给明确缺失文件 |
| 误提交模型或 key | 低 | 高 | `.gitignore`、security-check、提交前 `git status --short` |
| 降噪模型 API 与当前 sherpa-onnx 版本不一致 | 中 | 高 | 用本机 `sherpa_onnx 1.13.3` introspection 确认 `OnlineSpeechDenoiser` 构造;新增 fake module 单测;model-check 尝试加载 provider |
| GTCRN 对 wake 特征造成误伤 | 中 | 中 | wake 默认不走降噪,仅正式问题阶段降噪;保留 `OWNER_WAKE_DENOISE_ENABLED=1` 作为显式实验开关 |
| CTC 模型比旧 14M 模型更大导致下载慢 | 中 | 中 | 仍放 `models/` 并跳过已存在文件;README 明确首次下载较久;下载脚本保留重试 |
| partial 过滤过严导致实时字幕少显示 | 中 | 低 | 过滤只影响用户可见 partial,不影响 final STT 和 LLM;后续可配置更细阈值 |
| 降噪失败后用户无法继续本轮 | 低 | 中 | 当前 turn 结构化失败并恢复待机,避免错误音频进入 LLM;下一轮可继续唤醒 |
| 模拟验收与真实麦克风仍有差异 | 中 | 中 | 明确模拟验收用于自动调试 live pipeline 状态机和事件顺序;仍保留 4.3 真人 `run-live` 验收,不在用户确认前归档 |
| 模拟帧耗尽导致测试卡死 | 中 | 高 | 使用 bounded simulated transport,空读超过阈值直接抛结构化错误并让命令返回非 0 |
| 模拟 provider 掩盖真实模型加载问题 | 中 | 中 | 模拟命令只验证 pipeline;模型加载仍由 `model-check` 真实加载 KWS/VAD/STT/denoiser 覆盖 |
## 任务分解
### 1. OpenSpec 修正与提交
- [ ] 1.1 编写 proposal/design/spec/tasks;前置条件:当前工作树干净;验收标准:文档明确本地 KWS 唤醒和终端转写;测试要点:OpenSpec strict 通过;优先级:P0;预计:50 分钟。
- [ ] 1.2 校验并提交 OpenSpec;前置条件:1.1 完成;验收标准:`openspec validate separate-wake-and-realtime-transcript --strict``openspec validate --all --strict` 通过后提交;测试要点:中文 commit;优先级:P0;预计:15 分钟。
### 2. 本地 KWS 模型与配置
- [ ] 2.1 扩展 speech model manifest,加入 KWS URL、目录、关键文件和 keywords 文件;前置条件:OpenSpec 提交;验收标准:manifest 包含 wake provider;测试要点:manifest unit test;优先级:P0;预计:45 分钟。
- [ ] 2.2 扩展下载脚本下载 KWS 模型并生成关键词文件;前置条件:2.1 完成;验收标准:`models/wake/...``models/wake/keywords.txt` 存在;测试要点:脚本幂等;优先级:P0;预计:60 分钟。
- [ ] 2.3 扩展 `.env.example` 和 AppConfig wake 配置;前置条件:配置字段确定;验收标准:默认 `OWNER_WAKE_PROVIDER=local_kws`;测试要点:配置加载测试;优先级:P0;预计:30 分钟。
- [ ] 2.4 扩展 `model-check` 检查 KWS 并尝试加载;前置条件:2.1 至 2.3;验收标准:缺文件失败、完整模型成功;测试要点:mock/临时目录测试;优先级:P0;预计:45 分钟。
- [ ] 2.5 验证并提交“本地唤醒模型”模块;前置条件:2.1 至 2.4;验收标准:compileall、相关 unittest、model-check、security-check、OpenSpec 通过;优先级:P0;预计:20 分钟。
### 3. Runtime 独立唤醒与实时转写
- [ ] 3.1 实现 `SherpaOnnxKeywordWakeWordProvider`;前置条件:KWS 路径 helper 完成;验收标准:缺模型结构化失败,fake sherpa 可检测;测试要点:unit test;优先级:P0;预计:60 分钟。
- [ ] 3.2 修改 `LiveVoiceRuntime` 使用 wake provider,不再用 STT 判断唤醒;前置条件:3.1;验收标准:wake 阶段 STT 调用次数为 0;测试要点:两轮 runtime 测试;优先级:P0;预计:60 分钟。
- [ ] 3.3 新增 `RuntimeReporter.transcript` 并在 LLM 前输出转写结果;前置条件:3.2;验收标准:终端显示转写文本;测试要点:状态顺序测试;优先级:P0;预计:40 分钟。
- [ ] 3.4 保证 wake 词不进入 LLM 当前 user message;前置条件:3.2;验收标准:LLM call 最后一条 user 不含 wake;测试要点:污染回归测试;优先级:P0;预计:30 分钟。
- [ ] 3.5 验证并提交“独立唤醒与转写显示”模块;前置条件:3.1 至 3.4;验收标准:compileall、unittest、security-check、OpenSpec 通过;优先级:P0;预计:20 分钟。
### 4. 文档、真实验收与归档
- [ ] 4.1 更新 README 运行说明;前置条件:实现完成;验收标准:说明本地 wake 模型、下载、model-check、run-live 输出;测试要点:命令可复制;优先级:P0;预计:30 分钟。
- [ ] 4.2 下载 KWS 模型并运行 `model-check`;前置条件:脚本完成;验收标准:本机模型检查通过;测试要点:输出不泄露 key;优先级:P0;预计:60 分钟。
- [ ] 4.3 执行 run-live 单轮/两轮验收;前置条件:模型、设备、.env 齐全;验收标准:唤醒命中不等待云 ASR,终端显示转写结果;优先级:P0;预计:60 分钟。
- [ ] 4.4 最终门禁、归档、提交;前置条件:全部任务完成;验收标准:compileall、unittest、security-check、model-check、device-check、OpenSpec strict、工作树干净;优先级:P0;预计:45 分钟。
### 5. 唤醒应答与快速端点修正
- [ ] 5.1 增加唤醒后本地语音应答;前置条件:本地 KWS 已可唤醒;验收标准:wake 命中后播放“我在”再进入录音;测试要点:fake runtime 播放顺序;优先级:P0;预计:45 分钟。
- [ ] 5.2 清理应答播放期间的麦克风缓冲;前置条件:5.1 完成;验收标准:应答音频不进入正式问题 VAD/STT;测试要点:transport flush 测试;优先级:P0;预计:45 分钟。
- [ ] 5.3 live 默认改用本地 `sherpa-onnx` VAD 并缩短静音端点;前置条件:模型已下载;验收标准:`.env` 可配置 VAD provider、静音结束时间和最大录音时长;测试要点:配置和 runtime 构造测试;优先级:P0;预计:45 分钟。
- [ ] 5.4 验证并提交“唤醒应答与快速端点”模块;前置条件:5.1 至 5.3 完成;验收标准:compileall、unittest、security-check、model-check、OpenSpec strict 通过后 commit;优先级:P0;预计:20 分钟。
### 6. 灵敏度与录音端点恢复
- [ ] 6.1 调整唤醒提示顺序;前置条件:真人验收暴露用户会在“我在”播放前抢说;验收标准:终端顺序为“唤醒命中 -> 应答中:我在 -> 请说出问题 -> 录音中:正在听取问题”;测试要点:runtime reporter 顺序断言;优先级:P0;预计:30 分钟。
- [ ] 6.2 降低默认 KWS 阈值;前置条件:真人反馈唤醒难触发;验收标准:默认 `OWNER_WAKE_KWS_THRESHOLD=0.15`README 说明 0.10 至 0.20 调参范围;测试要点:配置默认值测试;优先级:P0;预计:20 分钟。
- [ ] 6.3 增加 `hybrid` VAD;前置条件:真人验收出现 `VAD_TIMEOUT_NO_SPEECH`;验收标准:本地 VAD 和能量阈值任一判断为语音即可开始录音,默认 `OWNER_VAD_PROVIDER=hybrid`;测试要点:能量兜底单测;优先级:P0;预计:45 分钟。
- [ ] 6.4 验证并提交“唤醒灵敏度与端点恢复”模块;前置条件:6.1 至 6.3 完成;验收标准:compileall、unittest、security-check、model-check、device-check、OpenSpec strict 通过后 commit;优先级:P0;预计:30 分钟。
### 7. 开源语音助手式 Pipeline 重构
- [ ] 7.1 更新 OpenSpec 以描述 stage 化 pipeline、事件总线、TurnController 和主说话人端点;前置条件:公开参考已确认;验收标准:proposal/design/spec/tasks 覆盖新架构和任务;测试要点:OpenSpec strict;优先级:P0;预计:45 分钟。
- [ ] 7.2 实现 pipeline event bus 和终端事件映射;前置条件:7.1 完成;验收标准:所有 live 用户可见状态由事件产生;测试要点:事件顺序和终端文案测试;优先级:P0;预计:60 分钟。
- [ ] 7.3 实现 `TurnController``VoiceAssistantPipeline`;前置条件:7.2 完成;验收标准:`run-live` 使用统一 pipeline,成功/失败 turn 均恢复待机;测试要点:两轮 fake runtime、错误恢复、上下文回归;优先级:P0;预计:60 分钟。
- [ ] 7.4 实现本轮主说话人端点;前置条件:7.3 完成;验收标准:主说话人音色消失约 300 ms 后结束采集;测试要点:一次提问后背景噪声不拖尾、短暂停顿不断句、画像不足回退;优先级:P0;预计:60 分钟。
- [ ] 7.5 更新 README、`.env.example`、本地 `.env` 非密钥配置;前置条件:7.2 至 7.4 完成;验收标准:运行说明匹配新 pipeline;测试要点:`--show-config` 不泄露 key;优先级:P0;预计:30 分钟。
- [ ] 7.6 验证并提交“Pipeline 文档验收”模块;前置条件:7.1 至 7.5 完成;验收标准:compileall、unittest、security-check、model-check、device-check、OpenSpec strict 全通过;优先级:P0;预计:30 分钟。
### 10. 本地语音链路、高质量实时转写与噪音过滤
- [ ] 10.1 更新 OpenSpec 描述本地语音链路和降噪阶段;前置条件:真人日志确认旧本地 14M partial 会产生 `家/家确` 等噪声误识别;验收标准:proposal/design/spec/tasks 明确 wake/VAD/STT/partial/TTS/denoise 全本地、LLM 云端、正式问题阶段默认降噪;测试要点:OpenSpec strict;优先级:P0;预计:45 分钟。
- [ ] 10.2 升级 speech model manifest 和下载脚本;前置条件:确认 sherpa-onnx 官方 2025 中文 CTC int8 模型和 GTCRN denoiser URL;验收标准:`models/manifest.json` 默认 STT 指向 CTC `model.int8.onnx`,新增 `denoise/gtcrn_simple.onnx` required file;测试要点:manifest、required files、下载脚本单测/真实下载;优先级:P0;预计:60 分钟。
- [ ] 10.3 实现 `AudioPreprocessStage`;前置条件:10.2 完成;验收标准:新增可注入的 no-op 和 sherpa-onnx GTCRN online denoiser provider,正式问题帧在进入 VAD、partial STT、final STT 前共用同一份降噪后音频;测试要点:fake denoiser 验证 capture/STT 收到降噪后帧;优先级:P0;预计:60 分钟。
- [ ] 10.4 扩展本地 STT Provider 支持 CTC 模型和 partial 稳定过滤;前置条件:10.2 完成;验收标准:`SherpaOnnxSttProvider` 可按 manifest 加载 transducer 或 zipformer2 CTCpartial 不显示单字噪声和短暂跳变;测试要点:fake sherpa CTC、single-char partial filter、transient partial filter;优先级:P0;预计:60 分钟。
- [ ] 10.5 修改 runtime 默认本地语音链路;前置条件:10.3 至 10.4 完成;验收标准:默认 `OWNER_SPEECH_PROVIDER=local`TTS 使用 `MacSayTtsProvider`,云端只用于 LLM`--show-config` 显示噪音过滤配置且不泄露 key;测试要点:build runtime provider 类型、cloud ASR/TTS 不被调用;优先级:P0;预计:45 分钟。
- [ ] 10.6 更新 README、`.env.example` 和本地 `.env` 非密钥配置;前置条件:10.5 完成;验收标准:中文运行说明包含本地语音默认值、降噪开关、模型下载、model-check、run-live 验收;测试要点:命令可复制,`.env` 不进入提交;优先级:P0;预计:35 分钟。
- [ ] 10.7 下载/校验新本地模型并执行门禁;前置条件:10.2 至 10.6 完成;验收标准:下载脚本、compileall、unittest、security-check、model-check、device-check、OpenSpec strict、git diff check 通过或记录真实设备失败原因;测试要点:输出不泄露 key,模型文件不进 Git;优先级:P0;预计:60 分钟。
- [ ] 10.8 提交“本地语音降噪”模块;前置条件:10.7 门禁通过;验收标准:中文 commit 信息为 `[本地语音降噪]:完成本地语音链路和噪音过滤,包含降噪模型、本地ASR和实时字幕稳定策略`,提交后除本地 `.env` 非提交修改外无未提交源码/文档中间状态;优先级:P0;预计:10 分钟。
### 11. 模拟麦克风自动验收与自调试
- [ ] 11.1 更新 OpenSpec 描述模拟麦克风验收;前置条件:用户要求“自己一直测试到完整正常,自己弄模拟音频给麦克风”;验收标准:proposal/design/spec/tasks 明确新增可重复 `simulate-live`,覆盖两轮 live pipeline、实时字幕、降噪、上下文和恢复待机;测试要点:OpenSpec strict;优先级:P0;预计:35 分钟。
- [ ] 11.2 实现模拟麦克风帧生成和 bounded transport;前置条件:live pipeline 已 stage 化;验收标准:模拟输入包含 wake、正式问题、短噪声 partial、背景噪声、两轮 turn,空队列不会无限等待;测试要点:失败时命令返回非 0;优先级:P0;预计:50 分钟。
- [ ] 11.3 新增 `owner_voice_pet simulate-live` CLI;前置条件:11.2 完成;验收标准:默认两轮模拟,输出 JSON checks,可写入/回放 JSONL fixture;测试要点:CLI 单测和真实命令;优先级:P0;预计:40 分钟。
- [ ] 11.4 补充 README 和自动化测试;前置条件:11.3 完成;验收标准:README 包含模拟验收命令,测试验证两轮闭环、partial 噪声过滤、第二轮临时上下文、fixture 写入/回放;测试要点:unittest;优先级:P0;预计:35 分钟。
- [ ] 11.5 执行全量门禁并提交“模拟麦克风验收”模块;前置条件:11.1 至 11.4 完成;验收标准:`simulate-live --turns 2`、compileall、unittest、security-check、model-check、device-check、OpenSpec strict、git diff check 通过后中文 commit;优先级:P0;预计:45 分钟。
## Spec Deltas
### 新增能力
无。继续修改既有 `voice-pet-pipeline` 能力。
### 修改能力
1. `Wake word detection`:从“监听本地唤醒词”强化为“默认必须使用本地 KWS 模型,不得通过云 ASR 或正式 STT 判定唤醒”。
2. `Live terminal state reporting`:新增终端转写结果输出要求。
3. `Local speech model management`:新增 wake KWS 模型和关键词文件管理。
4. `Testability`:新增 wake/STT 分离测试和唤醒污染回归测试。
5. `Wake acknowledgement before recording`:明确“请说出问题”必须在应答播放完成后输出,避免用户抢说被清缓冲。
6. `Fast user utterance endpointing`:默认 VAD provider 改为 `hybrid`,用能量阈值兜底真实麦克风音量差异。
7. `Live assistant pipeline events`:新增 stage 化事件要求,终端和后续 GUI 必须消费事件。
8. `Primary speaker endpointing`:新增本轮临时主说话人音色消失结束录音要求。
9. `Low latency capture and first utterance preservation`:新增 ACK 后不额外丢弃正式问题、批量读帧、独立画像就绪阈值和快速主说话人端点要求。
10. `Realtime partial transcript output`:新增录音期间 partial transcript 事件、终端显示和上下文隔离要求。
11. `Local voice chain and noise filtering`:新增本地 STT/TTS 默认、GTCRN 降噪、CTC 模型、partial 稳定过滤和音频不上传要求。
12. `Simulated microphone live acceptance`:新增不依赖真人麦克风的 live pipeline 模拟验收,覆盖两轮重复对话、短噪声过滤、背景噪声端点、fixture 写入/回放和空帧防卡死。
### 删除项
不删除能力,但废弃“通过完整 STT/ASR transcript 搜索唤醒词”的运行路径作为默认实现。
### 推翻重做理由
旧路径把唤醒和正式语音识别耦合,已经在真实运行中表现为响应慢和文本污染。该问题不是调阈值可以解决的局部问题,必须拆分架构。
## 实施计划
1. M1OpenSpec 修正完成并提交。
2. M2KWS 模型下载、manifest、config、model-check 完成并提交。
3. M3Runtime 独立 wake provider 和转写输出完成并提交。
4. M4README、真实验收、archive 和最终提交完成。
5. M5:真人验收反馈修正唤醒提示顺序、KWS 阈值和 hybrid VAD,并在门禁通过后提交。
6. M6Stage 化 pipeline、事件总线、TurnController、主说话人端点和文档验收分模块提交。
7. M7:低延迟端点与首句保留修正完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
8. M8:录音期间实时转写显示完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
9. M9:本地语音链路、高质量实时字幕和正式问题降噪完成后提交,继续保留真人 `run-live` 验收任务,不在用户确认前归档。
10. M10:模拟麦克风自动验收完成后提交,作为真人验收前的可重复自测入口;仍不归档变更,直到真实 `run-live` 行为由用户确认。
估时:
1. 乐观:4 小时,KWS 模型格式一次通过。
2. 最可能:6 小时,需要调关键词格式和测试。
3. 悲观:9 小时,KWS 模型对“小杰小杰”识别效果差,需要改关键词文件或阈值。
迁移策略:
1. 现有 `.env` 继续兼容,新增 wake 配置使用默认值。
2. 现有 `models/` 保留,下载脚本增量补 wake 模型。
3. 旧测试按新职责更新,不保留 STT 唤醒路径。
## Git 提交规范
1. 每完成一个大模块必须立即 commit。
2. 提交前必须运行该模块适用验证;源码模块至少运行 compileall、unittest、security-check、OpenSpec strict。
3. 模型模块必须运行下载脚本或 `model-check`
4. 提交信息必须为中文格式:“[模块名]:完成[具体功能描述],包含[关键变更]”。
5. `.env``.venv/``models/` 不得进入提交。