from __future__ import annotations from collections import Counter from typing import Any from sqlalchemy import func, select from sqlalchemy.ext.asyncio import AsyncSession from app.models.alert import Alert, AlertSeverity, AlertStatus from app.models.bgp_anomaly import BGPAnomaly from app.models.bgp_incident import BGPIncident from app.schemas.ai import SituationalAnalysisRequest from app.ai_tasks.prompts import get_effective_prompt from app.services.bgp_ai_brief_store import get_latest_bgp_brief_record SITUATIONAL_ALERT_BRIEF_PROMPT_KEY = "alerts.situational.brief" def _format_pairs(pairs: list[tuple[str, int]], empty_text: str = "无") -> str: if not pairs: return empty_text return ",".join(f"{key} {value}" for key, value in pairs if key) async def build_situational_alert_brief_request( db: AsyncSession, ) -> tuple[SituationalAnalysisRequest, list[str], dict[str, Any]]: total_alerts_result = await db.execute(select(func.count(Alert.id))) active_alerts_result = await db.execute( select(func.count(Alert.id)).where(Alert.status == AlertStatus.ACTIVE) ) alert_severity_result = await db.execute( select(Alert.severity, func.count(Alert.id)) .where(Alert.status == AlertStatus.ACTIVE) .group_by(Alert.severity) ) alert_source_result = await db.execute( select(Alert.datasource_name, func.count(Alert.id)) .where(Alert.status == AlertStatus.ACTIVE) .group_by(Alert.datasource_name) .order_by(func.count(Alert.id).desc()) .limit(6) ) recent_alerts_result = await db.execute( select(Alert) .order_by(Alert.created_at.desc(), Alert.id.desc()) .limit(6) ) total_incidents_result = await db.execute(select(func.count(BGPIncident.id))) active_incidents_result = await db.execute( select(func.count(BGPIncident.id)).where(BGPIncident.status == "active") ) bgp_severity_result = await db.execute( select(BGPIncident.severity, func.count(BGPIncident.id)) .where(BGPIncident.status == "active") .group_by(BGPIncident.severity) ) bgp_region_counter: Counter[str] = Counter() recent_incidents_result = await db.execute( select(BGPIncident) .order_by(BGPIncident.created_at.desc(), BGPIncident.id.desc()) .limit(5) ) total_anomalies_result = await db.execute(select(func.count(BGPAnomaly.id))) active_anomalies_result = await db.execute( select(func.count(BGPAnomaly.id)).where(BGPAnomaly.status == "active") ) anomaly_type_result = await db.execute( select(BGPAnomaly.anomaly_type, func.count(BGPAnomaly.id)) .where(BGPAnomaly.status == "active") .group_by(BGPAnomaly.anomaly_type) .order_by(func.count(BGPAnomaly.id).desc()) .limit(6) ) recent_incidents = recent_incidents_result.scalars().all() for incident in recent_incidents: for region in incident.affected_regions or []: if not isinstance(region, dict): continue label = ", ".join(part for part in [region.get("city"), region.get("country")] if part) or "未知区域" bgp_region_counter[label] += 1 latest_bgp_brief = get_latest_bgp_brief_record() active_alert_severities = [ (item[0].value if isinstance(item[0], AlertSeverity) else str(item[0]), item[1]) for item in alert_severity_result.fetchall() if item[0] ] active_bgp_severities = [ (str(item[0]), item[1]) for item in bgp_severity_result.fetchall() if item[0] ] active_anomaly_types = [(str(item[0]), item[1]) for item in anomaly_type_result.fetchall() if item[0]] active_alert_sources = [ (str(item[0] or "未命名数据源"), item[1]) for item in alert_source_result.fetchall() ] total_alerts = total_alerts_result.scalar() or 0 active_alerts = active_alerts_result.scalar() or 0 total_incidents = total_incidents_result.scalar() or 0 active_incidents = active_incidents_result.scalar() or 0 total_anomalies = total_anomalies_result.scalar() or 0 active_anomalies = active_anomalies_result.scalar() or 0 facts = [ ( f"系统告警侧:总告警 {total_alerts} 条,active {active_alerts} 条;" f"活跃告警严重度分布为 {_format_pairs(active_alert_severities)}。" ), ( f"BGP态势侧:累计 incidents {total_incidents} 条,active incidents {active_incidents} 条;" f"活跃 incidents 严重度分布为 {_format_pairs(active_bgp_severities)}。" ), ( f"BGP异常侧:累计 anomalies {total_anomalies} 条,active anomalies {active_anomalies} 条;" f"活跃 anomaly 类型分布为 {_format_pairs(active_anomaly_types)}。" ), ] if active_alert_sources: facts.append(f"当前系统告警主要集中在:{_format_pairs(active_alert_sources)}。") if bgp_region_counter: facts.append(f"BGP近期高风险区域线索:{_format_pairs(bgp_region_counter.most_common(5))}。") recent_alerts = recent_alerts_result.scalars().all() if recent_alerts: facts.append( "最近系统告警摘录:" + ";".join( [ f"{alert.datasource_name or '未命名数据源'} / {alert.severity.value if alert.severity else '-'} / {alert.status.value if alert.status else '-'} / {alert.message or '-'}" for alert in recent_alerts ] ) ) if recent_incidents: facts.append( "最近BGP事件摘录:" + ";".join( [ f"{incident.incident_type} / {incident.severity} / {incident.status} / {incident.summary}" for incident in recent_incidents ] ) ) if latest_bgp_brief: facts.append( f"最近一份 BGP AI 简报生成于 {latest_bgp_brief.generated_at},模型 {latest_bgp_brief.model},可作为当前态势的补充说明。" ) context = { "source": "situational-alerts", "active_system_alerts": active_alerts, "active_system_alert_severities": dict(active_alert_severities), "top_system_alert_sources": dict(active_alert_sources), "active_bgp_incidents": active_incidents, "active_bgp_incident_severities": dict(active_bgp_severities), "active_bgp_anomalies": active_anomalies, "active_bgp_anomaly_types": dict(active_anomaly_types), "bgp_hot_regions": dict(bgp_region_counter.most_common(5)), "latest_bgp_brief_id": latest_bgp_brief.id if latest_bgp_brief else None, "latest_bgp_brief_generated_at": latest_bgp_brief.generated_at if latest_bgp_brief else None, } prompt = await get_effective_prompt(db, SITUATIONAL_ALERT_BRIEF_PROMPT_KEY) request = SituationalAnalysisRequest( title="态势告警 AI 简报", objective=prompt.prompt, system_prompt=prompt.system_prompt or None, observations=facts, constraints=[ "明确区分事实、推断与建议。", "优先指出仍在 active 状态的系统告警与 BGP 风险是否存在联动。", "不要把单一数据源的局部异常夸大成全局态势。", "如果证据不足,请明确写出仍缺哪些模块或区域信息。", ], context=context, ) return request, facts, context