AI--知识库RAG实战
本地RAG知识库实战
1、什么是RAG?
RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合信息检索技术和 AI 内容生成的混合架构,可以解决大模型的知识时效性限制和幻觉问题。
简单来说,RAG 就像给 AI 配了一个 “小抄本”,让 AI 回答问题前先查一查特定的知识库来获取知识,确保回答是基于真实资料而不是凭空想象。
2、RAG 和传统 AI 模型的区别:
特性 | 传统大语言模型 | RAG增强模型 |
---|---|---|
知识时效性 | 受训练数据截止日期限制 | 可接入最新知识库 |
领域专业性 | 泛化知识,专业深度有限 | 可接入专业领域知识 |
响应准确性 | 可能产生 “幻觉” | 基于检索的事实依据 |
可控性 | 依赖原始训练 | 可通过知识库定制输出 |
资源消耗 | 较高(需要大模型参数) | 模型可更小,结合外部知识 |
3、RAG 工作流程
3.1 文档收集和切割
文档收集:从各种来源(网页、PDF、数据库等)收集原始文档
文档预处理:清洗、标准化文本格式
文档切割:将长文档分割成适当大小的片段
- 基于固定大小(如 512 个 token)
- 基于语义边界(如段落、章节)
如下图:
3.2 向量转换和存储
向量转换:使用 Embedding 模型将文本块转换为高维向量表示,可以捕获到文本的语义特征
向量存储:将生成的向量和对应文本存入向量数据库,支持高效的相似性搜索
如下图:
3.3 文档过滤和检索
查询处理:将用户问题也转换为向量表示
过滤机制:基于元数据、关键词或自定义规则进行过滤
相似度搜索:在向量数据库中查找与问题向量最相似的文档块,常用的相似度搜索算法有余弦相似度、欧氏距离等
上下文组装:将检索到的多个文档块组装成连贯上下文
3.4 查询增强和关联
提示词组装:将检索到的相关文档与用户问题组合成增强提示
上下文融合:大模型基于增强提示生成回答
源引用:在回答中添加信息来源引用
后处理:格式化、摘要或其他处理以优化最终输出
完整工作流程
分别理解上述 4 个步骤后,我们可以将它们组合起来,形成完整的 RAG 检索增强生成工作流程:
4、代码实现
可以参考SpringAi官方文档案例进行开发,建议存到向量库尽量不要用PDF格式,感觉效果并不是那么好,当然可以让AI调用调用工具,将PDF转成text文本或者markdown格式在存入。
官网地址:https://docs.spring.io/spring-ai/reference/api/etl-pipeline.html#_markdown
先编写一个接口:
@PostMapping("/insertUserVetor")@Operation(summary = "上传文件添加到向量库中")public ResponseEntity<String> insertUserVetor(@RequestParam("file") MultipartFile[] files) {operatePgVertorManager.loadUserDocumentsToVectorStore(files);return ResponseEntity.ok("文件上传成功");}
保存到向量库可以用VectorStore
创建一个bean就OK的
加载用户上传的文件,调用add方法进行添加到向量库
/*** 加载用户上传的 markdown 文档并存入向量库:mrsun_agent*/public void loadUserDocumentsToVectorStore(MultipartFile[] files) {List<Document> documents = documentLoader.loadUserMarkdowns(files);List<Document> enrichedDocs = myKeywordEnricher.enrichDocument(documents);pgVectorVectorStore.add(enrichedDocs);}
加载文档以及处理文档
/*** 加载用户上传的文档切分入向量库* @param files 可以是多个* @return*/public List<Document> loadUserMarkdowns(MultipartFile[] files) {List<Document> allDocuments = new ArrayList<>();for (MultipartFile file : files) {try {String fileName = file.getOriginalFilename();if (fileName == null || fileName.length() < 6) {log.warn("文件名无效或过短,跳过: {}", fileName);continue;}Resource resource = new FileSystemResource(saveToTempFile(file));String status = fileName.substring(fileName.length() - 6, fileName.length() - 4);MarkdownDocumentReaderConfig config = MarkdownDocumentReaderConfig.builder().withHorizontalRuleCreateDocument(true).withIncludeCodeBlock(false).withIncludeBlockquote(false).withAdditionalMetadata("filename", fileName).withAdditionalMetadata("status", status).build();MarkdownDocumentReader reader = new MarkdownDocumentReader(resource, config);//处理matadata的信息List<Document> docs = updateMetadata(reader.get(), fileName);allDocuments.addAll(docs);} catch (IOException e) {log.error("Markdown 文件加载失败: {}", file.getOriginalFilename(), e);}}return allDocuments;}
5、效果
打开调试台。上传一个文件测试效果
进行切片入向量库。用的是pg数据库。查看结果:
6、查询重写
查询重写可以使查询更加精确和专业,但是要注意保持查询的语义完整性
主要应用包括:
- 使用 RewriteQueryTransformer 优化查询结构
参考 官方文档 实现查询重写:
地址:https://java2ai.com/docs/1.0.0-M6.1/tutorials/rag/#32-query-rewrite-%E6%9F%A5%E8%AF%A2%E9%87%8D%E5%86%99
代码如下:
/*** 查询重写器* 查询重写和翻译可以使查询更加精确和专业,但是要注意保持查询的语义完整性*/
@Component
public class QueryRewriter {private final QueryTransformer queryTransformer;/**** 自动注入:使用阿里云百炼千问plus模型,dashscopeChatModel* @param dashscopeChatModel*/public QueryRewriter(ChatModel dashscopeChatModel) {ChatClient.Builder builder = ChatClient.builder(dashscopeChatModel);// 创建查询重写转换器queryTransformer = RewriteQueryTransformer.builder().chatClientBuilder(builder).build();}/*** 接收一个原本的查询---->转成新的查询** @param prompt* @return*/public String doQueryRewrite(String prompt) {Query query = new Query(prompt);//执行查询重写Query transformedQuery = queryTransformer.transform(query);// 输出重写后的查询return transformedQuery.text();}
}
7、知识库查询
查询知识库的流程和上面流程图是一样的。用户需要输入问题,Embedding模型会帮我们将问题转换成向量,如:[1.3,1.6,2.1]等等。。。。然后去向量库中进行检索相关的文档,取出最接近的文档进行切片,进行精排,喂给AI(用户问题+文档)最终返回一个精准的文档。
代码如下:
先编写问题的入口,参数分别为消息内容,以及chatId(不可重复)
@GetMapping("/getQueryInfo")@Operation(summary = "从RAG中查询结果")public ResponseEntity getQueryInfo(String message, String chatId) {String resopnse = null;try {resopnse = pgVertorManager.queryMarkdownKnowledge(message, chatId);return ResponseEntity.ok(resopnse);} catch (Exception e) {return ResponseEntity.internalServerError().body("查询失败,错误原因为: " + e.getMessage());}}
定义一个manager层,进行调用查询等操作:
public String queryMarkdownKnowledge(String message, String chatId) {return queryFromVectorStore(message, chatId, pgVectorVectorStore, SYSTEM_PROMPT_AI);}
传递进去的为消息,会话id,从哪个库中查询,以及提示词。由于不想让AI回答的太官方,或者一看就是从知识库中拿到的文档,这里进行设置一个预先的提示词:
private final static String SYSTEM_PROMPT_AI = """你是一个全公司最厉害的人事小助手,你需要使用文档内容对用户提出的问题进行回复,同时你需要表现得天生就知道这些内容,不能在回复中体现出你是根据给出的文档内容进行回复的,这点非常重要。文档内容如下:{documents}""";
编写查询的业务,可以用到用户问题重写,
/*** 通用 RAG 查询接口*/private String queryFromVectorStore(String message, String chatId, VectorStore vectorStore, String systemPromptTemplate) {SearchRequest request = SearchRequest.builder().query(message).build();List<Document> similarDocs = vectorStore.similaritySearch(request);String documents = similarDocs.stream().map(Document::getText).collect(Collectors.joining());Message systemMessage = new SystemPromptTemplate(systemPromptTemplate).createMessage(Map.of("documents", documents));//构建AI查询重写器,可以改写输入的问题String rewrittenQuery = queryRewriter.doQueryRewrite(message);//调用大模型,交给AI构造请求上下文ChatResponse response = chatClient.prompt().messages(systemMessage).user(rewrittenQuery).advisors(spec -> spec.param(CHAT_MEMORY_CONVERSATION_ID_KEY, chatId).param(CHAT_MEMORY_RETRIEVE_SIZE_KEY, 10))
// .advisors(RagCustomAdvisorFactory.createAppRagCustomAdvisor(vectorStore,"红线")).call().chatResponse();return response.getResult().getOutput().getText();}
测试效果:
可以打个断点到用户重写的方法那,看看AI给我们转成了什么?
最终回答:
这就是我们公司的红线,如果不加提示词模版。它会回答的很传统,直接把向量库中的内容给我念出来了。
错误处理机制
在实际应用中,可能出现多种异常情况,如找不到相关文档、相似度过低、查询超时等。良好的错误处理机制可以提升用户体验。
异常处理主要包括:
1、允许空上下文查询(即处理边界情况)
2、提供友好的错误提示
3、引导用户提供必要信息
边界情况处理可以使用 Spring AI 的 ContextualQueryAugmenter 上下文查询增强器:
RetrievalAugmentationAdvisor.builder().queryAugmenter(ContextualQueryAugmenter.builder().allowEmptyContext(false).build())
如果不使用自定义处理器,或者未启用 “允许空上下文” 选项,系统在找不到相关文档时会默认改写用户查询 userText:
The user query is outside your knowledge base.
Politely inform the user that you can't answer it.
如果启用 “允许空上下文”,系统会自动处理空 Prompt 情况,不会改写用户输入,而是使用原本的查询。
我们也可以自定义错误处理逻辑,来运用工厂模式创建一个自定义的 ContextualQueryAugmenter:
/*** 创建上下文工厂增强器----用于异常处理,如果AI没有检索到知识。则让自定义回答*/
public class ContextualQueryAugmenterFactory {/*** 创建自定义提示词* 注:如果没有下面的代码。或者改成true AI则会给你改写提示词。* .allowEmptyContext(false) //允许空上下文* .emptyContextPromptTemplate(emptyContextPromptTemplate) 使用自定义的提示词。检索不到的情况* The user query is outside your knowledge base.* Politely inform the user that you can't answer it.** @return*/public static ContextualQueryAugmenter createInstance() {PromptTemplate emptyContextPromptTemplate = new PromptTemplate("""你应该输出下面的内容:很抱歉,您的问题超出了当前知识范围,暂时无法为您提供准确答案。如需更多帮助,请联系南瓜:xxxxxxx@qq.com""");return ContextualQueryAugmenter.builder().allowEmptyContext(false) //允许空上下文.emptyContextPromptTemplate(emptyContextPromptTemplate).build();}}
给检索增强生成 Advisor 应用自定义的 ContextualQueryAugmenter:
public static Advisor createAppRagCustomAdvisor(VectorStore vectorStore, String status) {/*** 过滤特定状态文档*/Filter.Expression expression = new FilterExpressionBuilder().eq("status", status).build();//创建检索器 实现更精准的检索能力VectorStoreDocumentRetriever documentRetriever = VectorStoreDocumentRetriever.builder().vectorStore(vectorStore).filterExpression(expression) //过滤条件.similarityThreshold(0.5) //相似度阈值.topK(3) //返回文档数量.build();return RetrievalAugmentationAdvisor.builder().documentRetriever(documentRetriever)//异常处理.queryAugmenter(ContextualQueryAugmenterFactory.createInstance()).build();}
测试
执行效果如下:
AI返回内容:
此文章希望对你学习RAG有所帮助,重点是理解RAG工作流程
。