- IntentClassifier: 意圖分類 (告警/部署/查詢/維運/審查) - ComplexityScorer: 複雜度評分 (1-5 分) - AIRouter: 動態模型選擇 (整合 Intent + Complexity) - 測試: 完整單元測試覆蓋 Phase 13.3 設計: project_phase13_3_smart_router.md Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
186 lines
4.8 KiB
Python
186 lines
4.8 KiB
Python
"""
|
||
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
|