amrita_plugin_memory
基于向量数据库与 Loop Engineering 的长期记忆与知识库插件 — 双层架构:表层 Function Calling 记忆工具 + 底层事件驱动潜意识推理。
- 表层 — LLM 对话中按需调用记忆工具(读写删改列),ChromaDB 语义检索。
- 底层 — 用户发消息时触发后台 Agent 整理记忆库,基于 Core Agent 框架复用 ChatObject 管线。
安装
ambot plugin add amrita_plugin_memory
前置依赖:Python 3.11+ / AmritaBot 实例 / Ollama 或 OpenAI 嵌入服务 / ChromaDB。
快速开始
1. 配置环境变量(.env)
VECTOR_DB_TYPE=local
EMBEDDING_MODEL_URL=http://127.0.0.1:11434
EMBEDDING_MODEL_NAME=auto
EMBEDDING_PROCTOL=ollama-embed
2. 开启表层记忆
表层记忆开箱即用,无需额外配置。LLM 会在需要时自动调用 write_memory / read_memory 等工具。
3. 开启潜意识推理(可选)
编辑 config/amrita_plugin_memory/config.toml:
[subconscious]
enabled = true
target_user_id = "你的QQ号"
设置 target_user_id 为目标用户的 QQ 号,重启 Bot 即可。用户每次发消息后,后台 Agent 会在 30 分钟后自动整理记忆库。
适用场景:潜意识推理专为个人助理场景设计——单个 Bot 服务单个用户。它会在后台持续调用 LLM 进行记忆整理,每轮推理可能消耗数万 tokens。如果 Bot 服务于大量用户或对 token 成本敏感,建议保持
enabled = false。
如需允许 Agent 主动给用户发私聊消息,额外开启:
allow_send_to_user = true
如需关闭全局知识库以节省 token:
enable_knowledge = false
知识库依赖潜意识推理——当
enabled = false时,知识库也会自动禁用。
4. 验证
观察日志中 [Subconscious] 前缀的输出:
[Subconscious] Starting for user=你的QQ号
[Subconscious] Idle — waiting for user chat to trigger first run
用户发消息后约 30 分钟,会看到 Cycle #1 开始执行。
双层架构
flowchart TB
subgraph Surface["表层:Long-Term Memory Tools"]
S_LLM["对话 LLM"] -->|"write_memory"| S_Write["写入记忆"]
S_LLM -->|"read_memory"| S_Read["语义检索"]
S_LLM -->|"update/delete/list"| S_Mut["更新 / 删除 / 列表"]
S_Write --> ChromaDB[("ChromaDB<br/>向量数据库")]
S_Read --> ChromaDB
S_Mut --> ChromaDB
S_LLM -->|"knowledge_list/read/search"| ChromaDB
end
subgraph Subconscious["底层:Subconscious Reasoning"]
UserChat["用户发消息"] --> Hook["on_precompletion hook"]
Hook -->|"cancel_and_reschedule"| Scheduler["APScheduler<br/>指数惩罚延迟"]
Scheduler --> Runner["SubconsciousRunner"]
Runner --> ChatObject["ChatObject<br/>(容器)"]
ChatObject --> WF["Workflow 管线"]
WF --> LM["LIMITING_MEMORY<br/>Core MemoryLimiter"]
LM --> Build["BUILD_MESSAGE"]
Build --> AgentLoop["ReAct Agent Loop"]
AgentLoop -->|"工具调用"| STools["subconscious_* 工具"]
STools --> ChromaDB
AgentLoop --> KB["KnowledgeBaseManager<br/>文件 + JSON + ChromaDB"]
AgentLoop --> Profile["用户画像<br/>行级增量更新"]
end
Surface -->|"knowledge_suggest"| SuggQueue["知识建议队列"]
SuggQueue --> AgentLoop
Surface -.->|"共享 ChromaDB"| Subconscious
Runner -->|"持久化状态"| CachedRepo["CachedUserDataRepository"]
Runner -->|"usage 统计"| Insights["InsightsModel<br/>全局 Token 统计"]
功能
表层:长期记忆
| 功能 | 说明 |
|---|---|
| 语义检索 | ChromaDB 嵌入向量相似度搜索 |
| 分区隔离 | scope="user" 个人 / scope="group" 群共享 |
| 重要性 | low / medium / high 三级,支持过滤 |
| 标签分类 | 自定义标签(preference、project、personal 等) |
| 过期清理 | 短期 7 天 / 长期 90 天 |
| 并发安全 | 用户 ID 粒度 aiologic.Lock |
底层:潜意识推理
| 功能 | 说明 |
|---|---|
| 事件驱动 | 用户发消息触发,无活动则永远空闲 |
| 惩罚退避 | 连续触发时指数延长延迟(30min→45min→...→1440min) |
| 自动整理 | LLM 后台去重、合并、标签补全、低质清理 |
| 记忆压缩 | Core MemoryLimiter 截断超限 + 自动摘要 |
| 去重辅助 | subconscious_duplicate_helper 返回待整理记忆 + 合并指导 |
| 统计概览 | subconscious_get_memory_stats 总量/重要性/标签分布 |
| 膨胀感知 | ChromaDB 超 memory_warn_threshold 时注入压缩提示 |
| 滑动窗口 | max_abstracts 轮摘要保留,跨轮传递进度 |
| 用户画像 | 行级增量更新,Markdown 文件持久化 |
| Session 摘要 | MemoryLimiter 全量摘要 + LRU 缓存 |
| 主动消息 | LLM 向用户发起主动问候(需 allow_send_to_user) |
| Token 统计 | 复用 Bot InsightsModel 全局统计 |
共享:全局知识库
知识库是表层和潜意识双层共享的资源。读取操作(list/read/search)通过双重 @on_tools 注册,对话 LLM 和后台 Agent 均可直接调用。写入操作(create/update/delete)仅限潜意识 Agent——表层通过 knowledge_suggest 提交建议,由 Agent 在下一轮推理中审查后决定是否实际写入:
flowchart LR
ChatLLM["对话 LLM"] -->|"knowledge_suggest"| Queue["建议队列<br/>(持久化)"]
Queue -->|"subconscious_read_suggestions"| Agent["Subconscious Agent<br/>下一轮推理"]
Agent -->|"审查"| Decision{"值得记录?"}
Decision -->|"Yes"| SubCreate["subconscious_knowledge_create"]
Decision -->|"No"| Drop["忽略"]
每条知识由三个组件共同管理:
flowchart LR
subgraph File["Markdown 文件"]
direction TB
Title["# 标题"]
Summary["摘要文本"]
Sep["---"]
Body["正文内容"]
Title --> Summary --> Sep --> Body
end
subgraph Index["JSON 索引"]
KnowledgeIndex["knowledge_index.json<br/>[{kid, title, summary, ...}]"]
end
subgraph Vector["ChromaDB"]
Embedding["{kid → embedding(summary)}"]
end
File <-->|"解析/写入"| Index
File <-->|"向量化/搜索"| Vector
Index <-->|"校验/修复"| Vector
文件格式:第一行 # 标题,然后摘要文本,--- 之后是正文。框架自动管理分割——LLM 只需传 title/summary/body 三个字段,无需手动处理 ---。摘要被向量化存入 ChromaDB 用于语义搜索,正文存在文件中支持按行分段读取。
启动自修复(validate_on_startup):启动时计算三方 ID 集合的差集,自动修复四种不一致:
| 场景 | 检测 | 修复 |
|---|---|---|
| 孤文件 | 文件在,JSON 索引无 | 解析文件追加到索引 + 向量化 |
| 孤索引 | JSON 在,文件无 | 从索引中删除 + 清理向量 |
| 缺向量 | JSON+文件都在,ChromaDB 缺失 | 从摘要重新向量化写入 |
| 悬空向量 | 向量在,JSON 索引无 | 从 ChromaDB 删除 |
行级读取:knowledge_read 支持 start_line/end_line 参数——LLM 可以用滑动窗口分段读取长知识,避免一次加载超长内容。knowledge_search 只匹配摘要向量,找到相关条目后再用 knowledge_read 按需拉取正文。
工具参考
表层工具(tools.py)
| 工具 | 参数 |
|---|---|
write_memory |
content, tags, importance(enum), scope(enum) |
read_memory |
query, top_k(5), importance?, scope(enum) |
update_memory |
id, scope, content?, tags?, importance? |
delete_memory |
id, scope |
list_memory |
limit, scope |
knowledge_list |
— |
knowledge_read |
kid, start_line?, end_line? |
knowledge_search |
query, top_k? |
knowledge_suggest |
action, title, summary, body, reason |
潜意识工具(rethinking/tools.py)
记忆和 session/画像工具注册在隔离的 _SUBCONSCIOUS_TOOLS 上。知识库中 list/read/search 通过双重注册同时暴露给表层和潜意识;create/update/delete 仅潜意识可用(表层通过 knowledge_suggest 提交建议):
| 工具 | 用途 |
|---|---|
subconscious_read_memory |
语义检索 |
subconscious_write_memory |
写入新记忆 |
subconscious_update_memory |
更新指定 ID 记忆 |
subconscious_delete_memory |
删除指定 ID 记忆 |
subconscious_list_memory |
列出全部记忆 |
subconscious_iter_stop |
结束本轮推理 |
subconscious_send_to_user |
主动向用户发消息 |
subconscious_read_chat_context |
读取最近聊天记录 |
subconscious_duplicate_helper |
去重辅助(返回记忆 + 合并指导 prompt) |
subconscious_get_memory_stats |
统计概览 |
subconscious_knowledge_list |
列出全局知识条目 |
subconscious_knowledge_read |
读取知识条目(支持行滑动) |
subconscious_knowledge_create |
创建知识条目 |
subconscious_knowledge_update |
更新知识条目 |
subconscious_knowledge_delete |
删除知识条目 |
subconscious_read_suggestions |
读取待审查的知识建议(读取后清空) |
subconscious_knowledge_search |
语义搜索知识库 |
subconscious_read_sessions |
读取归档 sessions(LLM 摘要) |
subconscious_get_profile |
读取用户画像(行滑动窗口) |
subconscious_update_profile |
增量更新用户画像 |
持久化与状态恢复
潜意识推理的状态跨重启持久化,确保 Bot 重启后不丢失进度。
持久化存储
| 存储 | 技术 | 存什么 |
|---|---|---|
| Runner 元状态 | CachedUserDataRepository(uid=amrita_memory) |
total_runs、last_abstracts(最近 N 轮摘要)、pending_messages(待发送消息队列) |
| 当前摘要 | 同上 memory_json.abstract |
最新一轮 MemoryLimiter 产出的摘要,注入 Jinja2 模板 <SUMMARY> |
| Session 摘要缓存 | LRUCache[int, str](最大 128 条) |
session DB id → LLM 生成的摘要文本,避免重复调用 MemoryLimiter |
| 惩罚计数器 | 内存(不持久化) | _penalty_count:重启后从 0 开始,等价于"新鲜启动" |
| 用户画像 | data/user_profile.md |
Markdown 文件,summary---body 格式,行级增量更新 |
| 全局知识库 | data/knowledge/ + knowledge_index.json + ChromaDB |
三方同步管理 |
| Token 统计 | InsightsModel(复用 Bot ORM) |
全局 prompt/completion token 累加 |
生命周期
flowchart TD
Startup["Bot 启动"] --> Load["_load_from_repo()<br/>恢复 total_runs / last_abstracts / pending_messages"]
Load --> KB["KnowledgeBaseManager<br/>init() + validate_on_startup()"]
KB --> Idle["进入空闲<br/>等待用户聊天触发"]
Idle -->|用户发消息| Cancel["cancel_and_reschedule()"]
Cancel -->|延迟到达| Run["_run() ReAct 循环"]
Run --> Save1["_save_to_repo()"]
Run --> Save2["_save_pending_to_repo()"]
Save1 & Save2 --> Check{"都成功?"}
Check -->|Yes| Reset["reset_penalty()"]
Check -->|No| Keep["惩罚不重置<br/>(重试保护)"]
Reset --> Idle
Keep --> Idle
跨重启连续性:last_abstracts 通过 Jinja2 模板 {{ last_abstracts }} 和 {{ last_run }} 注入 prompt,让 Agent 知道"上一轮做了什么"——即使 Bot 重启,推理上下文也能部分延续。
惩罚计数器不持久化:重启后从 0 开始。设计意图:重启本身就是一次完整的"冷启动",已有的记忆整理结果已经通过 ChromaDB 持久化了,不需要保留旧的退避状态。
调度策略
事件驱动 + 指数惩罚退避。不使用定时自循环——只有目标用户发消息时才触发推理。
用户每次聊天 → 取消现有计划 → 惩罚计数 +1 → 重新计算延迟:
$$\text{delay} = \min(\text{base} \times \text{multiplier}^{\text{penalty}-1},\ \text{cap})$$
默认参数:base=30min,multiplier=1.5,cap=1440min。推理成功后惩罚重置为 0。用户连续聊天会自动推开推理,长时间沉默后恢复正常频率。
Workflow 管线
SubconsciousRunner 将 ChatObject 作为数据容器,注入自定义 SubconsciousBackend + Core ReActAgentStrategy:
flowchart TD
LOAD_STATE --> JINJA2_RENDER --> LIMITING_MEMORY --> BUILD_MESSAGE --> REACT_BLOCK
LIMITING_MEMORY 在 Agent Loop 之前运行 Core MemoryLimiter:消息截断 → 摘要生成。_build_config() 将 enable_memory_compress 和 loop_detect_threshold 注入 AmritaConfig。
技术栈
| 组件 | 技术 |
|---|---|
| 后端框架 | Python 3.10+ / NoneBot2 / AmritaCore / AmritaSense |
| 向量数据库 | ChromaDB(PersistentClient / HttpClient) |
| 嵌入模型 | OpenAI Embedding / Ollama Embedding |
| 调度引擎 | nonebot_plugin_apscheduler(date trigger) |
| 持久化 | CachedUserDataRepository |
| Token 统计 | InsightsModel(复用 Bot 全局 usage) |
| 缓存 | nonebot_plugin_amrita.cache.LRUCache |
| 配置管理 | Pydantic + TOML |
| 代码质量 | Ruff + Pyright |
配置详解
config/amrita_plugin_memory/config.toml
# 记忆过期
short_term_expiry_days = 7
long_term_expiry_days = 90
per_session_memory_limit = 50
# 常驻推理循环
[subconscious]
enabled = false
target_user_id = ""
allowed_tools = []
max_iterations = 10
loop_detect_threshold = 3
rethink_base_delay_minutes = 30
rethink_penalty_multiplier = 1.5
rethink_max_delay_minutes = 1440
prompt_file = "prompt/subconscious_main.md.jinja2"
prompt_send_file = "prompt/subconscious_send.md.jinja2"
prompt_knowledge_file = "prompt/knowledge_guide.md.jinja2"
prompt_profile_file = "prompt/profile_guide.md.jinja2"
enable_memory_compress = true
allow_send_to_user = false
memory_warn_threshold = 100
max_abstracts = 5
knowledge_max_chars = 10000
knowledge_collection_name = "amrita_global_knowledge"
enable_knowledge = true
| 字段 | 默认值 | 说明 |
|---|---|---|
enabled |
false |
是否启用潜意识循环 |
target_user_id |
"" |
目标用户 ID,为空则不启动 |
allowed_tools |
[] |
额外可用工具(从全局拉取) |
max_iterations |
10 |
单轮 ReAct 最大步数(预留) |
loop_detect_threshold |
3 |
传入 Core loop_reasoning_trigger |
rethink_base_delay_minutes |
30 |
惩罚退避基数 |
rethink_penalty_multiplier |
1.5 |
惩罚指数倍率 |
rethink_max_delay_minutes |
1440 |
惩罚延迟上限(1 天) |
prompt_file |
"prompt/subconscious_main.md.jinja2" |
主推理提示词模板 |
prompt_send_file |
"prompt/subconscious_send.md.jinja2" |
主动消息生成模板 |
prompt_knowledge_file |
"prompt/knowledge_guide.md.jinja2" |
知识库使用指南 |
prompt_profile_file |
"prompt/profile_guide.md.jinja2" |
画像构建指南 |
enable_memory_compress |
true |
传入 Core enable_memory_abstract |
allow_send_to_user |
false |
允许 Agent 主动发私聊消息 |
memory_warn_threshold |
100 |
ChromaDB 超此数量注入压缩提示 |
max_abstracts |
5 |
摘要滑动窗口大小 |
knowledge_max_chars |
10000 |
知识条目单条正文上限 |
knowledge_collection_name |
"amrita_global_knowledge" |
ChromaDB 知识库 collection |
enable_knowledge |
true |
是否启用全局知识库(需 enabled=true) |
提示词模板
4 个 Jinja2 模板文件位于 config/amrita_plugin_memory/prompt/,首次缺失时自动从默认值创建:
| 文件 | 用途 | 变量 |
|---|---|---|
subconscious_main.md.jinja2 |
主推理提示词 | character_prompt, last_run, last_abstracts, current_time, target_user_id, total_runs |
subconscious_send.md.jinja2 |
主动消息生成 | character_prompt, intent, memory_context, current_time |
knowledge_guide.md.jinja2 |
知识库指南 | current_time, target_user_id |
profile_guide.md.jinja2 |
画像构建指南 | current_time, target_user_id |
环境变量(.env)
| 变量 | 类型 | 默认值 | 说明 |
|---|---|---|---|
VECTOR_DB_TYPE |
local/remote | local | ChromaDB 类型 |
VECTOR_DB_SERVER |
string | 127.0.0.1 | 远程地址 |
VECTOR_DB_PORT |
int | 8000 | 远程端口 |
VECTOR_DB_SERVER_SSL |
bool | false | 远程 SSL |
VECTOR_DB_REMOTE_HEADERS |
dict | {} | 远程请求头 |
VECTOR_DB_TENANT |
string | default | 租户 |
VECTOR_DB_DATABASE |
string | default | 数据库 |
EMBEDDING_MODEL_URL |
string | http://127.0.0.1:11434 | 嵌入模型地址 |
EMBEDDING_MODEL_NAME |
string | auto | 模型名 |
EMBEDDING_PROCTOL |
openai/ollama-embed | ollama-embed | 协议 |
EMBEDDING_MODEL_API_KEY |
string | (空) | API Key |
项目结构
amrita_plugin_memory/
├── config.py # 配置模型(SubconsciousConfig / ConfigFile / EnvConfig)
├── tools.py # 表层记忆工具(write/read/update/delete/list)
├── vector.py # ChromaDB 封装(AsyncUserMemory / 锁池)
├── embed.py # 嵌入模型
├── matchers.py # 匹配器
├── rethink/ # 潜意识推理子系统
│ ├── _state.py # 模块级状态(runner / penalty / pending / 隔离工具管理器)
│ ├── types.py # TypedDict 定义(7 个数据结构)
│ ├── consts.py # 默认 Prompt 模板 / ensure_prompt_file / load_character_prompt
│ ├── backend.py # SubconsciousBackend — 隔离的工具 + memory 后端
│ ├── nodes.py # LIMITING_MEMORY 工作流节点
│ ├── runner.py # SubconsciousRunner — 核心编排器
│ ├── hooks.py # on_precompletion hook / 生命周期管理
│ ├── tools.py # 19 个潜意识工具 handler
│ ├── schemas.py # 19 个 FunctionDefinitionSchema
│ └── knowledge.py # KnowledgeBaseManager — 三方同步知识库
config/amrita_plugin_memory/
├── config.toml # 插件配置文件
└── prompt/ # Jinja2 提示词模板
├── subconscious_main.md.jinja2
├── subconscious_send.md.jinja2
├── knowledge_guide.md.jinja2
├── profile_guide.md.jinja2
└── README.md
data/amrita_plugin_memory/
├── vector_db.chroma/ # ChromaDB 持久化
├── knowledge/ # 知识文件(KNOWLEDGE_*.md)
├── knowledge_index.json # 知识索引
└── user_profile.md # 用户画像
开发
uv sync # 安装依赖
ruff check . # 代码检查
pyright # 类型检查
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file amrita_plugin_memory-0.3.1.tar.gz.
File metadata
- Download URL: amrita_plugin_memory-0.3.1.tar.gz
- Upload date:
- Size: 67.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1e49f98a8cde25056ab8509165ecb312977ed2214bafec8ac52134148b8705ad
|
|
| MD5 |
b9c48a12d35312df6f00707b154fb121
|
|
| BLAKE2b-256 |
498b3a0da2c080e642d3f7472295319645286d6d58c26427d4a9d9b8d1b78fb7
|
File details
Details for the file amrita_plugin_memory-0.3.1-py3-none-any.whl.
File metadata
- Download URL: amrita_plugin_memory-0.3.1-py3-none-any.whl
- Upload date:
- Size: 67.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.32 {"installer":{"name":"uv","version":"0.11.32","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c2df421ca4a43d4a15a3f256ca8f1e9c07ef25ddd89112a9de8f6ca9fff71137
|
|
| MD5 |
e8c4aa8479fa142b11c0de460b49a477
|
|
| BLAKE2b-256 |
7b2d479a5df431b121c73a853a44e5a8407359663fe6eddc0da94fc637f79345
|