know-engine

如何实现查询路由——检索器路由?

✅RAG优化技术:查询路由 RAG到这里,相信很多人都有了一些基本的了解了,但是其实我想说,还不够。 在回顾一下RAG的概念: RAG(Retrieval Augmented Generation,检索增强生成)从外部知识源中检索相…

TL;DR

✅RAG优化技术:查询路由 RAG到这里,相信很多人都有了一些基本的了解了,但是其实我想说,还不够。 在回顾一下RAG的概念: RAG(Retrieval Augmented Generation,检索增强生成)从外部知识源中检索相…

✅RAG优化技术:查询路由

RAG到这里,相信很多人都有了一些基本的了解了,但是其实我想说,还不够。 在回顾一下RAG的概念: RAG(Retrieval-Augmented Generation,检索增强生成)从外部知识源中检索相关信息,将相关的知识注入LLM中,使 LLMentor 我们在上面的课程中介绍过关于查询路由,提到多RAG系统的知识来源不一定只能从向量数据库,还是可以从关系型数据库、图数据库的,所以,我们需要实现一个检索器的路由,根据用户的问题路由到不同的检索器中。 我们需要支持三种数据源路由策略: - 关系型数据库 (relational_db):适用于结构化数据查询,如车辆信息、保险信息、订单信息等 - 图数据库 (graph_db):适用于实体关系查询,如车型关系、影响链、层级结构等 - 知识库 (knowledge_base):适用于语义相似性查询/BM25关键词查询,如售前咨询、售后支持、技术问题等。 路由决策流程: - 使用 LLM 分析用户查询语义 - 根据预定义的 Prompt 模板判断最适合的数据源策略 - 返回对应类型的 ContentRetriever 集合 核心实现的代码如下:

package cn.hollis.llm.mentor.know.engine.ai.modules;

import cn.hollis.llm.mentor.know.engine.ai.model.QueryRouteResult;
import cn.hollis.llm.mentor.know.engine.ai.utils.JsonUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONException;
import dev.langchain4j.community.rag.content.retriever.neo4j.Neo4jText2CypherRetriever;
import dev.langchain4j.experimental.rag.content.retriever.sql.SqlDatabaseContentRetriever;
import dev.langchain4j.model.chat.ChatModel;
import dev.langchain4j.model.input.Prompt;
import dev.langchain4j.model.input.PromptTemplate;
import dev.langchain4j.rag.content.retriever.ContentRetriever;
import dev.langchain4j.rag.query.Query;
import dev.langchain4j.rag.query.router.QueryRouter;
import dev.langchain4j.store.embedding.elasticsearch.AbstractElasticsearchEmbeddingStore;
import lombok.extern.slf4j.Slf4j;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import static dev.langchain4j.internal.Utils.getOrDefault;

/**
 * 查询路由器
 * <p>
 * 基于 LLM 智能判断用户查询意图,将查询路由到最合适的内容检索器。
 * 支持三种数据源路由策略:
 * <ul>
 *   <li><b>关系型数据库 (relational_db)</b>:适用于结构化数据查询,如车辆信息、保险信息、订单信息等</li>
 *   <li><b>图数据库 (graph_db)</b>:适用于实体关系查询,如车型关系、影响链、层级结构等</li>
 *   <li><b>知识库 (knowledge_base)</b>:适用于语义相似性查询,如售前咨询、售后支持、技术问题等</li>
 * </ul>
 * <p>
 * <b>路由决策流程:</b>
 * <ol>
 *   <li>使用 LLM 分析用户查询语义</li>
 *   <li>根据预定义的 Prompt 模板判断最适合的数据源策略</li>
 *   <li>返回对应类型的 ContentRetriever 集合</li>
 * </ol>
 * <p>
 * 当路由决策失败(JSON 解析异常或其他错误)时,返回空列表作为降级处理。
 *
 * @see QueryRouter
 * @see ContentRetriever
 */
@Slf4j
public class KnowEngineQueryRouter implements QueryRouter {

    private final Collection<ContentRetriever> contentRetrievers;

    protected final PromptTemplate promptTemplate;

    private final ChatModel chatModel;

