开篇: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 上的自动化工程师
字节 Bernini:让 MLLM 当指挥家,DiT 安心画视频
构建可演进的第二大脑:基于 OpenKB + OpenRouter + Llama 3.3 的"编译式"RAG 架构深度实践