Spring AI之向量数据库

[复制链接]
发表于 2025-6-7 22:52:49 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
向量数据库是一种在人工智能应用中发挥关键作用的专用数据库范例。
在向量数据库中,查询方式与传统关系型数据库存在显著差异。它们并非进行准确匹配,而是执行相似性搜索。当以向量作为查询输入时,向量数据库会返回与查询向量"相似"的向量集合。关于这种相似性在高层级的计算方式,详见《向量相似性》章节的说明。
向量数据库用于将数据与人工智能模型进行整合。其使用流程的第一步是将数据加载到向量数据库中。当必要向AI模型发送用户查询时,系统会起首检索一组相似文档。这些文档将作为用户题目的上下文信息,与用户查询一起被发送至AI模型。这种技术被称为检索增强天生(Retrieval Augmented Generation, RAG)。
后续章节将介绍Spring AI框架中用于利用多种向量数据库实现的接口规范,以及相干高级用法示例。
最后一节旨在剖析向量数据库中相似性搜索技术的底层实现原理。
API 概述

本节作为 Spring AI 框架中 VectorStore 接口 及其关联类的指南。
Spring AI 通过 VectorStore 接口 提供了与向量数据库交互的抽象化 API
以下是 VectorStore 接口 的界说:
  1. public interface VectorStore extends DocumentWriter {
  2.     default String getName() {
  3.         return this.getClass().getSimpleName();
  4.     }
  5.     void add(List<Document> documents);
  6.     void delete(List<String> idList);
  7.     void delete(Filter.Expression filterExpression);
  8.     default void delete(String filterExpression) { ... };
  9.     List<Document> similaritySearch(String query);
  10.     List<Document> similaritySearch(SearchRequest request);
  11.     default <T> Optional<T> getNativeClient() {
  12.         return Optional.empty();
  13.     }
  14. }
复制代码
以及相干的 SearchRequest 构建器 :
  1. public class SearchRequest {
  2.     public static final double SIMILARITY_THRESHOLD_ACCEPT_ALL = 0.0;
  3.     public static final int DEFAULT_TOP_K = 4;
  4.     private String query = "";
  5.     private int topK = DEFAULT_TOP_K;
  6.     private double similarityThreshold = SIMILARITY_THRESHOLD_ACCEPT_ALL;
  7.     @Nullable
  8.     private Filter.Expression filterExpression;
  9.     public static Builder from(SearchRequest originalSearchRequest) {
  10.         return builder().query(originalSearchRequest.getQuery())
  11.             .topK(originalSearchRequest.getTopK())
  12.             .similarityThreshold(originalSearchRequest.getSimilarityThreshold())
  13.             .filterExpression(originalSearchRequest.getFilterExpression());
  14.     }
  15.     public static class Builder {
  16.         private final SearchRequest searchRequest = new SearchRequest();
  17.         public Builder query(String query) {
  18.             Assert.notNull(query, "查询内容不能为空。");
  19.             this.searchRequest.query = query;
  20.             return this;
  21.         }
  22.         public Builder topK(int topK) {
  23.             Assert.isTrue(topK >= 0, "TopK 必须为非负数。");
  24.             this.searchRequest.topK = topK;
  25.             return this;
  26.         }
  27.         public Builder similarityThreshold(double threshold) {
  28.             Assert.isTrue(threshold >= 0 && threshold <= 1, "相似度阈值必须在 [0,1] 范围内。");
  29.             this.searchRequest.similarityThreshold = threshold;
  30.             return this;
  31.         }
  32.         public Builder similarityThresholdAll() {
  33.             this.searchRequest.similarityThreshold = 0.0;
  34.             return this;
  35.         }
  36.         public Builder filterExpression(@Nullable Filter.Expression expression) {
  37.             this.searchRequest.filterExpression = expression;
  38.             return this;
  39.         }
  40.         public Builder filterExpression(@Nullable String textExpression) {
  41.             this.searchRequest.filterExpression = (textExpression != null)
  42.                 ? new FilterExpressionTextParser().parse(textExpression) : null;
  43.             return this;
  44.         }
  45.         public SearchRequest build() {
  46.             return this.searchRequest;
  47.         }
  48.     }
  49.     public String getQuery() {...}
  50.     public int getTopK() {...}
  51.     public double getSimilarityThreshold() {...}
  52.     public Filter.Expression getFilterExpression() {...}
  53. }
