🔴 違規修正: 規則匹配/Expert System 不是 AI 分析,confidence 必須 = 0.0 修正檔案: - agents/action_planner.py: 0.9 → 0.0 - agents/blast_radius.py: 0.85/0.5/0.9 → 0.0 - agents/security.py: 計算公式 → 0.0 - signoz_webhook.py: 0.7 → 0.0 - auto_approve.py: default 0.5 → 0.0 - ci_auto_repair.py: 整個計算函數 → return 0.0 - error_analyzer_service.py: default 0.5 → 0.0 - intent_classifier.py: 計算公式 → 0.0 - openclaw.py: default 0.5 → 0.0 - resource_resolver.py: 0.8 → 0.0 - k8s_naming.py: 0.9/0.7 → 0.0 只有 LLM 真實分析返回的 confidence 才能 > 0 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
370 lines
12 KiB
Python
370 lines
12 KiB
Python
"""
|
||
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
|