六维教程

向量索引与相似度搜索

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

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

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