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

小智 AI Client Architecture Plan

文档:小智 AI 硬件开放应用平台 —— 总体架构与开发 Plan
Version: V1.1
Date: 2026-08-30
本文在 V1.0 概念稿上,依据《Xiaozhi AI 开放应用平台 Master Plan》与《Architecture Audit》修订。
只设计,不编码。


0. 本文定位

本文只定义 Device / Client(仓库 xiaozhi-esp32-2.2.6)如何从「语音对话固件」演进为「可执行多种 InteractionMode 的 Device Runtime」。

不在本文展开、由 Server Plan 负责的内容:

  • App / Task Definition 的权威存储
  • TaskInstance / StepInstance 持久化
  • Scheduler / Plan / Subscription
  • ASR / LLM / Assessment / 家长报告
  • 管理后台与开发者平台

相对 V1.0 的关键修正:

V1.0 V1.1(本次)
设备上有完整 Task Runtime 权威 Task Runtime 在 Server;设备只有 Task Executor
Phase 1 做复读 + 跟读 + 口算 + 成语接龙 Phase 1 只做 CHAT + PLAY_AUDIO(英语复读),不破坏现有 Chat
新造统一 Command Protocol 扩展现有 JSON type + MCP,不新开总线
Skill 列表含 math.calculate / llm.evaluate Skill 必须映射到**已有代码**;没有的标为新建
设备维护 App Manager 全套生命周期 Phase 1–2 不在固件实现 App 商店;资源同步可复用板级下载

1. 当前 Client 是什么(Audit 事实)

当前固件**不是** App 平台,而是:

Application 单例
  + DeviceState 状态机
  + Opus 实时双工
  + MCP 工具面
项 真实位置
入口 main/main.cc → Application::Initialize → Application::Run
主循环 application.cc:FreeRTOS EventGroup,永不返回
对话态 device_state.h:Idle / Connecting / Listening / Speaking …
音频管道 audio/audio_service.{h,cc}:Mic→Opus→Server;Server→Opus→Speaker
协议 protocols/protocol.{h,cc} + websocket_protocol / mqtt_protocol
Skill 面 mcp_server.cc + 各 board InitializeTools()
资源 OTA ota.{h,cc}(固件/配置)、assets.{h,cc}(语音模型等)
板级旁路 Sening:self.mp3.*、BoardMqtt(snlp/v1 DOWNLOAD / PLAY_MEDIA)

完整对话链:

Mic → AudioService → Opus 16k/60ms → Protocol.SendAudio
     → Server ASR/LLM/TTS
     → type=tts/stt + binary Opus → AudioService decode → Speaker

没有: InteractionMode 枚举、Task/Step、可归档录音文件、核心 play_audio(仅 Sening 板级)、统一 Event Bus。

ListeningMode(auto/manual/realtime)只是拾音策略,**不是** Master Plan 的 InteractionMode。


2. 目标 Device Runtime

目标不是把手机 OS 搬到 ESP32,而是:

Device Runtime          ← 现有 Application 事件环升级,不是推翻
  ├── Chat Runtime      ← 现有 Listening ↔ Speaking,降为 Mode=CHAT
  └── Task Executor     ← 薄执行器:收指令、跑 Skill、回 Event
        ├── InteractionMode 切换
        └── Skill → Device Capability

同一时间只运行一个 InteractionMode(Chat 与 Task 音频互斥)。

设备**不理解**业务:

禁止固件出现:if (app == english) / if (task == idiom)

设备只理解:

mode + skill + resource_ref + 完成条件(超时 / 播放结束 / VAD 静音)

3. 职责边界

Server(权威)
  创建 TaskInstance
  推进 Step
  指定 InteractionMode
  ASR / LLM / Assessment
  持久化状态与音频对象

Client(执行)
  保持 Chat 热路径
  按 mode 使用麦/扬声器/屏
  执行 Skill(播、听、显示、下载)
  上报 Event(开始/结束/失败)
  缓存当前 step(断线 UI),不以本地为权威

对应 Master Plan:Device 负责执行、播放、采集、缓存、通信;Server 负责调度与评估。


4. 分层(保留 V1.0,落到现有代码)

业务层     App / Task          ← 不下发到固件业务代码;只下发 JSON + 资源
交互层     Interaction Mode    ← 新增;挂在 Application,与 DeviceState 正交
能力层     Skill               ← 现有 MCP tools 升级为正式 Skill 目录
设备层     Device Capability   ← AudioCodec / Display / Button / SD / LED

DeviceState 与 InteractionMode 必须分开:

