amrita_plugin_memory 0.2.2__tar.gz → 0.2.2.post1__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 (27) hide show
  1. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/PKG-INFO +1 -1
  2. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/__init__.py +20 -0
  3. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/_state.py +41 -0
  4. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/backend.py +73 -0
  5. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/consts.py +88 -0
  6. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/hooks.py +75 -0
  7. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/nodes.py +48 -0
  8. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/runner.py +383 -0
  9. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/schemas.py +217 -0
  10. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/tools.py +448 -0
  11. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory/rethinking/workflow.py +39 -0
  12. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory.egg-info/PKG-INFO +1 -1
  13. amrita_plugin_memory-0.2.2.post1/amrita_plugin_memory.egg-info/SOURCES.txt +24 -0
  14. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/pyproject.toml +2 -2
  15. amrita_plugin_memory-0.2.2/amrita_plugin_memory.egg-info/SOURCES.txt +0 -14
  16. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/LICENSE +0 -0
  17. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/README.md +0 -0
  18. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/__init__.py +0 -0
  19. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/config.py +0 -0
  20. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/embed.py +0 -0
  21. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/matchers.py +0 -0
  22. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/tools.py +0 -0
  23. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory/vector.py +0 -0
  24. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory.egg-info/dependency_links.txt +0 -0
  25. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory.egg-info/requires.txt +0 -0
  26. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/amrita_plugin_memory.egg-info/top_level.txt +0 -0
  27. {amrita_plugin_memory-0.2.2 → amrita_plugin_memory-0.2.2.post1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: amrita_plugin_memory
3
- Version: 0.2.2
3
+ Version: 0.2.2.post1
4
4
  Summary: Amrita长期记忆插件
5
5
  Requires-Python: <3.14,>=3.10
6
6
  Description-Content-Type: text/markdown
@@ -0,0 +1,20 @@
1
+ """潜意识推理循环(rethinking 子包)。
2
+
3
+ 导入此包时触发工具注册和 hook 注册。
4
+ 注意:避免模块级导入 tools/runner 等会触发循环导入的子模块。
5
+ 这些模块的副作用(@on_tools 注册、hook 注册)通过 hooks 和 tools 内部的延迟导入自动生效。
6
+ """
7
+
8
+ from . import _state, backend, hooks, nodes, schemas, workflow
9
+
10
+ __all__ = [
11
+ "_state",
12
+ "backend",
13
+ "consts",
14
+ "hooks",
15
+ "nodes",
16
+ "runner",
17
+ "schemas",
18
+ "tools",
19
+ "workflow",
20
+ ]
@@ -0,0 +1,41 @@
1
+ """模块级共享状态管理。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ from amrita_core.tools.manager import MultiToolsManager
8
+
9
+ if TYPE_CHECKING:
10
+ from .runner import SubconsciousRunner
11
+
12
+ _runner: SubconsciousRunner | None = None
13
+ _pending_messages: list[dict[str, Any]] = []
14
+ # 隔离的工具管理器 — 所有 @on_tools 装饰器通过 bound_to 注册到这里,不污染全局 ToolsManager
15
+ _SUBCONSCIOUS_TOOLS = MultiToolsManager()
16
+
17
+
18
+ def get_runner() -> SubconsciousRunner | None:
19
+ return _runner
20
+
21
+
22
+ def set_runner(r: SubconsciousRunner | None) -> None:
23
+ global _runner
24
+ _runner = r
25
+
26
+
27
+ def get_target_user_id() -> str:
28
+ return _runner._config.target_user_id if _runner is not None else ""
29
+
30
+
31
+ def get_pending() -> list[dict[str, Any]]:
32
+ return _pending_messages
33
+
34
+
35
+ def set_pending(msgs: list[dict[str, Any]]) -> None:
36
+ global _pending_messages
37
+ _pending_messages = msgs
38
+
39
+
40
+ def get_tools_manager() -> MultiToolsManager:
41
+ return _SUBCONSCIOUS_TOOLS
@@ -0,0 +1,73 @@
1
+ """自定义 Backend — 为潜意识循环提供隔离的工具和内存池。"""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, ClassVar
6
+
7
+ from amrita_core.base.backend import AbilityBackend, MemoryBackend
8
+ from amrita_core.contexts import AbilityContext
9
+ from amrita_core.preset import MultiPresetManager
10
+ from amrita_core.tools.manager import MultiToolsManager, ToolsManager
11
+ from amrita_core.tools.mcp import ClientManager, MultiClientManager
12
+ from amrita_core.types import MemoryModel
13
+ from nonebot import logger
14
+
15
+ from ..config import SubconsciousConfig
16
+ from . import _state
17
+
18
+
19
+ def _get_global_tools() -> ToolsManager:
20
+ return ToolsManager()
21
+
22
+
23
+ class SubconsciousBackend(AbilityBackend, MemoryBackend):
24
+ """为潜意识循环提供隔离的工具和内存池。"""
25
+
26
+ _instance: ClassVar[SubconsciousBackend | None] = None
27
+ _tools_manager: MultiToolsManager
28
+ _presets: MultiPresetManager
29
+ _memory: MemoryModel
30
+
31
+ def __new__(cls, *args: Any, **kwargs: Any):
32
+ if cls._instance is None:
33
+ cls._instance = super().__new__(cls)
34
+ return cls._instance
35
+
36
+ def __init__(self, config: SubconsciousConfig):
37
+ if not hasattr(self, "_tools_manager"):
38
+ self._config = config
39
+ self._presets = MultiPresetManager()
40
+ self._memory = MemoryModel()
41
+ self._register_tools()
42
+
43
+ def _register_tools(self) -> None:
44
+ # 所有工具已通过 @on_tools(bound_to=_state.get_tools_manager()) 注册到隔离的 MultiToolsManager
45
+ # 这里只需拉取它们的 ToolData 用于 debug 校验
46
+ self._tools_manager = _state.get_tools_manager()
47
+ # allowed_tools 从全局 ToolsManager 拉取额外工具
48
+ gm = _get_global_tools()
49
+ for tool_name in self._config.allowed_tools:
50
+ if (td := gm.get_tool(tool_name)) is not None:
51
+ self._tools_manager.register_tool(td)
52
+ else:
53
+ logger.warning(f"[EXP Subconscious] Tool '{tool_name}' not found")
54
+
55
+ async def load_ability_all(self, session_id: str) -> AbilityContext:
56
+ return AbilityContext(
57
+ tools=self._tools_manager, presets=self._presets, mcp=ClientManager()
58
+ )
59
+
60
+ async def load_mcp_clients(self, session_id: str) -> MultiClientManager:
61
+ return ClientManager()
62
+
63
+ async def load_tools(self, session_id: str) -> MultiToolsManager:
64
+ return self._tools_manager
65
+
66
+ async def load_presets(self, session_id: str) -> MultiPresetManager:
67
+ return self._presets
68
+
69
+ async def load_memory(self, session_id: str) -> MemoryModel:
70
+ return MemoryModel(messages=[], abstract="")
71
+
72
+ async def commit_memory(self, session_id: str, memory: MemoryModel) -> None:
73
+ self._memory = memory
@@ -0,0 +1,88 @@
1
+ """常量、默认提示词模板和工具函数。"""
2
+
3
+ from pathlib import Path
4
+
5
+ from nonebot import logger
6
+
7
+ DEFAULT_SUBCONSCIOUS_PROMPT = """\
8
+ 你是用户的记忆管家(Memory Curator),在后台静默运行。你的职责是整理、优化用户的长期记忆库。
9
+ {% if character_prompt -%}
10
+ {{ character_prompt }}
11
+ {% endif %}
12
+
13
+ <ROLE>
14
+ 你是一个细心、可靠、不喧宾夺主的记忆管家。你不会和用户直接对话——所有操作通过工具调用完成。
15
+ </ROLE>
16
+
17
+ <INFO>
18
+ 当前时间:{{ current_time }}
19
+ 用户ID:{{ target_user_id }}
20
+ 这是第 {{ total_runs }} 次推理循环。
21
+ {% if last_run %}
22
+ <PREVIOUS_RUN_SUMMARY>
23
+ {{ last_run }}
24
+ </PREVIOUS_RUN_SUMMARY>
25
+ {% endif %}
26
+ </INFO>
27
+
28
+ <RULES>
29
+ 1. 必须使用工具检查操作用户记忆库,不凭空想象。
30
+ 2. 每轮先了解状态(subconscious_list_memory / subconscious_read_memory),再执行整理。
31
+ 3. 识别重复/矛盾/过时/缺标签的记忆,用 subconscious_write_memory / update / delete 处理。
32
+ 4. 整理完毕或有新发现需要处理时,必须调用 subconscious_iter_stop。
33
+ 5. 别单轮做太多,保持高效。
34
+ </RULES>
35
+
36
+ <MEMORY_QUALITY>
37
+ - 去重:内容高度相似 → 合并为一条精炼的
38
+ - 矛盾检测:保留较新或更具体的
39
+ - 标签管理:有意义且一致
40
+ - 重要性校准:评估 low/medium/high
41
+ - 过时信息:考虑更新或降低重要性
42
+ </MEMORY_QUALITY>"""
43
+
44
+ DEFAULT_SEND_PROMPT = """\
45
+ {% if character_prompt -%}
46
+ <ORIGINAL_CHARACTER_PROMPT>
47
+ {{ character_prompt }}
48
+ </ORIGINAL_CHARACTER_PROMPT>
49
+
50
+
51
+ {% endif %}
52
+ 你是AI助手本身,以你的日常角色身份对用户说话。不要暴露你是"后台进程"。
53
+
54
+ <INTENT>{{ intent }}</INTENT>
55
+ {% if memory_context %}<MEMORY>{{ memory_context }}</MEMORY>{% endif %}
56
+
57
+ <RULES>
58
+ - 只输出一句话,≤60字
59
+ - 语气自然随意,像朋友发消息
60
+ - 有记忆上下文就自然提及(别背诵原文)
61
+ - 不用任何技术标签或格式
62
+ </RULES>"""
63
+
64
+
65
+ def ensure_prompt_file(path: Path, default_content: str) -> None:
66
+ """确保 prompt 文件存在,缺失时创建默认文件。"""
67
+ if not path.exists():
68
+ path.parent.mkdir(parents=True, exist_ok=True)
69
+ path.write_text(default_content, encoding="utf-8")
70
+ logger.info(f"[EXP Subconscious] Created default prompt: {path}")
71
+
72
+
73
+ async def load_character_prompt() -> str:
74
+ """加载 Bot 的角色设定 prompt(从 config/chat/private_prompts/ 中读取)。"""
75
+ try:
76
+ from amrita.plugins.chat.config import config_manager
77
+
78
+ prompts_dir = config_manager.prompt_store.private_dir
79
+ character = config_manager.config.private_prompt_character
80
+ char_file = prompts_dir / f"{character}.txt"
81
+ if char_file.exists():
82
+ return char_file.read_text(encoding="utf-8").strip()
83
+ default_file = prompts_dir / "default.txt"
84
+ if default_file.exists():
85
+ return default_file.read_text(encoding="utf-8").strip()
86
+ except Exception as e:
87
+ logger.debug(f"[EXP Subconscious] Load character prompt failed: {e}")
88
+ return ""
@@ -0,0 +1,75 @@
1
+ """Hook 注册与生命周期管理。"""
2
+
3
+ from amrita_core.types import Message
4
+ from nonebot import get_driver, logger
5
+
6
+ from ..config import DataManager
7
+ from . import _state
8
+
9
+
10
+ async def _setup_subconscious_hook() -> None:
11
+ from amrita.plugins.chat.runtime import try_get_amrita_ctx
12
+ from amrita_core.hook.event import PreCompletionEvent
13
+ from amrita_core.hook.on import on_precompletion
14
+
15
+ pre_chat_hook = on_precompletion(priority=10, block=False)
16
+
17
+ @pre_chat_hook.handle()
18
+ async def _subconscious_pre_hook(event: PreCompletionEvent) -> None:
19
+ runner = _state.get_runner()
20
+ if runner is None:
21
+ return
22
+ amrita_ctx = try_get_amrita_ctx(event.chat_object)
23
+ if amrita_ctx is None:
24
+ return
25
+ if str(amrita_ctx["event"].user_id) != runner._config.target_user_id:
26
+ return
27
+
28
+ if runner._config.pause_on_user_chat:
29
+ await runner.pause_due_to_user_activity()
30
+
31
+ pending = _state.get_pending()
32
+ if pending and runner._config.allow_send_to_user:
33
+ msgs = list(pending)
34
+ _state.set_pending([])
35
+ await runner._save_pending_to_repo()
36
+ for entry in msgs:
37
+ content = entry.get("content", "")
38
+ ts = entry.get("timestamp", "")
39
+ if not content:
40
+ continue
41
+ inject = Message(
42
+ role="system",
43
+ content=f"[系统提示] 在 {ts} 时,你(潜意识)曾想对用户说:{content}\n现在用户发来了新消息,请自然地将这个背景融入你的回复中。",
44
+ )
45
+ event.chat_object.data.messages.insert(0, inject)
46
+ logger.info(
47
+ f"[EXP Subconscious] Injected pending msg: {content[:50]}..."
48
+ )
49
+
50
+ logger.debug("[EXP Subconscious] on_precompletion hook registered")
51
+
52
+
53
+ @get_driver().on_startup
54
+ async def _subconscious_startup() -> None:
55
+ from .runner import SubconsciousRunner # lazy to avoid circular import
56
+
57
+ cfg = (await DataManager().safe_get_config()).subconscious
58
+ if not cfg.enabled or not cfg.target_user_id:
59
+ logger.debug("[EXP Subconscious] Not enabled or no target user, skip")
60
+ return
61
+ runner = SubconsciousRunner(cfg)
62
+ _state.set_runner(runner)
63
+ await runner.start()
64
+ try:
65
+ await _setup_subconscious_hook()
66
+ except Exception as e:
67
+ logger.warning(f"[EXP Subconscious] Hook setup failed: {e}")
68
+
69
+
70
+ @get_driver().on_shutdown
71
+ async def _subconscious_shutdown() -> None:
72
+ runner = _state.get_runner()
73
+ if runner is not None:
74
+ runner.stop()
75
+ _state.set_runner(None)
@@ -0,0 +1,48 @@
1
+ """工作流 Node — 复用 Core Agent 框架。"""
2
+
3
+ from amrita_core.agent.context import StrategyContext
4
+ from amrita_core.chatmanager import ChatObject
5
+ from amrita_core.chatmanager.memory_limiter import MemoryLimiter
6
+ from amrita_core.contexts import AgentLoopState, WorkingState
7
+ from amrita_core.types import Message, SendMessageWrap
8
+ from amrita_core.utils import gather_usage
9
+ from amrita_sense import Node
10
+
11
+
12
+ @Node(tag="subconscious_limiting_memory")
13
+ async def LIMITING_MEMORY(chat_obj: ChatObject) -> None:
14
+ """运行 Core MemoryLimiter — 截断超限消息并生成摘要。
15
+
16
+ 等价于 chat_object.py 的 _limiting_memory,去掉了 SuspendEnum 暂停机制
17
+ (因为我们不使用 ChatObject 的流式暂停)。
18
+ """
19
+ mem_ctx = chat_obj._di_memory
20
+ input_ctx = chat_obj._di_input
21
+ ab = chat_obj._di_ability
22
+ resp = chat_obj._di_resp
23
+ if not ab.config.llm.enable_memory_abstract:
24
+ return
25
+ assert mem_ctx.memory is not None, "Memory must be loaded before limiting"
26
+ async with MemoryLimiter(mem_ctx.memory, input_ctx.train, config=ab.config) as lim:
27
+ await lim.run_enforce()
28
+ if abs_usage := lim.usage:
29
+ resp.extra_usage = gather_usage(resp.extra_usage, abs_usage)
30
+ mem_ctx.memory = lim.memory
31
+
32
+
33
+ @Node(tag="subconscious_strategy_init")
34
+ async def STRATEGY_INIT(
35
+ chat_obj: ChatObject, loop: AgentLoopState, wok: WorkingState
36
+ ) -> None:
37
+ """初始化策略上下文。
38
+
39
+ 等价于 chat_object.py 的 _run_strategy('agent' 分支)。
40
+ AGENT_ENTRY 紧随其后执行,agent.strategy(loop.stg_ctx) 实例化 ReActAgentStrategy。
41
+ """
42
+ assert wok.context_wrap is not None, "Context wrap must be built before strategy"
43
+ input_ctx = chat_obj._di_input
44
+
45
+ context = SendMessageWrap.validate_messages(
46
+ [input_ctx.train, Message(role="user", content=input_ctx.user_input)]
47
+ )
48
+ loop.stg_ctx = StrategyContext(input_ctx.user_input, context, chat_obj)