在 Spring 体系中做 RAG,可以不单独引入向量数据库——用 ES 就够了

如果本身项目是基于 SpringBoot 的,或者项目中已经引入了 ElasticSearch ,其实可以考虑直接使用 ES ,而不再引入向量数据库
ES!我的超人!


第一章|为什么 Spring 体系做 RAG,很多人第一个想到的就是向量数据库?

本章带你理解:向量数据库到底是什么,为什么大家都在谈它,以及为什么它不是唯一的选择。

假设你是一家在线教育公司的后端开发,某天产品经理跑来找你:"我们要做一个智能答疑助手,学生提问后,系统能从我们的题库和讲义里自动找到相关内容,然后用自然语言回答。"

你上网一搜------满屏都在说 RAG(检索增强生成) ,而 RAG 的标配似乎就是 向量数据库:Milvus、Qdrant、Pinecone、Weaviate......一个个名字扑面而来。

你的内心可能是这样的:

flowchart LR A[产品提需求:智能答疑] --> B[搜索解决方案] B --> C[到处都在说向量数据库] C --> D{我也要上向量数据库吗?} D --> E[需要新组件、新运维、新学习成本] D --> F[等等...我已经有 ES 了]

向量数据库到底干了什么?

要搞清楚这个问题,先得明白向量数据库在 RAG 里扮演的角色。

Embedding(向量化) 的本质是把一段文本映射成一个高维数字数组。比如"Spring Boot 的自动装配原理"这段话,经过 Embedding 模型处理后,可能变成一个 1024 维的浮点数数组:[0.023, -0.157, 0.891, ..., 0.044]

语义相近的文本,在高维空间里的距离就近。这就是向量检索的基础。

语义是否相近常用的计算方法是这样计算的,基本原理是大家上学时学过的余弦相似度: <math xmlns="http://www.w3.org/1998/Math/MathML"> cosine_similarity ( A , B ) = A ⋅ B ∥ A ∥ × ∥ B ∥ = ∑ i = 1 n a i × b i ∑ i = 1 n a i 2 × ∑ i = 1 n b i 2 \text{cosine\similarity}(A, B) = \frac{A \cdot B}{\|A\| \times \|B\|} = \frac{\sum{i=1}^{n} a_i \times b_i}{\sqrt{\sum_{i=1}^{n} a_i^2} \times \sqrt{\sum_{i=1}^{n} b_i^2}} </math>cosine_similarity(A,B)=∥A∥×∥B∥A⋅B=∑i=1nai2 ×∑i=1nbi2 ∑i=1nai×bi 但也有一些其他的方法,只是余弦相似度是最常用的方法之一

其中 A 和 B 是两个向量,分子是点积,分母是两个向量的模长乘积。

简单来说:Embedding 模型把文字变成"语义指纹",向量数据库存储这些指纹并支持"找最相似的指纹"。

向量数据库 (如 Milvus、Qdrant)就是专门为这种"找最相似指纹"的场景设计的存储和检索引擎。它们的核心能力是 ANN(近似最近邻)搜索,在海量向量中快速找到与查询向量最接近的结果。

ANN 与 KNN 的区别:传统的 KNN(精确最近邻)需要遍历所有向量才能找到最相似的,计算量随数据量线性增长;
ANN 则通过预先建立索引结构(如 HNSW 图),用"跳着找"的方式近似地快速定位,在精度和速度之间做权衡。百万级数据下,ANN 可以把检索时间从 O(n) 降到 O(log n) 或更快。

主流 ANN 算法一览
算法 代表产品 核心思路 优势 劣势
HNSW ES、Milvus、Qdrant、Pinecone 多层跳表图,从粗到细逐层搜索 速度快、精度高、参数少 内存占用较高
IVF FAISS、Milvus 先聚类再搜索,只在目标簇内查找 内存友好、可压缩 需要预训练聚类中心
PQ FAISS 向量分片后量化压缩 极致压缩、适合十亿级数据 精度损失明显
HNSW + IVF 混合方案 图索引 + 聚类双重加速 兼顾速度与内存 配置调优复杂

选型建议:中小规模(< 1000万)优先选 HNSW,简单高效;超大规模(> 1亿)可考虑 PQ 或 HNSW+IVF 混合方案。

