feat: add all application source code
- apps/api: FastAPI backend with Dockerfile - apps/web: Next.js frontend with Dockerfile - apps/sensor: Signal collection agent - packages: shared packages Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
219
apps/api/src/models/ai.py
Normal file
219
apps/api/src/models/ai.py
Normal file
@@ -0,0 +1,219 @@
|
||||
"""
|
||||
AI Decision Models - Phase 2 Structured Output
|
||||
===============================================
|
||||
CAI-101: ClawBot AI 結構化輸出模型
|
||||
|
||||
防禦性工程鐵律:
|
||||
- 絕對禁止 LLM 輸出無法解析的自由文本
|
||||
- 必須強制 JSON 格式 + Pydantic 驗證
|
||||
- blast_radius 為 REQUIRED 欄位,不可遺漏
|
||||
"""
|
||||
|
||||
from enum import Enum
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class SuggestedAction(str, Enum):
|
||||
"""
|
||||
AI 建議操作類型
|
||||
|
||||
必須與 executor.OperationType 對應
|
||||
"""
|
||||
RESTART_DEPLOYMENT = "RESTART_DEPLOYMENT"
|
||||
DELETE_POD = "DELETE_POD"
|
||||
SCALE_DEPLOYMENT = "SCALE_DEPLOYMENT"
|
||||
NO_ACTION = "NO_ACTION" # 無需處理
|
||||
|
||||
|
||||
class AIRiskLevel(str, Enum):
|
||||
"""AI 風險評估等級"""
|
||||
LOW = "low"
|
||||
MEDIUM = "medium"
|
||||
CRITICAL = "critical"
|
||||
|
||||
|
||||
class AIDataImpact(str, Enum):
|
||||
"""AI 資料影響評估"""
|
||||
NONE = "NONE"
|
||||
READ_ONLY = "READ_ONLY"
|
||||
WRITE = "WRITE"
|
||||
DESTRUCTIVE = "DESTRUCTIVE"
|
||||
|
||||
|
||||
class AIBlastRadius(BaseModel):
|
||||
"""
|
||||
爆炸半徑分析 (REQUIRED - 符合 API 契約)
|
||||
|
||||
此物件為必填,LLM 輸出必須包含完整結構
|
||||
"""
|
||||
affected_pods: int = Field(
|
||||
...,
|
||||
ge=0,
|
||||
description="受影響的 Pod 數量",
|
||||
)
|
||||
estimated_downtime: str = Field(
|
||||
...,
|
||||
description="預估停機時間 (例如: '~30s', '~2 min', '0')",
|
||||
)
|
||||
related_services: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="相關受影響服務",
|
||||
)
|
||||
data_impact: AIDataImpact = Field(
|
||||
default=AIDataImpact.NONE,
|
||||
description="資料影響程度",
|
||||
)
|
||||
|
||||
@field_validator("data_impact", mode="before")
|
||||
@classmethod
|
||||
def normalize_data_impact(cls, v):
|
||||
"""正規化 data_impact (LLM 可能輸出小寫)"""
|
||||
if isinstance(v, str):
|
||||
return v.upper()
|
||||
return v
|
||||
|
||||
|
||||
class OpenClawDecision(BaseModel):
|
||||
"""
|
||||
OpenClaw AI 決策輸出 (強制結構化)
|
||||
|
||||
LLM 必須輸出此格式的 JSON,否則視為解析失敗。
|
||||
blast_radius 為 REQUIRED 欄位!
|
||||
"""
|
||||
# === 基本操作欄位 ===
|
||||
suggested_action: SuggestedAction = Field(
|
||||
...,
|
||||
description="建議執行的操作類型",
|
||||
)
|
||||
target_resource: str = Field(
|
||||
...,
|
||||
description="目標資源名稱 (e.g., 'harbor', 'grafana')",
|
||||
)
|
||||
namespace: str = Field(
|
||||
default="default",
|
||||
description="Kubernetes namespace",
|
||||
)
|
||||
kubectl_command: str = Field(
|
||||
default="",
|
||||
description="具體的 kubectl 指令",
|
||||
)
|
||||
|
||||
# === 風險評估欄位 ===
|
||||
risk_level: AIRiskLevel = Field(
|
||||
...,
|
||||
description="風險等級評估",
|
||||
)
|
||||
|
||||
# === REQUIRED: 爆炸半徑 (符合 API 契約) ===
|
||||
blast_radius: AIBlastRadius = Field(
|
||||
...,
|
||||
description="爆炸半徑分析 - REQUIRED",
|
||||
)
|
||||
|
||||
# === 分析說明欄位 ===
|
||||
action_title: str = Field(
|
||||
default="",
|
||||
description="操作標題 (繁體中文)",
|
||||
)
|
||||
description: str = Field(
|
||||
default="",
|
||||
description="根本原因分析說明 (繁體中文)",
|
||||
)
|
||||
reasoning: str = Field(
|
||||
default="",
|
||||
description="給人類主管看的決策理由 (繁體中文)",
|
||||
)
|
||||
deviation_analysis: str = Field(
|
||||
default="",
|
||||
description="基準線偏差分析 (例如:CPU 85% 超出基準線 45% 達 +4σ)",
|
||||
)
|
||||
|
||||
# === 信心度與影響範圍 ===
|
||||
confidence: float = Field(
|
||||
default=0.8,
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="決策信心度 (0-1)",
|
||||
)
|
||||
affected_services: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="可能受影響的相關服務",
|
||||
)
|
||||
|
||||
# === v6.0 AI 仲裁欄位 ===
|
||||
primary_responsibility: str = Field(
|
||||
default="COLLAB",
|
||||
description="主要責任團隊 (FE/BE/INFRA/DB/COLLAB)",
|
||||
)
|
||||
responsibility_reasoning: str = Field(
|
||||
default="",
|
||||
description="責任判定理由",
|
||||
)
|
||||
secondary_teams: list[str] = Field(
|
||||
default_factory=list,
|
||||
description="需協助的其他團隊",
|
||||
)
|
||||
|
||||
# === v7.0 調優建議與 SignOz 整合 ===
|
||||
optimization_suggestions: list[dict] = Field(
|
||||
default_factory=list,
|
||||
description="預防性調優建議 (含 kubectl 指令)",
|
||||
)
|
||||
signoz_correlation: str = Field(
|
||||
default="",
|
||||
description="SignOz 指標與告警的關聯分析",
|
||||
)
|
||||
|
||||
@field_validator("risk_level", mode="before")
|
||||
@classmethod
|
||||
def normalize_risk_level(cls, v):
|
||||
"""正規化 risk_level (處理 LLM 可能輸出的非標準值)"""
|
||||
if isinstance(v, str):
|
||||
mapping = {
|
||||
"high": "critical",
|
||||
"severe": "critical",
|
||||
"warning": "medium",
|
||||
"normal": "low",
|
||||
"safe": "low",
|
||||
}
|
||||
return mapping.get(v.lower(), v.lower())
|
||||
return v
|
||||
|
||||
@field_validator("suggested_action", mode="before")
|
||||
@classmethod
|
||||
def normalize_suggested_action(cls, v):
|
||||
"""正規化 suggested_action"""
|
||||
if isinstance(v, str):
|
||||
return v.upper().replace("-", "_").replace(" ", "_")
|
||||
return v
|
||||
|
||||
|
||||
class ClawBotAnalysisRequest(BaseModel):
|
||||
"""分析請求"""
|
||||
force_refresh: bool = Field(
|
||||
default=False,
|
||||
description="強制重新抓取監控數據",
|
||||
)
|
||||
|
||||
|
||||
class ClawBotAnalysisResponse(BaseModel):
|
||||
"""分析回應"""
|
||||
success: bool
|
||||
message: str
|
||||
decision: OpenClawDecision | None = None
|
||||
approval_created: bool = Field(
|
||||
default=False,
|
||||
description="是否已建立待簽核卡片",
|
||||
)
|
||||
approval_id: str | None = Field(
|
||||
default=None,
|
||||
description="建立的 ApprovalRecord ID",
|
||||
)
|
||||
ai_provider: str = Field(
|
||||
default="unknown",
|
||||
description="使用的 AI 提供者 (ollama/gemini/claude)",
|
||||
)
|
||||
raw_llm_response: str | None = Field(
|
||||
default=None,
|
||||
description="LLM 原始回應 (debug 用)",
|
||||
)
|
||||
Reference in New Issue
Block a user