DeviceState(已有) InteractionMode(新增)
回答 设备现在能不能听/说/升级 这一步用什么交互方式
例子 Idle, Listening, Speaking CHAT, PLAY_AUDIO, FOLLOW_READ
谁改 音频通道占用 Server 指定 / Client 执行

不要把 PLAY_AUDIO 做成一个新的 kDeviceStatePlayingEnglishLesson。播放时可保持 Idle 或增加**非对话**占用标记,但 禁止把 Task 塞进 Listening/Speaking 迁移表。


5. Chat Runtime:保留并降级

现有 Chat 必须继续作为默认能力。无 App、无 Task 时:

唤醒 / 按键 → Connecting → Listening ↔ Speaking → Idle

路径、协议 hello/listen/abort/tts/stt/mcp、Opus 热路径 Phase 1–2 禁止改语义。

降级方式:

InteractionMode = CHAT
  → 走现有 Application 对话状态机

InteractionMode = PLAY_AUDIO
  → 不进入 LLM
  → 扬声器给 play_audio
  → 麦可关或只用于打断/唤醒退出

任意 Mode 下唤醒词 / 退出
  → 回到 CHAT 或 Idle(可配置)

Chat 不是 Task Runtime 的父容器,而是 Device Runtime 下与 Task Executor 并列的子 Runtime。


6. Task Executor(不是完整 Task Runtime)

V1.0 把 Task Runtime 画在设备上。Audit 结论:编排已在 Server ConnectionHandler。设备侧只增加:

收到 skill.execute / 板级 PLAY_MEDIA / MCP tools/call
  → 执行
  → 上报 completed / failed
  → 等待下一步

设备 不 负责:

  • 创建 TaskInstance
  • 判断「跟读是否及格后是否重试」(可执行 Server 下发的「再播一次」)
  • 评分、ASR、LLM

设备 可以 本地判断的完成条件仅限硬件可观测:

播放结束
VAD 静音超时
用户按键取消
下载完成 / 失败

状态机(执行器级,缓存用):

IDLE → EXECUTING → WAITING_USER? → COMPLETED | FAILED | CANCELLED

权威状态仍在 Server 的 TaskInstance / StepInstance。


7. Interaction Mode

Interaction Mode 不是业务场景(不是「英语课」),只描述硬件怎么与人交互。

7.1 分阶段支持

Mode Phase 现有代码基础 设备要做的
CHAT 0(已有) Listening/Speaking 保持
PLAY_AUDIO 1 Sening self.mp3.play / PLAY_MEDIA;PauseSdCardMp3 抽成可调用 Skill;与 TTS 互斥
FOLLOW_READ 2 listen 流 + 播放 先播后听;听完上报,不进 LLM
LISTEN 2 SendStartListening 可单独听,不自动 Chat
QUIZ / TRANSLATION / GAME 3 无独立 Mode 先用 CHAT+Skill 组合,再固化 Mode

V1.0 要求第一阶段就支持 QUIZ/TRANSLATION/GAME —— 取消。那些是 Server 编排 + CHAT/LISTEN 即可先做,不值得先改固件状态机。

7.2 PLAY_AUDIO(英语复读)

Server:重复 N 次的编排(权威)
Client:play_audio(resource) → 播完 event → 等下一次 play 或 stop
LLM = false,ASR = false

TTS 来时沿用现有 PauseSdCardMp3 / resume_sdcard_mp3_after_tts_,**不要**另写一套混音。

7.3 FOLLOW_READ(英语跟读)

play 标准音频
  → play.completed
listen(复用上行 Opus)
  → speech.stopped / listen.completed
Server ASR + v0 文本评估
  → speak 反馈(可走现有 TTS)

设备不评分。


8. Skill:只基于真实能力

Skill = 对 Device Capability 的稳定封装。第三方 App 禁止直接 GPIO/I2S。

8.1 已有,可直接当作 Skill

Skill 当前实现 范围
device.get_status self.get_device_status 全局 MCP
audio.set_volume self.audio_speaker.set_volume 全局
screen.set_brightness / set_theme self.screen.* 有屏
camera.take_photo self.camera.take_photo 有摄像头
audio.play_sound AudioService::PlaySound(OGG 提示音) 全局;不能当课程音频
mp3.play/stop/list self.mp3.* Sening
media.play BoardMqtt PLAY_MEDIA Sening snlp
resource.download DOWNLOAD / Assets Sening / Assets
alarm.* self.alarm.* Sening;≠ 家长 Schedule
led / 运动类 各 board MCP 板相关

8.2 Phase 1 需要「提升」的

