diff --git a/amrita_plugin_memory/cli.py b/amrita_plugin_memory/cli.py index 012b5e7..021ec90 100644 --- a/amrita_plugin_memory/cli.py +++ b/amrita_plugin_memory/cli.py @@ -59,9 +59,6 @@ def _progress(done: int, total: int) -> None: return _progress -# status - - def cmd_status() -> None: """显示嵌入指纹、记忆分布与备份列表。""" collection = _get_collection() @@ -118,9 +115,6 @@ def _print_scope_counts(collection: Collection) -> None: click.echo(f" {scope_id}: {count}") -# reindex - - def cmd_reindex(*, assume_yes: bool = False) -> None: """用当前嵌入模型全量重映射。""" collection = _get_collection() @@ -138,9 +132,6 @@ def cmd_reindex(*, assume_yes: bool = False) -> None: click.echo(f"完成,已重新嵌入 {done} 条记忆") -# migrate-keys - - def cmd_migrate_keys(*, dry_run: bool = False) -> None: """把分区键迁移到当前 Amrita 的 uni_id 格式。""" client = get_db_conn() @@ -156,9 +147,6 @@ def cmd_migrate_keys(*, dry_run: bool = False) -> None: click.echo(f"完成,已改写 {changed} 条记忆的分区键") -# reset-fingerprint - - def cmd_reset_fingerprint() -> None: """只写入当前指纹、不重嵌入(逃生舱)。""" collection = _get_collection(create=True) @@ -169,9 +157,6 @@ def cmd_reset_fingerprint() -> None: click.echo("⚠️ 未重新嵌入 —— 若模型确实已变更,检索结果将不准确") -# backup - - def cmd_backup_list() -> None: """列出备份。""" backups = list_backups() @@ -219,9 +204,6 @@ def cmd_backup_restore(file: str) -> None: click.echo(f"完成,已恢复 {done} 条记忆") -# click 命令组 - - @click.group() def memory() -> None: """amrita_plugin_memory 维护命令""" diff --git a/amrita_plugin_memory/config.py b/amrita_plugin_memory/config.py index 03049b1..96c63ee 100644 --- a/amrita_plugin_memory/config.py +++ b/amrita_plugin_memory/config.py @@ -1,4 +1,3 @@ -# Configuration for your_plugin_name plugin from typing import Literal from amrita_core import ModelPreset @@ -14,11 +13,15 @@ class SubconsciousConfig(BaseModel): - """常驻推理循环(潜意识层)配置 — 实验性功能""" + """常驻推理循环(潜意识层)配置。 + + 默认只实现单用户:多用户场景与用户体量难以预测,需要多用户支持时请自行实现。 + """ enabled: bool = Field(default=False, description="是否启用常驻推理循环") target_user_id: str = Field( - default="", description="目标用户ID(MVP仅支持单用户),为空则不启动" + default="", + description=("目标用户ID(默认仅实现单用户,多用户需自行实现),为空则不启动"), ) allowed_tools: list[str] = Field( default_factory=list, diff --git a/amrita_plugin_memory/embedding.py b/amrita_plugin_memory/embedding.py index 5b84f86..bd99286 100644 --- a/amrita_plugin_memory/embedding.py +++ b/amrita_plugin_memory/embedding.py @@ -99,9 +99,6 @@ def describe_stored(collection: Collection) -> str: return f"{protocol} / {model} @ {base_url}" -# 备份 - - def _prune_backups(keep: int) -> None: backups = sorted(BACKUP_DIR.glob("embed_backup_*.json")) for stale in backups[:-keep] if keep > 0 else backups: @@ -131,9 +128,6 @@ def list_backups() -> list[Path]: return sorted(BACKUP_DIR.glob("embed_backup_*.json")) -# 全量重映射 - - async def _write_in_batches( collection: Collection, ids: list[str], @@ -265,9 +259,6 @@ def _run_async(coro: Any) -> Any: ) -# 启动检查 - - def _confirm_reindex(reason: str, stored_desc: str) -> bool: """交互确认;非 TTY 时抛出 _RefuseToLoad。""" if not sys.stdin.isatty(): @@ -322,7 +313,6 @@ def _check_fingerprint(collection: Collection) -> None: _do_reindex_with_progress() return - # policy == "ask" stored_desc = describe_stored(collection) if stored else "(无记录)" if not _confirm_reindex(reason, stored_desc): logger.warning("[Memory] 用户拒绝重映射,保持现有数据不变。") diff --git a/amrita_plugin_memory/keys.py b/amrita_plugin_memory/keys.py index d9af435..25beb76 100644 --- a/amrita_plugin_memory/keys.py +++ b/amrita_plugin_memory/keys.py @@ -1,12 +1,12 @@ """L2 向量层分区键 — 统一跟随已安装 Amrita 的 uni_id 格式。 -Amrita 的会话 ID 格式随版本演进: +Amrita 的会话 ID 存在以下两种格式,本模块需要同时识别: -- 旧版(≤1.9.x):``user_{qq}`` / ``group_{群号}`` -- 新版(开发中):``QQPlatform_Private_{qq}`` / ``QQPlatform_Group_{群号}`` +- ``user_{qq}`` / ``group_{群号}`` +- ``QQPlatform_Private_{qq}`` / ``QQPlatform_Group_{群号}`` 本模块**不硬编码任一格式**,而是委托框架的 ``make_uni_id`` 生成, -并提供一个能识别两种历史格式的解析器,用于存量数据的 Key 迁移。 +并提供一个能识别两种格式的解析器,用于存量数据的 Key 迁移。 """ from __future__ import annotations @@ -22,14 +22,14 @@ Scope = Literal["group", "user"] -# 集合 metadata 中的 Key 体系版本号,用于幂等迁移 +# 集合 metadata 中的 Key 体系版本号,用于幂等迁移 KEY_SCHEMA_VERSION = 2 KEY_SCHEMA_VERSION_META = "key_schema_version" _GROUP_KINDS = {"group", "Group"} _PRIVATE_KINDS = {"user", "Private"} -# 同时匹配新旧两种格式(可选平台前缀 + 类型 + 数字 payload) +# 同时匹配两种格式(可选平台前缀 + 类型 + 数字 payload) _ANY_ID_RE = re.compile( r"^(?:[A-Za-z0-9]+_)?(?PPrivate|Group|Channel|user|group)_(?P[0-9]+)$" ) diff --git a/amrita_plugin_memory/matchers.py b/amrita_plugin_memory/matchers.py index 8bb2fad..c38f49d 100644 --- a/amrita_plugin_memory/matchers.py +++ b/amrita_plugin_memory/matchers.py @@ -184,11 +184,9 @@ async def _handle_delete( result = await ope.get_all_notes(partition_id, include=["metadatas"]) all_ids: list[str] = result.get("ids") or [] - # 精确匹配 if doc_id in all_ids: resolved_id = doc_id else: - # 前缀匹配 matches = [mid for mid in all_ids if mid.startswith(doc_id)] if len(matches) == 0: await matcher.finish( diff --git a/amrita_plugin_memory/migrations/21f55abc2b90_init.py b/amrita_plugin_memory/migrations/21f55abc2b90_init.py index 2a65e43..662a67a 100644 --- a/amrita_plugin_memory/migrations/21f55abc2b90_init.py +++ b/amrita_plugin_memory/migrations/21f55abc2b90_init.py @@ -22,7 +22,6 @@ def upgrade(name: str = "") -> None: if name: return - # ### commands auto generated by Alembic - please adjust! ### op.create_table('amrita_plugin_memory_user_memo', sa.Column('user_id', sa.String(length=64), nullable=False), sa.Column('content', sa.Text(), nullable=False), @@ -32,12 +31,9 @@ def upgrade(name: str = "") -> None: sa.PrimaryKeyConstraint('user_id', name=op.f('pk_amrita_plugin_memory_user_memo')), info={'bind_key': 'amrita_plugin_memory'} ) - # ### end Alembic commands ### def downgrade(name: str = "") -> None: if name: return - # ### commands auto generated by Alembic - please adjust! ### op.drop_table('amrita_plugin_memory_user_memo') - # ### end Alembic commands ### diff --git a/amrita_plugin_memory/migrations/6004d221a7de_state.py b/amrita_plugin_memory/migrations/6004d221a7de_state.py index 925a5c8..4040eba 100644 --- a/amrita_plugin_memory/migrations/6004d221a7de_state.py +++ b/amrita_plugin_memory/migrations/6004d221a7de_state.py @@ -22,7 +22,6 @@ def upgrade(name: str = "") -> None: if name: return - # ### commands auto generated by Alembic - please adjust! ### op.create_table('amrita_plugin_memory_subconscious_state', sa.Column('uid', sa.String(length=64), nullable=False), sa.Column('payload', sa.Text(), nullable=False), @@ -30,12 +29,9 @@ def upgrade(name: str = "") -> None: sa.PrimaryKeyConstraint('uid', name=op.f('pk_amrita_plugin_memory_subconscious_state')), info={'bind_key': 'amrita_plugin_memory'} ) - # ### end Alembic commands ### def downgrade(name: str = "") -> None: if name: return - # ### commands auto generated by Alembic - please adjust! ### op.drop_table('amrita_plugin_memory_subconscious_state') - # ### end Alembic commands ### diff --git a/amrita_plugin_memory/rethinking/knowledge.py b/amrita_plugin_memory/rethinking/knowledge.py index 2cd16a3..7bb6a52 100644 --- a/amrita_plugin_memory/rethinking/knowledge.py +++ b/amrita_plugin_memory/rethinking/knowledge.py @@ -54,8 +54,6 @@ def __init__( self._collection: Collection | None = None self._index: list[KnowledgeEntry] = [] - # 生命周期 - async def init(self) -> None: """创建目录,获取 ChromaDB collection。""" self._knowledge_dir.mkdir(parents=True, exist_ok=True) @@ -76,7 +74,6 @@ async def validate_on_startup(self) -> None: index = self._load_index() index_map: dict[str, KnowledgeEntry] = {e["kid"]: e for e in index} - # 扫描 knowledge/ 目录 existing_files: set[str] = set() if self._knowledge_dir.exists(): for f in self._knowledge_dir.iterdir(): @@ -125,8 +122,6 @@ async def validate_on_startup(self) -> None: f"[KB] validate_on_startup: all {len(index)} entries consistent" ) - # 公开 API - async def list_all(self) -> list[KnowledgeListItem]: """返回索引中全部知识条目(不含正文)。""" return [ @@ -304,7 +299,6 @@ async def search(self, query: str, top_k: int = 5) -> list[KnowledgeSearchItem]: list[dict[str, object]], (raw_result.get("metadatas") or [[]])[0] ) - # 从索引获取完整信息 index_map = {e["kid"]: e for e in self._index} items: list[KnowledgeSearchItem] = [] for i, kid in enumerate(ids): @@ -322,8 +316,6 @@ async def search(self, query: str, top_k: int = 5) -> list[KnowledgeSearchItem]: ) return items - # 内部方法 - async def _recover_orphan_file( self, kid: str, diff --git a/amrita_plugin_memory/rethinking/runner.py b/amrita_plugin_memory/rethinking/runner.py index 45477ba..7061bec 100644 --- a/amrita_plugin_memory/rethinking/runner.py +++ b/amrita_plugin_memory/rethinking/runner.py @@ -81,7 +81,6 @@ def __init__(self, config: SubconsciousConfig) -> None: self._kb_manager: KnowledgeBaseManager | None = None # session 摘要缓存(LRU):session DB id -> 摘要文本,最多 128 条 self._session_cache: LRUCache[int, str] = LRUCache(128) - # 用户画像文件 self._profile_path = DATA_PATH / "user_profile.md" @property @@ -95,8 +94,6 @@ def _build_config(self) -> AmritaConfig: cfg.llm.enable_memory_abstract = self._config.enable_memory_compress return cfg - # 生命周期 - async def start(self) -> None: logger.info(f"[Subconscious] Starting for user={self._config.target_user_id}") await self._load_state() @@ -150,8 +147,6 @@ def _schedule_once(self, delay_seconds: int) -> None: misfire_grace_time=30, ) - # 核心运行 - async def _run(self) -> None: """调度入口 — 保证任何异常路径都会释放运行标志。""" if self._is_running: @@ -210,8 +205,6 @@ def _is_native_thinking(preset: ModelPreset) -> bool: and preset.thinking_config.thinking_type == "enabled" ) - # 后处理 - async def _post_process(self, chat_obj: ChatObject) -> None: """本轮结束后:提取摘要、更新全局 usage、持久化、调度下次运行。""" # 1. 更新全局 usage(复用 Bot 的 InsightsModel 统计) @@ -275,8 +268,6 @@ async def _update_global_usage(chat_obj: ChatObject) -> None: except Exception as e: logger.warning(f"[Subconscious] Update global usage failed: {e}") - # SubconsciousState 持久化 - @staticmethod async def _read_state_payload(uid: str) -> dict[str, Any]: async with get_session() as session: @@ -376,8 +367,6 @@ async def _save_state(self) -> None: }, ) - # Prompt 加载 - async def _load_prompt(self) -> str: main_path = (self._prompt_dir / self._config.prompt_file).resolve() kn_path = (self._prompt_dir / self._config.prompt_knowledge_file).resolve() @@ -421,7 +410,7 @@ async def _load_prompt(self) -> str: f"审查后对值得保留的用 subconscious_knowledge_create/update 实际写入。" ) - # Phase 3: 膨胀感知 — 查 ChromaDB 总量,超阈值注入警告 + # 膨胀感知:查 ChromaDB 总量,超阈值注入警告 try: pid = make_scope_id(self._config.target_user_id, is_group=False) ope = AsyncUserMemory(get_db_conn()) @@ -447,8 +436,6 @@ async def _load_prompt(self) -> str: return prompt - # Session 读取 & 摘要缓存 - async def _read_recent_sessions(self, n: int = 5) -> list[SessionSummary]: """读取目标用户最近 N 个归档 sessions,按需生成摘要并缓存。""" try: @@ -530,8 +517,6 @@ async def _summarize_session(self, session: MemorySessionsSchema) -> str: else f"[{date_str}] 无法生成摘要" ) - # 用户画像 - async def _read_profile( self, start_line: int | None = None, end_line: int | None = None ) -> ProfileResult: @@ -598,11 +583,9 @@ async def _update_profile( body_lines = existing_body.split("\n") if existing_body else [] new = new_lines.split("\n") if start_line is None or end_line is None: - # 追加模式 new_body_lines = body_lines + new operation = f"append {len(new)} lines" else: - # 替换模式 s = max(0, start_line) e = max(s, min(len(body_lines), end_line)) new_body_lines = body_lines[:s] + new + body_lines[e:] diff --git a/amrita_plugin_memory/rethinking/schemas.py b/amrita_plugin_memory/rethinking/schemas.py index 0c61ef1..9c8f723 100644 --- a/amrita_plugin_memory/rethinking/schemas.py +++ b/amrita_plugin_memory/rethinking/schemas.py @@ -145,7 +145,6 @@ ), ) -# 工具注册常量 DUPLICATE_HELPER_SCHEMA = FunctionDefinitionSchema( name="subconscious_duplicate_helper", @@ -183,7 +182,6 @@ parameters=FunctionParametersSchema(type="object", properties={}, required=[]), ) -# 全局知识库工具 KNOWLEDGE_LIST_SCHEMA = FunctionDefinitionSchema( name="subconscious_knowledge_list", @@ -281,7 +279,7 @@ ), ) -# 知识建议(对话 LLM 提议,潜意识 Agent 审查后实际写入) +# 知识建议:对话 LLM 提议,潜意识 Agent 审查后实际写入 KNOWLEDGE_SUGGEST_SCHEMA = FunctionDefinitionSchema( name="knowledge_suggest", @@ -328,7 +326,6 @@ parameters=FunctionParametersSchema(type="object", properties={}, required=[]), ) -# Session 与用户画像工具 READ_SESSIONS_SCHEMA = FunctionDefinitionSchema( name="subconscious_read_sessions", diff --git a/amrita_plugin_memory/rethinking/tools.py b/amrita_plugin_memory/rethinking/tools.py index 16ba86b..b243237 100644 --- a/amrita_plugin_memory/rethinking/tools.py +++ b/amrita_plugin_memory/rethinking/tools.py @@ -43,8 +43,6 @@ _SUBCONSCIOUS_TOOLS = _state.get_tools_manager() -# 辅助 - def _make_operator() -> AsyncUserMemory: return AsyncUserMemory(get_db_conn()) @@ -67,9 +65,6 @@ def _tools_ok(**extra: Any) -> str: return json.dumps({"status": "success", **extra}, ensure_ascii=False, indent=2) -# Handler - - @on_tools(READ_MEMORY_SCHEMA, strict=True, bound_to=_SUBCONSCIOUS_TOOLS) async def subconscious_read_memory(data: dict[str, Any]) -> str: ope = _make_operator() @@ -259,7 +254,6 @@ async def subconscious_send_to_user(data: dict[str, Any]) -> str: pending = _state.get_pending() pending.append({"content": content, "timestamp": ts}) await runner._save_state() - # 即时发送 try: from nonebot import get_bot @@ -306,9 +300,6 @@ async def subconscious_read_chat_context(data: dict[str, Any]) -> str: return _tools_err(str(e)) -# 消息生成 - - async def _generate_send_content(intent: str, memory_context: str) -> str: runner = _state.get_runner() if runner is None: @@ -337,9 +328,6 @@ async def _generate_send_content(intent: str, memory_context: str) -> str: return response.content.strip() -# 压缩辅助工具 - - @on_tools(DUPLICATE_HELPER_SCHEMA, strict=True, bound_to=_SUBCONSCIOUS_TOOLS) async def subconscious_duplicate_helper(data: dict[str, Any]) -> str: """返回指定范围内全部记忆 + LLM 合并指导 prompt。 @@ -364,7 +352,6 @@ async def subconscious_duplicate_helper(data: dict[str, Any]) -> str: {} for _ in ids ] - # 过滤 tag_filter = data.get("tag") imp_filter = data.get("importance") sort_by = str(data.get("sort_by", "created_at")) @@ -387,7 +374,6 @@ async def subconscious_duplicate_helper(data: dict[str, Any]) -> str: } ) - # 排序 if sort_by == "importance": importance_order = {"high": 0, "medium": 1, "low": 2} items.sort(key=lambda x: importance_order.get(str(x["importance"]), 1)) @@ -482,9 +468,6 @@ async def subconscious_get_memory_stats(data: dict[str, Any]) -> str: return _tools_err(str(e)) -# 全局知识库工具 - - def _get_kb_manager(): """获取 KnowledgeBaseManager 实例,未初始化或已禁用则报错。""" runner = _state.get_runner() @@ -622,7 +605,7 @@ async def subconscious_knowledge_search(data: dict[str, Any]) -> str: return _tools_err(str(e)) -# 知识建议(对话 LLM -> 潜意识 Agent 审查管线) +# 知识建议:对话 LLM -> 潜意识 Agent 审查管线 @on_tools(KNOWLEDGE_SUGGEST_SCHEMA, strict=True) @@ -676,9 +659,6 @@ async def subconscious_read_suggestions(data: dict[str, Any]) -> str: ) -# Session 与用户画像工具 - - def _get_runner(): """获取 SubconsciousRunner 实例,未初始化则报错。""" runner = _state.get_runner() diff --git a/amrita_plugin_memory/tools.py b/amrita_plugin_memory/tools.py index 4442f5b..39929ce 100644 --- a/amrita_plugin_memory/tools.py +++ b/amrita_plugin_memory/tools.py @@ -62,7 +62,6 @@ def _check_required(ctx: ToolContext, tool_name: str, *params: str) -> str | Non return None -# 公共参数:scope _SCOPE_PROP = FunctionPropertySchema( type="string", description=( @@ -72,7 +71,6 @@ def _check_required(ctx: ToolContext, tool_name: str, *params: str) -> str | Non enum=["group", "user"], ) -# Function Schema 定义 WRITE_MEMORY_FUN = FunctionDefinitionSchema( name="write_memory", @@ -199,9 +197,6 @@ def _check_required(ctx: ToolContext, tool_name: str, *params: str) -> str | Non ) -# Handler 实现 - - @on_tools(WRITE_MEMORY_FUN, custom_run=True, strict=True) async def w(ctx: ToolContext) -> str: if err := _check_required(