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}}"
            );

            @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进行检索。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值