Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 0 additions & 18 deletions amrita_plugin_memory/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,6 @@ def _progress(done: int, total: int) -> None:
return _progress


# status


def cmd_status() -> None:
"""显示嵌入指纹、记忆分布与备份列表。"""
collection = _get_collection()
Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand All @@ -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)
Expand All @@ -169,9 +157,6 @@ def cmd_reset_fingerprint() -> None:
click.echo("⚠️ 未重新嵌入 —— 若模型确实已变更,检索结果将不准确")


# backup


def cmd_backup_list() -> None:
"""列出备份。"""
backups = list_backups()
Expand Down Expand Up @@ -219,9 +204,6 @@ def cmd_backup_restore(file: str) -> None:
click.echo(f"完成,已恢复 {done} 条记忆")


# click 命令组


@click.group()
def memory() -> None:
"""amrita_plugin_memory 维护命令"""
Expand Down
9 changes: 6 additions & 3 deletions amrita_plugin_memory/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
# Configuration for your_plugin_name plugin
from typing import Literal

from amrita_core import ModelPreset
Expand All @@ -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,
Expand Down
10 changes: 0 additions & 10 deletions amrita_plugin_memory/embedding.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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():
Expand Down Expand Up @@ -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] 用户拒绝重映射,保持现有数据不变。")
Expand Down
12 changes: 6 additions & 6 deletions amrita_plugin_memory/keys.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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]+_)?(?P<kind>Private|Group|Channel|user|group)_(?P<payload>[0-9]+)$"
)
Expand Down
2 changes: 0 additions & 2 deletions amrita_plugin_memory/matchers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
4 changes: 0 additions & 4 deletions amrita_plugin_memory/migrations/21f55abc2b90_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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 ###
4 changes: 0 additions & 4 deletions amrita_plugin_memory/migrations/6004d221a7de_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,16 @@
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),
sa.Column('updated_at', sa.DateTime(), nullable=False),
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 ###
8 changes: 0 additions & 8 deletions amrita_plugin_memory/rethinking/knowledge.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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():
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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):
Expand All @@ -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,
Expand Down
19 changes: 1 addition & 18 deletions amrita_plugin_memory/rethinking/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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 统计)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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())
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:]
Expand Down
5 changes: 1 addition & 4 deletions amrita_plugin_memory/rethinking/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@
),
)

# 工具注册常量

DUPLICATE_HELPER_SCHEMA = FunctionDefinitionSchema(
name="subconscious_duplicate_helper",
Expand Down Expand Up @@ -183,7 +182,6 @@
parameters=FunctionParametersSchema(type="object", properties={}, required=[]),
)

# 全局知识库工具

KNOWLEDGE_LIST_SCHEMA = FunctionDefinitionSchema(
name="subconscious_knowledge_list",
Expand Down Expand Up @@ -281,7 +279,7 @@
),
)

# 知识建议(对话 LLM 提议,潜意识 Agent 审查后实际写入)
# 知识建议:对话 LLM 提议,潜意识 Agent 审查后实际写入

KNOWLEDGE_SUGGEST_SCHEMA = FunctionDefinitionSchema(
name="knowledge_suggest",
Expand Down Expand Up @@ -328,7 +326,6 @@
parameters=FunctionParametersSchema(type="object", properties={}, required=[]),
)

# Session 与用户画像工具

READ_SESSIONS_SCHEMA = FunctionDefinitionSchema(
name="subconscious_read_sessions",
Expand Down
Loading
Loading