嵌入生成与向量化
向量化是 RAG 系统的核心环节,它将文本转换为高维向量表示,使语义相似的内容在向量空间中距离更近。本章将详细介绍如何选择合适的嵌入模型,包括千问和 Gemini 等主流模型的对比分析。我们将深入探讨文档向量化处理的完整流程,从 API 调用到批量处理优化,并重点讲解如何将生成的向量高效存储到 Cloudflare Vectorize 数据库中。通过掌握这些技术,您能够构建出高质量的向量索引,为后续的相似度检索奠定基础。
选择合适的嵌入模型
生成执行向量化的核心在于选择合适的嵌入模型:
- 千问(Qwen)提供的模型:特别适合中文内容,支持丰富的上下文理解与生成。
- Gemini 的通用模型:适合多模态内容,灵活应对多种格式的信息。
模型对比指南:
- 如果你的内容主要是简体中文,或者需要良好的本地化处理,建议使用千问。
- 若需处理多语言内容,或需从多个数据模态中提取信息,建议使用 Gemini。
常用嵌入模型及其维度
不同的嵌入模型输出不同维度的向量,创建 Vectorize 索引时需要与模型维度匹配:
模型/嵌入 API | 输出维度 | 最大行数 | 单行最大处理 Token 数 | 支持语种 | 单价(每百万输入 Token) |
---|---|---|---|---|---|
千问 text-embedding-v4 (Qwen3-Embedding 系列) | 2,048、1,536、1,024(默认)、768、512、256、128、64 | 10 | 8,192 | 中文、英语、西班牙语、法语、葡萄牙语、印尼语、日语、韩语、德语、俄罗斯语等 100+ 主流语种 | $0.07(约合 0.0005 元/千 Token,按 1 美元 ≈ 7 元人民币换算) |
Workers AI - @cf/baai/bge-base-en-v1.5 | 1,024 | - | 512 | 英文文本 | $0.20 |
OpenAI - ada-002 | 1,536 | - | - | 多语言文本 | $0.10 |
Gemini - gemini-embedding-001 | 128~3,072(推荐:768、1,536、3,072) | - | 2,048 | 多语言文本 | $0.15 |
需要特别说明的是表格中的“最大行数”是指每次调用嵌入 API 时,允许同时提交的文本行(chunk)数量上限,并不是限制整个文件可以被拆分成多少份。比如千问模型的最大行数为 10,表示你可以一次性批量提交最多 10 个文本块(chunk)进行嵌入生成。如果你的文件被拆分成 30 个 chunk,可以分 3 次批量提交,每次不超过 10 个。单行最大处理 Token 数则是指每个 chunk 的最大长度限制,超出部分需要进一步切分。最大行数限制的是每次批量请求的 chunk 数量,不是文件总共能拆分多少 chunk。
文档向量化处理
在文档中,文本向量化是一项重要的操作:
- API 调用与配置:根据
wrangler.toml
中的配置,调用嵌入模型 API。 - 向量化步骤:
- 加载文档片段和相关元数据。
- 调用嵌入 API,将文本片段转换成向量。
- 将向量存储在 Cloudflare Vectorize 中。
示例代码:
async function embedText(text: string, apiKey: string, apiUrl: string): Promise<number[]> {
const response = await fetch(apiUrl, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${apiKey}`
},
body: JSON.stringify({ text })
});
if (!response.ok) {
throw new Error('API 请求失败');
}
const result = await response.json();
return result.vector;
}
批量向量化处理
为了提高效率,通常会批量处理文档块。这段代码实现了根据不同的嵌入提供者(如 Cloudflare Vectorize)调用相应的 API,获取文本的嵌入向量。
async function batchEmbedTexts(texts: string[], provider: string, config: any): Promise<number[][]> {
switch(provider) {
case 'qwen':
// 调用千问批量嵌入 API
const response = await fetch(`${config.baseUrl}/embeddings`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${config.apiKey}`
},
body: JSON.stringify({
input: texts,
model: config.embeddingModel
})
});
if (!response.ok) {
throw new Error(`嵌入 API 请求失败:${response.status}`);
}
const data = await response.json();
return data.data; // 返回嵌入向量数组
default:
throw new Error(`不支持的嵌入提供者:${provider}`);
}
}
主要流程:
- 通过 fetch 发送 POST 请求,将输入文本和模型参数传递给嵌入服务。
- 检查响应状态码,如果请求失败则抛出异常,便于后续错误处理。
- 请求成功后解析 JSON 响应,并返回嵌入向量数组(data.data)。
- 如果传入了不支持的 provider,会抛出异常提醒开发者。
注意事项:
- 需要确保 config.embeddingModel 配置正确,且 texts 为待处理的文本数组。
- 嵌入向量通常用于后续的向量检索或相似度计算场景。
向量存储与索引
在 RAG 系统中,向量的高效存储与检索是实现智能问答和内容发现的基础。通过 Cloudflare Vectorize,可以将生成的文本向量安全地存储在向量数据库中,并利用其对余弦相似度的高效支持,实现快速的相似内容检索。为了保证检索性能和系统的可扩展性,通常需要对大规模数据集进行合理切分,使每次检索请求都能在有限的数据范围内完成,从而提升响应速度。此外,随着文档内容的不断更新,索引也需要定期维护和刷新,以确保检索结果始终反映最新的数据状态。通过科学的存储与索引策略,可以为后续的语义搜索和智能推荐打下坚实的基础。
Vectorize 向量格式
在将文本向量存储到 Cloudflare Vectorize 之前,需要遵循其特定的数据结构。下面的 TypeScript 接口定义了向量对象的基本格式,包括唯一标识符、向量值数组、可选的元数据和命名空间字段。
interface VectorizeVector {
id: string; // 向量唯一标识符
values: number[]; // 向量值数组
metadata?: Record<string, any>; // 可选元数据
namespace?: string; // 可选命名空间
}
说明:id
字段用于唯一标识每个向量,values
存储实际的嵌入向量数据。metadata
可用于存储检索和过滤所需的附加信息,namespace
便于对不同类型或语言的向量进行分组管理。
向量元数据设计
为了提升检索效率和灵活性,建议为每个向量对象设计合理的元数据结构。以下示例展示了常用的元数据字段,涵盖了文档定位、内容描述、分块信息和标签等。
const vectorMetadata = {
url: "/blog/article-title", // 文档 URL
title: "文章标题", // 文档标题
sourcePath: "content/zh/blog/article-title/index.md", // 源文件路径
chunkIndex: 0, // 块索引
chunkTitle: "章节标题", // 块标题(如果有)
wordCount: 256, // 字数统计
language: "zh", // 语言标识
tags: ["技术", "AI", "RAG"], // 标签
text: "文章内容片段", // 文本内容
createdAt: "2025-07-31T10:00:00Z", // 创建时间
updatedAt: "2025-07-31T10:00:00Z" // 更新时间
};
说明:实际应用中,推荐至少包含 url
、title
、sourcePath
等字段。这些元数据有助于后续的向量检索、过滤和内容追溯。
批量上传到 Vectorize
在处理大规模文档时,批量上传向量可以显著提升效率。下面的代码演示了如何将多个向量对象分批上传到 Cloudflare Vectorize,每批最多支持 1000 个向量。
async function uploadToVectorize(vectors: VectorizeVector[], vectorize: Vectorize) {
// Vectorize 支持最多 1000 个向量的批量操作
const batchSize = 1000;
for (let i = 0; i < vectors.length; i += batchSize) {
const batch = vectors.slice(i, i + batchSize);
try {
// 使用 upsert 确保更新已存在的向量
const result = await vectorize.upsert(batch);
console.log(`上传批次 ${Math.floor(i/batchSize) + 1} 成功,处理了 ${batch.length} 个向量`);
} catch (error) {
console.error(`上传批次 ${Math.floor(i/batchSize) + 1} 失败:`, error);
}
}
}
注意事项:
- 建议根据数据量合理设置批次大小,避免单次请求过大导致超时或失败。
- 使用
upsert
方法可以自动更新已存在的向量,便于数据同步和维护。
命名空间管理
合理使用命名空间(namespace)有助于对不同语言、内容类型或业务场景的向量进行分组管理。以下示例展示了如何为向量批量分配命名空间。
// 按语言分组
const chineseVectors = vectors.map(v => ({
...v,
namespace: "zh"
}));
const englishVectors = vectors.map(v => ({
...v,
namespace: "en"
}));
// 按内容类型分组
const blogVectors = vectors.map(v => ({
...v,
namespace: "blog"
}));
const docsVectors = vectors.map(v => ({
...v,
namespace: "docs"
}));
说明:通过为向量指定不同的 namespace
,可以在检索时快速筛选目标数据集,提升查询效率和系统可扩展性。
向量更新策略
随着文档内容的不断变化,向量数据也需要同步更新。常见的向量更新策略包括增量更新、全量更新和混合策略。下面的代码示例演示了如何实现增量更新,仅针对发生变化的文档块生成和上传新的向量。
// 增量更新示例
async function updateDocumentVectors(
documentPath: string,
vectorize: Vectorize,
embeddingFunction: Function
) {
// 1. 处理文档生成新的向量块
const newChunks = await processDocument(documentPath);
// 2. 生成向量
const vectors = await Promise.all(
newChunks.map(async (chunk, index) => {
const embedding = await embeddingFunction(chunk.content);
return {
id: generateVectorId(documentPath, index),
values: embedding,
metadata: {
...chunk.metadata,
updatedAt: new Date().toISOString()
}
};
})
);
// 3. 使用 upsert 更新向量数据库
await vectorize.upsert(vectors);
}
实践建议:
- 增量更新适用于内容变动频繁的场景,可减少不必要的全量重算。
- 建议结合文档的更新时间戳和内容哈希,判断是否需要重新生成向量。
- 定期执行全量更新,可确保整体数据一致性,适用于大规模内容重构或模型升级场景。
通过上述方法,可以高效地将生成的向量存储到 Cloudflare Vectorize,支撑 RAG 系统的高性能语义检索和内容发现能力。