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.
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/PKG-INFO +1 -1
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/__init__.py +9 -2
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/embedding.py +125 -88
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/keys.py +21 -3
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/PKG-INFO +1 -1
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/pyproject.toml +1 -4
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/LICENSE +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/README.md +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/cli.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/config.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/embed.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/matchers.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/memo.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/migrations/21f55abc2b90_init.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/migrations/6004d221a7de_state.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/models.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/__init__.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/_state.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/backend.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/consts.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/hooks.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/knowledge.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/nodes.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/runner.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/schemas.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/tools.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/types.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/tools.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/vector.py +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/SOURCES.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/entry_points.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/requires.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
- {amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/setup.cfg +0 -0
|
@@ -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)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "amrita_plugin_memory"
|
|
3
3
|
description = "Amrita长期记忆插件"
|
|
4
|
-
version = "0.4.
|
|
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]
|
|
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
|
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/_state.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/backend.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/consts.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/hooks.py
RENAMED
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/nodes.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/runner.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/schemas.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/tools.py
RENAMED
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory/rethinking/types.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{amrita_plugin_memory-0.4.0 → amrita_plugin_memory-0.4.1}/amrita_plugin_memory.egg-info/requires.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|