Apache Cassandra Vector Store

本节将引导您设置 CassandraVectorStore,以存储文档嵌入并执行相似性搜索。

什么是 Cassandra?

Apache Cassandra® 是一款真正的开源分布式数据库,以其线性可扩展性、经过验证的容错能力和低延迟而闻名,是处理关键事务数据的理想平台。

其向量最近邻搜索(VSS)基于 JVector 库,确保了顶级的性能和相关性。

在 Cassandra 中进行向量搜索操作简单如下:

SELECT content FROM table ORDER BY content_vector ANN OF query_embedding;

这款 Spring AI 向量存储库旨在既适用于全新的 RAG 应用,又能无缝集成到现有数据和表格之上。

该存储同样适用于现有数据库中的非 RAG 应用场景,例如语义搜索、地理位置邻近搜索等。

商店将根据其配置自动创建或增强模式。如果您不希望模式被修改,请通过 initializeSchema 配置商店。

使用 spring-boot-autoconfigure 时,按照 Spring Boot 的标准,initializeSchema 默认为 false,您必须通过在 application.properties 文件中设置 initialize-schema=true 来明确选择启用模式创建 / 修改。

什么是 JVector?

JVector 是一个纯 Java 嵌入式向量搜索引擎。

它之所以在众多 HNSW 向量最近邻搜索实现中脱颖而出,原因在于:

  • 算法高效:JVector 采用了受 DiskANN 及相关研究启发的尖端图算法,确保了高召回率和低延迟。
  • 实现迅速:JVector 采用 Panama SIMD API 加速索引构建与查询过程。
  • 内存高效:JVector 采用乘积量化技术压缩向量,确保其在搜索过程中能常驻内存。
  • 磁盘优化:JVector 的磁盘布局设计旨在查询时执行最少的必要 IOPS 操作。
  • 并发性:索引构建至少可线性扩展至 32 线程。线程数翻倍,构建时间减半。
  • 增量式:在构建索引的同时即可进行查询。添加向量后,无需等待即可在搜索结果中找到它。
  • 易于集成:API 设计旨在简化嵌入过程,深受生产环境使用者的青睐。

先决条件

  1. 用于计算文档嵌入的 EmbeddingModel 实例。通常配置为 Spring Bean,有多种选项可供选择:
    • Transformers 嵌入 —— 在您的本地环境中计算嵌入。默认通过 ONNX 和 all-MiniLM-L6-v2 Sentence Transformers 实现。开箱即用。
    • 若欲使用 OpenAI 的嵌入功能 —— 需调用 OpenAI 的嵌入端点。请先在 OpenAI 注册页面 创建账户,并在 API 密钥 处生成 API 密钥令牌。
    • 还有更多选择,请参阅 Embeddings API 文档。
  2. 一个 Cassandra 实例,自 5.0-beta1 版本起

依赖关系

对于依赖管理,我们建议按照 依赖管理 章节所述,使用 Spring AI BOM。

将这些依赖项添加到您的项目中:

  • 仅针对 Cassandra 向量存储:
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-cassandra-store</artifactId>
    </dependency>
  • 或者,获取构建 RAG 应用所需的一切(使用默认的 ONNX 嵌入模型):
    <dependency>
      <groupId>org.springframework.ai</groupId>
      <artifactId>spring-ai-starter-vector-store-cassandra</artifactId>
    </dependency>

配置属性

您可以在 Spring Boot 配置中使用以下属性来自定义 Cassandra 向量存储。

使用用例

基本使用用例

创建 CassandraVectorStore 实例作为 Spring Bean:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .session(session)
        .keyspace("my_keyspace")
        .table("my_vectors")
        .build();
}

一旦获得向量存储实例,您就可以添加文档并执行搜索:

// Add documents
vectorStore.add(List.of(
    new Document("1", "content1", Map.of("key1", "value1")),
    new Document("2", "content2", Map.of("key2", "value2"))
));

// Search with filters
List<Document> results = vectorStore.similaritySearch(
    SearchRequest.query("search text")
        .withTopK(5)
        .withSimilarityThreshold(0.7f)
        .withFilterExpression("metadata.key1 == 'value1'")
);

高级配置

针对更复杂的使用场景,您可以在 Spring Bean 中配置额外的设置:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    return CassandraVectorStore.builder(embeddingModel)
        .session(session)
        .keyspace("my_keyspace")
        .table("my_vectors")
        // Configure primary keys
        .partitionKeys(List.of(
            new SchemaColumn("id", DataTypes.TEXT),
            new SchemaColumn("category", DataTypes.TEXT)
        ))
        .clusteringKeys(List.of(
            new SchemaColumn("timestamp", DataTypes.TIMESTAMP)
        ))
        // Add metadata columns with optional indexing
        .addMetadataColumns(
            new SchemaColumn("category", DataTypes.TEXT, SchemaColumnTags.INDEXED),
            new SchemaColumn("score", DataTypes.DOUBLE)
        )
        // Customize column names
        .contentColumnName("text")
        .embeddingColumnName("vector")
        // Performance tuning
        .fixedThreadPoolExecutorSize(32)
        // Schema management
        .initializeSchema(true)
        // Custom batching strategy
        .batchingStrategy(new TokenCountBatchingStrategy())
        .build();
}

