news 2026/10/3 2:02:52

基于 Cloudflare Vectorize 的 Rust 向量检索集成:rig-vectorize 使用指南与源码解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于 Cloudflare Vectorize 的 Rust 向量检索集成:rig-vectorize 使用指南与源码解析
  • AI Agent
  • Agent 框架
  • RAG
  • 后端

【免费下载链接】rig

⚙️🦀 Build modular and scalable LLM Applications in Rust

项目地址:https://gitcode.com/GitHub_Trending/rig2/rig
点击查看免费下载

在 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"

两种安装方式等价,按需选择:

  1. 直接依赖rig-vectorize,并配合rig-core使用其vector_store、providers、embeddings等模块;
  2. 通过根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实现:

  1. 通过rig_core::vector_store::flatten_embedded把「文档 + 多个嵌入」展开为「每个嵌入一条向量记录」,每条记录用Uuid::new_v4()生成全新的向量 ID,文档的 JSON 序列化结果作为metadata保存;
  2. 以BATCH_SIZE = 1000分批调用upsert(Cloudflare API 单次最多接受 5000 条,见 crates/rig-vectorize/src/client/mod.rs 注释),分批是稳妥的工程选择;
  3. 空向量列表直接短路返回,不发请求。

查询链路:先嵌入、再过滤、后阈值

query_matches(crates/rig-vectorize/src/lib.rs)的顺序值得注意:

  1. 若请求带过滤器,先调用filter.validate()预检——不支持的操作在发起任何 HTTP 请求前就会报错;
  2. 用与写入相同的嵌入模型对查询文本现场嵌入;
  3. 构造 API 查询请求(return_values: false,不返回向量本身);
  4. 客户端查询后,在本地再按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 认证:

方法端点说明
queryPOST .../query相似度查询,top_k上限为 20(带元数据)或 100(不带,见 types.rs 注释)
upsertPOST .../upsert按 ID 插入或覆盖向量,单请求上限 5000 条
delete_by_idsPOST .../delete_by_ids按 ID 删除,单请求上限 1000 个 ID
list_vectorsGET .../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等过滤,必须先为它们建立元数据索引,否则过滤查询无法生效。

工程实践要点小结

  1. 嵌入模型必须与写入时一致:VectorizeVectorStore的查询向量由同一模型现场生成,换模型会导致检索结果失去意义(源码文档注释明确此点);
  2. 维度与度量在建索引时锁定:创建索引时指定的dimensions必须匹配模型输出,之后不易更改;
  3. 记住最终一致性:写入后立即查询可能查不到结果,示例与测试分别采用 2 秒与 5 秒的等待策略;
  4. OR 过滤器不可用:设计查询条件时优先用$in替代 OR 语义,或把析取拆成多次查询;
  5. 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

项目地址:https://gitcode.com/GitHub_Trending/rig2/rig
点击查看免费下载
上一篇:Element Plus 快速上手指南:5 步搭出后台管理界面
下一篇:ESP-BOX开发平台终极指南:从入门到精通AIoT开发

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/3 2:00:21

Python 密码学最佳实践:语言边界、RSA 陷阱与 FFI 突围路径

【免费下载链接】publications Publications from Trail of Bits 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/pu/publications 点击查看 免费下载 本文基于 Trail of Bits 首席安全工程师 Paul Kehrer 在 PyCon AU 2019 的演讲《Best Practices for Cryptogra…

作者头像 李华
网站建设 2026/10/3 1:57:50

如何安装配置 Ruffle 扩展:完整让旧 Flash 内容在浏览器里跑起来

如何安装配置 Ruffle 扩展&#xff1a;完整让旧 Flash 内容在浏览器里跑起来 【免费下载链接】ruffle A Flash Player emulator written in Rust 项目地址: https://gitcode.com/GitHub_Trending/ru/ruffle 读完这篇&#xff0c;你能独立完成 Ruffle 扩展在浏览器里的安…

作者头像 李华