Files

42 KiB
Raw Permalink Blame History

补齐真实可重复对话的实时语音运行版

功能目标

完整业务价值

本变更把现有语音桌宠从“可用 mock/fixture 验收链路”升级为“可真实运行的无 GUI 实时语音程序”。完成后,用户可以在 macOS 本机启动 owner_voice_pet run-live,程序常驻监听本机麦克风,说出唤醒词“小杰小杰”后进入录音,完成 VAD 端点检测、按 .env 选择的 ASR 转写、云端 LLM 回复、按 .env 选择的 TTS 语音合成和扬声器播放,然后自动回到待机继续监听下一轮。

该能力的核心价值是让桌宠语音链路从工程骨架变成可反复使用的真实语音入口。用户不需要手动运行一次性 acceptance,也不需要手工提供文本输入;只要进程存活,就可以多轮唤醒、多轮提问、多轮播放,并且同一次运行进程内的第二轮、第三轮会携带前面 user/assistant 历史,从而支持“刚才那句话”“继续解释上一轮”这类连续对话。

目标用户场景

  1. 实时语音助手场景:用户启动 owner_voice_pet run-live 后,程序进入待机监听;用户说“小杰小杰”,随后提问,系统用扬声器播放回复;播放结束后继续等待下一次唤醒。
  2. 重复多轮对话场景:用户第一轮询问一个主题,第二轮再次唤醒并说“那换个例子”,LLM 请求必须携带本次进程内第一轮 user/assistant 历史,使模型能够理解指代。
  3. 本地模型验收场景:开发者运行模型下载脚本,把 VAD/STT 所需模型放入项目 models/model-check 能检查模型目录和 Provider 可用性。
  4. 设备验收场景:开发者运行 device-check 检查本机麦克风和扬声器;真实运行使用 sounddevice 读取麦克风,TTS 采用 macOS say/afplay 或可播放音频文件完成扬声器输出。
  5. 故障恢复场景:任一轮 STT 空文本、LLM 失败、TTS 失败、播放失败,都不能让进程退出;程序必须输出状态和错误,并恢复到待机,下一次唤醒仍可继续。

量化成功指标 KPI

  1. 运行入口可用性:PYTHONPATH=src python3.11 -m owner_voice_pet run-live --once 在模型、设备和 .env 配置齐全时可以完成一轮真实语音交互。
  2. 重复对话稳定性:run-live 默认常驻循环,连续两轮成功问答后仍回到待机监听状态;自动化测试必须模拟两轮并验证两次 STT、两次 LLM、两次 TTS、两次播放。
  3. 临时上下文正确性:同一进程内第二轮 LLM 请求必须包含第一轮 user/assistant 消息;新建 runtime/session 时上下文为空,不读取旧会话历史。
  4. 状态可观测性:每轮至少输出“待机、唤醒命中、录音中、转写中、思考中、播放中、恢复待机”这些终端状态之一或其明确对应状态。
  5. 模型与设备可诊断性:model-check 对模型缺失、依赖缺失、模型加载失败给出明确失败项;device-check 对麦克风和扬声器缺失或权限问题给出明确失败项。
  6. 音频链路真实性:真实运行必须使用本机麦克风输入和扬声器输出;不能用 acceptance fixture 替代 run-live 的默认路径。
  7. 安全边界:.env 可以存在真实 API key 但必须被 .gitignore 排除;models/ 必须被 .gitignore 排除;日志和 security check 不得泄露 key。
  8. 验证门禁:最终必须通过 compileallunittest discoversecurity-checkopenspec validate --all --strict;可选硬件验收命令若因权限或设备失败,必须输出结构化原因。

预期影响

  1. OpenSpec 主规范会从“可实现的桌宠 Pipeline 规格”进一步收紧为“完整交付必须包含真实常驻重复语音运行入口”。
  2. CLI 增加 run-livemodel-checkdevice-checkREADME 增加 .env、模型下载和真实运行说明。
  3. Runtime 增加常驻循环,成功或失败都返回待机;--once 只用于测试和单轮人工验收。
  4. Audio Transport 从占位实现升级为 sounddevice 本机麦克风输入和本机扬声器/播放器输出。
  5. ASR/TTS 从占位边界升级为可配置 Provider:OWNER_SPEECH_PROVIDER=cloud 默认使用 NewAPI mimo-v2.5-asrmimo-v2.5-ttsOWNER_SPEECH_PROVIDER=local 使用本地模型/本地播放路径。
  6. 本地模型仍下载到项目 models/,不提交 Git,并通过 model-check 验证。
  7. 对话上下文策略明确为“进程内临时历史”:同一个 run-live 进程保留多轮 user/assistant,进程退出即丢弃,不落盘。
  8. 测试套件新增重复 runtime、临时上下文、进程本地上下文隔离、模型检查、设备检查的自动化覆盖。

对现有问题的系统性总结

