[OpenSpec 修正]:完成本地唤醒与转写显示规划,包含KWS模型、终端转写和任务分解
This commit is contained in:
@@ -0,0 +1,2 @@
|
|||||||
|
schema: spec-driven
|
||||||
|
created: 2026-06-17
|
||||||
@@ -0,0 +1,120 @@
|
|||||||
|
# 独立本地唤醒与终端转写显示设计
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
真实运行日志显示,当前 `run-live` 会先截取语音段并使用 STT 判断是否包含“小杰小杰”。该实现把 wake detection 与 user utterance transcription 绑定在一起,导致唤醒慢、唤醒词污染正式问题、终端输出难以区分 STT 和 LLM 阶段。本设计把 wake word detection 升级为本地 KWS 模型路径,并把正式问题转写作为独立可见事件输出。
|
||||||
|
|
||||||
|
## Goals
|
||||||
|
|
||||||
|
1. 唤醒词“小杰小杰”由本地模型检测。
|
||||||
|
2. wake 阶段不调用云 ASR,也不调用正式 STT provider。
|
||||||
|
3. wake 命中后才开始正式问题录音和 STT。
|
||||||
|
4. 终端在 LLM 前显示正式问题转写文本。
|
||||||
|
5. 模型下载和检查覆盖 wake KWS、VAD、STT。
|
||||||
|
6. 自动化测试证明 wake/STT 分离、重复对话、上下文和错误恢复仍正常。
|
||||||
|
|
||||||
|
## Non-Goals
|
||||||
|
|
||||||
|
1. 不实现 GUI 桌宠窗口。
|
||||||
|
2. 不实现跨进程长期记忆。
|
||||||
|
3. 不在本阶段实现逐字 partial ASR 字幕;本阶段先保证正式问题 STT 完成后立即可见,且出现在 LLM 前。
|
||||||
|
4. 不把连续麦克风流上传到云端。
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```text
|
||||||
|
run-live
|
||||||
|
-> AppConfig(.env)
|
||||||
|
-> SoundDeviceAudioTransport
|
||||||
|
-> SherpaOnnxKeywordWakeWordProvider(local models/wake)
|
||||||
|
-> VadRecorder(user utterance)
|
||||||
|
-> CloudAsrSttProvider or SherpaOnnxSttProvider
|
||||||
|
-> TerminalRuntimeReporter.transcript()
|
||||||
|
-> ConversationContext
|
||||||
|
-> OpenAICompatibleLlmProvider
|
||||||
|
-> CloudTtsProvider or MacSayTtsProvider
|
||||||
|
-> speaker playback
|
||||||
|
```
|
||||||
|
|
||||||
|
## Runtime Lifecycle
|
||||||
|
|
||||||
|
1. Load config.
|
||||||
|
2. Validate `OWNER_WAKE_PROVIDER=local_kws`.
|
||||||
|
3. Load KWS model from `OWNER_SPEECH_MODELS_DIR`.
|
||||||
|
4. Load VAD, STT, LLM, TTS.
|
||||||
|
5. Open microphone stream.
|
||||||
|
6. Wait for KWS wake event by feeding frames directly into wake provider.
|
||||||
|
7. On wake hit, reset wake stream and VAD recorder.
|
||||||
|
8. Record user utterance with VAD.
|
||||||
|
9. Transcribe user utterance.
|
||||||
|
10. Emit transcript to terminal.
|
||||||
|
11. Append user text and call LLM.
|
||||||
|
12. Synthesize/play reply.
|
||||||
|
13. Append assistant reply and return to standby.
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
|
||||||
|
### `SherpaOnnxKeywordWakeWordProvider`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SherpaOnnxKeywordWakeWordProvider:
|
||||||
|
def __init__(self, models_dir, keyword, keywords_file=None, threshold=0.25, score=1.0, sherpa_module=None): ...
|
||||||
|
def load(self) -> None: ...
|
||||||
|
def detect(self, frame: AudioFrame) -> WakeEvent | None: ...
|
||||||
|
def reset(self) -> None: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### `RuntimeReporter`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class RuntimeReporter(Protocol):
|
||||||
|
def status(self, state: str, message: str, *, turn_id: int | None = None) -> None: ...
|
||||||
|
def transcript(self, text: str, *, final: bool, turn_id: int | None = None) -> None: ...
|
||||||
|
def error(self, stage: str, code: str, message: str, *, turn_id: int | None = None) -> None: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Model Files
|
||||||
|
|
||||||
|
```text
|
||||||
|
models/
|
||||||
|
manifest.json
|
||||||
|
wake/
|
||||||
|
sherpa-onnx-kws-zipformer-wenetspeech-3.3M-2024-01-01-mobile/
|
||||||
|
tokens.txt
|
||||||
|
encoder-epoch-12-avg-2-chunk-16-left-64.int8.onnx
|
||||||
|
decoder-epoch-12-avg-2-chunk-16-left-64.onnx
|
||||||
|
joiner-epoch-12-avg-2-chunk-16-left-64.int8.onnx
|
||||||
|
keywords.txt
|
||||||
|
vad/
|
||||||
|
silero_vad.onnx
|
||||||
|
stt/
|
||||||
|
sherpa-onnx-streaming-zipformer-zh-14M-2023-02-23/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
1. Missing KWS model: `WAKE_MODEL_MISSING`.
|
||||||
|
2. KWS load failure: `WAKE_MODEL_LOAD_FAILED`.
|
||||||
|
3. KWS runtime failure: `WAKE_MODEL_LOAD_FAILED` with retryable true.
|
||||||
|
4. Empty user STT: existing `STT_EMPTY_TRANSCRIPT`.
|
||||||
|
5. Invalid wake provider config: `CONFIG_MISSING_VALUE`.
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
1. Unit test fake wake provider detects wake without STT calls.
|
||||||
|
2. Unit test repeated runtime runs two turns with exactly two STT calls.
|
||||||
|
3. Unit test terminal reporter records transcript before LLM stage.
|
||||||
|
4. Unit test LLM user content excludes wake keyword.
|
||||||
|
5. Unit test KWS provider missing model raises structured error.
|
||||||
|
6. Model-check test validates wake required files.
|
||||||
|
|
||||||
|
## Migration
|
||||||
|
|
||||||
|
No database migration. Users should run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python3.11 scripts/download_speech_models.py --dir models
|
||||||
|
.venv/bin/python -m owner_voice_pet model-check --models-dir models
|
||||||
|
```
|
||||||
|
|
||||||
|
Existing `.env` remains valid because new wake keys have defaults.
|
||||||
@@ -0,0 +1,281 @@
|
|||||||
|
## 功能目标
|
||||||
|
|
||||||
|
### 完整业务价值
|
||||||
|
|
||||||
|
当前 `run-live` 已能完成真实重复语音对话,但唤醒路径仍把麦克风片段送入 STT/ASR 判断“是否包含小杰小杰”。这种做法会把一次唤醒变成一次完整语音识别请求,造成明显延迟;同时当用户连续说“小杰小杰”或环境里有回声时,唤醒片段会被混入正式用户问题,最终出现终端中“思考中:杰小杰。小杰小杰...”这类错误输入。本变更的业务价值是把“唤醒词检测”从“用户问题转写”中彻底拆开,形成独立、本地、低延迟的 wake pipeline,并让用户在终端中看到被识别出来的正式问题文本。
|
||||||
|
|
||||||
|
目标用户是正在本机运行语音桌宠的使用者。用户希望桌宠像真实语音助手一样先快速识别“小杰小杰”,进入听取问题状态,再把后续问题转写显示出来并交给 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` 仍可完成一轮真实语音交互。
|
||||||
|
|
||||||
|
### 预期影响
|
||||||
|
|
||||||
|
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 本地模型,唤醒词检测为本地模型路径。
|
||||||
|
|
||||||
|
### 对现有问题的系统性总结
|
||||||
|
|
||||||
|
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 模型和关键词表。
|
||||||
|
|
||||||
|
## 详细需求
|
||||||
|
|
||||||
|
### 功能需求
|
||||||
|
|
||||||
|
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”。
|
||||||
|
|
||||||
|
### 非功能需求
|
||||||
|
|
||||||
|
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 模型。
|
||||||
|
|
||||||
|
### 边缘案例
|
||||||
|
|
||||||
|
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。
|
||||||
|
|
||||||
|
### 输入输出规格
|
||||||
|
|
||||||
|
新增 `.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 关键词分数。
|
||||||
|
|
||||||
|
终端输出:
|
||||||
|
|
||||||
|
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
|
||||||
|
-> LocalWakeWordProvider(sherpa-onnx KeywordSpotter, models/wake, keywords.txt)
|
||||||
|
-> wake_hit
|
||||||
|
-> VadRecorder(user utterance only)
|
||||||
|
-> SttProvider(cloud or local, configured by OWNER_SPEECH_PROVIDER)
|
||||||
|
-> RuntimeReporter.transcript("转写结果:...")
|
||||||
|
-> 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 历史并恢复待机。
|
||||||
|
|
||||||
|
### 接口定义
|
||||||
|
|
||||||
|
```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
|
||||||
|
SherpaOnnxKeywordWakeWordProvider(
|
||||||
|
models_dir: Path,
|
||||||
|
keyword: str,
|
||||||
|
keywords_file: Path,
|
||||||
|
threshold: float,
|
||||||
|
score: float,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 状态机变更
|
||||||
|
|
||||||
|
```text
|
||||||
|
standby
|
||||||
|
-> local_wake_listening
|
||||||
|
-> wake_hit
|
||||||
|
-> 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。
|
||||||
|
|
||||||
|
### 数据库/状态管理变更
|
||||||
|
|
||||||
|
无数据库变更。新增 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 |
|
||||||
|
| 本地 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 分钟。
|
||||||
|
|
||||||
|
## 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 分离测试和唤醒污染回归测试。
|
||||||
|
|
||||||
|
### 删除项
|
||||||
|
|
||||||
|
不删除能力,但废弃“通过完整 STT/ASR transcript 搜索唤醒词”的运行路径作为默认实现。
|
||||||
|
|
||||||
|
### 推翻重做理由
|
||||||
|
|
||||||
|
旧路径把唤醒和正式语音识别耦合,已经在真实运行中表现为响应慢和文本污染。该问题不是调阈值可以解决的局部问题,必须拆分架构。
|
||||||
|
|
||||||
|
## 实施计划
|
||||||
|
|
||||||
|
1. M1:OpenSpec 修正完成并提交。
|
||||||
|
2. M2:KWS 模型下载、manifest、config、model-check 完成并提交。
|
||||||
|
3. M3:Runtime 独立 wake provider 和转写输出完成并提交。
|
||||||
|
4. M4:README、真实验收、archive 和最终提交完成。
|
||||||
|
|
||||||
|
估时:
|
||||||
|
|
||||||
|
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/` 不得进入提交。
|
||||||
+89
@@ -0,0 +1,89 @@
|
|||||||
|
## ADDED Requirements
|
||||||
|
|
||||||
|
### Requirement: Realtime transcript terminal output
|
||||||
|
The live runtime SHALL display the recognized user utterance text in the terminal after STT succeeds and before the LLM request is sent.
|
||||||
|
|
||||||
|
#### Scenario: User utterance is transcribed
|
||||||
|
- **WHEN** a live turn captures a user utterance and STT returns non-empty text
|
||||||
|
- **THEN** the terminal output SHALL include a transcript message containing the recognized text before the thinking/LLM status is emitted
|
||||||
|
|
||||||
|
#### Scenario: Transcript is empty
|
||||||
|
- **WHEN** STT returns empty text, punctuation-only text, or an invalid transcript
|
||||||
|
- **THEN** the runtime SHALL NOT emit a misleading transcript as valid user input and SHALL recover to standby without invoking the LLM
|
||||||
|
|
||||||
|
## MODIFIED Requirements
|
||||||
|
|
||||||
|
### Requirement: Wake word detection
|
||||||
|
The system SHALL listen locally for the Chinese wake word “小杰小杰” with a dedicated local wake word provider before accepting user speech for a conversation turn, and the default live runtime implementation SHALL use a project-local KWS model rather than cloud ASR or the formal user STT provider for wake detection.
|
||||||
|
|
||||||
|
#### Scenario: Wake word is detected
|
||||||
|
- **WHEN** the user says “小杰小杰” and the local wakeword provider returns confidence above the configured threshold
|
||||||
|
- **THEN** the pipeline SHALL transition from wake listening to speech detection without invoking the configured STT provider for the wake audio
|
||||||
|
|
||||||
|
#### Scenario: Wake word is not detected
|
||||||
|
- **WHEN** background speech or noise does not match “小杰小杰”
|
||||||
|
- **THEN** the pipeline SHALL remain in wake listening and SHALL NOT invoke STT, LLM, or TTS
|
||||||
|
|
||||||
|
#### Scenario: Wake model fails
|
||||||
|
- **WHEN** the wakeword provider cannot load or process audio
|
||||||
|
- **THEN** the system SHALL report a wakeword error and SHALL NOT crash the desktop pet process
|
||||||
|
|
||||||
|
#### Scenario: Wake audio is isolated from user utterance
|
||||||
|
- **WHEN** the local wakeword provider detects “小杰小杰”
|
||||||
|
- **THEN** the runtime SHALL reset the user utterance recorder and SHALL NOT add wake audio or wake transcript text to the LLM conversation context
|
||||||
|
|
||||||
|
### Requirement: Local speech model management
|
||||||
|
The system SHALL provide project-local speech model preparation and diagnostics for live wake/VAD/STT operation, storing downloaded model artifacts under `models/` without committing them to Git.
|
||||||
|
|
||||||
|
#### Scenario: Models are downloaded
|
||||||
|
- **WHEN** the user runs `python3.11 scripts/download_speech_models.py --dir models`
|
||||||
|
- **THEN** the script SHALL create or update a project-local model directory with the files required by the configured wake, VAD, and STT providers
|
||||||
|
|
||||||
|
#### Scenario: Model check succeeds
|
||||||
|
- **WHEN** required wake, VAD, STT dependencies and model files are available
|
||||||
|
- **THEN** `PYTHONPATH=src python3.11 -m owner_voice_pet model-check` SHALL exit successfully and report the model directory and checked providers
|
||||||
|
|
||||||
|
#### Scenario: Model check fails
|
||||||
|
- **WHEN** `sherpa-onnx` is unavailable, a wake model file is missing, a VAD/STT model file is missing, or a model cannot be loaded
|
||||||
|
- **THEN** `model-check` SHALL fail with a structured model error and SHALL NOT start live microphone listening
|
||||||
|
|
||||||
|
### Requirement: Live repeat voice runtime
|
||||||
|
The system SHALL provide a `run-live` command that performs real repeated voice conversation with local microphone input, local model wake detection, configured speech recognition and speech synthesis providers, cloud LLM reply generation, local speaker playback, and automatic return to standby.
|
||||||
|
|
||||||
|
#### Scenario: Live runtime starts in standby
|
||||||
|
- **WHEN** the user runs `PYTHONPATH=src python3.11 -m owner_voice_pet run-live`
|
||||||
|
- **THEN** the system SHALL load `.env`, validate required live dependencies, initialize local wake/audio/model providers, and enter a standby listening loop
|
||||||
|
|
||||||
|
#### Scenario: Live runtime completes two turns
|
||||||
|
- **WHEN** the user wakes the system with “小杰小杰”, asks a question, hears the reply, then wakes it again and asks another question
|
||||||
|
- **THEN** the system SHALL complete local wake, recording, STT, transcript display, LLM, TTS, playback for both turns and SHALL return to standby after each turn
|
||||||
|
|
||||||
|
#### Scenario: Once mode completes one turn
|
||||||
|
- **WHEN** the user runs `PYTHONPATH=src python3.11 -m owner_voice_pet run-live --once`
|
||||||
|
- **THEN** the system SHALL run at most one local-wake-to-playback turn and exit after the turn completes or fails with a documented live error
|
||||||
|
|
||||||
|
### Requirement: Live terminal state reporting
|
||||||
|
The live runtime SHALL emit concise Chinese terminal status messages for observable runtime states, including explicit user transcript output.
|
||||||
|
|
||||||
|
#### Scenario: Normal turn status
|
||||||
|
- **WHEN** a live turn succeeds
|
||||||
|
- **THEN** terminal output SHALL include states equivalent to standby, wake hit, recording, transcribing, transcript result, thinking, speaking, and returning to standby
|
||||||
|
|
||||||
|
#### Scenario: Recoverable error status
|
||||||
|
- **WHEN** a live turn encounters empty STT, LLM failure, TTS failure, or playback failure
|
||||||
|
- **THEN** terminal output SHALL include the failing stage and a stable error code or recoverable explanation
|
||||||
|
|
||||||
|
### Requirement: Testability
|
||||||
|
The system SHALL be designed so each stage can be tested with mock providers, file-based audio fixtures, and fake live runtime components without requiring real devices in automated tests.
|
||||||
|
|
||||||
|
#### Scenario: Wake and STT separation is unit tested
|
||||||
|
- **WHEN** fake live providers run a turn with a wake frame and a user utterance
|
||||||
|
- **THEN** tests SHALL verify wake detection happens through the wake provider and the formal STT provider is called only for the user utterance
|
||||||
|
|
||||||
|
#### Scenario: Repeated runtime is unit tested
|
||||||
|
- **WHEN** fake live providers produce two deterministic turns
|
||||||
|
- **THEN** tests SHALL verify two wake detections, two STT calls, two LLM calls, two TTS calls, two playback calls, transcript output, and final return to standby
|
||||||
|
|
||||||
|
#### Scenario: Wake keyword does not pollute LLM input
|
||||||
|
- **WHEN** a wake frame contains “小杰小杰” and the later user utterance transcript is “第一问”
|
||||||
|
- **THEN** the current user message sent to the LLM SHALL be exactly “第一问” rather than a concatenation with the wake keyword
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
# 独立本地唤醒与终端转写显示任务
|
||||||
|
|
||||||
|
## 1. OpenSpec 修正与提交
|
||||||
|
|
||||||
|
- [x] 1.1 编写 `proposal.md`;前置条件:用户明确唤醒使用本地模型;验收标准:覆盖功能目标、详细需求、设计方案、风险与权衡、任务分解、Spec Deltas、实施计划、Git 提交规范;测试要点:人工检查不再把 ASR 唤醒作为默认方案;优先级:P0;预计:50 分钟。
|
||||||
|
- [x] 1.2 编写 `design.md`;前置条件:proposal 完成;验收标准:明确本地 KWS 模型、Runtime 生命周期、接口、错误处理和测试策略;测试要点:设计可映射到代码任务;优先级:P0;预计:35 分钟。
|
||||||
|
- [x] 1.3 编写 `specs/voice-pet-pipeline/spec.md` delta;前置条件:主 spec 已读;验收标准:修改 Wake word、模型管理、live runtime、终端状态和 testability 要求;测试要点:OpenSpec strict 通过;优先级:P0;预计:35 分钟。
|
||||||
|
- [x] 1.4 编写 `tasks.md` 原子任务;前置条件:design/spec 完成;验收标准:每项不超过 1 小时,包含前置条件、优先级、验收标准、测试要点;优先级:P0;预计:25 分钟。
|
||||||
|
- [x] 1.5 校验并提交 OpenSpec;前置条件:1.1 至 1.4 完成;验收标准:`openspec validate separate-wake-and-realtime-transcript --strict` 和 `openspec validate --all --strict` 通过后 commit;测试要点:中文提交信息;优先级:P0;预计:15 分钟。
|
||||||
|
|
||||||
|
## 2. 本地 KWS 模型与配置
|
||||||
|
|
||||||
|
- [ ] 2.1 扩展 `speech_models.py` manifest 和路径 helper;前置条件:OpenSpec 提交完成;验收标准:包含 KWS URL、目录、tokens、encoder、decoder、joiner、keywords;测试要点:manifest/default paths 测试;优先级:P0;预计:45 分钟。
|
||||||
|
- [ ] 2.2 扩展 `download_speech_models.py` 下载 KWS 模型并写入 keywords 文件;前置条件:2.1 完成;验收标准:脚本幂等,能补齐 `models/wake`;测试要点:真实下载或 skip existing;优先级:P0;预计:60 分钟。
|
||||||
|
- [ ] 2.3 扩展 `AppConfig` 和 `.env.example` wake 配置;前置条件:字段确定;验收标准:默认 `local_kws`;测试要点:配置加载测试;优先级:P0;预计:30 分钟。
|
||||||
|
- [ ] 2.4 扩展 `model-check` 检查 wake 模型并尝试加载;前置条件:2.1 至 2.3;验收标准:完整模型通过,缺模型结构化失败;测试要点:临时目录和真实模型;优先级:P0;预计:45 分钟。
|
||||||
|
- [ ] 2.5 验证并提交“本地唤醒模型”模块;前置条件:2.1 至 2.4 完成;验收标准:compileall、相关 unittest、security-check、model-check、OpenSpec 通过后 commit;优先级:P0;预计:20 分钟。
|
||||||
|
|
||||||
|
## 3. Runtime 独立唤醒与实时转写
|
||||||
|
|
||||||
|
- [ ] 3.1 实现 `SherpaOnnxKeywordWakeWordProvider`;前置条件:KWS 路径 helper 完成;验收标准:可加载 KWS 模型,缺文件结构化失败;测试要点:missing/fake provider 测试;优先级:P0;预计:60 分钟。
|
||||||
|
- [ ] 3.2 修改 `LiveVoiceRuntime` 注入并使用 wake provider;前置条件:3.1 完成;验收标准:wake 阶段不调用 STT,唤醒命中后录正式问题;测试要点:两轮 runtime STT 调用次数;优先级:P0;预计:60 分钟。
|
||||||
|
- [ ] 3.3 增加 `RuntimeReporter.transcript`;前置条件:3.2 完成;验收标准:转写结果在 LLM 前输出;测试要点:reporter 状态顺序;优先级:P0;预计:40 分钟。
|
||||||
|
- [ ] 3.4 增加唤醒词污染回归测试;前置条件:3.2 完成;验收标准:LLM user message 不含 wake 音频文本;测试要点:fake wake metadata;优先级:P0;预计:30 分钟。
|
||||||
|
- [ ] 3.5 验证并提交“独立唤醒与转写显示”模块;前置条件:3.1 至 3.4 完成;验收标准:compileall、unittest、security-check、OpenSpec 通过后 commit;优先级:P0;预计:20 分钟。
|
||||||
|
|
||||||
|
## 4. 文档、真实验收与归档
|
||||||
|
|
||||||
|
- [ ] 4.1 更新 README;前置条件:实现完成;验收标准:说明本地 KWS、模型下载、model-check、run-live 输出;测试要点:命令可复制;优先级:P0;预计:30 分钟。
|
||||||
|
- [ ] 4.2 下载并验收 KWS 模型;前置条件:下载脚本完成;验收标准:`python3.11 scripts/download_speech_models.py --dir models` 和 `model-check` 通过;测试要点:输出不泄露 key;优先级:P0;预计:60 分钟。
|
||||||
|
- [ ] 4.3 执行真实 run-live 验收;前置条件:模型、设备、.env 齐全;验收标准:唤醒命中使用本地模型,终端显示转写结果;测试要点:单轮或两轮状态输出;优先级:P0;预计:60 分钟。
|
||||||
|
- [ ] 4.4 最终门禁;前置条件:全部实现完成;验收标准:compileall、unittest、security-check、model-check、device-check、OpenSpec strict 全通过;优先级:P0;预计:30 分钟。
|
||||||
|
- [ ] 4.5 归档变更并提交;前置条件:4.4 通过;验收标准:主 spec 更新,archive 完成,最终 commit,`git status --short` 为空;测试要点:中文提交信息;优先级:P0;预计:20 分钟。
|
||||||
Reference in New Issue
Block a user