但是------你可能不需要向量数据库

如果你的技术栈已经在用 Spring Boot + Elasticsearch,那么请先回答三个问题:

  1. 你的Agent需要检索的数据量是百万级以下吗?
  2. 你需要同时支持关键词检索和语义检索吗?
类型 说明 劣势 常用方式
关键词检索 直接精准匹配关键词,尤其是对确切的数字、术语效果很好 无法理解同义词/语义关联 BM25
语义检索 匹配"相关意思"的内容,本质是向量相似度 可能偏离字面(比如要拉格朗日中值定理,结果匹配到了柯西中值定理),计算量大 HNSW等
  1. 你团队的运维带宽有限吗?

如果这三个问题的答案都是"是",那么 ES 8.x 的 dense_vector 字段 + HNSW 算法,完全能胜任你的向量检索需求。你不需要额外引入一个向量数据库。

如果只有部分是"是",那你可以看完本文后进行取舍和抉择。

市场现状:根据 2024-2025 年的开发者调研和实践反馈,在中小规模 RAG 场景中(数据量 < 500 万条),ES 作为向量存储的方案已经被大量公司采用。Spring AI 官方也原生支持 Elasticsearch 作为 向量存储(VectorStore),这本身就是一种"官方背书"。


第二章|ES 做"向量数据库",为什么够用?

本章详细拆解 ES 8.x 的向量检索能力------从底层算法 HNSW,到 dense_vector 字段,再到它和专用向量数据库的对比

ES 8.x 的向量检索能力从哪来?

ES 从 7.8.0 版本开始支持 dense_vector(稠密向量,稠密指向量的大部分位置非0) 字段类型,8.x 版本更是引入了原生的 kNN 搜索 能力,底层使用的是 HNSW(Hierarchical Navigable Small World) 算法。

准确的说,引入的是 ANN 能力,并将其封装在 kNN 这个 API 中。 (注意k的大小写,KNN是算法,kNN是 API)

HNSW 是什么?

HNSW 是目前工业界最主流的 ANN 算法之一,Milvus、Qdrant 也都用它。核心思想是构建一个多层图结构

flowchart TB subgraph Layer2["顶层 - 稀疏图 - 粗略"] A1((A)) --- B1((B)) end subgraph Layer1["中层 - 中等密度"] A2((A)) --- B2((B)) B2 --- C2((C)) A2 --- C2 end subgraph Layer0["底层 - 密集 - 精确"] A3((A)) --- B3((B)) B3 --- C3((C)) C3 --- D3((D)) A3 --- C3 B3 --- D3 D3 --- E3((E)) A3 --- E3 end A1 -.-> A2 A2 -.-> A3 B1 -.-> B2 B2 -.-> B3

搜索时从顶层入口 出发,逐层向下精搜,最终在底层找到最近的邻居。时间复杂度约为 O(log n) ,在百万级数据中,单次查询通常在 几十毫秒 级别。

类比理解:比如你想去一个叫"深圳"的地方旅游。你会先看中国地图(顶层,有各个省份)定位到广东省;然后看广东省地图(中层,有各个城市)定位到深圳市;最后看深圳地图(底层,精确搜索)走到具体景点,到了发现深圳的景点只有商场(bushi)。HNSW 就是这个思路。

ES 里的 dense_vector 字段

在 ES 中,你只需要在索引 mapping 里定义一个 dense_vector 字段,指定维度即可:

json 复制代码
PUT /knowledge_base
{
  "mappings": {
    "properties": {
      "textContent": { "type": "text" },
      "vector": {
        "type": "dense_vector",
        "dims": 1024,
        "index": true,
        "similarity": "cosine"
      }
    }
  }
}
  • dims:向量的维度,取决于你用的 Embedding 模型输出维度
  • index: true:开启 HNSW 索引(8.x 默认开启)
  • similarity :相似度算法,支持 cosine(余弦)、l2_norm(欧氏距离)、dot_product(内积)

选哪种相似度? 文本语义检索场景最常用的是 cosine(余弦相似度) ,因为它对向量长度不敏感,只关注方向,更适合文本语义的度量。l2_norm 更适合图像向量检索,dot_product 在模型训练时已归一化的情况下等价于 cosine 但更快。