当前实现已经完成项目骨架、.env 读取、CLI acceptance、Provider 协议、mock 测试、资产校验和 OpenSpec archive,但距离“完整桌宠语音运行版”仍有以下问题:

  1. 没有真实常驻入口:CLI 只有 acceptancevalidate-assetssecurity-checkllm-smoke--show-config 等命令,没有 run-live
  2. 没有真实循环:VoicePipeline.run_once() 是单轮 fixture/memory 风格链路,不负责无限待机、唤醒、录音、回复、恢复待机。
  3. 音频 Transport 仍是占位:SoundDeviceAudioTransport.start_input()play_pcm() 没有真正打开麦克风或播放音频。
  4. VAD/STT 模型未落地:sherpa-onnx 相关 Provider 仍偏占位或未接真实模型目录,不能完成真实麦克风语音转写。
  5. 模型管理缺失:没有 scripts/download_speech_models.py,没有 models/ 忽略规则,没有模型检查命令。
  6. 设备诊断缺失:没有 device-check 命令帮助用户判断 sounddevice、麦克风、扬声器、权限是否可用。
  7. README 配置说明不完整:仍可能写成“API key 只从环境变量读取”,与当前用户要求的“.env 直接读取,不依赖 shell export”冲突。
  8. 上下文策略未明确服务实时运行:虽然有内存 ConversationContext,但没有测试证明同一个 live runtime 的第二轮 LLM 请求携带第一轮 user/assistant。
  9. 错误恢复缺少 live 语义:现有 pipeline 可单轮返回错误,但未证明 live loop 在 LLM/TTS/播放失败后仍恢复待机并可进入下一轮。
  10. 测试不覆盖真实重复对话:现有测试主要验证模型、配置、Provider 边界、acceptance,没有连续两轮 runtime 行为和进程本地上下文隔离。

本变更不是推翻全部已有源码,而是在既有 Provider/配置/上下文基础上补齐真实运行层。已有 mock 和 acceptance 继续保留作为快速验证路径,但不能再代表“完整”。

详细需求

功能需求

  1. CLI 必须新增 owner_voice_pet run-live 命令。
  2. run-live 默认必须进入常驻循环,直到用户中断进程或发生不可恢复启动错误。
  3. run-live --once 必须只完成一轮唤醒到播放流程,然后正常退出,方便自动化测试和人工单轮验收。
  4. run-live 启动时必须从项目 .env 文件读取 NewAPI/OpenAI base URL、API key、模型名、API style;不得要求用户先 export OWNER_*
  5. run-live 必须读取本机麦克风音频,不能默认读取 fixture 或手工文本。
  6. run-live 必须使用本机扬声器播放回复音频,TTS 第一版允许通过 macOS say 生成音频,再用 afplay 或 transport 播放。
  7. 唤醒词必须固定为“小杰小杰”;第一版可通过短语音段 STT 命中唤醒词,或通过本地 KWS Provider 命中唤醒词,但检测链路必须在本机执行。
  8. 唤醒命中后必须进入录音和 VAD 端点检测;录音结束后进入 STT。
  9. STT/ASR 必须由 .envOWNER_SPEECH_PROVIDER 决定;cloud 使用 OWNER_ASR_MODEL=mimo-v2.5-asrlocal 使用项目 models/ 下的本地模型。
  10. LLM 必须使用 .env 配置的云端 NewAPI/OpenAI 兼容接口;模型名来自配置,不能写死在核心逻辑。
  11. 每轮 LLM 请求必须包含 system prompt、本次进程内最近 user/assistant 历史和当前 user message。
  12. LLM 回复成功后必须追加 assistant 消息到同一个进程内 ConversationContext
  13. 程序退出后上下文必须丢弃,不写文件、不写数据库、不读取上次运行历史。
  14. 每轮成功播放结束后必须输出恢复待机状态,并继续监听下一次唤醒。
  15. 每轮失败后必须输出错误状态,并尽可能恢复待机;LLM 成功/失败、TTS 成功/失败、播放成功/失败都不能自动退出常驻模式。
  16. CLI 必须新增 model-check,检查 sherpa-onnx 依赖、模型目录、VAD/STT 模型关键文件和可加载性。
  17. CLI 必须新增 device-check,检查 sounddevice 可导入、输入设备、输出设备、默认采样率或设备清单。
  18. README 必须说明 .env 如何配置、模型如何下载、如何运行 run-live、如何使用 --once
  19. .gitignore 必须忽略 .venv/.envmodels/、音频临时产物和 Python 缓存。
  20. scripts/download_speech_models.py --dir models 必须下载或准备本地 VAD/STT 模型,并保持模型文件不进入 Git。

非功能需求

性能优化

  1. 麦克风输入必须使用持续流或短间隔连续读取,避免每轮重新初始化设备导致明显延迟。
  2. 音频采样率默认使用 16 kHz 单声道 PCM,必要时在 Transport 或 Provider 边界转换。
  3. VAD 帧长应控制在 20 ms 到 100 ms 范围内,保证端点检测及时。
  4. STT 模型加载必须在启动或首次使用时完成,不能每帧或每个小片段重复加载。
  5. LLM 请求必须携带截断后的上下文,按 context_max_messagescontext_max_chars 控制请求大小。
  6. TTS 播放必须使用本地命令或本机音频设备,不能阻塞下一轮状态清理;播放结束后立即恢复待机。
  7. 自动化测试必须使用 fake runtime/transport/provider,避免真实模型和音频设备拖慢 CI。

UI/UX 与终端体验

  1. 第一版明确不做 GUI 桌宠窗口;真实运行先以终端状态输出作为可观测界面。
  2. 状态文本必须中文可读,至少覆盖:待机、唤醒命中、录音中、转写中、思考中、播放中、恢复待机。
  3. 错误输出必须包含阶段和可操作原因,例如缺少模型、没有麦克风权限、缺少 API key、LLM 网络失败。
  4. run-live --once 必须适合用户验证配置,不应要求用户阅读源码或手工调用内部类。
  5. README 必须避免“只支持 acceptance”的描述,明确真实运行命令和前置条件。

