SpringAI入门:文件功能与RAG功能
·
一、文件功能
让AI能够根据上传的文档来回答问题,下面我们来实现这个功能
1.添加文件读取pom
每种文档类型有各自的解析处理方式,本文使用的这个可以读取大多数类型的文档。
<!--Tika文档读取器-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-tika-document-reader</artifactId>
</dependency>
2.编写文档解析的工具类
package com.Luoyi.util;
import org.springframework.ai.document.Document;
import org.springframework.ai.reader.tika.TikaDocumentReader;
import org.springframework.core.io.Resource;
import org.springframework.web.multipart.MultipartFile;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Luoyi
* @description: 文件解析
*/
public class parseFileUtil {
/**
* 从上传的文件中提取文本内容
*
* @param file 上传的文件
* @return 提取的文本内容
*/
public static String extractContent(MultipartFile file) {
try {
// 将 MultipartFile 转换为 Resource
Resource resource = file.getResource();
// 使用 TikaDocumentReader 读取文档
TikaDocumentReader reader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();
// 提取所有文档的内容并合并
return documents.stream()
.map(doc -> doc.getText())
.collect(Collectors.joining("\n\n"));
} catch (Exception e) {
// 如果解析失败,返回错误信息
return "[文档解析失败: " + e.getMessage() + "]";
}
}
}
3.在MultiModelsController控制类中新增
/**
* @Description: 带文档上传的对话流式输出
* @param file 上传的文档
* @param message 消息
* @param model 指定模型
* @param conversationId 会话ID
* @return reactor.core.publisher.Flux<java.lang.String>
*/
@PostMapping(value = "/chat/upload", produces = "text/html;charset=utf-8")
public Flux<String> chatWithDocument(@RequestParam("file") MultipartFile file,
@RequestParam String message,
@RequestParam String model,
@RequestParam String conversationId) {
ChatClient chatClient = chatClientMap.get(model);
// 提取文档内容
String documentContent = parseFileUtil.extractContent(file);
// 将文档内容附加到用户消息中
String enrichedMessage = message + "\n\n【文档内容】\n" + documentContent;
Flux<String> content = chatClient.prompt()
.user(enrichedMessage)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId))
.stream()
.content();
return content;
}
4.前端增加文件上传
html增加文件上传功能,这一步就不展示了,大家可以使用AI进行前后端接口对接。
5.运行
UI界面进行了简单的修改,同时上述功能实现成功读取文件并分析正确

二、RAG功能
如果我不想每次都上传文件而是,让AI默认从我指定的文件中读取信息,这该如何实现呢?下面我们使用RAG来进行实现,通过下面的学习可以简单构建自己的知识库让AI根据知识库来进行回答。
RAG是什么?RAG是检索增强生成。哈哈哈哈大家自己查查吧,我这里放个图

1.添加pom配置
<!--向量存储-->
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-advisors-vector-store</artifactId>
</dependency>
向量解析需要使用专用的向量解析模型,我这里使用的是ollama的模型,打开ollama筛选Embedding,就都是向量模型了

拉取模型后在配置文件中进行添加
spring:
ai:
#ollama模型配置
ollama:
chat:
model: qwen3:1.7b
base-url: http://localhost:11434
embedding:
model: qwen3-embedding:4b #RAG模型
2.编写测试类
/**
* @author Luoyi
* @description: 向量模型测试
*/
@SpringBootTest
public class EmbaddingTest {
@Test
public void testEmbadding(@Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
float[] embedded = ollamaEmbeddingModel.embed("我叫德德");
System.out.println(embedded.length);
System.out.println(Arrays.toString(embedded));
}
}
运行查看效果,可以看到“我是德德”这句被向量模型向量化为数组

