- AI Agent
- Agent 框架
- RAG
- 后端
【免费下载链接】rig
⚙️🦀 Build modular and scalable LLM Applications in Rust
在 Rust 中构建模块化、可扩展的 LLM 应用时,向量数据库是 RAG(检索增强生成)链路的关键一环。rig-vectorize是 Rig 框架官方提供的 Cloudflare Vectorize 向量存储集成,它把 Cloudflare 全球分布式索引无缝接入 Rig 的VectorStoreIndex/InsertDocuments抽象,使开发者可以用同一套 Rust 代码在向量检索、文档插入之间自由切换后端。阅读本文后,你将掌握 rig-vectorize 的安装配置、端到端检索示例、元数据过滤器的能力边界、底层 HTTP 客户端的实现原理,以及如何运行真实索引上的集成测试。
概览:rig-vectorize 在整个框架中的位置
rig-vectorize 的核心是VectorizeVectorStore,它同时实现了 Rig 的VectorStoreIndex与InsertDocuments两个 trait(定义于 crates/rig-core/src/vector_store/mod.rs):
VectorStoreIndex::top_n/top_n_ids:执行向量相似度查询;InsertDocuments::insert_documents:把文档连同其嵌入向量批量写入索引。
查询时,向量由注入的嵌入模型现场生成;写入时,文档以 JSON 形式作为元数据随向量一并存储。仓库文档的原始描述见 crates/rig-vectorize/README.md,其lib.rs顶部注释给出了最简用法:
use rig_core::providers::openai; use rig_vectorize::VectorizeVectorStore; let openai = openai::OpenAI::from_env()?; let embedding_model = openai.embedding(openai::TEXT_EMBEDDING_3_SMALL, None); let vector_store = VectorizeVectorStore::new( embedding_model, "your-account-id", "your-index-name", std::env::var("CLOUDFLARE_API_TOKEN")?, );安装与特性开关
在Cargo.toml中声明依赖即可(版本号以仓库 workspace 实际为准,见 crates/rig-vectorize/Cargo.toml):
[dependencies] rig-vectorize = "0.2.5" rig-core = "0.36.0"两种安装方式等价,按需选择:
- 直接依赖
rig-vectorize,并配合rig-core使用其vector_store、providers、embeddings等模块; - 通过根
rigfacade:根 crate 在vectorizefeature 下暴露该 crate(见根 Cargo.toml 中vectorize = ["dep:rig-vectorize"]),适合统一管理所有 Rig 组件。
注意 TLS 后端特性
rig-vectorize直接持有reqwest::Client而非通过HttpClientExt抽象,因此 TLS 后端完全由该 crate 自己的特性决定(crates/rig-vectorize/Cargo.toml 中注释明确说明):
[features] default = ["rustls"] rustls = ["reqwest/rustls"] native-tls = ["reqwest/native-tls"]默认启用rustls;若改用native-tls,必须在依赖中显式default-features = false并开启对应特性。若两个 TLS 特性都未启用,reqwest没有 TLS 后端,所有指向 Cloudflare API 的 HTTPS 调用都会在握手阶段失败——这是一个容易踩中的运行时坑。
端到端示例:插入文档并执行相似度搜索
仓库提供了完整可运行的示例 crates/rig-vectorize/examples/vectorize_vector_search.rs,演示从嵌入、写入到查询的完整链路:
use rig_core::{ Embed, embeddings::EmbeddingsBuilder, providers::openai::{self, OpenAI}, vector_store::request::VectorSearchRequest, vector_store::{InsertDocuments, VectorStoreIndex}, }; use rig_vectorize::VectorizeVectorStore; #[derive(Embed, serde::Deserialize, serde::Serialize, Debug)] struct Word { id: String, #[embed] definition: String, } #[tokio::main] async fn main() -> Result<(), anyhow::Error> { let openai_client = OpenAI::from_env()?; let model = openai_client .embedding(openai::TEXT_EMBEDDING_3_SMALL, None) .erase(); let vector_store = VectorizeVectorStore::new( model.clone(), std::env::var("CLOUDFLARE_ACCOUNT_ID")?, "rig-example", std::env::var("CLOUDFLARE_API_TOKEN")?, ); let documents = EmbeddingsBuilder::new(model) .document(Word { id: "doc-1".to_string(), definition: "Definition of a *flurbo*: A flurbo is a green alien that lives on cold planets".to_string(), })? .document(Word { id: "doc-2".to_string(), definition: "Definition of a *glarb-glarb*: ...".to_string(), })? .document(Word { id: "doc-3".to_string(), definition: "Definition of a *linglingdong*: ...".to_string(), })? .build() .await?; vector_store.insert_documents(documents).await?; // Vectorize 是最终一致性存储,新写入的文档可能需数秒后才可查询 tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; let request = VectorSearchRequest::builder() .query("What is a linglingdong?") .samples(3) .build(); let results = vector_store.top_n::<Word>(request).await?; for (score, id, word) in results { println!(" Score: {score:.4}, ID: {id}"); println!(" Definition: {}", word.definition); } Ok(()) }运行方式(需要OPENAI_API_KEY、CLOUDFLARE_ACCOUNT_ID、CLOUDFLARE_API_TOKEN环境变量):
cargo run --release --example vectorize_vector_search示例中有几个值得注意的细节:
#[embed]属性:Word结构体通过#[derive(Embed)]声明哪个字段作为嵌入来源(这里是definition),其余字段(id)作为元数据随向量存储;.erase():把具体嵌入模型擦除为DynModel<Embedding>动态类型,使VectorizeVectorStore可以接受任意实现了Embed的模型(VectorizeVectorStore::new的第一个参数正是impl Into<rig_core::DynModel<Embedding>>);- 写入后需等待:因为 Vectorize 是最终一致性存储,立即查询可能查不到刚插入的向量。
写入链路:一个向量一条记录,千条一批
查看 crates/rig-vectorize/src/lib.rs 的insert_documents实现:
- 通过
rig_core::vector_store::flatten_embedded把「文档 + 多个嵌入」展开为「每个嵌入一条向量记录」,每条记录用Uuid::new_v4()生成全新的向量 ID,文档的 JSON 序列化结果作为metadata保存; - 以
BATCH_SIZE = 1000分批调用upsert(Cloudflare API 单次最多接受 5000 条,见 crates/rig-vectorize/src/client/mod.rs 注释),分批是稳妥的工程选择; - 空向量列表直接短路返回,不发请求。
查询链路:先嵌入、再过滤、后阈值
query_matches(crates/rig-vectorize/src/lib.rs)的顺序值得注意:
- 若请求带过滤器,先调用
filter.validate()预检——不支持的操作在发起任何 HTTP 请求前就会报错; - 用与写入相同的嵌入模型对查询文本现场嵌入;
- 构造 API 查询请求(
return_values: false,不返回向量本身); - 客户端查询后,在本地再按
req.threshold()过滤掉分数不达标的匹配。
top_n与top_n_ids的区别在于元数据回传策略:top_n请求ReturnMetadata::All并把每个匹配的 metadata 反序列化为T;top_n_ids请求ReturnMetadata::None,只返回(score, id),省去元数据网络开销。注意top_n中「匹配无元数据」时 metadata 以 JSONnull反序列化,对大多数T都会失败,这是 API 语义决定的边界情况。
底层 HTTP 客户端:VectorizeClient 四个端点
VectorizeClient 封装了 Vectorize v2 API 的四个端点,全部指向https://api.cloudflare.com/client/v4/accounts/{account_id}/vectorize/v2/indexes/{index_name},使用 Bearer Token 认证:
| 方法 | 端点 | 说明 |
|---|---|---|
query | POST .../query | 相似度查询,top_k上限为 20(带元数据)或 100(不带,见 types.rs 注释) |
upsert | POST .../upsert | 按 ID 插入或覆盖向量,单请求上限 5000 条 |
delete_by_ids | POST .../delete_by_ids | 按 ID 删除,单请求上限 1000 个 ID |
list_vectors | GET .../list | 分页列出向量 ID,count与cursor作为查询参数,next_cursor用于翻页 |
每个响应都经过统一的unwrap_api/parse_api处理:先读取完整响应体并tracing::debug!记录原始文本(便于排查问题),再反序列化 Cloudflare 的{success, result, errors, messages}信封。信封中success=false时取第一个错误构造ApiError;success=true但result缺失时也会报ApiError("No result in successful ... response")。这三条分支各有单元测试覆盖,见 crates/rig-vectorize/src/client/tests.rs。
元数据过滤:VectorizeFilter 的能力边界
Vectorize 的过滤依赖元数据索引(见下文「可选:启用过滤测试」),rig-vectorize 通过VectorizeFilter提供类型安全的构建器,它包装一个 JSON 值并实现SearchFiltertrait。支持的操作如下:
| 操作 | 方法 | 生成的 JSON |
|---|---|---|
| 等于 | eq | {"key": {"$eq": value}} |
| 不等于 | ne | {"key": {"$ne": value}} |
| 大于 | gt | {"key": {"$gt": value}} |
| 小于 | lt | {"key": {"$lt": value}} |
| 大于等于 | gte | {"key": {"$gte": value}} |
| 小于等于 | lte | {"key": {"$lte": value}} |
| 属于集合 | in_values | {"key": {"$in": [...]}} |
| 不属于集合 | nin | {"key": {"$nin": [...]}} |
组合规则与限制:
- AND(合取):
and把两个过滤器合并进同一个 JSON 对象,Vectorize 按合取语义求值;多个过滤器链式and会累积键,test_multiple_and_filters验证了三层合并后三个键共存; - OR(析取)不受支持:
or会丢弃两侧操作数,产出一个{"$unsupported_or": ...}哨兵过滤器,并在validate()时返回VectorizeError::UnsupportedFilterOperation。也就是说,在发起任何网络请求之前,带 OR 的查询就会失败。源码注释还提示:该预检只能识别顶层析取,嵌套在更深层的析取不会被探测到; - 以上行为均有单元测试佐证,见 crates/rig-vectorize/src/client/filter/tests.rs。
查询时过滤器以req.filter()传入VectorSearchRequest<VectorizeFilter>(Rig 的请求构建器:VectorSearchRequest::builder().query(..).samples(..).filter(..).threshold(..).build())。
错误类型与 trait 桥接
VectorizeError(crates/rig-vectorize/src/client/error.rs)覆盖四类失败:HTTP 层错误(reqwest::Error)、Cloudflare API 业务错误(携带code与message)、JSON 序列化错误、不支持的过滤操作。通过impl From<VectorizeError> for VectorStoreError(lib.rs 中VectorStoreError::datastore(err)),所有错误自动桥接为 Rig 统一的VectorStoreError,调用方无需感知具体后端。
运行集成测试(需真实 Vectorize 索引)
集成测试需要真实的 Cloudflare Vectorize 索引,crates/rig-vectorize/README.md 给出了完整流程。
1. 创建索引
npx wrangler vectorize create rig-integration-test --dimensions=1536 --metric=cosine--dimensions必须与嵌入模型输出的维度一致(如 OpenAItext-embedding-3-small为 1536 维),--metric选择相似度度量(这里用余弦)。
2. 设置环境变量并运行测试
export CLOUDFLARE_ACCOUNT_ID="your-account-id" export CLOUDFLARE_API_TOKEN="your-api-token" export VECTORIZE_INDEX_NAME="rig-integration-test" cargo test --package rig-vectorize --test integration_tests -- --test-threads=1测试必须串行执行(--test-threads=1):每个用例开始前会清空索引,并行会互相冲突。此外,由于 Vectorize 是最终一致性存储,测试在插入文档后等待 5 秒再查询,等待时长由源码中的EVENTUAL_CONSISTENCY_DELAY常量控制。
3. (可选)启用过滤测试
过滤测试依赖元数据索引;未创建时相关用例会被跳过:
npx wrangler vectorize create-metadata-index rig-integration-test --property-name=category --type=string npx wrangler vectorize create-metadata-index rig-integration-test --property-name=id --type=string这也从侧面印证了上一节的内容:要对category、id等字段执行$eq/$in等过滤,必须先为它们建立元数据索引,否则过滤查询无法生效。
工程实践要点小结
- 嵌入模型必须与写入时一致:
VectorizeVectorStore的查询向量由同一模型现场生成,换模型会导致检索结果失去意义(源码文档注释明确此点); - 维度与度量在建索引时锁定:创建索引时指定的
dimensions必须匹配模型输出,之后不易更改; - 记住最终一致性:写入后立即查询可能查不到结果,示例与测试分别采用 2 秒与 5 秒的等待策略;
- OR 过滤器不可用:设计查询条件时优先用
$in替代 OR 语义,或把析取拆成多次查询; - TLS 特性必须显式配置:默认
rustls,切换到native-tls需关闭默认特性,否则 HTTPS 握手必然失败。
至此,从安装、示例、过滤器到 HTTP 客户端与测试流程,你已经掌握了 rig-vectorize 的完整使用方式与内部实现脉络。下一步可以把VectorizeVectorStore直接挂到 Rig 的 Agent RAG 工具链中,用统一抽象替换其他向量后端而无需改动业务代码。
- AI Agent
- Agent 框架
- RAG
- 后端
【免费下载链接】rig
⚙️🦀 Build modular and scalable LLM Applications in Rust
相关推荐
rig-vectorize 版本演进与源码剖析:用 Rig 集成 Cloudflare Vectorize 向量检索
rig vectorize 版本演进与源码剖析:用 Rig 集成 Cloudflare Vectorize 向量检索 本文围绕 crates/rig vecto
AI AgentAgent 框架RAG后端Cloudflare Vectorize 实战模式全解析:从 Embedding 集成到多租户 RAG 检索
Cloudflare Vectorize 实战模式全解析:从 Embedding 集成到多租户 RAG 检索 本篇技术指南以 Cloudflare Vector
人工智能AI 技能AI 插件使用 rig-lancedb 在 Rust 中构建基于 LanceDB 的向量检索与 RAG 应用
使用 rig lancedb 在 Rust 中构建基于 LanceDB 的向量检索与 RAG 应用 本篇技术指南以 rig lancedb https://li
AI AgentAgent 框架RAG后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考