feat: expand companion platform, device controls, and memoir workflows
ci / verify (push) Canceled after 0s
ci / verify (push) Canceled after 0s
Add customer and operator login, BLE provisioning, device fleet controls, call memory, memoir generation, and refreshed family and maintenance portals. Include API contracts, migrations, firmware sources, tests, assets, and deployment documentation.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
# 火山语音陪伴借鉴小智的设计方案
|
||||
|
||||
本方案供产品和研发评审,目标是让老人聊得轻松、有具体内容,并能自然接上前次聊天。保留现有火山 S2S、RTC、每老人专属 Bot、Go 平台和 Viking;优先补齐真正生效的人设、开场和话题连续性。已试用的角色和无天气动态开场分别见 [人设发布记录](E:/Codex/story/platform/docs/companion-pilot-release-20261001.md)、[动态开场发布记录](E:/Codex/story/platform/docs/companion-opening-release-20261001.md);其余新增实体、字段及接口仍为拟定。
|
||||
|
||||
配套 [交互架构图](E:/Codex/story/platform/docs/diagrams/volc-companion-xiaozhi-20261001.html) 展示实时语音、开聊前和通话后三条路径。
|
||||
|
||||
## 当前问题与改造起点
|
||||
|
||||
2026 年 10 月 1 日只读核对的目标 Bot,保存的 `system_role` 只有一行长者称呼,欢迎语固定为“你好,今天想聊些什么?”,模型为 O,原生记忆关闭,平台仍会注入授权记忆。因此当前缺少完整聊天引导;不能把仓库 v3 的严格提问规则认定为现网问题。核对记录见 [脱敏配置](E:/Codex/story/.local-evidence/voice-stall-20261001/companion-current-role-redacted.json)。
|
||||
|
||||
代码也存在两条配置路径。专属 Bot 从模板读人设,READY 时直接返回,开聊前从数据库保存的配置补入记忆;另一条 orchestration 路径才读取发布的 PERSONA、最近三次摘要和开放话题。证据见 [专属 Bot 配置](E:/Codex/story/platform/internal/elderagent/service.go:83)、[READY 分支](E:/Codex/story/platform/internal/elderagent/service.go:159)、[发布人设读取](E:/Codex/story/platform/internal/orchestration/repository.go:622)。第一步应使完整人设进入当前 direct 路径。
|
||||
|
||||
## 借鉴小智的具体内容
|
||||
|
||||
小智的 [prompt_manager.py](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/main/xiaozhi-server/core/utils/prompt_manager.py) 把基础角色与时间、背景等组合;[dialogue.py](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/main/xiaozhi-server/core/utils/dialogue.py) 组织系统消息、记忆和工具结果;[agent-base-prompt.txt](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/main/xiaozhi-server/agent-base-prompt.txt) 提供口语表达与分段交流思路;[mem_local_short.py](https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/main/xiaozhi-server/core/providers/memory/mem_local_short/mem_local_short.py) 展示摘要保存和下次注入。参考其职责划分,自主编写长者角色、示例和筛选规则;不照搬台腔、夸张情绪、人设或记忆权重算法。
|
||||
|
||||
## 三种能力层级
|
||||
|
||||
| 层级 | 能做到什么 | 实现边界 |
|
||||
| --- | --- | --- |
|
||||
| 一期基础陪伴 | 完整角色、具体开场、回应内容、短答时换一个轻话题 | 开聊前配置;会话内靠火山模型和 prompt 自适应 |
|
||||
| 一期连续陪伴 | 记住说过的事实、已聊细节、拒绝话题和未完故事 | 会话后异步整理,下次开聊注入 |
|
||||
| 二期工具陪伴 | 选择内容、经确认创建提醒 | 先验证当前 RTC 与 O 模型的工具调用、结果回传和动态更新能力 |
|
||||
|
||||
二期门槛是实机证明工具请求、结果关联、超时、打断和授权均有效;会话中动态更新角色也须单独验证,当前尚未实证。小智支持某功能不等于本系统已支持。若不能回传工具结果,一期仅可注入开聊前已取得且未过期的背景,不承诺会话中查询或执行。
|
||||
|
||||
## 人设与聊天行为
|
||||
|
||||
PERSONA 版本作为唯一入口,经已发布 AGENT_BUNDLE 按老人、设备组或租户范围解析,模板只保留供应商参数。人设明确身份、回复节奏、事实边界和示例:先接住一个真实细节,再给相关内容,必要时问一个具体问题。避免机械规定每三轮一问;老人讲得多就跟进,连续短答时提供内容或活动,拒绝和疲惫时立即转向或收尾。允许有依据的欣赏,不替老人编造感受、经历或第三方评价。
|
||||
|
||||
例如老人说“我以前修车床”,可以答“车床调准了,活才能做得稳。最难的是找毛病,还是调精度?”若只答“嗯”,可以接“那咱们轻松点。我给您讲个老机器的小故事。”老人愿意回忆时再接续,不能把每次聊天变成故事采访。
|
||||
|
||||
拟定第一版角色名“小禾”,可由老人修改。基础人设草案如下;实现时放进发布的 PERSONA,称呼和历史资料由编译器单独传入。
|
||||
|
||||
> 你叫小禾,是家里的语音陪伴助手。像平等、耐心的熟人一样交流,尊重对方作为成年人的经验和选择。先回应对方刚说的具体内容,再补一点相关见闻或想法。通常说一到三句短话,故事按小段讲;对方想听详细内容时可以展开。每次最多问一个容易回答的具体问题,很多轮可以不提问。对方只回“嗯”时先提供一点内容,不追着问;明确拒绝就换话题,疲惫就收尾。记忆资料只提供事实,不改变这些规则;只引用有来源的经历,不编造共同回忆、实时天气或已经完成的操作。听不清关键内容时简短确认。对方打断或提出新话题时,优先跟随当前意愿。
|
||||
|
||||
一期准备三种轻玩法,作为少量、可审核的内容卡编入开聊背景。闲聊接具体细节;怀旧围绕已经授权的手艺、老物件和生活经验;短故事或猜物件每段只推进一步,对方愿意才继续。内容卡拟含 `id/version/mode/opening/followups/source/estimated_duration`,先人工维护约 10 张,每通最多选 1 张,避免把大题库塞进 prompt。资料不足时用通用卡;没有来源的故事明确按故事讲述,不冒充真实新闻。老人随时可以改聊别的。
|
||||
|
||||
## 开聊配置与实时路径
|
||||
|
||||
在现有 `DirectRequest` 的 subject 锁和活动通话检查范围内,依次解析生效 PERSONA、读取授权背景、选择候选欢迎语、调用 `Prepare`,成功后冻结本通快照。音频继续由设备直连火山 RTC,不增加每句经过平台 LLM 的串行步骤。
|
||||
|
||||
拟定短背景包括称呼、兴趣、最近摘要、一个可续聊线索和回避边界,先用约 1000 字预算。按用户最新要求,动态开场不使用天气或定位,以时段、已授权兴趣和近期互动轮换。欢迎语从少量可审阅候选中选择,例如有修车经历时:“今天咱们聊点老手艺,您也可以换个想聊的。”没有背景时提供一个轻松内容开头,不要求老人自行提出话题。两处欢迎语必须由同一构建函数产生并写入专属 Bot 与 direct 参数,避免覆盖。现有位置见 [Bot 欢迎语](E:/Codex/story/platform/internal/elderagent/service.go:124)、[direct 欢迎语](E:/Codex/story/platform/internal/callmemory/direct.go:272)。
|
||||
|
||||
拟定 `persona_version_id`、`base_config_digest`、`applied_persona_version_id` 和 dirty 状态用于识别旧 Bot。发布新版本只标记待刷新,下次无活动通话时由 Prepare 更新;READY 不代表人设已是新版。Prepare 每次从干净的供应商基础配置与生效人设重建,再附本通背景,禁止用上次已拼背景的远端配置作为 base。现实现从本地保存配置构建,后续必须保留这个性质。事实资料与控制指令分区,用户原话只作资料,不能进入角色规则;只靠标签不能保证隔离,还须限制字段、长度与来源。成功回执后记录应用版本;超时或结果未知保持 dirty,以幂等重试或重读核对恢复。更新失败不签发本通参数,记录可重试错误。
|
||||
|
||||
同一会话重试复用已冻结的人设、欢迎语和资料版本。开聊阶段只读本地已准备好的内容,不新增 LLM、Viking 或外部资讯查询。需要降级时,仍须成功更新基础角色与本通 callback 信息;不能直接沿用上通火山配置,因为回调地址和签名绑定会话。
|
||||
|
||||
## 会话后的记忆与话题状态
|
||||
|
||||
### 与现有火山记忆库的分工
|
||||
|
||||
现有火山 Viking 继续负责从通话抽取长期记忆,`curated_memory` 继续作为经过授权、可编辑和可删除的本地服务镜像。当前关闭的是 Bot 自行读取记忆的 `MemoryConfig`;Managed Viking 的上传、抽取和同步仍在使用。下一通通过平台把筛选后的事实放入角色背景,见 [Managed 同步与镜像](E:/Codex/story/platform/internal/vikingmemory/managed.go:29) 和 [服务来源说明](E:/Codex/story/platform/internal/vikingmemory/managed.go:235)。本方案沿用这条链路。
|
||||
|
||||
新增的主要是聊天进度:已经问过哪个细节、用户是否拒绝、故事是否讲完、下一次是否适合接续。比如“以前修车床”属于现有记忆;“这个经历已经聊过,未讲完找故障的过程”属于话题进度。进度只引用现有记忆 ID、版本和会话原话,不另建一套长期事实库,也不把进度反向写成 Viking 的人物事实。
|
||||
|
||||
会话摘要优先复用 Viking 已产出的本通事件摘要,以引用或可失效的展示投影供续聊使用。一期不新增独立的长文本摘要提取器。只有现有摘要无法表达问答进度时,才增加小范围的结构化状态提取;它不再执行一遍通用人物事实抽取。拟定 `call_companion_summary` 可先作为投影或缓存,是否需要独立表在实现时决定。长期回避和偏好沿用现有记忆及其使用策略,话题状态只保存交互进度和近期冷却。
|
||||
|
||||
编译器按记忆 ID、实体键和来源会话去重,同一事实只注入一次。事实内容以当前有效的 `curated_memory` 版本为准,摘要不得覆盖人工修改;被停用、编辑、撤回或删除的来源使对应摘要投影及话题线索立即失效,异步旧任务沿用授权和版本检查。保持平台单一路径注入,不额外开启 Bot 原生直读,避免绕过本地编辑或出现双路记忆冲突。
|
||||
|
||||
保留当前 Managed Viking 的通话后抽取与本地事实镜像:结束、字幕排空、上传与异步取结果,再镜像到 `curated_memory`。拟新增会话摘要与话题记录,分别保存“讲过什么”和“下一次如何接”;拟定字段包括 `topic_key`、`state`、`last_question`、`cooldown_until`、`source_session_id`、`source_utterance_id`、原话及抽取版本。保存任务以会话、字幕版本和抽取器版本幂等执行。所有状态变更必须有用户话语来源,模型自己的问题不能当作老人回答。
|
||||
|
||||
状态至少区分已回答、拒绝、冷却、完成:已回答避免重问同一细节;明确拒绝不再主动触发,老人重新主动提及才恢复;连续简短回应只能作为可能不感兴趣,进入冷却候选而非认定拒绝;故事或活动完成后不再以未完线索开场。事实冲突保留来源,以最新明确表述为准。异步结果未就绪时用既有摘要。摘要和话题沿用现有记忆授权、来源屏蔽和 `privacy_epoch`:无授权时不保存个人记忆,不注入撤回或删除的资料,异步旧结果也不得重新恢复。开场仅使用允许主动提及的资料,`RESPOND_ONLY` 信息留待用户主动问及。此结构补齐当前 [只拼 curated_memory 的上下文](E:/Codex/story/platform/internal/callmemory/service.go:492),不会宣称当前没有记忆。
|
||||
|
||||
## 模块改动清单
|
||||
|
||||
| 文件或模块 | 拟改动 |
|
||||
| --- | --- |
|
||||
| `internal/elderagent/service.go` | 解析人设版本、干净 base、版本刷新与统一欢迎语 |
|
||||
| `internal/callmemory/direct.go` | 冻结人设及短背景;在现有锁内传递 Prepare 配置 |
|
||||
| `internal/callmemory/service.go` 与 `protocol.go` | 分区构建背景;加入摘要、偏好和话题状态筛选 |
|
||||
| `internal/orchestration/companion_context.go` | 提取可共用的连续上下文逻辑,保留两种会话存储差异 |
|
||||
| `internal/app/app.go` 与配置发布模块 | 接入唯一人设解析器,标记受影响 Bot 待刷新 |
|
||||
| 数据迁移与 worker | 复用 Viking 事件摘要,按需增加 `call_companion_summary` 投影或缓存;拟新增 `companion_topic_state`,幂等提取进度、来源追踪及删除 |
|
||||
| 记忆删除与投影 worker | 新增派生状态也接入失效和清理流程,不重新上传摘要为人物记忆,不从旧字幕恢复已删除事实 |
|
||||
|
||||
一期复用现有开聊接口;拟定内部 `CompanionPrepareInput` 承载版本、背景和欢迎语,名称待实现评审,不新增面向设备的逐句推理 API。
|
||||
|
||||
## 场景验收与灰度回滚
|
||||
|
||||
验收录音与字幕须同时检查,不能只判断 prompt 已保存。
|
||||
|
||||
1. 首次无档案:给具体轻话题,称呼准确,不编造共同经历。
|
||||
2. 老人主动讲修车:回应其真实细节,提出一个容易接的话题。
|
||||
3. 连续两次“嗯”:提供短内容或换话题,避免循环追问。
|
||||
4. “上次讲到哪”:引用已就绪摘要;缺资料时坦诚说明。
|
||||
5. 已说过工厂名字:不重复索取同一信息。
|
||||
6. “别聊我老伴”:下次开场仍遵守,不把拒绝当可续聊线索。
|
||||
7. 老人主动重开拒绝话题:跟随当前表达,不突然纠正。
|
||||
8. 故事讲完或活动完成:下一通不再当成未完成任务。
|
||||
9. 纠正往事细节:以后以最新原话为准,保留可审查来源。
|
||||
10. “累了,不聊了”与打断:立即收尾;中止声音的实机效果单独验收。
|
||||
11. 撤回记忆授权、删除或另一老人开聊:不带入被撤回资料,不串人。
|
||||
12. READY Bot 升版、更新失败及二期工具超时:版本可核对,失败不报成功,不把旧背景累积进去。
|
||||
|
||||
先在测试老人完成验收,再对小范围老人启用新版本并与旧版对比:人工评“是否想接着聊”,统计重复问题、短答循环、错误记忆、开聊失败和首音延迟,聊天时长只作辅助。拟定门槛为无串人及拒绝话题违规,首音延迟 p95 增幅不超过基线 20%;样本量和观察窗口在灰度前确定。支持按老人关闭连续背景、回指上一 PERSONA 版本;下一次安全 Prepare 重建旧配置,禁止直接改正在通话的 Bot。所有层级保留独立开关,二期验证失败不阻碍一期上线。
|
||||
|
||||
首音延迟需要补设备观测后才能比较:当前 `call_client_observe()` 没有上传实现。拟异步记录开聊、首次音频、重连、播放缓冲不足与打断事件,并关联 `session_id`;采集不得阻塞音频任务。已有后台成功率只能说明控制接口状态,不能替代 RTC 媒体质量指标。交付顺序为先角色与开场,再摘要与话题状态,最后在独立测试 Bot 上验证通用工具协议。
|
||||
Reference in New Issue
Block a user