amrita_plugin_memory 0.4.0__tar.gz → 0.5.0__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.0 → amrita_plugin_memory-0.5.0}/PKG-INFO +10 -10
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/README.md +6 -6
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/__init__.py +9 -2
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/embedding.py +125 -88
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/keys.py +21 -3
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/__init__.py +1 -2
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/runner.py +19 -37
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/PKG-INFO +10 -10
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/SOURCES.txt +0 -1
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/requires.txt +3 -3
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/pyproject.toml +4 -7
- amrita_plugin_memory-0.4.0/amrita_plugin_memory/rethinking/nodes.py +0 -27
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/LICENSE +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/cli.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/config.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/embed.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/matchers.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/memo.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/migrations/21f55abc2b90_init.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/migrations/6004d221a7de_state.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/models.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/_state.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/backend.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/consts.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/hooks.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/knowledge.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/schemas.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/tools.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/types.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/tools.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/vector.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/entry_points.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/setup.cfg +0 -0
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amrita_plugin_memory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Amrita长期记忆插件
|
|
5
5
|
Requires-Python: <3.14,>=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: aiohttp>=3.13.3
|
|
9
9
|
Requires-Dist: aiologic>=0.16.0
|
|
10
|
-
Requires-Dist: amrita-core
|
|
11
|
-
Requires-Dist: amrita>=1.
|
|
10
|
+
Requires-Dist: amrita-core<2.0.0,>=1.0.0
|
|
11
|
+
Requires-Dist: amrita>=1.10
|
|
12
12
|
Requires-Dist: chromadb>=1.5.8
|
|
13
13
|
Requires-Dist: click>=8.1
|
|
14
|
-
Requires-Dist: nonebot-plugin-amrita>=0.
|
|
14
|
+
Requires-Dist: nonebot-plugin-amrita>=0.4.0
|
|
15
15
|
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0
|
|
16
16
|
Requires-Dist: nonebot-plugin-localstore>=0.7.4
|
|
17
17
|
Requires-Dist: nonebot-plugin-orm[default]>=0.8.2
|
|
@@ -165,7 +165,7 @@ L3 在代码中沿用历史标识符 `subconscious` / `rethinking`(避免破
|
|
|
165
165
|
| `subconscious_*` 工具 | 巩固循环工具 | — |
|
|
166
166
|
| 用户聊天 → `cancel_and_reschedule` | 任务负激活 | 专注任务时 DMN 被抑制 |
|
|
167
167
|
| 静默期触发 `_run` | 空闲时活跃 | DMN 在无任务时活跃 |
|
|
168
|
-
| 去重 / 压缩 / `
|
|
168
|
+
| 去重 / 压缩 / `ContextCompactor` | 记忆巩固 | 海马 → 皮层的系统巩固 |
|
|
169
169
|
| 画像构建(`*_profile`) | 自传体记忆 | DMN 负责自我参照加工 |
|
|
170
170
|
| 主动消息(`send_to_user`) | 心智游移 | 走神产生与自我相关的念头 |
|
|
171
171
|
| 每轮数万 tokens | 静息高能耗 | DMN 占脑能耗约 20% |
|
|
@@ -287,7 +287,7 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
287
287
|
| 阶段 | 内容 |
|
|
288
288
|
| -------------- | ---------------------------------------------------------- |
|
|
289
289
|
| 状态加载 | 从 `SubconsciousState` 表恢复 `total_runs` / 摘要窗口 |
|
|
290
|
-
| 记忆限幅 | Core `
|
|
290
|
+
| 记忆限幅 | Core `ContextCompactor` 截断超限消息并生成摘要 |
|
|
291
291
|
| ReAct 循环 | Agent 调用 `subconscious_*` 工具执行整理 |
|
|
292
292
|
| 后处理 | 更新全局 usage、持久化元状态、重置惩罚、调度下次运行 |
|
|
293
293
|
|
|
@@ -304,12 +304,13 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
304
304
|
|
|
305
305
|
```mermaid
|
|
306
306
|
flowchart TD
|
|
307
|
-
LOAD_STATE -->
|
|
307
|
+
LOAD_STATE --> NORMALIZE_MESSAGES --> COMPACT_HISTORY --> JINJA2_RENDER --> BUILD_MESSAGE --> REACT_BLOCK
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
`SubconsciousRunner` 把 ChatObject 当数据容器,注入自定义 `SubconsciousBackend`
|
|
311
311
|
(隔离的工具 + memory 后端)与 Core `ReActAgentStrategy`。
|
|
312
|
-
|
|
312
|
+
工作流直接复用 Core 内置的 `REACT_ONLY`,其中的 `COMPACT_HISTORY` 节点在 Agent
|
|
313
|
+
Loop 前运行 Core `ContextCompactor`。压缩排在渲染之前,摘要才能进入本次请求的系统提示词。
|
|
313
314
|
|
|
314
315
|
---
|
|
315
316
|
|
|
@@ -473,7 +474,7 @@ flowchart TD
|
|
|
473
474
|
| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
474
475
|
| L1 备忘录 | `UserMemo` 表(插件自有 ORM,主键 = uni_id) | LLM 维护的常驻用户元信息(≤ `memo_max_chars`) |
|
|
475
476
|
| 循环元状态 | `SubconsciousState` 表(插件自有 ORM,uid=`amrita_memory`) | `total_runs`、`last_abstracts`、`pending_messages`、`knowledge_suggestions` |
|
|
476
|
-
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用
|
|
477
|
+
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用 `ContextCompactor` |
|
|
477
478
|
| 惩罚计数器 | 内存(不持久化) | `_penalty_count`:重启后从 0 开始 |
|
|
478
479
|
| L2 记忆 | ChromaDB `amrita_user_memory` | 向量 + documents + metadatas(含嵌入指纹与分区键版本) |
|
|
479
480
|
| 用户画像 | `data/amrita_plugin_memory/user_profile.md` | Markdown 文件,`summary---body` 格式,行级增量更新 |
|
|
@@ -649,7 +650,6 @@ amrita_plugin_memory/
|
|
|
649
650
|
├── types.py # TypedDict 定义
|
|
650
651
|
├── consts.py # 默认 Prompt 模板 / ensure_prompt_file
|
|
651
652
|
├── backend.py # SubconsciousBackend — 隔离的工具 + memory 后端
|
|
652
|
-
├── nodes.py # LIMITING_MEMORY 工作流节点
|
|
653
653
|
├── runner.py # SubconsciousRunner — 核心编排器
|
|
654
654
|
├── hooks.py # on_precompletion hook / 生命周期管理
|
|
655
655
|
├── tools.py # 20 个巩固循环工具 handler
|
|
@@ -146,7 +146,7 @@ L3 在代码中沿用历史标识符 `subconscious` / `rethinking`(避免破
|
|
|
146
146
|
| `subconscious_*` 工具 | 巩固循环工具 | — |
|
|
147
147
|
| 用户聊天 → `cancel_and_reschedule` | 任务负激活 | 专注任务时 DMN 被抑制 |
|
|
148
148
|
| 静默期触发 `_run` | 空闲时活跃 | DMN 在无任务时活跃 |
|
|
149
|
-
| 去重 / 压缩 / `
|
|
149
|
+
| 去重 / 压缩 / `ContextCompactor` | 记忆巩固 | 海马 → 皮层的系统巩固 |
|
|
150
150
|
| 画像构建(`*_profile`) | 自传体记忆 | DMN 负责自我参照加工 |
|
|
151
151
|
| 主动消息(`send_to_user`) | 心智游移 | 走神产生与自我相关的念头 |
|
|
152
152
|
| 每轮数万 tokens | 静息高能耗 | DMN 占脑能耗约 20% |
|
|
@@ -268,7 +268,7 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
268
268
|
| 阶段 | 内容 |
|
|
269
269
|
| -------------- | ---------------------------------------------------------- |
|
|
270
270
|
| 状态加载 | 从 `SubconsciousState` 表恢复 `total_runs` / 摘要窗口 |
|
|
271
|
-
| 记忆限幅 | Core `
|
|
271
|
+
| 记忆限幅 | Core `ContextCompactor` 截断超限消息并生成摘要 |
|
|
272
272
|
| ReAct 循环 | Agent 调用 `subconscious_*` 工具执行整理 |
|
|
273
273
|
| 后处理 | 更新全局 usage、持久化元状态、重置惩罚、调度下次运行 |
|
|
274
274
|
|
|
@@ -285,12 +285,13 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
285
285
|
|
|
286
286
|
```mermaid
|
|
287
287
|
flowchart TD
|
|
288
|
-
LOAD_STATE -->
|
|
288
|
+
LOAD_STATE --> NORMALIZE_MESSAGES --> COMPACT_HISTORY --> JINJA2_RENDER --> BUILD_MESSAGE --> REACT_BLOCK
|
|
289
289
|
```
|
|
290
290
|
|
|
291
291
|
`SubconsciousRunner` 把 ChatObject 当数据容器,注入自定义 `SubconsciousBackend`
|
|
292
292
|
(隔离的工具 + memory 后端)与 Core `ReActAgentStrategy`。
|
|
293
|
-
|
|
293
|
+
工作流直接复用 Core 内置的 `REACT_ONLY`,其中的 `COMPACT_HISTORY` 节点在 Agent
|
|
294
|
+
Loop 前运行 Core `ContextCompactor`。压缩排在渲染之前,摘要才能进入本次请求的系统提示词。
|
|
294
295
|
|
|
295
296
|
---
|
|
296
297
|
|
|
@@ -454,7 +455,7 @@ flowchart TD
|
|
|
454
455
|
| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
455
456
|
| L1 备忘录 | `UserMemo` 表(插件自有 ORM,主键 = uni_id) | LLM 维护的常驻用户元信息(≤ `memo_max_chars`) |
|
|
456
457
|
| 循环元状态 | `SubconsciousState` 表(插件自有 ORM,uid=`amrita_memory`) | `total_runs`、`last_abstracts`、`pending_messages`、`knowledge_suggestions` |
|
|
457
|
-
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用
|
|
458
|
+
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用 `ContextCompactor` |
|
|
458
459
|
| 惩罚计数器 | 内存(不持久化) | `_penalty_count`:重启后从 0 开始 |
|
|
459
460
|
| L2 记忆 | ChromaDB `amrita_user_memory` | 向量 + documents + metadatas(含嵌入指纹与分区键版本) |
|
|
460
461
|
| 用户画像 | `data/amrita_plugin_memory/user_profile.md` | Markdown 文件,`summary---body` 格式,行级增量更新 |
|
|
@@ -630,7 +631,6 @@ amrita_plugin_memory/
|
|
|
630
631
|
├── types.py # TypedDict 定义
|
|
631
632
|
├── consts.py # 默认 Prompt 模板 / ensure_prompt_file
|
|
632
633
|
├── backend.py # SubconsciousBackend — 隔离的工具 + memory 后端
|
|
633
|
-
├── nodes.py # LIMITING_MEMORY 工作流节点
|
|
634
634
|
├── runner.py # SubconsciousRunner — 核心编排器
|
|
635
635
|
├── hooks.py # on_precompletion hook / 生命周期管理
|
|
636
636
|
├── tools.py # 20 个巩固循环工具 handler
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from nonebot import require
|
|
1
|
+
from nonebot import get_driver, require
|
|
2
2
|
|
|
3
3
|
require("nonebot_plugin_localstore")
|
|
4
4
|
require("nonebot_plugin_apscheduler")
|
|
@@ -19,9 +19,16 @@ from . import (
|
|
|
19
19
|
vector,
|
|
20
20
|
)
|
|
21
21
|
|
|
22
|
-
#
|
|
22
|
+
# import 期只做 Key 迁移与指纹比对(不重嵌入):call_embedding 依赖的全局 AmritaConfig 此时尚未设置。
|
|
23
23
|
embedding.run_startup_check()
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
@get_driver().on_startup
|
|
27
|
+
async def _memory_deferred_fingerprint_check() -> None:
|
|
28
|
+
"""startup 期处理 import 期发现的指纹失配(此时 AmritaConfig 已就绪)。"""
|
|
29
|
+
await embedding.run_deferred_check()
|
|
30
|
+
|
|
31
|
+
|
|
25
32
|
__all__ = [
|
|
26
33
|
"config",
|
|
27
34
|
"embed",
|
|
@@ -38,13 +38,8 @@ EMBED_BASE_URL_META = "embed_base_url"
|
|
|
38
38
|
|
|
39
39
|
BACKUP_DIR = DATA_PATH / "backups"
|
|
40
40
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
"""内部信号 — 需要拒绝加载插件。不对外抛出。
|
|
44
|
-
|
|
45
|
-
对外统一转成内建 ``RuntimeError``:插件加载失败时父包处于半导入状态,
|
|
46
|
-
loguru 无法 pickle 本模块定义的异常类,会产生噪声堆栈。
|
|
47
|
-
"""
|
|
41
|
+
REBUILD_SUFFIX = "__rebuild"
|
|
42
|
+
"""重映射期间使用的临时集合名后缀。"""
|
|
48
43
|
|
|
49
44
|
|
|
50
45
|
def compute_fingerprint() -> str:
|
|
@@ -162,13 +157,61 @@ async def _write_in_batches(
|
|
|
162
157
|
return done
|
|
163
158
|
|
|
164
159
|
|
|
160
|
+
def _collection_names(client: ClientAPI) -> set[str]:
|
|
161
|
+
return {c.name for c in client.list_collections()}
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _drop_if_exists(client: ClientAPI, name: str) -> None:
|
|
165
|
+
if name in _collection_names(client):
|
|
166
|
+
client.delete_collection(name)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
async def _replace_collection(
|
|
170
|
+
client: ClientAPI,
|
|
171
|
+
ids: list[str],
|
|
172
|
+
documents: list[str],
|
|
173
|
+
metadatas: list[Metadata],
|
|
174
|
+
*,
|
|
175
|
+
batch_size: int,
|
|
176
|
+
progress: Callable[[int, int], None] | None = None,
|
|
177
|
+
) -> int:
|
|
178
|
+
"""把内容重新嵌入到临时集合,全部成功后才替换正式集合。
|
|
179
|
+
|
|
180
|
+
失败时正式集合原封不动 —— “先删后嵌”曾导致嵌入失败即丢数据。
|
|
181
|
+
"""
|
|
182
|
+
tmp_name = f"{MEMORY_COLLECTION_NAME}{REBUILD_SUFFIX}"
|
|
183
|
+
_drop_if_exists(client, tmp_name)
|
|
184
|
+
tmp = client.create_collection(tmp_name)
|
|
185
|
+
try:
|
|
186
|
+
done = await _write_in_batches(
|
|
187
|
+
tmp,
|
|
188
|
+
ids,
|
|
189
|
+
documents,
|
|
190
|
+
metadatas,
|
|
191
|
+
batch_size=batch_size,
|
|
192
|
+
progress=progress,
|
|
193
|
+
)
|
|
194
|
+
write_fingerprint(tmp)
|
|
195
|
+
except BaseException:
|
|
196
|
+
# 保留正式集合;清掉半成品,下次可重试
|
|
197
|
+
_drop_if_exists(client, tmp_name)
|
|
198
|
+
raise
|
|
199
|
+
# 数据已完整落在 tmp 中,此刻替换才是安全的
|
|
200
|
+
_drop_if_exists(client, MEMORY_COLLECTION_NAME)
|
|
201
|
+
tmp.modify(name=MEMORY_COLLECTION_NAME)
|
|
202
|
+
return done
|
|
203
|
+
|
|
204
|
+
|
|
165
205
|
async def reindex_all(
|
|
166
206
|
*,
|
|
167
207
|
batch_size: int | None = None,
|
|
168
208
|
progress: Callable[[int, int], None] | None = None,
|
|
169
209
|
client: ClientAPI | None = None,
|
|
170
210
|
) -> int:
|
|
171
|
-
"""
|
|
211
|
+
"""用当前嵌入模型重新嵌入全部记忆。
|
|
212
|
+
|
|
213
|
+
采用「先重建、后替换」:新向量全部写入临时集合后才替换正式集合,
|
|
214
|
+
因此嵌入服务不可用等失败不会丢数据(“先删后嵌”曾导致这一点)。
|
|
172
215
|
|
|
173
216
|
Returns:
|
|
174
217
|
重新嵌入的记忆条数。
|
|
@@ -179,31 +222,22 @@ async def reindex_all(
|
|
|
179
222
|
ids: list[str] = list(result.get("ids") or [])
|
|
180
223
|
documents: list[str] = list(result.get("documents") or [])
|
|
181
224
|
metadatas: list[Metadata] = list(result.get("metadatas") or [])
|
|
182
|
-
total = len(ids)
|
|
183
|
-
|
|
184
|
-
# 先备份:这是删除集合后唯一的回滚依据
|
|
185
|
-
if total:
|
|
186
|
-
backup_collection(collection)
|
|
187
225
|
|
|
188
|
-
#
|
|
189
|
-
if
|
|
190
|
-
|
|
191
|
-
fresh = client.create_collection(MEMORY_COLLECTION_NAME)
|
|
192
|
-
write_fingerprint(fresh)
|
|
226
|
+
# 空集合:无需调用嵌入模型,写个指纹即可
|
|
227
|
+
if not ids:
|
|
228
|
+
write_fingerprint(collection)
|
|
193
229
|
logger.info("[Memory] Collection was empty, fingerprint written")
|
|
194
230
|
return 0
|
|
195
231
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
fresh,
|
|
232
|
+
backup_collection(collection)
|
|
233
|
+
done = await _replace_collection(
|
|
234
|
+
client,
|
|
200
235
|
ids,
|
|
201
236
|
documents,
|
|
202
237
|
metadatas,
|
|
203
238
|
batch_size=batch_size or env_config.reembed_batch_size,
|
|
204
239
|
progress=progress,
|
|
205
240
|
)
|
|
206
|
-
write_fingerprint(fresh)
|
|
207
241
|
logger.info(f"[Memory] Reindexed {done} memories with {describe_current()}")
|
|
208
242
|
return done
|
|
209
243
|
|
|
@@ -217,6 +251,8 @@ async def restore_from_backup(
|
|
|
217
251
|
) -> int:
|
|
218
252
|
"""从备份文件恢复集合(用当前嵌入模型重新嵌入)。
|
|
219
253
|
|
|
254
|
+
同样先重建后替换,失败不会破坏当前数据。
|
|
255
|
+
|
|
220
256
|
Returns:
|
|
221
257
|
恢复的记忆条数。
|
|
222
258
|
"""
|
|
@@ -227,65 +263,42 @@ async def restore_from_backup(
|
|
|
227
263
|
metadatas: list[Metadata] = list(payload.get("metadatas") or [])
|
|
228
264
|
|
|
229
265
|
client = client or get_db_conn()
|
|
230
|
-
|
|
231
|
-
if MEMORY_COLLECTION_NAME in existing:
|
|
266
|
+
if MEMORY_COLLECTION_NAME in _collection_names(client):
|
|
232
267
|
# 恢复前先备份当前状态,避免误操作不可逆
|
|
233
268
|
backup_collection(client.get_collection(MEMORY_COLLECTION_NAME))
|
|
234
|
-
client.delete_collection(MEMORY_COLLECTION_NAME)
|
|
235
|
-
fresh = client.create_collection(MEMORY_COLLECTION_NAME)
|
|
236
269
|
|
|
237
|
-
done = await
|
|
238
|
-
|
|
270
|
+
done = await _replace_collection(
|
|
271
|
+
client,
|
|
239
272
|
ids,
|
|
240
273
|
documents,
|
|
241
274
|
metadatas,
|
|
242
275
|
batch_size=batch_size or env_config.reembed_batch_size,
|
|
243
276
|
progress=progress,
|
|
244
277
|
)
|
|
245
|
-
write_fingerprint(fresh)
|
|
246
278
|
logger.info(f"[Memory] Restored {done} memories from {path.name}")
|
|
247
279
|
return done
|
|
248
280
|
|
|
249
281
|
|
|
250
|
-
def
|
|
251
|
-
"""
|
|
252
|
-
try:
|
|
253
|
-
asyncio.get_running_loop()
|
|
254
|
-
except RuntimeError:
|
|
255
|
-
return asyncio.run(coro)
|
|
256
|
-
raise _RefuseToLoad(
|
|
257
|
-
"检测到嵌入模型变更,但当前处于运行中的事件循环内,无法执行重映射。"
|
|
258
|
-
"请在终端运行 `ambot memory reindex`。"
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
def _confirm_reindex(reason: str, stored_desc: str) -> bool:
|
|
263
|
-
"""交互确认;非 TTY 时抛出 _RefuseToLoad。"""
|
|
282
|
+
def _confirm_reindex(reason: str) -> bool:
|
|
283
|
+
"""交互确认;非 TTY 时返回 False(数据保持原样,提示手动执行)。"""
|
|
264
284
|
if not sys.stdin.isatty():
|
|
265
|
-
|
|
266
|
-
f"{reason}\n"
|
|
267
|
-
"当前为非交互环境,无法确认。请在终端运行 `ambot memory reindex
|
|
285
|
+
logger.warning(
|
|
286
|
+
f"[Memory] {reason}\n"
|
|
287
|
+
" 当前为非交互环境,无法确认。请在终端运行 `ambot memory reindex`,"
|
|
268
288
|
"或设置 EMBED_MISMATCH_POLICY=auto / never,"
|
|
269
289
|
"或设置 EMBED_CHECK_ON_STARTUP=false 跳过检查。"
|
|
270
290
|
)
|
|
291
|
+
return False
|
|
271
292
|
return click.confirm(
|
|
272
293
|
f"{reason}\n"
|
|
273
|
-
f" 库中记录: {stored_desc}\n"
|
|
274
294
|
f" 当前配置: {describe_current()}\n"
|
|
275
295
|
"是否用当前模型重新嵌入全部记忆?(旧向量将与新模型不兼容)",
|
|
276
296
|
default=True,
|
|
277
297
|
)
|
|
278
298
|
|
|
279
299
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
click.echo(f" 重映射进度: {done}/{total}", err=True)
|
|
283
|
-
|
|
284
|
-
return _run_async(reindex_all(progress=_progress))
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
def _check_fingerprint(collection: Collection) -> None:
|
|
288
|
-
"""指纹校验主逻辑(可能抛出 _RefuseToLoad)。"""
|
|
300
|
+
def _inspect_fingerprint(collection: Collection) -> str | None:
|
|
301
|
+
"""比对指纹,返回失配原因;``None`` 表示无需处理。"""
|
|
289
302
|
current = compute_fingerprint()
|
|
290
303
|
stored = stored_fingerprint(collection)
|
|
291
304
|
|
|
@@ -293,49 +306,36 @@ def _check_fingerprint(collection: Collection) -> None:
|
|
|
293
306
|
if collection.count() == 0:
|
|
294
307
|
write_fingerprint(collection)
|
|
295
308
|
logger.debug("[Memory] Empty collection, fingerprint written")
|
|
296
|
-
return
|
|
309
|
+
return None
|
|
297
310
|
|
|
298
311
|
if stored == current:
|
|
299
312
|
logger.debug("[Memory] Embedding fingerprint matches")
|
|
300
|
-
return
|
|
313
|
+
return None
|
|
301
314
|
|
|
302
315
|
if stored is None:
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
reason = "嵌入模型已变更,现有向量与当前模型不兼容。"
|
|
316
|
+
return "库中未记录嵌入模型信息,无法确认现有向量与当前配置是否一致。"
|
|
317
|
+
return "嵌入模型已变更,现有向量与当前模型不兼容。"
|
|
306
318
|
|
|
307
|
-
policy = env_config.embed_mismatch_policy
|
|
308
|
-
if policy == "never":
|
|
309
|
-
logger.warning(f"[Memory] {reason} 策略为 never,保持现有数据不变。")
|
|
310
|
-
return
|
|
311
|
-
if policy == "auto":
|
|
312
|
-
logger.warning(f"[Memory] {reason} 策略为 auto,开始全量重映射。")
|
|
313
|
-
_do_reindex_with_progress()
|
|
314
|
-
return
|
|
315
319
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
logger.warning("[Memory] 用户拒绝重映射,保持现有数据不变。")
|
|
319
|
-
return
|
|
320
|
-
_do_reindex_with_progress()
|
|
320
|
+
_pending_mismatch: str | None = None
|
|
321
|
+
"""import 期检测到、待 startup 期处理的指纹失配原因。"""
|
|
321
322
|
|
|
322
323
|
|
|
323
324
|
def run_startup_check() -> None:
|
|
324
|
-
"""
|
|
325
|
+
"""import 期检查:Key 迁移 + 指纹比对(**不重嵌入**)。
|
|
325
326
|
|
|
326
|
-
Key
|
|
327
|
+
Key 迁移与嵌入无关,始终执行(幂等且廉价)。指纹比对受
|
|
327
328
|
``EMBED_CHECK_ON_STARTUP`` 控制。
|
|
328
329
|
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
这里刻意不做重映射:``call_embedding`` 依赖全局 ``AmritaConfig``,而它要到
|
|
331
|
+
bot 启动流程才被设置 —— import 期调用必然抛
|
|
332
|
+
``Global AmritaConfig is not initialized``。重映射因此推迟到
|
|
333
|
+
:func:`run_deferred_check`(startup 期)。
|
|
331
334
|
|
|
332
335
|
处于 ``ambot <cmd>`` 命令上下文(``AMBOT_COMMAND_CONTEXT``)时整体跳过:
|
|
333
|
-
|
|
334
|
-
甚至直接拒绝加载)。
|
|
335
|
-
|
|
336
|
-
Raises:
|
|
337
|
-
RuntimeError: 模型变更且无法确认时(插件将不被加载)。
|
|
336
|
+
维护命令本就是为了修复状态,启动检查反过来会挡住它们。
|
|
338
337
|
"""
|
|
338
|
+
global _pending_mismatch
|
|
339
339
|
if os.environ.get("AMBOT_COMMAND_CONTEXT"):
|
|
340
340
|
logger.debug("[Memory] 命令上下文,跳过启动检查")
|
|
341
341
|
return
|
|
@@ -357,9 +357,46 @@ def run_startup_check() -> None:
|
|
|
357
357
|
return
|
|
358
358
|
|
|
359
359
|
try:
|
|
360
|
-
|
|
361
|
-
except _RefuseToLoad as e:
|
|
362
|
-
logger.error(f"[Memory] 拒绝加载插件:{e}")
|
|
363
|
-
raise RuntimeError(str(e)) from None
|
|
360
|
+
_pending_mismatch = _inspect_fingerprint(collection)
|
|
364
361
|
except Exception as e:
|
|
365
362
|
logger.warning(f"[Memory] 嵌入指纹检查跳过:{e}")
|
|
363
|
+
return
|
|
364
|
+
|
|
365
|
+
if _pending_mismatch:
|
|
366
|
+
logger.warning(
|
|
367
|
+
f"[Memory] {_pending_mismatch} 将在启动完成后处理"
|
|
368
|
+
f"(策略:{env_config.embed_mismatch_policy})。"
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
async def run_deferred_check() -> None:
|
|
373
|
+
"""startup 期执行重映射 —— 此时 ``AmritaConfig`` 已就绪。
|
|
374
|
+
|
|
375
|
+
策略语义(``EMBED_MISMATCH_POLICY``):
|
|
376
|
+
|
|
377
|
+
- ``never``:只告警,保持现有数据
|
|
378
|
+
- ``ask``:TTY 下交互确认;非 TTY 则告警并保持数据(不再拒绝加载)
|
|
379
|
+
- ``auto``:自动全量重映射
|
|
380
|
+
|
|
381
|
+
重映射失败只会记录错误,数据保持原样(``reindex_all`` 先重建后替换)。
|
|
382
|
+
"""
|
|
383
|
+
global _pending_mismatch
|
|
384
|
+
if not _pending_mismatch:
|
|
385
|
+
return
|
|
386
|
+
reason, _pending_mismatch = _pending_mismatch, None
|
|
387
|
+
|
|
388
|
+
policy = env_config.embed_mismatch_policy
|
|
389
|
+
if policy == "never":
|
|
390
|
+
logger.warning(f"[Memory] {reason} 策略为 never,保持现有数据不变。")
|
|
391
|
+
return
|
|
392
|
+
if policy == "ask" and not await asyncio.to_thread(_confirm_reindex, reason):
|
|
393
|
+
logger.warning("[Memory] 未执行重映射,保持现有数据不变。")
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
logger.warning(f"[Memory] {reason} 开始全量重映射……")
|
|
397
|
+
try:
|
|
398
|
+
done = await reindex_all()
|
|
399
|
+
except Exception as e:
|
|
400
|
+
logger.error(f"[Memory] 重映射失败,现有数据未被修改:{e}")
|
|
401
|
+
return
|
|
402
|
+
logger.info(f"[Memory] 重映射完成,共 {done} 条。")
|
|
@@ -34,6 +34,9 @@ _ANY_ID_RE = re.compile(
|
|
|
34
34
|
r"^(?:[A-Za-z0-9]+_)?(?P<kind>Private|Group|Channel|user|group)_(?P<payload>[0-9]+)$"
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
+
# 更早期的数据只有纯数字 ID,既无平台前缀也无类型前缀,单看键本身无法判断是用户还是群 —— 需借助 metadata 里的 scope。
|
|
38
|
+
_BARE_ID_RE = re.compile(r"^(?P<payload>[0-9]+)$")
|
|
39
|
+
|
|
37
40
|
|
|
38
41
|
def make_scope_id(payload: int | str, *, is_group: bool) -> str:
|
|
39
42
|
"""生成分区键 — 委托框架,自动跟随已安装 Amrita 的格式。"""
|
|
@@ -59,13 +62,18 @@ def resolve_scope_uni_id(event: OB11Event, scope: str) -> str:
|
|
|
59
62
|
raise ValueError(f"无效的 scope: {scope}")
|
|
60
63
|
|
|
61
64
|
|
|
62
|
-
def to_current_format(raw: str) -> str | None:
|
|
65
|
+
def to_current_format(raw: str, *, scope: str | None = None) -> str | None:
|
|
63
66
|
"""把任意历史格式的分区键规范化到当前框架格式。
|
|
64
67
|
|
|
65
68
|
已是当前格式时返回原值(幂等);无法识别或本插件未使用的类型返回 None。
|
|
69
|
+
``scope``(``"user"`` / ``"group"``)用于补全只有纯数字的历史键 ——
|
|
70
|
+
这类键本身不含类型信息,缺了 scope 就只能放弃迁移。
|
|
66
71
|
"""
|
|
67
72
|
match = _ANY_ID_RE.match(raw)
|
|
68
73
|
if match is None:
|
|
74
|
+
bare = _BARE_ID_RE.match(raw)
|
|
75
|
+
if bare is not None and scope in ("user", "group"):
|
|
76
|
+
return make_scope_id(bare.group("payload"), is_group=scope == "group")
|
|
69
77
|
return None
|
|
70
78
|
kind = match.group("kind")
|
|
71
79
|
payload = match.group("payload")
|
|
@@ -101,14 +109,22 @@ def migrate_collection_keys(collection: Collection, *, dry_run: bool = False) ->
|
|
|
101
109
|
|
|
102
110
|
target_ids: list[str] = []
|
|
103
111
|
target_metas: list[Metadata] = []
|
|
112
|
+
skipped = 0
|
|
104
113
|
for doc_id, raw_meta in zip(ids, metadatas):
|
|
105
114
|
if not raw_meta:
|
|
106
115
|
continue
|
|
107
116
|
current = raw_meta.get("user_id")
|
|
108
117
|
if not isinstance(current, str):
|
|
109
118
|
continue
|
|
110
|
-
|
|
111
|
-
|
|
119
|
+
scope = raw_meta.get("scope")
|
|
120
|
+
converted = to_current_format(
|
|
121
|
+
current, scope=scope if isinstance(scope, str) else None
|
|
122
|
+
)
|
|
123
|
+
if converted is None:
|
|
124
|
+
skipped += 1
|
|
125
|
+
logger.warning(f"[Memory] 无法识别的分区键,已跳过:{current!r}")
|
|
126
|
+
continue
|
|
127
|
+
if converted == current:
|
|
112
128
|
continue
|
|
113
129
|
target_ids.append(doc_id)
|
|
114
130
|
target_metas.append({**raw_meta, "user_id": converted})
|
|
@@ -119,5 +135,7 @@ def migrate_collection_keys(collection: Collection, *, dry_run: bool = False) ->
|
|
|
119
135
|
if target_ids:
|
|
120
136
|
collection.update(ids=target_ids, metadatas=target_metas)
|
|
121
137
|
logger.info(f"[Memory] Key migrated for {len(target_ids)} memories")
|
|
138
|
+
if skipped:
|
|
139
|
+
logger.warning(f"[Memory] {skipped} 条记忆的分区键无法识别,未迁移")
|
|
122
140
|
collection.modify(metadata={**meta, KEY_SCHEMA_VERSION_META: KEY_SCHEMA_VERSION})
|
|
123
141
|
return len(target_ids)
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/runner.py
RENAMED
|
@@ -12,13 +12,12 @@ from amrita.plugins.chat.utils.libchat import add_usage
|
|
|
12
12
|
from amrita_core import SuspendObjectStream
|
|
13
13
|
from amrita_core.base.backend import BackendSlots
|
|
14
14
|
from amrita_core.builtins.agent import ReActAgentStrategy
|
|
15
|
-
from amrita_core.builtins.workflows import
|
|
15
|
+
from amrita_core.builtins.workflows import REACT_ONLY
|
|
16
16
|
from amrita_core.chatmanager import ChatObject
|
|
17
|
-
from amrita_core.components.
|
|
18
|
-
from amrita_core.components.process import BUILD_MESSAGE, LOAD_STATE
|
|
17
|
+
from amrita_core.components.compaction import ContextCompactor
|
|
19
18
|
from amrita_core.config import AmritaConfig, get_config
|
|
20
19
|
from amrita_core.preset import ModelPreset
|
|
21
|
-
from amrita_core.types import Message, UniResponseUsage
|
|
20
|
+
from amrita_core.types import CONTENT_LIST_TYPE, Message, UniResponseUsage
|
|
22
21
|
from amrita_core.utils import gather_usage
|
|
23
22
|
from jinja2 import Template
|
|
24
23
|
from nonebot import logger
|
|
@@ -44,7 +43,6 @@ from .consts import (
|
|
|
44
43
|
load_character_prompt,
|
|
45
44
|
)
|
|
46
45
|
from .knowledge import KnowledgeBaseManager
|
|
47
|
-
from .nodes import LIMITING_MEMORY
|
|
48
46
|
from .types import ProfileResult, SessionSummary
|
|
49
47
|
|
|
50
48
|
_STATE_UID = "amrita_memory"
|
|
@@ -52,10 +50,8 @@ _LEGACY_REPO_UID = (
|
|
|
52
50
|
"amrita_memory" # 旧版本元状态占用的 Bot Memory 表 uid(仅用于升级迁移)
|
|
53
51
|
)
|
|
54
52
|
|
|
55
|
-
# 工作流 =
|
|
56
|
-
_WORKFLOW = (
|
|
57
|
-
LOAD_STATE >> JINJA2_RENDER >> LIMITING_MEMORY >> BUILD_MESSAGE >> REACT_BLOCK
|
|
58
|
-
).render()
|
|
53
|
+
# 工作流 = Core 内置 REACT_ONLY(已包含上下文压缩节点),不再自建
|
|
54
|
+
_WORKFLOW = REACT_ONLY.render()
|
|
59
55
|
|
|
60
56
|
|
|
61
57
|
async def _ign_cb(*_, **__): ...
|
|
@@ -65,8 +61,8 @@ class SubconsciousRunner:
|
|
|
65
61
|
"""常驻推理循环运行器。
|
|
66
62
|
|
|
67
63
|
使用 ChatObject 作为数据容器 + 标准 Agent 框架(ReActAgentStrategy)。
|
|
68
|
-
|
|
69
|
-
|
|
64
|
+
工作流直接复用 Core 的 REACT_ONLY,其中的 COMPACT_HISTORY 节点在 Agent
|
|
65
|
+
Loop 之前压缩会话消息。元状态持久化使用独立的 SubconsciousState 表。
|
|
70
66
|
"""
|
|
71
67
|
|
|
72
68
|
def __init__(self, config: SubconsciousConfig) -> None:
|
|
@@ -91,7 +87,7 @@ class SubconsciousRunner:
|
|
|
91
87
|
"""构建自定义 AmritaConfig —— 从全局拷贝并覆写本插件关心的字段。"""
|
|
92
88
|
cfg = get_config().model_copy(deep=True)
|
|
93
89
|
cfg.builtin.loop_reasoning_trigger = self._config.loop_detect_threshold
|
|
94
|
-
cfg.llm.
|
|
90
|
+
cfg.llm.enable_compaction = self._config.enable_memory_compress
|
|
95
91
|
return cfg
|
|
96
92
|
|
|
97
93
|
async def start(self) -> None:
|
|
@@ -195,7 +191,7 @@ class SubconsciousRunner:
|
|
|
195
191
|
finally:
|
|
196
192
|
self._chat_obj = None
|
|
197
193
|
|
|
198
|
-
#
|
|
194
|
+
# 压缩节点已在 workflow 中把摘要写入 chat_obj._di_memory.memory.abstract
|
|
199
195
|
await self._post_process(chat_obj)
|
|
200
196
|
|
|
201
197
|
@staticmethod
|
|
@@ -210,7 +206,7 @@ class SubconsciousRunner:
|
|
|
210
206
|
# 1. 更新全局 usage(复用 Bot 的 InsightsModel 统计)
|
|
211
207
|
await self._update_global_usage(chat_obj)
|
|
212
208
|
|
|
213
|
-
# 2.
|
|
209
|
+
# 2. 提取压缩节点产出的摘要
|
|
214
210
|
mem = chat_obj._di_memory.memory
|
|
215
211
|
if mem is not None and mem.abstract:
|
|
216
212
|
self._last_abstracts.append(mem.abstract)
|
|
@@ -246,7 +242,7 @@ class SubconsciousRunner:
|
|
|
246
242
|
"""更新全局 InsightsModel usage,复用 Bot 的 add_usage 逻辑。
|
|
247
243
|
|
|
248
244
|
chat_obj._di_resp.response 是 LLM 最终返回的 UniResponse,
|
|
249
|
-
chat_obj._di_resp.extra_usage
|
|
245
|
+
chat_obj._di_resp.extra_usage 是压缩等组件累积的额外 token。
|
|
250
246
|
"""
|
|
251
247
|
try:
|
|
252
248
|
resp_state = chat_obj._di_resp
|
|
@@ -480,10 +476,7 @@ class SubconsciousRunner:
|
|
|
480
476
|
return []
|
|
481
477
|
|
|
482
478
|
async def _summarize_session(self, session: MemorySessionsSchema) -> str:
|
|
483
|
-
"""用
|
|
484
|
-
from amrita_core.chatmanager.memory_limiter import MemoryLimiter
|
|
485
|
-
from amrita_core.types import MemoryModel
|
|
486
|
-
|
|
479
|
+
"""用 Core 的 ContextCompactor 生成单个 session 的全会话摘要。"""
|
|
487
480
|
data = session.data
|
|
488
481
|
if data is None:
|
|
489
482
|
return "空会话"
|
|
@@ -498,24 +491,13 @@ class SubconsciousRunner:
|
|
|
498
491
|
except (ValueError, OSError):
|
|
499
492
|
date_str = str(created_at)
|
|
500
493
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
Message(role="system", content=""),
|
|
509
|
-
) as lim:
|
|
510
|
-
# 把所有消息放入 dropped_part -> 触发全量摘要
|
|
511
|
-
lim._dropped_messages = list(memory.messages)
|
|
512
|
-
memory.messages = []
|
|
513
|
-
await lim._make_abstract()
|
|
514
|
-
return (
|
|
515
|
-
f"[{date_str}] {memory.abstract}"
|
|
516
|
-
if memory.abstract
|
|
517
|
-
else f"[{date_str}] 无法生成摘要"
|
|
518
|
-
)
|
|
494
|
+
messages: CONTENT_LIST_TYPE = [
|
|
495
|
+
Message.model_validate(m) for m in raw_messages if isinstance(m, dict)
|
|
496
|
+
]
|
|
497
|
+
if not messages:
|
|
498
|
+
return "空会话"
|
|
499
|
+
abstract = await ContextCompactor(config=get_config()).summarize(messages)
|
|
500
|
+
return f"[{date_str}] {abstract}" if abstract else f"[{date_str}] 无法生成摘要"
|
|
519
501
|
|
|
520
502
|
async def _read_profile(
|
|
521
503
|
self, start_line: int | None = None, end_line: int | None = None
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/PKG-INFO
RENAMED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: amrita_plugin_memory
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Amrita长期记忆插件
|
|
5
5
|
Requires-Python: <3.14,>=3.10
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Requires-Dist: aiohttp>=3.13.3
|
|
9
9
|
Requires-Dist: aiologic>=0.16.0
|
|
10
|
-
Requires-Dist: amrita-core
|
|
11
|
-
Requires-Dist: amrita>=1.
|
|
10
|
+
Requires-Dist: amrita-core<2.0.0,>=1.0.0
|
|
11
|
+
Requires-Dist: amrita>=1.10
|
|
12
12
|
Requires-Dist: chromadb>=1.5.8
|
|
13
13
|
Requires-Dist: click>=8.1
|
|
14
|
-
Requires-Dist: nonebot-plugin-amrita>=0.
|
|
14
|
+
Requires-Dist: nonebot-plugin-amrita>=0.4.0
|
|
15
15
|
Requires-Dist: nonebot-plugin-apscheduler>=0.5.0
|
|
16
16
|
Requires-Dist: nonebot-plugin-localstore>=0.7.4
|
|
17
17
|
Requires-Dist: nonebot-plugin-orm[default]>=0.8.2
|
|
@@ -165,7 +165,7 @@ L3 在代码中沿用历史标识符 `subconscious` / `rethinking`(避免破
|
|
|
165
165
|
| `subconscious_*` 工具 | 巩固循环工具 | — |
|
|
166
166
|
| 用户聊天 → `cancel_and_reschedule` | 任务负激活 | 专注任务时 DMN 被抑制 |
|
|
167
167
|
| 静默期触发 `_run` | 空闲时活跃 | DMN 在无任务时活跃 |
|
|
168
|
-
| 去重 / 压缩 / `
|
|
168
|
+
| 去重 / 压缩 / `ContextCompactor` | 记忆巩固 | 海马 → 皮层的系统巩固 |
|
|
169
169
|
| 画像构建(`*_profile`) | 自传体记忆 | DMN 负责自我参照加工 |
|
|
170
170
|
| 主动消息(`send_to_user`) | 心智游移 | 走神产生与自我相关的念头 |
|
|
171
171
|
| 每轮数万 tokens | 静息高能耗 | DMN 占脑能耗约 20% |
|
|
@@ -287,7 +287,7 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
287
287
|
| 阶段 | 内容 |
|
|
288
288
|
| -------------- | ---------------------------------------------------------- |
|
|
289
289
|
| 状态加载 | 从 `SubconsciousState` 表恢复 `total_runs` / 摘要窗口 |
|
|
290
|
-
| 记忆限幅 | Core `
|
|
290
|
+
| 记忆限幅 | Core `ContextCompactor` 截断超限消息并生成摘要 |
|
|
291
291
|
| ReAct 循环 | Agent 调用 `subconscious_*` 工具执行整理 |
|
|
292
292
|
| 后处理 | 更新全局 usage、持久化元状态、重置惩罚、调度下次运行 |
|
|
293
293
|
|
|
@@ -304,12 +304,13 @@ $$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\
|
|
|
304
304
|
|
|
305
305
|
```mermaid
|
|
306
306
|
flowchart TD
|
|
307
|
-
LOAD_STATE -->
|
|
307
|
+
LOAD_STATE --> NORMALIZE_MESSAGES --> COMPACT_HISTORY --> JINJA2_RENDER --> BUILD_MESSAGE --> REACT_BLOCK
|
|
308
308
|
```
|
|
309
309
|
|
|
310
310
|
`SubconsciousRunner` 把 ChatObject 当数据容器,注入自定义 `SubconsciousBackend`
|
|
311
311
|
(隔离的工具 + memory 后端)与 Core `ReActAgentStrategy`。
|
|
312
|
-
|
|
312
|
+
工作流直接复用 Core 内置的 `REACT_ONLY`,其中的 `COMPACT_HISTORY` 节点在 Agent
|
|
313
|
+
Loop 前运行 Core `ContextCompactor`。压缩排在渲染之前,摘要才能进入本次请求的系统提示词。
|
|
313
314
|
|
|
314
315
|
---
|
|
315
316
|
|
|
@@ -473,7 +474,7 @@ flowchart TD
|
|
|
473
474
|
| ---------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
474
475
|
| L1 备忘录 | `UserMemo` 表(插件自有 ORM,主键 = uni_id) | LLM 维护的常驻用户元信息(≤ `memo_max_chars`) |
|
|
475
476
|
| 循环元状态 | `SubconsciousState` 表(插件自有 ORM,uid=`amrita_memory`) | `total_runs`、`last_abstracts`、`pending_messages`、`knowledge_suggestions` |
|
|
476
|
-
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用
|
|
477
|
+
| Session 摘要缓存 | `LRUCache[int, str]`(最大 128 条) | session DB id → LLM 生成的摘要文本,避免重复调用 `ContextCompactor` |
|
|
477
478
|
| 惩罚计数器 | 内存(不持久化) | `_penalty_count`:重启后从 0 开始 |
|
|
478
479
|
| L2 记忆 | ChromaDB `amrita_user_memory` | 向量 + documents + metadatas(含嵌入指纹与分区键版本) |
|
|
479
480
|
| 用户画像 | `data/amrita_plugin_memory/user_profile.md` | Markdown 文件,`summary---body` 格式,行级增量更新 |
|
|
@@ -649,7 +650,6 @@ amrita_plugin_memory/
|
|
|
649
650
|
├── types.py # TypedDict 定义
|
|
650
651
|
├── consts.py # 默认 Prompt 模板 / ensure_prompt_file
|
|
651
652
|
├── backend.py # SubconsciousBackend — 隔离的工具 + memory 后端
|
|
652
|
-
├── nodes.py # LIMITING_MEMORY 工作流节点
|
|
653
653
|
├── runner.py # SubconsciousRunner — 核心编排器
|
|
654
654
|
├── hooks.py # on_precompletion hook / 生命周期管理
|
|
655
655
|
├── tools.py # 20 个巩固循环工具 handler
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/SOURCES.txt
RENAMED
|
@@ -26,7 +26,6 @@ amrita_plugin_memory/rethinking/backend.py
|
|
|
26
26
|
amrita_plugin_memory/rethinking/consts.py
|
|
27
27
|
amrita_plugin_memory/rethinking/hooks.py
|
|
28
28
|
amrita_plugin_memory/rethinking/knowledge.py
|
|
29
|
-
amrita_plugin_memory/rethinking/nodes.py
|
|
30
29
|
amrita_plugin_memory/rethinking/runner.py
|
|
31
30
|
amrita_plugin_memory/rethinking/schemas.py
|
|
32
31
|
amrita_plugin_memory/rethinking/tools.py
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/requires.txt
RENAMED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
aiohttp>=3.13.3
|
|
2
2
|
aiologic>=0.16.0
|
|
3
|
-
amrita-core
|
|
4
|
-
amrita>=1.
|
|
3
|
+
amrita-core<2.0.0,>=1.0.0
|
|
4
|
+
amrita>=1.10
|
|
5
5
|
chromadb>=1.5.8
|
|
6
6
|
click>=8.1
|
|
7
|
-
nonebot-plugin-amrita>=0.
|
|
7
|
+
nonebot-plugin-amrita>=0.4.0
|
|
8
8
|
nonebot-plugin-apscheduler>=0.5.0
|
|
9
9
|
nonebot-plugin-localstore>=0.7.4
|
|
10
10
|
nonebot-plugin-orm[default]>=0.8.2
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "amrita_plugin_memory"
|
|
3
3
|
description = "Amrita长期记忆插件"
|
|
4
|
-
version = "0.
|
|
4
|
+
version = "0.5.0"
|
|
5
5
|
dependencies = [
|
|
6
6
|
"aiohttp>=3.13.3",
|
|
7
7
|
"aiologic>=0.16.0",
|
|
8
|
-
"amrita-core>=0.
|
|
9
|
-
"amrita>=1.
|
|
8
|
+
"amrita-core>=1.0.0,<2.0.0",
|
|
9
|
+
"amrita>=1.10",
|
|
10
10
|
"chromadb>=1.5.8",
|
|
11
11
|
"click>=8.1",
|
|
12
|
-
"nonebot-plugin-amrita>=0.
|
|
12
|
+
"nonebot-plugin-amrita>=0.4.0",
|
|
13
13
|
"nonebot-plugin-apscheduler>=0.5.0",
|
|
14
14
|
"nonebot-plugin-localstore>=0.7.4",
|
|
15
15
|
"nonebot-plugin-orm[default]>=0.8.2",
|
|
@@ -18,9 +18,6 @@ readme = "README.md"
|
|
|
18
18
|
requires-python = ">=3.10, <3.14"
|
|
19
19
|
|
|
20
20
|
[project.entry-points."ambot.commands"]
|
|
21
|
-
# [full_load] 是 ambot-inlinectl 认识的 extra:命令真正被调用时,
|
|
22
|
-
# 它会先 amrita.init() + amrita.load_plugins() 再加载本命令。
|
|
23
|
-
# 因为 amrita_plugin_memory 包在插件加载器之外无法 import。
|
|
24
21
|
memory = "amrita_plugin_memory.cli:memory [full_load]"
|
|
25
22
|
|
|
26
23
|
[tool.nonebot]
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
"""工作流 Node — 复用 Core Agent 框架。"""
|
|
2
|
-
|
|
3
|
-
from amrita_core.chatmanager import ChatObject
|
|
4
|
-
from amrita_core.chatmanager.memory_limiter import MemoryLimiter
|
|
5
|
-
from amrita_core.utils import gather_usage
|
|
6
|
-
from amrita_sense import Node
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
@Node(tag="subconscious_limiting_memory")
|
|
10
|
-
async def LIMITING_MEMORY(chat_obj: ChatObject) -> None:
|
|
11
|
-
"""运行 Core MemoryLimiter — 截断超限消息并生成摘要。
|
|
12
|
-
|
|
13
|
-
等价于 chat_object.py 的 _limiting_memory,去掉了 SuspendEnum 暂停机制
|
|
14
|
-
(因为我们不使用 ChatObject 的流式暂停)。
|
|
15
|
-
"""
|
|
16
|
-
mem_ctx = chat_obj._di_memory
|
|
17
|
-
input_ctx = chat_obj._di_input
|
|
18
|
-
ab = chat_obj._di_ability
|
|
19
|
-
resp = chat_obj._di_resp
|
|
20
|
-
if not ab.config.llm.enable_memory_abstract:
|
|
21
|
-
return
|
|
22
|
-
assert mem_ctx.memory is not None, "Memory must be loaded before limiting"
|
|
23
|
-
async with MemoryLimiter(mem_ctx.memory, input_ctx.train, config=ab.config) as lim:
|
|
24
|
-
await lim.run_enforce()
|
|
25
|
-
if abs_usage := lim.usage:
|
|
26
|
-
resp.extra_usage = gather_usage(resp.extra_usage, abs_usage)
|
|
27
|
-
mem_ctx.memory = lim.memory
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/_state.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/backend.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/consts.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/schemas.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/tools.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|