3.测试升级
提前准备一个文档或者pdf,我这里准备了2025年国家公务员考试.docx,通过拦截器设置读取文档并将文档向量化
/**
* @author Luoyi
* @description: 向量模型测试
*/
@SpringBootTest
public class EmbaddingTest {
@Test
public void testEmbadding(@Autowired OllamaEmbeddingModel ollamaEmbeddingModel) {
float[] embedded = ollamaEmbeddingModel.embed("我叫德德");
System.out.println(embedded.length);
System.out.println(Arrays.toString(embedded));
}
/**
* @Description: 初始化向量数据库
* @param vectorStore
* @return void
*/
@BeforeEach
public void init(@Autowired VectorStore vectorStore) throws IOException {
// 1. 读取resources中的docx文档
Resource resource = new ClassPathResource("2025年国家公务员考试.docx");
// 2. 使用TikaDocumentReader读取docx文件
TikaDocumentReader documentReader = new TikaDocumentReader(resource);
List<Document> documents = documentReader.get();
// 3. 将文档进行向量化,并且存入向量数据库(无需再手动向量化)
vectorStore.add(documents);
}
/**
* @Description: RAG 测试
* @param vectorStore
* @param chatModel
* @return void
*/
@Test
void chatRagTest(
@Autowired
VectorStore vectorStore,
@Autowired DeepSeekChatModel chatModel
) {
ChatClient chatClient = ChatClient.builder(chatModel)
.build();
String message="考前做什么准备需要?";
String content = chatClient.prompt().user(message)
.advisors(
new SimpleLoggerAdvisor(),
QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest
.builder().query(message)
.topK(5)
.similarityThreshold(0.3)
.build())
.build()
).call().content();
System.out.println(content);
}
}
运行结果和文档中内容一样


