""" Error Analyzer Service - #39 Sentry 錯誤 AI 分析 ================================================= Phase 10: Sentry + OpenClaw + UI 整合 功能: 1. 接收 Sentry Issue + Stacktrace 數據 2. 使用 OpenClaw LLM 進行根因分析 3. 生成修復建議與預防措施 遵循 leWOOOgo 積木化原則: - Service 層負責業務邏輯 - 不直接存取 Redis/DB - 使用 DI 支援測試 版本: v1.0 建立: 2026-03-26 18:45 (台北時區) 建立者: Claude Code (#39 Error Analyzer Agent) """ import json from typing import Protocol, runtime_checkable from pydantic import BaseModel, Field from src.core.logging import get_logger from src.utils.timezone import now_taipei_iso logger = get_logger("awoooi.error_analyzer") # ============================================================================= # Error Analysis Prompt # ============================================================================= ERROR_ANALYZER_SYSTEM_PROMPT = """# OpenClaw Error Analyzer - AWOOOI 錯誤分析專家 You are a senior Software Engineer specialized in debugging and error analysis. ## 🌐 Language Requirement (CRITICAL) - You MUST respond in **Traditional Chinese (繁體中文/正體中文)** for all text fields - FORBIDDEN: Simplified Chinese characters (简体字) - Use Taiwan locale conventions (台灣用語) ## 🎯 Your Mission Analyze the given error from Sentry and provide: 1. **Root Cause Analysis** - Why did this error occur? 2. **Impact Assessment** - How serious is this error? 3. **Fix Recommendations** - How to fix this error? 4. **Prevention Suggestions** - How to prevent recurrence? ## 📊 Analysis Categories - **CODE_BUG**: Logic error, null pointer, type error - **DEPENDENCY**: Third-party library issue, version conflict - **CONFIGURATION**: Missing env var, wrong config - **INFRASTRUCTURE**: Network, timeout, resource exhaustion - **DATA_INTEGRITY**: Corrupt data, schema mismatch - **EXTERNAL_SERVICE**: API failure, rate limit - **UNKNOWN**: Cannot determine from available information ## ⚠️ Output Rules - Respond with ONLY valid JSON - confidence MUST be between 0.0 and 1.0 - severity MUST be one of: LOW, MEDIUM, HIGH, CRITICAL - All text fields in Traditional Chinese ## 📋 JSON Schema (REQUIRED) ```json { "root_cause": "string - 根因分析 (繁體中文)", "category": "CODE_BUG|DEPENDENCY|CONFIGURATION|INFRASTRUCTURE|DATA_INTEGRITY|EXTERNAL_SERVICE|UNKNOWN", "severity": "LOW|MEDIUM|HIGH|CRITICAL", "impact_assessment": "string - 影響評估 (繁體中文)", "fix_recommendation": { "summary": "string - 修復摘要", "steps": ["array - 修復步驟"], "code_suggestion": "string | null - 建議的代碼修改" }, "prevention": [ { "type": "CODE_REVIEW|UNIT_TEST|MONITORING|VALIDATION|ERROR_HANDLING", "description": "string - 預防措施描述" } ], "related_files": ["array - 可能相關的檔案路徑"], "confidence": "number - 0.0 to 1.0", "reasoning": "string - 分析推理過程 (繁體中文)" } ``` Now analyze the following error: """ # ============================================================================= # Response Models # ============================================================================= class FixRecommendation(BaseModel): """修復建議""" summary: str = Field(description="修復摘要") steps: list[str] = Field(default_factory=list, description="修復步驟") code_suggestion: str | None = Field(None, description="建議的代碼修改") class PreventionMeasure(BaseModel): """預防措施""" type: str = Field(description="類型 (CODE_REVIEW, UNIT_TEST, etc.)") description: str = Field(description="描述") class ErrorAnalysisResult(BaseModel): """錯誤分析結果""" root_cause: str = Field(description="根因分析") category: str = Field(description="分類") severity: str = Field(description="嚴重度") impact_assessment: str = Field(description="影響評估") fix_recommendation: FixRecommendation = Field(description="修復建議") prevention: list[PreventionMeasure] = Field( default_factory=list, description="預防措施" ) related_files: list[str] = Field(default_factory=list, description="相關檔案") confidence: float = Field(description="信心度") reasoning: str = Field(description="分析推理過程") # ============================================================================= # Protocol Interface # ============================================================================= @runtime_checkable class ILLMProvider(Protocol): """LLM Provider Protocol""" async def call(self, prompt: str) -> tuple[str, str, bool]: """ 呼叫 LLM Returns: (response, provider_name, success) """ ... # ============================================================================= # Error Analyzer Service # ============================================================================= class ErrorAnalyzerService: """ Error Analyzer Service - Sentry 錯誤 AI 分析 職責: 1. 組裝分析 Prompt 2. 呼叫 OpenClaw LLM 3. 解析並驗證分析結果 """ def __init__(self, llm_provider: ILLMProvider | None = None) -> None: """ 初始化 Error Analyzer Service Args: llm_provider: LLM 提供者 (預設使用 OpenClaw) """ self._llm_provider = llm_provider async def _get_llm_provider(self) -> ILLMProvider: """取得 LLM Provider (lazy init)""" if self._llm_provider is None: from src.services.openclaw import get_openclaw self._llm_provider = get_openclaw() return self._llm_provider async def analyze_error( self, issue_id: str, title: str, level: str, culprit: str | None, count: int, stacktrace: str, context: dict | None = None, ) -> tuple[ErrorAnalysisResult | None, str, bool]: """ 分析 Sentry 錯誤 Args: issue_id: Sentry Issue ID title: 錯誤標題 level: 嚴重度 (error, warning, etc.) culprit: 錯誤來源 (函數/檔案) count: 發生次數 stacktrace: 堆疊追蹤 context: 額外上下文 (browser, os, tags, etc.) Returns: (analysis_result, provider, success) """ # 組裝 Prompt error_context = { "issue_id": issue_id, "title": title, "level": level, "culprit": culprit, "occurrence_count": count, "stacktrace": stacktrace, "context": context or {}, "analyzed_at": now_taipei_iso(), } prompt = ERROR_ANALYZER_SYSTEM_PROMPT + "\n```json\n" prompt += json.dumps(error_context, ensure_ascii=False, indent=2) prompt += "\n```" logger.info( "error_analysis_start", issue_id=issue_id, title=title, level=level, ) # 呼叫 LLM try: llm = await self._get_llm_provider() response, provider, success = await llm.call(prompt) if not success: logger.error( "error_analysis_llm_failed", issue_id=issue_id, provider=provider, ) return None, provider, False logger.info( "error_analysis_llm_response", issue_id=issue_id, provider=provider, response_length=len(response), ) # 解析結果 result = self._parse_analysis_result(response) if result: logger.info( "error_analysis_complete", issue_id=issue_id, category=result.category, severity=result.severity, confidence=result.confidence, ) else: logger.warning( "error_analysis_parse_failed", issue_id=issue_id, raw_response=response[:300], ) return result, provider, True except Exception as e: logger.exception( "error_analysis_failed", issue_id=issue_id, error=str(e), ) return None, "error", False def _parse_analysis_result(self, raw_response: str) -> ErrorAnalysisResult | None: """ 解析 LLM 回應為結構化結果 Args: raw_response: LLM 原始回應 Returns: 解析後的 ErrorAnalysisResult,解析失敗返回 None """ try: # 嘗試找到 JSON 區塊 json_str = raw_response # 處理可能的 markdown 包裝 if "```json" in raw_response: start = raw_response.find("```json") + 7 end = raw_response.find("```", start) if end > start: json_str = raw_response[start:end] elif "```" in raw_response: start = raw_response.find("```") + 3 end = raw_response.find("```", start) if end > start: json_str = raw_response[start:end] # 解析 JSON data = json.loads(json_str.strip()) # 建立 FixRecommendation fix_data = data.get("fix_recommendation", {}) fix_recommendation = FixRecommendation( summary=fix_data.get("summary", "無建議"), steps=fix_data.get("steps", []), code_suggestion=fix_data.get("code_suggestion"), ) # 建立 PreventionMeasure 列表 prevention = [] for p in data.get("prevention", []): prevention.append(PreventionMeasure( type=p.get("type", "UNKNOWN"), description=p.get("description", ""), )) # 建立最終結果 return ErrorAnalysisResult( root_cause=data.get("root_cause", "無法判斷根因"), category=data.get("category", "UNKNOWN"), severity=data.get("severity", "MEDIUM"), impact_assessment=data.get("impact_assessment", "影響評估中"), fix_recommendation=fix_recommendation, prevention=prevention, related_files=data.get("related_files", []), confidence=float(data.get("confidence", 0.0)), # 🔴 無信心度=規則匹配 reasoning=data.get("reasoning", ""), ) except json.JSONDecodeError as e: logger.warning( "error_analysis_json_decode_failed", error=str(e), raw_response=raw_response[:200], ) return None except Exception as e: logger.warning( "error_analysis_parse_error", error=str(e), ) return None # ============================================================================= # Singleton # ============================================================================= _error_analyzer_service: ErrorAnalyzerService | None = None def get_error_analyzer_service() -> ErrorAnalyzerService: """取得 Error Analyzer Service 實例 (Singleton)""" global _error_analyzer_service if _error_analyzer_service is None: _error_analyzer_service = ErrorAnalyzerService() return _error_analyzer_service def set_error_analyzer_service(service: ErrorAnalyzerService) -> None: """設定 Error Analyzer Service 實例 (for testing)""" global _error_analyzer_service _error_analyzer_service = service