[Pipeline OpenSpec]:完成开源语音助手式重构规划,包含事件总线、TurnController和主说话人端点

This commit is contained in:
mkbk
2026-06-17 21:23:57 +08:00
parent 95e4996434
commit 1ee9f8ba95
4 changed files with 130 additions and 9 deletions
@@ -4,6 +4,8 @@
当前 `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,也不希望唤醒词本身污染正式对话历史。
### 目标用户场景
@@ -22,6 +24,8 @@
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`
### 预期影响
@@ -31,6 +35,8 @@
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. 采集阶段将新增本轮临时主说话人端点,默认不保存声纹、不跨进程记忆、不新增长期主人注册流程。
### 对现有问题的系统性总结
@@ -40,6 +46,8 @@
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 或能量阈值任一为语音”同时控制开始和结束,底噪偏高时会持续重置静音计数,导致录音结束慢。
## 详细需求
@@ -55,6 +63,10 @@
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 不保存长期声纹、不写音频文件、不跨进程复用音色画像。
### 非功能需求
@@ -63,6 +75,8 @@
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;最大录音时长仍作为兜底。
### 边缘案例
@@ -73,6 +87,8 @@
5. STT 返回空文本:输出空转写/错误并恢复待机,不调用 LLM。
6. 用户正式问题中包含“小杰小杰”:只有 wake 命中后的正式录音内容可进入上下文;如果用户确实在正式问题中重复该词,允许保留。
7. 播放回声误触发:播放期间仍遵循既有音频反馈抑制要求,不能把 TTS 当作新 wake。
8. 背景噪声拖尾:用户停止说话后若仍有非主说话人或噪声,主说话人端点必须允许结束录音。
9. 音色画像不足:如果开头音频太短或能量不足,采集阶段必须回退到普通 VAD 静音端点,不能卡死。
### 输入输出规格
@@ -83,6 +99,13 @@
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`:本次进程内临时上下文。
终端输出:
@@ -106,11 +129,13 @@
```text
SoundDeviceAudioTransport
-> PipelineEventBus
-> LocalWakeWordProvider(sherpa-onnx KeywordSpotter, models/wake, keywords.txt)
-> wake_hit
-> VadRecorder(user utterance only)
-> AcknowledgeStage("我在")
-> CaptureStage(primary speaker endpoint, user utterance only)
-> SttProvider(cloud or local, configured by OWNER_SPEECH_PROVIDER)
-> RuntimeReporter.transcript("转写结果:...")
-> PipelineEvent(transcript_final)
-> ConversationContext(temporary process history)
-> Cloud LLM
-> TTS
@@ -128,6 +153,7 @@ SoundDeviceAudioTransport
6. STT 成功后调用 `reporter.transcript(user_text, final=True)`,终端立即显示转写结果。
7. Runtime 将 `user_text` 追加到临时上下文并调用 LLM。
8. TTS 播放后追加 assistant 历史并恢复待机。
9. 所有 stage 同步发出 pipeline events;终端 reporter 只把事件映射为中文文案。
### 接口定义
@@ -141,6 +167,17 @@ WakeWordProvider.reset() -> None
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,
@@ -157,6 +194,7 @@ SherpaOnnxKeywordWakeWordProvider(
standby
-> local_wake_listening
-> wake_hit
-> acknowledging
-> recording_user_utterance
-> transcribing_user_utterance
-> transcript_visible
@@ -171,6 +209,7 @@ standby
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` 后结束采集。
### 数据库/状态管理变更
@@ -198,6 +237,9 @@ standby
| KWS 模型误唤醒或漏唤醒 | 中 | 中 | 暴露 threshold/score 配置;保留状态输出;后续可替换 KWS Provider |
| 唤醒应答期间用户抢说被缓冲清理吞掉 | 中 | 高 | 终端提示顺序改为“唤醒命中 -> 应答中 -> 请说出问题 -> 录音中”,用户只在应答完成后收到提问提示;默认播放后排水从 250 ms 降为 50 ms |
| 本地 VAD 对真实麦克风音量过保守 | 中 | 高 | 默认 VAD provider 改为 `hybrid`,本地模型判断和能量阈值兜底任一命中即认为有语音;保留 `local``energy` 可配置回退 |
| 能量兜底让录音无法及时结束 | 高 | 高 | 将能量兜底限制为“开始录音辅助”,结束录音优先使用主说话人音色消失和本地 VAD 静音 |
| 手写音色特征不等于严格声纹识别 | 中 | 中 | 明确第一版为本轮临时主说话人端点;不承诺长期主人识别;保留后续接入 speaker embedding 模型的接口空间 |
| Pipeline 重构影响现有 CLI/测试 | 中 | 高 | 保留 `run-live` 命令和兼容类名;新增事件顺序测试、两轮回归测试和错误恢复测试 |
| 本地 KWS 增加启动加载时间 | 低 | 中 | 模型约 15 MB,启动加载一次;不在每轮重复加载 |
| 正式问题和唤醒词连在同一句导致问题前半段丢失 | 中 | 中 | 第一版交互明确为先唤醒再提问;后续可做 pre-roll buffer,但不得把 wake 音频直接进 LLM |
| 终端转写不是逐字流式 | 中 | 低 | 第一版至少在 LLM 前即时显示最终 STT 文本;后续可接入本地 streaming STT partial |
@@ -248,6 +290,15 @@ standby
- [ ] 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
### 新增能力
@@ -262,6 +313,8 @@ standby
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`:新增本轮临时主说话人音色消失结束录音要求。
### 删除项
@@ -278,6 +331,7 @@ standby
3. M3Runtime 独立 wake provider 和转写输出完成并提交。
4. M4README、真实验收、archive 和最终提交完成。
5. M5:真人验收反馈修正唤醒提示顺序、KWS 阈值和 hybrid VAD,并在门禁通过后提交。
6. M6Stage 化 pipeline、事件总线、TurnController、主说话人端点和文档验收分模块提交。
估时: