Apache Cassandra

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

什么是 Apache Cassandra?

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

其向量相似性搜索 (VSS) 基于 JVector 库,可确保一流的性能和相关性。

在 Apache Cassandra 中进行向量搜索与以下操作一样简单

SELECT content FROM table ORDER BY content_vector ANN OF query_embedding ;

您可以在 此处 阅读有关此内容的更多文档。

此 Spring AI 向量存储旨在适用于全新的 RAG 应用程序,并且能够在现有数据和表之上进行改造。

该存储库还可用于现有数据库中的非 RAG 用例,例如语义搜索、地理位置搜索等。

向量存储实现可以为您初始化必要的架构,但您必须通过在适当的构造函数中指定 initializeSchema 布尔值或在 application.properties 文件中设置 …​initialize-schema=true 来选择加入。

这是一个重大变更!在早期版本的 Spring AI 中,此模式初始化默认情况下会发生。

存储库将根据其配置自动创建或增强模式。如果您不希望模式修改,请使用 disallowSchemaChanges 配置存储库。

什么是 JVector?

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

它在其他 HNSW 向量相似性搜索实现中脱颖而出,因为它

  • 算法速度快。JVector 使用受 DiskANN 和相关研究启发的最先进的图算法,提供高召回率和低延迟。

  • 实现速度快。JVector 使用 Panama SIMD API 加速索引构建和查询。

  • 内存效率高。JVector 使用乘积量化压缩向量,以便它们可以在搜索期间保留在内存中。(作为我们 PQ 实现的一部分,我们的 SIMD 加速 kmeans 类比 Apache Commons Math 中的快 5 倍。)

  • 磁盘感知。JVector 的磁盘布局旨在在查询时执行最少的必要 IOPS。

  • 并发。索引构建线性扩展到至少 32 个线程。线程数翻倍,构建时间减半。

  • 增量式。在构建索引时查询索引。在添加向量和能够在搜索结果中找到它之间没有延迟。

  • 易于嵌入。API 旨在易于嵌入,供生产中使用它的人员使用。

先决条件

  1. 一个 Embedding 实例来计算文档嵌入。这通常配置为 Spring Bean。有几种选择可用

    • Transformers Embedding - 在您的本地环境中计算嵌入。默认情况下使用 ONNX 和 all-MiniLM-L6-v2 Sentence Transformers。这很有效。

    • 如果您想使用 OpenAI 的 Embeddings` - 使用 OpenAI 嵌入端点。您需要在 OpenAI 注册 创建一个帐户,并在 API 密钥 生成 api-key 令牌。

    • 还有更多选择,请参阅 Embeddings API 文档。

  2. 一个 Apache Cassandra 实例,从版本 5.0-beta1 开始

    1. DIY 快速入门

    2. 对于托管产品,Astra DB 提供了一个健康的免费层。

依赖项

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

  • 仅适用于 Cassandra 向量存储

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-cassandra-store</artifactId>
</dependency>
  • 或者,对于 RAG 应用程序中所需的一切(使用默认的 ONNX Embedding Client)

<dependency>
  <groupId>org.springframework.ai</groupId>
  <artifactId>spring-ai-cassandra-store-spring-boot-starter</artifactId>
</dependency>
请参阅 依赖项管理 部分,将 Spring AI BOM 添加到您的构建文件中。

用法

创建一个连接到 Apache Cassandra 数据库的 CassandraVectorStore 实例

@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {

  CassandraVectorStoreConfig config = CassandraVectorStoreConfig.builder().build();

  return new CassandraVectorStore(config, embeddingModel);
}

默认配置连接到 localhost:9042 上的 Cassandra,并将自动在 keyspace springframework 中创建默认模式,表名为 ai_vector_store

Cassandra Java 驱动程序最容易通过类路径上的 application.conf 文件进行配置。更多信息 在此

然后在您的主代码中,创建一些文档

List<Document> documents = List.of(
   new Document("Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!! Spring AI rocks!!", Map.of("country", "UK", "year", 2020)),
   new Document("The World is Big and Salvation Lurks Around the Corner", Map.of()),
   new Document("You walk forward facing the past and you turn back toward the future.", Map.of("country", "NL", "year", 2023)));

现在将文档添加到您的向量存储中

vectorStore.add(documents);

最后,检索与查询相似的文档

List<Document> results = vectorStore.similaritySearch(
   SearchRequest.query("Spring").withTopK(5));

如果一切顺利,您应该检索到包含文本“Spring AI rocks!!”的文档。

您还可以根据相似度阈值限制结果

List<Document> results = vectorStore.similaritySearch(
   SearchRequest.query("Spring").withTopK(5)
      .withSimilarityThreshold(0.5d));

元数据过滤

您也可以将通用的、可移植的 元数据过滤器 与 CassandraVectorStore 一起使用。元数据列必须在 CassandraVectorStoreConfig 中配置。

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

vectorStore.similaritySearch(
   SearchRequest.query("The World").withTopK(TOP_K)
      .withFilterExpression("country in ['UK', 'NL'] && year >= 2020"));

或者使用表达式 DSL 以编程方式

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

vectorStore.similaritySearch(
   SearchRequest.query("The World").withTopK(TOP_K)
      .withFilterExpression(f));

可移植的过滤器表达式会自动转换为 CQL 查询

要使元数据列可搜索,它们必须是主键或 SAI 索引。要使非主键列被索引,请使用 SchemaColumnTags.INDEXED 配置元数据列。

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

以下示例演示了如何在现有模式上使用存储。这里我们使用来自 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 CassandraVectorStore store(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));

    CassandraVectorStoreConfig conf = CassandraVectorStoreConfig.builder()
        .withKeyspaceName("wikidata")
        .withTableName("articles")
        .withPartitionKeys(partitionColumns)
        .withClusteringKeys(clusteringColumns)
        .withContentColumnName("body")
        .withEmbeddingColumndName("all_minilm_l6_v2_embedding")
        .withIndexName("all_minilm_l6_v2_ann")
        .disallowSchemaChanges()
        .addMetadataColumns(extraColumns)
        .withPrimaryKeyTranslator((List<Object> primaryKeys) -> {
            // the deliminator used to join fields together into the document's id is arbitary
            // here "§¶" is used
            if (primaryKeys.isEmpty()) {
                return "test§¶0";
            }
            return format("%s§¶%s", primaryKeys.get(2), primaryKeys.get(3));
        })
        .withDocumentIdTranslator((id) -> {
            String[] parts = id.split("§¶");
            String title = parts[0];
            int chunk_no = 0 < parts.length ? Integer.parseInt(parts[1]) : 0;
            return List.of("simplewiki", "en", title, chunk_no, 0);
        })
        .build();

    return new CassandraVectorStore(conf, embeddingModel());
}

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

完整的维基百科数据集

并且,如果您想加载完整的维基百科数据集。首先从以下链接下载 simplewiki-sstable.tar s.apache.org/simplewiki-sstable-tar。这将需要一段时间,文件大小为数十 GB。

tar -xf simplewiki-sstable.tar -C ${CASSANDRA_DATA}/data/wikidata/articles-*/

nodetool import wikidata articles ${CASSANDRA_DATA}/data/wikidata/articles-*/
如果您在此表中已有现有数据,您需要检查 tarball 的文件在执行 tar 时不会覆盖现有的 sstable。
除了使用 nodetool import 命令,还可以重启 Cassandra 来解决问题。
如果索引中存在任何错误,它们将自动重建。