OpenClaw + Mem0 + Ollama 排障实录:从 403、维度错配到最终打通

5 阅读9分钟

OpenClaw + Mem0 Open-Source Mode 接入本地 Ollama:一次从 403 到维度错配再到打通的完整排障记录

背景

这次目标是把 openclaw-mem0 插件切到 open-source mode,并让它通过 本地 Ollama 提供:

  • LLM:用于 memory extraction / capture
  • Embedding model:用于 semantic search / recall

期望结果:

  1. memory_store 能正常抽取并写入 memory
  2. memory_search 能正常检索并返回 recall 结果
  3. 整个链路尽量避免外部 cloud 依赖导致的 region / availability 问题

初始现状

本机 Ollama 可用,但最开始只有两个模型:

  • glm-5:cloud
  • kimi-k2.5:cloud

问题在于:

  • 这两个都不是 embedding model
  • Mem0 open-source mode 需要 LLM + Embedder 两套能力

因此第一步先补 embedding model。


最终目标架构

最后选定的思路是:

  • Embeddernomic-embed-text
  • LLMkimi-k2.5:cloud
  • Vector store:Mem0 OSS 默认 memory vector store(SQLite 落盘)

其中:

  • nomic-embed-text 输出 768 维 embedding
  • kimi-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:cloud
  • glm-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 = 768
  • new 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 做了:

  1. 备份
  2. 移走旧库
  3. 重启 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 added
  • memory_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 是否能找回
  • 是否能稳定复测

可复用的排障步骤清单

如果后续有人再遇到类似问题,可以按这个顺序排:

  1. 确认 Ollama 服务可用
  2. 确认 embedding model 已安装
  3. 确认 LLM / embedder 字段名是否正确(url vs baseURL
  4. 查看 gateway 实际 cwd
  5. 定位真正的 vector_store.db
  6. 检查 embedding dims 是否和 vector store 维度一致
  7. 必要时 patch 运行时默认维度
  8. 必要时修复 fenced JSON 清洗逻辑
  9. 备份并移走旧 vector store
  10. 用 systemd 重启
  11. 做至少两轮 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/

后续优化建议

如果要把这套方案从“能用”提升到“更稳”,建议继续做:

  1. 减少对 cloud-backed LLM 的依赖

    • 尽量换到本地、稳定、JSON 输出更规整的 model
  2. 对 facts extraction 做更强的 response sanitization

    • 不仅去 fenced code block
    • 还可以做 JSON 片段截取、尾逗号清理等
  3. 把 patch 上游化

    • 提 PR 给 mem0aiopenclaw-mem0
    • 避免升级后 patch 丢失
  4. 增加健康检查脚本

    • 自动验证:模型存在、向量库路径、维度一致性、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 测试来验证。