ES vs 专用向量数据库

对比维度 Elasticsearch 8.x Milvus Qdrant
核心定位 全能搜索引擎(文本+向量) 专用向量数据库 轻量级向量数据库
文本检索 ✅ BM25 原生支持 ❌ 需外部组件 ❌ 需外部组件
向量检索 ✅ HNSW ✅ HNSW/IVF/多种 ✅ HNSW
混合检索 ✅ 一套索引内完成 ❌ 需两套系统拼接 ⚠️ 支持 Dense+Sparse
数据量上限 百万~千万级 百亿级 亿级
运维复杂度 低(大部分团队已有ES) 高(独立集群+依赖) 中(单进程/Docker)
Spring AI 支持 ✅ 原生 VectorStore ✅ 原生 VectorStore ✅ 原生 VectorStore
学习成本 低(沿用ES知识) 高(新概念+新API)

专用向量数据库可能需要两套系统拼接,这也是选型的重要因考虑因素一。像 Milvus 这种向量数据库不支持关键字检索,就需要拼接,如果你本就不熟悉向量数据库,还要在这基础上进行拼接,更是难上加难。
关键结论 :如果你的数据量在百万~千万级,且需要混合检索,ES 是最优选。只有在数据量超过亿级,或者向量检索是系统唯一核心功能时,才需要考虑 Milvus 这类专业向量数据库。


第三章|ES 在 RAG 的角色

本章从"为什么大模型需要外挂知识库"讲起,完整梳理 RAG 的工作流程,并标出每一步 ES 能做什么。

为什么大模型"知识不够用"?

大模型(如 GPT、DeepSeek、千问)有两个硬伤:

  1. 训练数据有截止日期------它不知道昨天发生的事
  2. 它不知道你公司的内部知识------你的业务文档、产品手册、历史工单,模型从未见过

💬 举个不恰当但直观的例子:大模型就像一个读了万卷书但从未出过门的学者。你问他"唐朝的税制",他能滔滔不绝;但你问他"我们公司上个月的退款政策改了什么",他就一脸懵。

RAG(Retrieval Augmented Generation,检索增强生成) 就是解决这个问题的方案:先检索,再生成。把相关知识从外部知识库中找出来,喂给大模型,让它基于真实资料回答。

RAG 的完整工作流

flowchart LR subgraph 离线阶段["📚 离线阶段:知识入库"] A[原始文档] --> B[文档解析] B --> C[文本分片] C --> D[Embedding 向量化] D --> E[存入 ES] end subgraph 在线阶段["🔍 在线阶段:用户提问"] F[用户提问] --> G[问题向量化] G --> H[混合检索] H --> I[重排序+过滤] I --> J[拼装 Prompt] J --> K[LLM 生成回答] end E -.-> H

在这个流程中,ES 可以覆盖以下环节:

RAG 环节 ES 能做什么
文档存储 原文 + 元数据一起存
向量索引 dense_vector 字段 + HNSW
向量检索 _knn_search 原生接口
关键词检索 BM25,ES 的看家本领
混合检索 KNN + BM25 rescore,一套查询搞定
权限过滤 bool + filter,按用户/组织隔离数据

🏆 业界最佳实践 :目前大量企业级 RAG 方案(包括阿里云的企业知识库、字节内部的智能客服等)都采用了 "ES 作为统一存储 + 混合检索" 的架构,而不是"Milvus 做向量 + ES 做文本"的双系统方案。双系统的痛点在于数据同步------写入时要同时写两个库,更新时双删双改,运维成本直接翻倍。


第四章|ES 混合检索:BM25 + KNN,这才是正确打开方式

本章是全文的核心------详细讲解为什么纯向量检索不够好,以及如何用 ES 的 rescore 机制实现"KNN 粗召回 + BM25 精排"的混合检索策略。

纯向量检索的"翻车现场"

纯 KNN 向量检索有一个典型问题:语义相近但答案错位

假设你的知识库里有这么一段话

竖屏模式采用上下分栏布局(题干-答案-草稿纵向排列),横屏模式采用左右分栏布局(题干+答案 | 草稿左右排列)。经典模式布局比例:题干50% + 答案30% + 草稿20%。

用户问:"横屏模式的布局比例是多少?"

