文章

记一次 agentmemory embedding 故障:8 次 hack 全败,一次 GitHub 搜索解决

agent 基础设施出问题时,本能反应是 hack 源码、接旁路。但这个本能是错的。

记一次 agentmemory embedding 故障:8 次 hack 全败,一次 GitHub 搜索解决

背景

agentmemory 是我本地 agent 体系的核心记忆服务,跑在 macOS Apple Silicon 上。某天发现它的向量搜索挂了–embedding 模块死循环,CPU 400%+,REST API 无响应。

降级为 BM25-only(纯关键词搜索),语义搜索完全不可用。

根因

agentmemory v0.9.24 用 @xenova/transformers 2.17.2 做本地 embedding,模型 Xenova/all-MiniLM-L6-v2(384 维,ONNX 量化版)。这个包硬依赖 onnxruntime-node 1.14.0,在 Apple Silicon 上跑量化模型的 DynamicQuantizeMatMul 时,多线程 NEON 内核死循环。

版本兼容性死锁:

1
2
3
onnxruntime-node 1.14.0  -> NEON 死循环(多线程触发)
onnxruntime-node 1.15.0+ -> NEON 修了,但 Tensor API 变了,xenova 2.17.2 不兼容
onnxruntime-node 1.20.0+ -> API 又变,仍然不兼容

@xenova/transformers 已更名为 @huggingface/transformers(v4+),支持新版 onnxruntime。但 agentmemory 硬编码了 @xenova/transformers import。

8 次 hack,全败

接下来是一个经典的”接旁路”循环。每次尝试看起来都比上一次更聪明,但全部失败:

1
2
3
4
5
6
7
8
1. 换 onnxruntime-node 1.19.2         -> Tensor.data must be a typed array
2. 换 onnxruntime-node 1.26.0         -> Tensor.location must be a string
3. 删 xenova 内嵌的 onnxruntime-node   -> 顶层版本不匹配,not a valid backend
4. onnxruntime-common 降级到 1.14.0    -> 加载成功,但 NEON 死循环依旧
5. OMP_NUM_THREADS=1 / ORT_NUM_THREADS=1 -> 原生线程池不受控
6. patch backend.js 注入 intraOpNumThreads:1 -> C++ binding 不读这个选项
7. patch wasm.numThreads=1            -> 只影响 WASM 后端,不影响原生
8. 禁用原生后端强制走 WASM            -> no available backend found

每个方案都有合理的逻辑推导,每一步都基于上一步的失败深入分析。8 个方案排除了所有”换版本 + 降线程”的技术路径。

但方向本身就是错的。

方法论纠偏

在准备第 9 个 hack 的时候,我说了一段话:

各种 agent 基础设施软件都在快速迭代阶段,出现问题是必然的。 本地 agent 往往倾向于接旁路、改源码等很 hack 的解决方案。 hack 很酷,很直接,但不适合这种场景–agent 基础设施出了问题,不是传统软件。 更好的思路:先查上游 issue、pr、release,然后要么等上游修并发布正式,要么提前把上游的 pr 合进来。

这段话的核心判断:agent 基础设施迭代极快,你遇到的问题大概率别人也遇到了,维护者很可能已经在修。 在 node_modules 里 patch 编译产物,是在和一个你无法控制的快速移动目标搏斗。

传统软件(数据库、操作系统、编译器)迭代慢,bug 可能存续多年,hack 有其合理性。但 agent 工具链的迭代周期是周级别的–maintainer 面对几十上百个 PR,你遇到的问题可能已经在某个 PR 里解决了。

一次 GitHub 搜索

1
搜索: agentmemory rohitg00 github issue onnxruntime xenova embedding Apple Silicon NEON

第一个结果就是 agentmemory 主仓库的最新 commit:

