Files
all-in-rag/code/C9/rag_modules/intelligent_query_router.py
2026-05-12 09:41:56 +08:00

306 lines
12 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
智能查询路由器
根据查询特点自动选择最适合的检索策略:
- 传统混合检索:适合简单的信息查找
- 图RAG检索:适合复杂的关系推理和知识发现
"""
import json
import logging
from typing import List, Dict, Tuple, Any, Optional
from dataclasses import dataclass
from enum import Enum
from langchain_core.documents import Document
logger = logging.getLogger(__name__)
class SearchStrategy(Enum):
"""搜索策略枚举"""
HYBRID_TRADITIONAL = "hybrid_traditional" # 传统混合检索
GRAPH_RAG = "graph_rag" # 图RAG检索
COMBINED = "combined" # 组合策略
@dataclass
class QueryAnalysis:
"""查询分析结果"""
query_complexity: float # 查询复杂度 (0-1)
relationship_intensity: float # 关系密集度 (0-1)
reasoning_required: bool # 是否需要推理
entity_count: int # 实体数量
recommended_strategy: SearchStrategy
confidence: float # 推荐置信度
reasoning: str # 推荐理由
class IntelligentQueryRouter:
"""
智能查询路由器
核心能力:
1. 查询复杂度分析:识别简单查找 vs 复杂推理
2. 关系密集度评估:判断是否需要图结构优势
3. 策略自动选择:路由到最适合的检索引擎
4. 结果质量监控:基于反馈优化路由决策
"""
def __init__(self,
traditional_retrieval, # 传统混合检索模块
graph_rag_retrieval, # 图RAG检索模块
llm_client,
config):
self.traditional_retrieval = traditional_retrieval
self.graph_rag_retrieval = graph_rag_retrieval
self.llm_client = llm_client
self.config = config
# 路由统计
self.route_stats = {
"traditional_count": 0,
"graph_rag_count": 0,
"combined_count": 0,
"total_queries": 0
}
def analyze_query(self, query: str) -> QueryAnalysis:
"""
深度分析查询特征,决定最佳检索策略
"""
logger.info(f"分析查询特征: {query}")
# 使用LLM进行智能分析
analysis_prompt = f"""
作为RAG系统的查询分析专家,请深度分析以下查询的特征:
查询:{query}
请从以下维度分析:
1. 查询复杂度 (0-1)
- 0.0-0.3: 简单信息查找(如:红烧肉怎么做?)
- 0.4-0.7: 中等复杂度(如:川菜有哪些特色菜?)
- 0.8-1.0: 高复杂度推理(如:为什么川菜用花椒而不是胡椒?)
2. 关系密集度 (0-1)
- 0.0-0.3: 单一实体信息(如:西红柿的营养价值)
- 0.4-0.7: 实体间关系(如:鸡肉配什么蔬菜?)
- 0.8-1.0: 复杂关系网络(如:川菜的形成与地理、历史的关系)
3. 推理需求:
- 是否需要多跳推理?
- 是否需要因果分析?
- 是否需要对比分析?
4. 实体识别:
- 查询中包含多少个明确实体?
- 实体类型是什么?
基于分析推荐检索策略:
- hybrid_traditional: 适合简单直接的信息查找
- graph_rag: 适合复杂关系推理和知识发现
- combined: 需要两种策略结合
返回JSON格式:
{{
"query_complexity": 0.6,
"relationship_intensity": 0.8,
"reasoning_required": true,
"entity_count": 3,
"recommended_strategy": "graph_rag",
"confidence": 0.85,
"reasoning": "该查询涉及多个实体间的复杂关系,需要图结构推理"
}}
"""
try:
response = self.llm_client.chat.completions.create(
model=self.config.llm_model,
messages=[{"role": "user", "content": analysis_prompt}],
temperature=0.1,
max_tokens=800
)
result = json.loads(response.choices[0].message.content.strip())
analysis = QueryAnalysis(
query_complexity=result.get("query_complexity", 0.5),
relationship_intensity=result.get("relationship_intensity", 0.5),
reasoning_required=result.get("reasoning_required", False),
entity_count=result.get("entity_count", 1),
recommended_strategy=SearchStrategy(result.get("recommended_strategy", "hybrid_traditional")),
confidence=result.get("confidence", 0.5),
reasoning=result.get("reasoning", "默认分析")
)
logger.info(f"查询分析完成: {analysis.recommended_strategy.value} (置信度: {analysis.confidence:.2f})")
return analysis
except Exception as e:
logger.error(f"查询分析失败: {e}")
# 降级方案:基于规则的简单分析
return self._rule_based_analysis(query)
def _rule_based_analysis(self, query: str) -> QueryAnalysis:
"""基于规则的降级分析"""
# 简单的规则判断
complexity_keywords = ["为什么", "如何", "关系", "影响", "原因", "比较", "区别"]
relation_keywords = ["配", "搭配", "组合", "相关", "联系", "连接"]
complexity = sum(1 for kw in complexity_keywords if kw in query) / len(complexity_keywords)
relation_intensity = sum(1 for kw in relation_keywords if kw in query) / len(relation_keywords)
if complexity > 0.3 or relation_intensity > 0.3:
strategy = SearchStrategy.GRAPH_RAG
else:
strategy = SearchStrategy.HYBRID_TRADITIONAL
return QueryAnalysis(
query_complexity=complexity,
relationship_intensity=relation_intensity,
reasoning_required=complexity > 0.3,
entity_count=len(query.split()),
recommended_strategy=strategy,
confidence=0.6,
reasoning="基于规则的简单分析"
)
def route_query(self, query: str, top_k: int = 5) -> Tuple[List[Document], QueryAnalysis]:
"""
智能路由查询到最适合的检索引擎
"""
logger.info(f"开始智能路由: {query}")
# 1. 分析查询特征
analysis = self.analyze_query(query)
# 2. 更新统计
self._update_route_stats(analysis.recommended_strategy)
# 3. 根据策略执行检索
documents = []
try:
if analysis.recommended_strategy == SearchStrategy.HYBRID_TRADITIONAL:
logger.info("使用传统混合检索")
documents = self.traditional_retrieval.hybrid_search(query, top_k)
elif analysis.recommended_strategy == SearchStrategy.GRAPH_RAG:
logger.info("🕸️ 使用图RAG检索")
documents = self.graph_rag_retrieval.graph_rag_search(query, top_k)
elif analysis.recommended_strategy == SearchStrategy.COMBINED:
logger.info("🔄 使用组合检索策略")
documents = self._combined_search(query, top_k)
# 4. 结果后处理
documents = self._post_process_results(documents, analysis)
logger.info(f"路由完成,返回 {len(documents)} 个结果")
return documents, analysis
except Exception as e:
logger.error(f"查询路由失败: {e}")
# 降级到传统检索
documents = self.traditional_retrieval.hybrid_search(query, top_k)
return documents, analysis
def _combined_search(self, query: str, top_k: int) -> List[Document]:
"""
组合搜索策略:结合传统检索和图RAG的优势
"""
# 分配结果数量
traditional_k = max(1, top_k // 2)
graph_k = top_k - traditional_k
# 执行两种检索
traditional_docs = self.traditional_retrieval.hybrid_search(query, traditional_k)
graph_docs = self.graph_rag_retrieval.graph_rag_search(query, graph_k)
# 合并和去重
combined_docs = []
seen_contents = set()
# 交替添加结果(Round-robin
max_len = max(len(traditional_docs), len(graph_docs))
for i in range(max_len):
# 先添加图RAG结果(通常质量更高)
if i < len(graph_docs):
doc = graph_docs[i]
content_hash = hash(doc.page_content[:100])
if content_hash not in seen_contents:
seen_contents.add(content_hash)
doc.metadata["search_source"] = "graph_rag"
combined_docs.append(doc)
# 再添加传统检索结果
if i < len(traditional_docs):
doc = traditional_docs[i]
content_hash = hash(doc.page_content[:100])
if content_hash not in seen_contents:
seen_contents.add(content_hash)
doc.metadata["search_source"] = "traditional"
combined_docs.append(doc)
return combined_docs[:top_k]
def _post_process_results(self, documents: List[Document], analysis: QueryAnalysis) -> List[Document]:
"""
结果后处理:根据查询分析优化结果
"""
for doc in documents:
# 添加路由信息到元数据
doc.metadata.update({
"route_strategy": analysis.recommended_strategy.value,
"query_complexity": analysis.query_complexity,
"route_confidence": analysis.confidence
})
return documents
def _update_route_stats(self, strategy: SearchStrategy):
"""更新路由统计"""
self.route_stats["total_queries"] += 1
if strategy == SearchStrategy.HYBRID_TRADITIONAL:
self.route_stats["traditional_count"] += 1
elif strategy == SearchStrategy.GRAPH_RAG:
self.route_stats["graph_rag_count"] += 1
elif strategy == SearchStrategy.COMBINED:
self.route_stats["combined_count"] += 1
def get_route_statistics(self) -> Dict[str, Any]:
"""获取路由统计信息"""
total = self.route_stats["total_queries"]
if total == 0:
return self.route_stats
return {
**self.route_stats,
"traditional_ratio": self.route_stats["traditional_count"] / total,
"graph_rag_ratio": self.route_stats["graph_rag_count"] / total,
"combined_ratio": self.route_stats["combined_count"] / total
}
def explain_routing_decision(self, query: str) -> str:
"""解释路由决策过程"""
analysis = self.analyze_query(query)
explanation = f"""
查询路由分析报告
查询:{query}
特征分析:
- 复杂度:{analysis.query_complexity:.2f} ({'简单' if analysis.query_complexity < 0.4 else '中等' if analysis.query_complexity < 0.8 else '复杂'})
- 关系密集度:{analysis.relationship_intensity:.2f} ({'单一实体' if analysis.relationship_intensity < 0.4 else '实体关系' if analysis.relationship_intensity < 0.8 else '复杂关系网络'})
- 推理需求:{'是' if analysis.reasoning_required else '否'}
- 实体数量:{analysis.entity_count}
推荐策略:{analysis.recommended_strategy.value}
置信度:{analysis.confidence:.2f}
决策理由:{analysis.reasoning}
"""
return explanation