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

Phase 4 之后:评估报告与更新后的 Roadmap

Date: 2026-09-18
对照:当前仓库代码 · roadmap.md(2026-09-02)· phase-1-review.md · phase-2-review.md · phase-3-review.md · phase-4-plan.md
产品决策:家长端(小程序)提前;小智硬件代际 / 多版本(原 Phase V)放到最后。
粒度:架构与项目管理。不是 Coding Plan。
下一编码阶段:phase-7a-plan.md——家长任务下发(场景+立即/计划;RTC 只写最近一次)+ 执行记录(后,无计量 UI)。

原则不变:每完成一个 Phase → Completion Review → Architecture 更新 → Roadmap Review → 下一份 Phase Plan。
本文取代旧路线图里「下一编码 = Phase 4 / Phase V 可与 4 并行」的排序。


0. 一页结论

跟读/复读已经能在**这一款**色宁设备上完整跑完:清单一次下发、设备本地播→听→下一句、后评估定量、本地 wav + 同名 txt、Instance 可入库。缺的不是再做一个英语 App,而是**家长能设一天的计划,设备自主开/关/切模式**;历史记录第二刀,分数先不给家长看。

判断 说明
Phase 1–4 产品骨架 可用。 对话 / 翻译 / 中英 / 复读 / 跟读已在设备上验收通过(锁定路径见 .cursor/rules)
Phase 4 相对原施工单 目标达成,执行器换了地方。 步进权威从「Server 时钟 + 每句 MCP」改成「设备播放列表循环」;后评估仍在 Server,不指挥喇叭
演示闭环缺口 无云端计划表;闹钟 pending 只有一份;已开机到点不切模式;课文写死;manager-mobile 无计划页;Instance GET 仅 server secret
下一刀 Phase 7a 家长任务下发。全表同步、RTC 只写下一时刻;立即优先于计划;History 后做;metrics UI 不做
原 Phase V 最后做。 现网只有一款硬件;hello 代际字段不增加演示能力

不要在 7a 之前开倒背/口算/接龙、不要做摄像头兼容矩阵、不要把课文下载平台化当成选课前提(SD 手工拷卡 + catalog 与卡上路径一致即可)。不要先做分数页。


1. 总进度(对照旧 Roadmap)

编号 旧 Roadmap 一句话 代码事实(2026-09-18) 状态
Phase 1 薄 App Runtime apps.json + start_app_task;CHAT / PLAY_AUDIO / TRANSLATION / BILINGUAL / SHADOWING;MQTT START_APP_TASK;离线口令挂钩 完成(有债,见 §2.1)
Freeze Capability Spec v1.0 文档冻结 完成
Phase 2 事件 + 采集 [skill_event] + duration_ms;TaskSession JSON;SD /{app}/{task}/lessons 完成(跟读已不是 TTS 范读)
Phase 3 TaskInstance 持久化 三表 + PUT/GET;report 可回写 audio_id 完成(GET 鉴权不对家长;见 §2.3)
Phase 4 跟读时钟 + 后评估 设备播放列表 + listen_ratio 听窗;expected_text / metrics;本地 data/recordings + 同名 .txt 完成(与原文「固件零 diff」不符,见 §3)
Phase V 产品代际 + 端云矩阵 无 generation/revision/cap_set 未做 → 调到最后
Phase 4b 倒背 / 口算 / 接龙 inline 仅 apps.json 意图槽预留 未做 → 演示闭环之后
Phase 5 Server Scheduler 设备 RTC + NVS pending;**无**云端日历表 未做 → 权威表并入 7a-B;5 剩到点补推
Phase 6 资源 DOWNLOAD + 在线 Locale 只有 mkdir;词表仍硬编码中文 未做
Phase 6b 离线 Locale OTA 仅现网中文 MultiNet 未做
Phase 7 家长任务管理 manager-mobile 无计划/Instance UI 未做 → 7a 下发优先、历史后置
Phase 8 / 9 开发者 / 订阅 — 后置
已落地(设备可用)
  chat.conversation · 翻译 · 中英对话 · 复读清单 · 跟读清单
  TaskSession JSON · Instance 三表 · 后评估 metrics · 本地 wav/txt

未落地(挡住「真实使用」)
  家长:按场景创建任务(泛听/跟读/双语…);立即执行或周期计划
  固件:本地计划全表;RTC 只编程最近一次;已开机到点执行
  云端 ai_parent_task + SYNC_SCHEDULE(不要 10 槽 SET_ALARM 一对一)
  历史:oauth2 读 Instance / 聊天(7a-D,无 metrics)

2. 已完成 Phase 的缺失(不要假装没有)

只列**仍影响产品或后续施工**的缺口。已在对应 *-review.md 写过、且不再挡演示的过程债(例如「Phase 1 无 pytest」)从略——Phase 2 之后已有 pytest。

