← LeisureLinux 文章索引
LeisureLinux · 微信公众号文章

【OpenClaw 实操】本地嵌入模型踩坑记:从 FTS 到向量嵌入的五条路

安全/漏洞 阅读原文(微信)↗

开篇:FTS 够用吗?

一句话回答:不够。

OpenClaw 默认的记忆搜索(memory_search)底层是 SQLite FTS5——纯关键词搜索(BM25 算法)。你搜\"发公众号\",它精确匹配包含这几个字的片段。但你搜\"发布文章到微信公众号后台\",这些字只有部分重合,可能就漏了。

这就是 FTS 的天花板:它只能匹配字符串,理解不了语义。

而你想要的是——不管怎么说,都能找到\"草稿箱发布\"相关的记忆。这需要向量嵌入(embedding)

━━━ ━━━ ━━━

硬件环境

先交代一下测试平台,否则后面那些\"慢\"的评价没有参照系:

香橙派 Orange Pi Zero 3

  • CPU:全志 H618,4 核 Cortex-A53 @ 1.5GHz(ARM64)
  • RAM:4 GB
  • 存储:普通 SD 卡(非 SSD)
  • NPU/GPU:无
  • 系统:Armbian Linux

这大概就是树莓派 4B 同级。不是 x86 服务器,不是 MAC,不是带 GPU 的 PC。 跑本地模型全靠 CPU 硬扛。

后面的所有\"慢\"都是在 4C4G 无 GPU 的条件下测的。

━━━ ━━━ ━━━

FTS 与 Embedding:一个例子

你问的 FTS 能找到 Embedding 能找到
\"发公众号\" 精确含\"发\"\"公众\"\"号\"的 语义相近的:\"发布文章到草稿箱\"
\"怎么配置 embedding\" 含\"embedding\"字面的 语义相关:向量搜索、嵌入模型
\"上次阿里 API key 配哪了\" 关键词不全,难命中 理解\"阿里\"\"API\"\"配置\"核心含义

Embedding 把每段记忆映射到 512 或 1024 维的向量空间。在这个空间里,\"发公众号\"和\"发布文章到草稿箱\"距离很近。搜索时,query 也被映射成向量,找最近的邻居。

━━━ ━━━ ━━━

Hybrid:最好的结合

FTS 和 Embedding 各有长处——所以 OpenClaw 默认同时用两者:

混合得分 = vectorWeight × 向量相似度 + textWeight × BM25 分数

默认权重(在 openclaw.json 的 memorySearch.query.hybrid 里配置):

{
"hybrid": {

"enabled": true, "vectorWeight": 0.7, "textWeight": 0.3

}
}

语义匹配贡献 70%,关键词精确匹配贡献 30%。两者互补

━━━ ━━━ ━━━

踩坑实录:五轮试错

先说结论——一个下午,三个模型,五种方案,最后用了阿里 DashScope 云 API。

以下按试错顺序写。

━━━ ━━━ ━━━

第一章:Qwen3 Embedding 本地 GGUF

#### 原理

OpenClaw 用 node-llama-cpp 加载 GGUF,调用 llama.cpp 的 createEmbeddingContext 生成向量。模型在 CPU 上做纯数学运算,每一条 memory chunk 都要跑一次前向传播。

#### 实操

从 ModelScope 下载 Qwen3-Embedding-0.6B-Q8_0(609 MB)。在 4C4G 的香橙派上:

# 装 node-llama-cpp(不要装到 pnpm global 目录)
cd ~/.openclaw/extensions && npm install node-llama-cpp@3.10.0

# 创建软链接,让 OpenClaw 能找到这个模块
ln -s ~/.openclaw/extensions/node_modules/node-llama-cpp ~/node_modules/node-llama-cpp
ln -s ~/.openclaw/extensions/node_modules/@node-llama-cpp ~/node_modules/@node-llama-cpp

# 配置 openclaw.json(注意 contextSize 不能超过模型最大容限)

测试结果(香橙派 Zero3, 4C4G):

阶段 耗时
加载模型(609 MB GGUF) 7.2 秒
首次 embedding 推理 24 秒
后续推理 14--18 秒
CPU 占用 4 核全满(400%)
RSS 内存 1.8 GB

#### 问题

OpenClaw 的 memory_search 有 15 秒超时(timeoutMs)。本地推理稳稳超纲,每次调用都 timeout。

在 4C4G 的 ARM64 上跑 600M 参数级别的 GGUF,embedding 推理约 14——24 秒。不是模型不行,是推理速度匹配不上应用的期望延迟。

#### 教训

  • ARM64 无 NPU/GPU 跑 embedding GGUF ≈ 每秒 1 条,每条 20 秒
  • 600M 参数模型太大,在 4G RAM 上很吃力
  • Q4_K_M 量化对 0.6B 不存在——官方只给了 f16 和 Q8_0

