六维教程

向量索引与相似度搜索

传统数据库靠精确匹配找数据,按主键、按关键词。但有些场景要找”意思相近”的内容,比如搜”如何训练猫”要能召回讲”猫咪教养”的文档,关键词匹配做不到,得靠向量相似度搜索。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 就能跑相似度搜索。重点是创建索引前要先确定嵌入模型,维度和度量都跟着模型和应用场景走,创建后改不了。下一篇讲元数据过滤和索引管理,让检索结果更精准。

下一篇 元数据过滤与索引管理

上一篇
后端校验与集成
下一篇
元数据过滤与索引管理