简介
在前一个 Workers AI 嵌入实验中,文本变成了一个嵌入:由一组有序数字组成,用于表示不同含义之间的有用关系。嵌入不是原始文章,也不是生成的答案。只有当应用能够使用稳定的文档 ID 保存嵌入,并在之后查找相近向量时,嵌入才可以用于搜索。
Cloudflare Vectorize 是一个向量数据库。与围绕行和列设计的表不同,向量索引专门用于高效比较数值向量。创建索引时,必须确定两项兼容性设置:
- 维度(dimensions) — 每个向量包含多少个数字;
- 距离度量(distance metric) — Vectorize 如何判断哪些向量彼此最接近。
你将创建一个 384 维索引,用于存储 Cloudflare 托管的 @cf/baai/bge-small-en-v1.5 嵌入,并选择余弦距离,也就是 A04 中介绍的基于方向的比较方式。你还将为 category 和 published 添加元数据索引,插入三个很小的合成帮助文章向量,等待异步变更变得可读,并确认三维向量会被拒绝。
这是 Vectorize 课程中的第一个实验。如果你是直接进入本实验,请先完成将 LabEx 连接到 Cloudflare 账户,以熟悉如何使用 LabEx VM 终端、授权 Wrangler、确认学习账户并配置账户 ID。如果你不熟悉向量、维度或余弦相似度,请先完成 Workers AI A04。
Vectorize 可在 Workers Free 中使用。目前包含的额度远高于本实验所需的三个 384 维向量和只读检查,因此不需要 Workers Paid。本实验不会调用 Workers AI,也不会消耗 Neurons。
安装过程会在 /home/labex/project/document-vector-index 中安装 Node.js 22.22.0 和项目本地的 Wrangler 4.132.0。同时,安装过程还会提供独立的只读检查。安装过程不会授权 Wrangler、创建索引、写入向量或修改你的 Cloudflare 账户。
授权 VM 并命名索引
在本步骤中,你将授权全新的 VM,选择指定的学习账户,并记录一个唯一的一次性索引名称。
Cloudflare Dashboard 的登录状态属于浏览器。在这个全新的 VM 中,Wrangler 是独立的客户端,因此在管理 Vectorize 资源之前,需要先授予它有限的授权。
进入准备好的项目目录,并确认固定的 CLI 版本:
cd /home/labex/project/document-vector-index
npx wrangler --version
预期输出为 4.132.0。请求账户身份信息和 Workers 资源管理权限。在当前 Wrangler 版本中,workers:write OAuth 作用域包含本实验所需的 Vectorize 管理操作;由于本实验不执行推理,因此不请求 AI 作用域。
npx wrangler login --device --browser=false --scopes account:read user:read workers:write
打开显示的链接,输入当前代码,检查账户和权限,然后授权你的学习账户。接着检查结构化的身份数据:
npx wrangler whoami --json
确认 loggedIn: true,并识别指定的学习账户。生成一个唯一的一次性索引名称:
RUN="labex-c08-v01-$(openssl rand -hex 6)"
printf '%s\n' "$RUN"
将 YOUR_ACCOUNT_ID 替换为该账户的实际 ID:
cat > wrangler.jsonc <<JSON
{
"\$schema": "./node_modules/wrangler/config-schema.json",
"name": "$RUN-tools",
"account_id": "YOUR_ACCOUNT_ID",
"compatibility_date": "2026-09-16",
"vectorize": [
{ "binding": "DOCUMENTS", "index_name": "$RUN", "remote": true }
]
}
JSON
该绑定记录了后续实验将在 Worker 代码中使用的关系:DOCUMENTS 是应用侧使用的名称,而 index_name 是实际拥有的云资源名称。remote: true 表示本地 Worker 将连接真实的远程索引,而不是使用隔离的本地模拟环境。
创建索引及其可筛选字段
在本步骤中,你将创建固定的向量契约,并为之后的筛选准备两个元数据字段。
索引的维度和距离度量是固定的,因为每次比较都必须遵循同一个数值契约。BGE Small 会生成 384 个数字。余弦距离比较向量的方向,适合 A04 中介绍的面向含义的嵌入。
创建 V2 索引:
npx wrangler vectorize create "$RUN" --dimensions=384 --metric=cosine --update-config=false
向量还可以携带少量元数据,例如文档类别。保存元数据并不会自动使其可筛选。元数据索引(metadata index)会告诉 Vectorize 应该为哪些字段准备筛选能力。在插入向量之前创建以下字段:
npx wrangler vectorize create-metadata-index "$RUN" --propertyName=category --type=string | tee .labex/category-index-output.txt
npx wrangler vectorize create-metadata-index "$RUN" --propertyName=published --type=boolean | tee .labex/published-index-output.txt
--update-config=false 可防止 Wrangler 提议替换你已经写入的绑定。元数据索引的创建是异步的。每条命令都会将一个变更加入队列,因此成功消息只表示 Cloudflare 已接受该变更,并不表示所有读取操作都已经可以看到它。
创建一个可重复使用的等待脚本。它只运行只读的 vectorize info 命令,比较完全一致的变更 ID,并要求连续三次读取结果匹配后,才确认结果可信。额外的确认可以避免把短暂过时的读取副本误认为最终状态。等待脚本会在四分钟后报错并停止,而不是无限等待:
cat > scripts/wait-for-vectorize.mjs <<'JS'
import { execFileSync } from "node:child_process";
const [indexName, mutationId, expectedCountText] = process.argv.slice(2);
const expectedCount = Number(expectedCountText);
const wrangler = "./node_modules/wrangler/bin/wrangler.js";
let consecutiveMatches = 0;
for (let attempt = 1; attempt <= 120; attempt += 1) {
const output = execFileSync(process.execPath, [wrangler, "vectorize", "info", indexName, "--json"], { encoding: "utf8" });
const info = JSON.parse(output);
if (info.processedUpToMutation === mutationId && info.vectorCount === expectedCount) {
consecutiveMatches += 1;
} else {
consecutiveMatches = 0;
}
if (consecutiveMatches === 3) {
console.log(`mutation ${mutationId} is consistently readable with ${expectedCount} vectors`);
console.log(JSON.stringify(info, null, 2));
process.exit(0);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
throw new Error(`mutation ${mutationId} was not readable within four minutes`);
JS
METADATA_MUTATION_ID=$(sed -nE 's/.*Mutation changeset identifier: ([0-9a-f-]{36}).*/\1/p' .labex/published-index-output.txt | tail -n 1)
test -n "$METADATA_MUTATION_ID"
node scripts/wait-for-vectorize.mjs "$RUN" "$METADATA_MUTATION_ID" 0
npx wrangler vectorize get "$RUN"
npx wrangler vectorize list-metadata-index "$RUN"
最后的表格应显示:384 个维度、余弦距离、category 的类型为 String,以及 published 的类型为 Bool。使用 --type=boolean 创建的字段,在当前 API 中显示名称为 Bool。等待第二个元数据变更完成,可以避免下一步插入向量时仍在等待索引准备完成。
构建带标识的文档向量
在本步骤中,你将生成一个透明的小型向量测试数据集,其 ID 和元数据可以独立检查。
向量数据库不会替代源文档。每个向量都需要一个稳定的 ID,以便应用能够将它映射回真实内容。本实验使用三个合成的帮助文章 ID,并将它们的类别、发布状态、嵌入模型和池化选项记录为元数据。
实时嵌入将在 V03 中返回。本实验使用确定性向量,使存储行为可重复且不产生费用:每个文档沿着不同的坐标轴指向 1,其余位置填充 0,直到达到 384 个位置。
创建透明的测试数据生成器:
cat > scripts/create-vectors.mjs <<'JS'
import { writeFileSync } from "node:fs";
const DIMENSIONS = 384;
const MODEL = "@cf/baai/bge-small-en-v1.5";
const POOLING = "cls";
const documents = [
{ id: "password-reset", axis: 0, category: "account" },
{ id: "upload-pdf", axis: 1, category: "files" },
{ id: "billing-receipt", axis: 2, category: "billing" }
];
function unitVector(axis) {
const values = Array(DIMENSIONS).fill(0);
values[axis] = 1;
return values;
}
const rows = documents.map((document) => ({
id: document.id,
values: unitVector(document.axis),
metadata: {
category: document.category,
published: true,
model: MODEL,
pooling: POOLING
}
}));
writeFileSync("vectors/documents.ndjson", rows.map(JSON.stringify).join("\n") + "\n");
console.log(`wrote ${rows.length} vectors with ${DIMENSIONS} dimensions each`);
JS
node scripts/create-vectors.mjs
NDJSON 表示按换行分隔的 JSON:每行包含一个完整的向量对象,而不是由一个外层 JSON 数组包裹所有对象。Wrangler 可以分批流式处理这种格式。不打印全部 1,152 个数字,只检查各个对象的标识和形状:
node - <<'JS'
const rows = require("fs").readFileSync("vectors/documents.ndjson", "utf8").trim().split("\n").map(JSON.parse);
console.table(rows.map(({ id, values, metadata }) => ({ id, dimensions: values.length, category: metadata.category, published: metadata.published })));
JS
三行数据都应报告 384 个维度。模型和 cls 池化元数据用于记录兼容性;Vectorize 不会代替你推断或验证这些语义信息。
插入向量并等待其变更完成
在本步骤中,你将插入一个批次,并等待其对应的异步变更对读取操作可见。
Vectorize 的写入是异步的。插入操作会先进入持久化的预写日志,并返回一个变更 ID。随后,后台处理会让该变更对读取操作可见。这种设计可以提高写入效率,但也意味着「已接受」和「可读取」是两个不同的时刻。
插入包含三个向量的批次,并保存完整结果。pipefail 可以避免 Wrangler 失败后,后面的成功 tee 命令掩盖真实错误:
set -o pipefail
npx wrangler vectorize insert "$RUN" --file=vectors/documents.ndjson 2>&1 | tee .labex/insert-output.txt
只有在 Wrangler 表示已将三个向量加入队列并打印出变更标识符后,才继续操作。如果 API 返回身份验证错误或网络错误,该结果无法说明插入是否成功:先确认 npx wrangler whoami --json,然后重新运行一次相同的插入代码块。没有真实的变更 ID 时,不要启动等待脚本。
提取已接受的变更,并且只在变更 ID 存在时等待:
MUTATION_ID=$(sed -nE 's/.*Mutation changeset identifier: ([0-9a-f-]{36}).*/\1/p' .labex/insert-output.txt | tail -n 1)
if [ -z "$MUTATION_ID" ]; then
printf '%s\n' 'No mutation ID was returned; fix the insert error before waiting.' >&2
else
printf 'Waiting for mutation %s\n' "$MUTATION_ID"
node scripts/wait-for-vectorize.mjs "$RUN" "$MUTATION_ID" 3
fi
等待脚本输出的最终 JSON 应显示 vectorCount 为 3,并包含记录的变更 ID。要求连续三次读取匹配,可以降低短暂副本延迟对学习者所见结果的影响。相比固定等待一段时间,有界轮询更安全:变更处理较快时可以及时完成,处理较慢但状态正常时也有足够时间,同时不会产生重复写入。
读取文档并测试兼容性
在本步骤中,你将读取已接受的记录,观察不兼容的写入被拒绝,并将 CLI 状态与 Dashboard 对照起来。
通过应用 ID 读取已保存的记录:
保存完整记录,然后打印紧凑的表格,避免将 1,152 个数字全部输出到终端:
npx wrangler vectorize get-vectors "$RUN" --ids password-reset upload-pdf billing-receipt > .labex/stored-vectors.txt
node - <<'JS'
const text = require("fs").readFileSync(".labex/stored-vectors.txt", "utf8");
const rows = JSON.parse(text.slice(text.indexOf("[")));
console.table(rows.map(({ id, values, metadata }) => ({ id, dimensions: values.length, category: metadata.category, published: metadata.published })));
JS
每一行摘要都应保留原有 ID、384 个值的向量形状以及元数据。原始文件包含完整的值,可用于独立检查。get-vectors 读取已知记录;它不是相似度搜索。相似度查询将在 V03 中介绍。
现在创建一条只有三个值的、故意不兼容的记录:
cat > vectors/incompatible.ndjson <<'NDJSON'
{"id":"wrong-dimensions","values":[1,0,0],"metadata":{"category":"account","published":true}}
NDJSON
if npx wrangler vectorize insert "$RUN" --file=vectors/incompatible.ndjson > .labex/incompatible.log 2>&1; then
STATUS=0
else
STATUS=$?
fi
printf '%s\n' "$STATUS" > .labex/incompatible-exit.txt
sed -n '/invalid vector/p' .labex/incompatible.log
test "$STATUS" -ne 0
拒绝操作可以保护索引契约:三位向量无法与 384 位向量进行有意义的比较。确认已接受的记录仍然存在,并且被拒绝的 ID 没有出现:
npx wrangler vectorize info "$RUN"
npx wrangler vectorize list-vectors "$RUN" --count=10
npx wrangler vectorize get-vectors "$RUN" --ids wrong-dimensions
打开所选账户的 Cloudflare Dashboard,进入 AI → Vectorize。资源列表会将 CLI 名称与真实索引对应起来,显示 384 个维度和余弦距离,并报告总共三个向量;在这个小型示例中不会产生计费使用量。

打开 $RUN 中命名的索引。索引摘要会显示当前存储的三个向量。由于本实验首次使用 ID 读取,查询数仍为 0;相似度查询将在 V03 中开始。

滚动到 Stored Vectors。该图表直观展示异步可见性:计数先保持为 0,插入变更处理完成后变为 3。

当前 Dashboard 不会列出单个向量 ID 或元数据索引定义。请使用前面 Wrangler 对 password-reset、upload-pdf、billing-receipt、category 和 published 的读取结果;不要从只显示计数的图表中推断这些详细信息。Dashboard 页面有助于了解资源位置,而独立检查使用权威的 API 读取结果。
这里展示的是实验在云端完成后、某次一次性运行中的示例截图。你的随机索引名称和时间戳会不同;请以配置和实际拥有的 ID 为准,不要直接复制示例值。
删除一次性索引并退出登录
在本步骤中,你将删除确切的已拥有索引,证明经过身份验证后该索引已不存在,然后移除 VM 的授权。
该索引、它的元数据索引以及其中的向量共同组成一个一次性资源。在授权仍然有效时,删除 wrangler.jsonc 中保存的确切名称:
npx wrangler vectorize delete "$RUN" --force
通过经过身份验证的资源列表读取确认索引已不存在:
npx wrangler vectorize list --json > .labex/indexes-after-cleanup.json
node -e '
const rows = JSON.parse(require("fs").readFileSync(process.argv[1], "utf8"));
if (rows.some((row) => row.name === process.argv[2])) throw new Error("lab index still exists");
console.log("lab index is absent");
' .labex/indexes-after-cleanup.json "$RUN"
这次成功的资源列表读取很重要:网络错误或授权错误都不能证明删除成功。在撤销 VM 授权之前,运行清理检查:
bash verify6-1.sh
最后删除 VM 的 Wrangler 登录状态,并检查结构化结果:
npx wrangler logout
npx wrangler whoami --json
预期结果为 loggedIn: false。Dashboard 的浏览器登录状态是独立的,仍可用于你的学习账户。
总结
你创建了一个 Vectorize V2 索引,其 384 维契约与所选嵌入模型一致,选择了余弦距离,并为后续筛选准备了两个元数据字段。你生成了带标识的确定性向量,以 NDJSON 格式插入它们,区分了已接受的异步变更和已处理的变更,并通过 ID 读取回已保存的记录。
你还验证了 Vectorize 会拒绝维度错误的向量,同时保留兼容的记录。最后,你在 Dashboard 中检查了真实资源,删除了确切的一次性索引,确认经过身份验证后该索引已不存在,并移除了全新 VM 的 Wrangler 授权。
下一个实验将在此生命周期基础上介绍 upsert 和删除操作,确保已修改或已停用的文档不会使索引保持过时状态。