复制代码
数据插入与向量嵌入

要向向量数据库插入数据,需将其封装在 Document 对象 中。
Document 类 封装了来自数据源(如 PDF 或 Word 文档)的内容,包含以字符串形式表示的文本,以及以键值对形式存储的元数据(如文件名等)。
插入向量数据库时,文本内容会通过 嵌入模型 (如 Word2Vec、GLoVE、BERT 或 OpenAI 的 text-embedding-ada-002)转换为数值数组(float[]),即 向量嵌入 (vector embeddings)。
向量数据库的职责是存储这些嵌入并向其提供相似性搜索功能,但其自己不天生嵌入。天生向量嵌入需使用 EmbeddingModel 。
相似性搜索参数

接口中的 similaritySearch 方法支持通过以下参数优化检索结果:


  • k (topK)
    指定返回的相似文档最大数目(整数)。此参数通常称为 Top K 搜索 或 K 近邻(KNN) 。
  • threshold (相似度阈值)
    范围为 0 到 1 的浮点值,数值越接近 1 表示相似度越高。比方,默认阈值 0.75 仅返回相似度高于此值的文档。
  • Filter.Expression
    用于传递类似 SQL WHERE 子句的范畴特定语言(DSL)表达式,但仅作用于文档的元数据键值对。
    示例:若元数据包含 country、year 和 isActive,可使用表达式 country == ‘UK’ && year >= 2020 && isActive == true。
  • filterExpression (字符串形式的过滤表达式)
    基于 ANTLR4 的外部 DSL,接受字符串形式的过滤表达式。
更多关于 Filter.Expression 的信息,请参考 元数据过滤器 章节。
模式初始化

某些向量存储必要先初始化后端模式后才能使用。默认情况下,系统不会主动完成此初始化利用。您必要通过以下方式主动启用:
在构造函数中传递一个布尔值参数,或
假如使用 Spring Boot,在 application.properties 或 application.yml 中将对应的 initialize-schema 属性设置为 true。
批处理计谋

在处理向量存储时,通常必要嵌入大量文档。尽管一次性嵌入所有文档看似直接,但这种方法可能导致题目。嵌入模型以令牌(token)为单位处理文本,并存在最大令牌限制(即上下文窗口巨细)。单次嵌入哀求若超出此限制,可能引发错误或截断嵌入结果。
为办理令牌限制题目,Spring AI 实现了批处理计谋 。该计谋将大量文档拆分为较小批次,确保每个批次的令牌数不凌驾嵌入模型的最大上下文窗口限制。批处理不仅规避了令牌限制,还能提拔性能并更高效地利用 API 速率限制。
Spring AI 通过 BatchingStrategy 接口提供此功能,支持基于令牌数对文档进行分批处理。
核心接口界说

BatchingStrategy 接口界说如下:
  1. public interface BatchingStrategy {
  2.     List<List<Document>> batch(List<Document> documents);
  3. }
复制代码
该接口界说了一个 batch 方法,吸收文档列表并返回分批后的文档列表。
默认实现:TokenCountBatchingStrategy

Spring AI 提供了默认实现 TokenCountBatchingStrategy,该计谋根据文档的令牌数进行分批,确保每批不凌驾最大输入令牌限制。
核心特性 :


  • 默认使用 OpenAI 的最大输入令牌数(8191)作为上限。
  • 包含保留百分比(默认 10%),为潜伏开销提供缓冲。
  • 实际最大输入令牌数计算公式:
    actualMaxInputTokenCount = originalMaxInputTokenCount * (1 - RESERVE_PERCENTAGE)
  • 若单个文档超出限制,将抛出异常。
    自界说设置示例 :
    可通过 Spring Boot 设置类界说自界说参数:
  1. @Configuration
  2. public class EmbeddingConfig {
  3.     @Bean
  4.     public BatchingStrategy customTokenCountBatchingStrategy() {
  5.         return new TokenCountBatchingStrategy(
  6.             EncodingType.CL100K_BASE,  // 指定编码类型
  7.             8000,                      // 最大输入令牌数
  8.             0.1                        // 保留百分比
  9.         );
  10.     }
  11. }
