536 lines
59 KiB
Markdown
536 lines
59 KiB
Markdown
## 功能目标
|
||
|
||
### 完整业务价值
|
||
|
||
当前 `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 -> 第二轮”路径上完整正常;真人测试前缺少可重复的自动调试入口。
|
||
18. ACK 缓冲实现偏差:文档要求 `OWNER_POST_PLAYBACK_DRAIN_MS=0` 时仍应清理播放期间已积压的输入队列,但当前实现 `<=0` 直接返回;真实麦克风上可能把“我在”播放回声或旧缓冲送入正式问题 VAD/STT,模拟预排帧又不能直接用真实 flush 语义清空。
|
||
|
||
## 详细需求
|
||
|
||
### 功能需求
|
||
|
||
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,便于后续持续复现同一组模拟麦克风输入。
|
||
36. ACK/TTS 播放完成后的输入清理 SHALL 在 `OWNER_POST_PLAYBACK_DRAIN_MS=0` 时仍执行一次 `flush_input()`,清掉播放期间已进入真实输入队列的回声或旧帧,但不得再主动读取并丢弃新音频。
|
||
37. 当 ACK 文本为空且没有实际播放行为时,runtime SHALL NOT 执行 post-playback drain,避免无播放场景误清正式问题开头。
|
||
38. 模拟麦克风和 fixture replay 场景 SHALL 能保留预排的未来输入帧,并仍记录 flush 调用次数,用于验证真实队列清理动作不吞掉测试中尚未“发生”的后续用户语音。
|
||
39. 完整链路自测 SHALL 尽量使用真实本地 provider 和真实云端 LLM:真实 KWS、VAD、降噪、STT、LLM、TTS 和播放;无法自动使用真人麦克风时,允许用生成的 16 kHz PCM 音频帧替代物理麦克风输入。
|
||
40. 系统 SHALL 提供 `owner_voice_pet real-live-check` 命令,把真实 Provider 完整链路自测固化为可重复入口,而不是依赖一次性内联脚本。
|
||
41. `real-live-check` SHALL 默认生成两轮音频并执行真实本地 KWS/VAD/降噪/STT、本地 TTS、Transport 播放和云端 LLM;输出 JSON 必须包含完成轮数、final transcripts、事件计数、播放段数、flush 次数、临时上下文检查和错误列表。
|
||
42. `real-live-check --no-playback` SHALL 仍执行本地 TTS 合成和 pipeline 播放事件记录,但不实际向扬声器发声,便于自动化测试和无声环境排查。
|
||
43. `real-live-check` SHALL 输出命令级开始/结束时间、总耗时、阶段耗时、pipeline 事件时间线和按 turn/stage 聚合的响应耗时。
|
||
44. `real-live-check` SHALL 输出每次云端 LLM 请求真正发出的 `sent_at`、首个文本回复耗时和请求总耗时,且不得泄露 API key。
|
||
45. `run-live` capture 阶段 SHALL 支持实时字幕停滞端点:当已经输出过 `transcript_partial` 后,如果 `OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS` 时间内没有新的实时字幕文本输出,当前正式问题采集必须立即结束并进入 final STT。
|
||
|
||
### 非功能需求
|
||
|
||
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 文件存在。
|
||
15. ACK 禁用:当 `OWNER_WAKE_ACK_TEXT=` 时,系统不得因为“播放后清理”逻辑而清空唤醒后的正式问题帧。
|
||
16. 模拟预排帧:当 `MemoryAudioTransport` 用于模拟完整 turn 序列时,flush 调用不得把未来 turn 的预排帧删除,否则模拟验收会掩盖真实 pipeline 顺序。
|
||
|
||
### 输入输出规格
|
||
|
||
新增 `.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。
|
||
20. `OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS=1500`:已有实时字幕后,若 1.5 秒没有新的文字输出,则结束本轮录音;设置为 `0` 可关闭该端点。
|
||
|
||
终端输出:
|
||
|
||
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 的文本直接忽略;若新文本比上一次已显示文本短且不构成稳定前缀推进,也忽略,避免把瞬时噪声显示给用户。
|
||
13. 零时长播放后清理:`_drain_input_after_playback()` 先执行一次 `transport.flush_input()`;若 `post_playback_drain_ms <= 0` 立即返回;若配置为正数,才按用户显式配置继续 read/drop drain window 并最终再次 flush。
|
||
14. 模拟 flush 隔离:`MemoryAudioTransport` 默认保持 flush 清空语义;模拟 live pipeline 显式设置 `flush_clears_input=False`,只记录 flush 调用,不删除预排帧,避免将真实时间队列与离线 fixture 序列混淆。
|
||
15. 实时字幕停滞端点:CaptureStage 记录最后一次成功输出 `transcript_partial` 的音频时间戳;后续已开始录音但没有新的 partial 文本推进时,若当前帧时间戳与最后 partial 输出时间差达到 `OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS`,调用 recorder `finish("partial_transcript_idle")` 构建当前片段并发出 `speech_ended`。
|
||
|
||
### 数据库/状态管理变更
|
||
|
||
无数据库变更。新增 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 覆盖 |
|
||
| `0ms` drain 不执行 flush 导致播放回声污染正式问题 | 中 | 高 | `_drain_input_after_playback()` 无论 drain window 是否为 0 都先 flush 一次真实输入队列;新增回归测试验证 flush 发生且首句不丢 |
|
||
|
||
## 任务分解
|
||
|
||
### 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 CTC,partial 不显示单字噪声和短暂跳变;测试要点: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 分钟。
|
||
|
||
### 12. 真实完整链路自测与 ACK 缓冲修正
|
||
|
||
- [ ] 12.1 更新 OpenSpec 描述真实完整链路自测和 ACK 零时长 flush 偏差;前置条件:模拟验收已提交且本机模型齐全;验收标准:proposal/design/spec/tasks/spec delta 覆盖真实 provider 验收、0ms flush、模拟预排帧隔离和 ACK 禁用边缘案例;测试要点:OpenSpec strict;优先级:P0;预计:35 分钟。
|
||
- [ ] 12.2 修正 `VoiceAssistantPipeline` 和兼容 `LiveVoiceRuntime` 播放后输入清理;前置条件:12.1 完成;验收标准:ACK/TTS 播放后即使 `OWNER_POST_PLAYBACK_DRAIN_MS=0` 也执行一次 flush,ACK 文本为空时不执行 post-playback drain;测试要点:零时长 flush 单测、ACK 禁用不清空单测;优先级:P0;预计:45 分钟。
|
||
- [ ] 12.3 调整 `MemoryAudioTransport` 和模拟验收;前置条件:12.2 完成;验收标准:默认 flush 仍清空输入,模拟 live 显式保留预排帧并记录 flush 调用次数;测试要点:transport flush 双语义测试、两轮 simulated live 回归;优先级:P0;预计:35 分钟。
|
||
- [ ] 12.4 执行真实 provider 脚本化闭环验收;前置条件:本地模型、`.env` LLM key、macOS TTS/播放可用;验收标准:生成 wake/question 音频帧驱动真实 KWS、VAD、denoiser、本地 STT、云端 LLM、本地 TTS、播放,两轮上下文可引用第一轮历史;测试要点:输出不泄露 key,模型文件不进 Git;优先级:P0;预计:60 分钟。
|
||
- [ ] 12.5 执行全量门禁并提交“真实链路自测”模块;前置条件:12.1 至 12.4 完成;验收标准:compileall、unittest、simulate-live、security-check、model-check、device-check、OpenSpec strict、git diff check 全通过后中文 commit;优先级:P0;预计:45 分钟。
|
||
|
||
### 13. 固化真实 Provider 完整流程 CLI
|
||
|
||
- [ ] 13.1 更新 OpenSpec 和 README 描述 `real-live-check`;前置条件:12 组发现临时脚本证据不可重复;验收标准:proposal/design/spec/tasks/README 明确命令用途、默认真实播放、`--no-playback` 和 JSON 检查项;测试要点:OpenSpec strict;优先级:P0;预计:35 分钟。
|
||
- [ ] 13.2 实现 `owner_voice_pet real-live-check`;前置条件:13.1 完成;验收标准:命令从 `.env` 读取配置,生成 wake/question PCM,运行真实 KWS/VAD/denoise/STT/LLM/TTS/pipeline/playback,并输出结构化 JSON;测试要点:CLI wiring 测试、transport 单测;优先级:P0;预计:60 分钟。
|
||
- [ ] 13.3 执行真实命令验收和全量门禁;前置条件:13.2 完成;验收标准:`real-live-check --turns 2`、compileall、unittest、simulate-live、security-check、model-check、device-check、OpenSpec strict、git diff check 通过;优先级:P0;预计:60 分钟。
|
||
- [ ] 13.4 提交“真实流程命令”模块;前置条件:13.3 通过;验收标准:中文 commit 信息为 `[真实流程命令]:完成完整链路自测入口,包含真实Provider CLI、播放验收和文档测试`,提交后 `git status --short` 为空;优先级:P0;预计:10 分钟。
|
||
|
||
### 14. 真实流程命令计时输出
|
||
|
||
- [ ] 14.1 更新 OpenSpec 和 README 描述真实流程计时字段;前置条件:`real-live-check` 已可运行;验收标准:proposal/design/spec/tasks/README 明确 `started_at`、`finished_at`、`duration_ms`、`stage_timings`、`llm_request_timings.sent_at`;测试要点:OpenSpec strict;优先级:P0;预计:30 分钟。
|
||
- [ ] 14.2 实现命令级和事件级计时;前置条件:14.1 完成;验收标准:`real-live-check` JSON 包含命令总耗时、生成音频/build/pipeline phase 耗时、每轮 wake/ACK/capture/STT/LLM/TTS/turn_total 耗时;测试要点:stage timing 单测;优先级:P0;预计:45 分钟。
|
||
- [ ] 14.3 实现 LLM 请求发送时间和响应耗时;前置条件:14.2 完成;验收标准:每次 LLM 调用记录 `sent_at`、`first_delta_ms`、`duration_ms`、最后 user 预览,且不包含 key;测试要点:RecordingLlmProvider 单测;优先级:P0;预计:35 分钟。
|
||
- [ ] 14.4 执行真实命令验收和全量门禁并提交;前置条件:14.1 至 14.3 完成;验收标准:`real-live-check --turns 2 --no-playback` 和默认播放版本均输出 timing 且成功,全量门禁通过后中文提交;优先级:P0;预计:60 分钟。
|
||
|
||
### 15. 实时字幕停滞端点
|
||
|
||
- [ ] 15.1 更新 OpenSpec 和 README 描述 1.5 秒无新实时字幕即结束录音;前置条件:真人 `run-live` 日志确认 final STT 前仍等待过久;验收标准:proposal/design/spec/tasks/README 明确 `OWNER_REALTIME_TRANSCRIPT_IDLE_TIMEOUT_MS=1500`、关闭方式和 end_reason;测试要点:OpenSpec strict;优先级:P0;预计:30 分钟。
|
||
- [ ] 15.2 实现配置与 capture 端点;前置条件:15.1 完成;验收标准:`AppConfig`、`.env.example`、`--show-config` 支持新配置,`VoiceAssistantPipeline` 在已有 partial 后 1500 ms 无新文本时调用 recorder finish;测试要点:配置解析和 show-config 单测;优先级:P0;预计:45 分钟。
|
||
- [ ] 15.3 补充 recorder finish 和回归测试;前置条件:15.2 完成;验收标准:持续有语音但 partial 不再推进时,segment end_reason 为 `partial_transcript_idle`,final STT 仍执行且 partial 不进上下文;测试要点:live runtime 单测;优先级:P0;预计:45 分钟。
|
||
- [ ] 15.4 执行门禁并提交“实时字幕端点”模块;前置条件:15.1 至 15.3 完成;验收标准:compileall、unittest、simulate-live、security-check、OpenSpec strict、git diff check 通过后中文提交;优先级: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 写入/回放和空帧防卡死。
|
||
13. `Real provider live fixture acceptance`:新增真实 provider 脚本化闭环验收和 ACK/TTS 播放后 0ms flush 语义,确保真实本地模型、云端 LLM、本地 TTS/播放在同一 pipeline 中可连续运行。
|
||
14. `Real live check CLI`:新增 `real-live-check` 可重复命令,把完整真实 Provider 链路验收从一次性脚本升级为仓库内正式工具。
|
||
15. `Real live check timing`:新增真实流程命令计时输出,覆盖命令发起时间、LLM 请求发送时间、各阶段响应耗时和 pipeline 事件时间线。
|
||
16. `Realtime transcript idle endpoint`:新增已有实时字幕后 1.5 秒无新文字即结束录音的端点要求,避免用户已停说但 capture 继续等待 VAD/音色端点。
|
||
17. `Automatic continuous dialog decision`:新增回复后自动判断继续/待机能力,使用规则优先、LLM 小分类兜底,不确定时默认回到待机。
|
||
18. `Barge-in playback interruption`:新增播报中用户有效说话打断能力,使用 echo guard、最短语音时长和 realtime partial 共同确认,避免播放回声误触发。
|
||
|
||
### 删除项
|
||
|
||
不删除能力,但废弃“通过完整 STT/ASR transcript 搜索唤醒词”的运行路径作为默认实现。
|
||
|
||
### 推翻重做理由
|
||
|
||
旧路径把唤醒和正式语音识别耦合,已经在真实运行中表现为响应慢和文本污染。该问题不是调阈值可以解决的局部问题,必须拆分架构。
|
||
|
||
## 实施计划
|
||
|
||
1. M1:OpenSpec 修正完成并提交。
|
||
2. M2:KWS 模型下载、manifest、config、model-check 完成并提交。
|
||
3. M3:Runtime 独立 wake provider 和转写输出完成并提交。
|
||
4. M4:README、真实验收、archive 和最终提交完成。
|
||
5. M5:真人验收反馈修正唤醒提示顺序、KWS 阈值和 hybrid VAD,并在门禁通过后提交。
|
||
6. M6:Stage 化 pipeline、事件总线、TurnController、主说话人端点和文档验收分模块提交。
|
||
7. M7:低延迟端点与首句保留修正完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
|
||
8. M8:录音期间实时转写显示完成后提交,保留真人 `run-live` 验收任务,不在用户确认前归档。
|
||
9. M9:本地语音链路、高质量实时字幕和正式问题降噪完成后提交,继续保留真人 `run-live` 验收任务,不在用户确认前归档。
|
||
10. M10:模拟麦克风自动验收完成后提交,作为真人验收前的可重复自测入口;仍不归档变更,直到真实 `run-live` 行为由用户确认。
|
||
11. M11:真实 provider 脚本化闭环和 ACK 缓冲修正完成后提交;仍保留用户真人 `run-live` 体验验收,不在用户确认前归档。
|
||
12. M12:`real-live-check` 正式 CLI、文档、测试和真实命令验收完成后提交;仍保留物理麦克风 `run-live` 真人体验验收。
|
||
13. M13:`real-live-check` 计时输出完成后提交,使后续排查可以直接看到每段耗时。
|
||
14. M14:实时字幕停滞端点完成后提交,用户真实 `run-live` 若已有 partial 后 1.5 秒无新文字,应快速进入 final STT。
|
||
15. M15:自动持续对话判断与播报打断完成后提交,用户真实 `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/` 不得进入提交。
|