From cbd926bd41f0cba9c67d2a7b97bfa59994f0e279 Mon Sep 17 00:00:00 2001 From: slhafzjw Date: Thu, 30 Apr 2026 16:08:18 +0800 Subject: [PATCH] docs(memory): add document for memory --- README.md | 3 - doc/memory/after-rolling.md | 45 +++++++++++++ doc/memory/memory-retrieval.md | 113 +++++++++++++++++++++++++++++++++ doc/memory/memory-storage.md | 99 +++++++++++++++++++++++++++++ doc/memory/memory.md | 14 ++++ 5 files changed, 271 insertions(+), 3 deletions(-) create mode 100644 doc/memory/after-rolling.md create mode 100644 doc/memory/memory-retrieval.md create mode 100644 doc/memory/memory-storage.md diff --git a/README.md b/README.md index e4c57512..8da5cde7 100644 --- a/README.md +++ b/README.md @@ -142,9 +142,6 @@ Partner/ - [模型提供商](doc/model/providers.md) - [ContextWorkspace](doc/context/context-workspace.md) - [行动系统](doc/action/action.md) - -### 待完成 - - [记忆存储与组织](doc/memory/memory.md) --- diff --git a/doc/memory/after-rolling.md b/doc/memory/after-rolling.md new file mode 100644 index 00000000..e8d9c4f6 --- /dev/null +++ b/doc/memory/after-rolling.md @@ -0,0 +1,45 @@ +# AfterRolling + +`AfterRolling` 是 `DialogRolling` 完成一次上下文滚动后的扩展点。它不生成原始记忆,而是消费已经生成的 `RollingResult` +,适用于维护组织层索引、补充组织信息,或执行与新记忆相关的异步副作用。 + +```mermaid +flowchart TD + A["DialogRolling.triggerRolling"] --> B["buildRollingResult"] + B --> C["MemoryCapability.updateMemoryUnit"] + C --> D["MemoryUnit / MemorySlice"] + D --> E["applyRolling"] + E --> F["dialog_history ContextBlock"] + E --> G["rollChatMessagesWithSnapshot"] + E --> H["AfterRollingRegistry.trigger"] + H --> I["AfterRolling consumer[]"] +``` + +rolling 的主流程先完成原始记忆写入和当前上下文窗口裁剪,然后再触发 `AfterRollingRegistry`。因此,`AfterRolling` consumer +面对的是已经稳定生成的记忆结果,而不是待写入的临时数据。 + +## RollingResult + +`RollingResult` 是 rolling 阶段传递给后处理 consumer 的数据包。 + +| 字段 | 说明 | +|---------------------|-----------------------| +| `memoryUnit` | 本次 rolling 写入或更新的记忆单元 | +| `memorySlice` | 本次 rolling 新生成的记忆切片 | +| `incrementMessages` | 本次进入记忆的增量消息 | +| `summary` | 本次切片摘要 | +| `rollingSize` | rolling 发生时的对话窗口大小 | +| `retainDivisor` | rolling 后保留近期上下文的比例参数 | + +这些字段让 consumer 可以同时看到三类信息:已经落盘的记忆对象、原始增量消息,以及本次 rolling 对上下文窗口的处理参数。 + +## 当前 consumer + +当前 memory 侧的主要 consumer 是 `MemoryRecallProfileExtractor`。它消费 `RollingResult`,读取当前主题树、切片摘要和原始消息片段,提取: + +- `topicPath` +- `relatedTopicPaths` +- `ActivationProfile` + +随后它调用 `MemoryRuntime.recordMemory(...)`,把新切片写入主题索引和日期索引。也就是说,原始记忆的生成发生在 +`DialogRolling` 中,索引和召回组织信息的补充发生在 `AfterRolling` consumer 中。 diff --git a/doc/memory/memory-retrieval.md b/doc/memory/memory-retrieval.md new file mode 100644 index 00000000..69fbfd8b --- /dev/null +++ b/doc/memory/memory-retrieval.md @@ -0,0 +1,113 @@ +# 记忆检索 + +本文说明 Partner 记忆系统的组织层。存储层只保存稳定的 `MemoryUnit` 与 `MemorySlice`,检索层在这些数据之上建立可替换的召回结构。 + +当前默认实现是 `MemoryRuntime`:它维护主题路径索引和日期索引,并把索引结果重新解析为可注入上下文的 `ActivatedMemorySlice`。 + +| 索引 | 作用 | +|---------------------------|------------------------------------------| +| `TopicMemoryIndex` | 按主题路径组织 `MemorySlice`,支持主主题、父主题和相关主题扩散召回 | +| `DateMemoryIndex` | 按日期记录 `MemorySlice` 引用,支持按日期直接召回 | +| `MemoryRuntimeStateCodec` | 负责主题索引和日期索引的持久化与恢复 | + +## 索引建立 + +`MemoryRuntime` 不直接生成原始记忆。原始记忆由 `DialogRolling` 写入 `MemoryUnit` 后,`AfterRolling` consumer 会消费 +`RollingResult`,为新产生的 `MemorySlice` 提取主题路径、相关主题和激活参数,再调用 `MemoryRuntime.recordMemory(...)` 建立索引。 + +```mermaid +flowchart TD + A["DialogRolling"] --> B["RollingResult"] + B --> C["AfterRollingRegistry"] + C --> D["MemoryRecallProfileExtractor"] + D --> E["topicPath
主主题路径"] + D --> F["relatedTopicPaths
相关主题路径"] + D --> G["ActivationProfile
激活 / 扩散 / 上下文独立权重"] + B --> H["MemoryUnit"] + B --> I["MemorySlice"] + H --> J["SliceRef
unitId + sliceId"] + I --> J + I --> K["LocalDate
由 slice timestamp 转换"] + J --> L["MemoryRuntime.recordMemory"] + E --> L + F --> L + G --> L + K --> L + L --> M["DateMemoryIndex.record"] + L --> N["TopicMemoryIndex.recordBinding"] + L --> O["TopicMemoryIndex.ensureTopicPaths"] + M --> P["date -> SliceRef[]"] + N --> Q["topicPath -> TopicBinding[]"] + O --> R["related topic path nodes"] +``` + +`TopicMemoryIndex` 的绑定对象不是完整记忆内容,而是 `SliceRef`。它只记录 `unitId`、`sliceId`、时间戳、相关主题路径和 +`ActivationProfile`。真正需要展示原始消息时,`MemoryRuntime` 会再回到 `MemoryCapability` 读取对应 `MemoryUnit` 和 +`MemorySlice`。 + +主题路径使用 `->` 表示层级,例如: + +```text +project->partner->memory +``` + +`TopicMemoryIndex` 会按路径建立树节点;绑定到某个节点的 slice 表示该记忆切片属于这个主题。相关主题路径不会直接复制 +slice,而是作为扩散召回的候选入口参与后续评分。 + +## 使用检索: 主题路径和日期索引 + +运行时召回由 `MemorySelector` 触发。它会收集输入,使用当前主题树和输入内容提取召回线索,再按线索类型分别查询主题索引或日期索引。 + +```mermaid +flowchart TD + subgraph Input["输入收集"] + A["PartnerRunningFlowContext"] --> B["MemorySelector.collectInputs"] + B --> C["collectedInputs"] + C --> D["drainMemoryRecall"] + end + + subgraph Cue["召回线索抽取"] + D --> E["MemoryRuntime.getTopicTree"] + D --> F["MemoryRecallCueExtractor"] + E --> F + F --> G["ExtractorResult.matches"] + end + + subgraph Lookup["索引查询"] + G --> H{"match.type"} + H -- " TOPIC " --> I["MemoryRuntime.queryActivatedMemoryByTopicPath"] + H -- " DATE " --> J["MemoryRuntime.queryActivatedMemoryByDate"] + I --> K["TopicRecallCollector.collect"] + J --> L["DateMemoryIndex.find"] + K --> M["SliceRef[]"] + L --> M + end + + subgraph Materialize["切片还原"] + M --> N["MemoryCapability.getMemoryUnit"] + M --> O["MemoryCapability.getMemorySlice"] + N --> P["sliceMessages"] + O --> P + P --> Q["ActivatedMemorySlice"] + end + + subgraph Evaluate["评估与上下文注入"] + Q --> R["MemoryRecallEvaluator"] + R --> S["activated_memory_slices ContextBlock"] + S --> T["ContextWorkspace"] + end +``` + +按主题召回时,`TopicRecallCollector` 会从三个来源收集候选: + +| 来源 | 含义 | +|-----------|-----------------------| +| `PRIMARY` | 当前主题节点直接绑定的 slice | +| `PARENT` | 父主题节点的近期候选,用于保留上层语境 | +| `RELATED` | 由当前主题绑定声明的相关主题,用于扩散召回 | + +候选会经过 `TopicRecallScorer` 打分。分数综合来源类型、时间新近性、激活权重、上下文独立权重和扩散权重。最终结果会限制数量,并转换成 +`ActivatedMemorySlice`。 + +`ActivatedMemorySlice` 包含 slice 摘要、日期、时间戳以及对应原始消息片段。`MemorySelector` 会基于这些结果构造 +`activated_memory_slices` 上下文块,使后续 communication、cognition 或 action 模块能够读取被激活的记忆。 diff --git a/doc/memory/memory-storage.md b/doc/memory/memory-storage.md new file mode 100644 index 00000000..68a60546 --- /dev/null +++ b/doc/memory/memory-storage.md @@ -0,0 +1,99 @@ +# 记忆存储 + +本文说明 Partner 记忆系统的稳定存储模型和原始记忆生成方式。 + +## MemoryUnit 与 MemorySlice + +### 数据模型 + +存储层由 `MemoryCore`、`MemoryUnit` 和 `MemorySlice` 三层组成。 + +`MemoryCore` 维护记忆单元集合和当前记忆会话 id;`MemoryUnit` 是可持久化的记忆单元,保存一段对话消息及其摘要切片; +`MemorySlice` 是对 `MemoryUnit` 中一段消息区间的摘要。 + +```mermaid +flowchart TD + A["MemoryCore"] --> B["memorySessionId
当前记忆会话"] + A --> C["memoryUnits
Map<unitId, MemoryUnit>"] + C --> D["MemoryUnit"] + D --> E["id"] + D --> F["timestamp
最近更新时间"] + D --> G["conversationMessages
原始对话消息"] + D --> H["slices
List<MemorySlice>"] + H --> I["MemorySlice"] + I --> J["id"] + I --> K["startIndex / endIndex
对应 conversationMessages 区间"] + I --> L["summary
该区间摘要"] + I --> M["timestamp
切片创建时间"] +``` + +`MemoryUnit` 的核心作用是把原始消息和摘要切片绑定在一起。`conversationMessages` 保存完整消息序列,`MemorySlice` +只记录它覆盖的消息区间和摘要内容。这样,召回层可以先使用摘要切片进行轻量检索,再在需要时回到对应 `MemoryUnit` 读取原始消息。 + +`MemoryCore` 自身只保存 `memorySessionId` 和 `memory_unit_uuid_set`,每个 `MemoryUnit` 独立持久化到自己的 state +path。也就是说,`MemoryCore` 负责管理有哪些记忆单元,而具体消息和切片内容保存在 `MemoryUnit` 中。 + +| 对象 | 作用 | +|---------------|------------------------------------| +| `MemoryCore` | 维护当前记忆会话 id、记忆单元集合,并提供读写入口 | +| `MemoryUnit` | 保存一组原始对话消息及其摘要切片,是稳定落盘单元 | +| `MemorySlice` | 描述 `MemoryUnit` 中一段消息区间的摘要,可被索引和召回 | + +`MemorySlice` 会按创建时间参与排序;`MemoryCore` 在更新记忆时会对 slice 的 id、时间戳和消息区间做规范化,避免索引越界或缺失基础字段。 + +### 生成方式 + +`MemoryUnit` 与 `MemorySlice` 的原始材料来自对话轨迹。`CommunicationProducer` 在完成对外回复后,将本轮用户输入和 assistant +输出写回 `CognitionCapability` 维护的 chat messages;`DialogRolling` 在对话窗口过长或长时间未交互时,基于这些 chat +messages 生成记忆切片。 + +```mermaid +flowchart TD + subgraph Communication["CommunicationProducer"] + A["PartnerRunningFlowContext
本轮输入"] --> B["生成对外回复"] + B --> C["updateChatMessages"] + C --> D["CognitionCapability.chatMessages"] + end + + subgraph Rolling["DialogRolling"] + D --> E{"触发 rolling?"} + E -- " 对话数量达到阈值 " --> F["triggerRolling(false)"] + E -- " 长时间未交互 " --> G["triggerRolling(true)"] + F --> H["snapshotChatMessages"] + G --> H + H --> I["resolveChatIncrement"] + I --> J["MessageCompressor"] + J --> K["MessageSummarizer"] + K --> L["MemoryCapability.updateMemoryUnit"] + L --> M["MemoryUnit"] + L --> N["MemorySlice"] + end + + subgraph Context["滚动后的上下文处理"] + N --> O["dialog_history ContextBlock"] + O --> P["ContextWorkspace"] + M --> Q["RollingResult"] + N --> Q + Q --> R["AfterRollingRegistry.trigger"] + end +``` + +生成流程可以分成两段: + +1. **对话轨迹写入**:`CommunicationProducer` 负责把本轮交流结果追加到 `chatMessages`。这里保存的是后续 rolling + 能够消费的原始对话轨迹。 +2. **对话滚动入库**:`DialogRolling` 从当前对话快照中计算尚未进入当前 `MemoryUnit` 的增量消息,压缩并总结后调用 + `MemoryCapability.updateMemoryUnit(chatMessages, summary)`。该调用会把消息增量追加到 `MemoryUnit.conversationMessages` + ,并创建一个覆盖这段消息区间的 `MemorySlice`。 + +`DialogRolling` 有两类触发方式: + +| 触发方式 | 含义 | +|----------|------------------------------------------------| +| 对话数量达到阈值 | 当前上下文对话过长时触发 rolling,只保留部分近期上下文 | +| 长时间未交互 | 定时检查发现距离上次交互超过阈值时触发 rolling,并刷新 memory session | + +rolling 完成后,系统会把新切片摘要注册为 `dialog_history` 上下文块,并裁剪当前 chat messages。随后 `AfterRollingRegistry` +会异步触发已注册的 `AfterRolling` consumer。这里的 consumer 只消费 `RollingResult` +,用于在原始记忆生成后执行额外维护逻辑;具体索引构建与召回组织在 [记忆检索](memory-retrieval.md) +中说明,扩展点机制见 [AfterRolling](after-rolling.md)。 diff --git a/doc/memory/memory.md b/doc/memory/memory.md index e69de29b..37d186a3 100644 --- a/doc/memory/memory.md +++ b/doc/memory/memory.md @@ -0,0 +1,14 @@ +# 记忆存储与组织 + +本文是 Partner 记忆系统文档索引。Partner 的记忆系统分为两个层面: + +- **存储层**:负责保存稳定的记忆数据。当前核心模型是 `MemoryUnit` 与 `MemorySlice`。 +- **组织层**:负责在存储数据之上建立可替换的召回结构。当前默认实现基于主题路径和日期索引;如果替换召回实现,需要由新实现自行维护对应索引。 + +这种划分让记忆落盘格式保持稳定,同时允许上层召回策略独立演进。 + +## 目录 + +- [`memory-storage.md`](memory-storage.md):说明 `MemoryCore`、`MemoryUnit`、`MemorySlice` 的数据模型,以及原始记忆如何由 `CommunicationProducer` 和 `DialogRolling` 生成。 +- [`memory-retrieval.md`](memory-retrieval.md):说明默认组织层 `MemoryRuntime`,包括主题路径索引、日期索引、索引建立和运行时召回。 +- [`after-rolling.md`](after-rolling.md):说明 `DialogRolling` 后的扩展触发点,以及 memory 侧如何通过 `AfterRolling` consumer 补充索引信息。