告别大模型“幻觉“!Spring AI + RAG 实战,打造导游知识库智能问答系统
告别大模型"幻觉"!Spring AI + RAG 实战,打造导游知识库智能问答系统
文章目录
前言
在前面的文章中,我们分别学习了 Spring AI 的基本使用、向量数据库 Milvus 的安装与操作,以及 Spring AI 中 RAG 相关 Advisor 的原理。本文将通过一个完整的实战案例——导游知识库智能问答系统,把前面学到的知识串联起来,演示如何使用 Spring AI 从零到一搭建一个 RAG 应用。
什么是 RAG?
RAG(Retrieval-Augmented Generation,检索增强生成) 是目前大模型应用中最主流的技术方案之一。它的核心思想是:
先检索,再生成 —— 在用户提问时,先从知识库中检索出与问题相关的文档片段,再将这些片段作为上下文注入到大模型的 Prompt 中,让大模型基于这些"参考资料"来生成回答。
RAG 解决了大模型的两个核心痛点:
- 知识时效性:大模型的训练数据有截止日期,无法回答最新信息;而 RAG 的知识库可以随时更新。
- 领域专业性:大模型对特定领域(如企业内部文档、专业考试题库)知之甚少;RAG 通过将领域知识注入上下文,让大模型"临时学习"后作答。
本案例要做什么?
本案例模拟了一个导游考试智能问答系统:
- 将一份"导游面试问答.doc"文档作为知识库来源
- 将文档内容切分后,通过 Embedding 模型转成向量,存入 Milvus 向量数据库
- 用户提问时,系统从 Milvus 中检索出最相关的文档片段
- 将检索到的片段作为上下文,交给 DeepSeek 大模型生成最终回答
整个流程如下图所示:
两种 Advisor 对比
本案例同时使用了 Spring AI 提供的两种 RAG Advisor,并在后续测试中对比它们的效果:
| 对比项 | QuestionAnswerAdvisor | RetrievalAugmentationAdvisor |
|---|---|---|
| 定位 | 简单直接的 RAG 实现 | 模块化、可扩展的 RAG 实现 |
| 工作流程 | 检索文档 → 直接拼接到 Prompt | 检索文档 → 查询增强 → 上下文注入 → 生成回答 |
| 查询增强 | 不支持 | 支持(通过 QueryAugmenter) |
| 可扩展性 | 较低,开箱即用 | 高,各模块可自定义替换 |
| 适用场景 | 快速原型、简单问答 | 生产级应用、复杂 RAG 流程 |
项目说明
| 项目 | 说明 |
|---|---|
| 项目名称 | SpringAIRAGDemo |
| 项目简介 | 基于 Spring AI 实现的导游知识库智能问答系统,演示 RAG(检索增强生成)从零到一的综合开发流程 |
| 技术栈 | Spring Boot 3.5.3 + Spring AI 1.0.0-SNAPSHOT + Milvus 向量数据库 + DeepSeek(对话模型)+ 智谱AI(Embedding 模型) |
| JDK 版本 | 17 |
| 核心功能 | 1. 从本地 DOC 文档构建知识库,将内容切片后存入 Milvus 向量数据库 2. 使用 QuestionAnswerAdvisor 实现基于向量检索的问答3. 使用 RetrievalAugmentationAdvisor 实现增强检索问答4. 两种 Advisor 对比测试 |
| 核心依赖 | spring-ai-starter-model-deepseek(DeepSeek 对话模型)spring-ai-starter-model-zhipuai(智谱AI Embedding 模型)spring-ai-starter-vector-store-milvus(Milvus 向量存储)spring-ai-advisors-vector-store(QuestionAnswerAdvisor)spring-ai-rag(RetrievalAugmentationAdvisor)spring-ai-tika-document-reader(DOC 文档解析) |
| 外部服务 | Milvus 向量数据库(Docker 部署,默认端口 19530) |
| 项目结构 | config/AIConfig.java — 配置 Advisor、ChatClient,启动时初始化向量数据controller/RagController.java — 提供 /ai/chat1 和 /ai/chat2 两个问答接口 |
| API 接口 | GET /ai/chat1?message=xxx — 使用 QuestionAnswerAdvisor 问答GET /ai/chat2?message=xxx — 使用 RetrievalAugmentationAdvisor 问答 |
项目目录结构
SpringAIRAGDemo/
├── src/main/java/com/example/springairagdemo/
│ ├── config/
│ │ └── AIConfig.java # 核心配置类:Advisor、ChatClient、向量数据初始化
│ ├── controller/
│ │ └── RagController.java # REST 控制器:提供两个问答接口
│ └── SpringAiragDemoApplication.java # Spring Boot 启动类
├── src/main/resources/
│ ├── application.properties # 配置文件:API Key、Milvus 连接等
│ └── 导游面试问答.doc # 知识库源文档
└── pom.xml # Maven 依赖配置
实战步骤
1. 创建SpringBoot项目
SpringBoot项目命名为SpringAIRAGDemo,设置使用的JDK为17版本。
2. 在项目中加入如下Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>SpringAIRAGDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>SpringAIRAGDemo</name>
<description>SpringAIRAGDemo</description>
<properties>
<java.version>17</java.version>
</properties>
<!-- 导入 Spring AI BOM,用于统一管理 Spring AI 依赖的版本,
引用每个 Spring AI 模块时不用再写 <version>,只要依赖什么模块 Mavens 自动使用 BOM 推荐的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-bom</artifactId>
<version>1.0.0-SNAPSHOT</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Deepseek AI 依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-deepseek</artifactId>
</dependency>
<!-- 智普AI 依赖 -->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-model-zhipuai</artifactId>
</dependency>
<!-- QuestionAnswerAdvisor 依赖包-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
<!-- RetrievalAugmentationAdvisor 依赖包-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-rag</artifactId>
</dependency>
<!-- Milvus VectorStore 依赖包-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-starter-vector-store-milvus</artifactId>
</dependency>
<!-- Tika DocumentReader 依赖包-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
</dependencies>
<!-- 声明仓库, 用于获取 Spring AI 以及相关预发布版本-->
<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
<repository>
<name>Central Portal Snapshots</name>
<id>central-portal-snapshots</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
依赖说明
下面对各个核心依赖的作用做一个梳理:
| 依赖 | 作用 | 在本案例中的角色 |
|---|---|---|
spring-ai-bom (1.0.0-SNAPSHOT) | Spring AI 统一版本管理 | 通过 BOM 管理所有 Spring AI 模块版本,避免版本冲突 |
spring-ai-starter-model-zhipuai | 智谱AI SDK 集成 | 用作 Embedding 模型,将文档片段转换成向量 |
spring-ai-starter-model-deepseek | DeepSeek SDK 集成 | 用作 Chat 模型,负责最终的对话生成 |
spring-ai-advisors-vector-store | 基于向量存储的 Advisor | 提供 QuestionAnswerAdvisor,实现简单的 RAG 问答 |
spring-ai-rag | RAG 框架 | 提供 RetrievalAugmentationAdvisor,实现增强检索问答 |
spring-ai-starter-vector-store-milvus | Milvus 向量存储集成 | 连接 Milvus 向量数据库,存储和检索文档向量 |
spring-ai-tika-document-reader | Apache Tika 文档解析 | 读取和解析 .doc 格式的 Word 文档 |
为什么需要两个 AI 模型?
在 RAG 流程中,Embedding 模型和Chat 模型各司其职:
- 智谱AI(Embedding 模型):负责将文本转换成高维向量(本案例中为 1024 维),用于语义相似度计算和检索
- DeepSeek(Chat 模型):负责理解用户问题并结合检索到的上下文,生成自然语言回答
3. 配置resources/application.properties
spring.application.name=SpringAIRAGDemo
server.port=8080
## 使用 智普AI Embedding 模型,需要在pom.xml中引入对应依赖
spring.ai.zhipuai.api-key=d...4
spring.ai.zhipuai.base-url=https://open.bigmodel.cn/api/paas
spring.ai.zhipuai.embedding.options.model=embedding-2
## 配置 Chat Model:Deepseek的基础URL、密钥和使用模型
spring.ai.deepseek.base-url=https://api.deepseek.com
spring.ai.deepseek.api-key=sk-8...1
spring.ai.deepseek.chat.options.model=deepseek-chat
## Milvus 配置
spring.ai.vectorstore.milvus.client.host=node2
spring.ai.vectorstore.milvus.client.port=19530
# 默认用户名和密码为 root 和 Milvus
spring.ai.vectorstore.milvus.client.token=root:Milvus
# 要使用的 Milvus 数据库的名称
spring.ai.vectorstore.milvus.database-name=default
# 用于存储 vector 的 Milvus 集合名称
spring.ai.vectorstore.milvus.collection-name=guide_qa_store
#是否自动初始化 schema,如:vector_store如果没有会自动创建
spring.ai.vectorstore.milvus.initialize-schema=true
# Milvus 集合中要存储的 vector 的维度,默认为1536,要与所使用的 embedding 模型的维度匹配(如:DeepSeek、ZhiPuAI 或其他,都是1024维)
spring.ai.vectorstore.milvus.embedding-dimension=1024
配置说明
配置文件中涉及三组关键配置:
① 智谱AI Embedding 配置
spring.ai.zhipuai.api-key:智谱AI 开放平台的 API Keyspring.ai.zhipuai.embedding.options.model=embedding-2:使用 embedding-2 模型,输出 1024 维向量
② DeepSeek Chat 配置
spring.ai.deepseek.api-key:DeepSeek 平台的 API Keyspring.ai.deepseek.chat.options.model=deepseek-chat:使用 deepseek-chat 模型进行对话
③ Milvus 向量数据库配置
host/port:Milvus 服务地址和端口collection-name=guide_qa_store:存储导游知识库向量的集合名称initialize-schema=true:自动创建集合和 Schema,无需手动建表embedding-dimension=1024:向量维度必须与 Embedding 模型匹配(智谱AI embedding-2 输出 1024 维)
注意:
embedding-dimension必须与 Embedding 模型的输出维度一致,否则写入 Milvus 时会报错。
4. 准备知识库文档
将"导游面试问答.doc"文档放到项目resources资源目录下。
这份文档包含了导游考试中常见的面试问答内容,涵盖了景点知识、导游规范、应急处理等方面的问答对。这些内容将作为 RAG 系统的知识库来源。
5. 构建两个Rag Advisor和Chat Client
在项目中创建config/AIConfig.java ,该类标记为Configuration类。类中注入QuestionAnswerAdvisor、RetrievalAugmentationAdvisor以及ChatClient,并且每次运行SpringBoot项目时读取项目resources资源目录下的"导游面试问答.doc"内容,切分文档内容,并向Milvus中插入分片文档向量数据(如果已存在则不重复插入)。
package com.example.springairagdemo.config;
import io.milvus.client.MilvusServiceClient;
import io.milvus.grpc.GetCollectionStatisticsResponse;
import io.milvus.param.R;
import io.milvus.param.collection.FlushParam;
import io.milvus.param.collection.GetCollectionStatisticsParam;
import io.milvus.response.GetCollStatResponseWrapper;
import jakarta.annotation.PostConstruct;
import org.apache.tika.Tika;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.deepseek.DeepSeekChatModel;
import org.springframework.ai.document.Document;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.ai.rag.generation.augmentation.ContextualQueryAugmenter;
import org.springframework.ai.rag.retrieval.search.VectorStoreDocumentRetriever;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.ai.transformer.splitter.TokenTextSplitter;
import org.springframework.ai.vectorstore.SearchRequest;
import org.springframework.ai.vectorstore.milvus.MilvusVectorStore;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class AIConfig {
// 注入 MilvusVectorStore 实例
@Autowired
private MilvusVectorStore vectorStore;
// 从配置文件中读取 Milvus 集合名称
@Value("${spring.ai.vectorstore.milvus.collection-name}")
private String collectionName;
//配置 QuestionAnswerAdvisor,用于问答任务
@Bean
public QuestionAnswerAdvisor questionAnswerAdvisor() {
return QuestionAnswerAdvisor.builder(vectorStore)
// 配置向量搜索的参数,这里设置了相似度阈值为 0.5,返回前 6 个结果
.searchRequest(SearchRequest.builder().similarityThreshold(0.1d).topK(6).build())
.build();
}
//配置 RetrievalAugmentationAdvisor,用于增强检索任务
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor() {
//VectorStoreDocumentRetriever 负责在向量数据库中根据查询获取最相关的文档
//支持根据相似度阈值、top-K(返回前K个最相关文档)和过滤表达式等方式进行精确检索
VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore) // 设置向量存储对象
.similarityThreshold(0.1)//设置相似度阈值
.topK(6)//设置返回前K个最相关文档
.build();
//ContextualQueryAugmenter 用于增强查询,根据上下文信息生成更具体的查询
//支持基于上下文的查询增强,例如根据用户输入的上下文信息生成更具体的查询,提高检索的准确性和效率
ContextualQueryAugmenter cqa = ContextualQueryAugmenter.builder()
.allowEmptyContext(true)//允许空上下文
.build();
//返回 RetrievalAugmentationAdvisor 对象,用于增强检索任务
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(retriever) // 设置文档检索器
.queryAugmenter(cqa) // 设置查询增强器
.build();
}
//配置 ChatClient,初始化聊天客户端
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel,QuestionAnswerAdvisor qaAdvisor) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个助手,回答用户问题是不要提及回复是从上下文信息中获取的," +
"不要回答你不知道的问题,如果你不知道答案,就回答:抱歉,我不清楚这个问题。")
.build();
}
/**
* @PostConstruct 注解的方法会在 Spring 容器完成依赖注入后自动调用一次,常用于执行初始化逻辑。
*/
@PostConstruct
public void initVectorData() {
System.out.println("初始化向量数据,写入到 Milvus 数据库中...");
// 获取 Milvus 客户端实例
MilvusServiceClient client = (MilvusServiceClient)vectorStore.getNativeClient().get();
// 获取集合统计信息,查询集合的记录数量
R<GetCollectionStatisticsResponse> resp = client.getCollectionStatistics(GetCollectionStatisticsParam.newBuilder()
.withCollectionName(collectionName)
.build());
// 获取实体数量,即数据行数
long rowCount = new GetCollStatResponseWrapper(resp.getData()).getRowCount();
System.out.println("Milvus vector_store 中数据数量:" + rowCount);
//如果集合没有数据就写入
if(rowCount==0){
System.out.println("Milvus vector_store 中没有数据,开始写入...");
loadAndStoreDocumentData();
}
// 获取 Milvus 客户端并手动调用 flush,刷新索引,确保数据被持久化到 Milvus 数据库中
System.out.println("Milvus vector_store 刷新索引...");
client.flush(FlushParam.newBuilder()
.withCollectionNames(List.of(collectionName)) // 设置刷新操作的集合名称
.build());
}
/**
* 读取 Word 文件内容并存入 Milvus 向量数据库
*/
private void loadAndStoreDocumentData() {
List<Document> documents = new ArrayList<>();
try {
// 从 resources 目录加载文档
var resource = new ClassPathResource("导游面试问答.doc");
Tika tika = new Tika();
String text = tika.parseToString(resource.getFile());
// 使用 TokenTextSplitter 拆分文本
TokenTextSplitter splitter = TokenTextSplitter.builder()
.withChunkSize(800) // 每段最多 800 个 token
.withMinChunkSizeChars(400) // 每段最小字符数 400
.withKeepSeparator(true) // 保留分隔符
.build();
// 拆分文本
List<Document> rawDocs = List.of(new Document(text));
List<Document> chunks = splitter.apply(rawDocs);
// 遍历每个 chunk,生成 embedding 并存储
for (Document chunk : chunks) {
String chunkText = chunk.getText().strip();
//chunk:chunk: Document{id='10d79633-7d1c-4736-9423-3877d05f6cd6', text='内容。。。', media='null', metadata={}, score=null}
System.out.println("chunk: " + chunk);
System.out.println("chunkText: " + chunkText);
if (!chunkText.isBlank()) {
// 将文档内容存入 Milvus 向量数据库
documents.add(chunk);
}
}
} catch (Exception e) {
e.printStackTrace();
}
// 将文档内容存入 Milvus 向量数据库,自动写入向量数据表 guide_qa_vectors 中
vectorStore.add(documents);
}
}
代码详解
这个配置类是整个项目的核心,包含以下几个关键部分:
① QuestionAnswerAdvisor 配置
@Bean
public QuestionAnswerAdvisor questionAnswerAdvisor() {
return QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(SearchRequest.builder()
.similarityThreshold(0.1d) // 相似度阈值
.topK(6) // 返回前6个最相关结果
.build())
.build();
}
QuestionAnswerAdvisor 是 Spring AI 提供的"开箱即用"型 RAG Advisor。它的工作流程非常简单:
- 接收用户问题
- 从 VectorStore 中检索相似度 ≥ 0.1 的前 6 个文档片段
- 将检索到的片段拼接成上下文,附加到用户问题的 Prompt 中
- 交给 Chat 模型生成回答
参数说明:
similarityThreshold(0.1):相似度阈值,取值 0~1,值越低召回越多但可能引入噪音,值越高越精确但可能遗漏相关内容topK(6):返回最相关的前 K 个文档片段
② RetrievalAugmentationAdvisor 配置
@Bean
public RetrievalAugmentationAdvisor retrievalAugmentationAdvisor() {
// 文档检索器
VectorStoreDocumentRetriever retriever = VectorStoreDocumentRetriever.builder()
.vectorStore(vectorStore)
.similarityThreshold(0.1)
.topK(6)
.build();
// 查询增强器
ContextualQueryAugmenter cqa = ContextualQueryAugmenter.builder()
.allowEmptyContext(true)
.build();
return RetrievalAugmentationAdvisor.builder()
.documentRetriever(retriever)
.queryAugmenter(cqa)
.build();
}
RetrievalAugmentationAdvisor 是 Spring AI 提供的模块化 RAG Advisor,相比 QuestionAnswerAdvisor 更加灵活:
VectorStoreDocumentRetriever:文档检索器,负责从向量数据库中检索与查询最相关的文档片段。它支持相似度阈值、Top-K、过滤表达式等检索策略。ContextualQueryAugmenter:查询增强器,负责将检索到的文档片段与用户问题进行智能组合,生成增强后的 Prompt。allowEmptyContext(true)表示当检索不到相关文档时,仍然允许大模型直接回答(而不是报错)。
③ ChatClient 配置
@Bean
public ChatClient chatClient(DeepSeekChatModel chatModel, QuestionAnswerAdvisor qaAdvisor) {
return ChatClient.builder(chatModel)
.defaultSystem("你是一个助手,回答用户问题是不要提及回复是从上下文信息中获取的," +
"不要回答你不知道的问题,如果你不知道答案,就回答:抱歉,我不清楚这个问题。")
.build();
}
ChatClient 使用 DeepSeek 作为底层 Chat 模型,并设置了系统提示词(System Prompt),关键约束:
- 不要暴露回答是基于上下文信息的(让回答更自然)
- 不知道的问题要如实回答"不知道"(避免大模型"幻觉")
④ 向量数据初始化(@PostConstruct)
@PostConstruct
public void initVectorData() {
// 1. 获取 Milvus 客户端
// 2. 查询集合中已有的数据量
// 3. 如果数据量为 0,则读取文档并写入
// 4. 手动 flush 确保数据持久化
}
这段初始化逻辑在 Spring 容器启动时自动执行一次,核心流程:
- 检查数据量:通过 Milvus 原生客户端查询
guide_qa_store集合中的记录数 - 按需写入:只有当集合为空时才执行文档读取和写入,避免重复导入
- 手动刷新:调用
flush确保数据被持久化到磁盘,使后续查询能立即命中新数据
⑤ 文档读取与切分(loadAndStoreDocumentData)
private void loadAndStoreDocumentData() {
// 1. 使用 Apache Tika 读取 .doc 文件为纯文本
// 2. 使用 TokenTextSplitter 将文本切分为多个 chunk
// 3. 过滤空白 chunk
// 4. 批量写入 Milvus(自动调用 Embedding 模型生成向量)
}
文档处理流程详解:
| 步骤 | 工具 | 说明 |
|---|---|---|
| 文档读取 | Apache Tika | 将 .doc 格式的 Word 文档解析为纯文本 |
| 文本切分 | TokenTextSplitter | 按 Token 数量切分,chunkSize=800 表示每段最多 800 个 Token |
| 最小长度 | minChunkSizeChars=400 | 每个切分片段最少 400 个字符,避免过短的无意义片段 |
| 保留分隔符 | keepSeparator=true | 保留原文中的分隔符,维持文本结构 |
| 向量生成 | MilvusVectorStore.add() | 调用 add() 时,Spring AI 自动使用智谱AI Embedding 模型将文本转成向量并存入 Milvus |
关于 TokenTextSplitter 的参数调优:
chunkSize不宜过大,否则单个片段包含太多主题,检索精度下降chunkSize不宜过小,否则语义不完整,大模型难以理解上下文- 一般建议 500~1000 Token 之间,根据实际文档类型和效果进行调整
6. 创建RagController.java
package com.example.springairagdemo.controller;
import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.vectorstore.QuestionAnswerAdvisor;
import org.springframework.ai.rag.advisor.RetrievalAugmentationAdvisor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("/ai")
public class RagController {
@Autowired
private ChatClient chatClient;
@Autowired
private QuestionAnswerAdvisor qaAdvisor;
@Autowired
private RetrievalAugmentationAdvisor ragAdvisor;
@GetMapping("/chat1")
public String ask1(@RequestParam String message) {
return chatClient.prompt()
.user(message)
//设置使用 QuestionAnswerAdvisor
.advisors(List.of(qaAdvisor))
.call()
.content();
}
@GetMapping("/chat2")
public String ask2(@RequestParam String message) {
return chatClient.prompt()
.user(message)
//设置使用 RetrievalAugmentationAdvisor
.advisors(List.of(ragAdvisor))
.call()
.content();
}
}
接口说明
控制器提供了两个 GET 接口,使用同一个 ChatClient,但分别搭配不同的 Advisor:
| 接口 | Advisor | 说明 |
|---|---|---|
GET /ai/chat1?message=xxx | QuestionAnswerAdvisor | 使用简单的 RAG 问答流程 |
GET /ai/chat2?message=xxx | RetrievalAugmentationAdvisor | 使用增强检索问答流程 |
两个接口的调用方式完全一致,区别仅在于底层使用的 Advisor 不同,这样方便我们对比两种 Advisor 在实际问答中的效果差异。
.advisors()的作用:ChatClient 的.advisors()方法用于指定本次请求要经过哪些 Advisor 处理。Advisor 会在用户请求发送给大模型之前,对 Prompt 进行增强处理(如注入检索到的上下文信息)。
7. 启动Milvus
在linux node2 节点启动Milvus:
#进入 milvus目录
cd /software/milvus
#启动 Milvus
docker compose up -d
确认 Milvus 启动成功后,可以通过以下命令查看容器状态:
docker compose ps
确保 Milvus 服务在 19530 端口正常监听后,再进行下一步。
8. 启动项目并测试
启动SpringAIRAGDemo项目,运行主应用类SpringAiragDemoApplication.java,启动该项目。
启动后控制台会输出向量数据初始化日志:
初始化向量数据,写入到 Milvus 数据库中...
Milvus vector_store 中数据数量:0
Milvus vector_store 中没有数据,开始写入...
chunk: Document{id='xxx', text='导游面试问答内容...', ...}
chunkText: 导游面试问答内容...
...(更多 chunk 输出)
Milvus vector_store 刷新索引...
首次启动时,系统会自动读取文档、切分、生成向量并写入 Milvus。再次启动时,由于集合中已有数据,不会重复写入。
测试用例与结果分析
在浏览器中分别测试两个接口:
测试一:知识库内的问题(导游知识)
请求:http://localhost:8080/ai/chat1?message=何为丹崖断墙
回答:丹崖断墙是一条自燕山运动以来多次活动的断层带,总断距600米以上。它是由紫红色石英砂岩
构成的断层面,长250米,高150米。该断墙下的一系列断阶完整地体现了造山运动的过程,展示了
地质运动的伟大力量。
请求:http://localhost:8080/ai/chat2?message=何为丹崖断墙
回答:丹崖断墙是一条自燕山运动以来多次活动的断层带,总断距600米以上。丹崖断墙是该断层最新
一次活动形成的断层面,由紫红色石英砂岩构成,长250米,高150米。断墙下的一系列断阶,完整地
体现了造山运动的过程,又展示了造山运动的伟大。
分析:两个接口都正确回答了知识库中的问题。对比来看,
chat2(RetrievalAugmentationAdvisor)的回答在表述上更加流畅自然,这是因为ContextualQueryAugmenter对查询进行了增强处理。
测试二:知识库外的问题
请求:http://localhost:8080/ai/chat1?message=Spring AI是什么?
回答:抱歉,我不清楚这个问题。
请求:http://localhost:8080/ai/chat2?message=今天天气如何?
回答:抱歉,我不清楚这个问题。
分析:对于知识库中没有涉及的问题,两个接口都能正确地拒绝回答,而不是让大模型"胡编乱造"。这正是 RAG 的价值所在——让大模型的回答有据可依,有效减少"幻觉"。
两种 Advisor 效果对比总结
| 测试场景 | QuestionAnswerAdvisor (chat1) | RetrievalAugmentationAdvisor (chat2) |
|---|---|---|
| 知识库内问题 | 能正确回答,表述较简洁 | 能正确回答,表述更流畅自然 |
| 知识库外问题 | 正确拒绝回答 | 正确拒绝回答 |
| 回答准确性 | 高 | 高 |
| 回答流畅度 | 中等 | 较好 |
| 配置复杂度 | 低(开箱即用) | 中等(需配置 Retriever 和 Augmenter) |
总结
本文通过一个完整的导游知识库智能问答系统,演示了使用 Spring AI 搭建 RAG 应用的全过程。核心流程回顾:
文档准备 → 文档切分 → 向量化 → 存入Milvus → 用户提问 → 向量检索 → 上下文注入 → 大模型生成回答
关键要点:
- RAG 的本质是"先检索,再生成",通过注入外部知识来增强大模型的回答能力
- Embedding 模型(智谱AI)负责将文本转成向量,Chat 模型(DeepSeek)负责生成回答,两者各司其职
- Milvus 作为向量数据库,负责高效存储和检索文档向量
- QuestionAnswerAdvisor 适合快速搭建简单的 RAG 应用,RetrievalAugmentationAdvisor 更适合需要精细控制的生产级应用
- 文档切分策略(chunk 大小、最小字符数等)直接影响检索效果,需要根据实际场景调优
后续可以探索的方向:
- 尝试不同的文档切分策略(如按段落切分、递归切分等)
- 调整相似度阈值和 Top-K 参数,观察对回答质量的影响
- 接入更多格式的知识库文档(PDF、Markdown 等)
- 使用自定义的 QueryAugmenter 实现更复杂的查询增强逻辑
如果觉得有用,点个关注不迷路,后续更新第一时间收到~
更多推荐

所有评论(0)