    public KnowEngineQueryRouter(Collection<ContentRetriever> contentRetrievers, ChatModel chatModel) {
        this(contentRetrievers, QUERY_ROUTE_PROMPT, chatModel);
    }

    public KnowEngineQueryRouter(Collection<ContentRetriever> contentRetrievers, PromptTemplate promptTemplate, ChatModel chatModel) {
        this.promptTemplate = getOrDefault(promptTemplate, QUERY_ROUTE_PROMPT);
        this.contentRetrievers = contentRetrievers;
        this.chatModel = chatModel;
    }

    private static final PromptTemplate QUERY_ROUTE_PROMPT = PromptTemplate.from("""
            你是一个汽车领域的智能助手,负责理解用户的问题,并智能判断最适合的数据查询方式。你的任务不是直接回答问题,而是分析问题语义,决定应调用哪种或哪几种数据源来获取答案。

            请根据以下规则进行判断:
            1、关系型数据库(Relational DB)适用场景:
            问题涉及结构化数据查询(如“车辆信息”、“保险信息”、“订单信息”等)
            问题涉及到用户个人拥有的车辆相关信息的查询的,如查询发动机号、查询下次保养时间、查询车辆里程等
            包含明确的实体属性、时间范围、数值比较、聚合操作(如 SUM、COUNT、AVG)
            示例:“我的保险还有多少天到期?”

            2、图数据库(Graph DB)适用场景:
            问题关注实体之间的关系、路径、连接性、层级或网络结构
            出现关键词如“谁的发动机是...”、“A和B之间有什么联系?”、“最短路径”、“影响链”
            示例:“纯电车型都有哪些?”、“型号A和型号B有什么关系?”

            3、知识库检索适用场景:
            问题基于语义相似性、模糊匹配、非结构化文本理解
            涉及“类似”、“相关”、“推荐”、“总结”、“解释某段内容”等意图
            涉及到汽车相关售前、售后、技术支持、营销政策等问题
            示例:“发动机异响怎么处理?”、“如何打开零重力座椅?”

            请严格按以下 JSON 格式输出决策结果,不要添加额外解释,不要添加任何markdown符号,如[```]:

            {
              "intent": "简要概括用户问题的核心意图",
              "strategy": "relational_db"
              "reasoning": "简明说明判断依据",
              "confidence": 置信度,0-1之间的小数
            }

            注意:
            strategy 仅使用以下三个字符串值:"relational_db"、"graph_db"、"knowledge_base",其一次只返回一个。
            confidence 表示你对策略推荐的置信度(0–1),评分保留两位小数
            reasoning 应简洁说明判断依据

            用户的原始查询:{{query}}
            """);


    @Override
    public Collection<ContentRetriever> route(Query query) {
        String response = chatModel.chat(createPrompt(query).text());

        try {
            QueryRouteResult queryRouteResult = JSON.parseObject(JsonUtil.fixJson(response), QueryRouteResult.class);
            String strategy = queryRouteResult.strategy();
            log.info("Route Success , query: {} , strategy: {}", query, strategy);

            switch (strategy) {
                case "relational_db":
                    return contentRetrievers.stream().filter(retriever -> retriever instanceof SqlDatabaseContentRetriever).collect(Collectors.toList());
                case "graph_db":
                    return contentRetrievers.stream().filter(retriever -> retriever instanceof Neo4jText2CypherRetriever).collect(Collectors.toList());
                case "knowledge_base":
                    return contentRetrievers.stream().filter(retriever -> retriever instanceof AbstractElasticsearchEmbeddingStore).collect(Collectors.toList());
                default:
                    return contentRetrievers;
            }

        } catch (JSONException jsonException) {
            log.info("Route Failed , query: {} , response: {}", query, response);
            log.info("Route Failed , jsonException: {}", jsonException);
            // fixme
        } catch (Exception e) {
            log.info("Route Failed , query: {} , response: {}", query, response);
            log.info("Route Failed , jsonException: {}", e);
            // fixme
        }
        return List.of();
    }

    protected Prompt createPrompt(Query query) {
        Map<String, Object> variables = new HashMap<>();
        variables.put("query", query.text());
        return promptTemplate.apply(variables);
    }


}