2.1 Phase 1 仍缺

缺口 现状 是否挡 7a / 演示
家长小程序 UI manager-mobile = 智能体列表 + 配网 + 聊天记录,不是练习控制台 挡。 7a 本体
Server Schedule 权威表 只有设备 SET_ALARM(软件最多 10 条)+ 唯一 NVS pending 挡。 7a:云端任务表 + 设备全表;DS3231 只写下一时刻。不要把 ALARM_MAX_NUM 当家长上限
冷场多次主动开口 进入对话有欢迎词;可配循环催促未做 不挡
非 Sening 板复读 Task Gateway 仅 sening-xiaozhi-s3 不挡(就这一款)
双技能表 固件 k_task_skills[] + Server apps.json 不挡;禁止再为新 App 扩固件表
离线/在线状态分叉 没网复读结束后 Server 仍可能以为在 CHAT 演示保持在线

2.2 Phase 2 仍缺

缺口 现状 是否挡 7a / 演示
D8 v1.1 本地文件上传 跟读 wav 在 xiaozhi-server 磁盘;chat report 另走 BLOB 家长播录音依赖现有 chat_audio 或 只展示文本。完整听原声可后做
D11 wait Skill 听窗在设备墙上时钟 不挡跟读;4b 间隔出题再用
D9 长按退出 Task 口令退出已有 不挡
D10 indicate 无 不挡
跟读句子表曾在 Server 已改: 整份 playlist JSON 一次下发,设备排序执行 已是现网;文档须承认

2.3 Phase 3 仍缺