安全与隐私

  1. .env 必须保持未提交;security check 必须扫描已跟踪文本文件中的疑似 API key。
  2. 模型文件必须保存在 models/ 并忽略,不把大模型提交到 Git。
  3. 原始麦克风音频默认不得持久化;若 TTS 需要临时文件,必须使用系统临时目录或可清理路径,不把用户语音写入仓库。
  4. LLM 请求只发送 STT 文本和必要上下文,不发送原始音频。
  5. 终端日志不得打印 API key、Authorization header 或 .env 原文。
  6. 进程内临时上下文不得落盘;程序重启后不得恢复历史。

可扩展性

  1. run-live 必须复用现有 Provider/协议结构,不能把 sounddevice、sherpa、LLM、TTS 全部硬编码在一个脚本中。
  2. Runtime 必须允许测试注入 fake Transport、fake STT、fake LLM、fake TTS,用于两轮重复对话测试。
  3. VAD/STT/TTS Provider 必须保持可替换;macOS say 只是第一版可播出 TTS,不是永久架构限制。
  4. 模型路径必须配置化或集中定义,后续可以替换不同 sherpa 模型目录。
  5. 上下文截断策略必须集中在 ConversationContext 或同等组件中,不能散落在 CLI 命令里。

边缘案例

  1. .env 缺少 API keyrun-live 启动前失败并提示配置项,不进入麦克风监听。
  2. .env 配置了 base URL 但模型名为空:启动前失败并提示模型配置。
  3. sounddevice 未安装:device-checkrun-live 输出缺失依赖,不抛出未捕获 ImportError。
  4. 麦克风设备不存在或权限被拒:run-live 输出 Transport 错误,并在无法启动输入流时退出;常驻循环不应空转。
  5. 扬声器设备不存在:device-check 输出失败;若播放阶段失败,当前轮进入错误恢复并回到待机或退出 --once
  6. 模型目录不存在:model-check 输出缺失路径;run-live 启动前失败,不进入假运行。
  7. 用户说唤醒词后不说内容:VAD 超时,输出恢复待机,不调用 LLM。
  8. 用户语音过长:达到最大录音时长后截断进入 STT,并输出截断原因。
  9. STT 返回空文本或纯标点:当前轮跳过 LLM/TTS,恢复待机。
  10. LLM 超时、限流、网络错误:当前轮输出思考失败,恢复待机;常驻模式继续监听下一轮。
  11. TTS say 失败或 afplay 失败:当前轮输出播放失败,恢复待机;不会保存不完整上下文为长期记忆。
  12. 第二轮“继续说”:LLM 请求必须携带第一轮 user/assistant;如果上下文超限,必须按配置只保留最近消息。
  13. 新建第二个 runtime 实例:上下文必须为空,不能继承上一个 runtime 对象或上一次进程历史。
  14. 用户按 Ctrl-C:程序应尽量关闭音频流和临时文件,然后退出,不把上下文保存到磁盘。

输入输出规格

.env 输入

  1. OWNER_LLM_BASE_URL:默认 https://token-plan-cn.xiaomimimo.com/v1,作为 OpenAI/NewAPI 兼容接口 base URL。
  2. OWNER_LLM_API_KEY:真实密钥,只允许存在于 .env 或用户本地未提交配置。
  3. OWNER_LLM_MODEL:云端模型名,通过 .env 配置指定。
  4. OWNER_LLM_API_STYLE:第一版保留 chat_completions 或现有兼容值。
  5. OWNER_CONTEXT_MAX_MESSAGES:本次进程内历史最大消息数。
  6. OWNER_CONTEXT_MAX_CHARS:本次进程内历史最大字符数。
  7. OWNER_SPEECH_PROVIDERcloudlocal,默认 cloud
  8. OWNER_ASR_MODEL:云 ASR 模型,默认 mimo-v2.5-asr
  9. OWNER_TTS_MODEL:云 TTS 模型,默认 mimo-v2.5-tts
  10. OWNER_SPEECH_MODELS_DIR:本地模型目录,默认 models/

CLI 输入

  1. owner_voice_pet run-live:进入常驻重复语音对话。
  2. owner_voice_pet run-live --once:只运行一轮。
  3. owner_voice_pet run-live --env-file .env:可选指定 .env 路径,默认项目根 .env
  4. owner_voice_pet model-check --models-dir models:检查本地模型。
  5. owner_voice_pet device-check:检查本机音频设备。

终端输出

  1. 状态输出必须包括轮次或阶段,使用户知道当前是否在等待、录音、转写、思考、播放。
  2. 错误输出必须包含稳定错误码或阶段名称。
  3. 不得输出完整 API key。

LLM 请求输出

  1. messages 必须以 system prompt 开始。
  2. 同一 runtime 内第二轮及后续轮次必须包含保留下来的最近 user/assistant 历史。
  3. 当前 user message 必须位于历史之后。
  4. 请求完成后,如果 assistant 回复非空,必须追加到内存上下文。

