Spaces:
Sleeping
Sleeping
File size: 6,846 Bytes
5add236 baa72f5 5add236 baa72f5 5add236 baa72f5 5add236 baa72f5 5add236 baa72f5 5add236 baa72f5 5add236 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 | """End-of-episode deterministic grader.
CRITICAL: All scores are clamped to the strictly open interval (0.001, 0.999).
Never returns exactly 0.0 or 1.0.
"""
from typing import Any, Dict, List, Set
from .config import (
GRADER_WEIGHTS, SCORE_MIN, SCORE_MAX,
Severity, severity_distance,
)
def _clamp(score: float) -> float:
"""Clamp score to strictly open interval (SCORE_MIN, SCORE_MAX)."""
if score <= 0.0:
return SCORE_MIN
if score >= 1.0:
return SCORE_MAX
# Also clamp values very close to boundaries
return max(SCORE_MIN, min(SCORE_MAX, score))
def _pair_set_f1(
agent_incidents: Dict[str, Set[str]],
true_incidents: Dict[str, List[str]],
) -> float:
"""Compute pair-set F1 over incident groupings."""
if not true_incidents:
return 1.0 # Vacuously correct
# True pairs
true_pairs = set()
for label, ids in true_incidents.items():
sids = sorted(ids)
for i in range(len(sids)):
for j in range(i + 1, len(sids)):
true_pairs.add((sids[i], sids[j]))
if not true_pairs:
return 1.0
# Agent pairs
agent_pairs = set()
for label, ids in agent_incidents.items():
sids = sorted(ids)
for i in range(len(sids)):
for j in range(i + 1, len(sids)):
agent_pairs.add((sids[i], sids[j]))
if not agent_pairs:
return 0.0
tp = len(true_pairs & agent_pairs)
precision = tp / len(agent_pairs) if agent_pairs else 0.0
recall = tp / len(true_pairs) if true_pairs else 0.0
if precision + recall == 0:
return 0.0
return 2 * precision * recall / (precision + recall)
def compute_grader_score(
task_id: str,
ground_truth: Dict[str, Dict],
agent_decisions: Dict[str, Dict],
agent_incidents: Dict[str, Set[str]],
true_incidents: Dict[str, List[str]],
skipped_alerts: Set[str],
stealth_root_service: str = None,
original_alert_ids: Set[str] = None,
) -> float:
"""Compute the final grader score for an episode.
Returns a score in the strictly open interval (0.001, 0.999).
"""
weights = GRADER_WEIGHTS[task_id]
# Only score original alerts (exclude cascade-spawned alerts)
if original_alert_ids:
scoreable_ids = original_alert_ids
else:
scoreable_ids = set(ground_truth.keys())
total_alerts = len(scoreable_ids)
if total_alerts == 0:
return SCORE_MIN
# Separate real alerts and false alarms
real_alert_ids = set()
false_alarm_ids = set()
for aid in scoreable_ids:
gt = ground_truth.get(aid, {})
if gt.get("is_false_alarm", False):
false_alarm_ids.add(aid)
else:
real_alert_ids.add(aid)
# ββ Root cause accuracy ββ
rc_correct = 0
for aid in scoreable_ids:
gt = ground_truth[aid]
if aid in skipped_alerts:
# Skipped = agent says false alarm
if gt["is_false_alarm"]:
rc_correct += 1
elif aid in agent_decisions:
dec = agent_decisions[aid]
if dec.get("root_cause") == gt["root_cause"]:
rc_correct += 1
# else: untriaged = incorrect
root_cause_acc = rc_correct / total_alerts
# ββ Severity accuracy ββ
sev_score_sum = 0.0
for aid in scoreable_ids:
gt = ground_truth[aid]
if aid in skipped_alerts:
if gt["is_false_alarm"]:
sev_score_sum += 1.0 # Correct skip
# else: wrong skip = 0
elif aid in agent_decisions:
dec = agent_decisions[aid]
try:
agent_sev = Severity(dec.get("severity", ""))
true_sev = Severity(gt["severity"])
dist = severity_distance(agent_sev, true_sev)
if dist == 0:
sev_score_sum += 1.0
elif dist == 1:
sev_score_sum += 0.15
except ValueError:
pass
# else: untriaged = 0
severity_acc = sev_score_sum / total_alerts
# ββ Remediation accuracy ββ
rem_correct = 0
for aid in scoreable_ids:
gt = ground_truth[aid]
if aid in skipped_alerts:
if gt["is_false_alarm"]:
rem_correct += 1 # dismiss is correct for FA
elif aid in agent_decisions:
dec = agent_decisions[aid]
if dec.get("remediation") == gt["remediation"]:
rem_correct += 1
remediation_acc = rem_correct / total_alerts
# ββ Incident link F1 ββ
incident_f1 = _pair_set_f1(agent_incidents, true_incidents)
# ββ False alarm accuracy ββ
# FA accuracy = correctly identified as FA (skipped) + correctly identified as real (triaged)
if false_alarm_ids or real_alert_ids:
fa_correct = 0
# Correctly skipped false alarms (agent correctly identified it as FA)
fa_correct += len(false_alarm_ids & skipped_alerts)
# Correctly triaged real alerts (agent correctly identified it as real, not skipped)
for aid in real_alert_ids:
if aid in agent_decisions:
fa_correct += 1
fa_total = len(false_alarm_ids) + len(real_alert_ids)
false_alarm_acc = fa_correct / fa_total if fa_total > 0 else 1.0
else:
false_alarm_acc = 1.0 # Vacuously correct
# ββ Coverage multiplier ββ
covered = (set(agent_decisions.keys()) | skipped_alerts) & scoreable_ids
coverage_count = len(covered)
coverage = coverage_count / total_alerts if total_alerts > 0 else 0.0
coverage_mult = coverage ** 1.5
# ββ Weighted score ββ
base_score = (
weights["root_cause_accuracy"] * root_cause_acc +
weights["severity_accuracy"] * severity_acc +
weights["remediation_accuracy"] * remediation_acc +
weights["incident_link_f1"] * incident_f1 +
weights["false_alarm_accuracy"] * false_alarm_acc
)
score = base_score * coverage_mult
# ββ Stealth bonus (hard only) ββ
if task_id == "hard" and stealth_root_service:
# Check if agent correctly identified the stealth root service
stealth_bonus_count = 0
for aid, gt in ground_truth.items():
if gt.get("is_stealth_root", False) and aid in agent_decisions:
dec = agent_decisions[aid]
if dec.get("root_cause") == gt["root_cause"]:
stealth_bonus_count += 1
# Apply bonus per correct stealth root (capped at 0.05)
score += min(stealth_bonus_count * 0.05, 0.05)
# ββ CRITICAL: Clamp to strictly open interval (0.001, 0.999) ββ
return _clamp(score)
|