向量索引与相似度搜索
传统数据库靠精确匹配找数据,按主键、按关键词。但有些场景要找”意思相近”的内容,比如搜”如何训练猫”要能召回讲”猫咪教养”的文档,关键词匹配做不到,得靠向量相似度搜索。Cloudflare Vectorize 是 Cloudflare 的向量数据库服务,专门用来存向量、做相似度检索。这篇从零讲清楚怎么创建索引、配置维度和度量、插入和查询向量。
Vectorize 是什么
Vectorize 是 Cloudflare 自家的向量数据库,运行在 Cloudflare Workers(Cloudflare 的边缘计算函数服务)之上。它存的是向量,也就是一串浮点数,每串向量代表一段文字、一张图片或一段音频的语义。查询时传入一个向量,它返回库里最相似的若干条。
它通常和 Workers AI(Cloudflare 的边缘 AI 推理服务)配合使用。Workers AI 负责把文字转成向量,Vectorize 负责存和查。两者都是 Worker 的绑定资源,代码里通过 env 变量直接调用,不用管密钥和基础设施。
和 Cloudflare 其他存储的定位区别
| 对比项 | Vectorize 向量数据库 | D1 关系型数据库 | KV 键值存储 |
|---|---|---|---|
| 数据形态 | 向量加元数据 | 表行数据 | 字符串值 |
| 查询方式 | 相似度检索 | SQL 精确查询 | 按 Key 存取 |
| 典型场景 | 语义搜索、推荐、RAG | 业务数据、复杂查询 | 配置、缓存 |
| 是否理解语义 | 是 | 否 | 否 |
简单记,D1 存结构化业务数据,KV 存配置和缓存,Vectorize 存向量做语义检索。三者常配合使用,比如 D1 存文档原文,Vectorize 存文档向量,KV 缓存热点查询结果。
创建索引
索引是 Vectorize 的基本单位,所有向量都插到某个索引里。创建索引用 wrangler 命令行,需要三个输入,索引名、维度数、距离度量。
npx wrangler vectorize create my-index --dimensions=768 --metric=cosine
执行后输出类似
✅ Successfully created a new Vectorize index: my-index
三个输入项说明
| 输入项 | 要求 | 示例 |
|---|---|---|
| 索引名 | kebab-case,全小写加短横线,账户内唯一 | prod-search-index |
| 维度数 | 固定数字,取决于嵌入模型 | 768、1024、1536 |
| 距离度量 | 三选一,cosine、euclidean、dot-product | cosine |
索引名每个账户下不能有同名活跃索引。维度和度量一经创建无法修改,要换就得重建索引并重新插入所有向量,所以创建前要确定好用哪个嵌入模型。
维度
维度是每条向量的长度,也就是数字的个数。它由生成向量的嵌入模型决定,模型输出多少维,索引就得配多少维,两者必须严格一致,否则插入会报错。
常见嵌入模型的维度
| 模型 | 来源 | 维度 | 语言 |
|---|---|---|---|
| @cf/baai/bge-base-en-v1.5 | Workers AI | 768 | 英文 |
| @cf/baai/bge-m3 | Workers AI | 1024 | 多语言含中文 |
| text-embedding-3-small | OpenAI | 1536 | 多语言 |
| text-embedding-ada-002 | OpenAI | 1536 | 多语言 |
维度影响三件事。检索精度,维度越高信息越丰富,大库里区分度更好。检索速度,维度越高计算量越大,延迟略增。存储成本,维度越高占的空间越多。
实际选型跟着嵌入模型走。中文场景用 bge-m3 就配 1024 维,英文用 bge-base 就配 768 维。不要凭感觉填数字,必须和模型输出对齐。
距离度量
距离度量决定怎么算两个向量”近不近”。Vectorize 支持三种
| 度量 | 分数范围 | 分数含义 | 适合场景 |
|---|---|---|---|
| cosine | -1 到 1 | 越接近 1 越相似,0 表示正交 | 语义搜索、文本匹配 |
| euclidean | 0 到正无穷 | 越接近 0 越相似,数越大越远 | 图像、聚类 |
| dot-product | 负无穷到正无穷 | 越小越相似,负得越多越像 | 已归一化的向量 |
dot-product 的分数理解要绕一下。它返回的是负的点积,所以分数越低越相似。比如 -1000 比 -500 更相似,15 比 50 更相似。
三种怎么选。绝大多数文本语义搜索用 cosine,它不受向量长度影响,最稳。图像特征或聚类场景用 euclidean。如果向量已经做过归一化处理,dot-product 计算最快,但前提是所有向量都归一化了。
度量同样无法创建后修改,选错只能重建索引。
绑定到 Worker
索引创建好后,要在 Worker 项目里声明绑定才能在代码里访问。打开 wrangler.toml 加一段
name = "vectorize-demo"
main = "src/index.js"
compatibility_date = "2024-09-01"
[[vectorize]]
binding = "MY_INDEX"
index_name = "my-index"
字段说明
| 字段 | 作用 |
|---|---|
| [[vectorize]] | 声明这是一个 Vectorize 绑定段 |
| binding | 代码里访问该索引的变量名,这里是 MY_INDEX |
| index_name | 要绑定的索引名,对应前面创建的 my-index |
配置好后,Worker 代码里通过 env.MY_INDEX 操作这个索引。同样不需要密钥,平台内部完成信任。
插入向量
插入用 insert 方法,传一个数组,每个元素包含 id 和 values,metadata 可选。为便于阅读,下面的示例用 5 维向量演示 API 用法,实际项目里向量由嵌入模型生成,维度必须和索引配置一致。
export default {
async fetch(request, env) {
const vectors = [
{ id: "1", values: [0.12, 0.45, 0.78, 0.23, 0.56] },
{ id: "2", values: [0.33, 0.66, 0.11, 0.88, 0.44] },
{ id: "3", values: [0.55, 0.22, 0.99, 0.14, 0.67], metadata: { category: "tech" } },
];
const result = await env.MY_INDEX.insert(vectors);
return Response.json(result);
},
};
返回的是一个 mutation 标识符。插入是异步的,要等几秒钟向量才真正可查。
insert 和 upsert 的区别是个重点
| 操作 | 重复 id 行为 | 用途 |
|---|---|---|
| insert | 已存在的 id 会被跳过,保留原来的 | 首次批量导入 |
| upsert | 已存在的 id 会被新值覆盖 | 更新已有向量 |
一条向量的完整结构
| 字段 | 必填 | 说明 |
|---|---|---|
| id | 是 | 唯一标识,字符串,建议和原文档 id 对应 |
| values | 是 | 向量数据,长度必须等于索引维度 |
| metadata | 否 | 键值对,用于过滤和附带信息 |
| namespace | 否 | 分区键,把索引切成互不干扰的段 |
metadata 的值可以是字符串、数字、布尔。键名有规则,不能含点号、双引号,不能以美元符号开头。metadata 最多存 10KB 每条向量。
查询向量
查询用 query 方法,传入一个向量,返回最相似的若干条。
export default {
async fetch(request, env) {
const queryVector = [0.15, 0.48, 0.80, 0.20, 0.60];
const matches = await env.MY_INDEX.query(queryVector, {
topK: 5,
returnValues: true,
returnMetadata: "all",
});
return Response.json(matches);
},
};
返回结构类似
{
"matches": [
{ "id": "1", "score": 0.98, "values": [0.12, 0.45, 0.78, 0.23, 0.56] },
{ "id": "3", "score": 0.91, "metadata": { "category": "tech" } }
]
}
查询参数说明
| 参数 | 默认值 | 作用 | 限制 |
|---|---|---|---|
| topK | 5 | 返回最相似的几条 | 最大 100 |
| returnValues | false | 是否返回向量本身 | 开启时 topK 限 50 |
| returnMetadata | “none” | 返回元数据的范围 | 见下表 |
| filter | 无 | 元数据过滤条件 | 下一篇详讲 |
returnMetadata 三个取值
| 取值 | 行为 | 注意 |
|---|---|---|
| none | 不返回 metadata | 默认,最快 |
| indexed | 只返回建了索引的字段 | 无延迟,长文本可能截断 |
| all | 返回全部 metadata | 较慢,topK 限 50 |
score 的解读要看度量。cosine 下分数越高越相似,euclidean 和 dot-product 下分数越低越相似。Vectorize 返回的结果已经按”最相似在前”排好序,直接用即可。
一个完整的相似度搜索示例
把绑定、插入、查询串起来,写一个能跑的接口。先批量插入几条向量,再根据传入的查询向量返回最相似的一条。
export default {
async fetch(request, env) {
const url = new URL(request.url);
// 初始化接口,插入示例数据
if (url.pathname === "/seed") {
const vectors = [
{ id: "doc-1", values: [0.12, 0.45, 0.78, 0.23, 0.56], metadata: { title: "边缘计算入门" } },
{ id: "doc-2", values: [0.33, 0.66, 0.11, 0.88, 0.44], metadata: { title: "对象存储基础" } },
{ id: "doc-3", values: [0.55, 0.22, 0.99, 0.14, 0.67], metadata: { title: "向量数据库" } },
];
await env.MY_INDEX.upsert(vectors);
return Response.json({ seeded: vectors.length });
}
// 查询接口
if (url.pathname === "/search" && request.method === "POST") {
const { vector } = await request.json();
const matches = await env.MY_INDEX.query(vector, {
topK: 3,
returnMetadata: "all",
});
return Response.json(matches);
}
return new Response("Not found", { status: 404 });
},
};
本地调试用 npx wrangler dev –remote。Vectorize 必须连到 Cloudflare 网络才能跑,本地模拟器无法模拟向量检索,所以要加 –remote。
实际项目里向量不会写死在代码里,而是用嵌入模型实时生成。把这段和 Workers AI 的嵌入模型接上,就是真正的语义搜索,下一篇会展开。
小结
Vectorize 把向量数据库的门槛降得很低。用 wrangler 一条命令建索引,wrangler.toml 加几行绑定,代码里通过 env 调用 insert 和 query 就能跑相似度搜索。重点是创建索引前要先确定嵌入模型,维度和度量都跟着模型和应用场景走,创建后改不了。下一篇讲元数据过滤和索引管理,让检索结果更精准。
下一篇 元数据过滤与索引管理