对应施工单:
phase-4-plan.md
代码事实:phase-3-review.md
语音回归:phase-3-test.md§1 +phase-2-voice-test.md§0 / §5
固件 不必重烧
本阶段验收:**跟读是 MP3 序列 + 定时录音**;分析失败不影响下一句;后评估只有定量 metrics,没有及格结论。
不要测当场「说错重播」、倒背/口算产品、音素、租户。
相对 Phase 2 口令表的刻意变化:听窗内说完 不会马上 下一句,要等到 listen_ratio × 该句 duration_ms。
expected_text / metrics / assessed_at。**没有** passed 列。未改 202608311800.sql。/english_learning/shadowing/lessons/unit01/01.mp3
/english_learning/shadowing/lessons/unit01/01.txt
/english_learning/shadowing/lessons/unit01/02.mp3
/english_learning/shadowing/lessons/unit01/02.txt
云端 transcripts 或 sentences[] 与上述 txt 正文一致(热路径不读卡上 txt 也可以测评估,但 stem 必须能对上)。
3. listen_ratio 在 1.2–1.5。
4. pytest:
cd xiaozhi-esp32-server/main/xiaozhi-server
python -m pytest tests/test_app_runtime.py tests/test_assessment.py -q
准备 server secret、mac。GET:/xiaozhi/task/instance/{id}。
| 用例 | 通过? |
|---|---|
resolve_expected_text:basename 01.mp3 → txt 正文;**路径字符串不得当原文** |
|
text_similarity_v0 返回 {method, value},**对象不含** passed/grade |
|
评估缺 asr 或缺 expected → metrics=[],不抛 |
|
| 模拟听窗:注入 ASR 不触发 play next;只有 timer 回调才 index+1 | |
| timer 回调在 ASR 失败、assess 抛错、upsert 抛错后仍 index+1 | |
listen_ratio 0.5 → 夹紧到 1.2;3.0 → 夹紧到 1.5 |
|
| Phase 3:upsert 调度失败仍能 save |
从 Chat 说 开始跟读。记下 01 播完到 02 响起的间隔,应大约是 ratio × 01 的时长(不要短到「一说完就切」)。
| 检查 | 通过? |
|---|---|
| 01 播完后立刻进入可跟读(不要先讲笑话) | |
| 听窗内认真跟读:02 不会立刻响,要等到时钟 | |
| 听窗内说「今天天气怎么样」或不说话:时钟到了仍播 02,**不**再播 01 | |
| 说 退出跟读 可立刻回 Chat | |
| 整段结束后招呼回 Chat;**讲个笑话**可用 |
| 场景 | 通过? |
|---|---|
| 停 manage_api,跟读至少两句:02 仍会在时钟后出现 | |
| 无 01.txt / 无 transcripts:播放两句仍完成;metrics 可空 | |
| 全程闭嘴:两句仍按时钟播完(turns 的 asr 可空) |
恢复 manage_api。
跟读至少一句(有说话),等会话结束或再等几秒,GET {id}。
| 检查 | 通过? |
|---|---|
prompt_text 仍以 .mp3 结尾 |
|
expected_text 等于对应 txt 正文(不是路径) |
|
若有 asr:metrics 含 method=text_similarity_v0 且 value 在 0–1 |
|
JSON 与 GET 没有 passed / grade / 「正确」字段 |
|
| 列表接口仍不返回整份 turns |
说错那句:value 可以很低——这是定量结果,**不要**因此期望设备重播。
| 检查 | 通过? |
|---|---|
| Chat 笑话只进 chat_history | |
| 复读:无 metrics 要求;结束仍只招呼一次 | |
复读默认会带 speed(省略则为按天数算出的值,第一天约 0.8) |
|
repeat=5 时每一遍倍速约 0.8→0.9→1.0→1.1→1.2,**不会**卡在 0.75/1.0 档 |
|
显式指定 speed=1.05 时五遍都用 1.05 |
本阶段跟读评估仍不改固件协议。**允许的补充:** play_audio.speed 从离散档改为 [0.5, 2.0] 夹紧(重采样本来就支持任意倍率)。
| 检查 | 通过? |
|---|---|
| 无 JUDGE / 新 MQTT action / 新 MCP 工具名 | |
self.mp3.play 仍是 name/speed/repeat/power_off_after;speed 可传 0.8 / 1.05 |
|
| 说错后日志里 没有 对同一路径的立即第二次 play(只有时钟后的 02) |
| 现象 | 多半是 |
|---|---|
| 一说完就切下一句 | ASR 仍在 handle_shadowing_turn 里 play next(未解耦) |
| 说错重播 01 | 把 S8 retry 做回去了 |
| 停 API 后卡在第一句 | upsert/assess 又变成同步 await |
| metrics 有 passed | DTO 没按计划 |
| expected 是路径 | 用 prompt_text 当了原文 |
| 时钟明显短于 1.2× | 仍用固定 800ms 而忽略 duration_ms |
| 意图 | 说 |
|---|---|
| 跟读 | 开始跟读 |
| 复读 | 复读机 |
| 回聊 | 退出跟读 / 我们来聊天吧 |