C1 (P0): AIRouterExecutor.execute() 補 Langfuse Trace (D5)
- 建立 langfuse_trace("ai_router_execute") 包住整個執行鏈
- 成功時記錄 generation (model/input/output/tokens/cost)
- prod 所有 AI 呼叫現在有 LLMOps 追蹤
C2 (P0): 絞殺者改為呼叫 AIRouter.route() 智慧路由
- 先取得 RoutingDecision (意圖分類 + 複雜度評分)
- provider_order 從 selected_provider + fallback_chain 動態生成
- D1 意圖路由矩陣、D7 隱私保護 (DIAGNOSE 強制 local) 生效
C3 (P1): 型別標注 typo 修復
- AIProviderEnumEnum → AIProviderEnum
- AIProviderEnumProtocol → AIProviderProtocol
I4 (P1): interfaces.py AIProvider Protocol 補 close() 定義
S1: ai_router.py 模組版本標頭更新至 v4.0
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
157 lines
4.4 KiB
Python
157 lines
4.4 KiB
Python
"""
|
||
AI Provider Interfaces - Phase 24 ADR-052
|
||
==========================================
|
||
定義 AI Provider 標準介面,類比 MCP interfaces.py (ADR-015)
|
||
|
||
設計原則:
|
||
1. Interface 先行 (Contract-First) — leWOOOgo 積木化鐵律
|
||
2. Provider 只負責「呼叫 LLM」,不管閘門/Cache/Trace
|
||
3. AIResult 標準化回傳,供 AIRouter 統一處理
|
||
|
||
2026-04-02 ogt: Phase 24-A 建立
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import os
|
||
from dataclasses import dataclass, field
|
||
from typing import Any, Protocol, runtime_checkable
|
||
|
||
|
||
# =============================================================================
|
||
# AIResult — 標準化 AI 分析結果
|
||
# =============================================================================
|
||
|
||
|
||
@dataclass
|
||
class AIResult:
|
||
"""
|
||
AI 分析標準化結果
|
||
|
||
所有 Provider 必須回傳此格式,供 AIRouter 統一處理。
|
||
類比 MCP 的 MCPToolResult。
|
||
"""
|
||
|
||
raw_response: str
|
||
success: bool
|
||
provider: str # e.g., "ollama", "gemini", "openclaw_nemo"
|
||
from_cache: bool = False
|
||
tokens: int = 0
|
||
cost_usd: float = 0.0
|
||
latency_ms: float = 0.0
|
||
error: str | None = None
|
||
|
||
def to_dict(self) -> dict:
|
||
return {
|
||
"raw_response": self.raw_response[:500],
|
||
"success": self.success,
|
||
"provider": self.provider,
|
||
"from_cache": self.from_cache,
|
||
"tokens": self.tokens,
|
||
"cost_usd": self.cost_usd,
|
||
"latency_ms": round(self.latency_ms, 2),
|
||
"error": self.error,
|
||
}
|
||
|
||
|
||
# =============================================================================
|
||
# AIProvider Protocol — 所有 LLM 引擎必須實作
|
||
# =============================================================================
|
||
|
||
|
||
@runtime_checkable
|
||
class AIProvider(Protocol):
|
||
"""
|
||
AI Provider 標準介面 — Brain Layer 的 MCPToolProvider
|
||
|
||
設計原則 (ADR-052 D3):
|
||
- Provider 是「純粹的運算單元 (Stateless Compute Units)」
|
||
- 所有閘門 (CB/RL/Sem)、Cache、Trace 由 AIRouter 管理
|
||
- Provider 只負責: 接收 prompt → 呼叫 LLM → 回傳 AIResult
|
||
|
||
隱私規則 (ADR-052 D7):
|
||
- privacy_level="local" → 可處理 DIAGNOSE/CODE_REVIEW (機密資料)
|
||
- privacy_level="cloud" → 禁止接收機密資料 (零信任)
|
||
"""
|
||
|
||
@property
|
||
def name(self) -> str:
|
||
"""Provider 唯一名稱 (e.g., 'ollama', 'gemini', 'openclaw_nemo')"""
|
||
...
|
||
|
||
@property
|
||
def is_enabled(self) -> bool:
|
||
"""根據環境變數 ENABLE_{NAME} 動態判斷是否啟用"""
|
||
...
|
||
|
||
@property
|
||
def capabilities(self) -> set[str]:
|
||
"""
|
||
支援的能力集合
|
||
|
||
標準能力:
|
||
- "rca": Root Cause Analysis (Incident 仲裁)
|
||
- "tool_calling": K8s Tool Calling (kubectl 指令生成)
|
||
- "code_review": 代碼審查
|
||
- "chat": 一般對話
|
||
"""
|
||
...
|
||
|
||
@property
|
||
def privacy_level(self) -> str:
|
||
"""
|
||
隱私等級: "local" | "cloud"
|
||
|
||
ADR-052 D7 + 首席架構師 Q2 裁示:
|
||
- "local": 本地運算,可處理機密資料
|
||
- "cloud": 雲端 API,禁止機密資料 (即使有企業協議)
|
||
"""
|
||
...
|
||
|
||
async def analyze(
|
||
self,
|
||
prompt: str,
|
||
context: dict[str, Any] | None = None,
|
||
) -> AIResult:
|
||
"""
|
||
執行 AI 分析
|
||
|
||
Args:
|
||
prompt: 完整的 LLM prompt
|
||
context: 額外上下文 (alert_type, target_resource 等)
|
||
|
||
Returns:
|
||
AIResult: 標準化結果
|
||
"""
|
||
...
|
||
|
||
async def health_check(self) -> bool:
|
||
"""健康檢查 (供 /health endpoint 和 AIRouter 動態路由)"""
|
||
...
|
||
|
||
async def close(self) -> None:
|
||
"""關閉 HTTP 連線 / 釋放資源 (shutdown hook, ADR-052 I5)"""
|
||
...
|
||
|
||
|
||
# =============================================================================
|
||
# 工具函數
|
||
# =============================================================================
|
||
|
||
|
||
def is_provider_enabled_by_env(provider_name: str) -> bool:
|
||
"""
|
||
根據環境變數判斷 Provider 是否啟用
|
||
|
||
環境變數格式: ENABLE_{PROVIDER_NAME_UPPER}
|
||
例如: ENABLE_OLLAMA, ENABLE_GEMINI, ENABLE_OPENCLAW_NEMO
|
||
|
||
Args:
|
||
provider_name: Provider 名稱 (e.g., "ollama", "openclaw_nemo")
|
||
|
||
Returns:
|
||
bool: 是否啟用 (預設 True)
|
||
"""
|
||
env_key = f"ENABLE_{provider_name.upper()}"
|
||
return os.getenv(env_key, "true").lower() == "true"
|