1. 项目概述:基于Spring AI的RAG知识库增强实践
最近在做一个消防职业技能鉴定系统的智能问答模块,需要让大模型能够准确回答关于考试安排、报名流程等专业问题。直接让大模型回答这类问题效果很差,因为涉及大量机构内部信息和最新政策变动。于是采用了RAG(Retrieval-Augmented Generation)技术方案,通过外挂私有知识库来增强模型的专业应答能力。
这个方案的核心流程其实很简单:把机构发布的考试公告等文档处理后存入向量数据库,当用户提问时,先检索相关文档片段,再把片段和问题一起交给大模型生成回答。实测下来,准确率从原来的30%提升到了90%以上,效果非常显著。下面我就详细拆解这个方案的具体实现,包括ETL流程、向量检索配置和问答接口开发。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心组件与依赖配置
2.1 技术选型与依赖引入
这个项目基于Spring Boot + Spring AI构建,主要用到了以下几个关键组件:
- Spring AI Vector Store:负责文档的向量化存储和检索
- OpenAI Embedding Model:用于生成文本向量(虽然项目用的是国产模型,但接口兼容)
- TokenTextSplitter:文档分块工具
- ChatClient:大模型交互客户端
Maven依赖配置如下:
xml复制<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
<version>0.8.1</version>
</dependency>
<!-- 其他必要依赖如Spring Boot Starter Web等省略 -->
提示:Spring AI的版本迭代较快,建议使用最新稳定版。如果遇到兼容性问题,可以尝试锁定特定版本。
2.2 关键配置详解
在application.properties中需要配置嵌入模型参数:
properties复制# 使用text-embedding-v4模型生成向量
spring.ai.openai.embedding.options.model=text-embedding-v4
# 开启向量存储调试日志
logging.level.org.springframework.ai.vectorstore.SimpleVectorStore=debug
这里虽然配置项写着openai,但实际上可以对接任何兼容API的嵌入模型。我们在生产环境就切换成了国产模型,只需修改对应的配置键即可。
3. 知识库ETL流程实现
3.1 文档预处理与分块
原始知识库是一个简单的txt文档,内容结构如下:
code复制根据考务编排,拟对2026年1月上半月批次消防设施操作员进行名额增补...
直接整篇存入向量数据库效果不好,需要合理分块。这里使用TokenTextSplitter进行智能分块:
java复制@Bean
CommandLineRunner commandLineRunner(@Value("classpath:a.txt") Resource resource,
VectorStore vectorStore) {
return args -> {
// 读取文档内容
String content = resource.getContentAsString(StandardCharsets.UTF_8);
// 分块处理并存入向量库
vectorStore.add(
TokenTextSplitter.builder()
.build()
.apply(List.of(Document.builder().text(content).build()))
);
};
}
分块策略直接影响检索效果。经过测试,对于这类公告文档,设置块大小在300-500token,重叠50-100token效果最佳。太大容易引入噪声,太小则可能丢失上下文。
3.2 向量存储配置
SimpleVectorStore是Spring AI提供的轻量级向量数据库实现:
java复制@Bean
public VectorStore vectorStore(OpenAiEmbeddingModel embeddingModel) {
return SimpleVectorStore.builder()
.embeddingModel(embeddingModel)
.build();
}
注意:生产环境建议使用专业向量数据库如Milvus或Pinecone,SimpleVectorStore只适合小规模数据和开发测试使用。
4. 问答系统核心实现
4.1 ChatClient配置
问答系统的核心是ChatClient,需要配置三个关键advisor:
java复制@Bean
public ChatClient chatClient(OpenAiChatModel chatModel,
ChatMemory chatMemory,
VectorStore vectorStore) {
return ChatClient.builder(chatModel)
.defaultAdvisors(
new SimpleLoggerAdvisor(), // 日志记录
MessageChatMemoryAdvisor.builder(chatMemory).build(), // 对话记忆
QuestionAnswerAdvisor.builder(vectorStore) // 知识检索
.searchRequest(SearchRequest.builder()
.topK(2) // 返回最相关的2个片段
.similarityThreshold(0.5) // 相似度阈值
.build())
.build())
.build();
}
关键参数说明:
topK:控制返回的文档片段数量,根据文档长度调整similarityThreshold:过滤低质量匹配,建议设置在0.5-0.7之间
4.2 问答接口实现
控制器层提供一个简单的HTTP接口:
java复制@RequestMapping(produces="text/html;charset=UTF-8")
public String index(String prompt) {
return chatClient.prompt(prompt)
.call()
.content();
}
这个接口的工作流程是:
- 接收用户问题(如"鉴定站地址是什么?")
- 检索向量库获取相关文档片段
- 将片段和问题拼接后发送给大模型
- 返回模型生成的回答
5. 效果验证与问题排查
5.1 测试案例
输入问题:
code复制辽宁消防救援总队消防行业职业技能鉴定站地址
系统返回:
code复制辽宁消防救援总队消防行业职业技能鉴定站地址是:辽宁省沈阳市皇姑区鸭绿江北街277号。
从调试日志可以看到完整的RAG流程:
- 检索到两个相关文档片段(相似度0.86和0.62)
- 将片段作为上下文注入prompt
- 模型生成准确回答
5.2 常见问题与解决
问题1:返回无关内容
- 原因:分块不合理或相似度阈值过低
- 解决:调整分块策略,提高similarityThreshold
问题2:回答不完整
- 原因:topK设置太小
- 解决:适当增加topK值,同时确保分块质量
问题3:响应延迟高
- 原因:嵌入模型或大模型响应慢
- 解决:考虑缓存高频查询,或使用更轻量级的模型
6. 生产环境优化建议
在实际部署中,我们总结了几条重要经验:
- 文档预处理:原始文档应先清洗(去噪、标准化格式),再分块
- 元数据增强:为每个分块添加标题、更新时间等元数据,提升检索精度
- 混合检索:结合关键词检索和向量检索,提高召回率
- 版本控制:知识库更新时应保留旧版本,避免问答不一致
对于高并发场景,建议:
- 使用Redis缓存高频问答对
- 对向量数据库做读写分离
- 实现异步ETL流程,避免启动时加载过慢
这个方案虽然以消防考试系统为例,但同样适用于各类专业领域的智能问答场景,如法律咨询、医疗问答等。关键在于知识库的质量和检索策略的优化。