数据验证规则

  1. .env 文件不存在时,CLI 可以提示复制 .env.example;不得静默改用 shell 环境变量作为唯一来源。
  2. OWNER_LLM_API_KEY 为空时,LLM smoke 和 run-live 必须失败并给出配置错误。
  3. 模型目录必须存在且包含 manifest 或关键模型文件;缺失时 model-check 失败。
  4. 音频帧必须校验采样率、声道、帧数据长度;非法帧不得进入 STT。
  5. 录音片段必须大于最小时长,小于等于最大时长。
  6. STT 文本 trim 后为空或只有标点时视为无效输入。
  7. 上下文截断后仍必须保留 system prompt 和最近当前轮消息。
  8. TTS 输入为空时不得调用 say
  9. TTS 临时文件播放后应清理,或放在系统临时目录且不进入仓库。

当前实现问题与全新解决方案

当前实现的基础模块可以保留,但完整运行层必须新增:

  1. 新增 LiveVoiceRuntime 或等价 runtime 编排器,管理常驻循环、一次性模式、状态输出、错误恢复和共享 ConversationContext
  2. 扩展 SoundDeviceAudioTransport,真实打开 sounddevice 输入流,提供录音片段读取,并支持本机播放或与 macOS 播放命令协作。
  3. 新增或完善 CloudAsrSttProviderCloudTtsProviderSherpaOnnxVadProviderSherpaOnnxSttProvider,由 OWNER_SPEECH_PROVIDER 选择云端或本地语音路径。
  4. 新增模型下载脚本和检查命令,把模型资产生命周期从“人工假设”变成“可诊断前置条件”。
  5. 修改 README 和 .env.example,用 .env 作为默认配置路径,明确运行命令和本地依赖安装命令。
  6. 新增两轮 runtime 测试,证明真实运行层不是单轮 acceptance 的包装。

设计方案

文字版全新架构图

Project .env
  -> AppConfig.from_dotenv()
  -> LiveRuntimeConfig

models/
  -> model-check
  -> SherpaOnnx VAD/STT providers

macOS Microphone
  -> SoundDeviceAudioTransport input stream
  -> Wake listening window
  -> local wake detection for "小杰小杰"
  -> VAD endpoint detector
  -> recorded AudioSegment
  -> SherpaOnnx STT
  -> temporary in-process ConversationContext
  -> OpenAI/NewAPI LLM provider
  -> macOS SayTtsProvider
  -> afplay / output transport
  -> macOS Speaker
  -> return to wake listening

Terminal State Reporter
  <- idle / wake_hit / recording / transcribing / thinking / speaking / standby

Unit Tests
  -> Fake live transport + fake wake/STT/LLM/TTS
  -> repeated two-turn verification
  -> temporary context isolation verification

数据流

  1. CLI 解析 run-live--once--env-file、模型目录和设备参数。
  2. AppConfig.from_dotenv() 读取 .env,校验 LLM base URL、API key、模型名和上下文预算。
  3. Runtime 初始化模型路径、音频 Transport、VAD/STT Provider、LLM Provider、TTS Provider 和新的空 ConversationContext
  4. 程序输出“待机”,麦克风输入流持续采集音频。
  5. Runtime 检测唤醒词“小杰小杰”;命中后输出“唤醒命中”。
  6. VAD 开始收集用户 utterance,输出“录音中”;静音结束或超时后关闭当前片段。
  7. STT 输出“转写中”,将语音片段转为文本;文本无效则恢复待机。
  8. Runtime 将 user 文本追加到当前进程内上下文,构造 system + 历史 + 当前 user 的 LLM messages。
  9. LLM 输出“思考中”,生成回复;失败则记录错误并恢复待机。
  10. TTS 输出“播放中”,用 macOS 本地 TTS 合成并播放;失败则记录错误并恢复待机。
  11. 回复非空时追加 assistant 到当前进程内上下文。
  12. 输出“恢复待机”;--once 退出,默认模式继续下一轮。

接口定义

CLI

python3.11 -m owner_voice_pet run-live [--once] [--env-file PATH] [--models-dir PATH]
返回码:
  0: --once 正常完成,或用户主动中断后完成清理
  2: 配置错误,例如缺少 .env 或 API key
  3: 模型错误,例如 VAD/STT 模型缺失
  4: 设备错误,例如 sounddevice 不可用或麦克风不可用
  5: 未预期 runtime 错误
python3.11 -m owner_voice_pet model-check [--models-dir PATH]
返回:
  0: 依赖和模型可用
  3: 模型目录或模型文件缺失、sherpa-onnx 不可导入、模型不可加载
python3.11 -m owner_voice_pet device-check
返回:
  0: sounddevice 可导入且存在输入/输出设备
  4: sounddevice 不可导入、设备缺失或查询失败

LiveVoiceRuntime

LiveVoiceRuntime(
  config: AppConfig,
  transport: AudioTransport,
  wake_detector: WakeDetector,
  vad_provider: VadProvider,
  stt_provider: SttProvider,
  llm_provider: LlmProvider,
  tts_provider: TtsProvider,
  context: ConversationContext,
  reporter: RuntimeReporter,
)

run(once: bool = false) -> RuntimeSummary
run_turn(turn_id: int) -> TurnResult
shutdown() -> None

错误码:

  1. LIVE_CONFIG_INVALID
  2. LIVE_MODEL_UNAVAILABLE
  3. LIVE_AUDIO_DEVICE_UNAVAILABLE
  4. LIVE_WAKE_TIMEOUT
  5. LIVE_NO_SPEECH
  6. LIVE_STT_EMPTY
  7. LIVE_LLM_FAILED
  8. LIVE_TTS_FAILED
  9. LIVE_PLAYBACK_FAILED

SoundDeviceAudioTransport