纯 KNN 检索的结果可能是这样的:

排名 召回内容 问题
1 "竖屏模式采用上下分栏布局,横屏模式采用左右分栏布局" 说了布局方向,但没说比例
2 "布局设置支持横屏和竖屏分别保存配置" 泛泛而谈
3 "经典模式布局比例:题干50% + 答案30% + 草稿20%" ✅ 这才是正确答案

看到问题了吗?KNN 返回的内容在语义上 确实都和"横屏布局"相关,但用户要的是具体比例数字,而 KNN 对数字这类"硬信息"的区分能力不足。

BM25:关键词检索的经典算法

BM25(Best Matching 25) 是信息检索领域的经典算法,ES 默认的文本相关性打分就是它。
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> B M 25 ( D , Q ) = ∑ i = 1 n I D F ( q i ) ⋅ f ( q i , D ) ⋅ ( k 1 + 1 ) f ( q i , D ) + k 1 ⋅ ( 1 − b + b ⋅ ∣ D ∣ a v g d l ) BM25(D, Q) = \sum_{i=1}^{n} IDF(q_i) \cdot \frac{f(q_i, D) \cdot (k_1 + 1)}{f(q_i, D) + k_1 \cdot \left(1 - b + b \cdot \frac{|D|}{avgdl}\right)} </math>BM25(D,Q)=i=1∑nIDF(qi)⋅f(qi,D)+k1⋅(1−b+b⋅avgdl∣D∣)f(qi,D)⋅(k1+1)

参数解释(不重要):

  • <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D 为目标文档, <math xmlns="http://www.w3.org/1998/Math/MathML"> Q = { q 1 , q 2 , ... , q n } Q = \{q_1, q_2, \ldots, q_n\} </math>Q={q1,q2,...,qn} 为查询词集合
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> f ( q i , D ) f(q_i, D) </math>f(qi,D) 为词 <math xmlns="http://www.w3.org/1998/Math/MathML"> q i q_i </math>qi 在文档 <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D 中的词频(TF)
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> ∣ D ∣ |D| </math>∣D∣ 为文档 <math xmlns="http://www.w3.org/1998/Math/MathML"> D D </math>D 的长度(词数), <math xmlns="http://www.w3.org/1998/Math/MathML"> a v g d l avgdl </math>avgdl 为语料库平均文档长度
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> k 1 k_1 </math>k1(默认 1.2)和 <math xmlns="http://www.w3.org/1998/Math/MathML"> b b </math>b(默认 0.75)为调参常数
  • <math xmlns="http://www.w3.org/1998/Math/MathML"> I D F ( q i ) = log ⁡ N − n ( q i ) + 0.5 n ( q i ) + 0.5 IDF(q_i) = \log\frac{N - n(q_i) + 0.5}{n(q_i) + 0.5} </math>IDF(qi)=logn(qi)+0.5N−n(qi)+0.5, <math xmlns="http://www.w3.org/1998/Math/MathML"> N N </math>N 为总文档数, <math xmlns="http://www.w3.org/1998/Math/MathML"> n ( q i ) n(q_i) </math>n(qi) 为包含 <math xmlns="http://www.w3.org/1998/Math/MathML"> q i q_i </math>qi 的文档数

BM25 它的核心思想有三点:

  1. 词频(TF):一个词在文档中出现越多,这个词对该文档越重要------但有上限(饱和曲线)
  2. 逆文档频率(IDF):一个词在所有文档中出现越普遍(如"的"、"是"),它的区分度越低,权重越低
  3. 文档长度归一化:长文档天然占优势,需要适当降低权重

BM25 vs TF-IDF 的关键差异 :早期常用的 TF-IDF 中词频是线性增长 的------一个词出现 100 次的文档得分是出现 10 次的 10 倍,这显然不合理。 BM25 引入了 词频饱和 机制,公式是 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( k 1 + 1 ) ⋅ t f k 1 + t f \frac{(k_1+1) \cdot tf}{k_1 + tf} </math>k1+tf(k1+1)⋅tf,让词频贡献有一个天花板,更符合直觉。

ES 中调用 BM25 时有两个关键参数,其实就是 BM25 公式中的参数:

