编辑 | blame | 历史 | 原始文档

Phase 4 测试

对应施工单: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。


0. 开始前

  1. Liquibase 新 changeset:expected_text / metrics / assessed_at。**没有** passed 列。未改 202608311800.sql。
  2. 音频契约(至少 unit01 两句):
/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}。


1. pytest(必须先过)

用例 通过?
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

2. 语音:序列时钟(核心)

从 Chat 说 开始跟读。记下 01 播完到 02 响起的间隔,应大约是 ratio × 01 的时长(不要短到「一说完就切」)。

检查 通过?
01 播完后立刻进入可跟读(不要先讲笑话)
听窗内认真跟读:02 不会立刻响,要等到时钟
听窗内说「今天天气怎么样」或不说话:时钟到了仍播 02,**不**再播 01
说 退出跟读 可立刻回 Chat
整段结束后招呼回 Chat;**讲个笑话**可用

3. 失败不得挡播放(必须)

场景 通过?
停 manage_api,跟读至少两句:02 仍会在时钟后出现
无 01.txt / 无 transcripts:播放两句仍完成;metrics 可空
全程闭嘴:两句仍按时钟播完(turns 的 asr 可空)

恢复 manage_api。


4. 后评估对账(允许晚几秒)

跟读至少一句(有说话),等会话结束或再等几秒,GET {id}。

检查 通过?
prompt_text 仍以 .mp3 结尾
expected_text 等于对应 txt 正文(不是路径)
若有 asr:metrics 含 method=text_similarity_v0 且 value 在 0–1
JSON 与 GET 没有 passed / grade / 「正确」字段
列表接口仍不返回整份 turns

说错那句:value 可以很低——这是定量结果,**不要**因此期望设备重播。


5. Agent ≠ App;复读

检查 通过?
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

6. 固件

本阶段跟读评估仍不改固件协议。**允许的补充:** 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)

7. 不要测什么

  • 说错重播同一句(那是已废弃草案)
  • 「>82% 算好」的产品结论
  • 倒背 / 口算 / 接龙 / 心理诊断
  • 家长 UI、租户
  • 把切换词写入固件

8. 失败时先看哪

现象 多半是
一说完就切下一句 ASR 仍在 handle_shadowing_turn 里 play next(未解耦)
说错重播 01 把 S8 retry 做回去了
停 API 后卡在第一句 upsert/assess 又变成同步 await
metrics 有 passed DTO 没按计划
expected 是路径 用 prompt_text 当了原文
时钟明显短于 1.2× 仍用固定 800ms 而忽略 duration_ms

口令(与 Phase 2 相同)

意图 说
跟读 开始跟读
复读 复读机
回聊 退出跟读 / 我们来聊天吧