1
2
3
4
5
6
7
8
9
chore(deps): migrate @xenova/transformers to @huggingface/transformers v4 (#1096)

@xenova/transformers@2.x is deprecated and silently broken on Node 22+
(see #479). The project was renamed to @huggingface/transformers; same
Apache-2.0 license, same code. v4 ships onnxruntime-node/web and sharp
as hard deps.

Verified: 1424/1424 tests pass, build clean.
CI Node matrix: [20, 22] -> [20, 22, 24, 26] across ubuntu/macos

3 天前合入。维护者自己做了方向 A 想做的事,而且做得更彻底:不仅换了包名,还修正了 dtype 默认值(v4 默认 fp32,不显式传 dtype:"q8" 会静默回归为 3.5x 下载量 + 更慢推理),加了测试覆盖,扩展了 CI 矩阵。

唯一的问题是:还没发版。 main 比 v0.9.28 领先 3 个 commit,修复在 main 上但 npm 包还没更新。

临时方案:Ollama embedding

上游修复还在路上,但 BM25-only 已经跑了几个月,不能继续等。翻 agentmemory 源码,发现它原生支持 embedding 和 chat 分离的 base URL:

1
2
3
4
OPENAI_EMBEDDING_BASE_URL  - embedding 专用 base URL(默认复用 OPENAI_BASE_URL)
OPENAI_EMBEDDING_API_KEY   - embedding 专用 API key
OPENAI_EMBEDDING_MODEL     - 模型名
OPENAI_EMBEDDING_DIMENSIONS - 维度(非内置模型必须设)

源码注释直接举了 Ollama 的例子。本地已有 Ollama + Qwen3-Embedding-0.6B-GGUF(1024 维),4 行配置搞定:

1
2
3
4
5
6
# ~/.agentmemory/.env
EMBEDDING_PROVIDER=openai
OPENAI_EMBEDDING_BASE_URL=http://localhost:11434/v1
OPENAI_EMBEDDING_API_KEY=ollama
OPENAI_EMBEDDING_MODEL=modelscope.cn/Qwen/Qwen3-Embedding-0.6B-GGUF
OPENAI_EMBEDDING_DIMENSIONS=1024

Ollama 是独立进程,不阻塞 Node 事件循环。每 batch 32 条,~2s/batch,通过 OpenAI 兼容的 /v1/embeddings 端点通信。零 patch,零旁路。

索引回填

配好 Ollama 后遇到第二个坑:DROP_STALE_INDEX=true 只丢弃向量索引,不回填旧数据。源码逻辑:

1
2
// src-B8J9Exum.mjs:19677
if (bm25Index.size === 0) rebuildIndex(kv)  // 只有 BM25 空了才完整重建

rebuildIndex() 同时重建 BM25 + 向量索引。但旧 BM25 索引还在(与 embedding 维度无关),所以条件不满足,重建不触发。结果:旧数据有 BM25(关键词搜索可用),但向量索引为空(语义搜索缺失)。

解决方案:手动删 BM25 索引文件 + 重启,强制触发完整重建:

1
2
rm -f ~/.agentmemory/data/state_store.db/mem%3Aindex%3Abm25*.bin
launchctl kickstart -k gui/$(id -u)/com.agentmemory.server

实测:212 个 observations,48 个 embedding 请求,~90s 完成重建。重建后 DROP_STALE_INDEX 改回 false

时序

1
2
3
4
5
6
7
2026-05-29  agentmemory v0.9.24 发布(当前安装版本)
2026-06-02  embedding 首次出问题,开始排查
2026-06-07  v0.9.27 发布
2026-06-19  v0.9.28 发布(不含修复)
2026-07-29  PR #1096 合入 main(xenova -> huggingface/transformers v4)
2026-08-01  本文:查上游 -> Ollama 临时方案 -> 索引回填
????        v0.9.29 发布(预计包含 PR #1096)

后续

等 v0.9.29 发布后:

1
2
3
npm install -g @agentmemory/agentmemory  # 升级
# 删 .env 中的 Ollama embedding 配置,改回 EMBEDDING_PROVIDER=local
# DROP_STALE_INDEX=true 重启一次(1024 -> 384 维度变),再改回 false

之前在 node_modules 里的 hack patches 不用清理,npm install -g 会覆盖整个目录。

总结

1
2
3
4
5
6
              本能路径              正确路径
发现问题  ->  读源码找根因       ->  搜上游 issue/PR
          ->  patch 编译产物      ->  发现维护者已修
          ->  排除一种可能        ->  等发版 or 提前合入
          ->  换另一种 patch      ->  临时方案用配置解决
          ->  ...8 次循环         ->  1 次搜索解决

不是说 hack 不对。传统软件、你自己的代码、没有上游的东西,hack 是合理的。但对于快速迭代的 agent 基础设施:

  1. 先查上游 – issue、PR、release notes,一般 issue 就够
  2. 等上游修 – 维护者面对几十上百个 PR,你遇到的问题大概率已有 PR
  3. 提前合入 – 如果等不及,cherry-pick 上游 PR 到本地,不要自己从头 patch
  4. 配置优于 patch – 临时方案优先用产品自带的配置选项(如 OPENAI_EMBEDDING_BASE_URL),不要改源码

8 次 hack 花了几个小时,一次 GitHub 搜索花了 10 秒。差的不是技术能力,是第一步的方向选择。

本文由作者按照 CC BY 4.0 进行授权