|
|
|
|
# mem0-integration Skill
|
|
|
|
|
|
|
|
|
|
## 功能概述
|
|
|
|
|
|
|
|
|
|
为 OpenClaw 提供基于 mem0 + Qdrant 的对话记忆系统 (Memory Layer 4),包括:
|
|
|
|
|
|
|
|
|
|
- Pre-Hook 语义检索注入(对话前自动召回相关记忆)
|
|
|
|
|
- Post-Hook 异步写入(对话后智能筛选并存储记忆)
|
|
|
|
|
- 三级可见性隔离 (public / project / private)
|
|
|
|
|
- 记忆衰减 (expiration_date: 7d / 30d / permanent)
|
|
|
|
|
- 智能写入过滤(跳过无价值对话)
|
|
|
|
|
- Layer 3 FTS5 本地全文检索 fallback(Qdrant 不可达时接管)
|
|
|
|
|
- 冷启动记忆预加载(新会话自动注入最近上下文)
|
|
|
|
|
|
|
|
|
|
> 架构全景文档: `docs/MEMORY_ARCHITECTURE.md`
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 文件结构
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
skills/mem0-integration/
|
|
|
|
|
├── SKILL.md # 本文档 — 开发者参考
|
|
|
|
|
├── openclaw.plugin.json # OpenClaw 插件声明 (lifecycle hook)
|
|
|
|
|
├── skill.json # Skill 元数据
|
|
|
|
|
├── index.js # JS 入口,桥接 OpenClaw Gateway ↔ Python
|
|
|
|
|
│
|
|
|
|
|
│ ── 核心运行时 ──
|
|
|
|
|
├── mem0_client.py # 核心客户端:初始化、检索、写入、队列、缓存
|
|
|
|
|
├── openclaw_interceptor.py # Pre/Post-Hook 拦截器(Gateway 调用入口)
|
|
|
|
|
├── session_init.py # 冷启动记忆预加载
|
|
|
|
|
│
|
|
|
|
|
│ ── 配置 ──
|
|
|
|
|
├── config.yaml # mem0 全局配置(Qdrant / LLM / Embedder / Cache)
|
|
|
|
|
├── project_registry.yaml # Agent-项目归属关系(决定 project 级可见性)
|
|
|
|
|
│
|
|
|
|
|
│ ── 辅助工具 ──
|
|
|
|
|
├── local_search.py # Layer 3: SQLite FTS5 本地全文检索 fallback
|
|
|
|
|
├── memory_cleanup.py # 月度记忆统计与清理脚本
|
|
|
|
|
├── migrate_to_single_collection.py # 从旧多 Collection 迁移到单库融合架构
|
|
|
|
|
├── recover_memories.py # 记忆恢复工具 v1
|
|
|
|
|
├── recover_memories_v2.py # 记忆恢复工具 v2
|
|
|
|
|
│
|
|
|
|
|
│ ── 旧版 / 命令 ──
|
|
|
|
|
├── commands.py # /memory 命令处理
|
|
|
|
|
├── openclaw_commands.py # OpenClaw 原生命令扩展
|
|
|
|
|
├── mem0_integration.py # 旧版集成入口(已被 mem0_client.py 取代)
|
|
|
|
|
│
|
|
|
|
|
│ ── 测试 ──
|
|
|
|
|
├── test_mem0.py # mem0 单元测试
|
|
|
|
|
├── test_integration.py # 集成测试
|
|
|
|
|
└── test_production.py # 生产环境测试
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 环境变量
|
|
|
|
|
|
|
|
|
|
### API 端点与密钥(优先级链)
|
|
|
|
|
|
|
|
|
|
| 变量名 | 用途 | 优先级 | 来源 |
|
|
|
|
|
|--------|------|--------|------|
|
|
|
|
|
| `LLM_BASE_URL` | LLM/Embedding API 端点 | **最高** | `.env`(OneAPI 网关) |
|
|
|
|
|
| `LLM_API_KEY` | API 密钥 | **最高** | `.env`(OneAPI token) |
|
|
|
|
|
| `OPENAI_BASE_URL` | 备选端点(已有环境变量) | 次之 | — |
|
|
|
|
|
| `MEM0_DASHSCOPE_API_KEY` | 备选密钥(DashScope 直连) | 次之 | — |
|
|
|
|
|
| `DASHSCOPE_API_KEY` | 备选密钥 | 最低 | — |
|
|
|
|
|
|
|
|
|
|
代码在模块加载时自动按优先级链解析,设置 `OPENAI_API_BASE` / `OPENAI_BASE_URL` / `OPENAI_API_KEY`,供 mem0 SDK 读取。**当前生产配置由 `.env` 中的 `LLM_BASE_URL` + `LLM_API_KEY` 驱动,指向本地 OneAPI 网关。**
|
|
|
|
|
|
|
|
|
|
### 模型配置
|
|
|
|
|
|
|
|
|
|
| 变量名 | 用途 | 优先级 | 默认值 |
|
|
|
|
|
|--------|------|--------|--------|
|
|
|
|
|
| `MEM0_LLM_MODEL` | 记忆提取/合并用 LLM | 最高 | — |
|
|
|
|
|
| `LLM_MODEL_ID` | 备选 LLM 模型名 | 次之 | `qwen-plus` |
|
|
|
|
|
| `MEM0_EMBEDDER_MODEL` | Embedding 模型名 | 最高 | — |
|
|
|
|
|
| `EMBEDDING_MODEL_ID` | 备选 Embedding 模型名 | 次之 | `text-embedding-v4` |
|
|
|
|
|
|
|
|
|
|
**当前生产值**(来自 `.env`):LLM = `MiniMax-M2.5`,Embedder = `text-embedding-v4`(1024 维)。
|
|
|
|
|
|
|
|
|
|
### Qdrant 连接
|
|
|
|
|
|
|
|
|
|
| 变量名 | 用途 | 默认值 |
|
|
|
|
|
|--------|------|--------|
|
|
|
|
|
| `MEM0_QDRANT_HOST` | Qdrant 地址 | `localhost` |
|
|
|
|
|
| `MEM0_QDRANT_PORT` | Qdrant 端口 | `6333` |
|
|
|
|
|
|
|
|
|
|
### 两个模型的分工
|
|
|
|
|
|
|
|
|
|
| 模型 | 用途 | 调用时机 |
|
|
|
|
|
|------|------|----------|
|
|
|
|
|
| **LLM**(MiniMax-M2.5) | 从对话中**提取有价值的记忆**,去重/合并已有记忆,生成结构化摘要 | Post-Hook 写入时(后台异步) |
|
|
|
|
|
| **Embedder**(text-embedding-v4) | 将文本转成 1024 维向量,写入 Qdrant;Pre-Hook 检索时同样用于向量化查询 | 读写均需 |
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 核心模块说明
|
|
|
|
|
|
|
|
|
|
### mem0_client.py — 核心客户端
|
|
|
|
|
|
|
|
|
|
**类: `Mem0Client`**
|
|
|
|
|
- `_init_memory()` — 初始化 mem0 Memory 实例(Qdrant + DashScope Embedder 1024 维)
|
|
|
|
|
- `start()` — 启动异步写入队列的后台 worker(必须在 event loop 中调用)
|
|
|
|
|
- `pre_hook_search()` — Pre-Hook: 三阶段检索(public → project → private → legacy fallback),带缓存和超时
|
|
|
|
|
- `post_hook_add()` — Post-Hook: 智能过滤 + 自动分类(memory_type / visibility)+ 入队
|
|
|
|
|
- `_execute_write()` — 后台异步写入 Qdrant,附带 metadata 和 expiration_date
|
|
|
|
|
|
|
|
|
|
**类: `AsyncMemoryQueue`**
|
|
|
|
|
- 基于 `collections.deque` 的有界异步队列
|
|
|
|
|
- 后台 worker 每秒轮询,批量处理
|
|
|
|
|
|
|
|
|
|
**全局实例:** `mem0_client = Mem0Client()` — 模块加载时自动创建
|
|
|
|
|
|
|
|
|
|
### openclaw_interceptor.py — 拦截器
|
|
|
|
|
|
|
|
|
|
Gateway 调用入口。从 `context` dict 中提取 `user_id`、`agent_id`、`visibility`、`project_id`、`memory_type`,桥接到 `mem0_client`。
|
|
|
|
|
|
|
|
|
|
### local_search.py — Layer 3 FTS5 Fallback
|
|
|
|
|
|
|
|
|
|
基于 SQLite FTS5 的本地全文检索,Qdrant 不可达时接管。
|
|
|
|
|
- CJK 字符逐字拆分 + ASCII 单词保持完整(过滤标点噪音)
|
|
|
|
|
- 每个 Agent 维护独立的 FTS5 索引文件
|
|
|
|
|
- `rebuild_index()` 扫描 MEMORY.md + memory/*.md + 共享核心文件
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 开发者注意事项
|
|
|
|
|
|
|
|
|
|
### mem0 filter 格式
|
|
|
|
|
|
|
|
|
|
mem0 `search()` 的 `filters` 参数使用**扁平 dict**,多个条件为隐式 AND:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
# 正确: 扁平 dict (mem0 Python SDK)
|
|
|
|
|
filters={"visibility": "private", "agent_id": "main"}
|
|
|
|
|
|
|
|
|
|
# 错误: 嵌套 AND (Qdrant 原生 API 语法,mem0 不支持)
|
|
|
|
|
filters={"AND": [{"visibility": "private"}, {"agent_id": "main"}]}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
直接操作 Qdrant (如 `memory_cleanup.py`) 时使用原生 Filter 对象:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
from qdrant_client.models import Filter, FieldCondition, MatchValue
|
|
|
|
|
Filter(must=[FieldCondition(key="visibility", match=MatchValue(value="public"))])
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### mem0 add() 的 agent_id
|
|
|
|
|
|
|
|
|
|
`mem0.add()` 必须同时传递 `agent_id` 作为顶层参数和 metadata 字段:
|
|
|
|
|
|
|
|
|
|
```python
|
|
|
|
|
self.local_memory.add(
|
|
|
|
|
messages=messages,
|
|
|
|
|
user_id=user_id,
|
|
|
|
|
agent_id=agent_id, # 顶层: mem0 内部索引用
|
|
|
|
|
metadata={
|
|
|
|
|
"agent_id": agent_id, # metadata: 自定义 filter 查询用
|
|
|
|
|
"visibility": "private",
|
|
|
|
|
...
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
原因: mem0 的 `search(agent_id=...)` 匹配顶层字段;`search(filters={"agent_id": ...})` 匹配 metadata 字段。两处都写入确保两种检索路径均能命中。
|
|
|
|
|
|
|
|
|
|
### FTS5 中文分词
|
|
|
|
|
|
|
|
|
|
`local_search.py` 使用字符级分词(非 jieba),仅保留 CJK 统一表意文字 (U+4E00–U+9FFF) 和 ASCII 字母数字:
|
|
|
|
|
|
|
|
|
|
- 输入 `"你好,world!"` → 输出 `"你 好 world"`
|
|
|
|
|
- 标点、emoji、特殊符号被过滤,避免 FTS5 索引噪音
|
|
|
|
|
- 搜索精度低于 jieba 词级分词,但零依赖、零内存开销
|
|
|
|
|
|
|
|
|
|
### 可见性自动分类
|
|
|
|
|
|
|
|
|
|
`_classify_visibility()` 返回 `(visibility, project_id)` 元组,支持三级自动推断:
|
|
|
|
|
|
|
|
|
|
1. **public**:对话含 `所有人 / 通知 / 全局 / 公告 / 大家 / 集群` 等关键词
|
|
|
|
|
2. **project**:对话匹配 `project_registry.yaml` 中 agent 所属项目的 name/description 关键词(如 `广告素材`→`advert`,`情感陪伴`→`life`)
|
|
|
|
|
3. **private**:默认回退
|
|
|
|
|
|
|
|
|
|
项目关键词在首次调用时从 `project_registry.yaml` 动态提取并缓存,修改注册表后重启生效。
|
|
|
|
|
|
|
|
|
|
### 记忆写入过滤规则
|
|
|
|
|
|
|
|
|
|
以下对话自动跳过写入:
|
|
|
|
|
1. 用户消息长度 < 5 字符
|
|
|
|
|
2. 匹配 SKIP_PATTERNS: 好的、收到、OK、嗯、行、没问题、感谢、谢谢 等
|
|
|
|
|
3. 以 `/` 开头的系统命令
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 命令
|
|
|
|
|
|
|
|
|
|
通过 Telegram 使用:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
/memory add <内容> # 手动添加记忆
|
|
|
|
|
/memory search <关键词> # 搜索记忆
|
|
|
|
|
/memory list # 列出所有记忆
|
|
|
|
|
/memory delete <ID> # 删除记忆
|
|
|
|
|
/memory status # 查看状态
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 依赖
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
mem0ai # 核心记忆管理
|
|
|
|
|
qdrant-client # Qdrant 向量数据库客户端
|
|
|
|
|
pyyaml # YAML 配置解析
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
## 更新记录
|
|
|
|
|
|
|
|
|
|
### v2.2 (2026-03-16)
|
|
|
|
|
- **架构**: mem0 LLM + Embedder 全面切换到 OneAPI 网关路由,不再直连 DashScope
|
|
|
|
|
- **环境变量**: API 端点和密钥改为优先读取 `LLM_BASE_URL` / `LLM_API_KEY`(与 OpenClaw Agent 配置对齐);同时支持 `LLM_MODEL_ID` / `EMBEDDING_MODEL_ID` 覆盖模型名
|
|
|
|
|
- **Layer 3 自动 Fallback**: Qdrant 不可达时(`_init_memory()` 失败或所有检索 phase 均报错),自动切换到 `LocalSearchFallback` FTS5 本地检索,无需手动干预
|
|
|
|
|
- **可见性自动分类**: `_classify_visibility()` 现支持三级推断(public / project / private),从 `project_registry.yaml` 动态提取项目关键词实现 project 级自动归档
|
|
|
|
|
- **memory_cleanup.py 修复**: 修复死代码导致 per-type 保留策略失效;修复过期判断使用 `expiration_date` 而非 `timestamp`;修复 `PointIdsList` 调用格式;移除无关的 DashScope 初始化
|
|
|
|
|
- **自动清理 cron**: `/etc/cron.d/mem0-cleanup` 每天凌晨 3:00 UTC 自动执行 `--execute`,`expiration_date` 过期强制执行
|
|
|
|
|
|
|
|
|
|
### v2.1 (2026-03-01)
|
|
|
|
|
- 修复: `_execute_search` filter 格式从 Qdrant 嵌套语法改为 mem0 扁平 dict
|
|
|
|
|
- 修复: `_execute_write` 补充 `agent_id` 顶层参数
|
|
|
|
|
- 修复: `session_init.py` 补充 `OPENAI_API_BASE` 环境变量
|
|
|
|
|
- 修复: `local_search.py` 中文分词过滤标点噪音
|
|
|
|
|
- 清理: 移除未使用的 import (Optional, os, re)
|
|
|
|
|
|
|
|
|
|
### v2.0 (2026-02-28)
|
|
|
|
|
- 新增: 三级可见性 (public / project / private) + 三阶段检索
|
|
|
|
|
- 新增: 记忆衰减 (expiration_date)
|
|
|
|
|
- 新增: 智能写入过滤
|
|
|
|
|
- 新增: 项目注册表 (project_registry.yaml)
|
|
|
|
|
- 新增: Layer 3 SQLite FTS5 本地全文检索
|
|
|
|
|
- 新增: 月度清理脚本 (memory_cleanup.py)
|
|
|
|
|
- 安全: 所有 API Key 改为环境变量
|
|
|
|
|
|
|
|
|
|
### v1.0 (2026-02-22)
|
|
|
|
|
- 初始版本: mem0 + Qdrant 基础集成
|