mirror of
https://github.com/slhaf/Partner.git
synced 2026-05-12 08:43:02 +08:00
docs(memory): add document for memory
This commit is contained in:
@@ -142,9 +142,6 @@ Partner/
|
|||||||
- [模型提供商](doc/model/providers.md)
|
- [模型提供商](doc/model/providers.md)
|
||||||
- [ContextWorkspace](doc/context/context-workspace.md)
|
- [ContextWorkspace](doc/context/context-workspace.md)
|
||||||
- [行动系统](doc/action/action.md)
|
- [行动系统](doc/action/action.md)
|
||||||
|
|
||||||
### 待完成
|
|
||||||
|
|
||||||
- [记忆存储与组织](doc/memory/memory.md)
|
- [记忆存储与组织](doc/memory/memory.md)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
45
doc/memory/after-rolling.md
Normal file
45
doc/memory/after-rolling.md
Normal file
@@ -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 中。
|
||||||
113
doc/memory/memory-retrieval.md
Normal file
113
doc/memory/memory-retrieval.md
Normal file
@@ -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<br/>主主题路径"]
|
||||||
|
D --> F["relatedTopicPaths<br/>相关主题路径"]
|
||||||
|
D --> G["ActivationProfile<br/>激活 / 扩散 / 上下文独立权重"]
|
||||||
|
B --> H["MemoryUnit"]
|
||||||
|
B --> I["MemorySlice"]
|
||||||
|
H --> J["SliceRef<br/>unitId + sliceId"]
|
||||||
|
I --> J
|
||||||
|
I --> K["LocalDate<br/>由 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 模块能够读取被激活的记忆。
|
||||||
99
doc/memory/memory-storage.md
Normal file
99
doc/memory/memory-storage.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
# 记忆存储
|
||||||
|
|
||||||
|
本文说明 Partner 记忆系统的稳定存储模型和原始记忆生成方式。
|
||||||
|
|
||||||
|
## MemoryUnit 与 MemorySlice
|
||||||
|
|
||||||
|
### 数据模型
|
||||||
|
|
||||||
|
存储层由 `MemoryCore`、`MemoryUnit` 和 `MemorySlice` 三层组成。
|
||||||
|
|
||||||
|
`MemoryCore` 维护记忆单元集合和当前记忆会话 id;`MemoryUnit` 是可持久化的记忆单元,保存一段对话消息及其摘要切片;
|
||||||
|
`MemorySlice` 是对 `MemoryUnit` 中一段消息区间的摘要。
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
flowchart TD
|
||||||
|
A["MemoryCore"] --> B["memorySessionId<br/>当前记忆会话"]
|
||||||
|
A --> C["memoryUnits<br/>Map<unitId, MemoryUnit>"]
|
||||||
|
C --> D["MemoryUnit"]
|
||||||
|
D --> E["id"]
|
||||||
|
D --> F["timestamp<br/>最近更新时间"]
|
||||||
|
D --> G["conversationMessages<br/>原始对话消息"]
|
||||||
|
D --> H["slices<br/>List<MemorySlice>"]
|
||||||
|
H --> I["MemorySlice"]
|
||||||
|
I --> J["id"]
|
||||||
|
I --> K["startIndex / endIndex<br/>对应 conversationMessages 区间"]
|
||||||
|
I --> L["summary<br/>该区间摘要"]
|
||||||
|
I --> M["timestamp<br/>切片创建时间"]
|
||||||
|
```
|
||||||
|
|
||||||
|
`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<br/>本轮输入"] --> 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)。
|
||||||
@@ -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 补充索引信息。
|
||||||
|
|||||||
Reference in New Issue
Block a user