# ADR-006: AI 降級備援策略 > **狀態**: 已接受 > **日期**: 2026-03-20 > **決策者**: CTO, CEO --- ## 背景 AWOOOI 系統高度依賴 AI 功能,包括 AI Copilot、異常偵測、智能摘要等。 當本地 Ollama 服務不可用時,需要有完善的降級備援機制,同時嚴格控制雲端 API 成本。 ### CEO 指示 #2 > 雲端備援的順序採 **Gemini API 然後才是 Claude API**,並且要有效控管、監控, > API Token 使用的數量,要搭配告警機制,避免費用暴增! --- ## 決策 ### 1. AI 服務優先順序 ``` ┌─────────────────────────────────────────────────────┐ │ 優先級 1: Ollama (本地) │ │ 192.168.0.188:11434 │ │ 成本: $0 / 延遲: ~200ms │ └─────────────────────────────────────────────────────┘ │ 失敗 ▼ ┌─────────────────────────────────────────────────────┐ │ 優先級 2: Gemini API (雲端備援 - 優先) │ │ 成本: ~$0.001/1K tokens │ └─────────────────────────────────────────────────────┘ │ 失敗 ▼ ┌─────────────────────────────────────────────────────┐ │ 優先級 3: Claude API (雲端備援 - 次選) │ │ 成本: ~$0.008/1K tokens │ └─────────────────────────────────────────────────────┘ │ 失敗 ▼ ┌─────────────────────────────────────────────────────┐ │ 優先級 4: 靜態回應 (完全降級) │ │ 返回預設訊息,不調用任何 AI │ └─────────────────────────────────────────────────────┘ ``` ### 2. Circuit Breaker 機制 ```python # apps/api/app/services/ai/circuit_breaker.py from enum import Enum from datetime import datetime, timedelta import asyncio class CircuitState(Enum): CLOSED = "closed" # 正常 OPEN = "open" # 熔斷 HALF_OPEN = "half_open" # 試探 class CircuitBreaker: def __init__( self, failure_threshold: int = 5, # 連續失敗 5 次觸發熔斷 recovery_timeout: int = 60, # 熔斷後 60 秒嘗試恢復 half_open_max_calls: int = 3 # 半開狀態最多 3 次試探 ): self.state = CircuitState.CLOSED self.failure_count = 0 self.last_failure_time = None self.half_open_calls = 0 # ... async def call(self, func, *args, **kwargs): if self.state == CircuitState.OPEN: if self._should_try_recovery(): self.state = CircuitState.HALF_OPEN self.half_open_calls = 0 else: raise CircuitOpenError("Circuit is open") try: result = await func(*args, **kwargs) self._on_success() return result except Exception as e: self._on_failure() raise ``` ### 3. Token 使用量監控與告警 #### 每日/每月配額 | API | 每日上限 | 每月上限 | 告警閾值 | |-----|---------|---------|---------| | Gemini | 100K tokens | 2M tokens | 70% | | Claude | 50K tokens | 500K tokens | 70% | #### 監控 Schema ```python # apps/api/app/models/ai_usage.py class AIUsageLog(Base): __tablename__ = "ai_usage_logs" id = Column(UUID, primary_key=True) provider = Column(String) # ollama, gemini, claude model = Column(String) input_tokens = Column(Integer) output_tokens = Column(Integer) latency_ms = Column(Integer) success = Column(Boolean) error_message = Column(String, nullable=True) user_id = Column(UUID, ForeignKey("users.id")) created_at = Column(DateTime, default=func.now()) ``` #### 告警規則 ```yaml # k8s/monitoring/prometheus/ai-usage-alerts.yaml groups: - name: ai-usage-alerts rules: # Gemini 每日用量 70% 告警 - alert: GeminiDailyUsageWarning expr: | sum(increase(ai_tokens_total{provider="gemini"}[24h])) > 70000 labels: severity: warning annotations: summary: "Gemini API 每日用量已達 70%" description: "今日 Gemini 已使用 {{ $value | humanize }} tokens" # Gemini 每日用量 90% 嚴重告警 - alert: GeminiDailyUsageCritical expr: | sum(increase(ai_tokens_total{provider="gemini"}[24h])) > 90000 labels: severity: critical annotations: summary: "Gemini API 每日用量已達 90%,即將觸發限流" # Claude 每日用量 70% 告警 - alert: ClaudeDailyUsageWarning expr: | sum(increase(ai_tokens_total{provider="claude"}[24h])) > 35000 labels: severity: warning annotations: summary: "Claude API 每日用量已達 70%" # Ollama 連續失敗告警 - alert: OllamaConsecutiveFailures expr: | increase(ai_requests_failed_total{provider="ollama"}[5m]) > 5 labels: severity: critical annotations: summary: "Ollama 服務可能已離線" description: "過去 5 分鐘 Ollama 請求失敗超過 5 次,已啟動雲端備援" # 月度預算 50% 提醒 - alert: MonthlyAIBudgetWarning expr: | ( sum(increase(ai_tokens_total{provider="gemini"}[30d])) * 0.000001 + sum(increase(ai_tokens_total{provider="claude"}[30d])) * 0.000008 ) > 5 labels: severity: warning annotations: summary: "AI 月度成本已達 $5 (預算 50%)" ``` ### 4. 成本預估 | 場景 | Gemini | Claude | 月成本 | |------|--------|--------|--------| | **正常** (Ollama 100%) | 0 | 0 | $0 | | **輕度降級** (Ollama 90%, Gemini 10%) | ~200K | 0 | ~$0.20 | | **中度降級** (Gemini 80%, Claude 20%) | ~1.6M | ~400K | ~$5 | | **完全降級** (雲端 100%) | ~2M | ~500K | ~$10 | ### 5. 實作範例 ```python # apps/api/app/services/ai/router.py from app.services.ai.providers import OllamaProvider, GeminiProvider, ClaudeProvider from app.services.ai.circuit_breaker import CircuitBreaker from app.services.ai.usage_tracker import UsageTracker class AIRouter: def __init__(self): self.ollama = OllamaProvider() self.gemini = GeminiProvider() self.claude = ClaudeProvider() self.ollama_circuit = CircuitBreaker(failure_threshold=3, recovery_timeout=30) self.gemini_circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=60) self.claude_circuit = CircuitBreaker(failure_threshold=5, recovery_timeout=60) self.usage_tracker = UsageTracker() async def generate(self, prompt: str, user_id: str) -> AIResponse: providers = [ ("ollama", self.ollama, self.ollama_circuit), ("gemini", self.gemini, self.gemini_circuit), ("claude", self.claude, self.claude_circuit), ] for name, provider, circuit in providers: # 檢查配額 if name in ["gemini", "claude"]: if await self.usage_tracker.is_quota_exceeded(name): logger.warning(f"{name} daily quota exceeded, skipping") continue try: result = await circuit.call(provider.generate, prompt) # 記錄使用量 await self.usage_tracker.log( provider=name, input_tokens=result.input_tokens, output_tokens=result.output_tokens, user_id=user_id, success=True ) return result except CircuitOpenError: logger.info(f"{name} circuit is open, trying next provider") continue except Exception as e: logger.error(f"{name} failed: {e}, trying next provider") await self.usage_tracker.log( provider=name, error_message=str(e), user_id=user_id, success=False ) continue # 所有 AI 都失敗,返回靜態回應 return AIResponse( content="抱歉,AI 服務暫時不可用。請稍後再試,或聯繫管理員。", provider="fallback", tokens=0 ) ``` ### 6. Dashboard 展示 AI 用量監控面板應顯示: - 今日各 Provider 使用量 (tokens) - 本月累計成本 (USD) - 各 Provider 健康狀態 (綠/黃/紅) - 平均延遲 (ms) - 成功率 (%) --- ## 影響 ### 正面 - 確保 AI 功能高可用性 - 成本可控、可預測 - 即時告警避免帳單爆炸 ### 需要注意 - 需維護多個 API Key - 不同 Provider 回應品質可能有差異 - 需要處理 API 格式轉換 --- --- ## 智能路由整合 (Phase 13.3 - 2026-03-26) > 參考: [ADR-016 智能路由](ADR-016-smart-routing.md) Phase 13.3 引入**智能路由**機制,動態選擇最適合的模型: ### 路由決策流程 ``` User Request │ ▼ ┌─────────────┐ │ Intent │ 分類意圖 (ALERT/QUERY/CODE_REVIEW/...) │ Classifier │ └─────────────┘ │ ▼ ┌─────────────┐ │ Complexity │ 評估複雜度 (1-5) │ Scorer │ └─────────────┘ │ ▼ ┌─────────────┐ │ AIRouter │ Intent + Complexity → Model Selection └─────────────┘ │ ├── Low (1-2) → Ollama llama3.2:3b ├── Medium (3) → Ollama qwen2.5:7b-instruct └── High (4-5) → Gemini / Claude ``` ### 意圖覆寫規則 | 意圖 | 強制模型 | 原因 | |------|---------|------| | CODE_REVIEW | qwen2.5:7b | 需要程式碼理解能力 | | ALERT_TRIAGE (複雜) | Cloud | 多服務關聯分析 | | QUERY | llama3.2:3b | 簡單查詢不需大模型 | ### 與原有 Fallback 整合 ``` AIRouter 選擇模型 │ ▼ ┌─────────────┐ │ Primary │ (依路由決策選擇) │ Model │ └─────────────┘ │ 失敗 ▼ ┌─────────────┐ │ Fallback │ ADR-006 原有順序 │ Chain │ Ollama → Gemini → Claude → Static └─────────────┘ ``` ### 實作位置 ``` apps/api/src/services/ ├── intent_classifier.py # IntentClassifier ├── complexity_scorer.py # ComplexityScorer └── ai_router.py # AIRouter ``` --- ## 7. Rate Limiter 實作 (v1.2) ### 背景 2026-03-26 統帥決策:Ollama CPU 推論問題導致 mock_fallback,臨時切換 Gemini 需成本保護。 ### 實作 ```python # apps/api/src/services/ai_rate_limiter.py RATE_LIMITS = { "gemini": { "rpm": 10, # 每分鐘 "daily_requests": 500, # 每日 "daily_tokens": 100_000, }, "claude": { "rpm": 5, "daily_requests": 200, "daily_tokens": 50_000, }, } ``` ### 整合位置 ```python # apps/api/src/services/openclaw.py for provider in settings.AI_FALLBACK_ORDER: if provider in ("gemini", "claude"): allowed, reason = await rate_limiter.check_and_increment(provider) if not allowed: continue # 自動降級到下一個 provider ``` ### 監控端點 ``` GET /api/v1/health/ai-usage ``` 返回各 provider 的 RPM/Daily/Token 用量統計。 --- --- ## 8. NVIDIA Nemotron 整合 (v1.3) > 參考: [ADR-036 Nemotron Tool Calling 整合](ADR-036-nemotron-tool-calling-integration.md) ### 背景 2026-03-28 統帥指示評估 NVIDIA Nemotron 模型。實測結果 Tool Calling 精準度 83.3%,顯著優於 CPU Ollama (~50%)。 ### Provider 更新 | Provider | 用途 | 延遲 | 精準度 | 成本 | |----------|------|------|--------|------| | **Ollama** | 即時對話、簡單查詢 | < 5s | 中 | $0 | | **Nemotron** | Tool Calling、K8s 操作 | 11-45s | 高 (83%) | 免費 tier | | **Gemini** | 通用備援 | 2-5s | 中高 | 低 | | **Claude** | 複雜推理、CRITICAL | 2-5s | 最高 | 高 | ### 路由規則更新 ``` 任務類型 → 路由目標 ──────────────────────────────── Tool Calling → Nemotron (精準度高) 即時對話 → Ollama (低延遲) 複雜推理 → Claude (最強) 通用備援 → Gemini (平衡) ``` ### Fallback 鏈 ``` Tool Calling 任務: Nemotron → Gemini → Claude → 拒絕執行 一般對話任務 (不變): Ollama → Gemini → Claude → Static Response ``` --- ## 變更記錄 | 日期 | 版本 | 變更 | 作者 | |------|------|------|------| | 2026-03-20 | v1.0 | 初版建立 | CTO | | 2026-03-26 | v1.1 | 新增智能路由整合章節 (Phase 13.3) | 首席架構師 | | 2026-03-26 | v1.2 | 新增 Rate Limiter 實作章節 | 首席架構師 | | 2026-03-29 | v1.3 | 新增 NVIDIA Nemotron 整合章節 (待統帥批准) | 首席架構師 | --- *此 ADR 記錄 AI 降級備援策略的決策過程與實作規範。*