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:
652
docs/adr/ADR-023-smart-routing-architecture.md
Normal file
652
docs/adr/ADR-023-smart-routing-architecture.md
Normal file
@@ -0,0 +1,652 @@
|
||||
# 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 路由實作。*
|
||||
173
docs/adr/ADR-024-api-layer-architecture.md
Normal file
173
docs/adr/ADR-024-api-layer-architecture.md
Normal file
@@ -0,0 +1,173 @@
|
||||
# ADR-024: API 分層架構 (Phase 16 絞殺者模式)
|
||||
|
||||
| 項目 | 內容 |
|
||||
|------|------|
|
||||
| **狀態** | ✅ 已採用 |
|
||||
| **日期** | 2026-03-26 |
|
||||
| **決策者** | 首席架構師 + 統帥 |
|
||||
| **Phase** | Phase 16 |
|
||||
|
||||
## 背景
|
||||
|
||||
Phase 16 架構大掃除需要明確的 API 分層規範,以支援絞殺者模式 (Strangler Fig Pattern) 的漸進式重構。
|
||||
|
||||
當前問題:
|
||||
- Router 層存在業務邏輯 (32 項違規)
|
||||
- 配置分散在多處
|
||||
- 缺乏明確的層級邊界
|
||||
|
||||
## 決策
|
||||
|
||||
採用 **四層架構** 標準:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Router Layer (api/v1/*.py) │
|
||||
│ - HTTP 轉發 ONLY │
|
||||
│ - 參數驗證 (Pydantic) │
|
||||
│ - 權限檢查 (Depends) │
|
||||
│ - ❌ 禁止: Redis/DB/外部 API 直接呼叫 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Service Layer (services/*.py) │
|
||||
│ - 業務邏輯 │
|
||||
│ - 外部 API 封裝 │
|
||||
│ - 快取策略 │
|
||||
│ - ✅ 可呼叫: Repository, 其他 Service │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Repository Layer (repositories/*.py) │
|
||||
│ - 資料存取抽象 │
|
||||
│ - SQL/ORM 操作 │
|
||||
│ - Redis 快取 │
|
||||
│ - ✅ 可呼叫: Model, Redis, PostgreSQL │
|
||||
└─────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Model Layer (models/*.py) │
|
||||
│ - Pydantic Schema │
|
||||
│ - SQLAlchemy ORM │
|
||||
│ - 純資料結構,無邏輯 │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Router 層禁止清單
|
||||
|
||||
```python
|
||||
# ❌ 禁止在 Router 層做的事
|
||||
|
||||
# 1. 直接 Redis 存取
|
||||
from src.core.redis_client import get_redis # ❌
|
||||
|
||||
# 2. 直接 DB Session
|
||||
from src.db.base import get_session # ❌
|
||||
|
||||
# 3. 直接外部 API 呼叫
|
||||
async with httpx.AsyncClient() as client: # ❌
|
||||
response = await client.get(external_url)
|
||||
|
||||
# 4. 內嵌 Lua 腳本
|
||||
LUA_SCRIPT = """...""" # ❌
|
||||
|
||||
# 5. 複雜業務邏輯 (>10 行)
|
||||
if condition1 and condition2: # ❌
|
||||
# 複雜處理...
|
||||
```
|
||||
|
||||
## Router 層允許清單
|
||||
|
||||
```python
|
||||
# ✅ Router 層可以做的事
|
||||
|
||||
# 1. 參數驗證
|
||||
@router.get("/items/{item_id}")
|
||||
async def get_item(
|
||||
item_id: str = Path(...),
|
||||
limit: int = Query(default=10, ge=1, le=100),
|
||||
) -> ItemResponse:
|
||||
|
||||
# 2. 權限檢查
|
||||
async def get_item(
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
|
||||
# 3. 呼叫 Service
|
||||
service = get_item_service()
|
||||
result = await service.get_item(item_id)
|
||||
|
||||
# 4. 回傳轉換 (簡單)
|
||||
return ItemResponse.from_orm(result)
|
||||
```
|
||||
|
||||
## 絞殺者模式四階段
|
||||
|
||||
```
|
||||
Phase 1: Identify (識別)
|
||||
├── 標記現有違規代碼
|
||||
├── 建立 Service 介面
|
||||
└── 不改變行為
|
||||
|
||||
Phase 2: Deprecate (標記棄用)
|
||||
├── 新代碼使用 Service
|
||||
├── 舊代碼加 @deprecated
|
||||
└── 監控舊路徑使用量
|
||||
|
||||
Phase 3: Migrate (遷移)
|
||||
├── 逐步遷移到 Service
|
||||
├── 每次遷移有測試覆蓋
|
||||
└── 回滾計畫就緒
|
||||
|
||||
Phase 4: Remove (移除)
|
||||
├── 確認無流量
|
||||
├── 移除舊代碼
|
||||
└── 更新文檔
|
||||
```
|
||||
|
||||
## 與 leWOOOgo 積木化的關係
|
||||
|
||||
```
|
||||
leWOOOgo 六大積木 API 四層對應
|
||||
─────────────────────────────────────
|
||||
BRAIN (決策) → Service Layer
|
||||
ACTION (執行) → Service + Repository
|
||||
SENSE (感知) → Repository Layer
|
||||
MEMORY (記憶) → Repository Layer
|
||||
OUTPUT (輸出) → Service Layer
|
||||
SAFETY (安全) → Router (Depends) + Service
|
||||
```
|
||||
|
||||
## 回滾策略
|
||||
|
||||
```yaml
|
||||
# 功能開關 (core/config.py)
|
||||
USE_NEW_LAYER: bool = Field(
|
||||
default=False,
|
||||
description="True=新分層, False=舊版內嵌",
|
||||
)
|
||||
|
||||
# 回滾指令
|
||||
kubectl set env deployment/awoooi-api USE_NEW_LAYER=false
|
||||
```
|
||||
|
||||
## 後果
|
||||
|
||||
### 正面
|
||||
- 清晰的層級邊界
|
||||
- 可測試性提升 (每層獨立測試)
|
||||
- 漸進式遷移,低風險
|
||||
|
||||
### 負面
|
||||
- 短期開發成本增加
|
||||
- 需要團隊學習新規範
|
||||
|
||||
## 相關文件
|
||||
|
||||
- ADR-005: BFF Architecture
|
||||
- ADR-003: leWOOOgo Module Architecture
|
||||
- `feedback_strangler_fig_pattern.md`
|
||||
- `reference_phase16_architecture.md`
|
||||
Reference in New Issue
Block a user