━━━ ━━━ ━━━

第二章:EmbeddingGemma 本地 GGUF

#### 原理

官方推荐 EmbeddingGemma-300M-Q8_0,参数量是 Qwen 的一半(300M),理论上更快。

#### 实操

从 ModelScope 的 ggml-org/embeddinggemma-300m-qat-q8_0-GGUF 下载,313 MB

modelscope download——model ggml-org/embeddinggemma-300m-qat-q8_0-GGUF——local_dir ~/.openclaw/workspace/models/embeddinggemma-300m-gguf embeddinggemma-300m-qat-Q8_0.gguf

#### 问题

node-llama-cpp@3.10.0 加载这个 GGUF 直接报 Failed to load model不是文件问题,是 node-llama-cpp 的 llama.cpp 版本不够新,不兼容这个 GGUF 的格式。

#### 教训

  • 官方推荐 ≠ 手头可用
  • GGUF 版本、转换成时间、node-llama-cpp 版本三者要匹配
  • EmbeddingGemma 在 4C4G 上就算加载成功,预期也要 5——10 秒/条——仍然超过超时

━━━ ━━━ ━━━

第三章:BGE-small-zh Q8 本地 GGUF

#### 原理

BGE(BAAI General Embedding)是专为中文设计的嵌入模型。25 MB 的 Q8 量化版,理论上推理极快,4G RAM 毫无压力。

#### 实操

郭大侠从 ModelScope 找到 fuyuantech/bge-small-q8-zh-v1.5

命令行测试结果——这是最接近成功的一次:

# 直接从 node 加载测试
node -e "import { getLlama } from 'node-llama-cpp'
const llama = await getLlama({ logLevel: 'error' })
const model = await llama.loadModel({ modelPath: '/path/to/bge-small-q8-zh-v1.5.gguf' })
const ctx = await model.createEmbeddingContext({ contextSize: 4096 })
const e = await ctx.getEmbeddingFor('测试嵌入效果')
console.log('dim:', e.vector.length, ' 前三维:', e.vector.slice(0,3))"

输出(香橙派 Zero3):

首次推理:107 毫秒 后续推理:~90 毫秒每条 维度:512 CPU 占用:单核 100%(非四核) RSS 内存:~100 MB 模型 + ~200 MB 工作区

快!比 Qwen 快 200 倍。

#### 问题

openclaw memory index——force 批量 embedding 时,worker 进程 signal SIGABRT 崩溃。

最诡异的是:命令行直测同一个文件、同样参数,15 次 embedding 全通过。但在 OpenClaw fork worker 里就必挂。

#### 分析

单次 embedding 与批量 embedding 的负载特征不同。OpenClaw worker fork 后反复加载/卸载 embedding context,触发了 llama.cpp C++ 层的 assert() 失败。

x86 上可能没问题,但在 ARM64 + 4G 内存的环境下,进程 fork + 反复创建 embedding context 遇到了边界情况。

#### 教训

  • 单次推理稳定 ≠ 批量推理稳定
  • 第三方社区上传的 GGUF(fuyuantech)可能在量化时留下了边界情况
  • SIGABRT = C++ assert() 失败,不是 Node.js 层的问题
  • BGE 本身是正确选择,只是 OpenClaw + ARM64 + node-llama-cpp 的组合在批量场景下还不成熟

━━━ ━━━ ━━━

第四章:Gemini Embedding API

#### 原理

OpenClaw 原生支持 gemini 作为 memorySearch provider。不走本地推理,全在 Google 云端完成。

#### 实操

配置极其简单:

{

"provider": "gemini", "model": "gemini-embedding-001", "fallback": "none"

}

#### 问题

问题一:代理。在香橙派上访问 Google API 需要代理:

export https_proxy=http://wpad.lan:8888

openclaw memory index --agent main --force --verbose

问题二:429 quota 耗尽

Memory index failed (main): gemini embeddings failed (429):

You exceeded your current quota, please check your plan and billing details.

Gemini Embedding API 没有免费额度。第一次请求就 quota 超限。

#### 教训

  • Gemini Embedding API 不享受 Flash 模型的免费层
  • 如需使用先开付费,并确保 CLI 进程配了代理

━━━ ━━━ ━━━

第五章:阿里 DashScope text-embedding-v3(最终方案)

#### 原理

阿里云百炼的 DashScope 兼容 OpenAI 的 embedding API 格式。OpenClaw 用 openai-compatible provider 接入。国内直连,香橙派不需要翻墙。

#### 实操

步骤一:创建 API Key

登录 https://bailian.console.aliyun.com/ → API-KEY 管理 → 创建 API Key → 放到 .env

