进阶 📋 6 个步骤 第 470 / 470 篇

用 Cloudflare Vectorize 做无服务器向量检索与 RAG(Workers AI 嵌入 + D1 存原文)

Cloudflare Vectorize 无服务器向量检索与 RAG 实操:wrangler 建索引、Workers AI 生成嵌入、D1 存原文,query 取 topK 相似向量后拼上下文调 LLM 生成答案,含维度定死与计费注意。

2026.09.24· 18 分钟阅读· 约 1223 字· ☁️ Cloudflare / 🗂️ 向量数据库

向量检索(把文本变成向量、按相似度查找)是 RAG 的底座。自己运维一个向量库(Milvus / Qdrant / pgvector)要管服务器,而 Cloudflare Vectorize 是绑定在 Workers 上的无服务器向量库——配一下就能用,按量计费、自动扩缩。配合 Workers AI 的嵌入模型生成向量、用 D1 存原文,可以在边缘做出一个零运维的语义检索 + RAG。本教程从建索引、插向量、查相似到拼成问答,在命令行一步一步跑通。

☁️ 本教程适合:想做文档语义搜索、FAQ 召回、边缘 RAG 的开发者。你需要一个 Cloudflare 账号并安装 Node.js 16.17+(Wrangler 要求),不需要自备向量数据库。

先搞懂:向量检索与 RAG 在边缘怎么搭

一条 RAG 链路:原文 → 切成段 → 用嵌入模型转成向量 → 存进 Vectorize;用户提问 → 同样转成向量 → 在 Vectorize 里查最相似的几段 → 把原文(从 D1 取)拼进提示 → 调 LLM 生成答案。Vectorize 只存向量和可选的 metadata,不存原文,所以原文要另外用 D1 或 metadata 配对。

组件职责
Workers AI把文本生成嵌入向量
Vectorize存向量、做相似查询
D1存原文,按 id 取回
Worker编排上述步骤、响应请求

Step 1:建 Worker 项目并登录

1 用 create cloudflare 起项目

用官方脚手架建一个 TypeScript Worker(选 Worker only、不部署),然后登录 Cloudflare。

npm create cloudflare@latest -- vectorize-tutorial
# 选择:Worker only / TypeScript / git=yes / deploy=no
cd vectorize-tutorial
npx wrangler login
💡 非交互环境用 CI=true npm create cloudflare@latest vectorize-tutorial --type=simple --git --ts --deploy=false 直接生成骨架。

Step 2:创建 Vectorize 索引

2 维度与度量建后不可改

索引名用小写字母、数字、短横线,不超过 32 字符。维度要和你的嵌入模型一致(bge-base 是 768,text-embedding-3-small 是 1536)。维度与距离度量一旦创建不能改,建前确认好。

npx wrangler vectorize create doc-index --dimensions=768 --metric=cosine

然后把绑定写进 wrangler.toml

[[vectorize]]
binding = "VECTORIZE_INDEX"
index_name = "doc-index"

维度/度量定死:cosine 适合语义相似,euclidean / dot-product 也可选但要和嵌入模型配套。建错只能新建索引、重新插向量,没有改的入口。

Step 3:生成嵌入并插入向量

3 Workers AI 嵌 + D1 存原文

在 Worker 里用 env.AI.run('@cf/baai/bge-base-en-v1.5', ...) 生成嵌入,把向量插进 Vectorize,同时把原文写进 D1 以便回取。每个向量要有唯一字符串 id,values 长度必须和索引维度一致。

// src/index.ts(插入接口 /insert?text=...&id=...)
const emb = await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: text });
await env.VECTORIZE_INDEX.insert([
  { id, values: emb.data[0], metadata: { url: '/docs/' + id } },
]);
await env.DB.prepare('INSERT INTO docs (id, text) VALUES (?, ?)').bind(id, text).run();
💡 D1 需先建表:npx wrangler d1 execute db --remote --command "CREATE TABLE IF NOT EXISTS docs (id TEXT PRIMARY KEY, text TEXT)"。向量和原文用同一个 id 关联,查询时先拿向量再取原文。

Step 4:查询相似向量

4 query 接口 + topK + 元数据过滤

把用户问题也嵌入,调用 VECTORIZE_INDEX.query(),指定 topK 取最相似的若干条。Vectorize 支持在相似度之外叠加 metadata 过滤,做多租户隔离或分类筛选。

const q = await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: question });
const matches = await env.VECTORIZE_INDEX.query(q.data[0], {
  topK: 5,
  // filter: { url: { eq: '/docs/intro' } },   // 可选的 metadata 过滤
});
const ids = matches.matches.map((m) => m.id);

向量不含原文:query 只返回 id 和 score,要回答必须再用 ids 去 D1 取回原文。别把"查到了向量"当成"查到了内容"。

Step 5:拼成最小 RAG

5 检索 → 拼上下文 → 调 LLM

把取回的原文拼进提示,调一个生成模型产出答案。下面是 fetch handler 的最小骨架:

export default {
  async fetch(req, env) {
    const question = new URL(req.url).searchParams.get('q') || '';
    const q = await env.AI.run('@cf/baai/bge-base-en-v1.5', { text: question });
    const top = await env.VECTORIZE_INDEX.query(q.data[0], { topK: 5 });
    const ctx = [];
    for (const m of top.matches) {
      const row = await env.DB.prepare('SELECT text FROM docs WHERE id=?').bind(m.id).first();
      if (row) ctx.push(row.text);
    }
    const ans = await env.AI.run('@cf/meta/llama-3.1-8b-instruct', {
      messages: [{ role: 'user', content: '根据资料回答:' + ctx.join('\n') + '\n问题:' + question }],
    });
    return Response.json({ answer: ans.response });
  },
} satisfies ExportedHandler;
🔑 回取原文后务必做"空值兜底":向量命中但 D1 缺行时跳过,别让 undefined 拼进提示导致报错。

上线与计费注意

6 部署与配额

写完用 npx wrangler deploy 把 Worker 推到全球边缘。Vectorize 在 Workers Free / Paid 都可用,按向量条数与查询量计费。

npx wrangler deploy

免费额度与冷启动:免费计划有向量条数与查询配额上限,超限需升 Paid。嵌入模型每次调用都计费,批量插入时控制频率;生产环境加缓存,避免相同问题反复嵌入。

常见问题

现象原因与处理
插入报维度不匹配嵌入模型输出维度与索引 dimensions 不一致,重建索引对齐
查询返回空先确认已插入向量;检查 id 唯一性与 topK 取值
答案与资料无关D1 取回原文失败,检查 ids→原文关联与空值兜底
← 返回教程中心