open_input(sample_rate: int = 16000, channels: int = 1, device_id: str | None = None) -> None
read_pcm_chunk(timeout_s: float) -> bytes
record_until_endpoint(vad_provider, max_duration_s: float, silence_duration_s: float) -> AudioSegment
play_pcm(segment: AudioSegment) -> PlaybackResult
close() -> None
list_devices() -> AudioDeviceReport

SherpaOnnx STT/VAD

SherpaOnnxModelPaths(
  models_dir: Path,
  vad_model: Path,
  stt_model_dir: Path,
  tokens: Path | None,
)

SherpaOnnxSttProvider.load(paths: SherpaOnnxModelPaths) -> None
SherpaOnnxSttProvider.transcribe(segment: AudioSegment) -> Transcript
SherpaOnnxVadProvider.load(paths: SherpaOnnxModelPaths) -> None
SherpaOnnxVadProvider.analyze(frame: AudioFrame) -> VadResult

ConversationContext

append_user(text: str) -> None
append_assistant(text: str) -> None
build_messages(current_user: str | None = None) -> list[Message]
reset() -> None

约束:ConversationContext 由每个 LiveVoiceRuntime 实例独占;不得使用全局单例保存历史。

状态机

BOOT
  -> VALIDATING_CONFIG
  -> VALIDATING_MODELS
  -> VALIDATING_DEVICES
  -> STANDBY
  -> WAKE_HIT
  -> RECORDING
  -> TRANSCRIBING
  -> THINKING
  -> SPEAKING
  -> RECOVERING_TO_STANDBY
  -> STANDBY

Any turn-stage recoverable failure
  -> ERROR
  -> RECOVERING_TO_STANDBY
  -> STANDBY

Ctrl-C or --once after first completed turn
  -> SHUTTING_DOWN
  -> EXIT

关键算法

  1. 唤醒检测:第一版允许用短窗口 STT 或本地 KWS 对麦克风片段识别“小杰小杰”;命中后重置 VAD 缓冲,避免把唤醒词作为用户正文传给 LLM。
  2. VAD 端点:按固定帧读取音频,连续 speech 超过 start threshold 进入 recording;连续 silence 超过 end threshold 结束;超过 max duration 强制结束。
  3. 上下文截断:追加 user/assistant 后按 context_max_messages 保留最近消息,再按 context_max_chars 从最老普通消息裁剪;system prompt 永远保留。
  4. 错误恢复:每轮错误转换为 TurnResult;常驻模式只在启动前置条件失败或用户中断时退出。
  5. TTS 播放:say 生成临时 AIFF/WAVafplay 播放;播放失败转换为 LIVE_PLAYBACK_FAILED

数据库/状态管理变更

本变更不新增数据库,不新增持久化历史,不写长期记忆。唯一新增运行时状态是 LiveVoiceRuntime 持有的进程内 ConversationContext。该状态满足:

  1. Runtime 创建时为空。
  2. 同一进程内多轮对话共享。
  3. 进程退出时自然丢弃。
  4. 不序列化到文件。
  5. 不从上次运行加载。

UI 组件重构方案

第一版不做 GUI 桌宠窗口,因此本变更的 UI 范围是终端状态体验:

  1. 新增 RuntimeReporter 或等价输出器,将状态变化打印为中文短句。
  2. 终端输出必须一眼区分待机、唤醒、录音、转写、思考、播放、恢复。
  3. GUI 资产和 PySide fallback 保留,不在本变更扩展;后续 GUI 接入应订阅 live runtime 状态,而不是重写 pipeline。

依赖影响分析

  1. 新增运行依赖:sounddevicenumpysherpa-onnx
  2. macOS 系统依赖:sayafplay,当前系统通常自带;缺失时 model-check 或 TTS provider 应报错。
  3. 模型资产:下载到 models/,由 .gitignore 排除。
  4. 网络依赖:只有 LLM 请求和模型下载需要网络;真实运行时 STT/VAD 不上传音频。
  5. 测试依赖:自动化测试默认使用 fake provider,不强制真实设备。

性能、UI、功能三大类优化路径

性能优化路径:

  1. 使用常驻音频流代替每轮打开关闭麦克风。
  2. 预加载 STT/VAD 模型。
  3. 对话历史按消息数和字符数截断。
  4. TTS 临时文件播放后清理,避免磁盘堆积。

UI 优化路径:

  1. 第一阶段用稳定中文终端状态覆盖真实运行可见性。
  2. 后续 GUI 接入复用 runtime 状态,不让 UI 阻塞音频线程。
  3. 错误文案保持短句和可操作原因。

功能优化路径:

  1. 从单轮 run_once 增加常驻 run-live
  2. 从 mock/fixture 增加真实 sounddevice 采集和播放。
  3. 从临时单轮上下文增加进程内多轮历史。
  4. 从模型假设增加下载脚本和 model-check

风险与权衡

