## 功能目标 ### 完整业务价值 当前 `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 静音或重复说话。 ## 详细需求 ### 功能需求 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 的最小时长门槛。 ### 非功能需求 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;最大录音时长仍作为兜底。 ### 边缘案例 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 静音端点,不能卡死。 ### 输入输出规格 新增 `.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`:主说话人画像参与端点判断的最低有效语音长度。 终端输出: 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(primary speaker endpoint, user utterance only) -> SttProvider(cloud or local, configured by OWNER_SPEECH_PROVIDER) -> PipelineEvent(transcript_final) -> ConversationContext(temporary process history) -> Cloud LLM -> TTS -> 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 只把事件映射为中文文案。 ### 接口定义 ```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 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`。 ### 数据库/状态管理变更 无数据库变更。新增 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 模型会失败。 ## 风险与权衡 | 风险 | 概率 | 影响 | 缓解措施 | | --- | --- | --- | --- | | KWS 关键词拼音格式不匹配模型 | 中 | 高 | 默认写入 sherpa 示例格式;保留 keywords 文件可编辑;测试下载后用真实模型加载;README 标明关键词文件位置 | | KWS 模型误唤醒或漏唤醒 | 中 | 中 | 暴露 threshold/score 配置;保留状态输出;后续可替换 KWS Provider | | 唤醒应答期间用户抢说被缓冲清理吞掉 | 中 | 高 | 终端提示顺序改为“唤醒命中 -> 应答中 -> 请说出问题 -> 录音中”,用户只在应答完成后收到提问提示;默认播放后排水从 250 ms 降为 50 ms | | 本地 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()` 批量返回已积压帧;新增批量读帧测试 | | 手写音色特征不等于严格声纹识别 | 中 | 中 | 明确第一版为本轮临时主说话人端点;不承诺长期主人识别;保留后续接入 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` | ## 任务分解 ### 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 分钟。 ## 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 后不额外丢弃正式问题、批量读帧、独立画像就绪阈值和快速主说话人端点要求。 ### 删除项 不删除能力,但废弃“通过完整 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` 验收任务,不在用户确认前归档。 估时: 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/` 不得进入提交。