此版本仍在开发中,尚未被视为稳定版。如需使用最新的稳定版本,请访问 Spring Data JPA 4.0.4spring-doc.cadn.net.cn

向量搜索

随着生成式人工智能的兴起,向量数据库在数据库领域获得了广泛关注。 这些数据库能够高效地存储和查询高维向量,非常适合用于语义搜索、推荐系统和自然语言理解等任务。spring-doc.cadn.net.cn

向量搜索是一种通过比较向量表示(也称为嵌入)来检索语义上相似数据的技术,而不是依赖传统的精确匹配查询。 这种方法能够实现智能的、具备上下文感知能力的应用程序,超越基于关键词的检索。spring-doc.cadn.net.cn

在 Spring Data 的上下文中,向量搜索为构建智能、上下文感知的应用程序开辟了新的可能性,特别是在自然语言处理、推荐系统和生成式 AI 等领域。 通过使用熟悉的 Repository 抽象来建模基于向量的查询,Spring Data 使开发者能够将支持相似性搜索的向量数据库无缝集成到 Spring Data 编程模型中,同时保持其简洁性和一致性。spring-doc.cadn.net.cn

要使用 Hibernate Vector Search,您需要在项目中添加以下依赖项。spring-doc.cadn.net.cn

以下示例展示了如何在 Maven 和 Gradle 中设置依赖项:spring-doc.cadn.net.cn

<dependencies>
    <dependency>
      <groupId>org.hibernate.orm</groupId>
      <artifactId>hibernate-vector</artifactId>
      <version>${hibernate.version}</version>
    </dependency>
</dependencies>
dependencies {
    implementation 'org.hibernate.orm:hibernate-vector:${hibernateVersion}'
}
虽然你可以将 Vector 用作查询的类型,但不能在你的领域模型中使用它,因为 Hibernate 要求向量类型必须是 float 或 double 数组。

向量模型

为了以类型安全且符合习惯的方式支持向量搜索,Spring Data 引入了以下核心抽象:spring-doc.cadn.net.cn

Vector

Vector 类型表示一个 n 维数值嵌入,通常由嵌入模型生成。 在 Spring Data 中,它被定义为围绕浮点数数组的轻量级包装器,以确保不可变性和一致性。 该类型可用作搜索查询的输入,或作为领域实体上的属性来存储关联的向量表示。spring-doc.cadn.net.cn

Vector vector = Vector.of(0.23f, 0.11f, 0.77f);

