向量化(向量数据库)
入门
向量化索引在您的 NuxtHub 项目中,在 nuxt.config.ts
文件中的 hub.vectorize
对象中管理。可以使用单独的键创建多个索引。
export default defineNuxtConfig({
hub: {
vectorize: {
<index-name>: {
dimensions: <number>,
metric: "dot-product" | "cosine" | "euclidean",
metadataIndexes: {
<property>: "string" | "number" | "boolean"
}
}
}
}
})
然后,确保 部署您的项目 以创建索引。
部署后,您可以使用 hubVectorize()
服务器组合,使用 远程存储 与向量数据库交互,使用 npx nuxt dev --remote
命令。
创建索引
创建索引需要三个输入
使用现有索引
- 在 Cloudflare 仪表板 → Workers & Pages → 您的 Pages 项目
- 转到设置 → 绑定 → 添加
- 选择向量化数据库
- 将变量名称设置为
VECTORIZE_<NAME>
。整个变量名称应大写。 - 选择现有的向量化索引
- 将变量名称设置为
- 将索引配置添加到
nuxt.config.ts
中的hub.vectorize
。名称应与变量名称中使用的<name>
相匹配,并且应为小写。
创建元数据索引
向量化允许您在索引中为每个向量添加最多 10 KiB 的元数据,并且还提供了在查询向量时对该元数据进行过滤的功能。为此,您需要将元数据字段指定为您的向量化索引的“元数据索引”。
hubVectorize()
服务器组合,返回一个 向量化索引。
const index = hubVectorize("<index>")
IntelliSense 将根据 hub.vectorize
中配置的索引建议 <index>
。
insert()
将向量插入索引。
const vectorsToInsert = [
{ id: "123", values: [32.4, 6.5, 11.2, 10.3, 87.9] },
{ id: "456", values: [2.5, 7.8, 9.1, 76.9, 8.5] },
];
const inserted = await index.insert(vectorsToInsert);
查看 向量对象 上的所有可用属性。
如果具有相同向量 ID 的向量已存在于索引中,则只会插入具有新 ID 的向量。
如果您需要更新现有向量,请使用 upsert 操作。
- 如果在向量化索引中两次插入相同的向量 ID,则索引将反映第一个添加的向量。
- 如果在向量化索引中两次更新相同的向量 ID,则索引将反映第二个添加的向量。
- 如果您想覆盖已存在于索引中的向量 ID 的向量值,请使用更新操作。
返回值
upsert()
将向量更新插入索引。
const vectorsToUpsert = [
{ id: "123", values: [32.4, 6.5, 11.2, 10.3, 87.9] },
{ id: "456", values: [2.5, 7.8, 9.1, 76.9, 8.5] },
{ id: "768", values: [29.1, 5.7, 12.9, 15.4, 1.1] },
];
const upserted = await index.upsert(vectorsToUpsert);
更新插入操作将在向量 ID 不存在的情况下将向量插入索引,并覆盖具有相同 ID 的向量。
查看 向量对象 上的所有可用属性。
返回值
query()
使用提供的向量查询索引,返回基于配置的距离度量的最近向量的分数。
const queryVector = [32.4, 6.55, 11.2, 10.3, 87.9];
const matches = await index.query(queryVector);
console.log(matches)
/*
{
"count": 5,
"matches": [
{ "score": 0.999909486, "id": "5" },
{ "score": 0.789848214, "id": "4" },
{ "score": 0.720476967, "id": "1234" },
{ "score": 0.463884663, "id": "6" },
{ "score": 0.378282232, "id": "1" }
]
}
*/
查询索引或向量搜索使您能够通过提供一个输入向量来搜索索引,并返回基于 配置的距离度量 的最近向量。
可选地,您可以应用 元数据过滤器 或 命名空间 来缩小向量搜索空间。
参数
将用于驱动相似性搜索的输入向量。
查询选项。
const matches = await index.query(queryVector, {
topK: 5,
returnValues: true,
returnMetadata: "all",
});
返回值
返回 VectorizeMatches
。
对评分精度和查询精度的控制
在查询向量时,您可以指定使用高精度评分,从而提高查询匹配分数的精度以及查询结果的准确性,或者使用近似评分来加快响应时间。使用近似评分,返回的分数将是您的查询与返回的向量之间实际距离/相似性的近似值。
通过在查询中设置 returnValues: true
来启用高精度评分;这告诉向量化获取并使用匹配项的原始向量值,这使得能够计算匹配项的精确分数,从而提高结果的准确性。
getByIds()
通过其 ID 检索指定的向量,包括值和元数据。
const ids = ["11", "22", "33", "44"];
const vectors = await index.getByIds(ids);
参数
应返回的向量 ID 列表。
返回值
返回 VectorizeVector
。
deleteByIds()
从当前索引中删除提供的向量 ID。
const idsToDelete = ["11", "22", "33", "44"];
const deleted = await index.deleteByIds(idsToDelete);
参数
应删除的向量 ID 列表。
返回值
describe()
直接检索给定索引的配置,包括其配置的 dimensions
和距离 metric
。
const details = await index.describe();
返回值
向量
向量对象
向量表示机器学习模型的向量嵌入输出。
在索引中标识向量的唯一 string
。这应映射回生成向量值的文档、对象或数据库标识符的 ID。
索引中的可选分区键。操作在每个命名空间执行,因此这可用于在较大索引中创建隔离段。
作为向量嵌入本身的 number
、Float32Array
或 Float64Array
数组。这必须是一个密集数组,并且此数组的长度必须与索引上配置的 dimensions
相匹配。
一组可选的键值对,可用于与向量一起存储附加元数据。
const vectorExample = {
id: "12345",
values: [32.4, 6.55, 11.2, 10.3, 87.9],
metadata: {
key: "value",
hello: "world",
url: "r2://bucket/some/object.json",
},
};
维度
维度由用于生成它们的机器学习 (ML) 模型的输出大小决定,并且是模型如何将特征编码和描述为向量嵌入的函数。
输出维度的数量可以决定向量搜索精度、搜索性能(延迟)以及索引的总体大小。较小的输出维度可能更快地搜索,这对于面向用户的应用程序很有用。较大的输出维度可以提供更准确的搜索,尤其是在更大的数据集或输入非常相似的数据集上。
创建索引的维度数不可更改。索引期望接收具有相同维度数的密集向量。
下表重点介绍了一些示例嵌入模型及其输出维度
模型 / 嵌入 API | 输出维度 | 用例 |
---|---|---|
Workers AI - @cf/baai/bge-base-en-v1.5 | 768 | 文本 |
OpenAI - ada-002 | 1536 | 文本 |
Cohere - embed-multilingual-v2.0 | 768 | 文本 |
Google Cloud - multimodalembedding | 1408 | 多模态(文本、图像) |
距离度量
距离度量是用来判断向量之间距离的函数。向量化索引支持以下距离度量
度量 | 详情 |
---|---|
余弦 | 距离从 -1 (最不相似)到 1 (完全相同)测量。 0 表示正交向量。 |
欧几里得 | 欧几里得 (L2) 距离。 0 表示相同向量。正数越大,向量之间的距离越远。 |
点积 | 负点积。较大的负值 *或* 较小的正值表示更相似的向量。分数 -1000 比 -500 更相似,分数 15 比 50 更相似。 |
确定向量之间的相似性可能是主观的,取决于用于表示结果向量嵌入中特征的机器学习模型。例如,使用 cosine
度量时,分数 0.8511
表示两个向量距离很近,但它们所代表的数据是否 *相似* 是模型能够代表原始内容的程度的函数。
查询向量时,您可以指定 Vectorize 使用以下任一项
- 高精度评分,这将提高查询匹配分数的精度以及查询结果的准确性。
- 为了更快地响应时间,使用近似评分。使用近似评分,返回的分数将是您的查询与返回向量之间真实距离/相似度的近似值。请参考 控制评分精度和查询准确性。
距离度量不能在索引创建后更改,并且每个度量都有不同的评分函数。
支持的向量格式
Vectorize 支持三种格式的向量
- 浮点数数组(转换为 JavaScript
number[]
数组)。 - 一个 Float32Array
- 一个 Float64Array
在大多数情况下,在处理其他 API 时,number[]
数组是最简单的,也是大多数机器学习 API 的返回类型。
元数据 & 过滤
元数据是一组可选的键值对,可以在插入或更新时附加到向量,并允许您嵌入或共置有关向量本身的数据。
.
),不能包含双引号字符 ("
),也不能以美元字符 ($
) 开头。元数据可用于
- 包含对象存储密钥、数据库 UUID 或其他标识符以查找向量嵌入所代表的内容。
- 原始内容(最多 元数据限制),这可以让您跳过对较小内容的额外查找。
- 日期、时间戳或其他描述向量嵌入生成时间或生成方式的元数据。
例如,表示图像的向量嵌入可以包含生成它的 blob 的路径、格式和类别查找
{ id: '1', values: [32.4, 74.1, 3.2, ...], metadata: { path: 'r2://bucket-name/path/to/image.png', format: 'png', category: 'profile_image' } }
元数据过滤
除了向查询提供输入向量之外,您还可以通过 与每个向量关联的向量元数据 进行过滤。查询结果仅包含与 filter
条件匹配的向量,这意味着 filter
首先应用,然后从过滤后的集合中获取 topK
结果。
通过使用元数据过滤来限制查询范围,您可以根据特定客户 ID、租户、产品类别或您与向量关联的任何其他元数据进行过滤。
string
、number
和 boolean
元数据索引。请参考 创建元数据索引 以获取详细信息。Vectorize 默认支持 命名空间 过滤。
创建元数据索引
元数据索引都在 nuxt.config.ts
中的索引配置中的 metadataIndexes
对象中进行管理。
export default defineNuxtConfig({
hub: {
vectorize: {
tutorial: {
dimensions: 32,
metric: "cosine",
metadataIndexes: {
// <property>: "string" | "number" | "boolean"
url: "string",
"nested.property": "boolean"
}
}
}
}
})
支持的元数据索引属性类型是 string
、number
和 boolean
类型。
使用 .
(点)定义嵌套属性。
对于类型为数字的元数据索引,索引的数字精度为 float64。
对于类型为字符串的元数据索引,每个向量都会索引字符串数据的头 64B,在 UTF-8 字符边界上截断,以该限制内的最长格式良好的 UTF-8 子字符串为准,因此向量可对每个索引属性的值的前 64B 进行过滤。
支持的操作
query()
方法上的可选 filter
属性指定元数据过滤器
操作符 | 描述 |
---|---|
$eq | 等于 |
$ne | 不等于 |
filter
必须是非空对象,其紧凑 JSON 表示必须小于 2048 字节。filter
对象键不能为空,不能包含" | .
(点用于嵌套),不能以$
开头,也不能超过 512 个字符。filter
对象非嵌套值可以是string
、number
、boolean
或null
值。
有效的 filter
示例
隐式 $eq
运算符
{ "streaming_platform": "netflix" }
显式运算符
{ "someKey": { "$ne": true } }
隐式逻辑 AND
,带有多个键
{ "pandas.nice": 42, "someKey": { "$ne": true } }
键使用 .
(点)定义嵌套
{ "pandas.nice": 42 }
// looks for { "pandas": { "nice": 42 } }
限制
您每个向量可以存储最多 10KiB 的元数据,每个 Vectorize 索引可以创建最多 10 个元数据索引。
对于类型为 number
的元数据索引,索引的数字精度为 float64。
对于类型为 string
的元数据索引,每个向量都会索引字符串数据的头 64B,在 UTF-8 字符边界上截断,以该限制内的最长格式良好的 UTF-8 子字符串为准,因此向量可对每个索引属性的值的前 64B 进行过滤。
有关限制的完整列表,请参见 Vectorize 限制。
示例
添加元数据
使用以下索引定义
export default defineNuxtConfig({
hub: {
vectorize: {
tutorial: {
dimensions: 32,
metric: "cosine",
metadataIndexes: {
streaming_platform: "string",
"property.nested": "boolean"
}
}
}
}
})
可以在 插入或更新向量 时添加元数据。
const index = hubVectorize("tutorial")
const newMetadataVectors: Array<VectorizeVector> = [
{
id: "1",
values: [32.4, 74.1, 3.2, ...],
metadata: { url: "/products/sku/13913913", streaming_platform: "netflix" },
},
{
id: "2",
values: [15.1, 19.2, 15.8, ...],
metadata: { url: "/products/sku/10148191", streaming_platform: "hbo" },
},
{
id: "3",
values: [0.16, 1.2, 3.8, ...],
metadata: { url: "/products/sku/97913813", streaming_platform: "amazon" },
},
{
id: "4",
values: [75.1, 67.1, 29.9, ...],
metadata: { url: "/products/sku/418313", streaming_platform: "netflix" },
},
{
id: "5",
values: [58.8, 6.7, 3.4, ...],
metadata: { url: "/products/sku/55519183", streaming_platform: "hbo" },
},
];
// Upsert vectors with added metadata, returning a count of the vectors upserted and their vector IDs
const upserted = await index.upsert(newMetadataVectors);
查询示例
使用 query()
方法
const queryVector: Array<number> = [54.8, 5.5, 3.1, ...];
const originalMatches = await index.query(queryVector, {
topK: 3,
returnValues: true,
returnMetadata: 'all',
});
没有元数据过滤的结果
{
"matches": [
{
"id": "5",
"score": 0.999909486,
"values": [58.79999923706055, 6.699999809265137, 3.4000000953674316],
"metadata": {
"url": "/products/sku/55519183",
"streaming_platform": "hbo"
}
},
{
"id": "4",
"score": 0.789848214,
"values": [75.0999984741211, 67.0999984741211, 29.899999618530273],
"metadata": {
"url": "/products/sku/418313",
"streaming_platform": "netflix"
}
},
{
"id": "2",
"score": 0.611976262,
"values": [15.100000381469727, 19.200000762939453, 15.800000190734863],
"metadata": {
"url": "/products/sku/10148191",
"streaming_platform": "hbo"
}
}
]
}
具有 filter
属性的相同 query()
方法支持元数据过滤。
const queryVector: Array<number> = [54.8, 5.5, 3.1, ...];
const metadataMatches = await index.query(queryVector, {
topK: 3,
filter: { streaming_platform: "netflix" },
returnValues: true,
returnMetadata: 'all',
});
带有元数据过滤的结果
{
"matches": [
{
"id": "4",
"score": 0.789848214,
"values": [75.0999984741211, 67.0999984741211, 29.899999618530273],
"metadata": {
"url": "/products/sku/418313",
"streaming_platform": "netflix"
}
},
{
"id": "1",
"score": 0.491185264,
"values": [32.400001525878906, 74.0999984741211, 3.200000047683716],
"metadata": {
"url": "/products/sku/13913913",
"streaming_platform": "netflix"
}
}
]
}
命名空间
命名空间提供了一种在索引中对向量进行分割的方法。例如,按客户、商家或商店 ID。
要将向量与命名空间关联,您可以在执行插入或更新操作时,选择性地提供 namespace: string
值。查询时,您可以将命名空间作为可选参数传递给查询,以在其中进行搜索。
命名空间的长度最多可达 64 个字符(字节),每个索引最多可以有 1000 个命名空间。有关更多详细信息,请参考 限制 文档。
在查询操作中指定命名空间后,只有该命名空间内的向量才会用于搜索。命名空间过滤在向量搜索之前应用,而不是之后。
插入带有命名空间的向量
// Mock vectors
// Vectors from a machine-learning model are typically ~100 to 1536 dimensions
// wide (or wider still).
const sampleVectors: Array<VectorizeVector> = [
{
id: "1",
values: [32.4, 74.1, 3.2, ...],
namespace: "text",
},
{
id: "2",
values: [15.1, 19.2, 15.8, ...],
namespace: "images",
},
{
id: "3",
values: [0.16, 1.2, 3.8, ...],
namespace: "pdfs",
},
];
// Insert your vectors, returning a count of the vectors inserted and their vector IDs.
const inserted = await index.insert(sampleVectors);
在命名空间内查询向量
// Your queryVector will be searched against vectors within the namespace (only)
const matches = await index.query(queryVector, {
namespace: "images",
});
命名空间与元数据过滤
两者 命名空间 和元数据过滤都可以缩小查询的向量搜索空间。在评估这两种过滤器类型时,请考虑以下事项
- 命名空间过滤器在元数据过滤器之前应用。
- 一个向量只能属于一个命名空间,其中包含记录在案的 限制。向量元数据可以包含多个键值对,最多可以达到 每个向量的元数据限制。元数据值支持不同的类型 (
string
、boolean
等),因此提供更高的灵活性。
限制
功能 | 当前限制 |
---|---|
每个帐户的索引数 | 100 个索引 |
每个向量的最大维度 | 1536 个维度 |
向量 ID 的最大长度 | 51 个字节 |
每个向量的元数据 | 10KiB |
带有值或元数据的最大返回结果 (topK ) | 20 |
没有值和元数据的最大返回结果 (topK ) | 100 |
最大更新批次大小(每批次) | 1000 |
最大索引名称长度 | 64 个字节 |
每个索引的最大向量数 | 5,000,000 |
每个索引的最大命名空间数 | 1000 个命名空间 |
最大命名空间名称长度 | 64 个字节 |
最大向量上传大小 | 100 MB |
每个 Vectorize 索引的最大元数据索引数 | 10 |
每个向量每个元数据索引的最大索引数据 | 64 个字节 |
了解有关 Cloudflare Vectorize 限制 的更多信息。
类型
VectorizeVector
参见 向量对象。
interface VectorizeVector {
/** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */
id: string;
/** The vector values */
values: VectorFloatArray | number[];
/** The namespace this vector belongs to. */
namespace?: string;
/** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */
metadata?: Record<string, VectorizeVectorMetadata>;
}
VectorizeMatches
特定查询的一组匹配 VectorizeMatch。
interface VectorizeMatches {
matches: VectorizeMatch[];
count: number;
}
VectorizeMatch
表示查询的匹配向量,以及其分数和(如果指定)匹配向量信息。
type VectorizeMatch = Pick<Partial<VectorizeVector>, "values"> &
Omit<VectorizeVector, "values"> & {
/** The score or rank for similarity, when returned as a result */
score: number;
};
VectorizeAsyncMutation
结果类型,指示 Vectorize 索引上的变异。实际变异是异步处理的,其中 mutationId
是操作的唯一标识符。
interface VectorizeAsyncMutation {
/** The unique identifier for the async mutation operation containing the changeset. */
mutationId: string;
}
VectorizeIndexInfo
有关现有索引的元数据。
interface VectorizeIndexInfo {
/** The number of records containing vectors within the index. */
vectorsCount: number;
/** Number of dimensions the index has been configured for. */
dimensions: number;
/** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */
processedUpToDatetime: number;
/** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */
processedUpToMutation: number;
}
示例
向量搜索
在本例中
- 从搜索查询生成嵌入向量。
- 使用嵌入向量查询 Vectorize 索引。
- 然后通过查询数据库获取 Vectorize 返回的 ID 以检索原始源数据。
在 https://developers.cloudflare.com/vectorize/reference/what-is-a-vector-database/#vector-search 上了解更多信息
import { z } from "zod";
interface EmbeddingResponse {
shape: number[];
data: number[][];
}
const Query = z.object({
query: z.string().min(1).max(256),
limit: z.coerce.number().int().min(1).max(20).default(10),
});
export default defineEventHandler(async (event) => {
const { query, limit } = await getValidatedQuery(event, Query.parse);
// 1. generate embeddings for search query
const embeddings: EmbeddingResponse = await hubAI().run("@cf/baai/bge-base-en-v1.5", { text: [query] });
const vectors = embeddings.data[0];
// 2. query vectorize to find similar results
const vectorize: VectorizeIndex = hubVectorize('jobs');
const { matches } = await vectorize.query(vectors, { topK: limit });
// 3. get details for matching items
const jobMatches = await useDrizzle().query.jobs.findMany({
where: (jobs, { inArray }) => inArray(jobs.id, matches.map((match) => match.id)),
with: {
department: true,
subDepartment: true,
},
});
// 4. add score to matches
const jobMatchesWithScore = jobMatches.map((job) => {
const match = matches.find((match) => match.id === job.id);
return { ...job, score: match!.score };
});
// 5. sort by score
return jobMatchesWithScore.sort((a, b) => b.score - a.score);
});
批量生成和导入
本例使用文本嵌入 AI 模型批量生成数据库表中所有数据的向量,使用 Nitro 任务。您可以通过 Nuxt DevTools 运行该任务。
import { jobs } from "../database/schema";
import { asc, count } from "drizzle-orm";
export default defineTask({
meta: {
name: "vectorize:seed",
description: "Generate text embeddings vectors",
},
async run() {
console.log("Running Vectorize seed task...");
const jobCount = (await useDrizzle().select({ count: count() }).from(tables.jobs))[0].count;
// process in chunks of 100 as that's the maximum supported by workers ai
const INCREMENT_AMOUNT = 100;
const totalBatches = Math.ceil(jobCount / INCREMENT_AMOUNT);
console.log(`Total items: ${jobCount} (${totalBatches} batches)`);
for (let i = 0; i < jobCount; i += INCREMENT_AMOUNT) {
console.log(`⏳ Processing items ${i} - ${i + INCREMENT_AMOUNT}...`);
const jobsChunk = await useDrizzle()
.select()
.from(tables.jobs)
.orderBy(asc(jobs.id))
.limit(INCREMENT_AMOUNT)
.offset(i);
// generate embeddings for job titles
const ai = hubAi();
const embeddings = await ai.run(
"@cf/baai/bge-base-en-v1.5",
{ text: jobsChunk.map((job) => job.jobTitle) },
{ gateway: { id: "new-role" } },
);
const vectors = embeddings.data;
const formattedEmbeddings = jobsChunk.map(({ id, ...metadata }, index) => ({
id,
metadata: { ...metadata },
values: vectors[index],
}));
// save vector embeddings to index
const index = hubVectorize('jobs');
await index.upsert(formattedEmbeddings);
console.log(`✅ Processed items ${i} - ${i + INCREMENT_AMOUNT}...`);
}
console.log("Vectorize seed task completed!");
return { result: "success" };
},
});