向量检索(把文本变成向量、按相似度查找)是 RAG 的底座。自己运维一个向量库(Milvus / Qdrant / pgvector)要管服务器,而 Cloudflare Vectorize 是绑定在 Workers 上的无服务器向量库——配一下就能用,按量计费、自动扩缩。配合 Workers AI 的嵌入模型生成向量、用 D1 存原文,可以在边缘做出一个零运维的语义检索 + RAG。本教程从建索引、插向量、查相似到拼成问答,在命令行一步一步跑通。
先搞懂:向量检索与 RAG 在边缘怎么搭
一条 RAG 链路:原文 → 切成段 → 用嵌入模型转成向量 → 存进 Vectorize;用户提问 → 同样转成向量 → 在 Vectorize 里查最相似的几段 → 把原文(从 D1 取)拼进提示 → 调 LLM 生成答案。Vectorize 只存向量和可选的 metadata,不存原文,所以原文要另外用 D1 或 metadata 配对。
| 组件 | 职责 |
|---|---|
| Workers AI | 把文本生成嵌入向量 |
| Vectorize | 存向量、做相似查询 |
| D1 | 存原文,按 id 取回 |
| Worker | 编排上述步骤、响应请求 |
Step 1:建 Worker 项目并登录
用官方脚手架建一个 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 索引
索引名用小写字母、数字、短横线,不超过 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:生成嵌入并插入向量
在 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();
npx wrangler d1 execute db --remote --command "CREATE TABLE IF NOT EXISTS docs (id TEXT PRIMARY KEY, text TEXT)"。向量和原文用同一个 id 关联,查询时先拿向量再取原文。Step 4:查询相似向量
把用户问题也嵌入,调用 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
把取回的原文拼进提示,调一个生成模型产出答案。下面是 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;
上线与计费注意
写完用 npx wrangler deploy 把 Worker 推到全球边缘。Vectorize 在 Workers Free / Paid 都可用,按向量条数与查询量计费。
npx wrangler deploy
免费额度与冷启动:免费计划有向量条数与查询配额上限,超限需升 Paid。嵌入模型每次调用都计费,批量插入时控制频率;生产环境加缓存,避免相同问题反复嵌入。
常见问题
| 现象 | 原因与处理 |
|---|---|
| 插入报维度不匹配 | 嵌入模型输出维度与索引 dimensions 不一致,重建索引对齐 |
| 查询返回空 | 先确认已插入向量;检查 id 唯一性与 topK 取值 |
| 答案与资料无关 | D1 取回原文失败,检查 ids→原文关联与空值兜底 |