对应施工单:
phase-3-plan.md
代码事实:phase-2-review.md
语音回归底表:phase-2-voice-test.md(本阶段固件 不必重烧,除非你还停留在 Phase 1 bin)
本阶段验收两件事:**库里能查到跟读/复读实例**,以及 Phase 2 语音行为没有回退。
不要测数字倒背、口算、成语接龙,不要测分数。
ai_task_instance / ai_task_step / ai_task_event)。/english_learning/shadowing/lessons/unit01/01.mp3
/english_learning/shadowing/lessons/unit01/02.mp3
/english_learning/repeater/lessons/unit01/full.mp3
cd xiaozhi-esp32-server/main/xiaozhi-server
python -m pytest tests/test_app_runtime.py -q
应通过(含 upsert 失败不阻断 save 的新用例)。不过这一关,不要上设备。
准备:
/agent/chat-history/report 相同)mac / device_id只跑 phase-2-voice-test.md 的这些节,勾选标准不变:
| 节 | 内容 | 通过? |
|---|---|---|
| §0 | 开机对话模式;唤醒;「今天天气怎么样」当闲聊 | |
| §2 | 跟读整段:播完再跟;不讲笑话;最后一轮回 Chat;JSON 含全部有效轮 | |
| §5-A | 复读自然结束 只招呼一次 | |
| §5-C | 播放中说「你好吗」继续播,不回答 |
本阶段 新增 的语音失败标准:
翻译 / 中英 / 离线:若时间紧可抽测「打开翻译 → 退出翻译 → 讲个笑话」。离线复读仍不得连云。
从对话模式说 开始跟读,按音频跟完(或至少两句后说 退出跟读)。
打开 xiaozhi-esp32-server/main/xiaozhi-server/data/task_sessions/ 最新 JSON:
| 检查 | 通过? |
|---|---|
task_session_id 为 32 位 hex |
|
app_id=english_learning,task_id=shadowing |
|
turns 条数 = 你跟读成功的句数(退出则少于音频总数) |
|
最后一轮 asr_text 不是空(若你说了最后一句) |
|
status / end_reason 为 completed+finished 或 cancelled+user_exit |
|
events 里有 audio.completed(每句播完) |
记下 task_session_id = {id}。
GET /task/instance/{id}
Authorization: Bearer {oauth token 或与 chat report 相同的 server secret}
(本阶段 Shiro 将 /task/instance/** 配成 server 过滤器,用 server secret 即可验通)
| 检查 | 通过? |
|---|---|
| HTTP 200 | |
status / end_reason / app_id / task_id 与 JSON 相同 |
|
turns(或响应里的 steps)条数与 JSON turns 相同 |
|
第 0 句 prompt_text 仍是 SD 路径,不是 TTS 英文范读 |
|
对应 asr_text 对得上你说的话(允许 ASR 误差) |
|
events 含 audio.completed |
若实现选了计划 §4.3-A:有效轮的 audio_id 非空,且能在 ai_agent_chat_audio 查到。选 B 则允许全空。
GET /task/instance?macAddress={mac}&limit=20
| 检查 | 通过? |
|---|---|
刚那条 {id} 出现在 list |
|
| list 项 没有 整份 turns 正文(避免把跟读当聊天记录 dump) |
说 复读机,等自然结束(或音频很短)。
| 检查 | 通过? |
|---|---|
新 JSON:task_id=repeater,end_reason=play_completed |
|
GET:turns 可以为空 |
|
GET:events 含 audio.completed |
|
| 设备只招呼回 Chat 一次(回归) |
中途 退出复读:end_reason 为 user_exit 或 play_stopped(与 Phase 2 谁先到有关),GET 能看到,不丢。
A. manage_api 不可用
停掉 manager-api(或改错密钥),再说 开始跟读 跟一句再 退出跟读。
| 检查 | 通过? |
|---|---|
| 设备仍播音频、仍能退出回 Chat | |
| 本地 JSON 仍然写出 | |
| xiaozhi-server 日志有 upsert 失败,**没有**把 WS 打挂 | |
| GET 可以 404 / 旧数据;这不算语音失败 |
测完恢复 manager-api。
B. 非法 PUT(可选)
用 server secret PUT 一个 app_id 含 ../ 或超长 id → 400,不写库。
在 对话模式 说「今天天气怎么样」或「讲个笑话」。
| 检查 | 通过? |
|---|---|
ai_agent_chat_history 仍增加聊天行(现网 report) |
|
没有因此新增一条 app_id=chat 的 task_instance(chat 不 begin_session;若实现误报了 chat instance 则失败) |
|
跟读 instance 仍然只在 /task/instance 查,不出现在「聊天记录就是音频」的错觉里 |
跟读说到第二句过程中,拔掉设备 Wi‑Fi 或杀 xiaozhi-server 连接。
| 检查 | 通过? |
|---|---|
本地 JSON end_reason=disconnected 或等价 cancelled |
|
| 若断线前至少成功 save/PUT 过一次:GET 能看到已有 turns,不是空实例把成功轮抹掉 |
(若断线发生在第一次 PUT 之前,库里可能没有行:以 JSON 为准,并在 PR 说明。)
| 检查 | 通过? |
|---|---|
xiaozhi-esp32-2.2.6 相对 Phase 2 无业务 diff(本阶段 PR 不应含该目录) |
|
| 未要求重烧 bin 即可完成 §1–§3 | |
未出现新的 WS type、新 MQTT action、新 MCP 名 |
新订阅 App 建目录属于 Phase 2 已交付能力,本阶段 不要当新功能再测一遍;若回归时可看连上后 SD 是否仍有 /english_learning/.../lessons。
TASK_PAUSE、wait Skill、本地录音上传/{app}/{task}/lessons 搬到 /resources/| 现象 | 多半是 |
|---|---|
| 跟读卡死 | upsert 同步阻塞;改为 end 时再 PUT 或后台任务 |
| JSON 有、GET 404 | Python 未挂钩 / Shiro 把 PUT 拦了 / 密钥不一致 |
| GET 200 但 turns 少最后一轮 | end_session 没在最后一轮 add_turn 之后 save(Phase 2 回归) |
| 复读结束招呼两次 | 不是 Phase 3 问题;固件又发了 [app_task]chat |
| chat 笑话进了 task_instance | begin_session 误绑 chat |
| Liquibase checksum 失败 | 改了旧 SQL;应只加新 changeset |
| 意图 | 说其中一句即可 |
|---|---|
| 唤醒 | 你好小智 |
| 跟读 | 开始跟读、我要跟读 |
| 复读 | 复读机、开始复读 |
| 回聊 | 退出跟读、退出复读、我们来聊天吧 |