186 lines
7.5 KiB
Python
186 lines
7.5 KiB
Python
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
|