缺口 现状 是否挡 7a / 演示
GET 鉴权 ShiroConfig:/task/instance/** 全部 server。计划写 oauth2,实施因「不能按方法拆过滤器」改成 server 挡 7a-D。 Dispatch 不依赖读 Instance。History 再拆 oauth2 新路径;PUT 仍 server
prompt_text 仍是 SD 路径 原文在 expected_text UI 必须显示 expected_text,不要把路径当课文
upsert 失败后 _skip_upsert 本会话不再上报。audio_id 已改为 VARCHAR(255) 消掉截断 部署 Liquibase 后新会话正常;旧 skip 会话需重新跟读
查询副本非权威 权威 = 连接内存 + data/task_sessions/*.json 家长读库即可演示;不要把库当步进引擎

2.4 Phase 4 仍缺 / 与计划漂移

见下一节。对家长 History:**asr_text + expected_text + 可选 chat_audio 已够**。metrics 已入库,7a 不展示。不要等音素、及格线、流利度。


3. Phase 4:原计划 vs 代码

3.1 原施工单(phase-4-plan.md)钉死的模型

设备(当时要求零 diff)
  播一句 → completed(duration_ms) → Server 开听窗 → 时钟到了 MCP 播下一句

分析全部后置、定量、允许失败;无 passed;无 JUDGE。

3.2 实际实现(必须当成 V5 架构,不要回退)

SRAM / 58 句清单证明:每句再 MCP 一次会把设备卡死或截断列表。现网是:

Server  一次 JSON playlist(url|name, repeat, speed;speed 默认 1.0)
Device  PSRAM 清单,本地循环:
          播一句 → 关解码器 → 听窗 = 墙钟播放时长 × listen_ratio(1.2)
          → 录音上行 → 下一句
        清单结束 → audio.playlist_completed → Server 播「跟读结束」回对话
Server  ASR 只暂存/评估,不进 chat/LLM
        后评估 text_similarity_v0 → step.metrics
        data/recordings/{mac}/{mode}/{date}/{time}_{asr|tts}_*.wav
        同 stem .txt(UTF-8)
复读    同样一次下发清单;播完「复读结束」;播放中忽略闲聊
        离线口令:退出复读 / 我们来聊天吧

锁定(已验收,后续禁止为修跟读去改):开机欢迎词时序、CHAT / TRANSLATION / BILINGUAL 的 ASR→LLM 路径。

原计划条目 代码 结论
固件零 diff app_task.cc 播放列表、听窗、listen_ratio;解码器/唤醒词在听窗前关掉 有意偏离。 设备成为步骤循环执行器,Server 不再逐句 MCP
下一句只由 Server 时钟触发 设备墙上时钟;Server 旁路评估 语义仍是「分析不挡喇叭」,触发器换端
expected_text 来自 txt 契约 resolve_expected_text;禁止用路径做相似度 符合
metrics[] 无 passed text_similarity_v0 0–1 符合
说错/不说仍下一句 听窗到点就进下一句;静音也落 wav 符合
退出立刻回 Chat 口令 is_exit_to_chat 符合
Liquibase 新列 expected_text / metrics / assessed_at;后又 audio_id VARCHAR(255) 符合(255 是 9-18 为路径加的)

宣称 Phase 4 Done 的产品标准(原 §2.3):热路径流畅 + 后评估定量 —— 达到。
宣称「固件冻结面未移动」—— 不要再写。 冻结的是 Skill 种类,不是「跟读循环必须在云端」。

3.3 Phase 4 未做(原 Out of Scope,保持)

当场纠错再播、及格线、音素、租户、4b 三个 Task、热路径读卡上 txt、manager-web 打卡页、Locale。


4. Current Architecture V5(基于代码)

4.1 设备(仅 sening-xiaozhi-s3)

Application 热路径(hello/listen/tts/stt/Opus 语义锁定)
Task Gateway
  START_APP_TASK / pending / 离线口令
  SET_ALARM 软件表最多 10 时刻;DS3231 只编程下一次 Alarm1
  **7a-A:家长计划改走本地全表,不要 10 槽一对一;pending = 下一笔计划**
  playlist:repeat + speed + listen_ratio
  skill_event:audio.completed|stopped|playlist_completed + duration_ms
  SD mkdir /{app}/{task}/lessons

4.2 Server

xiaozhi-server
  apps.json 权威定义
  start_app_task / mode 分流
  跟读/复读:一次下发 playlist,不逐句 MCP
  TaskSession → JSON + PUT Instance
  assessment.py 后评估(不 await 再 play)
  audio_archive:recordings + sidecar txt

manager-api
  PUT/GET /task/instance   目前仅 server secret
  ai_task_instance / step / event
  不执行 Skill、不判对错结论

4.3 家长端现状

manager-mobile(uni-app,可出微信小程序)
  登录 / 智能体 / 配网 / 聊天记录 / 声纹
  无:练习会话、metrics、START_APP_TASK、Schedule

manager-web
  智控台(人设、模型、OTA)
  无跟读页

色宁 MQTT START_APP_TASK 设备侧已通。缺的是小程序发出去,以及用用户身份读 Instance。

4.4 数据(跟读一句)

设备录音 Opus
  → Server ASR(不进 LLM)
  → recordings/..._asr_....wav + .txt
  → turn.asr_text / expected_text / metrics
  → PUT Instance(audio_id = 相对路径,≤255)
  → 可选 chat-history report → 32 位 blob,回写同一列则覆盖路径

家长 7a-D:**只展示文本**(跟读 expected/asr;对话类走 chat_history)。相似度不渲染。播放原声若要用现成 API,走 chat_audio id,不要假设 manager-api 能读 xiaozhi-server 磁盘路径。


5. 更新后的 Roadmap

5.1 新依赖图

Phase 1–4                         ✅ 设备侧英语学习可演示
    ↓
Phase 7a  家长任务下发            ← 下一编码
    │     A 本地计划全表;RTC 只写最近一次;开机到点也执行
    │     B ai_parent_task + 场景 catalog + SYNC_SCHEDULE / 立即 START
    │     C 小程序:场景参数 + 立即 / 周期
    │     D 历史(文本+录音;无分数)
    │
    ├───────────────┐
    ↓               ↓
Phase 5 到点补推    Phase 6 课文 DOWNLOAD + 在线 Locale
(7a 日历表已建)   (换课不靠拷卡时再做;不挡选课)
    │
    ↓
Phase 4b  倒背 / 口算 / 接龙(inline 评估)
    │     固件仍禁止 if correct;用已有 speak/listen
    ↓
Phase 6b  离线 Locale(仅 zh/en 有模型才做)
Phase 7b  家长完整:语言对、PAUSE、长按退出、听原声完善
Phase 8   开发者 App 平台
Phase 9   订阅
Phase V   产品代际 / 端云兼容     ← 最后(就这一款硬件)

必须串行: 7a-A 固件(全表+下一闹钟)→ 7a-B 云端 → 7a-C 小程序。未完成 A 不要宣称周期任务可用。7a-D 不挡 Dispatch Done。
7a 不依赖: 4b、6 下载、6b、V。SD 已有 unit01 即可选课。语言对可存 params。
5 不挡 Dispatch: 权威表在 7a-B;Phase 5 只补「已开机漏响」的云端再推。

5.2 Phase 7a — 家长任务下发(Roadmap 级)

  1. Objective
    家长先选场景再填参数:泛听/跟读(课文、遍数、结束后关机或回对话);双语(中英/中法/中日…,可选时长)。可 立即执行(临时,最高优先)或 存为周期计划。设备保存计划全表;DS3231 只写入下一次最近时刻。History 第二刀看文本/录音,**不展示分数**。

  2. Why Now
    10 槽闹钟一对一撑不住「每小时整点聊 10 分钟」。芯片本来就只编程下一次。现网 pending 会被覆盖;已开机到点不执行;课文写死;家长文案「复读/复读机」和跟读分不清。

  3. Dependencies
    START_APP_TASK / 软关机 / Alarm1;Phase 3 Instance(7a-D);登录与绑设备。不把 SET_ALARM 当任务总线。

  4. Scope

  • 7a-A 本地 schedule.json 全表;recompute_next_alarm() 写唯一 pending + Alarm1;已开机也执行;软关机前重算。
  • 7a-B ai_parent_task + 场景/课文/语言对 catalog;计划 SYNC_SCHEDULE;立即 START_APP_TASK。
  • 7a-C 小程序:场景动态表单 + 立即 / 每天 / 每小时。对外写 泛听,不写复读机。
  • 7a-D(后)oauth2 读 Instance + 聊天;不渲染 metrics。
  1. Out of Scope
    分数、课文 DOWNLOAD、倒背/口算、OTA 代际、改锁定热路径、用 ALARM_MAX_NUM 限制条数、删旧口令「复读」。

  2. Client(设备)
    允许补计划文件、只写下一闹钟、duration 墙钟、on_complete。禁止新 DeviceState、业务 if 树、把 58 句清单塞进 RTC。可新增一条 MQTT SYNC_SCHEDULE(不是新 WS type)。

  3. Acceptance(Dispatch)
    立即跟读可打断计划;≥15 条计划含 hourly 全量在设备、芯片只有最近一次;软关机唤醒进正确场景;结束后重算下一闹钟;计划不打断临时。Chat/翻译/中英口令回归不坏。History 不挡 Dispatch Done。

施工单:phase-7a-plan.md。未完成 7a-A 不要宣称周期任务可用。

5.3 后续 Phase(目标级,不锁类名)

编号 一句话 相对旧图的变化
Phase 5 设备已开机漏响时云端再推 START 任务表已并入 7a-B;本阶段是补救扫描
Phase 6 清单 / DOWNLOAD / resource_id;在线 phrase pack mkdir 已有。7a 已能存 language_pair;pack 不挡选课
Phase 4b 倒背/口算/接龙 = assessment.timing=inline + 及时 speak 挪到家长能看见跟读之后
Phase 6b 离线 zh/en 词表 OTA 不挡 7a/7b
Phase 7b PAUSE/RESUME、长按退出、录音播放完善 语言对选择已在 7a 双语场景;7b 补控制台与离线词
Phase 8 / 9 开发者沙箱 / 订阅 不变,更后
Phase V product.generation/revision + hello/OTA 矩阵 最后。 摄像头 SKU 或第二款硬件立项时再打开

评估时机表(deferred vs inline)仍有效,见旧 roadmap.md;4b 不得把跟读改回流内纠错。

5.4 明确后置

事项 最早
小智 AI 1 / 1-1 / 2 代际字段 Phase V(最后)
摄像头 / 新传感器 Skill Phase V 之后
D8 v1.1 本地文件当家长主播放通道 7b 或独立;7a 用文本 + 可选 chat_audio
D11 wait 一等 Skill 4b 需要「播完停 800ms」且不想云端睡时
D14 TASK_PAUSE/RESUME 7b
新 WS type=command 仅当 detect 前缀不够;不加新语义
德语离线唤醒 可能永久做不到(无官方 MultiNet)

6. 场景(7a Dispatch Done 时)

家长创建:
  立即     跟读 unit01 ×1,结束后回对话     → 马上执行(可打断当前计划)
  计划 A   每天 07:30 跟读 unit01
  计划 B   每天 10:00 泛听 unit01,结束后软关机
  计划 C   每小时整点 双语(中英) 10 分钟,结束后软关机
  (计划可以很多条,不是 10)

设备:
  本地保存全部计划
  软关机前只把「下一次最近」写入 Alarm1
  10:00 泛听关机后,闹钟变成 11:00 双语,而不是再占 10 个槽

History(7a-D,不挡 Dispatch):
  跟读:每句原文 / ASR / 可选录音
  泛听:完成态
  双语/翻译/对话:该时段聊天
  不展示相似度

7. 刻意不做进 7a 的事项

  • 为倒背/口算改 C++ 或加 MultiNet 业务口令
  • 把 branch(if correct) 放进固件
  • 在 7a 做兼容矩阵 / hello 代际字段
  • 把 audio_id 路径当成 manager-api 可直接 fopen 的文件
  • 把及格、等级写入 ai_task_step;家长页渲染 metrics
  • 改 CHAT / 翻译 / 中英 / 开机欢迎词去「顺便修」家长页
  • 重做 SD 目录或逐句 MCP 跟读
  • 用 10 个闹钟 id 一对一绑定家长任务
  • 先做历史页、后做「全表 + 下一闹钟」(顺序必须 A→B→C,再 D)

下一编码阶段: 只打开 phase-7a-plan.md。先补本地计划表与只写下一时刻的 RTC,再云端与小程序。对外文案用 泛听 / 跟读。