风险 概率 影响 缓解措施
sherpa-onnx 模型下载 URL 或模型结构变化 下载脚本使用 manifest 记录模型来源和关键文件;model-check 明确报缺失项;模型实现保持集中配置
macOS 麦克风权限导致 sounddevice 打不开输入流 device-check 先诊断;run-live 启动失败时输出权限提示;不进入假运行
本地唤醒词没有独立 KWS 模型,使用 STT 短窗口会延迟较高 第一版以真实可用为目标;保留 WakeDetector 接口,后续可换独立 KWS
VAD 误切或漏切导致录音太短/太长 设置最小时长、静音时长、最大时长;测试覆盖无语音和超长语音
LLM 网络失败影响多轮体验 当前轮失败后恢复待机;不清空上下文;终端输出阶段错误
TTS say/afplay 阻塞播放期间无法响应新唤醒 第一版明确播放期间不支持打断;播放结束后恢复待机
上下文无限增长导致 LLM 请求变慢 复用 context_max_messages/context_max_chars,新增测试验证截断
.env 误提交泄露 key .gitignore 忽略;security-check 扫描 tracked 文本;最终 grep 检查
models/ 大文件误提交 .gitignore 忽略 models/;提交前 git status --short 审计
真实设备测试在无权限环境失败 自动化测试不依赖真实设备;人工验收记录失败阶段;device-check 给出原因
新 runtime 破坏现有 acceptance 测试 保持 VoicePipeline.run_once() 行为;新增 live runtime 独立测试
在一个模块积累过多未提交修改 按 proposal 任务分解主要功能组完成后立即验证并中文 commit

任务分解

1. OpenSpec 修正与提交

  • 1.1 编写 proposal.md,完整说明真实重复语音运行版的业务目标、需求、设计、风险、任务、Spec Deltas、实施计划和 Git 提交规范;前置条件:确认 add-voice-pet-pipeline 已 archive;验收标准:文档明确当前变更是修改既有 voice-pet-pipeline 能力,不是新增能力;测试要点:人工检查不再把 acceptance 当完整;优先级:P0;预计:45 分钟。
  • 1.2 编写 design.md,细化 live runtime、sounddevice、sherpa 模型、macOS TTS、临时上下文和状态机;前置条件:proposal 完成;验收标准:接口、状态、错误码、依赖影响明确;测试要点:设计能映射到后续代码任务;优先级:P0;预计:45 分钟。
  • 1.3 编写 specs/voice-pet-pipeline/spec.md delta;前置条件:proposal 确认 modified capability;验收标准:包含 run-live、重复对话、临时上下文、模型/设备检查、错误恢复、安全隐私和性能指标 SHALL;测试要点:OpenSpec strict 校验通过;优先级:P0;预计:45 分钟。
  • 1.4 编写 tasks.md 原子任务;前置条件:design/spec 完成;验收标准:任务按阶段排序,每项不超过 1 小时,包含前置条件、优先级、验收标准、测试要点;测试要点:OpenSpec apply 能读取任务;优先级:P0;预计:45 分钟。
  • 1.5 执行 OpenSpec 校验并提交;前置条件:1.1 至 1.4 完成;验收标准:openspec validate complete-live-repeat-voice-runtime --strictopenspec validate --all --strict 通过后立即 commit;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

2. 依赖、模型和配置规划落地

  • 2.1 更新 .gitignore 忽略 models/.venv/、TTS 临时音频和 Python 缓存;前置条件:OpenSpec 提交完成;验收标准:模型和本地密钥不会出现在 git status;测试要点:git status --short 不列出模型大文件;优先级:P0;预计:20 分钟。
  • 2.2 更新 .env.example,加入模型目录、上下文预算和 live runtime 相关配置;前置条件:配置字段确认;验收标准:用户复制后可填写 key 并运行;测试要点:配置加载测试覆盖默认值;优先级:P0;预计:30 分钟。
  • 2.3 新增 scripts/download_speech_models.py --dir models;前置条件:确认模型来源;验收标准:脚本能创建模型目录、下载/解压模型、写入 manifest;测试要点:脚本参数解析和 manifest 测试通过;优先级:P0;预计:60 分钟。
  • 2.4 在项目本地 .venv 安装 sounddevicenumpysherpa-onnx;前置条件:python3.11 可用;验收标准:.venv/bin/python -c 可导入三个依赖;测试要点:不污染全局环境;优先级:P0;预计:30 分钟。
  • 2.5 下载模型到 models/ 并运行 model-check 预备验证;前置条件:下载脚本完成;验收标准:模型文件存在且未进入 Git;测试要点:git status --short 不列出模型文件;优先级:P0;预计:60 分钟。
  • 2.6 验证并提交“依赖、模型和配置”模块;前置条件:2.1 至 2.5 完成;验收标准:compileall、相关测试、security-check、OpenSpec strict 通过后 commit;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

3. 本机音频 Transport 与设备检查

  • 3.1 实现 device-check CLI;前置条件:sounddevice 依赖可导入或可诊断;验收标准:输出输入/输出设备可用性和失败原因;测试要点:mock sounddevice 成功/失败;优先级:P0;预计:45 分钟。
  • 3.2 完善 SoundDeviceAudioTransport 输入流;前置条件:现有 Transport 协议已读;验收标准:可打开麦克风并读取 16 kHz 单声道帧;测试要点:fake sounddevice 测试帧队列和关闭;优先级:P0;预计:60 分钟。
  • 3.3 完善 SoundDeviceAudioTransport 播放路径;前置条件:AudioSegment 播放格式确定;验收标准:可播放 PCM 或交给 afplay 播放临时文件;测试要点:播放成功/失败返回结构化结果;优先级:P0;预计:60 分钟。
  • 3.4 增加设备和 Transport 错误恢复测试;前置条件:3.1 至 3.3 完成;验收标准:无设备、缺依赖、播放失败均不抛未捕获异常;测试要点:unittest 覆盖;优先级:P0;预计:45 分钟。
  • 3.5 验证并提交“本机音频 Transport”模块;前置条件:3.1 至 3.4 完成;验收标准:compileall、transport tests、device-check、OpenSpec strict 通过后 commit;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

