[测试、性能、安全、验收与归档]:完成全量测试验收与 OpenSpec 归档,包含 CLI 验收、NewAPI smoke、安全扫描和主 spec 更新

This commit is contained in:
mkbk
2026-06-17 18:42:11 +08:00
parent 667c8941f2
commit 1b884ac6a9
15 changed files with 522 additions and 28 deletions
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-17
@@ -0,0 +1,3 @@
# add-voice-pet-pipeline
规划 Python 桌宠语音 Pipeline:小杰小杰唤醒词、本机音频 Transport、VAD、本地 STT、云端 LLM、本地 TTS 和桌宠资产规格
@@ -0,0 +1,242 @@
# Python 桌宠语音 Pipeline 技术设计
## Context
当前仓库从空 Git 仓库开始,没有既有 OpenSpec 能力、源码、测试、依赖、资产或构建脚本。本设计作为实现约束,后续必须创建 Python 源码、测试、运行入口、验收命令和项目内桌宠资产;API key 不得写入仓库。
目标系统是本地 Python 桌宠应用。用户说“小杰小杰”后,系统通过本机麦克风接收语音,经过 VAD、STT、对话上下文、云端 LLM、本地 TTS,再通过系统扬声器播放回复。桌宠窗口展示 pipeline 状态,并使用后续由 `imagegen` 生成的可爱 3D 透明资产。
## Goals / Non-Goals
**Goals:**
1. 明确后续实现的模块边界、接口、状态机和错误码。
2. 固定第一版范围:Python 桌面宠物、本机麦克风、本机扬声器、“小杰小杰”唤醒、本地 STT/TTS、云端 LLM。
3. 建立可测试的 pipeline 能力规范。
4. 把性能、UI、功能、安全和可扩展性风险提前纳入任务拆解。
**Non-Goals:**
1. 第一版不支持 WebSocket Transport、硬件设备协议、移动端、云端 STT/TTS 或长期记忆。
2. 第一版不要求播放中语音打断,默认播放期间自抑制。
3. 自动化测试不依赖真实麦克风、真实扬声器或真实模型文件;真实设备能力通过可选 Provider 和人工验收入口保留。
## Decisions
### Decision 1: 使用 Python 本地桌面应用作为第一版运行形态
选择:Python 主进程管理桌宠 UI、音频采集播放和 pipeline 状态。
理由:
1. 音频 I/O、本地模型和桌面窗口都可以在同一运行时内协调。
2. 初期链路短,便于调试 wake/VAD/STT/TTS 延迟。
3. 不需要额外维护前端进程和后端服务通信协议。
替代方案:
1. 前端桌宠加 Python 服务:UI 灵活,但第一版多一个进程通信边界。
2. 仅 pipeline 服务:实现更快,但不满足桌宠状态反馈目标。
### Decision 2: Transport 第一版只支持本机麦克风和扬声器
选择:定义可扩展 Transport 接口,但第一版只规划本机输入输出。
理由:
1. 用户当前目标是桌面宠物,不是远程设备网关。
2. 本机 Transport 能覆盖完整语音闭环。
3. 避免第一版被 WebSocket、硬件协议和文件流复杂度拖慢。
替代方案:
1. 多 Transport 同时开发:测试能力更强,但超出第一期范围。
2. 文件回放优先:适合测试,但不能满足真实麦克风唤醒体验。
### Decision 3: 唤醒词固定为“小杰小杰”
选择:规范固定第一版唤醒词为“小杰小杰”,检测在本地完成。
理由:
1. 唤醒词是用户明确指定的产品入口。
2. 本地检测减少持续上传音频的隐私风险。
3. 固定关键词便于建立误唤醒和漏唤醒验收。
替代方案:
1. 按钮触发:降低模型风险,但不满足 hands-free 目标。
2. 英文预置热词:容易接入,但不符合中文桌宠产品体验。
### Decision 4: STT/TTS 优先本地,LLM 走云端
选择:STT/TTS 默认候选 `sherpa-onnx`LLM 使用 OpenAI Responses API streaming。
理由:
1. STT/TTS 本地化可以减少原始音频和回复音频的云端依赖。
2. LLM 云端可以获得更稳定的推理质量和更低工程风险。
3. Provider 接口可替换,后续可按效果调整模型栈。
替代方案:
1. 全云端:开发快,但隐私和成本风险更高。
2. 全本地:隐私好,但模型效果、体积和性能风险更高。
### Decision 5: Pipeline 用异步状态机驱动
选择:所有阶段通过状态机和事件串联,Provider 失败统一进入错误恢复。
理由:
1. 语音链路存在大量异步事件:音频帧、唤醒、VAD、LLM streaming、TTS 队列、播放完成。
2. 状态机便于 UI 同步展示和测试断言。
3. 错误恢复路径清晰,避免某个阶段异常导致应用退出。
替代方案:
1. 顺序阻塞脚本:实现简单,但 UI 卡顿、延迟高、难以打断和恢复。
2. 多线程无状态共享:容易产生竞态和不可复现错误。
### Decision 6: 桌宠资产后续由 `imagegen` 生成,项目内保存
选择:优先用 `imagegen` 生成可爱 3D 透明 PNG,并在项目资产目录保存运行时引用的资产;若生成流程不可用或输出未通过角色/透明度验收,使用项目内可复现 PNG 生成脚本作为 fallback,并保留代码级资产校验入口。
理由:
1. 资产必须和 UI 状态绑定,不能只做临时预览图。
2. 项目引用的资产不能留在 `$CODEX_HOME` 生成目录。
3. 透明背景资产需要本地验证边缘质量。
4. 生图输出不稳定时,确定性 fallback 比提交不合格资产更可验收。
替代方案:
1. 用占位 SVG:可快速开发,但不满足“桌宠照片自己用生图技能来做”。
2. 使用外部图片素材:版权和风格一致性不可控。
### Decision 7: 每个大模块完成后立即验证并提交
选择:以后续实施中的一级任务组和阶段里程碑作为强制提交边界。每完成一个大模块,必须先运行当前阶段适用的构建、测试、lint/type check 和 OpenSpec 校验,再使用中文格式提交,格式为“`[模块名]:完成[具体功能描述],包含[关键变更]`”。
理由:
1. 该项目从空仓库开始,模块边界清晰提交可以避免大量中间状态堆积。
2. 构建先于提交可以确保每个里程碑都是可回退的稳定点。
3. 中文固定格式能让后续审计和阶段回溯更直接。
替代方案:
1. 最后统一提交:减少提交数量,但会隐藏中间失败和跨模块混杂变更。
2. 随手小提交:回退粒度细,但不能保证每个提交对应可验收模块。
## Architecture
```text
AudioTransport(input)
-> WakeWordProvider
-> VadProvider
-> SttProvider
-> ConversationContext
-> LlmProvider
-> SentenceBuffer
-> TtsProvider
-> AudioTransport(output)
Pipeline State Machine
-> PetUI State Adapter
-> PetWindow / PetSpriteView / SettingsPanel / ErrorToast
Config Loader
-> Provider config
-> Audio device config
-> LLM config
-> Asset paths
Structured Logger
<- state transitions
<- provider latency
<- error codes
```
## Data Flow
1. 应用启动后进入 `idle`,完成配置校验和 Provider 预加载。
2. 输入 Transport 持续提供音频帧。
3. WakeWordProvider 检测到“小杰小杰”后触发 `WakeEvent`
4. VAD 开始端点检测,确认用户说话后聚合语音片段。
5. STT 转写音频片段,生成用户文本。
6. ConversationContext 添加用户消息并构造 LLM messages。
7. LLM Provider 返回流式 delta。
8. SentenceBuffer 按分句边界输出 TTS 文本块。
9. TTS Provider 合成音频块。
10. 输出 Transport 播放音频块。
11. UI 订阅状态变化,不直接调用 Provider。
## Interfaces
### AudioTransport
```text
start_input(device_id, sample_rate, channels) -> None
read_frames(timeout_ms) -> list[AudioFrame]
play_pcm(segment, interrupt=false) -> PlaybackResult
stop() -> None
health() -> TransportHealth
```
### WakeWordProvider
```text
load(model_path, keyword) -> None
detect(frame) -> WakeEvent | None
reset() -> None
```
### VadProvider
```text
load(model_path) -> None
analyze(frame) -> VadResult
reset() -> None
```
### SttProvider
```text
load(model_path, language) -> None
transcribe(segment) -> Transcript
```
### LlmProvider
```text
stream_reply(messages, model, timeout_s) -> AsyncIterator[ReplyDelta]
```
### TtsProvider
```text
load(model_path, voice) -> None
synthesize(text) -> AudioSegment
```
## Risks / Trade-offs
1. 中文唤醒词效果风险 -> Provider 可替换,先定义误唤醒和漏唤醒验收。
2. 本地模型性能风险 -> 模型预加载,阶段耗时日志,必要时替换 Provider。
3. LLM 网络依赖风险 -> 统一错误码,超时配置,错误恢复状态。
4. TTS 自回声风险 -> 播放期间默认抑制 Wake/VAD。
5. UI 与音频并发风险 -> UI 只订阅事件,不阻塞 pipeline。
6. 空仓库范围膨胀风险 -> 本阶段只写 OpenSpec,第一版只支持本机 Transport。
7. 未提交中间状态风险 -> 大模块完成后强制先验证再提交,失败时不得提交。
## Migration Plan
当前无既有数据和源码,不需要迁移。后续实现按 tasks.md 分阶段落地。若引入持久化配置或历史记录,必须保证旧配置有默认值或明确启动错误,并另起 OpenSpec 变更处理长期记忆。
## Open Questions
1. `sherpa-onnx` 是否作为唤醒词、STT、TTS 三者统一默认实现,还是只作为 STT/TTS 默认候选。
2. OpenAI Responses API 默认模型名和预算限制。
3. 桌宠角色的名字、颜色、性格和禁止元素。
4. 是否需要在播放期间支持用户语音打断。
@@ -0,0 +1,626 @@
# 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 PCMProvider 边界必须明确转换。
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 能力规范和工程计划,再进入实现。
## 实施计划
### 阶段 0OpenSpec 计划阶段
范围:
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/NewAPIAPI key 只从环境变量 `OWNER_LLM_API_KEY` 读取;真实本地 STT/TTS 采用可选 Provider,自动化测试使用 deterministic local/mock Provider;桌宠资产使用可爱 3D 风格透明 PNG;播放期间不支持用户打断。
@@ -0,0 +1,264 @@
## ADDED Requirements
### Requirement: Python desktop pet runtime
The system SHALL be specified as a local Python desktop pet application that owns the voice pipeline, desktop pet state, local audio input, and local audio output in the first version.
#### Scenario: App starts in local desktop mode
- **WHEN** the user starts the future application on macOS
- **THEN** the system SHALL initialize as a local desktop pet process rather than a remote service or browser-only tool
#### Scenario: Implementation follows OpenSpec artifacts
- **WHEN** this OpenSpec change is implemented
- **THEN** the repository SHALL contain Python source code, automated tests, validation commands, and project-local pet assets aligned with the planning artifacts
### Requirement: Local microphone and speaker transport
The system SHALL use the local microphone as the first-version input Transport and the local system speaker as the first-version output Transport.
#### Scenario: Microphone input is available
- **WHEN** the configured microphone is available and permitted
- **THEN** the Transport SHALL provide streaming audio frames to the wakeword and VAD stages
#### Scenario: Speaker output is available
- **WHEN** TTS returns a valid audio segment and the configured speaker is available
- **THEN** the Transport SHALL play the audio segment through the local speaker
#### Scenario: Audio device is unavailable
- **WHEN** the microphone or speaker is missing, denied, or unsupported
- **THEN** the system SHALL expose a recoverable Transport error with a stable error code
### Requirement: Wake word detection
The system SHALL listen locally for the Chinese wake word “小杰小杰” before accepting user speech for a conversation turn.
#### Scenario: Wake word is detected
- **WHEN** the user says “小杰小杰” and the wakeword provider returns confidence above the configured threshold
- **THEN** the pipeline SHALL transition from wake listening to speech detection
#### 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
### Requirement: VAD speech endpoint detection
After wakeword detection, the system SHALL use VAD to identify when the user starts and stops speaking.
#### Scenario: User begins speaking
- **WHEN** VAD detects continuous speech above the configured start threshold
- **THEN** the pipeline SHALL begin recording the current utterance
#### Scenario: User stops speaking
- **WHEN** VAD detects continuous silence above the configured end threshold
- **THEN** the pipeline SHALL close the current audio segment and send it to STT
#### Scenario: User says nothing after wakeword
- **WHEN** no speech is detected before the configured no-speech timeout
- **THEN** the pipeline SHALL return to wake listening without invoking STT or LLM
#### Scenario: Recording exceeds maximum duration
- **WHEN** speech continues beyond the configured maximum utterance duration
- **THEN** the pipeline SHALL end the segment, mark the end reason, and continue to STT with the captured audio
### Requirement: Local STT transcription
The system SHALL transcribe the captured user utterance through a local STT provider, with `sherpa-onnx` as the recommended first implementation candidate.
#### Scenario: STT succeeds
- **WHEN** STT returns non-empty text for a captured audio segment
- **THEN** the pipeline SHALL add the trimmed text as a user message to the conversation context
#### Scenario: STT returns empty text
- **WHEN** STT returns empty text, punctuation-only text, or an invalid transcript
- **THEN** the pipeline SHALL skip LLM invocation and return to wake listening with a recoverable status
#### Scenario: STT provider fails
- **WHEN** the STT provider raises an error or cannot load its local model
- **THEN** the system SHALL emit an STT error code and SHALL recover to a state where future wake attempts are possible
### Requirement: Conversation context management
The system SHALL maintain an in-memory conversation context for the active desktop pet session.
#### Scenario: User transcript is accepted
- **WHEN** a valid STT transcript is produced
- **THEN** the system SHALL append it to the context as a user message before invoking the LLM
#### Scenario: Assistant reply completes
- **WHEN** the LLM and TTS stages complete a reply
- **THEN** the system SHALL append the assistant text to the context
#### Scenario: Context exceeds configured budget
- **WHEN** the context exceeds the configured message, character, or token budget
- **THEN** the system SHALL preserve the system prompt and most recent conversation turns while removing older ordinary messages
### Requirement: Cloud LLM streaming reply
The system SHALL use a cloud LLM provider for reply generation, with OpenAI Responses API streaming as the recommended first implementation.
#### Scenario: LLM stream begins
- **WHEN** the LLM provider receives valid context messages and configuration
- **THEN** it SHALL stream reply deltas rather than waiting for the full reply before producing output
#### Scenario: LLM model is configured
- **WHEN** the system starts
- **THEN** the LLM model name SHALL be read from configuration and SHALL NOT be hard-coded in pipeline logic
#### Scenario: LLM request fails
- **WHEN** the LLM provider times out, is rate limited, lacks credentials, or encounters a network error
- **THEN** the pipeline SHALL emit a structured LLM error and transition through error recovery to wake listening
### Requirement: Local TTS synthesis and playback
The system SHALL synthesize assistant replies through a local TTS provider, with `sherpa-onnx` as the recommended first implementation candidate.
#### Scenario: Sentence is ready for speech
- **WHEN** the LLM stream produces a complete sentence or configured text chunk
- **THEN** the TTS provider SHALL synthesize that text into an audio segment
#### Scenario: First TTS segment is ready
- **WHEN** the first synthesized audio segment is available
- **THEN** the output Transport SHALL begin playback without waiting for every remaining LLM delta
#### Scenario: TTS fails
- **WHEN** TTS returns empty audio or raises an error
- **THEN** the pipeline SHALL emit a structured TTS error and SHALL recover without terminating the application
### Requirement: Pipeline state machine
The system SHALL expose deterministic pipeline states for `idle`, `wake_listening`, `speech_detecting`, `recording`, `transcribing`, `thinking`, `speaking`, `interrupted`, and `error_recovering`.
#### Scenario: Normal conversation turn
- **WHEN** wakeword, VAD, STT, LLM, TTS, and playback all succeed
- **THEN** the state sequence SHALL progress through wake listening, speech detection, recording, transcribing, thinking, speaking, and back to wake listening
#### Scenario: Recoverable provider error
- **WHEN** any provider fails during a conversation turn
- **THEN** the state machine SHALL enter error recovery and then return to wake listening after cleanup
#### Scenario: UI subscribes to state
- **WHEN** the pipeline state changes
- **THEN** the desktop pet UI SHALL be able to update its visible state without directly invoking audio or model providers
### Requirement: Desktop pet visual states
The system SHALL define desktop pet visual states for idle, listening, recording, thinking, speaking, and error feedback.
#### Scenario: Wakeword is detected
- **WHEN** the pipeline enters speech detection or recording
- **THEN** the desktop pet SHALL show a listening or recording visual state
#### Scenario: LLM is generating
- **WHEN** the pipeline enters thinking
- **THEN** the desktop pet SHALL show a thinking visual state
#### Scenario: TTS is playing
- **WHEN** the pipeline enters speaking
- **THEN** the desktop pet SHALL show a speaking visual state
#### Scenario: Error occurs
- **WHEN** the pipeline enters error recovery
- **THEN** the desktop pet SHALL show a concise visible error state and then return to idle or wake listening when recovered
### Requirement: Generated pet asset specification
The system SHALL specify that production desktop pet images are generated with the image generation skill when usable, or with a reproducible project-local fallback when image generation output is unavailable or fails validation, and saved inside the project as transparent PNG assets.
#### Scenario: Image generation output is usable
- **WHEN** pet images are generated with `imagegen` and pass role and transparency validation
- **THEN** the images SHALL be processed for transparency, validated, and saved to a project asset directory
#### Scenario: Image generation output is unavailable or invalid
- **WHEN** image generation output is unavailable, inaccessible as a project file, or fails role/transparency validation
- **THEN** the system SHALL use a reproducible project-local fallback to create transparent PNG pet state assets and SHALL validate them before use
#### Scenario: Planning phase is executed
- **WHEN** this OpenSpec change is implemented
- **THEN** generated or generated-derived pet assets SHALL be saved inside the project and SHALL NOT be referenced from a temporary generation directory
### Requirement: Audio feedback suppression
The system SHALL prevent the desktop pet's own TTS playback from being treated as a new user wakeword or speech input.
#### Scenario: TTS playback is active
- **WHEN** the output Transport is playing synthesized assistant speech
- **THEN** wakeword detection and VAD input processing SHALL be suppressed or ignored for that playback window by default
#### Scenario: User speaks during playback
- **WHEN** the user speaks while TTS playback is active in the first version
- **THEN** the system SHALL ignore that speech unless a future interruption feature is explicitly specified
### Requirement: Structured errors and logging
The system SHALL represent stage failures with structured error codes and log state transitions, latency metrics, and provider names.
#### Scenario: Stage transition occurs
- **WHEN** the pipeline changes state
- **THEN** the system SHALL log the turn ID, old state, new state, timestamp, and triggering event
#### Scenario: Provider call completes
- **WHEN** a provider call succeeds or fails
- **THEN** the system SHALL log provider name, stage, duration, and error code if present
#### Scenario: Sensitive data is present
- **WHEN** logs are written
- **THEN** the system SHALL NOT log API keys, authorization headers, or raw credentials
### Requirement: Performance targets
The system SHALL define measurable first-version latency and reliability targets for wakeword response, endpoint detection, LLM first output, and speech playback.
#### Scenario: Wakeword latency is measured
- **WHEN** wakeword detection succeeds in a normal local environment
- **THEN** the system SHALL target visible listening feedback within 800 ms
#### Scenario: User stops speaking
- **WHEN** the user stops speaking after a normal utterance
- **THEN** VAD endpoint detection SHALL target transition to transcription within 900 ms
#### Scenario: LLM begins responding
- **WHEN** a valid transcript has been sent to the LLM provider
- **THEN** the system SHALL target first playable reply text within 3.5 seconds
#### Scenario: Normal reply is spoken
- **WHEN** a normal Chinese question under 10 seconds is processed
- **THEN** the system SHALL target the start of spoken playback within 5 seconds P95 after the user stops speaking
### Requirement: Security and privacy
The system SHALL protect credentials and minimize audio persistence.
#### Scenario: API key is required
- **WHEN** the LLM provider needs an OpenAI API key
- **THEN** the key SHALL be loaded from environment or local uncommitted configuration and SHALL NOT be committed
#### Scenario: Audio is processed
- **WHEN** user speech is captured for STT
- **THEN** raw audio SHALL NOT be persisted by default
#### Scenario: Cloud LLM is invoked
- **WHEN** the system sends data to the cloud LLM
- **THEN** it SHALL send transcript text and necessary conversation context, not raw microphone audio
### Requirement: Testability
The system SHALL be designed so each stage can be tested with mock providers and file-based audio fixtures.
#### Scenario: Pipeline is unit tested
- **WHEN** mock providers produce deterministic events
- **THEN** tests SHALL verify state transitions, context updates, and error recovery
#### Scenario: Audio fixture is replayed
- **WHEN** a file-based audio fixture containing “小杰小杰” and user speech is replayed in a test Transport
- **THEN** the pipeline SHALL be testable without using a live microphone
#### Scenario: OpenSpec planning validation runs
- **WHEN** this planning change is complete
- **THEN** `openspec validate add-voice-pet-pipeline --strict` and `openspec validate --all --strict` SHALL pass
### Requirement: Module-level git commit gates
The system implementation process SHALL require an immediate Git commit after each major module or milestone is completed, where a major module means a top-level task group from `tasks.md` or a phase milestone from `proposal.md`.
#### Scenario: Major module is completed
- **WHEN** an implementer completes a top-level task group or milestone phase
- **THEN** the implementer SHALL run the applicable build, tests, lint/type checks, and OpenSpec validation before committing
#### Scenario: Build validation fails before commit
- **WHEN** the applicable build, tests, lint/type checks, or OpenSpec validation fail
- **THEN** the implementer SHALL NOT create the module commit until the failure is fixed and validation passes
#### Scenario: Module commit is created
- **WHEN** validation passes for the completed module
- **THEN** the implementer SHALL immediately create a Git commit using the Chinese message format “[模块名]:完成[具体功能描述],包含[关键变更]”
#### Scenario: Intermediate work remains after module completion
- **WHEN** module-related changes remain unstaged or uncommitted after the module is completed
- **THEN** the implementer SHALL include those changes in the module commit or explicitly separate unrelated external changes before continuing to the next module
@@ -0,0 +1,51 @@
# Python 桌宠语音 Pipeline 实施任务
## 1. OpenSpec 与项目骨架
- [x] 1.1 将 OpenSpec 工件从“只写计划”修正为“实施并验收 change”;前置条件:确认最新目标要求完整完成;验收标准:proposal、design、spec 不再把文档完成当作最终交付;测试要点:`openspec validate add-voice-pet-pipeline --strict` 通过;优先级:P0;预计:30 分钟。
- [x] 1.2 创建 Python 3.11+ 项目骨架;前置条件:本机 `python3.11` 可用;验收标准:存在 `pyproject.toml`、包目录、测试目录和命令入口;测试要点:`python3.11 -m compileall src tests` 通过;优先级:P0;预计:45 分钟。
- [x] 1.3 定义核心数据模型、状态枚举、错误码和 Provider 协议;前置条件:项目骨架已创建;验收标准:AudioFrame、AudioSegment、Transcript、Message、ReplyDelta、PipelineState、ProviderError 可被测试导入;测试要点:单元测试覆盖基础构造和错误码;优先级:P0;预计:60 分钟。
- [x] 1.4 实现配置加载和密钥边界;前置条件:核心模型已定义;验收标准:base URL、API key、模型名、设备、资产路径均来自环境变量或配置,API key 不写入仓库;测试要点:缺失 key 时返回结构化配置错误;优先级:P0;预计:45 分钟。
- [x] 1.5 完成“OpenSpec 与项目骨架”模块提交;前置条件:1.1 至 1.4 已完成;验收标准:先通过 compileall、单元测试和 OpenSpec strict 校验,再立即执行 Git commit;测试要点:提交信息使用“`[OpenSpec 与项目骨架]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。
## 2. 音频 Transport
- [x] 2.1 实现 `AudioTransport` 协议和本机音频错误对象;前置条件:核心模型已提交;验收标准:协议包含输入启动、帧读取、PCM 播放、停止、健康检查;测试要点:协议和错误码可导入;优先级:P0;预计:45 分钟。
- [x] 2.2 实现内存/文件回放 Transport;前置条件:AudioTransport 协议已定义;验收标准:测试可注入音频帧并捕获播放结果,不依赖真实麦克风;测试要点:回放帧顺序、超时和播放队列通过单元测试;优先级:P0;预计:60 分钟。
- [x] 2.3 实现可选本机 Transport 适配层;前置条件:Transport 协议已定义;验收标准:未安装音频依赖或无设备时返回结构化错误,不影响测试模式;测试要点:无依赖环境下导入不崩溃;优先级:P1;预计:60 分钟。
- [x] 2.4 实现音频环形缓冲和格式校验;前置条件:音频模型和 Transport 已定义;验收标准:保留最近音频帧并校验采样率、声道、时间戳;测试要点:缓冲容量、顺序和非法帧测试通过;优先级:P0;预计:60 分钟。
- [x] 2.5 完成“音频 Transport”模块提交;前置条件:2.1 至 2.4 已完成;验收标准:先通过相关测试、compileall 和 OpenSpec strict 校验,再立即执行 Git commit;测试要点:提交信息使用“`[音频 Transport]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。
## 3. Wake/VAD/STT
- [x] 3.1 实现本地唤醒词 Provider;前置条件:Transport 测试路径可用;验收标准:支持“小杰小杰”关键词事件和置信度阈值;测试要点:命中、未命中、模型失败场景通过;优先级:P0;预计:60 分钟。
- [x] 3.2 实现 VAD Provider 和端点检测;前置条件:音频帧可回放;验收标准:检测说话开始、连续静音结束、无语音超时和最大录音保护;测试要点:四类 VAD 场景通过;优先级:P0;预计:60 分钟。
- [x] 3.3 实现 STT Provider 协议、测试 Provider 和可选本地模型适配入口;前置条件:AudioSegment 可构造;验收标准:测试 Provider 能从 fixture metadata 转写,可选本地模型缺失时返回结构化错误;测试要点:成功、空文本、Provider 失败场景通过;优先级:P0;预计:60 分钟。
- [x] 3.4 实现 STT 文本验证规则;前置条件:STT Provider 已实现;验收标准:空文本、纯标点、过短音频不会进入 LLM;测试要点:文本验证和 pipeline 跳过 LLM 测试通过;优先级:P0;预计:45 分钟。
- [x] 3.5 完成“Wake/VAD/STT”模块提交;前置条件:3.1 至 3.4 已完成;验收标准:先通过相关测试、compileall 和 OpenSpec strict 校验,再立即执行 Git commit;测试要点:提交信息使用“`[Wake/VAD/STT]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。
## 4. LLM/TTS 与对话闭环
- [x] 4.1 实现 ConversationContext;前置条件:Message 模型已定义;验收标准:追加 user/assistant、构造 LLM messages、按预算截断;测试要点:上下文保留 system prompt 和最近轮次;优先级:P0;预计:45 分钟。
- [x] 4.2 实现 OpenAI/NewAPI 兼容 LLM Provider;前置条件:配置加载已完成;验收标准:base URL、API key、model 均配置化,支持流式或非流式解析,不记录密钥;测试要点:mock HTTP 和可选真实 smoke 测试通过;优先级:P0;预计:60 分钟。
- [x] 4.3 实现分句缓冲;前置条件:LLM delta 模型已定义;验收标准:中文标点、换行和长度阈值触发 TTS chunk;测试要点:空分句、超长句、连续 delta 测试通过;优先级:P0;预计:45 分钟。
- [x] 4.4 实现本地 TTS Provider;前置条件:AudioSegment 模型和 Transport 可用;验收标准:提供 macOS `say` 可选 Provider 和 deterministic 测试 Provider;测试要点:非空文本生成非空音频,空文本报错;优先级:P0;预计:60 分钟。
- [x] 4.5 实现 Pipeline 状态机和自抑制;前置条件:Wake/VAD/STT、Context、LLM、TTS、Transport 均可测试;验收标准:正常链路、空转写、LLM 失败、TTS 失败、播放期自抑制均可验证;测试要点:状态序列和错误恢复测试通过;优先级:P0;预计:60 分钟。
- [x] 4.6 完成“LLM/TTS 与对话闭环”模块提交;前置条件:4.1 至 4.5 已完成;验收标准:先通过相关测试、compileall、真实或 mock LLM smoke 和 OpenSpec strict 校验,再立即执行 Git commit;测试要点:提交信息使用“`[LLM/TTS 与对话闭环]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。
## 5. 桌宠 UI 与生图资产
- [x] 5.1 实现桌宠状态控制器;前置条件:PipelineState 已定义;验收标准:idle/listening/recording/transcribing/thinking/speaking/error 状态映射到可显示 UI 状态;测试要点:状态映射单元测试通过;优先级:P0;预计:45 分钟。
- [x] 5.2 实现可选桌面窗口入口和无 GUI fallback;前置条件:状态控制器已实现;验收标准:未安装 PySide6 时 CLI/测试不崩溃,安装后保留透明置顶窗口入口;测试要点:无 PySide6 环境导入和 fallback 测试通过;优先级:P1;预计:60 分钟。
- [x] 5.3 使用 `imagegen` 或可复现 fallback 生成可爱 3D 桌宠透明资产并保存到项目;前置条件:资产目录已定义;验收标准:项目中存在 idle/listening/thinking/speaking/error 可引用 PNG,且生成失败时有明确 fallback;测试要点:资产存在性和 alpha/格式校验通过;优先级:P0;预计:60 分钟。
- [x] 5.4 实现资产清单和校验命令;前置条件:资产已保存;验收标准:运行验收命令可检查资产路径、状态覆盖和透明度元数据;测试要点:缺失资产时测试失败;优先级:P0;预计:45 分钟。
- [x] 5.5 完成“桌宠 UI 与生图资产”模块提交;前置条件:5.1 至 5.4 已完成;验收标准:先通过相关测试、资产校验、compileall 和 OpenSpec strict 校验,再立即执行 Git commit;测试要点:提交信息使用“`[桌宠 UI 与生图资产]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。
## 6. 测试、性能、安全、验收与归档
- [x] 6.1 完成自动化测试套件;前置条件:所有模块已实现;验收标准:覆盖核心模型、配置、Transport、Wake/VAD/STT、Context、LLM/TTS、Pipeline、UI、资产;测试要点:`python3.11 -m unittest discover -s tests` 通过;优先级:P0;预计:60 分钟。
- [x] 6.2 完成端到端验收命令;前置条件:Pipeline 可用;验收标准:fixture 音频链路可从“小杰小杰”走到播放输出和上下文更新;测试要点:CLI acceptance 命令退出码为 0;优先级:P0;预计:45 分钟。
- [x] 6.3 完成 OpenAI/NewAPI smoke 验收;前置条件:提供临时 API key 环境变量;验收标准:调用配置的 base URL 获得非空 LLM 回复,失败时输出结构化诊断且不泄露 key;测试要点:真实 smoke 或可解释的网络失败证据;优先级:P0;预计:45 分钟。
- [x] 6.4 完成安全检查;前置条件:全部代码已实现;验收标准:仓库中不包含 API key,日志脱敏,默认不持久化原始音频;测试要点:secret grep、配置测试和日志脱敏测试通过;优先级:P0;预计:30 分钟。
- [x] 6.5 完成 OpenSpec archive;前置条件:所有任务完成并验证通过;验收标准:`openspec archive add-voice-pet-pipeline` 后主 spec 更新,change 进入 archive;测试要点:`openspec validate --all --strict` 通过;优先级:P0;预计:30 分钟。
- [x] 6.6 完成最终验收提交;前置条件:6.1 至 6.5 已完成;验收标准:完整测试、OpenSpec strict 校验、git status 审计通过后立即 commit;测试要点:提交信息使用“`[测试、性能、安全、验收与归档]:完成[具体功能描述],包含[关键变更]`”格式;优先级:P0;预计:20 分钟。