1 概述
这里将基于 Spring AI 框架与 RAG(检索增强生成) 技术实现一个聊天机器人。借助 Spring AI,接入 Redis 向量数据库,完成数据的存储与检索,用来给大模型的提示词补充上下文信息。 当大模型拿到附带相关资料的提示词之后,就可以基于最新数据,用自然语言生成对用户问题的回答。
2 什么是 RAG
大语言模型是基于互联网海量数据集预训练得到的机器学习模型。如果要让大模型适配企业内部业务场景,通常需要使用企业专属知识库对模型做微调。 但微调非常耗费时间,需要大量算力资源;并且经过微调后的大模型依然很可能输出无关、误导性内容,该现象就是大模型幻觉(hallucinations)。
面对这类问题,RAG 是非常优秀的方案,可以约束大模型输出,给回答补充业务上下文。 向量数据库在RAG架构中扮演关键角色,负责为大模型提供上下文资料。但在投入RAG使用前,需要通过 ETL(抽取‑转换‑加载) 流程完成数据入库:
- 读取器(Reader):从各类来源读取企业知识库文档;
- 转换器(Transformer):把文档切分成小块(分片chunk),调用嵌入模型把文本转为向量;
- 写入器(Writer):将向量(嵌入向量)存入向量数据库。
向量数据库是专门用于在多维空间存储向量数据的专用数据库。
只要向量数据库定期从企业知识库同步更新数据,RAG架构下大模型就可以近乎实时地使用最新业务数据回答问题。
向量数据库准备好数据之后,应用就可以针对用户提问检索上下文资料:
流程简述:
- 应用接收用户查询,向向量数据库发起语义检索;
- 获取语义检索返回的相关文档;
- 将用户提问 + 检索得到的上下文拼接为完整提示词发给大模型;
- 大模型严格基于提供的上下文生成回答,返回给应用。
大模型会在给定上下文资料的范围内生成自然语言回答,再把结果返回应用程序。
3 使用 Spring AI + Redis 实现RAG
Redis Stack 提供向量检索能力。我们将通过 Spring AI 对接该能力,开发基于RAG的聊天机器人;同时选用 OpenAI 的 GPT‑3.5‑Turbo 作为生成回答的大模型。
3.1 环境准备
要调用OpenAI服务,需要API密钥。注册OpenAI账号后,即可创建密钥。
还需要注册 Redis Cloud 账号,获取免费可用的Redis向量数据库实例。
引入Maven依赖,接入Redis向量库与OpenAI:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-openai-spring-boot-starter</artifactId> <version>1.0.0-M1</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-transformers-spring-boot-starter</artifactId> <version>1.0.0-M1</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-redis-spring-boot-starter</artifactId> <version>1.0.0-M1</version> </dependency> <dependency> <groupId>org.springframework.ai</groupId> <artifactId>spring-ai-pdf-document-reader</artifactId> <version>1.0.0-M1</version> </dependency> |
3.2 向Redis加载数据的核心类
在Spring Boot项目中,我们需要编写组件,完成Redis向量库的数据写入与读取。示例场景:把一份员工手册PDF文档加载进Redis向量库。
类关系说明:
DocumentReader:Spring AI 读取文档的接口;示例使用内置实现PagePdfDocumentReader读取PDF;DocumentWriter:文档写入接口;VectorStore:向量存储读写接口,提供保存、删除、相似度搜索;RedisVectorStore:VectorStore的Redis实现,用于Redis向量库的写入和检索;DataLoaderService:业务服务,组合上述组件,完成PDF文档导入。
3.3 实现数据加载服务 DataLoaderService
load() 方法完整实现:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
@Service public class DataLoaderService { private static final Logger logger = LoggerFactory.getLogger(DataLoaderService.class); @Value("classpath:/data/Employee_Handbook.pdf") private Resource pdfResource; @Autowired private VectorStore vectorStore; public void load() { PagePdfDocumentReader pdfReader = new PagePdfDocumentReader(this.pdfResource, PdfDocumentReaderConfig.builder() .withPageExtractedTextFormatter(ExtractedTextFormatter.builder() .withNumberOfBottomTextLinesToDelete(3) .withNumberOfTopPagesToSkipBeforeDelete(1) .build()) .withPagesPerDocument(1) .build()); var tokenTextSplitter = new TokenTextSplitter(); this.vectorStore.accept(tokenTextSplitter.apply(pdfReader.get())); } } |
Spring AI 会读取
spring.ai.vectorstore命名空间下配置,自动装配VectorStore接口,实际注入的实现是RedisVectorStore。
application.yml Redis向量库配置:
|
1 2 3 4 5 6 7 8 |
spring: ai: vectorstore: redis: uri: redis://:PQzkkZLOgOXXX@redis-19438.c330.asia-south1-1.gce.redns.redis-cloud.com:19438 index: faqs prefix: "faq:" initialize-schema: true |
TokenTextSplitter 负责把文档切分为文本块(chunk),再交由VectorStore存入Redis向量数据库。
3.4 生成最终回答的核心类
Redis向量库数据就绪之后,就可以根据用户提问检索相关上下文;再把上下文拼入提示词交给大模型生成回答。
类关系说明:
DataRetrievalService:数据检索服务,调用VectorStore.similaritySearch(),根据用户query检索相关Document文档列表;PromptTemplate:提示词模板,把用户提问和检索出来的上下文拼装成完整prompt;ChatBotService:聊天机器人业务服务,调用检索服务,拼装提示词,调用大模型;ChatModel:大模型调用接口,OpenAiChatModel为OpenAI的实现。
Spring Boot读取配置文件,自动装配
OpenAiChatModelBean。本示例激活Spring profile为airag。
3.5 实现聊天机器人服务 ChatBotService
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
@Service public class ChatBotService { @Qualifier("openAiChatModel") @Autowired private ChatModel chatClient; @Autowired private DataRetrievalService dataRetrievalService; private final String PROMPT_BLUEPRINT = """ Answer the query strictly referring the provided context: {context} Query: {query} In case you don't have any answer from the context provided, just say: I'm sorry I don't have the information you are looking for. """; public String chat(String query) { return chatClient.call(createPrompt(query, dataRetrievalService.searchData(query))); } private String createPrompt(String query, List<Document> context) { PromptTemplate promptTemplate = new PromptTemplate(PROMPT_BLUEPRINT); promptTemplate.add("query", query); promptTemplate.add("context", context); return promptTemplate.render(); } } |
OpenAI相关配置(application.yml):
|
1 2 3 4 5 6 7 8 9 10 11 12 |
spring: ai: vectorstore: redis: # Redis向量库配置省略 openai: temperature: 0.3 api-key: ${SPRING_AI_OPENAI_API_KEY} model: gpt-3.5-turbo #embedding-base-url: [https://api.openai.com](https://api.openai.com) #embedding-api-key: ${SPRING_AI_OPENAI_API_KEY} #embedding-model: text-embedding-ada-002 |
最佳实践:API密钥优先使用环境变量
SPRING_AI_OPENAI_API_KEY,更加安全。 开启embedding开头的配置项,会自动创建OpenAiEmbeddingModel,用于文档文本生成向量。
提示词模板做了严格约束:回答必须严格基于传入的上下文资料,不能使用模型内部知识编造答案。
chat()方法执行流程:
- 根据用户提问,从Redis向量库检索匹配的文档;
createPrompt()把检索结果+用户问题拼装完整提示词;- 调用
ChatModel.call(),请求OpenAI获取回答。
单元测试:提问知识库范围内的问题(员工着装规范)
|
1 2 3 4 5 6 |
@Test void whenQueryAskedWithinContext_thenAnswerFromTheContext() { String response = chatBotService.chat("How are employees supposed to dress?"); assertNotNull(response); logger.info("Response from LLM: {}", response); } |
输出结果:
|
1 |
Response from LLM: Employees are supposed to dress appropriately for their individual work responsibilities and position. |
回答完全来自导入的员工手册PDF文档。
单元测试:提问知识库不存在的问题
|
1 2 3 4 5 6 |
@Test void whenQueryAskedOutOfContext_thenDontAnswer() { String response = chatBotService.chat("What should employees eat?"); assertEquals("I'm sorry I don't have the information you are looking for.", response); logger.info("Response from the LLM: {}", response); } |
输出:
|
1 |
Response from the LLM: I'm sorry I don't have the information you are looking for. |
当上下文找不到相关资料,模型直接返回预设话术,拒绝编造答案。
4 总结
本文演示了如何基于Spring AI实现RAG架构应用。将检索得到的上下文和用户提问拼装提示词,是获得准确大模型回答的关键。 Redis向量数据库非常适合存储文档向量并执行相似度检索。
同时文档分片(chunking)也至关重要:既能够检索到精准的参考片段,又可以控制提示词Token开销,避免成本过高。