play_audio
  输入:SD 路径或已下载 resource_id
  实现:优先复用 self.mp3.play / PLAY_MEDIA
  目标:从 board 私有工具提升为 Runtime Skill(可仍 ifdef 板型)

不要求所有板第一天都能 play MP3。无 SD 的板 Phase 1 可用 Server 推流(不推荐占 TTS 通道做复读循环)。

8.3 Phase 2 需要新建的

Skill 现状 设计
record_audio(可归档) 不存在;只有流式 Opus 优先复用 listen 流,由 Server 句级落 WAV;设备端文件录音为增强(SD /recordings/pending)
listen(不进 Chat) listen 消息会进入对话 Mode=FOLLOW_READ/LISTEN 时 Client 仍发 Opus,Server 跳过 LLM
wait 无 可用 Server 计时;设备只需播完/超时事件
display.show 有屏 MCP / SetChatMessage 可扩展文本/图片资源

8.4 不要做成设备 Skill 的

asr.transcribe     → Server
llm.chat/evaluate  → Server
score.evaluate     → Server
translation.translate → Server
math.calculate     → Server

V1.0 Skill 表里的这些能力改挂 Server Plan。


9. 音频、录音、互斥

9.1 现有音频生命周期(必须保留)

对话:PCM 16k → Opus 60ms 流 → 不写盘 → Server 可选转 WAV 入库
提示音:OGG PlaySound
Sening 课程:SD MP3 独立播放,TTS 时 Pause

热路径禁止改:MAIN_EVENT_SEND_AUDIO、AudioInputTask、OpusCodecTask、FinishTtsResponse。

9.2 互斥规则

同一时刻扬声器只服务一个源:TTS | SD/MP3 | PlaySound
CHAT 的 Listening/Speaking 优先于 PLAY_AUDIO(可暂停课程音频)
Task 需要麦时:要么 Mode 独占 listen,要么明确与 Chat 互斥

9.3 跟读音频最小方案(Phase 2)

不先做设备完整 wav 文件上传。

复用现有上行 Opus
Server 按句 PCM→WAV(已有 chat_history_conf=2)
新增关联:audio_id ↔ step_instance_id

离线缓存 /recordings/pending 放到 Phase 3,除非跟读在弱网成为阻塞。

设备侧 没有 audio_id;只有 session_id(连接级)。audio_id 由 Server 生成后回传(可选)。


10. App 资源 vs Firmware OTA

原则保留 V1.0 / Master Plan:

OTA     = 驱动、协议、Audio Engine、Runtime、Skill 宿主
App Sync = manifest、task JSON、MP3、图片、配置

App 原则上不 OTA。但下列能力必须在 Firmware 里(可一次 OTA 补齐,之后新 App 不再改 C++):

Codec / VAD / 唤醒
WS 或 MQTT+UDP 协议栈
MCP / Skill 宿主
SD 文件系统
HTTP 下载(或等价)
音频互斥
InteractionMode 开关
(Phase 2)非 Chat 的 listen 语义

10.1 SD 布局(目标,Phase 2+ 落地)

/sdcard/
  apps/<appId>/          # manifest + tasks JSON
  resources/<appId>/     # mp3 / 图
  recordings/pending/    # Phase 3
  cache/

Sening 当前下载路径偏 /data/courses/...(smartlearning)。演进时 映射到统一布局,不要强迫立即改终端已有文件。

10.2 双 MQTT 收敛

当前存在两套:

通道 用途
Xiaozhi Protocol 对话 JSON + Opus
Sening snlp/v1 心跳、DOWNLOAD、PLAY_MEDIA、ack/progress

Phase 1: 允许并存;复读优先走已能工作的 MCP/PLAY_MEDIA。
Phase 2–3: 资源同步与播放控制逐步收到 Xiaozhi JSON type 或 MCP,snlp 降为板级实现细节,避免业务云长期分叉。
禁止 Phase 1 合并两套 MQTT。


11. 协议:扩展,不替换

现有设备→云:hello / listen / abort / iot / mcp / ping / server
现有云→设备:hello / stt / tts / llm / mcp / system / alert / custom

Phase 1

不强制新 type。复用:

type=mcp  tools/call  self.mp3.play | stop
或 snlp PLAY_MEDIA

Phase 2 新增(与 tts/stt/mcp 并列)

下行:

task.start / task.stop / task.pause / task.resume
step.start
skill.execute
mode.set          # CHAT | PLAY_AUDIO | FOLLOW_READ | LISTEN

上行:

task.started / task.completed / task.failed
step.started / step.completed / step.failed
skill.completed / skill.failed
audio.play.completed
speech.started / speech.stopped   # 可从现有 VAD 事件导出

