# 火山语音陪伴借鉴小智的设计方案 本方案供产品和研发评审,目标是让老人聊得轻松、有具体内容,并能自然接上前次聊天。保留现有火山 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 上验证通用工具协议。