amrita_plugin_memory 0.4.0__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.
Files changed (35) hide show
  1. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/PKG-INFO +1 -1
  2. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/__init__.py +9 -2
  3. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/embedding.py +125 -88
  4. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/keys.py +21 -3
  5. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/PKG-INFO +1 -1
  6. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/pyproject.toml +1 -4
  7. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/LICENSE +0 -0
  8. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/README.md +0 -0
  9. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/cli.py +0 -0
  10. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/config.py +0 -0
  11. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/embed.py +0 -0
  12. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/matchers.py +0 -0
  13. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/memo.py +0 -0
  14. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/migrations/21f55abc2b90_init.py +0 -0
  15. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/migrations/6004d221a7de_state.py +0 -0
  16. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/models.py +0 -0
  17. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/__init__.py +0 -0
  18. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/_state.py +0 -0
  19. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/backend.py +0 -0
  20. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/consts.py +0 -0
  21. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/hooks.py +0 -0
  22. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/knowledge.py +0 -0
  23. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/nodes.py +0 -0
  24. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/runner.py +0 -0
  25. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/schemas.py +0 -0
  26. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/tools.py +0 -0
  27. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/types.py +0 -0
  28. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/tools.py +0 -0
  29. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/vector.py +0 -0
  30. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/SOURCES.txt +0 -0
  31. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
  32. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/entry_points.txt +0 -0
  33. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/requires.txt +0 -0
  34. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
  35. {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amrita_plugin_memory
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Amrita长期记忆插件
5
5
  Requires-Python: <3.14,>=3.10
6
6
  Description-Content-Type: text/markdown
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amrita_plugin_memory
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Amrita长期记忆插件
5
5
  Requires-Python: <3.14,>=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "amrita_plugin_memory"
3
3
  description = "Amrita长期记忆插件"
4
- version = "0.4.0"
4
+ version = "0.4.1"
5
5
  dependencies = [
6
6
  "aiohttp>=3.13.3",
7
7
  "aiologic>=0.16.0",
@@ -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]