amrita_plugin_memory 0.3.1__tar.gz → 0.4.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- amrita_plugin_memory-0.4.1/PKG-INFO +722 -0
- amrita_plugin_memory-0.4.1/README.md +703 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/__init__.py +43 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/cli.py +259 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/config.py +30 -3
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/embedding.py +402 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/keys.py +141 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/matchers.py +9 -9
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/memo.py +119 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/migrations/21f55abc2b90_init.py +39 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/migrations/6004d221a7de_state.py +37 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory/models.py +44 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/_state.py +5 -2
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/backend.py +1 -2
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/consts.py +5 -5
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/hooks.py +1 -1
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/knowledge.py +5 -13
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/runner.py +113 -102
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/schemas.py +1 -4
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/tools.py +7 -26
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/tools.py +47 -23
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/vector.py +7 -3
- amrita_plugin_memory-0.4.1/amrita_plugin_memory.egg-info/PKG-INFO +722 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/SOURCES.txt +8 -0
- amrita_plugin_memory-0.4.1/amrita_plugin_memory.egg-info/entry_points.txt +2 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/requires.txt +3 -1
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/pyproject.toml +10 -4
- amrita_plugin_memory-0.3.1/PKG-INFO +0 -450
- amrita_plugin_memory-0.3.1/README.md +0 -433
- amrita_plugin_memory-0.3.1/amrita_plugin_memory/__init__.py +0 -10
- amrita_plugin_memory-0.3.1/amrita_plugin_memory.egg-info/PKG-INFO +0 -450
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/LICENSE +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/embed.py +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/__init__.py +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/nodes.py +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/types.py +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
- {amrita_plugin_memory-0.3.1 → amrita_plugin_memory-0.4.1}/setup.cfg +0 -0
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: amrita_plugin_memory
|
|
3
|
+
Version: 0.4.1
|
|
4
|
+
Summary: Amrita长期记忆插件
|
|
5
|
+
Requires-Python: <3.14,>=3.10
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Dist: aiohttp>=3.13.3
|
|
9
|
+
Requires-Dist: aiologic>=0.16.0
|
|
10
|
+
Requires-Dist: amrita-core>=0.8.5
|
|
11
|
+
Requires-Dist: amrita>=1.3
|
|
12
|
+
Requires-Dist: chromadb>=1.5.8
|
|
13
|
+
Requires-Dist: click>=8.1
|
|
14
|
+
Requires-Dist: nonebot-plugin-amrita>=0.3.0
|
|
15
|
+
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0
|
|
16
|
+
Requires-Dist: nonebot-plugin-localstore>=0.7.4
|
|
17
|
+
Requires-Dist: nonebot-plugin-orm[default]>=0.8.2
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# amrita_plugin_memory
|
|
21
|
+
|
|
22
|
+
基于向量数据库的长期记忆插件 — **三层记忆模型**:
|
|
23
|
+
|
|
24
|
+
| 层 | 名称 | 类比 | 载体 | 何时使用 |
|
|
25
|
+
| -- | ------------- | -------------------------- | -------------------------------- | -------------------- |
|
|
26
|
+
| L1 | 备忘录 | 恒常激活的持久记忆 | 插件自有 ORM 表(per-uni_id 单行) | 每轮常驻注入 |
|
|
27
|
+
| L2 | 向量记忆层 | 语义记忆(按线索提取) | ChromaDB 向量库 | LLM 按需检索 |
|
|
28
|
+
| L3 | 离线巩固循环 | 默认模式网络(DMN) | 后台 ReAct Agent | 静默期自动整理 |
|
|
29
|
+
|
|
30
|
+
- **L1** — 一段千字内的用户元信息(身份、稳定偏好、用户要求的工具调用模式),由 LLM 维护,每轮注入 system prompt。
|
|
31
|
+
- **L2** — 向量化的长尾事实库,LLM 通过工具按需读写,ChromaDB 语义检索。
|
|
32
|
+
- **L3** — 用户静默时触发的后台整理循环:去重、压缩、画像精炼、知识沉淀。
|
|
33
|
+
|
|
34
|
+
## 安装
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
ambot plugin add amrita_plugin_memory
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
前置依赖:Python 3.11+ / AmritaBot 实例 / **Ollama 或 OpenAI 嵌入服务** / ChromaDB。
|
|
41
|
+
|
|
42
|
+
> 嵌入服务是**必需**前置 —— 本插件不自带嵌入模型。
|
|
43
|
+
|
|
44
|
+
## 快速开始
|
|
45
|
+
|
|
46
|
+
### 1. 配置环境变量(`.env`)
|
|
47
|
+
|
|
48
|
+
```env
|
|
49
|
+
VECTOR_DB_TYPE=local
|
|
50
|
+
EMBEDDING_MODEL_URL=http://127.0.0.1:11434
|
|
51
|
+
EMBEDDING_MODEL_NAME=auto
|
|
52
|
+
EMBEDDING_PROCTOL=ollama-embed
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 2. L1 + L2 开箱可用
|
|
56
|
+
|
|
57
|
+
无需额外配置。LLM 会在需要时自动调用 `write_memory` / `read_memory` / `update_memo` 等工具;
|
|
58
|
+
L1 备忘录一旦写入,此后每轮对话都会自动注入。
|
|
59
|
+
|
|
60
|
+
### 3. 开启 L3 离线巩固循环(可选)
|
|
61
|
+
|
|
62
|
+
编辑 `config/amrita_plugin_memory/config.toml`:
|
|
63
|
+
|
|
64
|
+
```toml
|
|
65
|
+
[subconscious]
|
|
66
|
+
enabled = true
|
|
67
|
+
target_user_id = "你的QQ号"
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
用户每次发消息后,后台 Agent 会在 30 分钟后自动整理记忆库。
|
|
71
|
+
|
|
72
|
+
> **适用场景**:L3 专为**个人助理**场景设计 —— 单个 Bot 服务单个用户。
|
|
73
|
+
> 它会在后台持续调用 LLM,**每轮推理可能消耗数万 tokens**。
|
|
74
|
+
> 如果 Bot 服务于大量用户或对 token 成本敏感,建议保持 `enabled = false`。
|
|
75
|
+
|
|
76
|
+
允许 Agent 主动发私聊消息:
|
|
77
|
+
|
|
78
|
+
```toml
|
|
79
|
+
allow_send_to_user = true
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
关闭全局知识库以节省 token:
|
|
83
|
+
|
|
84
|
+
```toml
|
|
85
|
+
enable_knowledge = false
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
> 知识库当前依赖 L3 —— 当 `enabled = false` 时,知识库也会自动禁用。
|
|
89
|
+
|
|
90
|
+
### 4. 验证
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
ambot memory status # 查看嵌入指纹、记忆分布与备份
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
观察日志中 `[Subconscious]` / `[Memory]` 前缀的输出:
|
|
97
|
+
|
|
98
|
+
```text
|
|
99
|
+
[Subconscious] Starting for user=你的QQ号
|
|
100
|
+
[Subconscious] Idle — waiting for user chat to trigger first run
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
用户发消息后约 30 分钟,会看到 `Cycle #1` 开始执行。
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## 三层记忆模型
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
flowchart TB
|
|
111
|
+
subgraph Prompt["每轮 system prompt 注入"]
|
|
112
|
+
Memo["L1 备忘录<br/><MEMO> 事实块"]
|
|
113
|
+
Extra["extra_prompt<br/><EXTRA> 用户指定规则"]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
subgraph L1["L1 备忘录(常驻)"]
|
|
117
|
+
MemoTable[("UserMemo 表<br/>主键 = uni_id")]
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
subgraph L2["L2 向量记忆层(按需)"]
|
|
121
|
+
ChromaDB[("ChromaDB<br/>amrita_user_memory")]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
subgraph L3["L3 离线巩固循环(DMN)"]
|
|
125
|
+
Runner["SubconsciousRunner"]
|
|
126
|
+
KB["KnowledgeBaseManager"]
|
|
127
|
+
Profile["用户画像"]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
MemoTable --> Memo
|
|
131
|
+
Memo -->|"update_memo 整篇替换"| MemoTable
|
|
132
|
+
Memo -->|"细节下放"| ChromaDB
|
|
133
|
+
|
|
134
|
+
SurfaceTools["对话 LLM 的记忆工具"] -->|"write/read/update/delete/list"| ChromaDB
|
|
135
|
+
Runner -->|"去重 / 压缩 / 精炼"| ChromaDB
|
|
136
|
+
Runner --> KB
|
|
137
|
+
Runner --> Profile
|
|
138
|
+
Runner -->|"元状态"| StateTable[("SubconsciousState 表")]
|
|
139
|
+
Runner -->|"usage 统计"| Insights["InsightsModel"]
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### L1 与 `extra_prompt` 的正交性
|
|
143
|
+
|
|
144
|
+
两者都会注入 system prompt,但来源、作者与优先级完全不同:
|
|
145
|
+
|
|
146
|
+
| | `extra_prompt` | L1 备忘录 |
|
|
147
|
+
| -------- | ---------------------------------- | ---------------------------------- |
|
|
148
|
+
| 作者 | **用户**逐字指定(`/prompt` 命令) | **LLM** 规范化提取 |
|
|
149
|
+
| 管理方式 | 用户自己增删改 | LLM 自主维护 |
|
|
150
|
+
| 注入形态 | `<EXTRA>` 规则块,**冲突时忽略** | `<MEMO>` 事实块,作为可信背景 |
|
|
151
|
+
| 语义 | 用户想要的行为指令 | LLM 学到的关于该主体的元信息 |
|
|
152
|
+
|
|
153
|
+
插件**不会**读写 `extra_prompt` —— 那是用户的领地。
|
|
154
|
+
|
|
155
|
+
### 命名对照表
|
|
156
|
+
|
|
157
|
+
L3 在代码中沿用历史标识符 `subconscious` / `rethinking`(避免破坏性变更),
|
|
158
|
+
文档统一表述为"离线巩固循环",其行为与神经科学的**默认模式网络(DMN)**高度对应:
|
|
159
|
+
|
|
160
|
+
| 代码标识符 | 文档表述 | DMN 对应特征 |
|
|
161
|
+
| --------------------------------- | -------------- | ------------------------------------------------ |
|
|
162
|
+
| `rethinking/` 模块 | 离线巩固循环 | — |
|
|
163
|
+
| `SubconsciousRunner` | 巩固循环运行器 | — |
|
|
164
|
+
| `[subconscious]` 配置节 | 巩固循环配置 | — |
|
|
165
|
+
| `subconscious_*` 工具 | 巩固循环工具 | — |
|
|
166
|
+
| 用户聊天 → `cancel_and_reschedule` | 任务负激活 | 专注任务时 DMN 被抑制 |
|
|
167
|
+
| 静默期触发 `_run` | 空闲时活跃 | DMN 在无任务时活跃 |
|
|
168
|
+
| 去重 / 压缩 / `MemoryLimiter` | 记忆巩固 | 海马 → 皮层的系统巩固 |
|
|
169
|
+
| 画像构建(`*_profile`) | 自传体记忆 | DMN 负责自我参照加工 |
|
|
170
|
+
| 主动消息(`send_to_user`) | 心智游移 | 走神产生与自我相关的念头 |
|
|
171
|
+
| 每轮数万 tokens | 静息高能耗 | DMN 占脑能耗约 20% |
|
|
172
|
+
|
|
173
|
+
> 它**不是**弗洛伊德式的"潜意识":触发确定性、工具显式、日志完整、输入输出全透明。
|
|
174
|
+
> 它也不是"常驻循环"——而是**事件驱动的单次任务**,跑完即空闲;用户聊天越频繁,它跑得越少。
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## L1 备忘录
|
|
179
|
+
|
|
180
|
+
**定位**:每次对话都要用到的恒常信息 —— 身份(名字 / 证件 / 电话)、稳定偏好、
|
|
181
|
+
**用户要求的工具调用模式**。类比人类恒常激活的持久记忆。
|
|
182
|
+
|
|
183
|
+
### 关键约束:严格字数上限
|
|
184
|
+
|
|
185
|
+
`memo_max_chars`(默认 **1000** 字)是**硬上限**。这带来一个根本约束:
|
|
186
|
+
写入原语必须是**整篇替换**而非追加,否则上限形同虚设。
|
|
187
|
+
|
|
188
|
+
因此 `update_memo` 的语义是:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
LLM 读当前 memo → 融合新事实 → 输出新全文 → 服务端校验长度 → 落库
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
超长时工具**拒绝写入**并返回当前字数,由 LLM 自行压缩后重试。
|
|
195
|
+
|
|
196
|
+
### 内容规范
|
|
197
|
+
|
|
198
|
+
| 该放 L1 | 该放 L2 |
|
|
199
|
+
| --------------------------- | ------------------------------- |
|
|
200
|
+
| 身份信息、稳定偏好 | 具体事件、长尾偏好 |
|
|
201
|
+
| 用户要求的工具调用模式 | 项目细节、讨论记录 |
|
|
202
|
+
| 当前活跃上下文(高度浓缩) | 任何不需要每轮都看到的内容 |
|
|
203
|
+
|
|
204
|
+
写入指南(写进 L3 的提示词):
|
|
205
|
+
|
|
206
|
+
- 只写**陈述性事实**,不写祈使句规则(规则属于 `extra_prompt`)
|
|
207
|
+
- 内部按 `身份 > 偏好 > 工具模式 > 近期上下文` 排序,压缩时从尾部开始丢
|
|
208
|
+
- 临近上限时,细节应**下放 L2**,memo 只留指针(如"用户有 3 个项目")
|
|
209
|
+
|
|
210
|
+
### 存储与注入
|
|
211
|
+
|
|
212
|
+
- **表**:`amrita_plugin_memory_user_memo`,主键 `user_id` = 框架 uni_id
|
|
213
|
+
- **群与个人是同一张表的不同行**:
|
|
214
|
+
- `QQPlatform_Private_{qq}` / `user_{qq}` → 个人备忘录
|
|
215
|
+
- `QQPlatform_Group_{群号}` / `group_{群号}` → 群备忘录
|
|
216
|
+
- **注入**:`on_precompletion` hook(priority=15),与 L3 **完全解耦**(L3 关闭时 L1 照常工作)
|
|
217
|
+
- 私聊 → 注入个人 memo
|
|
218
|
+
- **群聊 → 只注入群 memo**(发言人的个人 memo 不注入,避免隐私外泄)
|
|
219
|
+
- 空 memo 不注入,行为自然降级
|
|
220
|
+
- **缓存**:进程内 `LRUCache(128)`,写入时穿透失效
|
|
221
|
+
|
|
222
|
+
### 工具
|
|
223
|
+
|
|
224
|
+
| 工具 | 参数 | 说明 |
|
|
225
|
+
| ------------- | --------- | -------------------------------------------- |
|
|
226
|
+
| `update_memo` | `content` | 整篇替换当前作用域的备忘录,超长则拒绝 |
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## L2 向量记忆层
|
|
231
|
+
|
|
232
|
+
**定位**:长尾事实库 —— 只在需要时检索,不常驻。类比需要线索提取的语义记忆。
|
|
233
|
+
|
|
234
|
+
### 分区键(uni_id)
|
|
235
|
+
|
|
236
|
+
分区键**跟随已安装 Amrita 的会话 ID 格式**(`amrita.plugins.chat.utils.sql.make_uni_id`):
|
|
237
|
+
|
|
238
|
+
| Amrita 版本 | 格式 |
|
|
239
|
+
| ----------- | -------------------------------------------------- |
|
|
240
|
+
| ≤ 1.9.x | `user_{qq}` / `group_{群号}` |
|
|
241
|
+
| 开发中 | `QQPlatform_Private_{qq}` / `QQPlatform_Group_{群号}` |
|
|
242
|
+
|
|
243
|
+
插件不硬编码任一格式,而是委托框架生成 —— Amrita 升级后自动跟随。
|
|
244
|
+
存量数据的 Key 迁移由启动检查自动完成(见下节)。
|
|
245
|
+
|
|
246
|
+
`scope` 参数表达语义,与 uni_id 类型是两个维度:
|
|
247
|
+
|
|
248
|
+
| `scope` | 含义 | 可用场景 |
|
|
249
|
+
| ------- | -------------------------- | ---------- |
|
|
250
|
+
| `user` | 个人专属(群聊私聊互通) | 任意 |
|
|
251
|
+
| `group` | 群共享(群内所有人可见) | 仅群聊 |
|
|
252
|
+
|
|
253
|
+
### 记忆元数据
|
|
254
|
+
|
|
255
|
+
每条记忆带 `user_id` / `scope` / `tags` / `importance` / `created_at`。
|
|
256
|
+
`importance` 分 `low` / `medium` / `high`,可在检索时作为过滤条件。
|
|
257
|
+
|
|
258
|
+
### 检索
|
|
259
|
+
|
|
260
|
+
`read_memory` 用嵌入向量做相似度搜索,支持 `top_k` 与 `importance` 过滤。
|
|
261
|
+
删除群共享记忆需管理员或群主权限。
|
|
262
|
+
|
|
263
|
+
检索结果附带**距离**(`distances`)——ChromaDB 默认使用 `l2`(平方欧氏距离)空间,
|
|
264
|
+
因此**数值越小越相似**(完全相同的向量为 `0`)。`/memory user search` 输出中的
|
|
265
|
+
"距离"即此值。
|
|
266
|
+
|
|
267
|
+
> 距离是原始度量而非归一化相似度:`l2` 空间下其取值范围取决于向量是否归一化,
|
|
268
|
+
> 因此不要跨模型比较绝对值,只用于同一查询内的相对排序。
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## L3 离线巩固循环(DMN)
|
|
273
|
+
|
|
274
|
+
### 触发:事件驱动 + 指数惩罚退避
|
|
275
|
+
|
|
276
|
+
**不使用定时自循环** —— 只有目标用户发消息时才触发。
|
|
277
|
+
|
|
278
|
+
用户每次聊天 → 取消现有计划 → 惩罚计数 +1 → 重新计算延迟:
|
|
279
|
+
|
|
280
|
+
$$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\ \text{cap})$$
|
|
281
|
+
|
|
282
|
+
默认 `base=30min`、`multiplier=1.5`、`cap=1440min`。推理成功后惩罚重置为 0。
|
|
283
|
+
用户连续聊天会自动推开推理(**任务负激活**),长时间沉默后恢复正常频率。
|
|
284
|
+
|
|
285
|
+
### 每轮做什么
|
|
286
|
+
|
|
287
|
+
| 阶段 | 内容 |
|
|
288
|
+
| -------------- | ---------------------------------------------------------- |
|
|
289
|
+
| 状态加载 | 从 `SubconsciousState` 表恢复 `total_runs` / 摘要窗口 |
|
|
290
|
+
| 记忆限幅 | Core `MemoryLimiter` 截断超限消息并生成摘要 |
|
|
291
|
+
| ReAct 循环 | Agent 调用 `subconscious_*` 工具执行整理 |
|
|
292
|
+
| 后处理 | 更新全局 usage、持久化元状态、重置惩罚、调度下次运行 |
|
|
293
|
+
|
|
294
|
+
### 整理职责
|
|
295
|
+
|
|
296
|
+
- **去重合并**:`subconscious_duplicate_helper` 返回待整理记忆 + 合并指导
|
|
297
|
+
- **重要性校准**:评估每条记忆的 `low` / `medium` / `high`
|
|
298
|
+
- **标签管理**:确保 tags 有意义且一致
|
|
299
|
+
- **画像精炼**:`*_profile` 渐进式构建用户画像(行级增量更新)
|
|
300
|
+
- **知识沉淀**:把可迁移的经验写入全局知识库
|
|
301
|
+
- **备忘录压缩**(L1 联动):临近上限时把细节下放 L2
|
|
302
|
+
|
|
303
|
+
### Workflow 管线
|
|
304
|
+
|
|
305
|
+
```mermaid
|
|
306
|
+
flowchart TD
|
|
307
|
+
LOAD_STATE --> JINJA2_RENDER --> LIMITING_MEMORY --> BUILD_MESSAGE --> REACT_BLOCK
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
`SubconsciousRunner` 把 ChatObject 当数据容器,注入自定义 `SubconsciousBackend`
|
|
311
|
+
(隔离的工具 + memory 后端)与 Core `ReActAgentStrategy`。
|
|
312
|
+
`LIMITING_MEMORY` 在 Agent Loop 前运行 Core `MemoryLimiter`。
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
|
|
316
|
+
## 嵌入模型与重映射
|
|
317
|
+
|
|
318
|
+
### 为什么需要指纹
|
|
319
|
+
|
|
320
|
+
向量只有在**同一嵌入模型**下才可比。更换模型(`nomic-embed-text` → `bge-m3`)
|
|
321
|
+
甚至只是服务端同名模型被更新,旧向量都会退化为噪声 —— 但 ChromaDB 不会报错,
|
|
322
|
+
检索结果会静默劣化。
|
|
323
|
+
|
|
324
|
+
因此插件在集合 metadata 中记录**嵌入配置指纹**:
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
fingerprint = sha256(protocol | model | base_url)
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
随库走(远程 Chroma 同样生效),并在**每次启动**校验。
|
|
331
|
+
|
|
332
|
+
### 启动检查流程
|
|
333
|
+
|
|
334
|
+
检查在**插件 import 期**(同步)执行:
|
|
335
|
+
|
|
336
|
+
```mermaid
|
|
337
|
+
flowchart TD
|
|
338
|
+
A["插件加载"] --> B{"EMBED_CHECK_ON_STARTUP?"}
|
|
339
|
+
B -->|false| Z["跳过"]
|
|
340
|
+
B -->|true| C["Key 迁移(幂等,始终执行)"]
|
|
341
|
+
C --> D{"集合为空?"}
|
|
342
|
+
D -->|是| E["写入指纹,零打扰"]
|
|
343
|
+
D -->|否| F{"指纹一致?"}
|
|
344
|
+
F -->|是| Z
|
|
345
|
+
F -->|否| G{"EMBED_MISMATCH_POLICY"}
|
|
346
|
+
G -->|never| H["告警,保持现有数据"]
|
|
347
|
+
G -->|auto| K["全量重映射"]
|
|
348
|
+
G -->|ask| I{"stdin 是 TTY?"}
|
|
349
|
+
I -->|否| X["拒绝加载插件<br/>(Bot 继续运行,插件不可用)"]
|
|
350
|
+
I -->|是| Y["交互确认"]
|
|
351
|
+
Y -->|确认| K
|
|
352
|
+
Y -->|拒绝| H
|
|
353
|
+
K --> L["备份 JSON → 删集合 → 重建 → 分批重嵌入 → 写新指纹"]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
**重映射对维度变化天然免疫** —— 走"删集合 → 重建"路径,因此无需探测向量维度。
|
|
357
|
+
|
|
358
|
+
**失败中断**:保留半成品集合 + 备份 + **不写新指纹**,下次启动重新提示(幂等重试)。
|
|
359
|
+
|
|
360
|
+
### 配置
|
|
361
|
+
|
|
362
|
+
| 环境变量 | 默认值 | 说明 |
|
|
363
|
+
| ------------------------ | ------ | ---------------------------------------------------------- |
|
|
364
|
+
| `EMBED_CHECK_ON_STARTUP` | `true` | 设为 `false` 跳过检查(排查 / 迁移期间临时使用) |
|
|
365
|
+
| `EMBED_MISMATCH_POLICY` | `ask` | `ask`=交互确认;`auto`=自动重映射;`never`=仅告警继续 |
|
|
366
|
+
| `REEMBED_BATCH_SIZE` | `64` | 重映射时每批嵌入的文本条数 |
|
|
367
|
+
| `REEMBED_BACKUP_KEEP` | `3` | 保留的历史备份份数 |
|
|
368
|
+
|
|
369
|
+
> `ask` 策略下,**非交互环境**(systemd / Docker)会拒绝加载插件并给出明确指引。
|
|
370
|
+
> 这类部署建议显式设置 `EMBED_MISMATCH_POLICY`。
|
|
371
|
+
|
|
372
|
+
### 已知限制
|
|
373
|
+
|
|
374
|
+
**模型名不变但服务端实际模型被换**(如 ollama 同名 tag 更新)无法检测 ——
|
|
375
|
+
指纹只看配置。此时用 `ambot memory reindex` 手动强制重映射。
|
|
376
|
+
|
|
377
|
+
### 备份
|
|
378
|
+
|
|
379
|
+
重映射前自动把集合全量内容落盘到 `data/amrita_plugin_memory/backups/embed_backup_<时间戳>.json`
|
|
380
|
+
(ids + documents + metadatas)。这是"删集合重建"路径**唯一的回滚手段**。
|
|
381
|
+
|
|
382
|
+
> 备份不含向量 —— 恢复时用**当前**模型重新嵌入,这正是恢复场景需要的。
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 工具参考
|
|
387
|
+
|
|
388
|
+
### 表层工具(对话 LLM 可用)
|
|
389
|
+
|
|
390
|
+
| 工具 | 参数 |
|
|
391
|
+
| ------------------- | --------------------------------------------- |
|
|
392
|
+
| `write_memory` | content, tags, importance(enum), scope(enum) |
|
|
393
|
+
| `read_memory` | query, top_k(5), importance?, scope(enum) |
|
|
394
|
+
| `update_memory` | id, scope, content?, tags?, importance? |
|
|
395
|
+
| `delete_memory` | id, scope |
|
|
396
|
+
| `list_memory` | limit, scope |
|
|
397
|
+
| `update_memo` | content(L1 备忘录整篇替换) |
|
|
398
|
+
| `knowledge_list` | — |
|
|
399
|
+
| `knowledge_read` | kid, start_line?, end_line? |
|
|
400
|
+
| `knowledge_search` | query, top_k? |
|
|
401
|
+
| `knowledge_suggest` | action, title, summary, body, reason |
|
|
402
|
+
|
|
403
|
+
### 巩固循环工具(`rethinking/tools.py`,共 20 个)
|
|
404
|
+
|
|
405
|
+
记忆与 session/画像工具注册在隔离的 `_SUBCONSCIOUS_TOOLS` 上,不污染全局工具管理器。
|
|
406
|
+
知识库的 `list`/`read`/`search` 双重注册(表层 + 循环均可调用);
|
|
407
|
+
`create`/`update`/`delete` 仅循环可用,表层通过 `knowledge_suggest` 提交建议。
|
|
408
|
+
|
|
409
|
+
| 工具 | 用途 |
|
|
410
|
+
| -------------------------------- | -------------------------------------- |
|
|
411
|
+
| `subconscious_read_memory` | 语义检索 |
|
|
412
|
+
| `subconscious_write_memory` | 写入新记忆 |
|
|
413
|
+
| `subconscious_update_memory` | 更新指定 ID 记忆 |
|
|
414
|
+
| `subconscious_delete_memory` | 删除指定 ID 记忆 |
|
|
415
|
+
| `subconscious_list_memory` | 列出全部记忆 |
|
|
416
|
+
| `subconscious_iter_stop` | 结束本轮推理 |
|
|
417
|
+
| `subconscious_send_to_user` | 主动向用户发消息 |
|
|
418
|
+
| `subconscious_read_chat_context` | 读取最近聊天记录 |
|
|
419
|
+
| `subconscious_duplicate_helper` | 去重辅助(返回记忆 + 合并指导 prompt) |
|
|
420
|
+
| `subconscious_get_memory_stats` | 统计概览 |
|
|
421
|
+
| `subconscious_knowledge_list` | 列出全局知识条目 |
|
|
422
|
+
| `subconscious_knowledge_read` | 读取知识条目(支持行滑动) |
|
|
423
|
+
| `subconscious_knowledge_create` | 创建知识条目 |
|
|
424
|
+
| `subconscious_knowledge_update` | 更新知识条目 |
|
|
425
|
+
| `subconscious_knowledge_delete` | 删除知识条目 |
|
|
426
|
+
| `subconscious_knowledge_search` | 语义搜索知识库 |
|
|
427
|
+
| `subconscious_read_suggestions` | 读取待审查的知识建议(读取后清空) |
|
|
428
|
+
| `subconscious_read_sessions` | 读取归档 sessions(LLM 摘要) |
|
|
429
|
+
| `subconscious_get_profile` | 读取用户画像(行滑动窗口) |
|
|
430
|
+
| `subconscious_update_profile` | 增量更新用户画像 |
|
|
431
|
+
|
|
432
|
+
---
|
|
433
|
+
|
|
434
|
+
## 命令参考
|
|
435
|
+
|
|
436
|
+
### 用户命令 `/memory`
|
|
437
|
+
|
|
438
|
+
```text
|
|
439
|
+
/memory user list — 列出个人记忆
|
|
440
|
+
/memory user search <关键词> — 搜索个人记忆
|
|
441
|
+
/memory user delete <ID> — 删除个人记忆
|
|
442
|
+
/memory group list — 列出群共享记忆
|
|
443
|
+
/memory group search <关键词> — 搜索群共享记忆
|
|
444
|
+
/memory group delete <ID> — 删除群共享记忆(仅管理员 / 群主)
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
兼容旧格式 `/memory list` 等(默认以个人范围执行)。
|
|
448
|
+
|
|
449
|
+
### 运维命令 `ambot memory`
|
|
450
|
+
|
|
451
|
+
需要本地安装含该命令组的 ambot-inlinectl(见 [开发](#开发))。
|
|
452
|
+
|
|
453
|
+
| 命令 | 作用 |
|
|
454
|
+
| ------------------------------ | ------------------------------------------------ |
|
|
455
|
+
| `ambot memory status` | 显示指纹、记忆分布、分区键版本、备份列表 |
|
|
456
|
+
| `ambot memory reindex [--yes]` | 强制全量重映射并写新指纹 |
|
|
457
|
+
| `ambot memory migrate-keys [--dry-run]` | 迁移分区键到当前 Amrita 的 uni_id 格式 |
|
|
458
|
+
| `ambot memory reset-fingerprint` | 只写指纹、不重嵌入(逃生舱) |
|
|
459
|
+
| `ambot memory backup list` | 列出备份 |
|
|
460
|
+
| `ambot memory backup create` | 手动创建备份 |
|
|
461
|
+
| `ambot memory backup restore <file>` | 从备份恢复(用当前模型重新嵌入) |
|
|
462
|
+
|
|
463
|
+
> 这些命令会先设置 `EMBED_CHECK_ON_STARTUP=false` 再加载插件 ——
|
|
464
|
+
> 避免启动检查在 CLI 中触发交互,保证命令**永远可用**(逃生舱)。
|
|
465
|
+
|
|
466
|
+
---
|
|
467
|
+
|
|
468
|
+
## 持久化与状态恢复
|
|
469
|
+
|
|
470
|
+
### 存储
|
|
471
|
+
|
|
472
|
+
| 存储 | 技术 | 存什么 |
|
|
473
|
+
| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
474
|
+
| L1 备忘录 | `UserMemo` 表(插件自有 ORM,主键 = uni_id) | LLM 维护的常驻用户元信息(≤ `memo_max_chars`) |
|
|
475
|
+
| 循环元状态 | `SubconsciousState` 表(插件自有 ORM,uid=`amrita_memory`) | `total_runs`、`last_abstracts`、`pending_messages`、`knowledge_suggestions` |
|
|
476
|
+
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用 MemoryLimiter |
|
|
477
|
+
| 惩罚计数器 | 内存(不持久化) | `_penalty_count`:重启后从 0 开始 |
|
|
478
|
+
| L2 记忆 | ChromaDB `amrita_user_memory` | 向量 + documents + metadatas(含嵌入指纹与分区键版本) |
|
|
479
|
+
| 用户画像 | `data/amrita_plugin_memory/user_profile.md` | Markdown 文件,`summary---body` 格式,行级增量更新 |
|
|
480
|
+
| 全局知识库 | `data/amrita_plugin_memory/knowledge/` + `knowledge_index.json` + ChromaDB | 三方同步管理 |
|
|
481
|
+
| Token 统计 | `InsightsModel`(复用 Bot ORM) | 全局 prompt/completion token 累加 |
|
|
482
|
+
|
|
483
|
+
### ORM 与迁移
|
|
484
|
+
|
|
485
|
+
插件使用 `nonebot_plugin_orm`,自带迁移链:
|
|
486
|
+
|
|
487
|
+
| revision | 内容 |
|
|
488
|
+
| ---------------- | ----------------------------- |
|
|
489
|
+
| `21f55abc2b90` | `UserMemo` 表(分支根,`depends_on=072361e8936f`) |
|
|
490
|
+
| `6004d221a7de` | `SubconsciousState` 表 |
|
|
491
|
+
|
|
492
|
+
迁移随包分发(`amrita_plugin_memory/migrations/`),并**依赖 Bot 的迁移链**
|
|
493
|
+
(`depends_on` 指向 `nonebot_plugin_amrita` 的初始迁移),保证外键引用有效。
|
|
494
|
+
|
|
495
|
+
### 生命周期
|
|
496
|
+
|
|
497
|
+
```mermaid
|
|
498
|
+
flowchart TD
|
|
499
|
+
Startup["Bot 启动"] --> Check["run_startup_check()<br/>Key 迁移 + 嵌入指纹校验"]
|
|
500
|
+
Check --> Load["_load_state()<br/>恢复 total_runs / last_abstracts / pending_messages"]
|
|
501
|
+
Load --> KB["KnowledgeBaseManager<br/>init() + validate_on_startup()"]
|
|
502
|
+
KB --> Idle["进入空闲<br/>等待用户聊天触发"]
|
|
503
|
+
Idle -->|用户发消息| Cancel["cancel_and_reschedule()"]
|
|
504
|
+
Cancel -->|延迟到达| Run["_run() → _run_cycle() ReAct 循环"]
|
|
505
|
+
Run --> Save["_save_state()"]
|
|
506
|
+
Save --> Check2{"成功?"}
|
|
507
|
+
Check2 -->|Yes| Reset["reset_penalty()"]
|
|
508
|
+
Check2 -->|No| Keep["惩罚不重置<br/>(重试保护)"]
|
|
509
|
+
Reset --> Idle
|
|
510
|
+
Keep --> Idle
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**跨重启连续性**:`last_abstracts` 通过 Jinja2 模板 `{{ last_abstracts }}` / `{{ last_run }}`
|
|
514
|
+
注入 prompt,让 Agent 知道"上一轮做了什么"。
|
|
515
|
+
|
|
516
|
+
**惩罚计数器不持久化**:重启本身即一次完整"冷启动",记忆整理结果已通过 ChromaDB 持久化,
|
|
517
|
+
无需保留旧退避状态。
|
|
518
|
+
|
|
519
|
+
---
|
|
520
|
+
|
|
521
|
+
## 技术栈
|
|
522
|
+
|
|
523
|
+
| 组件 | 技术 |
|
|
524
|
+
| ---------- | -------------------------------------------------- |
|
|
525
|
+
| 后端框架 | Python 3.10+ / NoneBot2 / AmritaCore / AmritaSense |
|
|
526
|
+
| 向量数据库 | ChromaDB(PersistentClient / HttpClient) |
|
|
527
|
+
| 嵌入模型 | OpenAI Embedding / Ollama Embedding |
|
|
528
|
+
| 调度引擎 | nonebot_plugin_apscheduler(date trigger) |
|
|
529
|
+
| 持久化 | nonebot_plugin_orm(插件自有模型 + 迁移链) |
|
|
530
|
+
| Token 统计 | InsightsModel(复用 Bot 全局 usage) |
|
|
531
|
+
| 缓存 | nonebot_plugin_amrita.cache.LRUCache |
|
|
532
|
+
| 配置管理 | Pydantic + TOML(插件配置)/ pydantic-settings(env) |
|
|
533
|
+
| 代码质量 | Ruff + Pyright |
|
|
534
|
+
|
|
535
|
+
---
|
|
536
|
+
|
|
537
|
+
## 配置详解
|
|
538
|
+
|
|
539
|
+
### `config/amrita_plugin_memory/config.toml`
|
|
540
|
+
|
|
541
|
+
```toml
|
|
542
|
+
# L2 记忆
|
|
543
|
+
short_term_expiry_days = 7
|
|
544
|
+
long_term_expiry_days = 90
|
|
545
|
+
per_session_memory_limit = 50
|
|
546
|
+
memo_max_chars = 1000
|
|
547
|
+
|
|
548
|
+
# L3 离线巩固循环
|
|
549
|
+
[subconscious]
|
|
550
|
+
enabled = false
|
|
551
|
+
target_user_id = ""
|
|
552
|
+
allowed_tools = []
|
|
553
|
+
max_iterations = 10
|
|
554
|
+
loop_detect_threshold = 3
|
|
555
|
+
rethink_base_delay_minutes = 30
|
|
556
|
+
rethink_penalty_multiplier = 1.5
|
|
557
|
+
rethink_max_delay_minutes = 1440
|
|
558
|
+
prompt_file = "prompt/subconscious_main.md.jinja2"
|
|
559
|
+
prompt_send_file = "prompt/subconscious_send.md.jinja2"
|
|
560
|
+
prompt_knowledge_file = "prompt/knowledge_guide.md.jinja2"
|
|
561
|
+
prompt_profile_file = "prompt/profile_guide.md.jinja2"
|
|
562
|
+
enable_memory_compress = true
|
|
563
|
+
allow_send_to_user = false
|
|
564
|
+
memory_warn_threshold = 100
|
|
565
|
+
max_abstracts = 5
|
|
566
|
+
knowledge_max_chars = 10000
|
|
567
|
+
knowledge_collection_name = "amrita_global_knowledge"
|
|
568
|
+
enable_knowledge = true
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
| 字段 | 默认值 | 说明 |
|
|
572
|
+
| ---------------------------- | ------- | ------------------------------------------- |
|
|
573
|
+
| `short_term_expiry_days` | `7` | ⚠️ **预留字段**,当前未实现自动过期清理 |
|
|
574
|
+
| `long_term_expiry_days` | `90` | ⚠️ **预留字段**,当前未实现自动过期清理 |
|
|
575
|
+
| `per_session_memory_limit` | `50` | 单个分区(uni_id)的记忆条数上限 |
|
|
576
|
+
| `memo_max_chars` | `1000` | L1 备忘录字数硬上限 |
|
|
577
|
+
| `enabled` | `false` | 是否启用离线巩固循环 |
|
|
578
|
+
| `target_user_id` | `""` | 目标用户 ID,为空则不启动 |
|
|
579
|
+
| `allowed_tools` | `[]` | 额外可用工具(从全局拉取,不存在仅告警) |
|
|
580
|
+
| `max_iterations` | `10` | 单轮 ReAct 最大步数(预留) |
|
|
581
|
+
| `loop_detect_threshold` | `3` | 传入 Core `loop_reasoning_trigger` |
|
|
582
|
+
| `rethink_base_delay_minutes` | `30` | 惩罚退避基数 |
|
|
583
|
+
| `rethink_penalty_multiplier` | `1.5` | 惩罚指数倍率 |
|
|
584
|
+
| `rethink_max_delay_minutes` | `1440` | 惩罚延迟上限(1 天) |
|
|
585
|
+
| `prompt_file` | 见上 | 主推理提示词模板 |
|
|
586
|
+
| `prompt_send_file` | 见上 | 主动消息生成模板 |
|
|
587
|
+
| `prompt_knowledge_file` | 见上 | 知识库使用指南 |
|
|
588
|
+
| `prompt_profile_file` | 见上 | 画像构建指南 |
|
|
589
|
+
| `enable_memory_compress` | `true` | 传入 Core `enable_memory_abstract` |
|
|
590
|
+
| `allow_send_to_user` | `false` | 允许 Agent 主动发私聊消息 |
|
|
591
|
+
| `memory_warn_threshold` | `100` | ChromaDB 超此数量注入压缩提示 |
|
|
592
|
+
| `max_abstracts` | `5` | 摘要滑动窗口大小 |
|
|
593
|
+
| `knowledge_max_chars` | `10000` | 知识条目单条正文上限 |
|
|
594
|
+
| `knowledge_collection_name` | `amrita_global_knowledge` | ChromaDB 知识库 collection |
|
|
595
|
+
| `enable_knowledge` | `true` | 是否启用全局知识库(需 `enabled=true`) |
|
|
596
|
+
|
|
597
|
+
### 提示词模板
|
|
598
|
+
|
|
599
|
+
4 个 Jinja2 模板位于 `config/amrita_plugin_memory/prompt/`,首次缺失时自动从默认值创建:
|
|
600
|
+
|
|
601
|
+
| 文件 | 用途 | 变量 |
|
|
602
|
+
| ----------------------------- | ------------ | ------------------------------------------------------------------------------------------------ |
|
|
603
|
+
| `subconscious_main.md.jinja2` | 主推理提示词 | `character_prompt`, `last_run`, `last_abstracts`, `current_time`, `target_user_id`, `total_runs` |
|
|
604
|
+
| `subconscious_send.md.jinja2` | 主动消息生成 | `character_prompt`, `intent`, `memory_context`, `current_time` |
|
|
605
|
+
| `knowledge_guide.md.jinja2` | 知识库指南 | `current_time`, `target_user_id` |
|
|
606
|
+
| `profile_guide.md.jinja2` | 画像构建指南 | `current_time`, `target_user_id` |
|
|
607
|
+
|
|
608
|
+
### 环境变量(`.env`)
|
|
609
|
+
|
|
610
|
+
| 变量 | 类型 | 默认值 | 说明 |
|
|
611
|
+
| -------------------------- | ------------------- | ---------------------- | --------------------------------- |
|
|
612
|
+
| `VECTOR_DB_TYPE` | local/remote | `local` | ChromaDB 类型 |
|
|
613
|
+
| `VECTOR_DB_SERVER` | string | `127.0.0.1` | 远程地址 |
|
|
614
|
+
| `VECTOR_DB_PORT` | int | `8000` | 远程端口 |
|
|
615
|
+
| `VECTOR_DB_SERVER_SSL` | bool | `false` | 远程 SSL |
|
|
616
|
+
| `VECTOR_DB_REMOTE_HEADERS` | dict | `{}` | 远程请求头 |
|
|
617
|
+
| `VECTOR_DB_TENANT` | string | `default` | 租户 |
|
|
618
|
+
| `VECTOR_DB_DATABASE` | string | `default` | 数据库 |
|
|
619
|
+
| `EMBEDDING_MODEL_URL` | string | `http://127.0.0.1:11434` | 嵌入模型地址 |
|
|
620
|
+
| `EMBEDDING_MODEL_NAME` | string | `auto` | 模型名 |
|
|
621
|
+
| `EMBEDDING_PROCTOL` | openai/ollama-embed | `ollama-embed` | 协议 |
|
|
622
|
+
| `EMBEDDING_MODEL_API_KEY` | string | (空) | API Key |
|
|
623
|
+
| `EMBED_CHECK_ON_STARTUP` | bool | `true` | 启动时是否检查嵌入指纹 |
|
|
624
|
+
| `EMBED_MISMATCH_POLICY` | ask/auto/never | `ask` | 嵌入模型变更时的处理策略 |
|
|
625
|
+
| `REEMBED_BATCH_SIZE` | int | `64` | 重映射每批条数 |
|
|
626
|
+
| `REEMBED_BACKUP_KEEP` | int | `3` | 保留备份份数 |
|
|
627
|
+
|
|
628
|
+
---
|
|
629
|
+
|
|
630
|
+
## 项目结构
|
|
631
|
+
|
|
632
|
+
```
|
|
633
|
+
amrita_plugin_memory/
|
|
634
|
+
├── config.py # 配置模型(SubconsciousConfig / ConfigFile / EnvConfig)
|
|
635
|
+
├── models.py # ORM 模型(UserMemo / SubconsciousState)
|
|
636
|
+
├── keys.py # L2 分区键(跟随框架 uni_id + 存量 Key 迁移)
|
|
637
|
+
├── embedding.py # 嵌入指纹 / 全量重映射 / 备份 / 启动检查
|
|
638
|
+
├── cli.py # `ambot memory` 命令实现
|
|
639
|
+
├── memo.py # L1 备忘录(读取 / 写入 / 注入 hook)
|
|
640
|
+
├── tools.py # 表层工具(write/read/update/delete/list/update_memo)
|
|
641
|
+
├── vector.py # ChromaDB 封装(AsyncUserMemory / 锁池)
|
|
642
|
+
├── embed.py # 嵌入适配器(Ollama)
|
|
643
|
+
├── matchers.py # /memory 命令
|
|
644
|
+
├── migrations/ # ORM 迁移(随包分发)
|
|
645
|
+
│ ├── 21f55abc2b90_init.py
|
|
646
|
+
│ └── 6004d221a7de_state.py
|
|
647
|
+
└── rethinking/ # L3 离线巩固循环
|
|
648
|
+
├── _state.py # 模块级状态(runner / penalty / pending / 隔离工具管理器)
|
|
649
|
+
├── types.py # TypedDict 定义
|
|
650
|
+
├── consts.py # 默认 Prompt 模板 / ensure_prompt_file
|
|
651
|
+
├── backend.py # SubconsciousBackend — 隔离的工具 + memory 后端
|
|
652
|
+
├── nodes.py # LIMITING_MEMORY 工作流节点
|
|
653
|
+
├── runner.py # SubconsciousRunner — 核心编排器
|
|
654
|
+
├── hooks.py # on_precompletion hook / 生命周期管理
|
|
655
|
+
├── tools.py # 20 个巩固循环工具 handler
|
|
656
|
+
├── schemas.py # FunctionDefinitionSchema 定义
|
|
657
|
+
└── knowledge.py # KnowledgeBaseManager — 三方同步知识库
|
|
658
|
+
|
|
659
|
+
config/amrita_plugin_memory/
|
|
660
|
+
├── config.toml # 插件配置
|
|
661
|
+
└── prompt/ # Jinja2 提示词模板
|
|
662
|
+
|
|
663
|
+
data/amrita_plugin_memory/
|
|
664
|
+
├── vector_db.chroma/ # ChromaDB 持久化
|
|
665
|
+
├── backups/ # 重映射前的 JSON 备份
|
|
666
|
+
├── knowledge/ # 知识文件(KNOWLEDGE_*.md)
|
|
667
|
+
├── knowledge_index.json # 知识索引
|
|
668
|
+
└── user_profile.md # 用户画像
|
|
669
|
+
```
|
|
670
|
+
|
|
671
|
+
## 开发
|
|
672
|
+
|
|
673
|
+
```bash
|
|
674
|
+
uv sync # 安装依赖
|
|
675
|
+
ruff check amrita_plugin_memory/ # 代码检查
|
|
676
|
+
pyright amrita_plugin_memory/ # 类型检查
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### ORM 迁移
|
|
680
|
+
|
|
681
|
+
迁移由 **`ambot orm`** 生成(不要用 `nb orm` —— 它不会加载 Amrita 插件,
|
|
682
|
+
我们的模型会被漏掉):
|
|
683
|
+
|
|
684
|
+
```bash
|
|
685
|
+
uv run ambot orm upgrade # 1. 数据库先到 heads
|
|
686
|
+
uv run ambot orm revision -m "描述" \
|
|
687
|
+
--branch-label amrita_plugin_memory # 2. 生成迁移
|
|
688
|
+
uv run ambot orm upgrade # 3. 应用
|
|
689
|
+
uv run ambot orm check # 4. 校验模型与库一致
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
生成的脚本会落到工作区 `migrations/versions/amrita_plugin_memory/`,
|
|
693
|
+
需手动移入包内 `amrita_plugin_memory/migrations/` 才能随包分发。
|
|
694
|
+
|
|
695
|
+
### `ambot memory` 命令
|
|
696
|
+
|
|
697
|
+
命令组定义在本插件的 `amrita_plugin_memory/cli.py`,通过 entry point 暴露给
|
|
698
|
+
`ambot`:
|
|
699
|
+
|
|
700
|
+
```toml
|
|
701
|
+
[project.entry-points."ambot.commands"]
|
|
702
|
+
memory = "amrita_plugin_memory.cli:memory [full_load]"
|
|
703
|
+
```
|
|
704
|
+
|
|
705
|
+
`[full_load]` 是 `ambot-inlinectl` 提供的 entry point 元信息(需带该支持的
|
|
706
|
+
版本)。本插件包在插件加载器之外无法 import(`__init__.py` 里的
|
|
707
|
+
`require(...)` 需要加载器上下文),因此
|
|
708
|
+
`ambot` 会在**命令真正被调用时**先 `amrita.init()` + `amrita.load_plugins()`,
|
|
709
|
+
再加载该命令;列表/帮助阶段只读 entry point 元信息,不触发加载 ——
|
|
710
|
+
`ambot --help` 不会因此变慢,插件坏了也不影响其它命令。
|
|
711
|
+
|
|
712
|
+
自举时 `ambot` 会写入 `AMBOT_COMMAND_CONTEXT=1`,插件据此跳过启动检查,
|
|
713
|
+
否则嵌入指纹确认会反过来挡住维护命令本身。
|
|
714
|
+
|
|
715
|
+
调试时把 ambot-inlinectl 以可编辑方式装入当前环境即可:
|
|
716
|
+
|
|
717
|
+
```bash
|
|
718
|
+
uv pip install -e <ambot-inlinectl 仓库路径>
|
|
719
|
+
uv run --no-sync ambot memory status
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
> `uv run` 会按 `uv.lock` 重新同步环境并冲掉可编辑安装,因此需加 `--no-sync`。
|