1. OpenClaw 记忆检索为什么绕不开 Embedding 与向量存储
OpenClaw 的 Agent Memory 本质上是一套“把对话、文件、会话记录变成可检索记忆”的机制,而 Embedding 引擎与向量存储就是这套机制的底座。简单说,Embedding 负责把一段文本变成一串高维浮点数,向量存储负责把这串数字存下来并支持“找最像的几条”。它适合谁?适合正在给 Agent 加长期记忆、又不想一上来就上重型向量数据库的开发者。你只要有一台能跑 Node.js 的机器,就能把文本切分、向量化、sqlite-vec 持久化、相似度检索这条链路跑通。
我试过用关键词检索去捞历史上下文,结果“我昨天提到的项目”和“我之前启动的那个工作”在字面上完全不重叠,关键词直接失效。换成 Embedding 之后,这两句话的向量距离很近,检索能命中。这就是语义检索和关键词检索的根本差别:前者比的是“意思像不像”,后者比的是“字面有没有”。
这一篇聚焦落地:从 chunkMarkdown 切分、embedding 生成、sqlite-vec 虚拟表创建,到相似度查询验证,给出可复制的配置片段。同时说明怎么把 endpoint 改到 TaoToken 统一 Key/API 通道,让 OpenAI 兼容的 Embedding 调用复用同一套调用方式,不用为每个供应商单独维护一套鉴权逻辑。
2. TaoToken 前置:统一 Key 与 API 通道怎么接
在动手写向量存储之前,先把 Embedding 的调用出口定下来。OpenClaw 支持 OpenAI、Gemini、Voyage、Mistral、Ollama、Local 六类供应商,但如果你每个供应商都单独配 Key、单独记 endpoint,维护成本会很高。TaoToken 提供的是 OpenAI 兼容的统一 Key/API 通道,你可以把 Embedding 请求的 base URL 指到https://taotoken.net/api,用同一个 Key 走通对话和向量化。
先拿 Key。打开https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite,创建一个 API Key,复制保存。注意这个 Key 只显示一次,丢了只能重建。拿到之后,OpenClaw 侧需要配置三件套:Base URL、API Key、Model ID。Base URL 填https://taotoken.net/api,Model ID 填你要用的 Embedding 模型,比如text-embedding-3-small。
这里有个容易踩的坑:OpenClaw 的 OpenAI provider 默认会拼/v1/embeddings,所以 base URL 不要带/v1,否则会变成/v1/v1/embeddings直接 404。正确写法是 base URL 只到/api,路径由客户端补全。如果你用的是 Claude Code 这类工具做润色或辅助编码,接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有完整的 endpoint 对照表。
配置好之后,建议先用模型对话页做一次连通性验证:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite。在页面上选一个模型发一句话,能正常返回就说明 Key 和通道没问题。这一步别跳过,因为 Embedding 报错往往比对话更隐蔽,先确认通道通,再排查向量逻辑,能省很多时间。
3. 可复制配置:切分、向量化与 sqlite-vec 持久化
这一节给可直接粘贴的配置和代码。先看 OpenClaw 的 memorySearch 配置片段,路径是agents.defaults.memorySearch:
agents: defaults: memorySearch: provider: "openai" fallback: "local" model: "text-embedding-3-small" baseUrl: "https://taotoken.net/api" apiKey: "${TAOTOKEN_API_KEY}" chunking: tokens: 512 overlap: 64 cache: enabled: true maxEntries: 10000 remote: batch: enabled: true wait: true concurrency: 4 pollIntervalMs: 5000 timeoutMs: 600000chunking.tokens: 512表示每个分块目标 512 token,overlap: 64表示相邻分块重叠 64 token。切分算法里有个换算:1 token 约等于 4 个 UTF-8 字符,所以 maxChars 是512 * 4 = 2048字符。重叠部分从当前分块尾部往前累计,直到达到64 * 4 = 256字符,作为下一分块的开头,保证语义连贯。
sqlite-vec 的加载与虚拟表创建是持久化的关键。加载逻辑在loadSqliteVecExtension()里,先enableLoadExtension(true),再用 npm 包提供的默认路径sqliteVec.load(db)。虚拟表用vec0引擎:
CREATE VIRTUAL TABLE IF NOT EXISTS chunks_vec USING vec0( id TEXT PRIMARY KEY, embedding FLOAT[1536] );维度必须和模型输出一致。text-embedding-3-small是 1536 维,text-embedding-3-large是 3072 维。如果维度不匹配,OpenClaw 会先dropVectorTable()再重建,所以换模型时旧向量会被清掉,需要重新索引。chunks 表里同时存了一份 JSON 序列化的 embedding 作为回退,chunks_vec 才是检索主力。
相似度查询用vec_distance_euclidean或vec_distance_cosine:
SELECT chunks.id, chunks.text, chunks.path FROM chunks_vec INNER JOIN chunks ON chunks_vec.id = chunks.id WHERE chunks.source = 'memory' ORDER BY vec_distance_euclidean(chunks_vec.embedding, ?) ASC LIMIT 10;所有向量在入库前都会过sanitizeAndNormalizeEmbedding():先把 NaN、Infinity 替换成 0,再算 L2 范数做归一化。归一化之后,欧几里得距离和余弦距离等价,检索结果更稳定。
4. 验证请求:从文本到相似度检索的完整跑通
配置写完,得验证整条链路。第一步,确认 sqlite-vec 扩展加载成功。在 OpenClaw 启动日志里找loadSqliteVecExtension的返回,ok: true才算过。如果返回ok: false,看 error 字段,常见的是扩展路径不对或 Node 版本不兼容。
第二步,写入两条语义相近但字面不同的文本,触发索引。比如:
文本A:我昨天提到的项目进度需要同步 文本B:之前启动的那个工作要更新状态第三步,用查询文本“那个项目的进展”去检索。如果 Embedding 生效,A 和 B 都应该出现在结果里,且距离值较小。你可以直接在 SQLite 里跑:
SELECT chunks.text, vec_distance_cosine(chunks_vec.embedding, ?) AS dist FROM chunks_vec INNER JOIN chunks ON chunks_vec.id = chunks.id ORDER BY dist ASC LIMIT 5;把?换成查询文本的向量。实测下来,语义相近的文本距离通常在 0.1 到 0.3 之间,完全不相关的会到 0.8 以上。如果所有距离都接近 1,说明向量没归一化或模型没生效。
第四步,验证缓存命中。第二次索引相同文本时,embedding_cache表里应该已有记录,不会重复调用 API。缓存键是(provider, model, provider_key, hash)四元组,其中provider_key是 API Key 的哈希,防止不同 Key 的向量混用。你可以查:
SELECT COUNT(*) FROM embedding_cache;索引前后对比这个数字,如果第二次没增长,说明缓存命中。
5. 本篇常见错排查:401、local proxy failed 与 reading choices
报错一:401 Unauthorized。这是 Key 或 base URL 配错。先确认apiKey环境变量真的注入进去了,再确认 base URL 是https://taotoken.net/api而不是带/v1。如果用的是 OpenAI provider 但 Key 是 TaoToken 的,检查 provider 是否被识别为 openai 兼容模式。401 还有一种情况是 Key 过期或被删,去 API Keys 页面重建一个。
报错二:local proxy failed。这个通常出现在 fallback 到 local provider 时,本地模型文件不存在或 node-llama-cpp 没装好。如果你没打算用本地模型,把fallback设成"none",避免它去尝试本地加载。如果确实要用 local,确认 GGUF 文件路径正确,Node 版本在 24+。
报错三:reading choices或Cannot read properties of undefined (reading 'choices')。这是响应体结构和预期不符,多半是 endpoint 返回了非 OpenAI 格式的错误页。检查 base URL 是否被重定向,或者模型名是否拼错导致返回 404 HTML。用 curl 直接打一次:
curl -X POST https://taotoken.net/api/v1/embeddings \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"text-embedding-3-small","input":"test"}'如果 curl 正常但 OpenClaw 报错,就是客户端拼接路径的问题。
报错四:OAuth相关。OpenClaw 某些 provider 走 OAuth 流程,如果你混用了 API Key 和 OAuth 配置,会鉴权冲突。Embedding 场景统一用 API Key,别开 OAuth。Codex 的auth.json里如果同时有 OAuth token 和 API Key,优先读 OAuth,导致 401。清掉 OAuth 字段,只留 Key。
排查顺序建议:先 curl 验证通道,再看 OpenClaw 日志里的 provider 选择,最后查 sqlite-vec 加载状态。三件套 Base URL、Key、Model ID 任何一个错都会在前面几步暴露。
6. 把 Embedding 通道固定下来,长期复用
整条链路跑通后,最有价值的动作是把 endpoint 固定到 TaoToken 统一通道。这样你换模型、加供应商、做批量索引,都复用同一个 Key 和同一套调用方式,不用每次改配置。长期做编码或 Agent 的,可以看 Coding Plan:https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite,把对话和向量化都收口到一个通道里。
验证模型是否可用,用模型对话页最快:https://taotoken.net/models?utm_source=taotoken_aicg_blog_end&utm_content=models&utm_campaign=rewrite。接入细节和 endpoint 对照在文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite。Key 管理在控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。
最后一个实用技巧:批量索引时把concurrency设成 4 到 8,别开太高。Embedding API 对并发有限制,开太高会触发 429,然后走重试退避,反而更慢。缓存maxEntries设 10000 够用,超过后按updated_at删最旧的,不会撑爆数据库。sqlite-vec 的虚拟表维度一旦定下就别频繁换模型,换一次要全量重建索引,成本不低。