Files
awoooi/docs/adr/ADR-023-smart-routing-architecture.md
OG T 579da38b8b 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>
2026-03-26 15:32:52 +08:00

653 lines
28 KiB
Markdown
Raw 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.
# ADR-023: Phase 13.3 智能路由架構
> **狀態**: 已接受
> **日期**: 2026-03-26
> **決策者**: CTO, CEO
> **相關**: ADR-006 (AI Fallback Strategy), ADR-016 (Smart Routing)
> **Phase**: 13.3
---
## 1. 背景與問題
### 問題描述
ADR-006 建立了固定順序的 AI 備援策略 (Ollama → Gemini → Claude)ADR-016 引入了智能路由概念。然而,隨著 AWOOOI 從「告警響應」升級為「全方位 AIOps 平台」(Phase 13),需要更完整的架構設計:
1. **意圖分類不夠精細**: 缺少針對 K8s 操作的專屬意圖類型
2. **複雜度評估不完整**: 缺少跨系統、有狀態資源的風險評估
3. **Token 用量無追蹤**: 無法掌握成本分佈與趨勢
4. **配置分散**: 模型選擇邏輯散落各處,難以維護
### 目標
- 建立完整的 **Intent → Complexity → Model** 決策流程
- 定義 K8s 專屬意圖 (RESTART/SCALE/CONFIG/DIAGNOSE)
- 建立 1-5 分複雜度評分系統
- 整合 Token 用量監控 (SignOz + Langfuse)
---
## 2. 決策: Intent Classifier + Complexity Scorer + AI Router
### 核心策略
```
Intent (意圖) + Complexity (複雜度) + Context (上下文) → Model Selection (模型選擇)
```
### 三元件架構
```
┌──────────────────────────────────────────────────────────────────┐
│ Phase 13.3 Smart Router │
├──────────────────────────────────────────────────────────────────┤
│ │
│ Request / Alert / Webhook │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Intent Classifier (意圖分類器) │ │
│ │ ├── 關鍵字匹配 (< 1ms) │ │
│ │ └── LLM 備援 (qwen2.5:1b, < 100ms) │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Complexity Scorer (複雜度評分器) │ │
│ │ ├── 服務數量 / 指標數量 │ │
│ │ ├── 跨系統判斷 / 有狀態風險 │ │
│ │ └── 歷史案例匹配 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ AI Router (智能路由器) │ │
│ │ ├── 意圖覆寫規則 │ │
│ │ ├── 複雜度 → 模型映射 │ │
│ │ └── Circuit Breaker + Fallback │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌────┴────────────────────┬───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌─────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Ollama │ │ Gemini │ │ Claude │ │
│ │ (Local) │ │ (Cloud) │ │ (Cloud) │ │
│ │ $0 │ │ $0.001/1K │ │ $0.008/1K │ │
│ └─────────┘ └─────────────┘ └─────────────┘ │
│ │
├──────────────────────────────────────────────────────────────────┤
│ Token Usage Monitor (SignOz + Langfuse) │
│ └── llm.tokens.* / llm.cost.* / trace.generation() │
└──────────────────────────────────────────────────────────────────┘
```
---
## 3. 架構圖
### 完整請求流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ AWOOOI Phase 13.3 │
│ Smart Routing Architecture │
└─────────────────────────────────────────────────────────────────────────┘
┌────────────────────┐
│ Alert / Request │
│ (Telegram/API) │
└─────────┬──────────┘
┌─────────────────────┐
│ Intent Classifier │
│ 目標: < 100ms │
└─────────┬───────────┘
┌─────────────────────┼─────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌───────────┐ ┌────────────┐
│ RESTART │ │ SCALE │ │ CONFIG │
│ 重啟類 │ │ 擴縮容 │ │ 配置變更 │
└────┬────┘ └─────┬─────┘ └─────┬──────┘
│ │ │
└─────────┬──────────┴─────────────────────┘
┌─────────────────────┐
│ Complexity Scorer │
│ 輸出: 1-5 分 │
└─────────┬───────────┘
┌─────────────┼─────────────┬─────────────┬─────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐
│ 1 │ │ 2 │ │ 3 │ │ 4 │ │ 5 │
│簡單 │ │低風險│ │中等 │ │高複雜│ │極複雜│
└──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘ └──┬──┘
│ │ │ │ │
└────────────┴─────┬──────┴────────────┴────────────┘
┌─────────────────┐
│ AI Router │
│ 模型選擇 │
└────────┬────────┘
┌────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Ollama │ │ Gemini │ │ Claude │
│ llama3.2 │ │ gemini-1.5 │ │ claude-3.5 │
│ qwen2.5 │ │ │ │ │
└────┬─────┘ └──────┬───────┘ └──────┬───────┘
│ │ │
└─────────────────┼───────────────────┘
┌─────────────────┐
│ Token Monitor │
│ SignOz/Langfuse│
└─────────────────┘
```
---
## 4. 意圖分類 (Intent Classification)
### 四大核心意圖
| IntentType | 說明 | 典型場景 | 關鍵字 |
|------------|------|----------|--------|
| `RESTART` | 重啟 Pod/Deployment/StatefulSet | Pod CrashLoopBackOff、服務無回應 | restart, 重啟, 重新啟動, rollout restart |
| `SCALE` | 擴縮容、HPA 調整 | CPU 高負載、流量激增 | scale, 擴容, 縮容, replicas, hpa |
| `CONFIG` | ConfigMap/Secret/ENV 變更 | 配置錯誤、環境變數缺失 | config, 配置, configmap, secret, env |
| `DIAGNOSE` | 日誌查詢、健康檢查、RCA | 錯誤追蹤、根因分析 | diagnose, 診斷, log, describe, rca |
### 輔助意圖
| IntentType | 說明 | 典型場景 |
|------------|------|----------|
| `DEPLOY` | 部署操作 | kubectl apply, helm upgrade |
| `ROLLBACK` | 回滾操作 | rollout undo, 版本回滾 |
| `QUERY` | 資訊查詢 | 狀態查詢、資源列表 |
| `CODE_REVIEW` | 程式碼審查 | PR Review, Commit 分析 |
| `ALERT_TRIAGE` | 告警分流 | 高負載告警、OOM、服務 Down |
| `UNKNOWN` | 未知意圖 | 無法分類的請求 |
### 分類策略 (兩階段)
```python
class IntentClassifier:
"""
意圖分類器 - 兩階段策略
階段 1: 關鍵字匹配 (< 1ms)
階段 2: LLM 備援 (qwen2.5:1b, < 100ms)
"""
async def classify(self, text: str) -> IntentType:
# 階段 1: 關鍵字快速匹配
intent = self._keyword_match(text.lower())
if intent != IntentType.UNKNOWN:
return intent
# 階段 2: LLM 分類 (備援)
return await self._llm_classify(text)
def _keyword_match(self, text: str) -> IntentType:
"""
關鍵字映射 (優先級: 越上面越優先)
"""
INTENT_KEYWORDS = {
IntentType.RESTART: [
"restart", "重啟", "重新啟動", "rollout restart",
"kill", "recreate", "delete pod",
],
IntentType.SCALE: [
"scale", "擴容", "縮容", "replicas", "hpa",
"autoscale", "capacity", "節點",
],
IntentType.CONFIG: [
"config", "配置", "configmap", "secret", "env",
"環境變數", "yaml", "設定",
],
IntentType.DIAGNOSE: [
"diagnose", "診斷", "log", "describe", "rca",
"root cause", "根因", "排查", "debug", "trace",
],
# ... 其他意圖
}
for intent, keywords in INTENT_KEYWORDS.items():
if any(kw in text for kw in keywords):
return intent
return IntentType.UNKNOWN
```
---
## 5. 複雜度評分 (Complexity Scoring)
### 評分維度與權重
| 維度 | 權重 | 說明 |
|------|------|------|
| `service_count` | +0.5/服務 | 每增加一個受影響服務 |
| `metric_count` | +0.3/指標 | 每增加一個相關指標 |
| `cross_namespace` | +1.0 | 跨命名空間操作 |
| `cross_cluster` | +2.0 | 跨叢集操作 |
| `stateful_resource` | +1.0 | 有狀態資源 (StatefulSet, PVC) |
| `database_operation` | +1.5 | 涉及資料庫操作 |
| `critical_severity` | +1.0 | CRITICAL 嚴重程度 |
| `has_playbook` | -0.5 | 有歷史 Playbook (降低複雜度) |
| `requires_multisig` | +1.0 | 需要 Multi-Sig 審核 |
### 複雜度等級定義
| 分數 | 等級 | 定義 | 範例 |
|------|------|------|------|
| **1** | 簡單 | 單一資源、無狀態、可立即回滾 | 重啟單一 Pod |
| **2** | 低風險 | 多資源但同命名空間、低風險 | 擴容 Deployment |
| **3** | 中等 | 跨命名空間、需要上下文收集 | 多服務診斷 |
| **4** | 高複雜 | 有狀態資源、需要 Multi-Sig | StatefulSet 操作 |
| **5** | 極複雜 | 跨叢集、資料庫操作、需要人工審核 | 資料庫 Schema 變更 |
### 評分邏輯
```python
class ComplexityScorer:
"""
複雜度評分器 - 純規則引擎 (< 10ms)
"""
def score(self, context: dict) -> ComplexityScore:
score = 1.0 # 基礎分
# 服務數量
service_count = len(context.get("affected_services", []))
score += service_count * 0.5
# 指標數量
metric_count = len(context.get("metrics", []))
score += metric_count * 0.3
# 跨命名空間
if context.get("cross_namespace"):
score += 1.0
# 跨叢集
if context.get("cross_cluster"):
score += 2.0
# 有狀態資源
if context.get("stateful_resource"):
score += 1.0
# 資料庫操作
if context.get("database_operation"):
score += 1.5
# CRITICAL 嚴重程度
if context.get("severity") == "CRITICAL":
score += 1.0
# 歷史 Playbook (降低)
if context.get("has_playbook"):
score -= 0.5
# Multi-Sig 需求
if context.get("requires_multisig"):
score += 1.0
# 限制範圍 1-5
final_score = min(5, max(1, round(score)))
return ComplexityScore(
score=final_score,
factors=self._extract_factors(context),
)
```
---
## 6. Provider 選擇邏輯
### 複雜度 → 模型映射
| 複雜度 | 主要模型 | Fallback 順序 | 理由 |
|--------|----------|---------------|------|
| **1** | `llama3.2:3b` | qwen2.5:7b → gemini → claude | 快速回應,資源節省 |
| **2** | `qwen2.5:7b-instruct` | llama3.2:3b → gemini → claude | 平衡品質與延遲 |
| **3** | `qwen2.5:7b-instruct` | gemini → claude | 需要較強推理能力 |
| **4** | `gemini` | claude → qwen2.5:7b | 需要雲端能力 |
| **5** | `claude` | gemini → qwen2.5:7b | 最強模型處理 |
### 意圖強制覆寫
某些意圖無論複雜度如何,都強制使用特定模型:
| 意圖 | 強制模型 | 原因 |
|------|---------|------|
| `DIAGNOSE` | `qwen2.5:7b-instruct` (本地) | 日誌可能含敏感資料,禁止送雲端 |
| `CODE_REVIEW` | `qwen2.5:7b-instruct` | 程式碼分析需要較強能力 |
| `QUERY` | `llama3.2:3b` | 簡單查詢不需大模型 |
### 路由決策流程
```python
class AIRouter:
"""
智能路由器 - 動態模型選擇
"""
COMPLEXITY_ROUTING = {
1: "llama3.2:3b",
2: "qwen2.5:7b-instruct",
3: "qwen2.5:7b-instruct",
4: "gemini",
5: "claude",
}
INTENT_OVERRIDES = {
IntentType.DIAGNOSE: "qwen2.5:7b-instruct", # 隱私優先
IntentType.CODE_REVIEW: "qwen2.5:7b-instruct",
IntentType.QUERY: "llama3.2:3b",
}
async def route(
self, text: str, context: dict | None = None
) -> RoutingDecision:
# Step 1: 意圖分類
intent = await self._intent_classifier.classify(text)
# Step 2: 複雜度評分
complexity = self._complexity_scorer.score(context or {})
# Step 3: 模型選擇 (考慮意圖覆寫)
if intent in self.INTENT_OVERRIDES:
model = self.INTENT_OVERRIDES[intent]
reason = f"意圖 {intent.value} 強制使用 {model}"
else:
model = self.COMPLEXITY_ROUTING[complexity.score]
reason = f"複雜度 {complexity.score}{model}"
# Step 4: 建立 Fallback 列表
fallbacks = self._build_fallback_list(model)
return RoutingDecision(
model=model,
intent=intent,
complexity=complexity,
reason=reason,
fallback_models=fallbacks,
)
def _build_fallback_list(self, primary: str) -> list[str]:
"""
建立 Fallback 順序 (ADR-006)
"""
FALLBACK_ORDER = [
"qwen2.5:7b-instruct",
"llama3.2:3b",
"gemini",
"claude",
]
return [m for m in FALLBACK_ORDER if m != primary]
```
---
## 7. Token 用量監控
### 監控架構
```
┌─────────────────────────────────────────────────────────────────┐
│ Token Usage Monitoring │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ SignOz │ │ Langfuse │ │
│ │ (Infra 層) │ │ (LLMOps 層) │ │
│ └────────┬────────┘ └────────┬────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ OTEL Metrics │ │ Trace/Generation│ │
│ │ - llm.tokens.* │ │ - cost tracking │ │
│ │ - llm.latency.* │ │ - model compare │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
```
### 關鍵指標
| 指標 | 說明 | 類型 |
|------|------|------|
| `llm.tokens.input` | 輸入 Token 數 | Counter |
| `llm.tokens.output` | 輸出 Token 數 | Counter |
| `llm.cost.usd` | 估算成本 (雲端 Provider) | Counter |
| `llm.latency.p99` | 延遲 P99 | Histogram |
| `llm.requests.total` | 總請求數 | Counter |
| `llm.requests.failed` | 失敗請求數 | Counter |
### 成本警報閾值 (ADR-006 延伸)
| Provider | 每日上限 | 每月上限 | 告警閾值 |
|----------|---------|---------|---------|
| Gemini | 100K tokens | 2M tokens | 70% |
| Claude | 50K tokens | 500K tokens | 70% |
### Langfuse 追蹤整合
```python
from langfuse.decorators import langfuse_context, observe
class AIRouter:
@observe(name="smart_routing")
async def route(self, text: str, context: dict) -> RoutingDecision:
decision = await self._make_decision(text, context)
# 記錄路由決策
langfuse_context.update_current_trace(
metadata={
"intent": decision.intent.value,
"complexity": decision.complexity.score,
"model": decision.model,
"reason": decision.reason,
}
)
return decision
@observe(name="llm_generation")
async def generate(self, model: str, prompt: str) -> str:
result = await self._call_model(model, prompt)
# 記錄 Token 用量
langfuse_context.update_current_observation(
usage={
"input_tokens": result.input_tokens,
"output_tokens": result.output_tokens,
},
model=model,
)
return result.content
```
---
## 8. 與 ADR-006 的關係
### 架構層次
```
┌─────────────────────────────────────────────────────────────────┐
│ ADR-023 (Phase 13.3) │
│ 智能路由架構 │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Intent Classifier → Complexity Scorer → AI Router │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ADR-016 (Smart Routing 基礎實作) │ │
│ │ - IntentClassifier / ComplexityScorer / AIRouter │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ADR-006 (AI Fallback Strategy) │ │
│ │ - Circuit Breaker / Token 配額 / 固定 Fallback 順序 │ │
│ └─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### 協作關係
| 面向 | ADR-006 (基礎) | ADR-016 (實作) | ADR-023 (架構) |
|------|---------------|----------------|----------------|
| **範疇** | Fallback 策略 | 路由實作 | 完整架構 |
| **觸發時機** | 服務失敗時 | 每個請求 | 架構設計層面 |
| **選擇邏輯** | 固定順序 | 意圖 + 複雜度 | 三元件協作 |
| **目標** | 高可用性 | 資源最佳化 | 全方位 AIOps |
| **狀態** | 仍然有效 | 已實作 | 本文件 |
### 請求流程整合
```
Request
[ADR-023: 三元件決策]
├── Intent Classifier
├── Complexity Scorer
└── AI Router → 選擇 Model A
失敗 ▼
[ADR-006: Fallback Chain]
├── Model B
├── Model C
└── Static Response
```
---
## 9. 後果分析
### 優點
| 面向 | 效益 |
|------|------|
| **資源優化** | 簡單任務用小模型 (3B),節省 GPU 資源 30%+ |
| **品質提升** | 複雜任務自動升級到強模型,減少人工介入 |
| **成本可控** | 只有真正需要時才使用雲端 API |
| **延遲改善** | 簡單查詢回應 < 5s (llama3.2:3b) |
| **可觀測性** | Token 用量透明,成本趨勢可預測 |
| **隱私保護** | DIAGNOSE 意圖強制本地,敏感日誌不送雲端 |
### 缺點
| 面向 | 風險 | 緩解措施 |
|------|------|---------|
| **分類錯誤** | 意圖分類可能有邊界情況 | 關鍵字優先 + LLM 備援 |
| **複雜度誤判** | 規則可能需要持續調優 | 收集數據 + 定期調整權重 |
| **延遲增加** | 分類 + 評分 增加約 100ms | 限制 LLM 分類僅作備援 |
| **維護成本** | 需維護關鍵字映射表 | 集中管理於 models.json |
### 風險
| 風險 | 等級 | 緩解策略 |
|------|------|---------|
| LLM 分類器 Timeout | 中 | 設定 100ms Timeoutfallback 到 UNKNOWN |
| 全部 Provider 失敗 | 低 | ADR-006 靜態回應兜底 |
| Token 預算超支 | 中 | 告警閾值 70%,超支自動切本地 |
| 意圖覆寫邏輯錯誤 | 低 | 嚴格測試 + 監控路由決策分佈 |
---
## 10. 實作位置
```
apps/api/src/services/
├── intent_classifier.py # IntentClassifier
├── complexity_scorer.py # ComplexityScorer
├── ai_router.py # AIRouter
└── token_tracker.py # TokenTracker (SignOz + Langfuse)
```
---
## 11. 配置集中管理
### 單一事實來源: `models.json`
```json
{
"providers": {
"ollama": {
"models": {
"default": "qwen2.5:7b-instruct",
"fast": "llama3.2:3b",
"intent": "qwen2.5:1b"
},
"circuit_breaker": {
"failure_threshold": 3,
"recovery_timeout": 60
}
},
"gemini": {
"model": "gemini-1.5-flash",
"daily_quota": 100000,
"monthly_quota": 2000000
},
"claude": {
"model": "claude-3-5-sonnet",
"daily_quota": 50000,
"monthly_quota": 500000
}
},
"complexity_routing": {
"1": "llama3.2:3b",
"2": "qwen2.5:7b-instruct",
"3": "qwen2.5:7b-instruct",
"4": "gemini",
"5": "claude"
},
"intent_overrides": {
"DIAGNOSE": "qwen2.5:7b-instruct",
"CODE_REVIEW": "qwen2.5:7b-instruct",
"QUERY": "llama3.2:3b"
}
}
```
---
## 12. 變更記錄
| 日期 | 版本 | 變更 | 作者 |
|------|------|------|------|
| 2026-03-26 | v1.0 | 初版建立 (Phase 13.3 #85-88) | 首席架構師 |
---
## 參考
- [ADR-006: AI 降級備援策略](./ADR-006-ai-fallback-strategy.md)
- [ADR-016: 智能路由 (基礎實作)](./ADR-016-smart-routing.md)
- [Skill 08: Model Router Expert](../../.agents/skills/08-model-router-expert.md)
- [Phase 13.3 Smart Router 設計](~/.claude/projects/-Users-ogt-awoooi/memory/project_model_router_design.md)
- [Phase 13 Enterprise AIOps](~/.claude/projects/-Users-ogt-awoooi/memory/project_phase13_enterprise_aiops.md)
---
*此 ADR 記錄 Phase 13.3 智能路由架構的完整決策過程,整合 ADR-006 Fallback 策略與 ADR-016 路由實作。*