Files
awoooi/apps/api/src/services/ai_providers/interfaces.py
Your Name 7e4d995e4b
Some checks failed
CD Pipeline / tests (push) Successful in 1m59s
Code Review / ai-code-review (push) Successful in 28s
run-migration / migrate (push) Failing after 24s
CD Pipeline / post-deploy-checks (push) Has been cancelled
CD Pipeline / build-and-deploy (push) Has been cancelled
feat(aiops): add mcp agent loop foundation
2026-05-01 13:21:19 +08:00

198 lines
5.6 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, Callable, Protocol, runtime_checkable
from src.plugins.mcp.interfaces import MCPTool, MCPToolResult
# =============================================================================
# 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,
}
@dataclass
class ToolCallResult:
"""One tool_use result returned to an Agent Loop capable model."""
tool_use_id: str
tool_name: str
output: Any
success: bool = True
error: str | None = None
def to_dict(self) -> dict:
return {
"tool_use_id": self.tool_use_id,
"tool_name": self.tool_name,
"output": self.output,
"success": self.success,
"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 analyze_with_tools(
self,
prompt: str,
available_tools: list[MCPTool],
tool_executor: Callable[[str, dict[str, Any]], Any],
max_iterations: int = 5,
agent_role: str = "openclaw",
context: dict[str, Any] | None = None,
) -> AIResult:
"""
Agent Loop mode: let the model request MCP tools, receive tool results,
and decide when it has enough evidence.
Providers may implement native tool_use. Providers without native support
should fail closed or fall back to analyze(); routing code decides whether
fallback is acceptable for the current incident.
"""
...
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"