载荷必须带:session_id;Phase 2 起带 task_instance_id / step_id。

不要新建第四套 EventBus。Sening rpt/event 可作实现,但语义应对齐上表。

插入点:Application 中 protocol_->OnIncomingJson(application.cc 约 647–778 行),unknown type 分支旁增加 task / mode。


12. Event 与主循环插入点

现有机制:MAIN_EVENT_* + 协议回调 + MCP JSON-RPC +(Sening)rpt/ack。

Task Executor 挂载顺序(侵入性从低到高):

  1. MCP tools/call — Phase 1 首选,Chat 路径不动
  2. Application::Schedule — 线程安全推进 UI / 非音频副作用
  3. OnIncomingJson 新 type — Phase 2
  4. 禁止把 Task 状态写入 DeviceStateMachine::IsValidTransition 的 Listening/Speaking 边

按键:今日绑定 ToggleChatState / Start/Stop Listening。Phase 2 起允许「取消当前 Task / 跳过」而不拆掉 Chat 按键。


13. 本地状态(薄)

设备只持久化必要项(NVS / 内存):

activeMode
activeTaskInstanceId     # 缓存,权威在 Server
activeStepId             # 缓存
sdcard play 状态         # 已有

不在设备保存完整 Step 历史、评分、ASR 文本。

App 生命周期 V1.0 的 INSTALL/LAUNCH/ACTIVE 全套 Phase 3 再做。Phase 1 甚至不需要 activeAppId。


14. 分阶段(Client)

与 Server Plan 阶段编号对齐。

Phase 1 — 双模式证明(必须不破坏 Chat)

目标:英语复读 N 遍。

Client 工作:

  • 文档化并复用 self.mp3.play / PLAY_MEDIA + PauseSdCardMp3
  • 约定:播放期间唤醒/按键可回到 Chat(现有行为优先)
  • 不改 Opus 热路径、DeviceState 迁移、hello/listen/tts 语义
  • 不实现 Task 状态机、App Manager、跟读录音文件

验收:

  1. 无 Task 时 Chat 与现网一致
  2. 能循环播放指定 SD/已下载音频 N 次
  3. TTS/唤醒仍能打断或暂停播放

Phase 2 — Task Executor + 跟读采集

  • mode.set + type=task / skill.execute
  • FOLLOW_READ:播完再 listen,Server 跳过 LLM
  • 上报 play/listen 完成事件(带 task/step id)
  • 录音最小方案:流式上传 + Server 归档关联,不强制设备 wav 文件

验收:一次跟读 Step 能在 Server 看到 audio_id + asr_text。

Phase 3 — Runtime 平台化

  • 核心 play_audio / 下载进非 Sening 抽象(或明确仅支持 SD 板)
  • App 资源目录与增量 sync
  • snlp 与 Xiaozhi 协议收敛
  • 可选:本地 pending 录音、QUIZ/GAME 作为 Mode 而不是硬编码业务
  • 按键可绑定 Task 取消

15. 禁止事项(Client)

  1. 重写 AudioService 热路径或「为了 Task 换一套编解码」
  2. 在固件写英语/口算/成语业务分支
  3. 用 OTA 发课程 App
  4. Phase 1 合并两套 MQTT
  5. 把 Task 塞进 Listening/Speaking
  6. 设备上做 ASR/评分/情绪分析
  7. 为 Demo 写 EnglishRepeatManager 这种一次性类(可用薄 TaskExecutor + Skill)
  8. 引入通用 EventBus / Redux
  9. 修改现有 Chat 协议字段含义

16. AI Coding 约束(保留并收紧 V1.0)

  1. 先读现有 Application / AudioService / Protocol / mcp_server,再改。
  2. 无 App/Task 时 Chat 必须可用。
  3. 业务不下沉到 Client。
  4. 配置/JSON 驱动 InteractionMode 与 Skill,不写死 App。
  5. 最小改动:能 MCP 解决的 Phase 1 不改协议。

17. 与 Server Plan 的契约

Client 保证:

  • CHAT 行为向后兼容
  • 能执行 play_audio(至少 Sening 路径)
  • Phase 2 能按 mode 听而不自动当 Chat 回复引擎
  • 事件可关联 session_id(及后续 instance id)

Client 要求 Server:

  • 权威推进 Step,不要指望设备记住整张 Workflow
  • PLAY_AUDIO / FOLLOW_READ 不要误入 ConnectionHandler.chat()
  • 音频对象与 step 关联在云端完成
  • App JSON 与资源 URL 由云下发,不要求固件内置课程

下一份可执行文档应是 Phase 1 Coding Plan(Client 切片),而不是继续加 Mode。