Files
awoooi/docs/adr/ADR-006-ai-fallback-strategy.md
OG T 95b46af986 docs: 新增稽核報告 + 靈感實驗室 + Runbook 更新
- AWOOOI_COMPREHENSIVE_AUDIT_2026Q1.md 全維度稽核
- INSPIRATION_LAB.md 靈感收集
- K3S-OPTIMIZATION-RUNBOOK.md 優化指南
- ADR-006 AI Fallback 策略更新

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-03-29 16:03:41 +08:00

14 KiB
Raw Permalink Blame History

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 機制

# 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

# 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())

告警規則

# 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. 實作範例

# 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 智能路由

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 需成本保護。

實作

# 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,
    },
}

整合位置

# 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 整合

背景

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 降級備援策略的決策過程與實作規範。