复制代码
参数说明 :


  • EncodingType.CL100K_BASE:用于令牌计数的编码范例,确保与 JTokkitTokenCountEstimator 兼容。
  • 8000:需小于等于嵌入模型的最大上下文窗口巨细。
  • 0.1:保留 10% 的令牌数作为缓冲。
内部机制与扩展性



  • 令牌计数估计器 :
    TokenCountBatchingStrategy 内部使用 JTokkitTokenCountEstimator 估算令牌数,支持通过 TokenCountEstimator 接口自界说实现。
    示例:
  1. TokenCountEstimator customEstimator = new YourCustomTokenCountEstimator();
  2. TokenCountBatchingStrategy strategy = new TokenCountBatchingStrategy(
  3.     customEstimator,
  4.     8000,    // 最大输入令牌数
  5.     0.1,     // 保留百分比
  6.     Document.DEFAULT_CONTENT_FORMATTER,
  7.     MetadataMode.NONE
  8. );
复制代码


  • 内容与元数据处理
    默认使用 Document.DEFAULT_CONTENT_FORMATTER 格式化内容,MetadataMode.NONE 忽略元数据。可通过完备构造函数自界说。
自界说批处理计谋

若需完全自界说批处理逻辑,可通过实现 BatchingStrategy 接口:
  1. @Configuration
  2. public class EmbeddingConfig {
  3.     @Bean
  4.     public BatchingStrategy customBatchingStrategy() {
  5.         return new CustomBatchingStrategy(); // 自定义实现
  6.     }
  7. }
复制代码
此自界说计谋将主动被 Spring AI 的 EmbeddingModel 实现使用。
注意事项


  • 支持的向量存储 :目前 SAP Hana 向量存储未设置批处理支持。
  • 性能优化 :合理设置 maxInputTokenCount 和 reservePercentage 可平衡吞吐量与稳定性。
VectorStore 实现

以下是 VectorStore 接口的可用实现:


  • Azure 向量搜索
  • Apache Cassandra
  • Chroma 向量存储
  • Elasticsearch 向量存储
  • GemFire 向量存储
  • MariaDB 向量存储
  • Milvus 向量存储
  • MongoDB Atlas
  • Neo4j 向量存储
  • OpenSearch 向量存储
  • Oracle 向量存储
  • PPostgreSQL/PGVector 向量存储。
  • Pinecone 向量存储
  • Qdrant 向量存储
  • Redis 向量存储
  • SAP HANA 向量存储
  • Typesense 向量存储
  • Weaviate 向量存储
  • SimpleVectorStore - 简单的持久化向量存储实现,适合教育用途。
未来版本可能会支持更多实现。
假如必要 Spring AI 支持您的向量数据库,可在 GitHub 提交题目(issue)或直接通过拉取哀求(pull request)贡献实现代码。
示例用法

要计算向量数据库的嵌入(embeddings),需选择与所使用的高层级 AI 模型相匹配的嵌入模型。
比方,使用 OpenAI 的 ChatGPT 时,需选用 OpenAiEmbeddingModel 及名为 text-embedding-ada-002 的模型。
Spring Boot 的 OpenAI starter 会通过主动设置,在 Spring 应用上下文中提供一个 EmbeddingModel 实现,用于依赖注入。
数据加载到向量存储

将数据加载到向量存储的利用通常以批处理模式完成:


  • 将数据加载到 Spring AI 的 Document 类中;
  • 调用 save 方法存储。
示例代码 :
  1. @Autowired
  2. VectorStore vectorStore;
  3. void load(String sourceFile) {
  4.     // 使用 JsonReader 加载 JSON 文件的指定字段(如 price、name 等)
  5.     JsonReader jsonReader = new JsonReader(
  6.         new FileSystemResource(sourceFile),
  7.         "price", "name", "shortDescription", "description", "tags"
  8.     );
  9.     List<Document> documents = jsonReader.get();
  10.     // 调用 VectorStore.add() 存储文档
  11.     this.vectorStore.add(documents); // [[4]]
  12. }
