之前的文章中,我们介绍了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}}"
);
@Override
public 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进行检索。