""" AI Router - Phase 13.3 #87 ========================== 動態模型選擇器,整合意圖分類和複雜度評分 目標: 根據請求特性自動選擇最適模型 策略: Intent + Complexity → Model Selection Phase 13.3 (2026-03-26): 初始實作 """ from dataclasses import dataclass import structlog from src.services.complexity_scorer import ( ComplexityScore, get_complexity_scorer, ) from src.services.intent_classifier import ( IntentType, get_intent_classifier, ) logger = structlog.get_logger(__name__) @dataclass class RoutingDecision: """路由決策結果""" model: str # 選擇的模型 intent: IntentType # 意圖分類 complexity: ComplexityScore # 複雜度評分 reason: str # 選擇原因 fallback_models: list[str] # 備援模型列表 class AIRouter: """ AI 路由器 整合 IntentClassifier 和 ComplexityScorer, 動態選擇最適合的模型。 路由策略: 1. 意圖優先覆寫 (某些意圖強制使用特定模型) 2. 複雜度導向選擇 3. 成本/延遲平衡 """ # 意圖強制覆寫 INTENT_OVERRIDES: dict[IntentType, str | None] = { IntentType.CODE_REVIEW: "qwen2.5:7b-instruct", # 程式碼審查需要強模型 IntentType.DEPLOYMENT: None, # 不覆寫,依複雜度 IntentType.ALERT_TRIAGE: None, IntentType.QUERY: "llama3.2:3b", # 查詢用快速模型 IntentType.MAINTENANCE: None, IntentType.UNKNOWN: None, } # Fallback 順序 FALLBACK_ORDER = [ "qwen2.5:7b-instruct", # 本地主力 "llama3.2:3b", # 本地備援 "gemini", # 雲端備援 "claude", # 最終備援 ] def __init__(self): self._intent_classifier = get_intent_classifier() self._complexity_scorer = get_complexity_scorer() async def route( self, text: str, context: dict | None = None, ) -> RoutingDecision: """ 路由請求到最適模型 Args: text: 用戶輸入或告警內容 context: 額外上下文 (服務、指標等) Returns: RoutingDecision: 路由決策 """ context = context or {} # Step 1: 意圖分類 intent = await self._intent_classifier.classify(text) # Step 2: 複雜度評分 complexity = self._complexity_scorer.score(context) # Step 3: 模型選擇 model, reason = self._select_model(intent, complexity) # Step 4: 建立 Fallback 列表 fallbacks = self._build_fallback_list(model) decision = RoutingDecision( model=model, intent=intent, complexity=complexity, reason=reason, fallback_models=fallbacks, ) logger.info( "ai_routing_decision", model=model, intent=intent.value, complexity_score=complexity.score, reason=reason, ) return decision def _select_model( self, intent: IntentType, complexity: ComplexityScore, ) -> tuple[str, str]: """ 選擇模型 Returns: (model_name, reason) """ # 檢查意圖覆寫 override = self.INTENT_OVERRIDES.get(intent) if override: return override, f"意圖 {intent.value} 強制使用 {override}" # 依複雜度選擇 model = complexity.recommended_model reason = f"複雜度 {complexity.score}/5 → {model}" # 特殊情況調整 if intent == IntentType.ALERT_TRIAGE and complexity.score >= 4: # 高複雜度告警優先用雲端 model = "gemini" reason = f"高複雜度告警 (score={complexity.score}) → 使用雲端模型" return model, reason def _build_fallback_list(self, selected_model: str) -> list[str]: """建立 Fallback 列表 (排除已選模型)""" fallbacks = [m for m in self.FALLBACK_ORDER if m != selected_model] return fallbacks def route_sync( self, text: str, context: dict | None = None, ) -> RoutingDecision: """同步版本 (僅關鍵字匹配)""" context = context or {} intent = self._intent_classifier.classify_sync(text) complexity = self._complexity_scorer.score(context) model, reason = self._select_model(intent, complexity) fallbacks = self._build_fallback_list(model) return RoutingDecision( model=model, intent=intent, complexity=complexity, reason=reason, fallback_models=fallbacks, ) # 單例 _router: AIRouter | None = None def get_ai_router() -> AIRouter: """取得 AIRouter 單例""" global _router if _router is None: _router = AIRouter() return _router