Files
planet/backend/app/services/situational_alert_ai_brief.py
linkong 8c204717cd
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.70.0
2026-06-04 17:16:23 +08:00

187 lines
7.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.core.enums import BGPStatus
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 == BGPStatus.ACTIVE.value)
)
bgp_severity_result = await db.execute(
select(BGPIncident.severity, func.count(BGPIncident.id))
.where(BGPIncident.status == BGPStatus.ACTIVE.value)
.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 == BGPStatus.ACTIVE.value)
)
anomaly_type_result = await db.execute(
select(BGPAnomaly.anomaly_type, func.count(BGPAnomaly.id))
.where(BGPAnomaly.status == BGPStatus.ACTIVE.value)
.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