feat(api): Phase 13 智能路由 + CI/CD 整合 (#74-88)
Phase 13.1 CI/CD Integration: - #76 workflow_run handler for CI failure diagnosis - #77 SignOz log query (query_logs, error_logs_summary MCP) - #78 CIAutoRepairService with risk-based execution decisions Phase 13.3 Smart Routing: - #85 Intent Classifier v2.0 (rule engine + LLM fallback) - #86 Complexity Scorer (9-dimension scoring) - #87 AI Router v3.0 (routing decision matrix) - #88 Token Counter (OTEL + Langfuse integration) New files: - services/ci_auto_repair.py (risk stratification) - services/model_registry.py (centralized model config) - services/token_counter.py (677 lines) - Skill 08: Model Router Expert - Skill 09: Strangler Pattern Expert - ADR-023: Smart Routing Architecture - ADR-024: API Layer Architecture Tests: - phase11-conversational.spec.ts (E2E tests) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,15 +1,41 @@
|
||||
"""
|
||||
AI Router - Phase 13.3 #87
|
||||
==========================
|
||||
動態模型選擇器,整合意圖分類和複雜度評分
|
||||
智能 AI 路由器,根據意圖和複雜度動態選擇 AI Provider
|
||||
|
||||
目標: 根據請求特性自動選擇最適模型
|
||||
策略: Intent + Complexity → Model Selection
|
||||
策略: Intent Classifier + Complexity Scorer → Routing Decision
|
||||
延遲目標: < 50ms (規則引擎優先)
|
||||
|
||||
Phase 13.3 (2026-03-26): 初始實作
|
||||
路由決策矩陣 (ADR-023):
|
||||
┌─────────────────┬───────────────┬──────────────────────────────┐
|
||||
│ 複雜度 + 風險 │ Provider │ 備註 │
|
||||
├─────────────────┼───────────────┼──────────────────────────────┤
|
||||
│ 1-2 + LOW │ Ollama │ 快速本地處理 │
|
||||
│ 3 + MEDIUM │ Ollama │ fallback → Gemini │
|
||||
│ 4-5 + HIGH │ Gemini │ fallback → Claude │
|
||||
│ DELETE/CRITICAL │ Claude │ 強制使用最強模型 │
|
||||
└─────────────────┴───────────────┴──────────────────────────────┘
|
||||
|
||||
版本: v3.0
|
||||
建立: 2026-03-26 (台北時區)
|
||||
建立者: Claude Code
|
||||
最後修改: 2026-03-26 (台北時區)
|
||||
修改者: Claude Code
|
||||
|
||||
變更紀錄:
|
||||
| 版本 | 日期 | 執行者 | 變更內容 |
|
||||
|------|------|--------|----------|
|
||||
| v1.0 | 2026-03-26 | Claude Code | 初始實作 |
|
||||
| v2.0 | 2026-03-26 | Claude Code | 支援 IntentResult + 新意圖類型 |
|
||||
| v3.0 | 2026-03-26 | Claude Code | Phase 13.3 #87 完整路由決策矩陣 |
|
||||
"""
|
||||
|
||||
from dataclasses import dataclass
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from dataclasses import dataclass, field
|
||||
from enum import Enum
|
||||
|
||||
import structlog
|
||||
|
||||
@@ -18,58 +44,169 @@ from src.services.complexity_scorer import (
|
||||
get_complexity_scorer,
|
||||
)
|
||||
from src.services.intent_classifier import (
|
||||
IntentResult,
|
||||
IntentType,
|
||||
RiskLevel,
|
||||
get_intent_classifier,
|
||||
normalize_intent,
|
||||
)
|
||||
from src.services.model_registry import get_model_registry
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Provider 定義
|
||||
# =============================================================================
|
||||
|
||||
|
||||
class AIProvider(Enum):
|
||||
"""AI 提供者"""
|
||||
|
||||
OLLAMA = "ollama"
|
||||
GEMINI = "gemini"
|
||||
CLAUDE = "claude"
|
||||
|
||||
|
||||
# Provider 對應延遲預算 (ms)
|
||||
PROVIDER_LATENCY_BUDGET: dict[AIProvider, int] = {
|
||||
AIProvider.OLLAMA: 60000, # 本地,允許較長處理時間
|
||||
AIProvider.GEMINI: 30000, # 雲端,較低延遲
|
||||
AIProvider.CLAUDE: 30000, # 雲端,較低延遲
|
||||
}
|
||||
|
||||
|
||||
@dataclass
|
||||
class RoutingDecision:
|
||||
"""路由決策結果"""
|
||||
"""
|
||||
路由決策結果 (Phase 13.3 #87)
|
||||
|
||||
model: str # 選擇的模型
|
||||
intent: IntentType # 意圖分類
|
||||
包含完整的路由資訊,供 OpenClaw 主流程使用
|
||||
"""
|
||||
|
||||
# 核心決策
|
||||
selected_provider: AIProvider # 選擇的 AI Provider
|
||||
selected_model: str # 選擇的模型名稱
|
||||
fallback_chain: list[tuple[AIProvider, str]] # 備援鏈 [(provider, model), ...]
|
||||
routing_reason: str # 路由決策原因
|
||||
latency_budget_ms: int # 延遲預算 (毫秒)
|
||||
|
||||
# 分類結果
|
||||
intent: IntentType # 意圖分類 (正規化後)
|
||||
intent_result: IntentResult # 完整 Intent 分類結果
|
||||
complexity: ComplexityScore # 複雜度評分
|
||||
reason: str # 選擇原因
|
||||
fallback_models: list[str] # 備援模型列表
|
||||
risk_level: RiskLevel = field(default=RiskLevel.MEDIUM) # 風險等級
|
||||
|
||||
# 路由 metadata
|
||||
routing_latency_ms: float = 0.0 # 路由決策耗時 (ms)
|
||||
|
||||
# 向後相容 (deprecated)
|
||||
model: str = "" # -> selected_model
|
||||
reason: str = "" # -> routing_reason
|
||||
fallback_models: list[str] = field(default_factory=list) # -> fallback_chain
|
||||
|
||||
def __post_init__(self):
|
||||
"""初始化後設定衍生欄位"""
|
||||
self.risk_level = self.intent_result.risk_level
|
||||
# 向後相容
|
||||
self.model = self.selected_model
|
||||
self.reason = self.routing_reason
|
||||
self.fallback_models = [model for _, model in self.fallback_chain]
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""轉換為字典 (API 回應用)"""
|
||||
return {
|
||||
"selected_provider": self.selected_provider.value,
|
||||
"selected_model": self.selected_model,
|
||||
"fallback_chain": [
|
||||
{"provider": p.value, "model": m} for p, m in self.fallback_chain
|
||||
],
|
||||
"routing_reason": self.routing_reason,
|
||||
"latency_budget_ms": self.latency_budget_ms,
|
||||
"intent": self.intent.value,
|
||||
"risk_level": self.risk_level.value,
|
||||
"complexity_score": self.complexity.score,
|
||||
"routing_latency_ms": round(self.routing_latency_ms, 2),
|
||||
}
|
||||
|
||||
|
||||
class AIRouter:
|
||||
"""
|
||||
AI 路由器
|
||||
AI 路由器 (Phase 13.3 #87)
|
||||
|
||||
整合 IntentClassifier 和 ComplexityScorer,
|
||||
動態選擇最適合的模型。
|
||||
動態選擇最適合的 AI Provider 和模型。
|
||||
|
||||
路由策略:
|
||||
1. 意圖優先覆寫 (某些意圖強制使用特定模型)
|
||||
2. 複雜度導向選擇
|
||||
3. 成本/延遲平衡
|
||||
路由決策矩陣:
|
||||
┌─────────────────┬───────────────┬──────────────────────────────┐
|
||||
│ 複雜度 + 風險 │ Provider │ 備註 │
|
||||
├─────────────────┼───────────────┼──────────────────────────────┤
|
||||
│ 1-2 + LOW │ Ollama │ 快速本地處理 │
|
||||
│ 3 + MEDIUM │ Ollama │ fallback → Gemini │
|
||||
│ 4-5 + HIGH │ Gemini │ fallback → Claude │
|
||||
│ DELETE/CRITICAL │ Claude │ 強制使用最強模型 │
|
||||
└─────────────────┴───────────────┴──────────────────────────────┘
|
||||
|
||||
路由策略 (按優先級):
|
||||
1. CRITICAL 風險強制使用 Claude
|
||||
2. DELETE 意圖強制使用 Claude
|
||||
3. HIGH 風險或複雜度 4-5 → Gemini
|
||||
4. 其他情況 → Ollama (成本優先)
|
||||
"""
|
||||
|
||||
# 意圖強制覆寫
|
||||
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()
|
||||
self._model_registry = get_model_registry()
|
||||
|
||||
# 從 ModelRegistry 取得模型配置
|
||||
self._ollama_default = self._model_registry.get_model("ollama", "default")
|
||||
self._ollama_summary = self._model_registry.get_model("ollama", "summary")
|
||||
self._gemini_default = self._model_registry.get_model("gemini", "default")
|
||||
self._claude_default = self._model_registry.get_model("claude", "default")
|
||||
|
||||
# Provider 對應模型映射
|
||||
self._provider_models: dict[AIProvider, str] = {
|
||||
AIProvider.OLLAMA: self._ollama_default,
|
||||
AIProvider.GEMINI: self._gemini_default,
|
||||
AIProvider.CLAUDE: self._claude_default,
|
||||
}
|
||||
|
||||
# 完整 Fallback 鏈 (Provider, Model)
|
||||
self._full_fallback_chain: list[tuple[AIProvider, str]] = [
|
||||
(AIProvider.OLLAMA, self._ollama_default),
|
||||
(AIProvider.GEMINI, self._gemini_default),
|
||||
(AIProvider.CLAUDE, self._claude_default),
|
||||
]
|
||||
|
||||
# 意圖對應 Provider 強制覆寫 (None = 依複雜度決定)
|
||||
self._intent_provider_overrides: dict[IntentType, AIProvider | None] = {
|
||||
# 四大核心意圖
|
||||
IntentType.RESTART: None, # 依複雜度
|
||||
IntentType.SCALE: None, # 依複雜度
|
||||
IntentType.CONFIG: None, # 依複雜度 (但 HIGH 會升級)
|
||||
IntentType.DIAGNOSE: AIProvider.OLLAMA, # 診斷優先本地 (隱私)
|
||||
# 輔助意圖
|
||||
IntentType.DELETE: AIProvider.CLAUDE, # CRITICAL → 強制 Claude
|
||||
IntentType.ROLLBACK: None, # 依複雜度
|
||||
IntentType.UNKNOWN: None,
|
||||
# 舊版兼容
|
||||
IntentType.CODE_REVIEW: None,
|
||||
IntentType.DEPLOYMENT: None,
|
||||
IntentType.ALERT_TRIAGE: AIProvider.OLLAMA,
|
||||
IntentType.QUERY: AIProvider.OLLAMA,
|
||||
IntentType.MAINTENANCE: None,
|
||||
}
|
||||
|
||||
# 向後相容
|
||||
self._default_model = self._ollama_default
|
||||
self._summary_model = self._ollama_summary
|
||||
self._fallback_order = [
|
||||
self._ollama_default,
|
||||
self._ollama_summary,
|
||||
"gemini",
|
||||
"claude",
|
||||
]
|
||||
|
||||
async def route(
|
||||
self,
|
||||
@@ -77,78 +214,203 @@ class AIRouter:
|
||||
context: dict | None = None,
|
||||
) -> RoutingDecision:
|
||||
"""
|
||||
路由請求到最適模型
|
||||
路由請求到最適 AI Provider 和模型
|
||||
|
||||
延遲目標: < 50ms (規則引擎優先,LLM 分類時可能稍長)
|
||||
|
||||
Args:
|
||||
text: 用戶輸入或告警內容
|
||||
context: 額外上下文 (服務、指標等)
|
||||
|
||||
Returns:
|
||||
RoutingDecision: 路由決策
|
||||
RoutingDecision: 完整路由決策
|
||||
"""
|
||||
start_time = time.perf_counter()
|
||||
context = context or {}
|
||||
|
||||
# Step 1: 意圖分類
|
||||
intent = await self._intent_classifier.classify(text)
|
||||
# Step 1: 意圖分類 (返回 IntentResult, 規則引擎 < 10ms)
|
||||
intent_result = await self._intent_classifier.classify(text)
|
||||
intent = normalize_intent(intent_result.intent)
|
||||
|
||||
# Step 2: 複雜度評分
|
||||
# Step 2: 複雜度評分 (< 10ms)
|
||||
complexity = self._complexity_scorer.score(context)
|
||||
|
||||
# Step 3: 模型選擇
|
||||
model, reason = self._select_model(intent, complexity)
|
||||
# Step 3: Provider + Model 選擇 (< 1ms)
|
||||
provider, model, reason = self._select_provider_and_model(
|
||||
intent, intent_result, complexity
|
||||
)
|
||||
|
||||
# Step 4: 建立 Fallback 列表
|
||||
fallbacks = self._build_fallback_list(model)
|
||||
# Step 4: 建立 Fallback 鏈
|
||||
fallback_chain = self._build_fallback_chain(provider)
|
||||
|
||||
# Step 5: 計算延遲預算
|
||||
latency_budget = PROVIDER_LATENCY_BUDGET.get(provider, 30000)
|
||||
|
||||
# 計算路由決策耗時
|
||||
routing_latency = (time.perf_counter() - start_time) * 1000
|
||||
|
||||
decision = RoutingDecision(
|
||||
model=model,
|
||||
selected_provider=provider,
|
||||
selected_model=model,
|
||||
fallback_chain=fallback_chain,
|
||||
routing_reason=reason,
|
||||
latency_budget_ms=latency_budget,
|
||||
intent=intent,
|
||||
intent_result=intent_result,
|
||||
complexity=complexity,
|
||||
reason=reason,
|
||||
fallback_models=fallbacks,
|
||||
routing_latency_ms=routing_latency,
|
||||
)
|
||||
|
||||
logger.info(
|
||||
"ai_routing_decision",
|
||||
provider=provider.value,
|
||||
model=model,
|
||||
intent=intent.value,
|
||||
intent_confidence=intent_result.confidence,
|
||||
risk_level=intent_result.risk_level.value,
|
||||
complexity_score=complexity.score,
|
||||
reason=reason,
|
||||
latency_budget_ms=latency_budget,
|
||||
routing_latency_ms=round(routing_latency, 2),
|
||||
fallback_count=len(fallback_chain),
|
||||
)
|
||||
|
||||
return decision
|
||||
|
||||
def _select_provider_and_model(
|
||||
self,
|
||||
intent: IntentType,
|
||||
intent_result: IntentResult,
|
||||
complexity: ComplexityScore,
|
||||
) -> tuple[AIProvider, str, str]:
|
||||
"""
|
||||
選擇 Provider 和模型 (Phase 13.3 #87 核心邏輯)
|
||||
|
||||
路由決策矩陣:
|
||||
┌─────────────────┬───────────────┬──────────────────────────────┐
|
||||
│ 複雜度 + 風險 │ Provider │ 備註 │
|
||||
├─────────────────┼───────────────┼──────────────────────────────┤
|
||||
│ 1-2 + LOW │ Ollama │ 快速本地處理 │
|
||||
│ 3 + MEDIUM │ Ollama │ fallback → Gemini │
|
||||
│ 4-5 + HIGH │ Gemini │ fallback → Claude │
|
||||
│ DELETE/CRITICAL │ Claude │ 強制使用最強模型 │
|
||||
└─────────────────┴───────────────┴──────────────────────────────┘
|
||||
|
||||
Args:
|
||||
intent: 正規化後的意圖
|
||||
intent_result: 完整分類結果
|
||||
complexity: 複雜度評分
|
||||
|
||||
Returns:
|
||||
(provider, model, reason)
|
||||
"""
|
||||
risk = intent_result.risk_level
|
||||
score = complexity.score
|
||||
|
||||
# =======================================================================
|
||||
# 規則 1: CRITICAL 風險強制 Claude (最高優先級)
|
||||
# =======================================================================
|
||||
if risk == RiskLevel.CRITICAL:
|
||||
provider = AIProvider.CLAUDE
|
||||
model = self._claude_default
|
||||
reason = f"CRITICAL 風險 ({intent.value}) 強制使用 Claude"
|
||||
return provider, model, reason
|
||||
|
||||
# =======================================================================
|
||||
# 規則 2: DELETE 意圖強制 Claude (不可逆操作)
|
||||
# =======================================================================
|
||||
if intent == IntentType.DELETE:
|
||||
provider = AIProvider.CLAUDE
|
||||
model = self._claude_default
|
||||
reason = "DELETE 意圖 (不可逆) 強制使用 Claude"
|
||||
return provider, model, reason
|
||||
|
||||
# =======================================================================
|
||||
# 規則 3: 檢查意圖強制覆寫
|
||||
# =======================================================================
|
||||
provider_override = self._intent_provider_overrides.get(intent)
|
||||
if provider_override is not None:
|
||||
provider = provider_override
|
||||
model = self._provider_models[provider]
|
||||
reason = f"意圖 {intent.value} 指定使用 {provider.value}"
|
||||
return provider, model, reason
|
||||
|
||||
# =======================================================================
|
||||
# 規則 4: 複雜度 4-5 或 HIGH 風險 → Gemini
|
||||
# =======================================================================
|
||||
if score >= 4 or risk == RiskLevel.HIGH:
|
||||
provider = AIProvider.GEMINI
|
||||
model = self._gemini_default
|
||||
reason = f"複雜度={score}/5, 風險={risk.value} → Gemini (fallback Claude)"
|
||||
return provider, model, reason
|
||||
|
||||
# =======================================================================
|
||||
# 規則 5: 複雜度 3 + MEDIUM → Ollama (fallback Gemini)
|
||||
# =======================================================================
|
||||
if score == 3:
|
||||
provider = AIProvider.OLLAMA
|
||||
model = self._ollama_default
|
||||
reason = f"複雜度={score}/5, 風險={risk.value} → Ollama (fallback Gemini)"
|
||||
return provider, model, reason
|
||||
|
||||
# =======================================================================
|
||||
# 規則 6: 複雜度 1-2 + LOW/MEDIUM → Ollama (快速本地處理)
|
||||
# =======================================================================
|
||||
provider = AIProvider.OLLAMA
|
||||
# 低複雜度使用輕量模型 (更快回應)
|
||||
model = self._ollama_summary if score <= 1 else self._ollama_default
|
||||
reason = f"複雜度={score}/5, 風險={risk.value} → Ollama (成本優先)"
|
||||
return provider, model, reason
|
||||
|
||||
def _select_model(
|
||||
self,
|
||||
intent: IntentType,
|
||||
intent_result: IntentResult,
|
||||
complexity: ComplexityScore,
|
||||
) -> tuple[str, str]:
|
||||
"""
|
||||
選擇模型
|
||||
選擇模型 (向後相容方法)
|
||||
|
||||
Deprecated: 請使用 _select_provider_and_model
|
||||
|
||||
Args:
|
||||
intent: 正規化後的意圖
|
||||
intent_result: 完整分類結果
|
||||
complexity: 複雜度評分
|
||||
|
||||
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}) → 使用雲端模型"
|
||||
|
||||
_, model, reason = self._select_provider_and_model(
|
||||
intent, intent_result, complexity
|
||||
)
|
||||
return model, reason
|
||||
|
||||
def _build_fallback_chain(
|
||||
self, selected_provider: AIProvider
|
||||
) -> list[tuple[AIProvider, str]]:
|
||||
"""
|
||||
建立 Fallback 鏈 (排除已選 Provider)
|
||||
|
||||
Fallback 順序: Ollama → Gemini → Claude
|
||||
|
||||
Args:
|
||||
selected_provider: 已選擇的 Provider
|
||||
|
||||
Returns:
|
||||
Fallback 鏈 [(provider, model), ...]
|
||||
"""
|
||||
fallback_chain: list[tuple[AIProvider, str]] = []
|
||||
|
||||
for provider, model in self._full_fallback_chain:
|
||||
if provider != selected_provider:
|
||||
fallback_chain.append((provider, model))
|
||||
|
||||
return fallback_chain
|
||||
|
||||
def _build_fallback_list(self, selected_model: str) -> list[str]:
|
||||
"""建立 Fallback 列表 (排除已選模型)"""
|
||||
fallbacks = [m for m in self.FALLBACK_ORDER if m != selected_model]
|
||||
"""建立 Fallback 列表 (向後相容)"""
|
||||
fallbacks = [m for m in self._fallback_order if m != selected_model]
|
||||
return fallbacks
|
||||
|
||||
def route_sync(
|
||||
@@ -156,22 +418,113 @@ class AIRouter:
|
||||
text: str,
|
||||
context: dict | None = None,
|
||||
) -> RoutingDecision:
|
||||
"""同步版本 (僅關鍵字匹配)"""
|
||||
"""
|
||||
同步版本路由 (僅關鍵字匹配,保證 < 50ms)
|
||||
|
||||
適用場景: 需要快速決策,不需要 LLM 分類的情況
|
||||
|
||||
Args:
|
||||
text: 用戶輸入或告警內容
|
||||
context: 額外上下文
|
||||
|
||||
Returns:
|
||||
RoutingDecision: 路由決策
|
||||
"""
|
||||
start_time = time.perf_counter()
|
||||
context = context or {}
|
||||
|
||||
intent = self._intent_classifier.classify_sync(text)
|
||||
# 同步分類 (僅規則引擎, < 10ms)
|
||||
intent_result = self._intent_classifier.classify_sync(text)
|
||||
intent = normalize_intent(intent_result.intent)
|
||||
|
||||
# 複雜度評分 (< 10ms)
|
||||
complexity = self._complexity_scorer.score(context)
|
||||
model, reason = self._select_model(intent, complexity)
|
||||
fallbacks = self._build_fallback_list(model)
|
||||
|
||||
# Provider + Model 選擇
|
||||
provider, model, reason = self._select_provider_and_model(
|
||||
intent, intent_result, complexity
|
||||
)
|
||||
|
||||
# 建立 Fallback 鏈
|
||||
fallback_chain = self._build_fallback_chain(provider)
|
||||
|
||||
# 延遲預算
|
||||
latency_budget = PROVIDER_LATENCY_BUDGET.get(provider, 30000)
|
||||
|
||||
# 計算路由決策耗時
|
||||
routing_latency = (time.perf_counter() - start_time) * 1000
|
||||
|
||||
return RoutingDecision(
|
||||
model=model,
|
||||
selected_provider=provider,
|
||||
selected_model=model,
|
||||
fallback_chain=fallback_chain,
|
||||
routing_reason=reason,
|
||||
latency_budget_ms=latency_budget,
|
||||
intent=intent,
|
||||
intent_result=intent_result,
|
||||
complexity=complexity,
|
||||
reason=reason,
|
||||
fallback_models=fallbacks,
|
||||
routing_latency_ms=routing_latency,
|
||||
)
|
||||
|
||||
# =========================================================================
|
||||
# 便捷方法
|
||||
# =========================================================================
|
||||
|
||||
def get_provider_for_intent(self, intent: IntentType) -> AIProvider:
|
||||
"""取得意圖對應的 Provider (不考慮複雜度)"""
|
||||
override = self._intent_provider_overrides.get(intent)
|
||||
return override if override else AIProvider.OLLAMA
|
||||
|
||||
def get_model_for_provider(self, provider: AIProvider) -> str:
|
||||
"""取得 Provider 對應的模型"""
|
||||
return self._provider_models.get(provider, self._ollama_default)
|
||||
|
||||
def get_routing_matrix(self) -> list[dict]:
|
||||
"""
|
||||
取得路由決策矩陣 (用於 API 文檔或除錯)
|
||||
|
||||
Returns:
|
||||
路由規則清單
|
||||
"""
|
||||
return [
|
||||
{
|
||||
"rule": 1,
|
||||
"condition": "CRITICAL risk",
|
||||
"provider": "claude",
|
||||
"reason": "不可逆/高風險操作強制最強模型",
|
||||
},
|
||||
{
|
||||
"rule": 2,
|
||||
"condition": "DELETE intent",
|
||||
"provider": "claude",
|
||||
"reason": "刪除操作強制最強模型",
|
||||
},
|
||||
{
|
||||
"rule": 3,
|
||||
"condition": "Intent override",
|
||||
"provider": "depends",
|
||||
"reason": "特定意圖有預設 Provider",
|
||||
},
|
||||
{
|
||||
"rule": 4,
|
||||
"condition": "complexity >= 4 OR HIGH risk",
|
||||
"provider": "gemini",
|
||||
"reason": "高複雜度需要雲端能力",
|
||||
},
|
||||
{
|
||||
"rule": 5,
|
||||
"condition": "complexity == 3",
|
||||
"provider": "ollama",
|
||||
"reason": "中等複雜度本地處理",
|
||||
},
|
||||
{
|
||||
"rule": 6,
|
||||
"condition": "complexity 1-2",
|
||||
"provider": "ollama",
|
||||
"reason": "低複雜度快速處理",
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# 單例
|
||||
_router: AIRouter | None = None
|
||||
@@ -183,3 +536,9 @@ def get_ai_router() -> AIRouter:
|
||||
if _router is None:
|
||||
_router = AIRouter()
|
||||
return _router
|
||||
|
||||
|
||||
def reset_ai_router() -> None:
|
||||
"""重置單例 (用於測試)"""
|
||||
global _router
|
||||
_router = None
|
||||
|
||||
Reference in New Issue
Block a user