Files
awoooi/apps/api/src/services/ai_providers/interfaces.py
OG T 5a8aae89c4
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 7m12s
E2E Health Check / e2e-health (push) Successful in 18s
fix(phase24): 首席架構師 Review C1/C2/C3/I4 修復
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>
2026-04-02 21:47:06 +08:00

157 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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"