OpenClaw + Mem0 (OSS 模式) 部署踩坑实录
背景: 在 macOS (Apple Silicon) 上将
@mem0/openclaw-mem0插件以 open-source 模式集成到 OpenClaw Gateway,使用自建 API 代理 (api.qadmlee.com) 替代 OpenAI 官方 API。耗时: 约 10 小时(2026-02-20 全天)
最终状态: SDK 独立测试 add/search 通过 ✅,OpenClaw Gateway 集成仍有 baseURL 透传 bug 🔧
目录
- 阶段一:Mem0 API Server 容器化部署
- 阶段二:OpenClaw 插件安装与 SQLite Binding 地狱
- 阶段三:pgvector → Qdrant 迁移
- 阶段四:SQLITE_CANTOPEN 崩溃与 Shim 方案
- 阶段五:401 Unauthorized — baseURL 被吞掉
- 元坑:AI 辅助调试的系统性陷阱
- 替代方案:OpenMemory (CaviraOSS)
- 坑点速查表
- 最终架构
阶段一:Mem0 API Server 容器化部署
坑 1:官方 Docker 镜像缺 psycopg 依赖
现象:mem0/mem0-api-server:latest 启动后连接 PostgreSQL 报 ModuleNotFoundError: No module named 'psycopg'
根因:官方镜像只预装了 SQLite 驱动,未包含 PostgreSQL 的 psycopg3 库和系统级 libpq-dev。
解决:自定义 Dockerfile 补装依赖:
FROM mem0/mem0-api-server:latest
RUN apt-get update && apt-get install -y --no-install-recommends libpq-dev \
&& pip install --no-cache-dir "psycopg[binary,pool]" psycopg2-binary \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /app/history
踩坑次数:3 轮 Dockerfile 重建(第一次漏 libpq-dev,第二次漏 psycopg2-binary,第三次漏 /app/history 目录)。
坑 2:SQLite history 路径不存在
现象:sqlite3.OperationalError: unable to open database file(Python 端)
根因:mem0 的 Memory.from_config() 默认把 SQLite history 数据库写到 /app/history/,但容器内该目录不存在。
解决:Dockerfile 里 RUN mkdir -p /app/history,docker-compose 挂载 volume 持久化。
坑 3:Embedding 维度不匹配 (1536 vs 1024)
现象:
psycopg.errors.DataException: expected 1536 dimensions, not 1024
根因:pgvector 表按 OpenAI 默认 1536 维创建,但实际使用的 text-embedding-qwen3-embedding-0.6b 模型输出 1024 维。
解决:
.env加EMBEDDER_DIMS=1024docker volume rm mem0-server_pgdata删掉旧 PG 数据- 重启让 mem0 以 1024 维重建表
二次踩坑:第一次「以为改了」但远程 .env 实际没同步,日志暴露 1536 仍在。教训:改完配置后一定看启动日志确认参数生效。
坑 4:pgdata Volume 没有真正删除
现象:反复重启后维度错误依旧。
根因:docker compose down 不删 volume,需要 docker compose down -v 或手动 docker volume rm。Volume 名可能带项目前缀(mem0-server_pgdata vs mem0_server_pgdata)。
解决:docker volume ls | grep pg 确认实际名称后精准删除。
阶段二:OpenClaw 插件安装与 SQLite Binding 地狱
坑 5:jiti 的 __dirname 路径不到 sqlite3 native binary
现象:
openclaw-mem0: recall failed: Error: Could not locate the bindings file.
→ /opt/homebrew/lib/node_modules/openclaw/node_modules/jiti/build/node_sqlite3.node
根因:OpenClaw 用 jiti(TypeScript 运行时)加载插件,jiti 把 __dirname 解析到自己的目录而非 sqlite3 包的目录,导致 node-pre-gyp 的路径查找全部失败。这是 jiti 自身的 bug,与 Node.js 版本无关。
排查历程:
- ❌
npm rebuild sqlite3— sqlite3 被编译到了插件目录,但 jiti 仍然看不到 - ❌ 尝试 symlink hack — 在 OpenClaw 的
node_modules/中找不到原始.node文件 - ❌ 降级 Node.js v25 → v22 LTS — 编解决了 ABI 兼容但 jiti 路径问题依旧
- ✅ 最终发现:native binary 在插件目录
/Users/qadmlee/.openclaw/extensions/openclaw-mem0/node_modules/sqlite3/build/Release/node_sqlite3.node,但 jiti 的路径解析逻辑永远不会找到这里
解决:见阶段四的 SQLite Shim 方案。
坑 6:Node.js v25 缺少预编译 Binary
现象:npm rebuild sqlite3 在 v25 上执行后仍无 .node 文件。
根因:Node.js v25 (ABI v141) 太新,sqlite3 / better-sqlite3 没有该版本的预编译 binary,需要手动 node-gyp rebuild。
解决:降级到 Node.js v22 LTS (ABI v127),重新 npm install -g openclaw。
二次踩坑:降级后旧 Gateway 进程(pid 78238)仍以 v25 运行,日志仍显示 runtimeVersion: 25.6.1。必须 kill -9 <pid> + launchctl bootout 彻底杀掉旧进程。
坑 7:openclaw gateway restart 并非原子操作
现象:openclaw gateway restart 后日志里大量 Gateway already running (pid 78238); lock timeout,循环 10 秒一次持续数十次。
根因:restart = stop + start,但 stop 通过 launchctl 异步执行,进程未完全退出时 start 就尝试绑定端口,被占用后 10s 重试。
解决:
openclaw gateway stop
sleep 3
launchctl bootout gui/$(id -u) ~/Library/LaunchAgents/ai.openclaw.gateway.plist
sleep 2
openclaw gateway install
openclaw gateway start
阶段三:pgvector → Qdrant 迁移
坑 8:Node.js SDK 不支持 pgvector
现象:
openclaw-mem0: recall failed: Error: Unsupported vector store provider: pgvector
根因:mem0 的 Node.js SDK(mem0ai/oss)只支持 Qdrant 作为向量存储,不支持 pgvector。 Python SDK 支持 pgvector,但 OpenClaw 的 @mem0/openclaw-mem0 插件用的是 Node.js SDK。
这是整个项目的关键转折点。 Python 端验证了 pgvector 全链路通过,但 OpenClaw 插件根本用不了。
解决:
docker-compose.yml加 Qdrant 容器openclaw.json的vectorStore.provider改为qdrant- Python 端的
main.py同步改回 Qdrant
坑 9:Qdrant Client/Server 版本不兼容
现象:
Client version 1.13.0 is incompatible with server version 1.17.0
根因:mem0ai 捆绑的 @qdrant/js-client-rest 是 1.13.0,默认拉取的 qdrant/qdrant:latest 是 1.17.0。客户端要求 minor 版本差不超过 1。
解决:固定 Docker 镜像版本为 qdrant/qdrant:v1.13.6(注意 必须带 v 前缀,不带会报 manifest unknown)。
坑 10:Qdrant Collection 维度又是 1536
现象:dimension → SDK 创建 collection 时使用默认 1536 维,searchPoints 返回 400。
根因:Node.js SDK 的 Qdrant provider 接受 dimension 字段(不是 embeddingModelDims),但代码 this.dimension = config.dimension || 1536 在某些路径下被忽略。
解决:预先手动创建正确维度的 collection:
curl -X PUT http://localhost:6333/collections/mem0_memories \
-H "Content-Type: application/json" \
-d '{"vectors":{"size":1024,"distance":"Cosine"}}'
阶段四:SQLITE_CANTOPEN 崩溃与 Shim 方案
坑 11:SQLITE_CANTOPEN 导致整个 Gateway 崩溃
现象:
{"0":"[openclaw] Uncaught exception: Error: SQLITE_CANTOPEN: unable to open database file"}
每条 TG/Discord 消息都触发崩溃,Gateway 进程直接退出。
根因:jiti 加载 sqlite3 native module 时路径解析到错误位置。即使 binary 存在于插件目录,jiti 的 __dirname 始终指向自己。这不是配置问题,是 OpenClaw 使用 jiti 编译 TypeScript 插件时的固有兼容性缺陷。
解决:SQLite Shim(垫片)
创建一个纯 JavaScript 的假 sqlite3 包,让所有数据库操作静默返回成功:
// ~/.openclaw/extensions/openclaw-mem0/node_modules/sqlite3/lib/sqlite3.js (覆盖)
class Database {
constructor(path, mode, cb) { if (typeof cb === 'function') cb(null); }
run(sql, params, cb) { if (typeof cb === 'function') cb(null); }
all(sql, params, cb) { if (typeof cb === 'function') cb(null, []); }
get(sql, params, cb) { if (typeof cb === 'function') cb(null, undefined); }
close(cb) { if (typeof cb === 'function') cb(null); }
}
module.exports = { Database, verbose: () => module.exports };
代价:History 记录(操作日志)不会持久化,但核心的 recall/capture 功能不受影响(记忆存在 Qdrant 中)。
阶段五:401 Unauthorized — baseURL 被吞掉
坑 12:SDK OpenAIEmbedder 硬编码 apiKey,丢弃 baseURL
现象:SDK 独立测试 Memory.search() 始终报 401 You didn't provide an API key,即使配置了正确的 baseURL 和 apiKey。
根因(SDK 源码级 Bug):
Bug #1:OpenAIEmbedder 构造器硬编码
// mem0ai/dist/oss/index.js 第 206 行
this.openai = new import_openai.default({ apiKey: config.apiKey });
// ❌ 完全没有传入 baseURL!
所有请求默认打到 api.openai.com,而用户的 key 只在自建代理有效。
Bug #2:ConfigManager.mergeConfig() 白名单过滤丢弃 baseURL
配置合并逻辑中,embedder 配置只提取了 apiKey / url / model,直接把用户写的 baseURL 丢弃了:
// 即使 openclaw.json 里写了 baseURL: "https://api.qadmlee.com/v1"
// ConfigManager 在合并时只保留了白名单字段
embedder: { model: ..., apiKey: ..., url: ... } // baseURL 消失了
Bug #3:OpenAIStructuredLLM 同样漏传
LLM 端的结构化提取类也存在相同问题。
解决(热补丁脚本):
// hot_patch.js — 修改 SDK dist 文件
const fs = require('fs');
const SDK = '~/.openclaw/extensions/openclaw-mem0/node_modules/mem0ai/dist/oss/index.js';
let code = fs.readFileSync(SDK, 'utf8');
// 1. 修复 ConfigManager 吞掉 embedder baseURL
code = code.replace(
/embedder:\s*\{[^}]*\}/,
match => match.replace('}', ', baseURL: userConf?.baseURL }')
);
// 2. 修复所有 OpenAI 客户端构造器
code = code.replace(
/new (import_openai\d*\.default)\(\s*\{\s*apiKey:\s*config\.apiKey\s*\}\s*\)/g,
'new $1({ apiKey: config.apiKey, baseURL: config.baseURL || config.url || process.env.OPENAI_BASE_URL })'
);
fs.writeFileSync(SDK, code);
当前状态:补丁已修复 embedder 端的 401,LLM 端(gpt-5.2 结构化调用)仍在调试中。
元坑:AI 辅助调试的系统性陷阱
元坑 1:AI 幻觉 OpenClaw CLI 命令
| AI 给出的命令 | 实际结果 |
|---|---|
openclaw chat | ❌ error: unknown command 'chat' |
openclaw gateway logs | ❌ error: too many arguments for 'gateway' |
openclaw mem0 stats | ❌ 不存在 mem0 子命令 |
openclaw mem0 search | ❌ 不存在 |
正确命令:openclaw memory search、openclaw logs、openclaw tui。
教训:AI 对非主流/闭源工具的 CLI 命令是猜测的。先跑 --help 确认命令存在再操作。
元坑 2:AI 猜测 config 字段名导致多轮返工
| AI 猜测的字段 | SDK 实际期望 |
|---|---|
embeddingModelDims | dimension |
historyStore.type | 不存在,历史由 SDK 内部管理 |
oss.vectorStore.config.embedding_model_dims | dimension |
Node.js SDK 的 baseURL 在 config 中 | 被 ConfigManager 白名单过滤 |
教训:遇到配置不生效时,直接 grep SDK 源码确认字段名,不要相信 AI 的推测。
元坑 3:Python SDK ≠ Node.js SDK 的功能差异
| 功能 | Python SDK | Node.js SDK |
|---|---|---|
| pgvector | ✅ 支持 | ❌ 不支持 |
| Qdrant | ✅ | ✅ |
| Redis | ✅ | ❌ |
| Supabase | ✅ | ❌ |
embedding_model_dims 字段 | ✅ | dimension |
教训:不要假设两种语言的 SDK 功能对等。这个差异导致了 pgvector 全部白做、不得不迁移到 Qdrant。
替代方案:OpenMemory (CaviraOSS)
在调试过程中研究了 CaviraOSS/OpenMemory 作为替代方案:
| 维度 | mem0 (openclaw-mem0) | OpenMemory (MCP) |
|---|---|---|
| 集成方式 | OpenClaw 原生插件,自动 autoRecall/Capture | MCP 工具,AI 手动调用 |
| 部署复杂度 | 坑极多(本文档内容) | 简单,SQLite 开箱即用 |
| 自动提取上下文 | ✅ 框架级 Hook | ❌ 依赖 AI 主动调用 |
| 跨工具通用 | 只对 OpenClaw | Claude/Antigravity/Cursor 通用 |
| 存储 | Qdrant (需外部) | SQLite (本地) |
结论:OpenMemory 部署更简单但缺少自动化的 autoCapture 机制,需要在 System Prompt 中指示 AI 主动存取记忆。
坑点速查表
| # | 坑点 | 阶段 | 状态 |
|---|---|---|---|
| 1 | Docker 镜像缺 psycopg 依赖 | 容器化 | ✅ 已解决 |
| 2 | SQLite history 目录不存在 | 容器化 | ✅ 已解决 |
| 3 | Embedding 维度 1536 vs 1024 (pgvector) | 容器化 | ✅ 已解决 |
| 4 | pgdata volume 未删除导致旧表残留 | 容器化 | ✅ 已解决 |
| 5 | jiti 路径解析找不到 sqlite3 binary | 插件 | ✅ Shim 绕过 |
| 6 | Node.js v25 缺預编译 binary | 插件 | ✅ 降级 v22 |
| 7 | Gateway restart 非原子操作 | 插件 | ✅ 已解决 |
| 8 | Node.js SDK 不支持 pgvector | 迁移 | ✅ 切换 Qdrant |
| 9 | Qdrant Client/Server 版本不兼容 | 迁移 | ✅ 固定 v1.13.6 |
| 10 | Qdrant Collection 维度 1536 默认值 | 迁移 | ✅ 手动建表 |
| 11 | SQLITE_CANTOPEN Gateway 崩溃 | 运行时 | ✅ SQLite Shim |
| 12 | OpenAIEmbedder 硬编码丢 baseURL | SDK Bug | 🔧 热补丁 |
| 13 | ConfigManager 白名单过滤 baseURL | SDK Bug | 🔧 热补丁 |
| 14 | AI 幻觉 CLI 命令 | 元坑 | ⚠️ 需要人工验证 |
总结教训
一、AI 犯了什么错?
1. 不验证就给命令,造成大量无效操作
- 编造了
openclaw chat、openclaw gateway logs、openclaw mem0 stats等根本不存在的命令,让用户执行后全部报错。应该先让用户跑--help或自己读文档。
2. 猜测配置字段名,反复猜错
- 把 Qdrant 的
dimension猜成embeddingModelDims,导致多轮「删 collection → 重建 → 又 400」的无效循环。 - 把
historyDbPath嵌套进了historyStore.type等不存在的结构里。 - 应该第一时间
grepSDK 源码确认字段名,而不是靠记忆猜测。
3. 没有提前确认 Node.js SDK 和 Python SDK 的功能差异
- 一开始基于 Python SDK 的文档给出了 pgvector 方案,让用户完成了整个 pgvector 部署(Dockerfile、docker-compose、main.py、.env 全套),结果发现 Node.js SDK 根本不支持 pgvector。这是最大的浪费——至少 2 小时白做。
- 应该一开始就查 OpenClaw 插件用的是哪个 SDK,以及该 SDK 支持什么向量库。
4. Dockerfile 分三次才写对
- 第一次漏
libpq-dev,第二次漏psycopg2-binary,第三次漏mkdir /app/history。每次都是用户跑起来报错才发现。 - 应该先读
mem0/mem0-api-server的官方 Dockerfile 和 entrypoint 确认依赖链,一次性补全。
5. 给错 Qdrant 镜像版本号
- 先给
v1.13.6(不确定是否存在),用户报 manifest unknown 后又改成1.13.6(少了v前缀还是错),再改成v1.14.0(用户已经不耐烦了)。几次都没先去 Docker Hub 验证 tag 是否存在。 - 应该先查 Docker Hub 确认可用 tag 再给用户。
6. 误判问题根因,导致弯路
- SQLite binding 问题上,先让用户跑了
npm rebuild(无效),再建议 symlink hack(找不到源文件),再降级 Node.js v22(仍然无效),折腾了 4 轮后才意识到这是 jiti 的路径解析 bug,无法通过常规编译解决。 - 应该先分析
node-pre-gyp的路径解析逻辑,快速定位到 jiti 的__dirname偏移问题,而不是盲试。
7. 声称「不影响核心功能」但实际导致 Gateway 崩溃
- 在
openclaw mem0 stats报 SQLite 错误时,我说「这只是统计命令,不影响核心功能」。实际上同一个 SQLite 问题在 recall/capture hook 里导致整个 Gateway 进程崩溃,TG/Discord 全部无法回复消息。 - 不应该在没有验证的情况下声称「不影响」。
二、用户犯了什么错?
1. 远程文件同步后没验证实际内容
.env加了EMBEDDER_DIMS=1024但远程.env没同步过去,启动日志暴露维度仍是 1536。改完本地文件后没在远程cat .env确认。
2. 过度信任 AI 给出的方案,没有及时 challenge
- AI 给了 pgvector 方案后直接执行了整套部署,没有先问「OpenClaw 的 Node.js 插件是否真的支持 pgvector?」——如果在这里停一下追问,就能省掉 2 小时。
- AI 说「不影响核心功能」时接受了这个说法,没有立刻去 TG 发消息验证。
3. 没有在每步操作后检查日志
- 多次「跑了命令但没检查日志就说没生效」,延长了排查时间。例如
docker compose up -d --build后没有docker logs mem0-api确认维度配置。
4. 改 Node.js 版本后没重启终端 session
- 切到 v22 后直接在同一终端跑
openclaw,但旧的 Gateway LaunchAgent 仍以 v25 运行。node -v显示 v22 但 Gateway 进程并非新终端产出。
三、以后怎么避免?
对 AI 的约束规则
| 规则 | 说明 |
|---|---|
| 先查后说 | 对不确定的 CLI 命令、config 字段、Docker tag,必须先用工具验证(--help / grep 源码 / 查 Docker Hub),禁止凭记忆猜测 |
| SDK 差异检查前置 | 涉及跨语言 SDK(Python vs Node.js)时,第一步确认目标 SDK 的功能支持范围,不可用 Python 文档推导 Node.js 行为 |
| 一次性写对 Dockerfile | 先读基础镜像的 ENTRYPOINT/CMD 和依赖树,列出所有缺失依赖后一次补全,禁止「跑一次 → 报错 → 加一行 → 再跑」的循环 |
| 禁止无根据的「不影响」 | 没有实际测试验证过的判断,不能说「不影响核心功能」。必须写「未验证,建议测试确认」 |
| 错误计数器 | 连续 2 轮给出无效方案后,主动暂停并声明:「我可能判断错了根因,建议换个角度排查」 |
对用户的建议
| 建议 | 说明 |
|---|---|
| 改配置后立刻 diff 验证 | 远程 cat 或 diff 确认文件内容已更新,检查启动日志确认参数生效 |
| AI 说「不影响」时追问「证据?」 | 让 AI 给出具体的代码路径或日志依据,而不是接受模糊保证 |
新工具前先跑 --help | 不管 AI 给什么命令,先 openclaw --help 看真实命令列表 |
| 大方案前先做最小验证 | 在 AI 给出「pgvector 全套部署方案」时,先问「OpenClaw 插件到底用哪个 SDK?这个 SDK 支持 pgvector 吗?」——5 分钟的追问可以省 2 小时 |
| 版本切换后验证全链路 | 切 Node.js 版本后:检查 node -v → 重启 Gateway → 看日志 runtimeVersion → 发消息测试。不只是看终端输出 |
最终架构
┌────────────┐ TG/Discord/Web ┌──────────────────┐
│ 用户终端 │ ─────────────────────── │ OpenClaw Gateway │
└────────────┘ │ (Node.js v22) │
│ │
│ @mem0/openclaw-mem0│
│ ├ autoRecall │
│ ├ autoCapture │
│ └ SQLite Shim │
└──────┬────────────┘
│ mem0ai/oss (Node.js SDK)
┌───────────────┼───────────────┐
│ │ │
┌─────▼─────┐ ┌─────▼─────┐ ┌────▼────┐
│ Qdrant │ │ LLM Proxy │ │Embedder │
│ v1.13.6 │ │gpt-5.2 │ │qwen3-0.6b│
│ :6333 │ │api.qadmlee│ │api.qadmlee│
└───────────┘ └───────────┘ └─────────┘
部署文件清单 (viking/mem0-server/):
Dockerfile— 补装 psycopg + 创建 history 目录main.py— Python Mem0 API Server (pgvector/Qdrant 双配置)docker-compose.yml— PostgreSQL + Qdrant + Mem0 APIdeploy.sh— 一键部署脚本(含清理旧容器).env.example— 环境变量模板
写给后来人:如果你在 2026 年看到这份文档,mem0 的 Node.js SDK 很可能已经修了 baseURL 透传和 pgvector 支持。在开始部署前,先检查
mem0ai的 changelog,或许这些坑已不再存在。