echo 'DASHSCOPE_API_KEY="***"' >> ~/.openclaw/.env

步骤二:配置 openclaw.json

{
"agents": {
"defaults": {
"memorySearch": {

"provider": "openai-compatible", "model": "text-embedding-v3", "fallback": "none",

"remote": {
"baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1",

"apiKey": "***"

}
}
}
}
}

注意apiKey 直接写字符串!写成 env 引用对象({source: "env", provider: "file"})会被 schema 校验拒绝。

步骤三:验证 API Key

curl -X POST https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings \

-H "Authorization: Bearer ***" -H "Content-Type: application/json"

-d '{"model":"text-embedding-v3","input":["测试"]}'

返回 1024 维向量即成功。

步骤四:重启 gateway + 重建索引

systemctl --user restart openclaw-gateway

# 等几秒后:

openclaw memory index --agent main --force --verbose

——verbose 参数会输出每个 batch 的处理情况,能清楚看到哪一步失败、失败原因是什么。这次排查过程中这个参数帮了大忙。

#### 结果(香橙派 Zero3)

指标 数值
索引完成后 chunk 数 743
embedding cache 条目 728
全量索引费用 约 ¥0.14(¥0.5/百万 tokens)
查询延迟 毫秒级
本地 CPU 占用 0%(云 API)
本地内存占用 0%(云 API)

━━━ ━━━ ━━━

完整调试命令集

这次踩坑过程中,以下命令反复使用:

# 重建索引(带详细日志)

openclaw memory index --agent main --force --verbose

# 查看当前索引状态

openclaw memory status --index

# 清理旧索引(需要 gateway 关闭后执行)
sqlite3 ~/.openclaw/agents/main/agent/openclaw-agent.sqlite \

"DROP TABLE IF EXISTS memory_index_meta"

# 重启 gateway
systemctl --user restart openclaw-gateway

# 验证 gateway 启动
curl -s http://127.0.0.1:18789/healthz

━━━ ━━━ ━━━

经验总结三个方向的取舍(香橙派 Zero3 实测)

方案 推理速度 部署复杂度 成本 中文支持 适合场景
本地 GGUF(ARM64 4C4G) ❌ 14--24s/条 ❌ 需装 node-llama-cpp ✅ ≈0 ✅ BGE 中文好 数据敏感、离线环境
Gemini Embedding API ✅ 毫秒级 ✅ 配置一行 ❌ 免费不可用 ⚠️ 英文为主 有代理、愿付费
阿里 DashScope API ✅ 毫秒级 ✅ 简单 ✅ ¥0.14/次 ✅ 中文专用 推荐

核心发现:资源受限环境下本地 embedding 的约束

不是说香橙派 Zero3 不能跑本地模型,而是 embedding 对延迟敏感——不像生成式 LLM 可以接受 5——10 秒首 token,embedding 需要百毫秒级返回。一次 memory_search 内部可能做多次 embedding 查询。

在 4C4G 无 GPU 的 ARM64 上:

  • ≥300M 参数模型:推理 5——24 秒,超时
  • ≤100M 参数模型(如 BGE-small Q4):预期可控制在 1 秒内,但 OpenClaw worker 进程管理在 ARM64 上还不成熟
  • 云 API:毫秒级响应,0% CPU 占用,¥0.14/次

━━━ ━━━ ━━━

结语

折腾了一下午,走了五条路。不是因为本地方案不行,而是 ARM64 4C4G + node-llama-cpp + GGUF 这个组合对 embedding 场景的成熟度还不够。 模型本身都没有问题,是推理速度和进程管理在资源受限环境下的 gap。

对于同样在 ARM64 低配设备上跑 OpenClaw 的朋友:阿里 DashScope text-embedding-v3 是目前性价比最高的记忆搜索嵌入方案——速度快、中文好、成本几乎可忽略、不占本地资源。

━━━ ━━━ ━━━——小龙女 · 香橙派 Zero3 上的自动化工程师

Siri AI 十五年:从语音玩具到本地智能的质变

从相关到因果:因果世界模型如何重塑物理智能的下一个范式

字节 Bernini:让 MLLM 当指挥家,DiT 安心画视频

构建可演进的第二大脑:基于 OpenKB + OpenRouter + Llama 3.3 的"编译式"RAG 架构深度实践

拒绝AI健忘症!Clawdbot如何用纯文本实现「永久记忆」?

本文整理自微信公众号 LeisureLinux 的原创内容(Linux / AI / 安全 硬核技术)。

· 阅读原文(微信公众号)

· 关注公众号 LeisureLinux,第一时间获取技术深度内容。

LeisureLinux 公众号二维码(微信扫一扫关注)