# Owner 语音桌宠 这是一个 Python 语音桌宠运行程序。当前第一版先提供无 GUI 的真实实时语音循环: `小杰小杰` 唤醒 -> 本地应答“我在” -> 主说话人端点采集问题 -> ASR 转文字 -> 携带本次进程内临时历史调用 LLM -> TTS 生成语音 -> 本机扬声器播放 -> 回到待机继续监听。 ## 当前能力 - `owner_voice_pet run-live`:真实常驻语音循环。 - `owner_voice_pet run-live --once`:只跑一轮,便于验收。 - `.env` 直接读取配置,不要求导出 shell 环境变量。 - 唤醒词检测使用本地 `sherpa-onnx` KWS 模型,不走云端 ASR。 - `OWNER_SPEECH_PROVIDER=cloud|local`:选择云端语音模型或本地语音模型。 - 默认云端语音模型:`mimo-v2.5-asr`、`mimo-v2.5-tts`。 - 本地设备:`sounddevice` 读取麦克风,扬声器或 `afplay` 播放。 - 本地模型:`models/` 存放 `sherpa-onnx` wake/VAD/STT 模型,目录不提交 Git。 - Pipeline:`run-live` 使用 stage 化 `VoiceAssistantPipeline`,通过事件总线输出终端状态。 - 主说话人端点:默认 `OWNER_ENDPOINT_MODE=primary_speaker`,本轮音色消失后结束录音,避免背景噪声拖慢 STT。 - 临时上下文:同一次 `run-live` 进程内携带最近 user/assistant 历史,退出即清空。 ## 首次准备 ```bash python3.11 -m venv .venv .venv/bin/python -m pip install --upgrade pip .venv/bin/python -m pip install -e '.[speech]' cp .env.example .env ``` 然后编辑 `.env`,填写本地密钥和模型配置。当前默认示例: ```dotenv OWNER_LLM_BASE_URL=https://token-plan-cn.xiaomimimo.com/v1 OWNER_LLM_API_KEY= OWNER_LLM_MODEL=mimo-v2.5 OWNER_LLM_API_STYLE=chat_completions OWNER_WAKE_PROVIDER=local_kws OWNER_WAKE_KEYWORDS_FILE= OWNER_WAKE_KWS_THRESHOLD=0.15 OWNER_WAKE_KWS_SCORE=1.0 OWNER_WAKE_ACK_TEXT=我在 OWNER_POST_PLAYBACK_DRAIN_MS=0 OWNER_PIPELINE_MODE=live_turn_based OWNER_ENDPOINT_MODE=primary_speaker OWNER_SPEAKER_PROFILE_MS=600 OWNER_SPEAKER_PROFILE_MIN_MS=120 OWNER_SPEAKER_ABSENT_MS=300 OWNER_SPEAKER_SIMILARITY_THRESHOLD=0.70 OWNER_SPEAKER_MIN_RMS=0.012 OWNER_VAD_PROVIDER=hybrid OWNER_VAD_THRESHOLD=0.5 OWNER_VAD_MIN_DURATION_MS=250 OWNER_VAD_END_SILENCE_MS=350 OWNER_VAD_NO_SPEECH_TIMEOUT_MS=5000 OWNER_VAD_MAX_RECORDING_MS=12000 OWNER_SPEECH_PROVIDER=cloud OWNER_ASR_MODEL=mimo-v2.5-asr OWNER_TTS_MODEL=mimo-v2.5-tts OWNER_TTS_VOICE=mimo_default OWNER_CONTEXT_MODE=session_memory ``` `OWNER_WAKE_PROVIDER=local_kws` 表示唤醒词“小杰小杰”由本地模型检测。唤醒命中后会先本地播报 `OWNER_WAKE_ACK_TEXT=我在`,再开始听取问题。`OWNER_SPEECH_PROVIDER=cloud` 只影响唤醒后的正式问题 ASR/TTS:它会把 VAD 切出来的用户问题片段发送到云端 ASR,不会上传连续麦克风流,也不会用云端判断唤醒。改成 `local` 时使用项目 `models/` 下的本地语音模型路径。 ## 本地模型 即使默认走云端 ASR/TTS,也必须准备本地唤醒模型;同一脚本会下载 wake、VAD、STT 模型,便于切换到 `OWNER_SPEECH_PROVIDER=local`: ```bash python3.11 scripts/download_speech_models.py --dir models .venv/bin/python -m owner_voice_pet model-check --models-dir models ``` `models/` 已在 `.gitignore` 中,不会提交大模型文件。 本地唤醒关键词文件位于 `models/wake/keywords.txt`。默认 `OWNER_WAKE_KWS_THRESHOLD=0.15` 已偏向灵敏;如果真人唤醒仍不灵敏,可以继续降到 `0.10`,若误唤醒变多再回调到 `0.20`。 默认 `OWNER_ENDPOINT_MODE=primary_speaker` 会在本轮问题开头建立临时音色画像;`OWNER_SPEAKER_PROFILE_MIN_MS=120` 表示最少 120 毫秒有效语音即可让画像参与端点判断。当这个主说话人音色连续消失 `OWNER_SPEAKER_ABSENT_MS=300` 毫秒后,直接结束录音进入转写。它不保存长期声纹、不做主人注册、不跨进程记忆。 默认 `OWNER_POST_PLAYBACK_DRAIN_MS=0` 表示“我在”播放结束后只清理已经积压的输入队列,不再额外读取并丢弃后续音频,避免切掉正式问题开头。默认 `OWNER_VAD_PROVIDER=hybrid` 会同时使用本地 `sherpa-onnx` VAD 和能量阈值兜底,帮助开始录音;结束录音优先由主说话人端点控制。如果你想临时回退旧行为,可以设置 `OWNER_ENDPOINT_MODE=vad`。如果唤醒后你已经停说但还长时间显示“录音中”,优先调小 `OWNER_SPEAKER_ABSENT_MS`,例如 `250`;如果句尾容易被切掉,再调高到 `400`。 ## 设备检查 ```bash .venv/bin/python -m owner_voice_pet device-check ``` 输出里 `ok=true` 表示已检测到可用麦克风和扬声器。 ## 运行 单轮验收: ```bash .venv/bin/python -m owner_voice_pet run-live --once ``` 常驻重复对话: ```bash .venv/bin/python -m owner_voice_pet run-live ``` 运行后终端状态来自 pipeline event bus,会显示待机、唤醒命中、应答中、请说出问题、录音中、检测到用户语音、用户语音结束、转写中、转写结果、思考中、播放中、恢复待机等状态。说“小杰小杰”,听到“我在”且看到“请说出问题”后再提问;听到回复后可以再次说“小杰小杰”继续下一轮。本次进程内会携带临时历史,程序退出后不保存。 ## 验证 ```bash .venv/bin/python -m compileall src tests scripts .venv/bin/python -m unittest discover -s tests .venv/bin/python -m owner_voice_pet acceptance .venv/bin/python -m owner_voice_pet validate-assets .venv/bin/python -m owner_voice_pet security-check .venv/bin/python -m owner_voice_pet model-check --models-dir models .venv/bin/python -m owner_voice_pet device-check openspec validate --all --strict ``` LLM smoke: ```bash .venv/bin/python -m owner_voice_pet llm-smoke --no-stream --message '用一句中文回复:小杰在线。' ``` ## 安全约束 - API key 只写入本地 `.env`,`.env` 不提交 Git。 - `models/`、`.venv/` 和临时音频文件不提交 Git。 - 默认不持久化麦克风原始音频。 - 云端 ASR 只接收 VAD 切分后的语音片段。 - `security-check` 会扫描已跟踪文本文件中的 `sk-...` 和 `tp-...` 形式密钥。