4. Wake/VAD/STT 与模型检查

  • 4.1 实现 model-check CLI;前置条件:模型 manifest 和路径约定完成;验收标准:检查依赖、目录、关键文件和 Provider 可加载性;测试要点:缺模型、缺依赖、成功三类测试;优先级:P0;预计:45 分钟。
  • 4.2 实现 SherpaOnnxVadProvider 或等价本地 VAD 适配;前置条件:模型文件可用;验收标准:可分析帧并输出 speech/silence;测试要点:fake 模型或短音频 fixture;优先级:P0;预计:60 分钟。
  • 4.3 实现 SherpaOnnxSttProvider 真实转写;前置条件:STT 模型文件可用;验收标准:可从 AudioSegment 返回中文文本;测试要点:空音频、无模型、成功 fixture;优先级:P0;预计:60 分钟。
  • 4.4 实现 live 唤醒检测策略;前置条件:STT/VAD 可用;验收标准:能从实时音频中识别“小杰小杰”并进入录音,不把唤醒词传给 LLM;测试要点:命中/未命中测试;优先级:P0;预计:60 分钟。
  • 4.5 增加 VAD 端点和 STT 文本有效性测试;前置条件:4.1 至 4.4 完成;验收标准:无语音、空文本、最大录音时长均可恢复待机;测试要点:runtime 状态断言;优先级:P0;预计:45 分钟。
  • 4.6 验证并提交“Wake/VAD/STT 与模型检查”模块;前置条件:4.1 至 4.5 完成;验收标准:compileall、wake/vad/stt tests、model-check、OpenSpec strict 通过后 commit;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

5. Live runtime、LLM/TTS 与临时上下文

  • 5.1 新增 LiveVoiceRuntime 常驻循环;前置条件:Transport、Wake/VAD/STT 可用;验收标准:默认循环、--once 单轮、Ctrl-C 清理;测试要点:fake runtime 两轮;优先级:P0;预计:60 分钟。
  • 5.2 新增 run-live CLI;前置条件:LiveVoiceRuntime 可构造;验收标准:读取 .env、构造 Provider、输出中文状态;测试要点:CLI 参数和错误码测试;优先级:P0;预计:60 分钟。
  • 5.3 接入 LLM 请求临时历史;前置条件:ConversationContext 可复用;验收标准:第二轮请求包含第一轮 user/assistant;新 runtime 上下文为空;测试要点:temporary-context 和 process-local 测试;优先级:P0;预计:60 分钟。
  • 5.4 接入 macOS say/afplay TTS 播放;前置条件:TTS Provider 已有边界;验收标准:非空回复可生成并播放音频;测试要点:mock subprocess 成功/失败;优先级:P0;预计:45 分钟。
  • 5.5 增加 live 错误恢复;前置条件:runtime 主流程完成;验收标准:STT 空文本、LLM 失败、TTS 失败、播放失败后都恢复待机;测试要点:状态序列测试;优先级:P0;预计:60 分钟。
  • 5.6 验证并提交“Live runtime 与临时上下文”模块;前置条件:5.1 至 5.5 完成;验收标准:compileall、unittest、security-check、OpenSpec strict 通过后 commit;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

6. 文档、真实验收、归档

  • 6.1 更新 README 中文运行说明;前置条件:CLI 和脚本完成;验收标准:包含 .venv.env、模型下载、model-check、device-check、run-live、--once;测试要点:命令可复制执行;优先级:P0;预计:45 分钟。
  • 6.2 执行本地模型验收;前置条件:模型已下载;验收标准:python3.11 scripts/download_speech_models.py --dir modelsmodel-check 通过或给出明确设备/模型失败证据;测试要点:输出不泄露 key;优先级:P0;预计:60 分钟。
  • 6.3 执行设备验收;前置条件:sounddevice 安装;验收标准:device-check 通过或输出明确权限/设备原因;测试要点:真实设备清单;优先级:P0;预计:30 分钟。
  • 6.4 执行真实运行验收;前置条件:.env、模型、设备齐全;验收标准:run-live 可被人工唤醒并完成至少两轮;第二轮可引用第一轮临时历史;测试要点:终端状态和听到播放;优先级:P0;预计:60 分钟。
  • 6.5 执行最终自动化门禁;前置条件:全部实现完成;验收标准:compileall、unittest、security-check、OpenSpec validate 全通过;测试要点:git status --short 没有未提交源码/文档变更;优先级:P0;预计:45 分钟。
  • 6.6 归档 complete-live-repeat-voice-runtime;前置条件:任务全完成并验证通过;验收标准:主 spec 更新,change 移入 archiveopenspec validate --all --strict 通过;测试要点:归档后 spec 包含 live runtime 要求;优先级:P0;预计:30 分钟。
  • 6.7 最终提交“文档、验收与归档”模块;前置条件:6.1 至 6.6 完成;验收标准:最终门禁通过后 commit,工作树干净;测试要点:提交信息为中文格式;优先级:P0;预计:20 分钟。

Spec Deltas

Capabilities

New Capabilities

无。本变更不新增独立能力目录,因为 voice-pet-pipeline 已由 add-voice-pet-pipeline 归档创建。

Modified Capabilities

  • voice-pet-pipeline:把“完整语音桌宠 pipeline”从单轮 acceptance/mock 验收扩展为真实 run-live 常驻重复语音运行;新增进程内临时上下文、模型下载/检查、设备检查、本机 sounddevice 输入输出、macOS 本地 TTS 播放和 live 错误恢复要求。