复制代码
过程说明 :


  • JsonReader 将 JSON 文件拆分为小块(如按字段分割),并封装为 Document 对象;
  • VectorStore 实现会主动计算嵌入(通过 EmbeddingModel),并将 JSON 内容与嵌入向量一并存储到数据库中。
相似性搜索与上下文注入

当用户提问传入 AI 模型时,流程如下:


  • 执行相似性搜索 :
  1. String question = <用户问题>;
  2. List<Document> similarDocuments = store.similaritySearch(question);
复制代码


  • 参数调优 :

    • topK:指定返回的相似文档数目(如默认 4 个);
    • similarityThreshold:设置相似度阈值(范围 0-1,值越大匹配越严酷)。

  • 上下文注入 :
    检索到的相似文档会被“添补”到 AI 模型的输入提示(prompt)中,作为用户题目的上下文。
关键技术点



  • 批处理计谋 :若文档量较大,需通过 TokenCountBatchingStrategy 分批处理,避免超出嵌入模型的上下文窗口限制(如 OpenAI 的 8191 令牌上限)。
  • 元数据过滤 :可通过 Filter.Expression 对元数据进行筛选(如 country == ‘UK’)。
元数据过滤器

本节描述可用于对查询结果进行过滤的多种方式。
过滤字符串(Filter String)

您可以通过 similaritySearch 方法的重载形式,传入类似 SQL 语法的字符串表达式作为过滤条件。
示例 :
  1. "country == 'BG'"                // 等值过滤
  2. "genre == 'drama' && year >= 2020" // 组合条件
  3. "genre in ['comedy', 'documentary', 'drama']" // 枚举过滤
复制代码
Filter.Expression 构建器

您可以通过 FilterExpressionBuilder 创建 Filter.Expression 实例,以构建链式 API 表达式。
基础示例 :
  1. FilterExpressionBuilder b = new FilterExpressionBuilder();
  2. Expression expression = b.eq("country", "BG").build(); // 等于条件
复制代码
支持的利用符 :


  • 比力利用符 :
    EQUALS(==)、GT(>)、GE(>=)、LT(<)、LE(<=)、NE(!=)
  • 组合利用符 :
    AND(&& 或 and)、OR(|| 或 or)
  • 集合利用符 :
    IN(包含)、NIN(不包含)、NOT(取反)
复杂表达式示例 :
  1. // 组合条件:genre 为 'drama' 且年份 ≥ 2020
  2. Expression exp = b.and(b.eq("genre", "drama"), b.gte("year", 2020)).build();
复制代码
技术细节


  • 预过滤优化 :部分向量数据库(如 LanceDB)会在向量搜索前应用元数据过滤,以缩小检索范围并降低耽误。
  • 嵌入模型兼容性 :过滤条件仅作用于文档元数据,不影响嵌入向量的计算。
从向量存储中删除文档

向量存储接口(VectorStore)提供了多种删除文档的方法,支持通过文档ID列表 或过滤表达式 删除数据。
按文档ID删除

最简单的删除方式是提供文档ID列表:
  1. void delete(List<String> idList);
复制代码
此方法会删除所有ID与列表匹配的文档。若列表中存在不存在的ID,将被忽略。
示例代码 :
  1. // 创建并添加文档
  2. Document document = new Document("The World is Big",
  3.     Map.of("country", "Netherlands")); // 元数据包含国家信息
  4. vectorStore.add(List.of(document));
  5. // 按ID删除文档
  6. vectorStore.delete(List.of(document.getId()));
复制代码
按过滤表达式删除

对于复杂删除条件,可使用 Filter.Expression 对象界说删除规则:
  1. void delete(Filter.Expression filterExpression);
