🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

Build an AI Observability Alert Noise Reducer with Claude API

Alert fatigue is a data problem before it's a culture problem — most teams have never actually measured which alerts fire often and get dismissed. Build a tool that analyzes historical alert-to-resolution patterns with Claude API and recommends specific tuning, not generic advice.

Shubham4 min read
Share:Tweet

"We have alert fatigue" is a common complaint with an uncommonly specific fix available: your alerting system already has the data to show exactly which alerts fire without leading to action, and by how much they should be adjusted. This tool turns Alertmanager's own history into concrete, per-alert tuning recommendations instead of a vague team retro about "being better about alerts."

Setup

bash
pip install anthropic requests

Alert History Analyzer

python
import anthropic
import requests
from collections import defaultdict
from datetime import datetime, timedelta
 
client = anthropic.Anthropic()
 
 
def get_alert_history(days: int = 90) -> list[dict]:
    """Pull historical alert firing/resolution data from Alertmanager."""
    resp = requests.get(
        "http://alertmanager:9093/api/v2/alerts/history",
        params={"since": (datetime.utcnow() - timedelta(days=days)).isoformat()}
    )
    return resp.json()
 
 
def compute_alert_stats(history: list[dict]) -> dict:
    """Aggregate per-alert-rule firing frequency, duration, and — critically —
    whether it ever led to a PagerDuty acknowledgment vs auto-resolved unattended."""
    stats = defaultdict(lambda: {"fire_count": 0, "durations": [], "was_acked": []})
 
    for event in history:
        rule = event["labels"]["alertname"]
        stats[rule]["fire_count"] += 1
        stats[rule]["durations"].append(event.get("duration_seconds", 0))
        stats[rule]["was_acked"].append(event.get("acknowledged", False))
 
    return dict(stats)

Cross-Referencing With Actual Action Taken

python
def get_incident_correlation(alert_rule: str, days: int = 90) -> dict:
    """Check PagerDuty history — did this alert ever correlate with an
    incident that got a real human response, or does it always
    auto-resolve with nobody looking at it?"""
    resp = requests.get(
        "https://api.pagerduty.com/incidents",
        headers={"Authorization": f"Token token={PAGERDUTY_TOKEN}"},
        params={"since": (datetime.utcnow() - timedelta(days=days)).isoformat()}
    )
    incidents = resp.json()["incidents"]
    matching = [i for i in incidents if alert_rule in i.get("title", "")]
 
    return {
        "total_incidents": len(matching),
        "resolved_by_human_action": len([i for i in matching if i.get("resolve_reason") == "manual"]),
        "auto_resolved": len([i for i in matching if i.get("resolve_reason") == "timeout"]),
    }

Tuning Recommendations With Claude

python
import json
 
ANALYZE_PROMPT = """Analyze this alert rule's firing history and recommend
specific tuning.
 
Alert rule: {rule_name}
Current threshold config: {current_config}
 
Firing statistics (last 90 days):
- Total fires: {fire_count}
- Average duration before auto-resolve: {avg_duration}s
- Percentage that were acknowledged by a human: {ack_rate}%
 
Incident correlation:
- Times this led to a PagerDuty incident: {total_incidents}
- Of those, resolved by actual human action (not auto-timeout): {resolved_by_human}
 
Determine:
1. Is this alert too sensitive (fires often, rarely acknowledged, rarely
   leads to real action) — recommend a specific threshold/duration adjustment
2. Is this alert well-tuned (fires rarely, but when it does, leads to
   real action) — leave alone, flag as healthy
3. Is this alert potentially UNDER-sensitive (rarely fires, but incident
   history suggests real problems happened without it firing) — recommend
   tightening
 
Respond with ONLY valid JSON:
{{"verdict": "too_noisy" | "well_tuned" | "under_sensitive",
  "recommended_change": "specific config change or null",
  "reasoning": "..."}}"""
 
 
def analyze_alert(rule_name: str, current_config: dict, stats: dict, correlation: dict) -> dict:
    ack_rate = (sum(stats["was_acked"]) / len(stats["was_acked"]) * 100) if stats["was_acked"] else 0
    avg_duration = sum(stats["durations"]) / len(stats["durations"]) if stats["durations"] else 0
 
    response = client.messages.create(
        model="claude-sonnet-5",
        max_tokens=500,
        messages=[{
            "role": "user",
            "content": ANALYZE_PROMPT.format(
                rule_name=rule_name, current_config=current_config,
                fire_count=stats["fire_count"], avg_duration=round(avg_duration, 1), ack_rate=round(ack_rate, 1),
                total_incidents=correlation["total_incidents"],
                resolved_by_human=correlation["resolved_by_human_action"],
            )
        }]
    )
    text = response.content[0].text.strip()
    if text.startswith("```"):
        text = text.split("```")[1].replace("json", "", 1).strip()
    return json.loads(text)

Example Output

Alert Noise Report — Last 90 Days

TOO NOISY (6 rules):
- HighMemoryUsage: fired 340 times, 4% acknowledgment rate, 0 incidents
  led to human action. Recommendation: raise threshold from 80% to 90%
  memory utilization, and require sustained breach for 15min (currently
  5min) — the current config is catching normal GC-related memory sawtooth
  patterns, not real problems.

- SlowQueryWarning: fired 210 times, 8% acknowledgment rate. Recommendation:
  this fires on the reporting service's known-slow analytical queries —
  either exclude that service from this rule or raise the threshold
  specifically for it.

WELL TUNED (12 rules): no changes recommended

UNDER-SENSITIVE (1 rule):
- DatabaseReplicationLag: fired only 2 times in 90 days, but incident
  history shows 3 separate replication-lag-caused incidents where this
  alert never fired. Recommendation: lower the lag threshold from 60s
  to 20s based on the incident pattern — the current threshold is too
  permissive to catch the lag levels that actually caused problems.

Why This Beats a Generic "Reduce Alert Fatigue" Initiative

The recurring failure of alert-fatigue cleanup efforts is that they're vague — "let's review our alerts" turns into an unstructured meeting nobody prepares real data for. This tool converts that into a ranked, evidence-backed list: exactly which rules to change, by how much, and why — grounded in your own system's actual firing and resolution history, not generic alerting best practices that may not fit your specific traffic patterns.


More AI observability tooling? Read our Build AI log pattern classifier with Claude API and Prometheus Grafana monitoring guide.

🔧

Today I Fixed

Short real fixes from production — posted daily

Browse fixes
Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments