# Python 桌宠语音 Pipeline OpenSpec 计划 ## 功能目标 ### 完整业务价值 本变更交付一个本地运行的 Python 桌宠语音交互系统,使用户可以通过唤醒词“小杰小杰”启动自然语音对话,由桌宠完成听取、理解、思考、回复和语音播放。OpenSpec 计划作为实现约束,最终验收必须包含源码、自动化测试、OpenSpec 校验、模块级中文 Git 提交和可复现的验收命令。 该能力的业务价值是把桌宠从静态陪伴或手动点击交互升级为可被语音自然唤醒的本地助手入口。用户不需要切换窗口、不需要按按钮、不需要手动复制文本,即可通过本机麦克风对桌宠发起问题,并从系统扬声器听到回复。桌宠 UI 负责展示监听、思考、说话和错误状态,降低语音系统不可见导致的不确定感。 ### 目标用户场景 1. 桌面陪伴场景:用户在 macOS 桌面工作时,说出“小杰小杰”后提出中文问题,桌宠进入监听状态并用中文语音回答。 2. 免手动问答场景:用户正在写代码、查资料或处理文档,不切换应用,通过语音让桌宠解释、提醒或提供建议。 3. 低打扰状态反馈场景:用户从桌宠表情和动作即可判断当前处于待机、监听、转写、思考、说话或错误恢复状态。 4. 后续扩展场景:未来可以在不推翻核心 pipeline 的前提下替换 STT、TTS、LLM、Transport、桌宠资产和窗口实现。 ### 量化成功指标 KPI 1. 唤醒词响应:在安静环境中,说出“小杰小杰”后 800 ms 内进入监听状态。 2. 端点检测:用户停止说话后 900 ms 内结束录音并进入转写。 3. 首字回复:有效语音输入结束后,LLM 文本首个可播放分句在 3.5 秒内进入 TTS 阶段。 4. 端到端延迟:10 秒以内的普通中文问题,从说话结束到开始播放语音回复的 P95 延迟不超过 5 秒。 5. 唤醒准确性:安静环境下连续 30 次“小杰小杰”唤醒成功率不低于 90%。 6. 误唤醒控制:普通背景对话或键盘声 30 分钟内误唤醒次数不超过 1 次。 7. STT 可用性:普通话近场语音转写可读文本比例不低于 95%,核心意图识别失败率不超过 10%。 8. TTS 可用性:非空 LLM 回复的语音合成成功率不低于 99%,播放中断后能回到待机状态。 9. 稳定性:连续 20 轮问答不崩溃;任一 Provider 失败时,pipeline 必须进入错误恢复而不是进程退出。 10. 可审计性:每一轮交互必须有结构化日志,覆盖状态切换、耗时、错误码和 Provider 名称。 ### 预期影响 1. 新增 OpenSpec 规划能力:当前仓库为空,变更将建立第一个 `voice-pet-pipeline` 能力规范。 2. 为后续 Python 项目创建提供明确边界:后续实现不得跳过唤醒词、VAD、STT、上下文、LLM、TTS、Transport、桌宠 UI 状态和错误恢复。 3. 为本地模型和云端模型混合架构建立默认方案:LLM 云端,STT/TTS/唤醒词优先本地。 4. 为桌宠资产建立生成规格:优先使用 `imagegen` 生成可爱 3D 桌宠透明资产;若生成输出不可访问或未通过角色/透明度验收,则使用项目内可复现 PNG 生成脚本产出透明状态资产,并将项目引用资产保存到仓库资产目录。 ### 对现有问题的系统性总结 当前仓库状态经检查为空 Git 仓库,尚无提交,只有工具生成的 `.serena/` 工作区元数据。不存在 `openspec/specs/` 既有规范,不存在 Python 源码,不存在测试、依赖管理、构建脚本、桌宠 UI、音频 pipeline、模型 Provider、资产目录或运行入口。 现有问题完整列表如下: 1. 规范缺失:没有 OpenSpec 能力定义,无法判断后续实现是否满足业务需求。 2. 工程骨架缺失:没有 `pyproject.toml`、源码目录、测试目录、配置样例或入口命令。 3. 音频链路缺失:没有麦克风采集、扬声器播放、采样率、声道数、PCM 格式、缓冲策略或设备错误处理。 4. 唤醒能力缺失:没有“小杰小杰”唤醒词检测方案,没有误唤醒和漏唤醒指标。 5. VAD 缺失:没有人声检测、端点检测、录音切分、静音超时或噪声环境处理。 6. STT 缺失:没有本地转写模型、模型路径配置、语言设置、置信度输出或空转写处理。 7. 对话上下文缺失:没有消息结构、上下文窗口、系统提示词、历史截断或隐私边界。 8. LLM 缺失:没有云端 LLM Provider、流式回复、超时、重试、错误码或 API key 配置。 9. TTS 缺失:没有本地语音合成、分句合成、音频格式转换、播放队列或失败回退。 10. 桌宠 UI 缺失:没有透明窗口、置顶、拖拽、状态动效、错误反馈或资产规格。 11. 安全隐私缺失:没有麦克风权限说明、API key 保护、本地音频保留策略或敏感日志脱敏。 12. 测试缺失:没有单元测试、集成测试、音频回放测试、mock Provider 或人工验收脚本。 ## 详细需求 ### 功能需求 1. 系统必须规划为 Python 桌面应用,第一版运行在本机 macOS 桌面环境。 2. 系统必须通过本机麦克风接收音频,通过系统扬声器播放回复音频。 3. 系统必须支持唤醒词“小杰小杰”,且唤醒词检测在本地执行。 4. 系统必须在唤醒后进入 VAD 人声检测阶段,只在检测到用户正在说话时录制有效语音。 5. 系统必须在用户停止说话后自动结束当前 utterance,避免无限录音。 6. 系统必须将用户语音通过本地 STT 转写为文本。 7. 系统必须把转写文本加入对话上下文,并保留必要历史消息。 8. 系统必须通过云端 LLM 生成回复,默认使用 OpenAI Responses API 的流式响应能力。 9. 系统必须将 LLM 回复文本通过本地 TTS 转成音频。 10. 系统必须通过 Transport 播放 TTS 音频。 11. 系统必须在桌宠窗口展示待机、监听、录音、转写、思考、说话、错误状态。 12. 系统必须支持播放期间的自抑制,避免 TTS 输出被麦克风重新当作用户唤醒或输入。 13. 系统必须支持错误恢复,任一阶段失败后都回到可继续唤醒的待机状态。 14. 系统必须记录每轮交互的结构化日志。 ### 非功能需求 #### 性能优化 1. 音频采集必须采用低延迟流式缓冲,避免每轮交互启动和关闭设备造成额外延迟。 2. 唤醒词、VAD、STT、TTS 模型必须在应用启动或首次使用时预加载,并在日志中记录加载耗时。 3. LLM 必须使用流式文本回复,收到完整分句后即可进入 TTS 分句合成,不得等待整段回复结束才开始合成。 4. TTS 必须支持分句队列,第一句合成完成后即可播放,后续句子继续后台合成。 5. 上下文管理必须限制 token 或字符窗口,防止历史无限增长导致延迟上升。 6. 桌宠 UI 状态更新不得阻塞音频采集线程或异步 pipeline。 #### UI/UX 重构 1. 桌宠必须是透明背景、置顶、可拖拽窗口。 2. 桌宠必须用不同状态资产或动效表达当前 pipeline 状态。 3. 错误状态必须可见,但不得使用大量弹窗打断用户。 4. 第一版必须预留最小设置入口,用于查看麦克风设备、扬声器设备、模型路径、LLM 配置状态和日志路径。 5. 桌宠资产优先通过 `imagegen` 生成可爱 3D 桌宠照片风格;若输出不可用或不合格,必须使用可复现 fallback 生成项目内透明 PNG,并保留资产校验。 6. UI 文案必须短句化,不在窗口内展示长篇功能说明。 #### 安全 1. OpenAI API key 必须从环境变量或本地未提交配置读取,不得写入源码或 OpenSpec 以外的可提交密钥文件。 2. 语音音频默认不得持久化保存;如需调试保存,必须通过显式配置开启。 3. 日志不得记录 API key、完整认证头或系统敏感路径中的凭据。 4. 麦克风权限缺失时必须显示可恢复错误,不得反复请求或静默失败。 5. 云端 LLM 请求必须只发送 STT 文本和必要上下文,不发送原始音频。 #### 可扩展性 1. Transport、WakeWord、VAD、STT、LLM、TTS 必须规划为 Provider 接口,具体实现可替换。 2. 第一版 Transport 只实现麦克风和扬声器,但接口必须允许后续扩展 WebSocket、文件回放和硬件设备。 3. STT/TTS 默认候选为本地 `sherpa-onnx`,但接口不得绑定单一模型。 4. LLM 模型名必须配置化,不得在核心逻辑中写死。 5. 桌宠资产必须按状态命名,允许后续扩展 idle/listening/thinking/speaking/error 多图或序列帧。 #### Git 提交与构建门禁 1. 后续实施阶段每完成一个大模块,必须立即执行一次 Git commit,不允许继续累积未提交的中间状态。 2. 大模块定义为 `tasks.md` 中的一级功能组,或本 proposal “实施计划”中的阶段性里程碑,以先完成者为提交边界。 3. 每次 commit 前必须运行当前阶段适用的构建、测试、类型检查、lint 或 OpenSpec 校验;若当时尚无源码构建命令,必须至少运行 `openspec validate --all --strict` 并记录原因。 4. 构建或校验失败时不得 commit,必须先修复失败项并重新验证通过。 5. 提交信息必须使用中文,格式固定为“`[模块名]:完成[具体功能描述],包含[关键变更]`”。 6. 模块完成后的 `git status --short` 必须只剩与当前模块无关且已明确保留的外部变更;属于当前模块的变更必须全部纳入该模块 commit。 ### 边缘案例 1. 启动时没有可用麦克风:系统进入配置错误状态,提示选择设备或授权。 2. 启动时没有可用扬声器:系统允许进入文本回复可用但语音播放不可用状态,后续实现可选择文本提示。 3. 用户只说唤醒词不说后续内容:VAD 超时后回到待机,不调用 LLM。 4. 用户语音过短:短于最小录音时长的输入丢弃并回到待机。 5. 用户语音过长:达到最大录音时长后强制截断并进入 STT。 6. 背景噪声持续存在:VAD 必须有最大录音保护和静音判定阈值。 7. STT 返回空文本:不调用 LLM,桌宠提示未听清并回到待机。 8. LLM 超时或网络错误:停止当前轮次,显示错误状态并回到待机。 9. TTS 失败:不崩溃,记录错误并回到待机。 10. 扬声器播放中用户再次说唤醒词:默认忽略,避免自回声和打断混乱;后续可扩展打断模式。 11. Provider 模型文件不存在:启动前配置校验失败,错误日志给出缺失路径。 12. 对话上下文过大:按策略截断较早用户和助手消息,保留系统提示和最近轮次。 ### 输入输出规格 #### 音频输入 1. 来源:本机默认麦克风,后续可配置设备 ID。 2. 推荐采样率:16 kHz 单声道 PCM,用于 Wake/VAD/STT。 3. 输入帧:推荐 20 ms 至 30 ms 帧长,供 Wake/VAD 流式处理。 4. 格式:float32 或 int16 PCM,Provider 边界必须明确转换。 5. 输出给 pipeline 的对象:`AudioFrame{pcm, sample_rate, channels, timestamp_ms, frame_id}`。 #### 语音片段 1. 对象:`AudioSegment{pcm, sample_rate, channels, start_time_ms, end_time_ms, duration_ms}`。 2. 最小时长:建议 300 ms,低于该值视为无效输入。 3. 最大时长:建议 30 秒,超过则截断。 #### STT 输出 1. 对象:`Transcript{text, language, confidence, duration_ms, provider, raw_metadata}`。 2. `text` 必须 trim,空字符串视为失败。 3. `language` 默认 `zh`,允许 Provider 返回自动检测结果。 #### LLM 输入输出 1. 输入消息:`Message{role, content, created_at}`。 2. role 允许 `system`、`user`、`assistant`。 3. 输出流:`ReplyDelta{text_delta, is_sentence_boundary, finish_reason}`。 4. 错误对象:`ProviderError{code, message, retryable, provider, stage}`。 #### TTS 输入输出 1. 输入:非空中文或中英混合文本分句。 2. 输出:`AudioSegment`,采样率可为 TTS 模型原生采样率,但播放前必须可转换到 Transport 支持格式。 3. 空文本不得调用 TTS。 ### 数据验证规则 1. 配置启动校验必须检查 API key 来源、模型路径、麦克风设备、扬声器设备、日志目录可写性。 2. 音频帧必须校验采样率、声道数、数据长度和时间戳递增。 3. 唤醒事件必须包含置信度和关键词。 4. VAD 结果必须包含是否有人声、置信度、连续静音时长和连续人声时长。 5. STT 文本为空或只有标点时必须视为无有效输入。 6. LLM 回复空文本时不得调用 TTS。 7. TTS 输出音频时长为 0 时必须视为合成失败。 8. 任一 Provider 抛异常必须转换成统一错误对象,不允许异常穿透导致进程退出。 ### 当前实现全部问题与全新解决方案 当前实现为空,因此不是局部修复,而是从规范层推翻重做。全新解决方案是建立一个严格分层、Provider 可插拔、异步状态机驱动的语音桌宠系统。核心改造不是先写业务代码,而是先用 OpenSpec 固化能力边界和验收标准,确保后续实现不会只做录音或聊天单点功能,而是完整覆盖 wake/VAD/STT/context/LLM/TTS/playback/UI/error/test 全链路。 ## 设计方案 ### 文字版全新架构图 ```text macOS Microphone -> AudioTransport(InputStream) -> AudioFrame Ring Buffer -> WakeWordProvider("小杰小杰") -> Pipeline State Machine -> VadProvider(endpoint detection) -> AudioSegment Builder -> SttProvider(local sherpa-onnx candidate) -> ConversationContext Manager -> LlmProvider(OpenAI Responses API streaming) -> Sentence Segmenter -> TtsProvider(local sherpa-onnx candidate) -> AudioTransport(OutputStream) -> macOS Speaker Pipeline State Events -> Pet UI Controller -> Transparent Always-on-top Desktop Pet -> State Assets: idle/listening/recording/thinking/speaking/error Structured Logs <- all providers, state transitions, latency metrics, errors ``` ### 数据流 1. 应用启动读取配置,校验 API key 来源、音频设备、模型路径和资产目录。 2. Transport 打开麦克风输入流,把 PCM 帧写入环形缓冲。 3. WakeWordProvider 持续消费小帧音频,检测“小杰小杰”。 4. 检测成功后 pipeline 从 `wake_listening` 进入 `speech_detecting`,桌宠切到监听状态。 5. VAD 判断用户开始说话后进入 `recording`,AudioSegment Builder 聚合有效语音。 6. VAD 判断静音结束或达到最大录音时长后,pipeline 进入 `transcribing`。 7. STT 将语音片段转为文本,空文本或无效文本直接结束当前轮次。 8. ConversationContext 将用户消息加入上下文,并按窗口策略截断历史。 9. LLM Provider 以流式方式返回文本 delta。 10. Sentence Segmenter 收到完整分句后提交给 TTS。 11. TTS Provider 合成音频片段并加入播放队列。 12. Transport 播放音频,期间麦克风输入对唤醒词和 VAD 默认抑制。 13. 播放结束后 pipeline 回到待机。 ### 接口定义 #### AudioTransport ```text start_input(device_id: str | None, sample_rate: int, channels: int) -> None read_frames(timeout_ms: int) -> list[AudioFrame] play_pcm(segment: AudioSegment, interrupt: bool = false) -> PlaybackResult stop() -> None health() -> TransportHealth ``` 错误码: 1. `AUDIO_INPUT_DEVICE_MISSING` 2. `AUDIO_OUTPUT_DEVICE_MISSING` 3. `AUDIO_PERMISSION_DENIED` 4. `AUDIO_STREAM_UNDERRUN` 5. `AUDIO_FORMAT_UNSUPPORTED` #### WakeWordProvider ```text load(model_path: str, keyword: str) -> None detect(frame: AudioFrame) -> WakeEvent | None reset() -> None ``` 返回: ```text WakeEvent{keyword: "小杰小杰", confidence: float, timestamp_ms: int} ``` 错误码: 1. `WAKE_MODEL_MISSING` 2. `WAKE_MODEL_LOAD_FAILED` 3. `WAKE_AUDIO_FORMAT_INVALID` #### VadProvider ```text load(model_path: str | None) -> None analyze(frame: AudioFrame) -> VadResult reset() -> None ``` 返回: ```text VadResult{is_speech: bool, confidence: float, speech_ms: int, silence_ms: int} ``` 错误码: 1. `VAD_MODEL_LOAD_FAILED` 2. `VAD_TIMEOUT_NO_SPEECH` 3. `VAD_MAX_RECORDING_REACHED` #### SttProvider ```text load(model_path: str, language: str) -> None transcribe(segment: AudioSegment) -> Transcript ``` 返回: ```text Transcript{text: str, language: str, confidence: float | None, provider: str} ``` 错误码: 1. `STT_MODEL_MISSING` 2. `STT_TRANSCRIBE_FAILED` 3. `STT_EMPTY_TRANSCRIPT` #### ConversationContext ```text append_user(text: str) -> None append_assistant(text: str) -> None build_llm_messages() -> list[Message] truncate(max_messages: int, max_chars: int) -> None reset() -> None ``` #### LlmProvider ```text stream_reply(messages: list[Message], model: str, timeout_s: float) -> AsyncIterator[ReplyDelta] ``` 错误码: 1. `LLM_API_KEY_MISSING` 2. `LLM_REQUEST_TIMEOUT` 3. `LLM_RATE_LIMITED` 4. `LLM_NETWORK_ERROR` 5. `LLM_EMPTY_REPLY` #### TtsProvider ```text load(model_path: str, voice: str | None) -> None synthesize(text: str) -> AudioSegment ``` 错误码: 1. `TTS_MODEL_MISSING` 2. `TTS_SYNTHESIS_FAILED` 3. `TTS_EMPTY_AUDIO` ### 状态机 ```text idle -> wake_listening: app ready and microphone active wake_listening -> speech_detecting: WakeEvent(keyword="小杰小杰") detected speech_detecting -> recording: VAD detects speech -> idle: no speech timeout recording -> transcribing: endpoint silence reached or max duration reached transcribing -> thinking: Transcript.text is valid -> idle: empty transcript thinking -> speaking: first TTS segment ready -> error_recovering: LLM failure speaking -> idle: playback finished -> error_recovering: playback or TTS failure error_recovering -> idle: cleanup finished ``` ### 关键算法 1. 环形缓冲:固定保存最近数秒音频帧,唤醒成功后允许包含唤醒后短暂前置音频,避免用户说话开头丢失。 2. VAD 端点检测:连续人声超过起始阈值才进入录音,连续静音超过结束阈值才结束录音。 3. 分句 TTS:LLM 流式 delta 按中文句号、问号、叹号、换行和长度阈值切分,完整分句立即进入 TTS。 4. 上下文截断:保留 system prompt 和最近 N 轮消息,超过字符或 token 预算时删除最早普通对话。 5. 自抑制:TTS 播放期间暂停 Wake/VAD 消费或标记输出音频窗口,避免系统回复被再次识别为用户输入。 ### 数据库/状态管理变更 第一版不引入数据库。运行时状态保存在内存: 1. Pipeline 当前状态。 2. 当前轮次 ID。 3. 对话上下文消息列表。 4. Provider 健康状态。 5. 音频播放队列。 6. 指标快照和最近错误。 后续若需要长期记忆、用户画像或历史记录,必须另起 OpenSpec 变更。 ### UI 组件重构方案 1. `PetWindow`:透明、置顶、可拖拽主窗口。 2. `PetSpriteView`:根据状态显示对应 PNG 或序列帧。 3. `StatusController`:订阅 pipeline 状态事件并映射到 UI 状态。 4. `SettingsPanel`:最小设置入口,显示设备、模型路径、LLM 配置状态和日志路径。 5. `ErrorToast`:短提示错误,不阻塞主交互。 ### 依赖影响分析 1. OpenSpec:本阶段必须新增规范文档。 2. Python 运行时:后续实现建议使用 Python 3.11 或更高版本,不使用系统 Python 3.9 作为开发基线。 3. 音频 I/O:后续候选 `sounddevice`。 4. 桌面 UI:后续候选 `PySide6`。 5. 本地语音模型:后续候选 `sherpa-onnx` 覆盖 KWS、ASR、TTS 或至少 ASR/TTS;若 KWS 中文效果不足,单独评估替代 Provider。 6. 云端 LLM:后续使用 OpenAI Responses API,模型名配置化。 7. 生图资产:后续用系统 `imagegen` 技能生成并落到项目资产目录。 ### 性能、UI、功能三大类优化路径 性能优化路径: 1. 启动时预热模型和音频设备。 2. Wake/VAD 使用小帧流式处理。 3. LLM streaming 与 TTS 分句并行。 4. 播放队列和合成队列解耦。 5. 上下文窗口限制和日志指标定位慢阶段。 UI 优化路径: 1. 每个 pipeline 状态都有可见桌宠反馈。 2. 错误提示短、可恢复、不阻断桌面。 3. 透明窗口不遮挡主要工作区,支持拖拽移动。 4. 资产统一 3D 可爱风格,避免状态之间角色不一致。 功能优化路径: 1. 先实现完整单轮闭环,再扩展多轮上下文质量。 2. Provider 接口先稳定,再替换具体模型。 3. 第一版只做本机麦克风/扬声器,保留 Transport 扩展点。 4. 不在第一版引入长期记忆、云端 STT/TTS、多设备硬件协议或复杂角色系统。 ## 风险与权衡 | 风险 | 概率 | 影响 | 缓解措施 | | --- | --- | --- | --- | | 中文唤醒词“小杰小杰”本地模型效果不足 | 高 | 高 | 使用可替换 WakeWordProvider;先定义误唤醒/漏唤醒验收;必要时在后续实现中提供按钮触发调试模式但不替代正式唤醒需求。 | | 本地 STT 模型体积较大或首次加载慢 | 中 | 中 | 模型路径配置化;启动日志记录加载时间;后续实现支持首次使用加载和健康检查。 | | 本地 TTS 中文音色质量不稳定 | 中 | 中 | TtsProvider 可替换;验收以清晰可懂优先;音色优化不阻塞 pipeline 闭环。 | | macOS 麦克风权限未授权 | 高 | 高 | 启动校验和错误状态必须覆盖权限缺失;文档要求可恢复提示。 | | OpenAI API key 缺失或网络失败 | 中 | 高 | LLM Provider 必须返回统一错误;pipeline 进入错误恢复;API key 不得硬编码。 | | LLM 流式输出和 TTS 分句边界不稳定 | 中 | 中 | 设计分句缓冲阈值;空分句不合成;超长句按长度切分。 | | TTS 播放被麦克风再次捕获造成自触发 | 高 | 高 | 播放期间默认抑制 Wake/VAD;后续如需打断另立需求。 | | 音频设备采样率不兼容 | 中 | 中 | Transport 边界定义格式转换和错误码;Provider 只接收规范格式。 | | 空仓库从零实现导致范围膨胀 | 高 | 高 | 按 OpenSpec 一级功能组分阶段实现和提交;任务拆到 1 小时以内;第一版只覆盖本机 Transport 和可测试 Provider。 | | 桌宠透明图片边缘质量差 | 中 | 低 | 生图使用 chroma-key 背景和本地抠图验证;资产不合格不进入项目引用。 | | 对话历史泄露隐私 | 中 | 高 | 默认不持久化音频;云端只发送文本和必要上下文;日志脱敏。 | | 长时间运行内存或线程泄漏 | 中 | 中 | 后续实现必须加入连续 20 轮问答测试和资源释放检查。 | | 本地 Provider 安装复杂 | 中 | 中 | 文档层不安装依赖;后续实现任务中单独列依赖验证和模型下载说明。 | | 过早绑定单一模型栈 | 中 | 中 | 规范只固定接口和默认候选,不把具体模型文件或版本写死为不可替换。 | | UI 线程阻塞音频 pipeline | 中 | 高 | UI 通过事件订阅状态,不直接执行音频和模型推理。 | | 大模块完成后未及时提交导致中间状态堆积 | 中 | 高 | 将一级功能组和实施里程碑设为强制 commit 边界;每次 commit 前必须验证构建或 OpenSpec 校验通过;提交信息使用固定中文格式。 | ## 任务分解 任务分解详见 `tasks.md`。所有任务必须是原子任务,每项预计不超过 1 小时,并包含前置条件、验收标准和测试要点。优先级顺序固定为: 1. OpenSpec 与项目骨架。 2. 音频 Transport。 3. Wake/VAD/STT。 4. LLM/TTS 与对话闭环。 5. 桌宠 UI 与生图资产。 6. 测试、性能、安全、验收与归档。 每完成以上任一主要功能组,后续实施者必须立即执行构建验证和 Git commit。提交信息必须严格使用“`[模块名]:完成[具体功能描述],包含[关键变更]`”格式,例如“`[音频 Transport]:完成本机音频输入输出规划,包含 AudioTransport 接口与设备错误码`”。若完成的是实施计划中的阶段里程碑,也按该阶段名作为模块名提交。 本变更必须完成源码实现、测试和验收;不得把 OpenSpec 文档完成误判为 change 完成。 ## Spec Deltas ### 与现有 `openspec/specs/` 的精确差异对比 当前 `openspec/specs/` 为空,不存在既有能力规范。因此本变更没有修改项和删除项,只有新增项。 ### 新增项 1. 新增能力:`voice-pet-pipeline` 2. 新增 delta 文件:`openspec/changes/add-voice-pet-pipeline/specs/voice-pet-pipeline/spec.md` 3. 新增能力覆盖: - Python 桌宠运行形态。 - 本机麦克风输入和扬声器输出 Transport。 - “小杰小杰”本地唤醒词检测。 - VAD 人声检测和端点检测。 - 本地 STT 转写。 - 对话上下文管理。 - 云端 LLM 流式回复。 - 本地 TTS 合成。 - TTS 音频播放。 - 桌宠状态 UI。 - 错误恢复、性能指标、安全隐私、测试验收。 ### 修改项 无。当前没有既有 spec 可修改。 ### 删除项 无。当前没有既有 spec 可删除。 ### 推翻重做的理由 仓库没有已有实现或规范,不存在可沿用模块。若直接从代码开始,将无法约束唤醒词、VAD、STT、LLM、TTS、Transport 和桌宠 UI 的完整闭环,容易落成单点 demo。必须先建立 OpenSpec 能力规范和工程计划,再进入实现。 ## 实施计划 ### 阶段 0:OpenSpec 计划阶段 范围: 1. 初始化 OpenSpec。 2. 创建 `add-voice-pet-pipeline` change。 3. 编写 `proposal.md`、`design.md`、`tasks.md` 和 `specs/voice-pet-pipeline/spec.md`。 4. 运行 OpenSpec strict 校验。 5. 阶段完成后立即提交,提交前必须通过 `openspec validate add-voice-pet-pipeline --strict` 和 `openspec validate --all --strict`。 6. 若实施目标发生变化,必须同步更新 OpenSpec 工件,避免任务仍停留在“只写计划”的旧状态。 估时: 1. 乐观:1.5 小时。 2. 最可能:2.5 小时。 3. 悲观:4 小时。 ### 阶段 1:项目骨架与配置 范围: 1. 创建 Python 3.11+ 项目骨架。 2. 配置 `uv`、测试框架、lint/type check。 3. 定义 Provider 接口、状态机类型和配置加载。 4. 阶段完成后立即提交,提交前必须通过当时已定义的构建、测试、lint/type check 和 OpenSpec 校验。 估时: 1. 乐观:4 小时。 2. 最可能:6 小时。 3. 悲观:10 小时。 ### 阶段 2:音频 Transport、Wake、VAD、STT 范围: 1. 实现本机麦克风采集和扬声器播放。 2. 接入“小杰小杰”唤醒词 Provider。 3. 接入 VAD 和本地 STT。 4. 完成文件回放和 mock 音频测试。 5. 阶段完成后立即提交,提交前必须通过音频相关单元测试、文件回放测试、lint/type check 和 OpenSpec 校验。 估时: 1. 乐观:10 小时。 2. 最可能:18 小时。 3. 悲观:32 小时。 ### 阶段 3:LLM、上下文、TTS、播放闭环 范围: 1. 实现对话上下文。 2. 接入 OpenAI Responses API streaming。 3. 实现本地 TTS 和分句播放队列。 4. 实现错误恢复和自抑制。 5. 阶段完成后立即提交,提交前必须通过 LLM/TTS mock 流式测试、错误恢复测试、lint/type check 和 OpenSpec 校验。 估时: 1. 乐观:8 小时。 2. 最可能:14 小时。 3. 悲观:24 小时。 ### 阶段 4:桌宠 UI 与生图资产 范围: 1. 实现透明置顶可拖拽桌宠窗口。 2. 使用 `imagegen` 生成可爱 3D 桌宠状态资产。 3. 抠图、验证透明背景并落到项目资产目录。 4. 绑定 pipeline 状态到 UI。 5. 阶段完成后立即提交,提交前必须通过 UI 状态映射测试、资产存在性与透明度检查、lint/type check 和 OpenSpec 校验。 估时: 1. 乐观:6 小时。 2. 最可能:10 小时。 3. 悲观:18 小时。 ### 阶段 5:测试、性能、安全、打包 范围: 1. 完成单元测试、集成测试、人工验收脚本。 2. 收集延迟指标并优化慢阶段。 3. 校验 API key、日志脱敏、音频不持久化默认策略。 4. 准备 macOS 运行说明和打包策略。 5. 阶段完成后立即提交,提交前必须通过完整构建、完整测试、lint/type check、安全隐私检查、打包验证和 OpenSpec 校验。 估时: 1. 乐观:8 小时。 2. 最可能:14 小时。 3. 悲观:24 小时。 ### 总耗时 1. 乐观总耗时:37.5 小时。 2. 最可能总耗时:64.5 小时。 3. 悲观总耗时:112 小时。 ### 数据/状态迁移策略 当前仓库为空,无数据库、无用户数据、无历史状态,因此第一版无需数据迁移。后续实现需要保证: 1. 配置文件新增字段必须有默认值或明确启动校验错误。 2. 对话上下文默认只在内存保存,应用退出即清空。 3. 若未来引入持久化记忆、日志数据库或用户配置迁移,必须另起 OpenSpec 变更。 ### 需人工澄清的点 1. 本地 STT/TTS 最终模型文件、下载来源、许可证和中文音色是否有固定要求。 2. OpenAI Responses API 使用的默认模型名、预算上限和超时上限。 3. 桌宠角色是否有品牌、颜色、性格、名字或禁止元素。 4. 是否需要支持用户在 TTS 播放期间打断桌宠说话。 5. 是否需要持久化对话历史或长期记忆。 在无人进一步澄清时,本次实现采用以下默认值:LLM base URL 通过 `OWNER_LLM_BASE_URL` 配置并默认兼容 OpenAI/NewAPI;API key 只从环境变量 `OWNER_LLM_API_KEY` 读取;真实本地 STT/TTS 采用可选 Provider,自动化测试使用 deterministic local/mock Provider;桌宠资产使用可爱 3D 风格透明 PNG;播放期间不支持用户打断。