# 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 Timeout，fallback 到 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 路由實作。*
