截至 2026-09-30,项目已具备知识检索、规则提取的长期记忆、当前会话上下文投影和断线恢复基础。历史记录页支持继续带有运行时标识的新记录;缺少标识的旧记录仍只供查看。跨服务重启续聊需要持久化 Journal。下面先用一个售后助手例子验证使用方式,再说明实际实现和缺口。
在页面里体验售后助手
1. 创建并绑定知识
打开知识库资源页,先选择准备体验的角色,填写名称「星河商店售后说明 v1」,粘贴以下正文并创建资料:
星河商店的退货期限为签收后 7 天。
退货条件:商品未使用、包装完整,并提供订单号。
客服服务时间为工作日 09:00—18:00。
退货运费规则:商品质量问题由商店承担,其他情况由买家承担。
仓库也提供同样主题的 docs/demos/shop-knowledge.md 文件,可以直接选它导入。勾选资料,测试「退货期限」与「退货运费规则」,展开结果核对原文。
新资料会自动加入所选角色。进入角色列表,打开同一角色,在「知识库」中确认这份资料已选中。完整导入和更新方式见知识库使用指南。
2. 添加一条长期记忆
在角色配置中启用「长期记忆」,再到记忆管理选择同一角色。添加一条「偏好」记忆:「我喜欢简洁回答」。
先手动添加可以确认记忆读取链路。自动保存则可另行测试:从这个角色开始对话,说「我的目标是学会日语」,等待对话记录保存后,重新打开 Memory 页面检查是否出现「目标」记录。页面不会自动刷新其他页面刚写入的记忆。
若列表未出现记录,请先查看对话记录是否保存了这句话,确认该条用户消息是最终文本、角色记忆已开启,且使用同一账号与角色。自动提取失败不会让对话保存失败,也没有独立的页面失败提示。
3. 验证多轮上下文与记忆
从配置好的角色开始会话,依次问:
| 提问 | 要验证的内容 |
|---|---|
| 「请查知识库,星河商店的退货期限是多少?」 | 回答依据「签收后 7 天」 |
| 「我是因为商品质量问题退货。」接着问「那运费谁承担?」 | 前文的质量问题与当前知识片段结合,回答商店承担 |
| 「退款多久到账?」 | 资料缺少答案时明确说明,不猜时间 |
| 「我之前说过的学习目标是什么?」 | 在新会话中验证保存的目标记忆是否可读 |
前两轮测试当前会话上下文;最后一问测试跨会话的长期记忆。模型回答会有变化,要结合原文、Memory 列表和实际工具调用判断,不能仅凭一次回答看起来正确就认定链路完整。
4. 测试关闭和删除记忆
关闭该角色的长期记忆后,后续提示词不再注入这些记录,自动提取也停止。关闭不会删除已保存记录,需要在 Memory 页面单独删除或清空。
验证关闭是否生效时,使用一个新会话,避免模型仍从当前会话的前文读到同样内容。删除或过期清理网页对话时,会清理对应来源自动记忆;手工编辑后已解除来源关联的记忆保留。删除弹窗另可选择擦除关联运行时对话,并安排录音清理;运行时不可用、归属验证失败或录音受保护时保留网页记录供重试。录音物理删除可能延迟,备份与外部供应商数据需独立处理。
按对话清理来源记忆会在移除内容前写入 Memory 删除回执和来源标记。使用最新删除快照重放时,仅恢复 Memory 模块也会排除这些已删记录,并拒绝从同一角色的旧来源重新提取;手工编辑后解除来源关联的记录保留。写回执失败时保留原 Memory 文件供重试。旧备份本体不因恢复而被修改,仍需执行独立的备份保留策略。
删除来源记忆后,同一条已保存的最终转写即使迟到修订,也不会通过普通历史保存重新提取该来源记忆。新的明确发言和手工添加仍可创建记忆;未删除事实的正常 ASR 更正仍会更新原条目。这个保护不撤回模型已接收的当前会话上下文。
手工编辑有来源的记忆时,也会屏蔽该角色旧来源的历史重提取,避免补全旧对话后再次出现编辑前的版本。新明确发言的正常修订仍可更新对应记忆;生日、当前居住城市这些已有稳定字段的事实,不会被较旧最终转写覆盖较新发言或手工编辑。
无模型凭据也能运行的代码 Demo
在项目根目录执行:
.venv/bin/python -m demos.knowledge_memory_demo
Demo 使用临时目录和真实项目组件,运行结束自动清理,输出 JSON。它执行知识库工具检索、Memory 文件读取以及预算化上下文投影,展示传给模型的数据;无需启动网页、麦克风或调用付费模型。
验证结果包含:未绑定不命中、绑定后命中、用户隔离、新 Session 读取保存的 Memory、关闭后停止注入、同一 thread 读取前文,以及新 thread 不自动继承旧历史。
输出的模型输入类似:
system_prompt:
你是商店助手,请依据资料回答。
<memory_data>我喜欢简洁回答</memory_data>
user_text:
最近对话上下文:
用户:我刚签收了星河商店的商品,想退货。
助手:资料规定签收后 7 天内可以退货……
当前用户输入:那运费谁承担?
knowledge_tool_result:
[资料来源: 星河商店售后说明 v1]
<untrusted_knowledge>退货运费规则……</untrusted_knowledge>
上面为简化展示;实际输出带 JSON 元数据。Demo 中的助手前文和 Memory 是预置测试数据,未运行自动提取、模型答案生成、浏览器续聊或 WebSocket 断线恢复。应与页面体验及各链路测试配合使用。
Memory 实际怎么工作
长期记忆保存到 .openrealtime/memory/owners/<用户标识的 SHA-256>/memories.json,可通过 VOICEAGENT_MEMORY_DIR 指定根目录。首次管理访问会迁移旧版单个文件;Python 兼容读取旧格式与新格式。每条记录包含用户、角色、类型、内容、来源对话 ID、时间和 revision。
自动提取发生在网页调用 POST /api/conversations 保存记录时:服务端检查角色的 memory_enabled,对新增或修改后的最终用户消息做正则匹配。重复保存未变化的记录不会重放旧事实。生日使用同一个事实键,明确的新生日替换旧生日;手工编辑采用 revision 检查,并解除与原对话的关联。自动提取失败会返回状态并在对话页面提示用户核对记忆列表。
| 类型 | 可触发的句式示例 |
|---|---|
| 偏好 | 我喜欢简洁回答、我不喜欢太长的解释、我偏好中文交流 |
| 目标 | 我的目标是学会日语、我打算长期学习编程 |
| 纠正 | 请记住我更喜欢分步骤说明、纠正一下称呼方式 |
| 关系 | 我和同事每周一起复盘 |
匹配针对整条消息,有长度限制,复杂句、多个事实、间接表达、否定或口语化改写可能不会保存。内容须满足敏感词与格式过滤;每条最多 300 字符,每个用户与角色最多 100 条,完全相同的内容会去重。
语音路径使用 SessionMemory.apply_async() 在线程中读取当前用户的记忆,检查角色开关,再选取该角色最近 20 条,作为 <memory_data> 注入系统提示词。同步 apply() 保留给离线调用。读取时冻结角色选择,角色切换不会改变已经开始的读取。当前按 updated_at(缺失时用 created_at)选择,不按相关性检索;最近编辑的旧记录也会进入优先读取范围。
没有自动处理「原来喜欢 A,现在喜欢 B」之类的冲突。冲突条目需在管理页面编辑或删除。资料目录、记忆目录与角色配置目录需要在 Next.js 与 Python 进程之间共享,身份格式也必须一致。
历史如何进入模型
项目中有两种历史存储,用途不同:
| 数据 | 写入方与用途 | 是否直接供当前模型读取 |
|---|---|---|
| 页面对话记录 | 浏览器分段提交 transcript,通过 /api/conversations 写入每个会话独立的 conversation.jsonl,用于列表与详情展示 | 可用来展示恢复前文;模型历史仍从经过授权的原 thread Journal 读取 |
| 服务端 Conversation Journal | 语音运行时记录用户输入、助手输出、工具结果等语义事件,按 tenant/user/thread 隔离 | 当前会话上下文来自这里 |
当前会话处理下一轮时,Cascaded Engine 获取当前输入之前的已投影 Journal 条目,通过 BudgetedConversationContextProjector 渲染历史,将其填入 TurnContent.conversation_context。TurnContent.model_text 再将历史和本轮输入组合,交给 Brain 与模型客户端。长期 Memory 通过系统提示词加入,知识库则由工具检索加入。
长历史有上下文预算与摘要压缩:预留输出、工具 schema、指令、当前输入和安全余量,达到阈值时压缩历史。摘要带源序列与摘要校验,不能随意替代原文;摘要 checkpoint 当前存在内存中。语音投影还区分已播放与被打断的输出,默认被打断的内容只保留确认听到的前缀。
「继续会话」需要区分使用场景
| 场景 | 当前实现 | 当前限制 |
|---|---|---|
| 同一页暂停麦克风再恢复 | 停止/恢复输入,原连接与 thread 保持,前文可继续使用 | 依赖会话与连接仍有效 |
| 网络短暂断开后恢复 | 使用 session ID、resume token、序列号和恢复屏障重连 | 凭据在页面控制器内存中,刷新后不会直接复用 |
| 服务端要求创建 continuation | 控制器发送 previous_voice_session_id 和 requested_thread_id,服务端校验原 thread 权限 | 属于协议恢复链路,还不是历史页面的入口 |
| 结束后的当前页「继续」 | 本地复用当前 thread;公开准入续期发送原 session/thread 的 continuation | 服务端验证历史所属用户与原 session;历史不可用时拒绝,不静默降级成空会话 |
| 在历史详情页继续旧对话 | 新记录保存 thread/session 标识,详情页提供「继续此对话」;进入后点击「继续」开始 | 旧记录无标识不可恢复;已删除的角色不可恢复;运行时 Journal 必须仍存在 |
| 浏览器刷新或服务重启后继续 | Journal 可以配置 PostgreSQL 持久化 | 默认 VOICEAGENT_STATE_BACKEND=memory 不持久;网页历史存在磁盘不等于模型历史可恢复 |
页面记录 ID 仍为独立 UUID,同时保存 thread_id 与 voice_session_id,不保存 resume token。历史页面先按当前账号读取记录,服务端按记录选择原角色;运行时再验证 thread 归属和原 session 是否确实出现在 Journal 中。不会把网页上传的 transcript 直接当成模型历史,也不会将页面 UUID 当成 thread ID。
新建对话会重置运行时身份;本地连接重新握手,公开模式在下次准入创建新 thread。「继续」保留原 thread,并沿用同一页面记录 ID。跨 session 的历史展示去除旧 generation/turn 编号,避免新 session 使用相同编号覆盖旧消息。
恢复失败时需要检查账号、角色是否存在、运行时是否重启,以及 Journal 是否启用 PostgreSQL。默认内存后端无法在进程重启后恢复;旧记录缺少映射时也不能推测原 thread。此时可以新建对话并自行提供必要背景,但不等同于恢复原会话。
记录保存失败显示错误与「重试保存」按钮。当前以固定 800 ms 节奏刷写,并在页面隐藏、离页和恢复联网时刷写;未发送快照合并为最新版本,提交按 UTF-8 字节限制分段,丢失响应时先重试同一个幂等操作。临时失败按 1–30 秒退避,不阻塞其他对话。单次段最多 100 条,总会话最多 100000 条,长对话不会因 500 条的单次限制停止保存。
已确认用户身份的页面使用 IndexedDB 保存最新待发送快照、已确认版本及未确认请求。提交前先完成本机写入,重开对话页面后按原账号重试;账号切换后的旧请求由服务端拒绝。版本冲突或无效请求停止自动覆盖,保留草稿导出与移除入口;服务端确认记录已删除或过期后清理相应待发送副本。多个页面写同一份本机草稿时使用版本检查,避免静默覆盖。
本机待发送内容是明文,仅按账号分区,不是加密存储。不要在共享设备上留下未保存草稿;清理浏览器站点数据会删除它们。存储权限/容量错误会显示单独提示,仍尝试服务器保存。浏览器在本机事务提交前被强杀、用户清理站点数据或设备故障时,仍不能保证末尾内容;多段 keepalive 超过浏览器总配额时会留待下一次打开对话页面补传。Journal 对账仍待完成。
完善程度与后续优先级
本次优化补齐了当前页结束后继续、新建 thread 隔离、运行时身份保存、历史详情续聊入口、旧消息编号隔离以及保存失败提示。
仍需继续完善:
- 可靠保存:页面隐藏时刷写、超过 500 条消息时分段保存、Memory 提取失败提示和浏览器待发送数据持久化已实现;仍需服务端 Journal 与页面记录对账。
- 旧记录迁移:缺少 thread 标识的旧记录目前只读,没有自动推测身份或直接注入模型的隐式恢复流程。
- 持久化验收:线上使用 PostgreSQL 后验证跨刷新、跨进程重启、已过期 thread 和删除历史的行为。
- Memory 质量:生日更正替换旧生日、手动编辑版本检查和删除对话时清理来源记忆已实现;多事实提取、其他事实冲突替换、相关性筛选和来源查看仍需完善。
回归测试覆盖客户端握手与编号隔离、网页历史用户隔离及身份一致性、服务端 Journal 与恢复权限。未连接真实语音模型进行完整端到端验收,不能将这些测试视为模型实际回答效果的证明。
管理操作补充(2026-10-01)
关闭记忆后仍可手动维护条目;页面支持刷新、数量提示和切换角色清除草稿。增删改、清空与会话归零的区别,以及 API、部署和备份说明,见数据管理指南。整体后续工作见项目完善清单。
知识库网页“测试检索”与Python Runtime采用一致的词法检索规则:标题和正文参与匹配、中文重叠双字词组、英文完整单词、最低查询词覆盖率,并优先覆盖不同来源。未命中可尝试资料标题或原文关键词;它不证明资料中没有答案。34条合成任务的评测及已知同义问法漏召回详见 docs/reports/2026-10-09-knowledge-quality-evaluation.md,当前实现不提供语义召回保证。