与现有 openspec/specs/voice-pet-pipeline/spec.md 的差异

新增项:

  1. 新增 Live repeat voice runtime 要求:run-live 默认常驻循环,--once 单轮退出。
  2. 新增 Temporary in-process conversation history 要求:同一进程携带最近历史,退出后丢弃。
  3. 新增 Local speech model management 要求:模型下载到 models/,不提交 Gitmodel-check 可诊断。
  4. 新增 Live audio device readiness 要求:device-check 和 sounddevice 本机麦克风/扬声器诊断。
  5. 新增 Live terminal state reporting 要求:输出待机、唤醒、录音、转写、思考、播放、恢复待机。
  6. 新增 Live runtime error recovery 要求:每轮失败后恢复待机,不让常驻进程退出。

修改项:

  1. Local microphone and speaker transport:从“Transport 抽象支持本机设备”强化为“run-live 默认真实使用本机设备”。
  2. Conversation context management:从“active desktop pet session 内存上下文”明确为“本次 run-live 进程内临时历史,不跨进程持久化”。
  3. Local STT transcription:从“推荐 sherpa-onnx”强化为“第一版真实 STT/VAD 模型放入 models/ 并可检查”。
  4. Local TTS synthesis and playback:从“推荐本地 TTS”调整为“第一版允许 macOS say/afplay 先保证真实播出”。
  5. Testability:从单轮 pipeline 测试增加两轮重复 runtime、上下文携带和进程隔离测试。

删除项:

无。本变更不删除已有桌宠 UI、资产、Provider、security、performance 要求;GUI 不在本阶段扩展,但既有规范继续保留。

推翻重做理由:

  1. 不能把 CLI acceptance 当成完整桌宠语音运行版,因为它没有真实常驻麦克风监听和扬声器播放。
  2. 不能把 Provider 协议占位当成本地模型能力,因为用户要求真实可重复对话。
  3. 不能只做无上下文的多轮循环,因为用户明确修正为携带本次历史对话。
  4. 不能把上下文落盘或做长期记忆,因为当前目标是“本次运行会话内临时历史”。

实施计划

阶段优先级

  1. 阶段 1:OpenSpec 修正与提交,保证实现前合同正确。
  2. 阶段 2:依赖、模型和配置,保证真实运行前置条件可准备、可检查、不可误提交。
  3. 阶段 3:本机音频 Transport 与设备检查,保证真实输入输出不是占位。
  4. 阶段 4Wake/VAD/STT 与模型检查,保证语音转文字是真实本地链路。
  5. 阶段 5Live runtime、LLM/TTS 与临时上下文,保证重复对话核心体验。
  6. 阶段 6:文档、真实验收、归档,保证用户能运行且 OpenSpec 状态闭环。

里程碑

  1. M1complete-live-repeat-voice-runtime OpenSpec 校验通过并提交。
  2. M2:本地 .venv、模型下载脚本、.env.example.gitignore 完成并提交。
  3. M3device-check 和真实音频 Transport 完成并提交。
  4. M4model-check、sherpa VAD/STT 和唤醒检测完成并提交。
  5. M5run-live 可两轮 fake 测试,携带临时历史,并能接 macOS TTS 播放。
  6. M6:README、真实模型/设备/运行验收、OpenSpec archive 和最终提交完成。

估时

  1. 乐观估时:6 小时。依赖安装顺利、模型 URL 可用、sounddevice 权限正常、现有接口无需大改。
  2. 最可能估时:9 小时。需要适配 sherpa 模型结构、补测试、调整音频格式和错误恢复。
  3. 悲观估时:14 小时。模型下载或 macOS 音频权限受阻,需要替换模型、增加诊断分支或降级策略。
  4. 总耗时预算:以 9 小时为主计划;遇到模型或设备不可控失败时,必须保留结构化验收证据并不伪造成功。

数据/状态迁移策略

  1. 无数据库迁移。
  2. 无长期会话历史迁移。
  3. .env 继续兼容现有 LLM 配置;新增配置提供默认值。
  4. 旧 acceptance 测试继续保留;新增 run-live 不破坏已有命令。
  5. 模型目录为新增本地运行资产,进入 .gitignore,不纳入历史提交。

Git 提交规范

  1. 每完成一个大模块必须立即执行 Git commit。大模块定义为本 proposal “任务分解”里的一级主要功能组或“实施计划”里的里程碑阶段。
  2. 每次 commit 前必须运行当前模块适用验证;源码模块至少运行 PYTHONPATH=src python3.11 -m compileall src tests scriptsPYTHONPATH=src python3.11 -m unittest discover -s testsPYTHONPATH=src python3.11 -m owner_voice_pet security-checkopenspec validate --all --strict
  3. OpenSpec 文档模块至少运行 openspec validate complete-live-repeat-voice-runtime --strictopenspec validate --all --strict
  4. 如果模块涉及模型或设备,提交前必须运行对应 model-checkdevice-check;若硬件权限导致失败,必须在提交说明或最终回复中记录真实失败原因。
  5. 构建、测试或 OpenSpec 校验失败时不得 commit。
  6. 提交信息必须使用中文,格式为“[模块名]:完成[具体功能描述],包含[关键变更]”。
  7. 提交前必须检查 git status --short,确认 .env.venv/models/ 和临时音频没有进入暂存区。