复制代码
此方法适用于基于元数据属性删除文档,比方按国家、年份等条件过滤。
示例代码 :
  1. // 创建不同元数据的测试文档
  2. Document bgDocument = new Document("The World is Big",
  3.     Map.of("country", "Bulgaria")); // 保加利亚文档
  4. Document nlDocument = new Document("The World is Big",
  5.     Map.of("country", "Netherlands")); // 荷兰文档
  6. // 添加文档到存储
  7. vectorStore.add(List.of(bgDocument, nlDocument));
  8. // 使用过滤表达式删除保加利亚文档
  9. Filter.Expression filterExpression = new Filter.Expression(
  10.     Filter.ExpressionType.EQ, // 等值操作符
  11.     new Filter.Key("country"), // 元数据键
  12.     new Filter.Value("Bulgaria") // 目标值
  13. );
  14. vectorStore.delete(filterExpression); // [[5]][[8]]
  15. // 验证删除结果
  16. SearchRequest request = SearchRequest.builder()
  17.     .query("World")
  18.     .filterExpression("country == 'Bulgaria'") // 过滤条件
  19.     .build();
  20. List<Document> results = vectorStore.similaritySearch(request);
  21. // 结果为空,因Bulgaria文档已被删除
复制代码
按字符串过滤表达式删除

为简化利用,支持直接传递字符串形式的过滤表达式:
  1. void delete(String filterExpression);
复制代码
此方法内部将字符串剖析为 Filter.Expression 对象,适用于动态天生的过滤条件。
示例代码 :
  1. // 添加文档
  2. vectorStore.add(List.of(bgDocument, nlDocument));
  3. // 使用字符串过滤表达式删除保加利亚文档
  4. vectorStore.delete("country == 'Bulgaria'"); // [[8]]
  5. // 验证剩余文档
  6. SearchRequest request = SearchRequest.builder()
  7.     .query("World")
  8.     .topK(5)
  9.     .build();
  10. List<Document> results = vectorStore.similaritySearch(request);
  11. // 仅返回荷兰文档
复制代码
删除利用的错误处理

所有删除方法可能因错误抛出异常,发起使用 try-catch 块包裹利用:
  1. try {
  2.     vectorStore.delete("country == 'Bulgaria'");
  3. } catch (Exception e) {
  4.     logger.error("无效的过滤表达式", e); // [[7]]
  5. }
复制代码
文档版本控制场景

范例用例是管理文档版本 ,比方更换旧版本文档:
实现步调 :


  • 添加初始版本 :
  1. Document documentV1 = new Document(
  2.     "AI与机器学习最佳实践",
  3.     Map.of(
  4.         "docId", "AIML-001",
  5.         "version", "1.0",
  6.         "lastUpdated", "2024-01-01"
  7.     )
  8. );
  9. vectorStore.add(List.of(documentV1));
复制代码


  • 删除旧版本并添加新版本 :
  1. // 使用过滤表达式删除旧版本
  2. Filter.Expression deleteOldVersion = new Filter.Expression(
  3.     Filter.ExpressionType.AND,
  4.     Arrays.asList(
  5.         new Filter.Expression(Filter.ExpressionType.EQ, "docId", "AIML-001"),
  6.         new Filter.Expression(Filter.ExpressionType.EQ, "version", "1.0")
  7.     )
  8. );
  9. vectorStore.delete(deleteOldVersion); // [[5]][[8]]
  10. // 添加新版本文档
  11. Document documentV2 = new Document(
  12.     "AI与机器学习最佳实践 - 更新版",
  13.     Map.of(
  14.         "docId", "AIML-001",
  15.         "version", "2.0",
  16.         "lastUpdated", "2024-02-01"
  17.     )
  18. );
  19. vectorStore.add(List.of(documentV2));
  20. // 验证仅保留新版本
  21. SearchRequest request = SearchRequest.builder()
  22.     .query("AI与机器学习")
  23.     .filterExpression("docId == 'AIML-001'")
  24.     .build();
  25. List<Document> results = vectorStore.similaritySearch(request);
  26. // results 仅包含版本2.0的文档
复制代码
性能优化发起



  • 按ID删除 :若已知详细ID列表,此方式速度更快。
  • 过滤表达式删除 :需扫描索引匹配文档,性能依赖详细向量存储实现。
  • 批量删除 :避免单次删除大量文档,发起分批处理。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
继续阅读请点击广告
回复

使用道具 举报

© 2001-2025 Discuz! Team. Powered by Discuz! X3.5

GMT+8, 2025-7-19 04:51 , Processed in 0.078774 second(s), 29 queries 手机版|qidao123.com技术社区-IT企服评测▪应用市场 ( 浙ICP备20004199 )|网站地图

快速回复 返回顶部 返回列表