4.RAG配置类
/**
* @author Luoyi
* @description: RAG(检索增强生成)配置
*/
@Configuration
public class RagConfig {
/**
* 创建 RAG Advisor - 问答增强顾问
* @param vectorStore 向量数据库
* @return RAG Advisor 实例
*/
@Bean
public Advisor ragAdvisor(VectorStore vectorStore) {
return QuestionAnswerAdvisor.builder(vectorStore)
.searchRequest(
SearchRequest.builder()
.similarityThreshold(0.6) // 相似度阈值 60%
.topK(5) // 检索前5个最相关文档
.build())
.build();
}
}
5.在AIConfig配置中修改
添加向量存储实例
/**
* @author Luoyi
* @description: 模型配置
*/
@Configuration
public class AIConfig {
@Autowired
private DeepSeekChatModel deepSeekChatModel;
@Autowired
private OllamaApi ollamaApi;
@Autowired
private OllamaChatProperties options;
@Autowired
private ChatMemory chatMemory;
@Autowired
private UserTool userTool;
/**
* @Description: deepseek模型(支持RAG检索增强)
* @param
* @return org.springframework.ai.chat.client.ChatClient
*/
@Bean
@Primary
public ChatClient deepseek(ToolCallbackProvider toolCallbackProvider){
return ChatClient.builder(deepSeekChatModel)
.defaultSystem("你是一个智能助手。如果用户问题超出知识库范围,某些操作无法进行时请明确告知用户。")
.defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())//MCP自动注册为工具
.defaultAdvisors(new SimpleLoggerAdvisor()) // 记录日志
.defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build()) // 维护对话历史
.defaultTools(userTool) // 集成用户工具
.build();
}
/**
* @Description: 本地ollama模型(支持RAG检索增强)
* @param
* @return org.springframework.ai.chat.client.ChatClient
*/
@Bean
public ChatClient ollama(ToolCallbackProvider toolCallbackProvider) {
OllamaChatModel ollamaChatModel = OllamaChatModel.builder()
.ollamaApi(ollamaApi)
.defaultOptions(OllamaOptions.builder().model(options.getModel()).build())
.build();
return ChatClient.builder(ollamaChatModel)
.defaultSystem("你是一个智能助手。如果用户问题超出知识库范围,某些操作无法进行时请明确告知用户。")
.defaultToolCallbacks(toolCallbackProvider.getToolCallbacks())//MCP自动注册为工具
.defaultAdvisors(new SimpleLoggerAdvisor())//记录日志
.defaultAdvisors(PromptChatMemoryAdvisor.builder(chatMemory).build())
.build();
}
/**
* 创建向量存储实例
* @param embeddingModel 嵌入模型,用于将文本转换为向量表示
* @return VectorStore 向量存储实例,用于存储和检索向量数据
*/
@Bean
public VectorStore vectorStore(EmbeddingModel embeddingModel) {
// 使用SimpleVectorStore构建器创建向量存储
SimpleVectorStore.SimpleVectorStoreBuilder builder = SimpleVectorStore.builder(embeddingModel);
// 构建并返回向量存储实例
return builder.build();
}
}
6.编写向量数据库服务接口
(1)接口编写 IVectorStoreService
/**
* @author Luoyi
* @description: 向量数据库服务接口
*/
public interface IVectorStoreService {
/**
* 添加文本到向量数据库
* @param text 文本内容
*/
void addText(String text);
/**
* 批量添加文本到向量数据库
* @param texts 文本列表
*/
void addTexts(List<String> texts);
/**
* 添加文档到向量数据库
* @param resource 文档资源(支持PDF、Word、Excel等)
*/
void addDocument(Resource resource);
}
(2)接口实现VectorStoreServiceImpl
/**
* @author Luoyi
* @description: 向量数据库服务实现
*/
@Service
public class VectorStoreServiceImpl implements IVectorStoreService {
@Autowired
private VectorStore vectorStore;
@Override
public void addText(String text) {
// 创建文档并添加到向量数据库
Document document = new Document(text);
vectorStore.add(List.of(document));
}
@Override
public void addTexts(List<String> texts) {
// 批量转换为文档并添加
List<Document> documents = texts.stream()
.map(Document::new)
.collect(Collectors.toList());
vectorStore.add(documents);
}
@Override
public void addDocument(Resource resource) {
// 使用 Tika 读取文档(支持PDF、Word、Excel等)
TikaDocumentReader reader = new TikaDocumentReader(resource);
List<Document> documents = reader.get();
vectorStore.add(documents);
}
}
7.添加RAG功能
增加变量enableKb 用于判断是否开启知识库功能 进行功能拼接.
增加知识库上传功能可以上传文本和文件
@RestController
public class MultiModelsController {
//注册ChatClient
@Autowired
private Map<String, ChatClient> chatClientMap;
@Autowired
private IChatService chatService;
@Autowired
private Advisor ragAdvisor;
@Autowired
private VectorStore vectorStore;
/**
* @param message 消息
* @param model 指定模型
* @return reactor.core.publisher.Flux<java.lang.String>
* @Description: 模型对话流式输出
*/
@RequestMapping(value = "/chat", produces = "text/html;charset=utf-8")
public Flux<String> generation(@RequestParam String message,
@RequestParam String model,
@RequestParam String conversationId,
@RequestParam(name = "enableKb", required = false, defaultValue = "false") boolean enableKb) {
ChatClient chatClient = chatClientMap.get(model);
var prompt = chatClient.prompt()//创建聊天请求对象
.user(message)//设置用户输入信息
//接受一个 AdvisorSpec 类型的参数 (Consumer 方式)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId));
if (enableKb) {
prompt = prompt.advisors(ragAdvisor);
}
Flux<String> content = prompt.stream()//开启流式响应并获取内容
.content();
return content;
}
/**
* @param file 上传的文档
* @param message 消息
* @param model 指定模型
* @param conversationId 会话ID
* @return reactor.core.publisher.Flux<java.lang.String>
* @Description: 带文档上传的对话流式输出
*/
@PostMapping(value = "/chat/upload", produces = "text/html;charset=utf-8")
public Flux<String> chatWithDocument(@RequestParam("file") MultipartFile file,
@RequestParam String message,
@RequestParam String model,
@RequestParam String conversationId,
@RequestParam(name = "enableKb", required = false, defaultValue = "false") boolean enableKb) {
ChatClient chatClient = chatClientMap.get(model);
// 提取文档内容
String documentContent = extractContent(file);
// 将文档内容附加到用户消息中
String enrichedMessage = message + "\n\n【文档内容】\n" + documentContent;
var prompt = chatClient.prompt()
.user(enrichedMessage)
.advisors(advisorSpec -> advisorSpec.param(ChatMemory.CONVERSATION_ID, conversationId));
if (enableKb) {
prompt = prompt.advisors(ragAdvisor);
}
Flux<String> content = prompt.stream()
.content();
return content;
}
/**
* @param text 要添加到知识库的文本内容
* @return java.lang.String
* @Description: 将文本内容添加到向量知识库
*/
@PostMapping("/kb/text")
public String addKbText(@RequestParam String text) {
vectorStore.add(java.util.List.of(new Document(text)));
return "ok";
}
/**
* @param file 要添加到知识库的文件
* @return java.lang.String
* @Description: 将文件内容解析并添加到向量知识库
*/
@PostMapping("/kb/file")
public String addKbFile(@RequestParam("file") MultipartFile file) {
try {
Resource resource = file.getResource();
TikaDocumentReader reader = new TikaDocumentReader(resource);
vectorStore.add(reader.get());
return "ok";
} catch (Exception e) {
return "error: " + e.getMessage();
}
}
}
8.前端文件修改
修改前端文件适配接口,这一步也是交给AI,也可以访问我的仓库Gitee,*https://gitee.com/Luoyi_good/aitest*
9.运行
先配置知识库,大家也可以把文件存放到数据库中,我这里目前没有做知识库内容的回显。

启用知识库

关闭知识库后AI无法进行检索

打开知识库后就可以进行检索

到这RAG知识库功能就实现了,大家可以自己继续进行深入开发,如设立多个知识库每个知识库不同...
更多推荐



所有评论(0)