OpenClaw + Mem0 Open-Source Mode 接入本地 Ollama:一次从 403 到维度错配再到打通的完整排障记录
背景
这次目标是把 openclaw-mem0 插件切到 open-source mode,并让它通过 本地 Ollama 提供:
- LLM:用于 memory extraction / capture
- Embedding model:用于 semantic search / recall
期望结果:
memory_store能正常抽取并写入 memorymemory_search能正常检索并返回 recall 结果- 整个链路尽量避免外部 cloud 依赖导致的 region / availability 问题
初始现状
本机 Ollama 可用,但最开始只有两个模型:
glm-5:cloudkimi-k2.5:cloud
问题在于:
- 这两个都不是 embedding model
- Mem0 open-source mode 需要 LLM + Embedder 两套能力
因此第一步先补 embedding model。
最终目标架构
最后选定的思路是:
- Embedder:
nomic-embed-text - LLM:
kimi-k2.5:cloud - Vector store:Mem0 OSS 默认 memory vector store(SQLite 落盘)
其中:
nomic-embed-text输出 768 维 embeddingkimi-k2.5:cloud负责事实抽取(capture/extraction)
第一步:切换到 Mem0 open-source mode
openclaw.json 中的 openclaw-mem0 配置先改成 open-source mode。
核心结构如下:
{
"plugins": {
"entries": {
"openclaw-mem0": {
"enabled": true,
"config": {
"mode": "open-source",
"userId": "default",
"oss": {
"embedder": {
"provider": "ollama",
"config": {
"model": "nomic-embed-text",
"url": "http://127.0.0.1:11434"
}
},
"vectorStore": {
"provider": "memory",
"config": {
"collectionName": "memories",
"dimension": 768
}
},
"llm": {
"provider": "ollama",
"config": {
"model": "kimi-k2.5:cloud",
"baseURL": "http://127.0.0.1:11434"
}
}
}
}
}
}
}
}
这里有两个关键点
1. Embedder 的 Ollama 配置字段是 url
不是 baseURL。
2. LLM 的 Ollama 配置字段实际要用 baseURL
这是后面排障时发现的一个坑:
- embedder(ollama) 读的是
url - llm(ollama) merge 后实际使用的是
baseURL
如果 LLM 仍写 url,运行时配置可能不会按预期生效。
第二步:安装本地 embedding model
拉取推荐的 embedding model:
ollama pull nomic-embed-text
理由:
- Ollama 生态里常用
- 对 embedding 场景成熟
- 体积较友好
- 很适合 Mem0 这种语义检索用途
第三步:最早遇到的问题 —— 403 region unsupported
最初配置里,LLM 先尝试过:
kimi-k2.5:cloudglm-5:cloud
在 Mem0 capture / recall 流程中,日志里一度出现:
openclaw-mem0: capture failed: Error: 403 Country, region, or territory not supported
openclaw-mem0: recall failed: Error: 403 Country, region, or territory not supported
为什么会这样?
虽然整个系统跑在本地 Ollama 上,但 *:cloud 这种模型本质上还是 cloud-backed。
也就是说:
- embedding 已经本地化
- 但 LLM 仍然可能走云端路径
- 因此会受上游 region 限制影响
误判与修正
后续手动执行:
ollama run kimi-k2.5:cloud
发现它在 shell 里其实能正常工作。
这说明:
- 不是模型本身绝对不可用
- 更可能是 Mem0 插件调用路径、配置字段或上游暂时状态导致的异常
最后通过把 LLM 的配置字段修正为 baseURL,403 就不再是主 blocker。
第四步:最大的主问题 —— embedding 维度错配
切到 nomic-embed-text 后,日志持续报:
Query dimension mismatch. Expected 1536, got 768
这是什么意思?
nomic-embed-text产出的是 768 dims- Mem0 的 vector store/query 逻辑却还按 1536 dims 在查
于是 recall/search 永远失败。
第五步:定位错误来源
一开始的错误假设
最早以为旧索引藏在:
~/.openclaw/memory/main.sqlite
于是先备份并移走了这个文件。
但问题仍然存在。
真正的关键发现
通过阅读 mem0ai 的运行时代码(dist/oss/index.mjs),发现 memory vector store 默认使用:
this.dbPath = path.join(process.cwd(), 'vector_store.db')
也就是说,真正的 vector store 路径取决于 gateway 进程的 cwd。
继续检查 systemd 运行状态后确认:
- gateway 实际 cwd 是:
/home/water/.openclaw/workspace
因此当前运行中的实际向量库路径是:
/home/water/.openclaw/workspace/vector_store.db
不是之前误以为的:
/home/water/vector_store.db
这一步是整个排障中的关键转折点。
第六步:为什么配置了 dimension: 768 仍然没生效?
从本地最小化验证看:
mem0ai确实能读到vectorStore.config.dimension = 768new Memory(config)后,实例上的 dimension 也是 768
说明:
- 配置文件没写错
- Mem0 配置 merge 逻辑也不是完全坏的
但运行时日志仍持续报:
Expected 1536, got 768
因此采取了更激进但高效的方式:
直接 patch 运行时的
mem0ai包。
第七步:直接 patch mem0ai 运行时代码
Patch 1:修改默认 vector dimension
在运行时实际加载的文件中:
~/.openclaw/extensions/openclaw-mem0/node_modules/mem0ai/dist/oss/index.mjs
把默认维度从 1536 改成 768。
示意:
this.dimension = config.dimension || 768;
这一步确保即使某些路径没吃到外部配置,也不会再 fallback 到 1536。
Patch 2:修复 fenced JSON 解析问题
另一个反复出现的问题是:
Failed to parse facts from LLM response
SyntaxError: Unexpected token '`'
根因是 LLM 有时返回:
```json
{
"facts": [...]
}
而原始 `removeCodeBlocks()` 逻辑是**直接删掉整个 fenced block**,导致要么:
- 留下空字符串 → `Unexpected end of JSON input`
- 要么 fenced block 没被正确清掉 → `Unexpected token '`'`
因此把它改成:
- **保留 code fence 内部内容**
- 只去掉外围的 ```json / ``` 包装
修复后的逻辑类似:
```js
function removeCodeBlocks(text) {
return text.replace(/```(?:json)?\s*([\s\S]*?)\s*```/gi, '$1').trim();
}
这一步显著改善了 facts extraction 的可解析性。
第八步:清理并重建真正的 vector store
在 patch 完维度逻辑之后,对真正使用中的 vector store 做了:
- 备份
- 移走旧库
- 重启 gateway
对应文件:
- 旧库:
/home/water/.openclaw/workspace/vector_store.db - 备份:
/home/water/.openclaw/workspace/vector_store.db.pre-patch-backup
这样可以确保新进程按修正后的维度重新创建向量库。
第九步:为什么还要强制用 systemd 重启?
普通的:
openclaw gateway restart
在调试过程中有几次回执并不稳定,难以确认是否真正切到了新进程。
最后改用:
systemctl --user restart openclaw-gateway.service
配合查看:
systemctl --user status openclaw-gateway.service
这样能更可靠地确认:
- 进程 PID 是否变化
- 新配置/patch 是否被新进程吃到
- gateway 当前 cwd 是什么
在这类 runtime patch 场景里,systemd 重启比应用内部 restart 更可信。
第十步:最终验证结果
验证 1:Recall/Search 恢复
做过一次 recall 验证后,memory_search() 已经能正常返回结果,说明:
- vector store 可读
- query path 正常
- 不再报 1536/768 mismatch
验证 2:Store/Search 端到端复测
失败的测试句子
像下面这种“测试 token 风格”的句子:
My temporary Mem0 verification city is Hangzhou.
结果可能是:
Stored: No new memories extracted.
这说明 capture/extraction 对句式敏感,不是任何文本都会被抽成 memory。
成功的测试句子
使用更自然的偏好句式后:
My favorite color is cobalt blue.
My favorite fruit is mango.
结果分别成功:
Stored: 1 new memory addedmemory_search()成功搜回相应事实
这说明:
Mem0 的核心 store + search 链路已经恢复正常。
最终结论
截至这次排障完成时,可以确认:
已解决的问题
- ✅
openclaw-mem0在 open-source mode 下成功初始化 - ✅ 本地 Ollama embedder 已切到
nomic-embed-text - ✅ 向量维度错配(1536 vs 768)已解决
- ✅ recall/search 已恢复正常
- ✅ store/capture 在正常自然句式下可工作
- ✅ fenced JSON 导致的解析错误已明显缓解
仍然存在的特性/注意点
- ⚠️ capture 并不是对所有句式都稳定抽取
- ⚠️
kimi-k2.5:cloud虽然可用,但本质仍是 cloud-backed - ⚠️ Mem0 的 facts extraction 对 prompt 风格、响应格式、JSON 清洁度仍较敏感
这次排障得到的经验总结
1. 不要只看配置,要看运行时 merge 后的真实对象
这次如果不做本地最小实例化验证,很容易一直怀疑 openclaw.json 写错。
2. 向量库路径经常取决于 process.cwd()
很多 OSS 包默认把 SQLite/向量库存到当前工作目录,不一定在你以为的应用数据目录里。
3. dist/、source map、运行时栈不一定一一对应
日志里看到 src/... 路径,不代表磁盘上真的有源码文件;有时只是 source map 映射。
4. systemd 重启在 runtime patch 阶段更可靠
尤其是在 Node 应用 + 扩展 + 动态依赖组合场景里。
5. 用 end-to-end 真实测试收尾
不要只靠“日志看起来没报错”。真正有效的标准应该是:
store是否成功search是否能找回- 是否能稳定复测
可复用的排障步骤清单
如果后续有人再遇到类似问题,可以按这个顺序排:
- 确认 Ollama 服务可用
- 确认 embedding model 已安装
- 确认 LLM / embedder 字段名是否正确(
urlvsbaseURL) - 查看 gateway 实际 cwd
- 定位真正的
vector_store.db - 检查 embedding dims 是否和 vector store 维度一致
- 必要时 patch 运行时默认维度
- 必要时修复 fenced JSON 清洗逻辑
- 备份并移走旧 vector store
- 用 systemd 重启
- 做至少两轮 store/search end-to-end 验证
附:本次排障中涉及的重要路径
配置文件
/home/water/.openclaw/openclaw.json
OpenClaw Mem0 插件
/home/water/.openclaw/extensions/openclaw-mem0/
运行时 patch 文件
/home/water/.openclaw/extensions/openclaw-mem0/node_modules/mem0ai/dist/oss/index.mjs
当前运行中的 vector store
/home/water/.openclaw/workspace/vector_store.db
备份文件
/home/water/.openclaw/workspace/vector_store.db.pre-patch-backup/home/water/.openclaw/workspace/tmp/mem0-reset-20260307-2054//home/water/.openclaw/workspace/tmp/mem0-reset-20260307-2140//home/water/.openclaw/workspace/tmp/mem0-reset-20260307-2153/
后续优化建议
如果要把这套方案从“能用”提升到“更稳”,建议继续做:
-
减少对 cloud-backed LLM 的依赖
- 尽量换到本地、稳定、JSON 输出更规整的 model
-
对 facts extraction 做更强的 response sanitization
- 不仅去 fenced code block
- 还可以做 JSON 片段截取、尾逗号清理等
-
把 patch 上游化
- 提 PR 给
mem0ai或openclaw-mem0 - 避免升级后 patch 丢失
- 提 PR 给
-
增加健康检查脚本
- 自动验证:模型存在、向量库路径、维度一致性、store/search smoke test
总结
这次问题表面上看是 Mem0 “接不上 Ollama”,但真正拆开后是多个问题叠加:
- 配置字段不一致
- cloud-backed LLM 的 region 干扰
- vector store 真实路径误判
- 默认 embedding 维度不匹配
- fenced JSON 解析缺陷
最终通过:
- 正确配置 Ollama
- 精确定位 vector store
- patch 运行时默认值
- 修复 JSON 清洗
- systemd 强制重启
- end-to-end 复测
把整个链路打通。
一句话总结:
这不是单点故障,而是配置、路径、默认值和响应格式多个细节叠加造成的系统性问题;排障的关键在于逐层缩小范围,并始终用真实 store/search 测试来验证。