✅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 ```