LangChain4j(15)——RAG高级之跳过检索
之前的文章中,我们介绍了RAG的使用,但是,每次提问时,都会通过RAG进行检索。有时,检索是不必要执行的,比如,当用户只是说“你好”时。于是,我们需要有条件的跳过检索过程。
跳过决策可以通过多种方式实施:
- 使用规则(例如,取决于用户的权限、位置等)
- 使用关键字(例如,如果查询包含特定单词)
- 使用语义相似性
- 使用 LLM 做出决定
注意:本例采用使用LLM做出决定的方案
测试代码
package com.renr.langchain4jnew.app5;import com.renr.langchain4jnew.constant.CommonConstants;
import dev.langchain4j.community.model.zhipu.ZhipuAiChatModel;
import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentParser;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.message.AiMessage;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.memory.chat.MessageWindowChatMemory;
import dev.langchain4j.model.chat.ChatLanguageModel;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.embedding.onnx.bgesmallenv15q.BgeSmallEnV15QuantizedEmbeddingModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.rag.DefaultRetrievalAugmentor;
import dev.langchain4j.rag.RetrievalAugmentor;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.content.retriever.EmbeddingStoreContentRetriever;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.service.AiServices;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Collection;import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;public class AdvancedRAG04_SkipRetrieval {public static void main(String[] args) throws URISyntaxException {ChatLanguageModel chatModel = ZhipuAiChatModel.builder()// 模型key.apiKey(CommonConstants.API_KEY)// 精确度.temperature(0.9).model("GLM-4-Flash").maxRetries(3).callTimeout(Duration.ofSeconds(60)).connectTimeout(Duration.ofSeconds(60)).writeTimeout(Duration.ofSeconds(60)).readTimeout(Duration.ofSeconds(60)).logRequests(true).logResponses(true).build();URL fileUrl = AdvancedRAG01_QueryCompression.class.getClassLoader().getResource("document/miles-of-smiles使用条款.txt");Path path = Paths.get(fileUrl.toURI());// 指定文档解析器DocumentParser documentParser = new TextDocumentParser();// 加载文档数据Document document = FileSystemDocumentLoader.loadDocument(path, documentParser);EmbeddingModel embeddingModel = new BgeSmallEnV15QuantizedEmbeddingModel();EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder().documentSplitter(DocumentSplitters.recursive(300, 0)).embeddingModel(embeddingModel).embeddingStore(embeddingStore).build();ingestor.ingest(document);ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel).maxResults(2).minScore(0.6).build();// 自定义QueryRouter,当需要跳过检索时,QueryRouter 将返回一个空列表QueryRouter queryRouter = new QueryRouter() {// 设置提示词模版private final PromptTemplate PROMPT_TEMPLATE = PromptTemplate.from("以下提问是否与汽车租赁公司的业务有关?只回答'是'、'否'或'可能'。提问内容:{{it}}");@Overridepublic Collection<ContentRetriever> route(Query query) {// 提示词对象中,关联用户的提问内容Prompt prompt = PROMPT_TEMPLATE.apply(query.text());// 针对用户的提问,先通过大模型判断是否满足我们的提示词的要求AiMessage aiMessage = chatModel.chat(prompt.toUserMessage()).aiMessage();System.out.println("LLM decided: " + aiMessage.text());// 如果大模型返回 否,则返回空列表,表示不进行检索if (aiMessage.text().toLowerCase().contains("否")) {return emptyList();}return singletonList(contentRetriever);}};RetrievalAugmentor retrievalAugmentor = DefaultRetrievalAugmentor.builder().queryRouter(queryRouter).build();Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(chatModel).retrievalAugmentor(retrievalAugmentor).chatMemory(MessageWindowChatMemory.withMaxMessages(10)).build();// 第一次提问,不会走检索过程String answer1 = assistant.chat("你好");System.out.println(answer1);// 第二次提问,执行检索String answer2 = assistant.chat("我可以取消预定吗?");System.out.println(answer2);}}
本例自定义QueryRouter,当需要跳过检索时,QueryRouter 将返回一个空列表,表示不需要经过任何检索对象。
执行流程
第一次提问"你好"时
先发送是否需要走检索的提问:
13:29:30.244 [main] DEBUG dev.langchain4j.community.model.zhipu.RequestLoggingInterceptor - Request:
- method: POST
- url: https://open.bigmodel.cn/api/paas/v4/chat/completions
- headers: [Authorization: Bearer ey...fQ.ew0KICAiZXhwIiA6IDE3NDU5OTI3NzAxNjAsDQogICJhcGlfa2V5IiA6ICJkMjMzNGIyZTk4NTE0YWUwOTIzMDQ4YTM4NDEzNTMwNiIsDQogICJ0aW1lc3RhbXAiIDogMTc0NTk5MDk3MDE2MA0KfQ.fwtna_6nALdYVqiTM_jJphWexVme4FRJxg-Lc1AXOTk]
- body: {"model" : "GLM-4-Flash","messages" : [ {"role" : "user","content" : "以下提问是否与汽车租赁公司的业务有关?只回答'是'、'否'或'可能'。提问内容:你好"} ],"stream" : false,"temperature" : 0.9,"max_tokens" : 512,"tool_choice" : "auto"
}
自动先根据我们例子中设置的提示词模版,询问是否与“汽车租赁公司业务相关”。
大模型根据提问内容,给出的回答:
{"choices": [{"finish_reason": "stop","index": 0,"message": {"content": "否","role": "assistant"}}],"created": 1745990971,"id": "2025043013293102690e02a7674aa7","model": "GLM-4-Flash","request_id": "2025043013293102690e02a7674aa7","usage": {"completion_tokens": 3,"prompt_tokens": 33,"total_tokens": 36}
}
通过响应看出,大模型返回了"否",表示不用进行检索。
发送用户提出的问题
13:29:31.281 [main] DEBUG dev.langchain4j.community.model.zhipu.RequestLoggingInterceptor - Request:
- method: POST
- url: https://open.bigmodel.cn/api/paas/v4/chat/completions
- headers: [Authorization: Bearer ey...fQ.ew0KICAiZXhwIiA6IDE3NDU5OTI3NzAxNjAsDQogICJhcGlfa2V5IiA6ICJkMjMzNGIyZTk4NTE0YWUwOTIzMDQ4YTM4NDEzNTMwNiIsDQogICJ0aW1lc3RhbXAiIDogMTc0NTk5MDk3MDE2MA0KfQ.fwtna_6nALdYVqiTM_jJphWexVme4FRJxg-Lc1AXOTk]
- body: {"model" : "GLM-4-Flash","messages" : [ {"role" : "user","content" : "你好"} ],"stream" : false,"temperature" : 0.9,"max_tokens" : 512,"tool_choice" : "auto"
}
可以看到这次只发送了"你好",并没有通过RAG进行检索。
第二次提问“我可以取消预定吗”时
依旧需要先询问大模型是否需要检索
发送请求数据如下:
13:29:31.818 [main] DEBUG dev.langchain4j.community.model.zhipu.RequestLoggingInterceptor - Request:
- method: POST
- url: https://open.bigmodel.cn/api/paas/v4/chat/completions
- headers: [Authorization: Bearer ey...fQ.ew0KICAiZXhwIiA6IDE3NDU5OTI3NzEyODEsDQogICJhcGlfa2V5IiA6ICJkMjMzNGIyZTk4NTE0YWUwOTIzMDQ4YTM4NDEzNTMwNiIsDQogICJ0aW1lc3RhbXAiIDogMTc0NTk5MDk3MTI4MQ0KfQ.iLHSgUhm6XqAB5LDPHXq2NnTUQ_NAiv0VE4ATTQbNWc]
- body: {"model" : "GLM-4-Flash","messages" : [ {"role" : "user","content" : "以下提问是否与汽车租赁公司的业务有关?只回答'是'、'否'或'可能'。提问内容:我可以取消预定吗?"} ],"stream" : false,"temperature" : 0.9,"max_tokens" : 512,"tool_choice" : "auto"
}
大模型认为用户的提问于知识库相关,于是返回内容:
{"choices": [{"finish_reason": "stop","index": 0,"message": {"content": "是","role": "assistant"}}],"created": 1745990972,"id": "20250430132932c36dbc932d2a4d9d","model": "GLM-4-Flash","request_id": "20250430132932c36dbc932d2a4d9d","usage": {"completion_tokens": 3,"prompt_tokens": 37,"total_tokens": 40}
}
大模型返回“是”,表示需要进行检索。
再次发送请求
请求数据中包括第一次的用户提问和大模型回答的内容
13:29:32.050 [main] DEBUG dev.langchain4j.community.model.zhipu.RequestLoggingInterceptor - Request:
- method: POST
- url: https://open.bigmodel.cn/api/paas/v4/chat/completions
- headers: [Authorization: Bearer ey...fQ.ew0KICAiZXhwIiA6IDE3NDU5OTI3NzE4MTgsDQogICJhcGlfa2V5IiA6ICJkMjMzNGIyZTk4NTE0YWUwOTIzMDQ4YTM4NDEzNTMwNiIsDQogICJ0aW1lc3RhbXAiIDogMTc0NTk5MDk3MTgxOA0KfQ.A7e2fMdaX_6oI5uaS6oRaZs8HRiHm9ixCsWtyiSaPJM]
- body: {"model" : "GLM-4-Flash","messages" : [ {"role" : "user","content" : "你好"}, {"role" : "assistant","content" : "你好\uD83D\uDC4B!有什么可以帮助你的吗?"}, {"role" : "user","content" : "我可以取消预定吗?\n\nAnswer using the following information:\n8. 本条款的变更 我们可能随时通过修改此页面来修改这些使用条款。您应该不时查看此页面,以注意我们所做的任何更改。\n9. 接受这些条款 使用服务,即表示您承认您已阅读并理解这些条款,并同意受其约束。如果您不同意这些条款,请不要使用或访问我们的服务。\n\n1. 引言\n本服务条款(以下简称“条款”)适用于您(个人)从世界任何国家/地区访问或使用由在美国注册的 Miles of Smiles Car Rental Services 公司提供的应用程序、网站、内容、产品和服务(以下简称“服务”)。\n2. 服务\nMiles of Smiles 将车辆出租给最终用户。我们保留随时暂时或永久终止服务的权利,并且不对服务的任何修改、暂停或中止负责。\n3. 预订\n3.1 用户可以通过我们的网站或移动应用程序进行预订。\n3.2 您必须在预订过程中提供准确、最新和完整的信息。您负责您账户下产生的所有费用。\n3.3 所有预订均视车辆供应情况而定\n4. 取消政策"} ],"stream" : false,"temperature" : 0.9,"max_tokens" : 512,"tool_choice" : "auto"
}
从请求数据中可以看到,针对第二次提出的问题,进行了检索。
通过上面的过程,可以看出,使用自定义QueryRouter后,每次发送问题之前,需要先根据我们的提示词模版,向大模型发送一个问题,让大模型判断是否需要进行检索。如果返回“否”,表示不需要进行检索;反之,需要通过RAG进行检索。