参数 默认值 作用
k1 1.2 控制词频饱和速度。值越大,词频对分数的影响越大
b 0.75 控制长度归一化强度。0 = 忽略长度,1 = 完全归一化

实战建议 :大多数场景下,默认值就够用了。只有在你发现短文档总是排不上、或者长文档霸榜时,才需要微调这两个参数。

混合检索:两阶段策略

核心思路是 "KNN 提供候选池,BM25 做最终判定"

flowchart LR A["用户提问"] --> B["问题 Embedding"] B --> C["KNN 粗召回
topK × 30 条"] C --> D["BM25 Rescore
在窗口内重排序"] D --> E["minScore 过滤
低于阈值丢弃"] E --> F["返回 topK 结果"] style C fill:#2E86AB,color:#fff style D fill:#E84855,color:#fff style E fill:#F9A825,color:#333

第一阶段:kNN 粗召回

用 ES 的 _knn_search 接口做向量近邻搜索,召回窗口设为 topK 的 30 倍。比如最终要返回 10 条结果,KNN 先粗召回 300 条作为候选池。

json 复制代码
{
  "knn": {
    "field": "vector",
    "query_vector": [0.023, -0.157, ...],
    "k": 300,
    "num_candidates": 300
  }
}

为什么是 30 倍? 这是很多博主推荐的值,经过我的实验30倍的效果也确实不错。20 倍召回不够充分,容易漏掉关键结果;50 倍召回质量没有明显提升,但耗时显著增加。30 倍是召回质量和性能的平衡点。

第二阶段:BM25 重排序

在 kNN 召回的候选窗口(就是上一步 kNN 匹配到出来的)内,用 BM25 重新打分。这一步使用 ES 的 rescore 机制:

json 复制代码
{
  "rescore": {
    "window_size": 300,
    "query": {
      "query_weight": 0.2,
      "rescore_query_weight": 1.0,
      "rescore_query": {
        "match": {
          "textContent": {
            "query": "分片大小是多少",
            "operator": "and"
          }
        }
      }
    }
  }
}

权重设计

  • KNN 权重 0.2:提供语义相关的候选池
  • BM25 权重 1.0:主导最终排序,让关键词匹配度高的内容排上来

为什么不反过来,让 KNN 主导? 因为在中文技术文档场景中,用户的问题往往包含专业术语和精确信息(如版本号、配置值、类名),BM25 对这类"硬词"的命中率远高于语义检索。让 BM25 做最终判定,能显著提升回答的准确性。

安全网:minScore 过滤

低于一定分数的结果,即使排在 topK 里也不应该返回。实践中设 min_score = 0.3,过滤掉语义和关键词都不匹配的噪音结果。

市面上的其他做法

混合检索不止 ES rescore 这一种实现,业界还有几种常见方案:

方案 原理 适用场景
ES Rescore KNN 召回 + BM25 重排序(本章方案) 已有 ES、中小规模数据,需要自由调整向量查找和文本查找的权重
RRF(倒数秩融合) 将 KNN 和 BM25 的排名取倒数后加权融合 两路检索结果需要公平融合
Ensemble Retriever LangChain 提供的混合检索器,支持 BM25 + VectorStore Python/LangChain 技术栈
Cross-Encoder 重排 用交叉编码器对候选结果精细打分 对精度要求极高、可接受额外延迟

对于最常见的 ES Rescore 和 RRF,虽然都加权,但在核心思想上和适用场景上还是有所区别的。

维度 RRF (倒数排名融合) Rescore (重评分)
核心原理 基于排名。忽略原始分数,只看文档在各自列表中的位置(第几名)。 基于分数。利用原始分数或新的查询分数进行加权计算。
解决痛点 分数不可通约。解决 BM25(关键词)和向量检索(语义)分数维度不同、无法直接相加的问题。 性能与精度的平衡。避免对全量数据进行昂贵计算(如复杂脚本、短语匹配),只对头部结果精排。
加权对象 加权的是"检索器"(例如:关键词检索权重 0.8,向量检索权重 0.2)。 加权的是"分数"(例如:原始分 0.7 + 重排分 1.2)。
计算范围 通常在协调节点进行,融合多个检索结果集(Window Size 内)。 在每个分片上,对Top-N (Window Size) 文档进行二次计算。
典型场景 混合检索。必须同时使用关键词和向量搜索时。 精细化排序。比如先搜"手机",再对前 50 个结果计算"销量*价格"或进行"短语匹配"。

目前最主流的做法 :在企业级 Java/Spring 技术栈中,ES Rescore 方案 是采用最广泛的,因为它在同一个引擎内完成所有操作,无需额外的数据同步和系统拼接。


第五章|总结------什么时候该用 ES,什么时候该上向量数据库?

本章帮你做最终的技术选型决策,给出清晰的判断标准和行动建议。

决策树

flowchart TD A["你需要做 RAG / 语义检索?"] --> B{"已有 ES 在用?"} B -->|是| C{"数据量 < 千万级?"} B -->|否| D{"愿意引入新组件吗?"} C -->|是| E["✅ 用 ES 就够了
KNN + BM25 混合检索"] C -->|否| F{"向量检索是核心业务吗?"} F -->|否| E F -->|是| G["🔶 考虑 ES + Milvus
ES 做文本,Milvus 做向量"] D -->|否| H["先用 ES 起步
Spring AI 抽象层让切换成本极低"] D -->|是| I{"数据量级?"} I -->|百万级以下| J["Qdrant(轻量好上手)"] I -->|亿级以上| K["Milvus(分布式强扩展)"] style E fill:#27AE60,color:#fff style G fill:#F39C12,color:#fff style H fill:#27AE60,color:#fff style J fill:#2E86AB,color:#fff style K fill:#2E86AB,color:#fff

总结

  1. 大多数 Spring 项目做 RAG,ES 8.x 完全够用------一套引擎同时搞定文本检索和向量检索,运维成本最低,Spring AI 原生支持。

  2. RAG 效果好不好,七成在数据,两成在召回,一成在模型------别上来就死磕模型和 prompt,先把分片策略和混合检索做好,效果提升立竿见影。

  3. 先用 ES 起步,未来切 Milvus 只需换配置------Spring AI 的 VectorStore 抽象层让你今天的选型不会被明天"锁死",拥抱变化,低成本试错。


第六章|Spring AI + ES:从配置到跑通

这章就是实战了,只学理论的可以收手了。本章带你用 Spring AI 框架,以 ES 作为 VectorStore,搭建一个最小可用的 RAG 应用。

环境准备

组件 最低版本 推荐
Spring Boot 3.3.x 3.5.x
Elasticsearch 8.13.x 8.14.x+
JDK 17 21

Docker 启动 ES

bash 复制代码
docker run -d \
  --name elasticsearch \
  -p 9200:9200 -p 9300:9300 \
  -e "discovery.type=single-node" \
  -e "xpack.security.enabled=false" \
  -e "xpack.security.http.ssl.enabled=false" \
  elasticsearch:8.14.3

访问 http://localhost:9200,看到 JSON 返回即启动成功。

本地部署 Embedding 模型(Ollama)

bash 复制代码
# 拉取 Embedding 模型
ollama pull mxbai-embed-large:latest

# 如果还需要对话模型
ollama pull qwen2.5:7b

项目搭建

1. 引入依赖

xml 复制代码
<dependencies>
    <!-- Spring AI 核心 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-openai-spring-boot-starter</artifactId>
    </dependency>

    <!-- Spring AI Elasticsearch 向量存储 -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-elasticsearch-store-spring-boot-starter</artifactId>
    </dependency>

    <!-- Ollama(如用本地模型) -->
    <dependency>
        <groupId>org.springframework.ai</groupId>
        <artifactId>spring-ai-ollama-spring-boot-starter</artifactId>
    </dependency>

    <!-- Spring Data Elasticsearch -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
    </dependency>
</dependencies>

2. 配置文件

yaml 复制代码
spring:
  ai:
    ollama:
      base-url: http://localhost:11434
      chat:
        model: qwen2.5:7b
      embedding:
        model: mxbai-embed-large
  elasticsearch:
    uris: http://localhost:9200

3. 配置 VectorStore

java 复制代码
@Configuration
public class EsRagConfig {

    @Bean
    public VectorStore vectorStore(RestClient restClient, EmbeddingModel embeddingModel) {
        ElasticsearchVectorStoreOptions options = new ElasticsearchVectorStoreOptions();
        options.setIndexName("knowledge_base");           // 索引名
        options.setSimilarity(SimilarityFunction.cosine);  // 余弦相似度
        options.setDimensions(1024);                       // 向量维度

        return ElasticsearchVectorStore.builder(restClient, embeddingModel)
                .options(options)
                .initializeSchema(true)                    // 自动创建索引
                .batchingStrategy(new TokenCountBatchingStrategy())
                .build();
    }
}

initializeSchema(true)` 会在项目启动时自动创建 ES 索引和 mapping,开发阶段非常方便。生产环境建议关闭,改为手动管理索引。

4. 最小 RAG Controller

java 复制代码
@RestController
@RequestMapping("/rag")
public class RagController implements CommandLineRunner {

    @Resource
    private ChatClient chatClient;
    @Resource
    private VectorStore vectorStore;

    // 启动时把文档写入向量存储
    @Override
    public void run(String... args) {
        List<Document> documents = List.of(
            new Document("Spring Boot 的自动装配通过 @EnableAutoConfiguration 注解触发,"
                + "会扫描 classpath 下的 META-INF/spring.factories 文件来加载配置类。"),
            new Document("Spring AI 是 Spring 官方推出的 AI 开发框架,"
                + "支持多种大模型和向量数据库的集成,提供统一的抽象接口。"),
            new Document("Elasticsearch 8.x 原生支持向量检索,"
                + "底层使用 HNSW 算法,可以在同一索引中同时进行文本检索和向量检索。")
        );
        vectorStore.add(documents);
        System.out.println("文档写入 ES 成功!");
    }

    // RAG 问答接口
    @GetMapping("/ask")
    public String ask(@RequestParam String question) {
        return chatClient.prompt()
                .user(question)
                .advisors(new QuestionAnswerAdvisor(vectorStore))
                .call()
                .content();
    }
}

5. 测试

bash 复制代码
curl "http://localhost:8888/rag/ask?question=Spring+Boot的自动装配原理是什么"

模型会先从 ES 中检索出最相关的文档片段,然后基于这些片段生成回答。

Spring AI 的 VectorStore 抽象

Spring AI 最大的价值在于 VectorStore 接口的统一抽象。你只需要换一个实现类,就能从 ES 切换到 Milvus、Qdrant、Redis 等任何向量存储:

flowchart TB A["你的业务代码"] --> B["VectorStore 接口
add() / similaritySearch()"] B --> C["ElasticsearchVectorStore"] B --> D["MilvusVectorStore"] B --> E["QdrantVectorStore"] B --> F["RedisVectorStore"] style C fill:#2E86AB,color:#fff style B fill:#F9A825,color:#333

这意味着 :假设今天你用 ES 跑通了,明天业务增长需要切 Milvus,业务代码一行不用改,只需要换依赖和配置。这就是 Spring 体系"约定优于配置"的威力。

相关推荐
财经资讯数据_灵砚智能2 小时前
基于全球经济类多源新闻的NLP情感分析与数据可视化(日间)2026年4月22日
人工智能·python·信息可视化·自然语言处理·ai编程
耶夫斯计2 小时前
Context Engineering:构建高可靠性 AI Agent 的底层逻辑
人工智能·python
C灿灿数模2 小时前
2026五一数学建模C题思路模型,解析2025五一数学建模C题
人工智能·机器学习·逻辑回归
輕華2 小时前
LSTM实战(下篇):微博情感分析——训练策略、早停机制与推理部署
人工智能·rnn·lstm
黑金IT2 小时前
AI自媒体自动化与Web Coding深度实战
人工智能·自动化·媒体
MaoziShan2 小时前
CMU Subword Modeling | 22 Phonological Similarity and Cognate Detection
人工智能·语言模型·自然语言处理·分类
智星云算力2 小时前
算力民主化的 “临界点”:RTX 5090 专属算力平台专项测评与租用实战分析
大数据·人工智能·gpu算力·智星云·gpu租用
掘金安东尼2 小时前
Cloudflare :Agent Readiness 评分来了!你的网站,AI 代理能"看懂"吗?
人工智能
我是发哥哈2 小时前
主流AI培训机构能力横向评测:核心维度与选型要点解析
大数据·人工智能·学习·机器学习·ai·chatgpt·aigc