diff --git a/apps/api/src/api/v1/webhooks.py b/apps/api/src/api/v1/webhooks.py index 3d271f26d..c8cdd65ab 100644 --- a/apps/api/src/api/v1/webhooks.py +++ b/apps/api/src/api/v1/webhooks.py @@ -1194,48 +1194,71 @@ async def alertmanager_webhook( if analysis_result: # analysis_result 是 OpenClawDecision Pydantic 模型 - risk_level = analysis_result.risk_level.value # enum → "low", "medium", "critical" - action = f"{analysis_result.action_title} | {analysis_result.kubectl_command}" - root_cause = analysis_result.description or message - estimated_downtime = analysis_result.blast_radius.estimated_downtime if analysis_result.blast_radius else "~30s" - primary_responsibility = analysis_result.primary_responsibility or "COLLAB" # 已是 str - confidence = analysis_result.confidence + # 轉換風險等級 + risk_mapping = { + "low": RiskLevel.LOW, + "medium": RiskLevel.MEDIUM, + "critical": RiskLevel.CRITICAL, + } + risk_level = risk_mapping.get(analysis_result.risk_level.value, RiskLevel.MEDIUM) - # 建立 ApprovalRecord - approval = await service.create_approval( - alert_type=alert_type, - target=target_resource, - action=action, + # 提取爆炸半徑 + blast = analysis_result.blast_radius + impact_mapping = { + "NONE": DataImpact.NONE, + "READ_ONLY": DataImpact.READ_ONLY, + "WRITE": DataImpact.WRITE, + "DESTRUCTIVE": DataImpact.DESTRUCTIVE, + } + data_impact = impact_mapping.get(blast.data_impact.value, DataImpact.NONE) if blast else DataImpact.NONE + + # 建立 ApprovalRequestCreate (同 /alerts 流程) + approval_create = ApprovalRequestCreate( + action=f"{analysis_result.action_title} | {analysis_result.kubectl_command}", + description=f"[AI: {ai_provider}] {analysis_result.description}", risk_level=risk_level, - details={ - "source": "alertmanager", - "alertname": alertname, - "namespace": namespace, - "message": message, - "ai_analysis": analysis_result.model_dump(), # 轉換為字典 - "ai_provider": ai_provider, - }, + blast_radius=BlastRadius( + affected_pods=blast.affected_pods if blast else 1, + estimated_downtime=blast.estimated_downtime if blast else "~30s", + related_services=list(set((blast.related_services if blast else []) + analysis_result.affected_services)), + data_impact=data_impact, + ), + dry_run_checks=[ + DryRunCheck(name="AI 信心度", passed=analysis_result.confidence >= 0.7, message=f"{analysis_result.confidence:.0%}"), + DryRunCheck(name="來源", passed=True, message="alertmanager"), + ], + requested_by=f"OpenClaw ({ai_provider})", + ) + + # 使用 create_approval_with_fingerprint (同 /alerts) + approval = await service.create_approval_with_fingerprint( + request=approval_create, fingerprint=fingerprint, ) + root_cause = analysis_result.description or message + estimated_downtime = blast.estimated_downtime if blast else "~30s" + primary_responsibility = analysis_result.primary_responsibility or "COLLAB" + confidence = analysis_result.confidence + # 推送 Telegram background_tasks.add_task( _push_to_telegram_background, approval_id=str(approval.id), - risk_level=risk_level, + risk_level=risk_level.value, resource_name=target_resource, root_cause=root_cause, - suggested_action=analysis_result.suggested_action.value, # 使用 enum value + suggested_action=analysis_result.suggested_action.value, estimated_downtime=estimated_downtime, hit_count=1, primary_responsibility=primary_responsibility, confidence=confidence, namespace=namespace, - signoz_rps=signoz_metrics.get("rps", 0) if signoz_metrics else 0, - signoz_rps_trend=signoz_metrics.get("rps_trend", "stable") if signoz_metrics else "stable", - signoz_error_rate=signoz_metrics.get("error_rate", 0) if signoz_metrics else 0, - signoz_p99_latency=signoz_metrics.get("p99_latency", 0) if signoz_metrics else 0, - signoz_latency_trend=signoz_metrics.get("latency_trend", "stable") if signoz_metrics else "stable", + signoz_rps=signoz_metrics.rps if signoz_metrics else 0, + signoz_rps_trend=signoz_metrics.rps_trend if signoz_metrics else "stable", + signoz_error_rate=signoz_metrics.error_rate if signoz_metrics else 0, + signoz_p99_latency=signoz_metrics.p99_latency_ms if signoz_metrics else 0, + signoz_latency_trend=signoz_metrics.latency_trend if signoz_metrics else "stable", signoz_trace_url=signoz_trace_url or "", ) @@ -1245,26 +1268,29 @@ async def alertmanager_webhook( alert_id=alert_id, approval_created=True, approval_id=str(approval.id), - risk_level=risk_level, - suggested_action=action, + risk_level=risk_level.value, + suggested_action=approval_create.action, hit_count=1, converged=False, ) else: # LLM 失敗 - 使用預設值 - approval = await service.create_approval( - alert_type=alert_type, - target=target_resource, + fallback_create = ApprovalRequestCreate( action="OBSERVE", - risk_level="medium", - details={ - "source": "alertmanager", - "alertname": alertname, - "namespace": namespace, - "message": message, - "ai_analysis": None, - "ai_error": "LLM analysis failed", - }, + description=f"[LLM Failed] {message}", + risk_level=RiskLevel.MEDIUM, + blast_radius=BlastRadius( + affected_pods=1, + estimated_downtime="unknown", + related_services=[], + data_impact=DataImpact.NONE, + ), + dry_run_checks=[], + requested_by="OpenClaw (fallback)", + ) + + approval = await service.create_approval_with_fingerprint( + request=fallback_create, fingerprint=fingerprint, )