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.
Files changed (35) hide show
  1. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/PKG-INFO +10 -10
  2. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/README.md +6 -6
  3. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/__init__.py +9 -2
  4. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/embedding.py +125 -88
  5. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/keys.py +21 -3
  6. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/__init__.py +1 -2
  7. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/runner.py +19 -37
  8. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/PKG-INFO +10 -10
  9. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/SOURCES.txt +0 -1
  10. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/requires.txt +3 -3
  11. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/pyproject.toml +4 -7
  12. amrita_plugin_memory-0.4.0/amrita_plugin_memory/rethinking/nodes.py +0 -27
  13. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/LICENSE +0 -0
  14. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/cli.py +0 -0
  15. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/config.py +0 -0
  16. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/embed.py +0 -0
  17. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/matchers.py +0 -0
  18. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/memo.py +0 -0
  19. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/migrations/21f55abc2b90_init.py +0 -0
  20. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/migrations/6004d221a7de_state.py +0 -0
  21. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/models.py +0 -0
  22. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/_state.py +0 -0
  23. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/backend.py +0 -0
  24. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/consts.py +0 -0
  25. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/hooks.py +0 -0
  26. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/knowledge.py +0 -0
  27. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/schemas.py +0 -0
  28. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/tools.py +0 -0
  29. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/rethinking/types.py +0 -0
  30. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/tools.py +0 -0
  31. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory/vector.py +0 -0
  32. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
  33. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/entry_points.txt +0 -0
  34. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.5.0}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
  35. {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.4.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>=0.8.5
11
- Requires-Dist: amrita>=1.3
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.3.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
- | 去重 / 压缩 / `MemoryLimiter` | 记忆巩固 | 海马 → 皮层的系统巩固 |
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 `MemoryLimiter` 截断超限消息并生成摘要 |
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 --> JINJA2_RENDER --> LIMITING_MEMORY --> BUILD_MESSAGE --> REACT_BLOCK
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
- `LIMITING_MEMORY` 在 Agent Loop 前运行 Core `MemoryLimiter`。
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 生成的摘要文本,避免重复调用 MemoryLimiter |
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
- | 去重 / 压缩 / `MemoryLimiter` | 记忆巩固 | 海马 → 皮层的系统巩固 |
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 `MemoryLimiter` 截断超限消息并生成摘要 |
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 --> JINJA2_RENDER --> LIMITING_MEMORY --> BUILD_MESSAGE --> REACT_BLOCK
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
- `LIMITING_MEMORY` 在 Agent Loop 前运行 Core `MemoryLimiter`。
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 生成的摘要文本,避免重复调用 MemoryLimiter |
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
- # 启动检查(import 期,同步):Key 迁移 + 嵌入模型指纹校验。内部已做防御性处理:只有“需要确认但环境不可交互”会抛出异常,从而拒绝加载插件(刻意的失败快);其余异常只告警。
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
- class _RefuseToLoad(RuntimeError):
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 total == 0:
190
- client.delete_collection(MEMORY_COLLECTION_NAME)
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
- client.delete_collection(MEMORY_COLLECTION_NAME)
197
- fresh = client.create_collection(MEMORY_COLLECTION_NAME)
198
- done = await _write_in_batches(
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
- existing = {c.name for c in client.list_collections()}
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 _write_in_batches(
238
- fresh,
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 _run_async(coro: Any) -> Any:
251
- """在同步上下文(import 期)执行协程;已有事件循环时给出明确指引。"""
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
- raise _RefuseToLoad(
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 _do_reindex_with_progress() -> int:
281
- def _progress(done: int, total: int) -> None:
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
- reason = "库中未记录嵌入模型信息,无法确认现有向量与当前配置是否一致。"
304
- else:
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
- stored_desc = describe_stored(collection) if stored else "(无记录)"
317
- if not _confirm_reindex(reason, stored_desc):
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
- """启动检查:Key 迁移 + 嵌入指纹校验。在插件 import 期调用(同步)。
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
- _check_fingerprint(collection)
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
- converted = to_current_format(current)
111
- if converted is None or converted == current:
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)
@@ -1,12 +1,11 @@
1
1
  """潜意识推理循环(rethinking)"""
2
2
 
3
- from . import _state, backend, hooks, nodes, schemas, tools
3
+ from . import _state, backend, hooks, schemas, tools
4
4
 
5
5
  __all__ = [
6
6
  "_state",
7
7
  "backend",
8
8
  "hooks",
9
- "nodes",
10
9
  "schemas",
11
10
  "tools",
12
11
  ]
@@ -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 REACT_BLOCK
15
+ from amrita_core.builtins.workflows import REACT_ONLY
16
16
  from amrita_core.chatmanager import ChatObject
17
- from amrita_core.components.llm import JINJA2_RENDER
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
- # 工作流 = 加载状态 -> Jinja2 渲染 -> 记忆限幅 -> 构建消息 -> ReAct 循环
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
- LIMITING_MEMORY 节点在 Agent Loop 之前运行 MemoryLimiter 压缩会话消息。
69
- 元状态持久化使用独立的 SubconsciousState 表。
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.enable_memory_abstract = self._config.enable_memory_compress
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
- # MemoryLimiter 在 workflow 中已经产出 chat_obj._di_memory.memory.abstract
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. 提取 MemoryLimiter 产出的摘要
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 是 MemoryLimiter 等组件累积的额外 token。
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
- """用 MemoryLimiter 生成单个 session 的全会话摘要。"""
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
- memory = MemoryModel(messages=[], abstract="")
502
- for m in raw_messages:
503
- if isinstance(m, dict):
504
- memory.messages.append(Message.model_validate(m))
505
-
506
- async with MemoryLimiter(
507
- memory,
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
@@ -1,17 +1,17 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amrita_plugin_memory
3
- Version: 0.4.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>=0.8.5
11
- Requires-Dist: amrita>=1.3
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.3.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
- | 去重 / 压缩 / `MemoryLimiter` | 记忆巩固 | 海马 → 皮层的系统巩固 |
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 `MemoryLimiter` 截断超限消息并生成摘要 |
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 --> JINJA2_RENDER --> LIMITING_MEMORY --> BUILD_MESSAGE --> REACT_BLOCK
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
- `LIMITING_MEMORY` 在 Agent Loop 前运行 Core `MemoryLimiter`。
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 生成的摘要文本,避免重复调用 MemoryLimiter |
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
@@ -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
@@ -1,10 +1,10 @@
1
1
  aiohttp>=3.13.3
2
2
  aiologic>=0.16.0
3
- amrita-core>=0.8.5
4
- amrita>=1.3
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.3.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.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.8.5",
9
- "amrita>=1.3",
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.3.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