fix(api): 修復全部 lint 錯誤 (ruff --fix)

- Import sorting (I001)
- Unused imports (F401)
- f-string without placeholders (F541)
- Loop variable unused (B007)
- zip() strict parameter (B905)
- Exception chaining (B904)
- collections.abc imports (UP035)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-26 16:06:20 +08:00
parent e26ea526b1
commit 30153496d1
38 changed files with 3041 additions and 101 deletions

View File

@@ -0,0 +1,369 @@
"""
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.5)),
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