连接配置

有两种方法可以配置与 Cassandra 的连接:

  • 使用注入的 CqlSession(推荐):
    @Bean
    public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
      return CassandraVectorStore.builder(embeddingModel)
          .session(session)
          .keyspace("my_keyspace")
          .table("my_vectors")
          .build();
    }
  • 直接在构建器中使用连接详细信息:
    @Bean
    public VectorStore vectorStore(EmbeddingModel embeddingModel) {
      return CassandraVectorStore.builder(embeddingModel)
          .contactPoint(new InetSocketAddress("localhost", 9042))
          .localDatacenter("datacenter1")
          .keyspace("my_keyspace")
          .build();
    }

元数据过滤(Metadata Filtering)

您可以利用 CassandraVectorStore 中的通用、可移植元数据过滤器。要使元数据列可搜索,它们必须是主键SAI 索引。要将非主键列设为索引,需使用 SchemaColumnTags.INDEXED 配置元数据列。

例如,您可以使用文本表达式语言:

vectorStore.similaritySearch(
    SearchRequest.builder().query("The World")
        .topK(5)
        .filterExpression("country in ['UK', 'NL'] && year >= 2020").build());

或通过表达式 DSL 以编程方式实现:

Filter.Expression f = new FilterExpressionBuilder()
    .and(
        f.in("country", "UK", "NL"),
        f.gte("year", 2020)
    ).build();

vectorStore.similaritySearch(
    SearchRequest.builder().query("The World")
        .topK(5)
        .filterExpression(f).build());

便携式过滤表达式会自动转换为 CQL 查询 。

高级示例:基于维基百科数据集的向量存储

以下示例展示了如何在现有模式上使用存储。这里我们采用 github.com/datastax-labs/colbert-wikipedia-data 项目中的模式,该模式附带了已向量化、可直接使用的完整维基百科数据集。

首先,在 Cassandra 数据库中创建模式:

wget https://s.apache.org/colbert-wikipedia-schema-cql -O colbert-wikipedia-schema.cql
cqlsh -f colbert-wikipedia-schema.cql

然后使用构建器模式配置存储:

@Bean
public VectorStore vectorStore(CqlSession session, EmbeddingModel embeddingModel) {
    List<SchemaColumn> partitionColumns = List.of(
        new SchemaColumn("wiki", DataTypes.TEXT),
        new SchemaColumn("language", DataTypes.TEXT),
        new SchemaColumn("title", DataTypes.TEXT)
    );

    List<SchemaColumn> clusteringColumns = List.of(
        new SchemaColumn("chunk_no", DataTypes.INT),
        new SchemaColumn("bert_embedding_no", DataTypes.INT)
    );

    List<SchemaColumn> extraColumns = List.of(
        new SchemaColumn("revision", DataTypes.INT),
        new SchemaColumn("id", DataTypes.INT)
    );

    return CassandraVectorStore.builder()
        .session(session)
        .embeddingModel(embeddingModel)
        .keyspace("wikidata")
        .table("articles")
        .partitionKeys(partitionColumns)
        .clusteringKeys(clusteringColumns)
        .contentColumnName("body")
        .embeddingColumnName("all_minilm_l6_v2_embedding")
        .indexName("all_minilm_l6_v2_ann")
        .initializeSchema(false)
        .addMetadataColumns(extraColumns)
        .primaryKeyTranslator((List<Object> primaryKeys) -> {
            if (primaryKeys.isEmpty()) {
                return "test§¶0";
            }
            return String.format("%s§¶%s", primaryKeys.get(2), primaryKeys.get(3));
        })
        .documentIdTranslator((id) -> {
            String[] parts = id.split("§¶");
            String title = parts[0];
            int chunk_no = parts.length > 1 ? Integer.parseInt(parts[1]) : 0;
            return List.of("simplewiki", "en", title, chunk_no, 0);
        })
        .build();
}

@Bean
public EmbeddingModel embeddingModel() {
    // default is ONNX all-MiniLM-L6-v2 which is what we want
    return new TransformersEmbeddingModel();
}

加载完整的维基百科数据集

要加载完整的维基百科数据集:

  1. 从 s.apache.org/simplewiki-sstable-tar 下载 simplewiki-sstable.tar(这将需要一些时间,文件大小达数十 GB)。
  2. 加载数据:
    tar -xf simplewiki-sstable.tar -C ${CASSANDRA_DATA}/data/wikidata/articles-*/
    nodetool import wikidata articles ${CASSANDRA_DATA}/data/wikidata/articles-*/

访问原生客户端

Cassandra 向量存储实现通过‘getNativeClient ()‘方法提供了对底层原生 Cassandra 客户端(CqlSession)的访问:

CassandraVectorStore vectorStore = context.getBean(CassandraVectorStore.class);
Optional<CqlSession> nativeClient = vectorStore.getNativeClient();

if (nativeClient.isPresent()) {
    CqlSession session = nativeClient.get();
    // Use the native client for Cassandra-specific operations
}

原生客户端使您能够访问可能未通过 VectorStore 接口暴露的 Cassandra 特有功能和操作。

作者:Jeebiz  创建时间:2025-09-07 20:07
最后编辑:Jeebiz  更新时间:2025-09-10 21:18