""" Schema Validators - Tier 1 測試 ================================ 驗證 LLM 輸出符合預定義 Schema 版本: v1.0 建立: 2026-03-26 (台北時區) """ import json import re from typing import Literal from pydantic import BaseModel, Field, ValidationError class LLMProposalOutput(BaseModel): """ LLM 提案輸出 Schema OpenClaw 必須輸出此格式的 JSON """ risk_level: Literal["LOW", "MEDIUM", "HIGH", "CRITICAL"] = Field( description="風險等級" ) kubectl_command: str | None = Field( None, description="kubectl 命令 (可選)" ) action_description: str = Field( description="行動描述" ) reasoning: str = Field( description="推理過程" ) confidence: float = Field( ge=0.0, le=1.0, description="信心度 0-1" ) class LLMAnalysisOutput(BaseModel): """ LLM 分析輸出 Schema 用於告警分析回應 """ root_cause: str = Field( description="根因分析" ) severity: Literal["P0", "P1", "P2", "P3"] = Field( description="嚴重度" ) affected_services: list[str] = Field( default_factory=list, description="受影響服務" ) recommended_actions: list[str] = Field( default_factory=list, description="建議行動" ) def validate_proposal_schema(response: str) -> tuple[bool, str, LLMProposalOutput | None]: """ 驗證 LLM 回應是否符合 Proposal Schema Args: response: LLM 原始回應 Returns: (is_valid, error_message, parsed_output) """ # 嘗試提取 JSON json_str = extract_json_from_response(response) if not json_str: return False, "無法從回應中提取 JSON", None # 解析 JSON try: data = json.loads(json_str) except json.JSONDecodeError as e: return False, f"JSON 解析失敗: {e}", None # Schema 驗證 try: output = LLMProposalOutput.model_validate(data) return True, "", output except ValidationError as e: errors = "; ".join([f"{err['loc']}: {err['msg']}" for err in e.errors()]) return False, f"Schema 驗證失敗: {errors}", None def validate_analysis_schema(response: str) -> tuple[bool, str, LLMAnalysisOutput | None]: """ 驗證 LLM 回應是否符合 Analysis Schema Args: response: LLM 原始回應 Returns: (is_valid, error_message, parsed_output) """ json_str = extract_json_from_response(response) if not json_str: return False, "無法從回應中提取 JSON", None try: data = json.loads(json_str) except json.JSONDecodeError as e: return False, f"JSON 解析失敗: {e}", None try: output = LLMAnalysisOutput.model_validate(data) return True, "", output except ValidationError as e: errors = "; ".join([f"{err['loc']}: {err['msg']}" for err in e.errors()]) return False, f"Schema 驗證失敗: {errors}", None def extract_json_from_response(response: str) -> str | None: """ 從 LLM 回應中提取 JSON 支援: - 純 JSON - ```json ... ``` 包裹 - 混合文字中的 JSON """ if not response: return None response = response.strip() # Case 1: 純 JSON if response.startswith("{") and response.endswith("}"): return response # Case 2: ```json 包裹 json_block_pattern = r"```(?:json)?\s*([\s\S]*?)```" match = re.search(json_block_pattern, response) if match: return match.group(1).strip() # Case 3: 尋找 { ... } 區塊 brace_pattern = r"\{[\s\S]*\}" match = re.search(brace_pattern, response) if match: return match.group(0) return None