需要增加依赖:

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-experimental-sql</artifactId>
    <version>1.11.0-beta19</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-neo4j</artifactId>
    <version>1.11.0-beta19</version>
</dependency>
<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-neo4j-spring-boot-starter</artifactId>
    <version>1.11.0-beta19</version>
</dependency>

<dependency>
    <groupId>dev.langchain4j</groupId>
    <artifactId>langchain4j-community-neo4j-retriever</artifactId>
    <version>1.11.0-beta19</version>
</dependency>

neo4j需要增加配置:

langchain4j:
    community:
      neo4j:
        await-index-timeout: 300
        auth:
          uri: bolt://localhost:7687
          user: neo4j
          password: neo4j666

neo4j:
  uri: bolt://localhost:7687
  username: neo4j
  password: neo4j666

如果查询适合用关系型数据库,则使用SqlDatabaseContentRetriever检索。 如果查询适合用图数据库,则使用Neo4jText2CypherRetriever检索。 如果查询适合用向量数据库,则使用AbstractElasticsearchEmbeddingStore检索。 默认情况下,使用AbstractElasticsearchEmbeddingStore检索。 增加一个测试的接口:

/**
 * 用于ai的各个模块的功能测试
 */
@RestController
@RequestMapping("/ai/module")
public class RagModuleController {


    @Autowired
    private ChatModel chatModel;

    @Autowired
    private StreamingChatModel streamingChatModel;

    @Autowired
    private OpenAiEmbeddingModel openAiEmbeddingModel;

    @Autowired
    private RestClient restClient;

    @Autowired
    private DataSource dataSource;

    @Autowired
    private Driver neo4jDriver;

    @Autowired
    private KnowledgeSegmentService knowledgeSegmentService;

    @Autowired
    private PromptService promptService;

    @Autowired
    private ChatMessageService chatMessageService;

    private ElasticsearchContentRetriever fullTextRetriever;

    private SqlDatabaseContentRetriever sqlRetriever;

    private Neo4jText2CypherRetriever neo4jRetriever;

    private static final int MAX_RESULT = 5;

    private static final double MIN_SCORE = 0.5;


    @PostConstruct
    public void init() throws IOException {

        this.fullTextRetriever = ElasticsearchContentRetriever.builder()
                .restClient(restClient)
                .configuration(ElasticsearchConfigurationFullText.builder().build())
                .maxResults(MAX_RESULT)
                .indexName(INDEX_NAME)
                .minScore(MIN_SCORE)
                .build();

        this.sqlRetriever = SqlDatabaseContentRetriever.builder().dataSource(dataSource)
                .chatModel(chatModel)
                .build();

        this.neo4jRetriever = Neo4jText2CypherRetriever.builder()
                .graph(Neo4jGraph.builder()
                        .driver(neo4jDriver)
                        .build())
                .chatModel(chatModel)
                .build();
    }

    @GetMapping("/router")
    public String testRouter(String query) {

        KnowEngineElasticsearchContentRetriever embeddingRetriever = KnowEngineElasticsearchContentRetriever.builder()
                .restClient(restClient)
                .embeddingModel(openAiEmbeddingModel)
                .configuration(ElasticsearchConfigurationKnn.builder().build())
                .maxResults(MAX_RESULT)
                .indexName(INDEX_NAME)
                .minScore(MIN_SCORE)
                .stringRedisTemplate(knowledgeSegmentService)
                .build();

        KnowEngineQueryRouter knowEngineQueryRouter = new KnowEngineQueryRouter(List.of(embeddingRetriever, fullTextRetriever, sqlRetriever, neo4jRetriever), chatModel);
        Collection<ContentRetriever> contentRetrievers = knowEngineQueryRouter.route(new Query(query));
        return contentRetrievers.toString();
    }
}

测试结果: 问题:我的车怎么加玻璃水? 检索器:ElasticsearchContentRetriever、KnowEngineElasticsearchContentRetriever 问题:我的车险什么时候到期? 检索器:SqlDatabaseContentRetriever 问题:问界L9混动版和纯电版哪些配置相同? 检索器:Neo4jText2CypherRetriever ```

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。