Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-rag/src/lexigram/ai/rag/routing/router.py: 40%
70 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
« prev ^ index » next coverage.py v7.15.4, created at 2026-08-25 07:19 +0800
1"""Main query router for RAG systems."""
3from __future__ import annotations
5from dataclasses import dataclass, field
7from lexigram.ai.rag.routing.analyzer import QueryAnalyzer
8from lexigram.ai.rag.routing.strategies.base import RoutingStrategy
9from lexigram.ai.rag.routing.strategies.rule_based import RuleBasedRouter
10from lexigram.ai.rag.routing.types import (
11 DataSource,
12 QueryFeatures,
13 RoutingDecision,
14)
17@dataclass
18class RoutingStatistics:
19 """Statistics for query routing.
21 Attributes:
22 total_queries: Total number of queries routed.
23 by_strategy: Count of queries by strategy.
24 by_data_source: Count of queries by data source.
25 avg_confidence: Average confidence score.
26 high_confidence_count: Number of high-confidence decisions (>0.7).
27 low_confidence_count: Number of low-confidence decisions (<0.3).
28 """
30 total_queries: int = 0
31 by_strategy: dict[str, int] = field(default_factory=dict)
32 by_data_source: dict[str, int] = field(default_factory=dict)
33 avg_confidence: float = 0.0
34 high_confidence_count: int = 0
35 low_confidence_count: int = 0
37 def update(self, decision: RoutingDecision) -> None:
38 """Update statistics with a routing decision.
40 Args:
41 decision: Routing decision to record.
42 """
43 self.total_queries += 1
45 # Track strategy
46 self.by_strategy[decision.strategy] = (
47 self.by_strategy.get(decision.strategy, 0) + 1
48 )
50 # Track data sources
51 for source in decision.data_sources:
52 self.by_data_source[source.name] = (
53 self.by_data_source.get(source.name, 0) + 1
54 )
56 # Update confidence stats
57 old_avg = self.avg_confidence
58 self.avg_confidence = (
59 old_avg * (self.total_queries - 1) + decision.confidence
60 ) / self.total_queries
62 if decision.confidence > 0.7:
63 self.high_confidence_count += 1
64 elif decision.confidence < 0.3:
65 self.low_confidence_count += 1
67 def to_dict(self) -> dict:
68 """Convert statistics to dictionary.
70 Returns:
71 Dictionary representation of statistics.
72 """
73 return {
74 "total_queries": self.total_queries,
75 "by_strategy": self.by_strategy,
76 "by_data_source": self.by_data_source,
77 "avg_confidence": round(self.avg_confidence, 3),
78 "high_confidence_count": self.high_confidence_count,
79 "low_confidence_count": self.low_confidence_count,
80 "high_confidence_rate": round(
81 self.high_confidence_count / max(self.total_queries, 1),
82 3,
83 ),
84 }
87class QueryRouter:
88 """Main query router for RAG systems.
90 Orchestrates query analysis and routing to appropriate data sources
91 using configurable routing strategies.
93 Example:
94 ```python
95 from lexigram.ai.rag import (
96 QueryRouter,
97 DataSourceProtocol,
98 DataSourceType,
99 RuleBasedRouter
100 )
102 # Create router
103 router = QueryRouter(
104 analyzer=QueryAnalyzer(),
105 strategy=RuleBasedRouter.with_defaults()
106 )
108 # Register data sources
109 router.register_source(DataSourceProtocol(
110 name="docs_vector",
111 type=DataSourceType.VECTOR_STORE,
112 description="Documentation vector store",
113 capabilities=["dense_search", "semantic_search"],
114 priority=10
115 ))
117 # Route query
118 decision = await router.route("How do I configure authentication?")
119 logger.info(f"Route to: {decision.data_sources[0].name}")
120 logger.info(f"Strategy: {decision.strategy}")
121 logger.info(f"Confidence: {decision.confidence}")
123 # Get statistics
124 stats = router.get_statistics()
125 logger.info(f"Total queries: {stats.total_queries}")
126 logger.info(f"Avg confidence: {stats.avg_confidence}")
127 ```
128 """
130 def __init__(
131 self,
132 *,
133 analyzer: QueryAnalyzer | None = None,
134 strategy: RoutingStrategy | None = None,
135 ):
136 """Initialize the query router.
138 Args:
139 analyzer: Query analyzer for feature extraction.
140 strategy: Routing strategy to use.
141 """
142 self.analyzer = analyzer or QueryAnalyzer()
143 self.strategy = strategy or RuleBasedRouter.with_defaults()
144 self.data_sources: list[DataSource] = []
145 self.statistics = RoutingStatistics()
147 def register_source(self, source: DataSource) -> None:
148 """Register a data source.
150 Args:
151 source: Data source to register.
152 """
153 # Check if source already exists
154 existing = list(filter(lambda s: s.name == source.name, self.data_sources))
155 if existing:
156 # Update existing source
157 self.data_sources.remove(existing[0])
159 self.data_sources.append(source)
161 # Sort by priority (highest first)
162 self.data_sources.sort(key=lambda s: s.priority, reverse=True)
164 def unregister_source(self, name: str) -> bool:
165 """Unregister a data source.
167 Args:
168 name: Name of the data source to unregister.
170 Returns:
171 True if source was found and removed, False otherwise.
172 """
173 initial_count = len(self.data_sources)
174 self.data_sources = list(filter(lambda s: s.name != name, self.data_sources))
175 return len(self.data_sources) < initial_count
177 def get_source(self, name: str) -> DataSource | None:
178 """Get a data source by name.
180 Args:
181 name: Name of the data source.
183 Returns:
184 Data source if found, None otherwise.
185 """
186 for source in self.data_sources:
187 if source.name == name:
188 return source
189 return None
191 def list_sources(self) -> list[DataSource]:
192 """List all registered data sources.
194 Returns:
195 List of registered data sources.
196 """
197 return self.data_sources.copy()
199 async def route(
200 self,
201 query: str,
202 *,
203 features: QueryFeatures | None = None,
204 ) -> RoutingDecision:
205 """Route a query to appropriate data sources.
207 Args:
208 query: Query text to route.
209 features: Pre-extracted query features (optional).
211 Returns:
212 Routing decision with selected data sources and strategy.
213 """
214 # Analyze query if features not provided
215 if features is None:
216 features = await self.analyzer.analyze(query)
218 # Route using strategy
219 decision = await self.strategy.route(features, self.data_sources)
221 # Update statistics
222 self.statistics.update(decision)
224 return decision
226 async def route_batch(
227 self,
228 queries: list[str],
229 ) -> list[RoutingDecision]:
230 """Route multiple queries.
232 Args:
233 queries: List of query texts to route.
235 Returns:
236 List of routing decisions.
237 """
238 decisions = []
239 for query in queries:
240 decision = await self.route(query)
241 decisions.append(decision)
242 return decisions
244 def get_statistics(self) -> RoutingStatistics:
245 """Get routing statistics.
247 Returns:
248 Current routing statistics.
249 """
250 return self.statistics
252 def reset_statistics(self) -> None:
253 """Reset routing statistics."""
254 self.statistics = RoutingStatistics()
256 def set_strategy(self, strategy: RoutingStrategy) -> None:
257 """Set the routing strategy.
259 Args:
260 strategy: New routing strategy to use.
261 """
262 self.strategy = strategy
264 def set_analyzer(self, analyzer: QueryAnalyzer) -> None:
265 """Set the query analyzer.
267 Args:
268 analyzer: New query analyzer to use.
269 """
270 self.analyzer = analyzer