在您的领域模型中使用 Vector 可以避免直接操作原始数组或数字列表,从而提供一种类型更安全、表达力更强的方式来处理向量数据。 这种抽象还便于与各种向量数据库和库轻松集成。 它还支持实现特定于提供商的优化,例如二进制向量或量化向量,这些向量无法映射到标准的浮点数(根据 IEEE 754 标准,即 doublehttps://en.wikipedia.org/wiki/IEEE_754)表示形式。 一个领域对象可以拥有一个向量属性,该属性可用于相似性搜索。 请考虑以下示例:spring-doc.cadn.net.cn

class Comment {

  @Id String id;
  String country;
  String comment;

  @Column(name = "the_embedding")
  @JdbcTypeCode(SqlTypes.VECTOR)
  @Array(length = 5)
  Vector embedding;

  // getters, setters, …
}
将一个向量与领域对象关联,会导致该向量作为实体生命周期的一部分被加载和存储,这可能会在检索和持久化操作中引入额外的开销。

搜索结果

SearchResult<T> 类型封装了向量相似性查询的结果。 它既包含匹配的领域对象,也包含一个相关性得分,用于指示该对象与查询向量的匹配程度。 这一抽象提供了一种结构化的方式来处理结果排序,使开发者能够轻松地同时处理数据及其上下文相关性。spring-doc.cadn.net.cn

示例 1. 在 Repository 搜索方法中使用 SearchResult<T>
interface CommentRepository extends Repository<Comment, String> {

  SearchResults<Comment> searchByCountryAndEmbeddingNear(String country, Vector vector, Score distance,
    Limit limit);

  @Query("""
      SELECT c, cosine_distance(c.embedding, :embedding) as distance FROM Comment c
      WHERE c.country = ?1
        AND cosine_distance(c.embedding, :embedding) <= :distance
      ORDER BY distance asc""")
  SearchResults<Comment> searchAnnotatedByCountryAndEmbeddingWithin(String country, Vector embedding,
      Score distance);
}

SearchResults<Comment> results = repository.searchByCountryAndEmbeddingNear("en", Vector.of(…), Score.of(0.9), Limit.of(10));

在此示例中,searchByCountryAndEmbeddingNear 方法返回一个 SearchResults<Comment> 对象,其中包含一组 SearchResult<Comment> 实例。 每个结果都包含匹配的 Comment 实体及其相关性得分。spring-doc.cadn.net.cn

相关性得分是一个数值,用于表示匹配向量与查询向量的对齐程度。 根据得分代表的是距离还是相似度,较高的得分可能意味着更接近的匹配,也可能意味着更远的距离。spring-doc.cadn.net.cn

用于计算此分数的评分函数可能会根据底层数据库、索引或输入参数的不同而有所变化。spring-doc.cadn.net.cn

分数、相似度和评分函数

Score 类型包含一个数值,用于表示搜索结果的相关性。 它可用于根据结果与查询向量的相似度对结果进行排序。 Score 类型通常是一个浮点数,其解释方式(值越大越好还是越小越好)取决于所使用的具体相似度函数。 分数是向量搜索的副产品,并非成功执行搜索操作所必需。 分数值不属于领域模型的一部分,因此最适合表示为带外数据。spring-doc.cadn.net.cn

通常,得分(Score)由一个ScoringFunction计算得出。 用于计算该得分的实际评分函数可能取决于底层数据库,并可从搜索索引或输入参数中获取。spring-doc.cadn.net.cn

Spring Data 支持为常用函数声明常量,例如:spring-doc.cadn.net.cn

欧几里得距离

计算 n 维空间中的直线距离,涉及各维度差值平方和的平方根。spring-doc.cadn.net.cn

余弦相似度

通过先计算两个向量的点积,然后将其结果除以它们长度的乘积进行归一化,从而测量两个向量之间的夹角。spring-doc.cadn.net.cn

点积

计算逐元素相乘后的总和。spring-doc.cadn.net.cn

相似度函数的选择会影响搜索的性能和语义,通常由所使用的底层数据库或索引决定。 Spring Data 会适配数据库原生的评分函数功能,以及该评分是否可用于限制结果数量。spring-doc.cadn.net.cn

Hibernate 将距离函数调用转换为 PGvector 和 Oracle 数据库的原生数据库函数。 其结果通常是一个距离值。 当使用 Similarity 而非 Score 时,Spring Data 会将距离得分归一化为介于 0 到 1 之间的相似度得分。得分越高,表示两个向量越相似。spring-doc.cadn.net.cn

示例 2. 在 Repository 搜索方法中使用 ScoreSimilarity
interface CommentRepository extends Repository<Comment, String> {

  SearchResults<Comment> searchByEmbeddingNear(Vector vector, ScoringFunction function);

  SearchResults<Comment> searchByEmbeddingNear(Vector vector, Score score);

  SearchResults<Comment> searchByEmbeddingNear(Vector vector, Similarity similarity);

  SearchResults<Comment> searchByEmbeddingNear(Vector vector, Range<Similarity> range);
}

repository.searchByEmbeddingNear(Vector.of(…), ScoringFunction.cosine());                               (1)

repository.searchByEmbeddingNear(Vector.of(…), Score.of(0.9, ScoringFunction.cosine()));                (2)

repository.searchByEmbeddingNear(Vector.of(…), Similarity.of(0.9, ScoringFunction.cosine()));           (3)

repository.searchByEmbeddingNear(Vector.of(…), Similarity.between(0.5, 1, ScoringFunction.euclidean()));(4)
1 执行一次搜索,并返回与给定的Vector相似的结果,使用余弦评分进行计算。
2 运行一次搜索,并使用余弦距离返回得分小于或等于 0.9 的结果。
3 执行一次搜索并将得分归一化为相似度值。 使用余弦评分,返回相似度大于或等于 0.9 的结果。
4 执行一次搜索并将得分归一化为相似度值。 使用欧几里得评分,返回相似度在 0.51.0 之间或更高的结果。
JPA 在创建 ScoringFunctionScore 实例以选择评分函数时,需要提供一个 Similarity

向量搜索方法

向量搜索方法在仓库中使用与标准 Spring Data 查询方法相同的约定进行定义。 这些方法返回 SearchResults<T>,并需要一个 Vector 参数来定义查询向量。 具体的实现取决于底层数据存储的实际内部机制及其对向量搜索的支持能力。spring-doc.cadn.net.cn

如果你是初次使用 Spring Data 仓库,请务必先熟悉仓库定义和查询方法的基础知识

通常,您可以选择使用两种方法来声明搜索方法:spring-doc.cadn.net.cn

向量搜索方法必须声明一个 Vector 参数以定义查询向量。spring-doc.cadn.net.cn

派生查询方法

派生的搜索方法使用方法名称来推导查询。 在声明搜索方法时,向量搜索支持以下关键字以执行向量搜索:spring-doc.cadn.net.cn

表1. 查询谓词关键字
逻辑关键字 关键字表达式

NEARspring-doc.cadn.net.cn

Near, IsNearspring-doc.cadn.net.cn

WITHINspring-doc.cadn.net.cn

Within, IsWithinspring-doc.cadn.net.cn

示例 3. 在 Repository 搜索方法中使用 NearWithin 关键字
interface CommentRepository extends Repository<Comment, String> {

  SearchResults<Comment> searchByEmbeddingNear(Vector vector, Score score);

  SearchResults<Comment> searchByEmbeddingWithin(Vector vector, Range<Similarity> range);

  SearchResults<Comment> searchByCountryAndEmbeddingWithin(String country, Vector vector, Range<Similarity> range);
}

派生的搜索方法可以在领域模型属性和 Vector 参数上声明谓词。spring-doc.cadn.net.cn

派生的搜索方法通常更易于阅读和维护,因为它们依靠方法名称来表达查询意图。 然而,派生的搜索方法需要在 Score/Range<Score> 关键字后将 ScoreFunctionNearWithin 之一作为第二个参数进行声明,以根据评分限制搜索结果。spring-doc.cadn.net.cn

已注解的搜索方法

带注解的方法可完全控制查询语义和参数。 与派生方法不同,它们不依赖于方法命名约定。spring-doc.cadn.net.cn

带注解的搜索方法必须定义完整的 JPQL 查询以执行向量搜索。spring-doc.cadn.net.cn

示例 4. 使用 @Query 搜索方法
interface CommentRepository extends Repository<Comment, String> {

  @Query("""
      SELECT c, cosine_distance(c.embedding, :embedding) as distance FROM Comment c
      WHERE c.country = ?1
        AND cosine_distance(c.embedding, :embedding) <= :distance
      ORDER BY distance asc""")
  SearchResults<Comment> searchAnnotatedByCountryAndEmbeddingWithin(String country, Vector embedding,
      Score distance);

  @Query("""
      SELECT c FROM Comment c
      WHERE c.country = ?1
        AND cosine_distance(c.embedding, :embedding) <= :distance
      ORDER BY cosine_distance(c.embedding, :embedding) asc""")
  List<Comment> findAnnotatedByCountryAndEmbeddingWithin(String country, Vector embedding, Score distance);
}

向量搜索方法在其投影中不要求包含分数或距离。 当使用返回 SearchResults 的注解式搜索方法时,执行机制会假定:如果存在第二个投影列,则该列保存的是分数值。spring-doc.cadn.net.cn

通过对实际查询拥有更多控制权,Spring Data 可以减少对查询及其参数的假设。 例如,Similarity 归一化使用查询内部的原生评分函数,将给定的相似度归一化为评分谓词值,反之亦然。 如果注解的查询未定义评分(例如 score),则返回的 SearchResult<T> 中的评分值将为零。spring-doc.cadn.net.cn

排序

默认情况下,搜索结果会根据其相关性得分进行排序。 您可以通过使用 Sort 参数来覆盖默认的排序方式:spring-doc.cadn.net.cn

示例 5. 在 Repository 搜索方法中使用 Sort
interface CommentRepository extends Repository<Comment, String> {

  SearchResults<Comment> searchByEmbeddingNearOrderByCountry(Vector vector, Score score);

  SearchResults<Comment> searchByEmbeddingWithin(Vector vector, Score score, Sort sort);
}

请注意,自定义排序不允许将评分(score)作为排序条件。 您只能引用领域属性。spring-doc.cadn.net.cn