LLM-Powered SIEM Alert Triage: Reduce Noise by 80% A developer built a Python pipeline that uses an LLM to triage SIEM alerts, reducing noise by up to 80%. The system enriches alerts with threat intel context and outputs a structured verdict with severity, disposition, and recommended actions. Your SIEM fires 3,000 alerts on a Tuesday night. Your on-call analyst acknowledges 40 of them. The other 2,960? Noise — mostly. This is the alert fatigue problem, and it gets worse as environments scale. A language model can automate the first-pass triage that currently eats analyst hours, and it can do it with enough context to be genuinely useful rather than adding yet another filter layer. This post walks through a working Python implementation that feeds SIEM alerts to an LLM, enriches them with threat intel context, and outputs a prioritized triage report. Most SIEM rules are threshold-based: if more than N failed logins happen in M minutes from the same IP, fire an alert. This works for known attack patterns but produces two failure modes at scale: Failed SSH login from 185.220.101.47 . It doesn't tell you that this IP is a known Tor exit node, that the targeted user has admin privileges, and that this is the third attempt in 6 hours from that subnet.A language model can synthesize these facts and produce a verdict. It won't replace a human for complex incidents, but it handles the 80% that are obviously benign or obviously high-priority. The pipeline has four steps: We'll use Python with the openai SDK works with any OpenAI-compatible endpoint, including local models , and the output schema is enforced via structured outputs so downstream code doesn't have to parse free text. Before touching the LLM, build good context. Garbage in, garbage out. python import json import ipaddress from dataclasses import dataclass, asdict from typing import Optional @dataclass class AlertContext: raw alert: dict user role: Optional str user dept: Optional str asset criticality: str "low", "medium", "high", "critical" ip reputation: Optional str related alerts 24h: int is business hours: bool def enrich alert alert: dict, user db: dict, asset db: dict - AlertContext: user = user db.get alert.get "username", "" , {} asset = asset db.get alert.get "hostname", "" , {} src ip = alert.get "src ip", "" ip rep = None try: addr = ipaddress.ip address src ip if addr.is private: ip rep = "internal" else: In production: call AbuseIPDB, VirusTotal, Shodan, etc. ip rep = "unknown external" except ValueError: pass return AlertContext raw alert=alert, user role=user.get "role" , user dept=user.get "department" , asset criticality=asset.get "criticality", "medium" , ip reputation=ip rep, related alerts 24h=count related alerts alert, window hours=24 , is business hours=is business hours alert.get "timestamp" , def count related alerts alert: dict, window hours: int - int: Query your SIEM or local alert cache here return 0 def is business hours ts: Optional str - bool: if not ts: return False from datetime import datetime, timezone dt = datetime.fromisoformat ts .astimezone timezone.utc return 8 <= dt.hour < 18 and dt.weekday < 5 The AlertContext object gives the model everything it needs without dumping a 10 KB log blob into the prompt. Now the core piece: sending the enriched alert to a language model and getting a structured verdict back. python from openai import OpenAI from pydantic import BaseModel from enum import Enum class Severity str, Enum : CRITICAL = "critical" HIGH = "high" MEDIUM = "medium" LOW = "low" INFO = "informational" class Disposition str, Enum : ESCALATE = "escalate" MONITOR = "monitor" CLOSE = "close" class TriageResult BaseModel : severity: Severity disposition: Disposition confidence: float 0.0 to 1.0 reasoning: str one concise paragraph recommended actions: list str false positive indicators: list str TRIAGE SYSTEM PROMPT = """You are a senior SOC analyst performing first-pass alert triage. Given an enriched security alert, output a structured triage verdict. Rules: - If asset criticality is "critical", escalate unless you have strong FP evidence. - Off-hours activity on privileged accounts increases severity by one level. - Known internal IPs with a single failed login are almost always false positives. - confidence reflects certainty, not severity. - reasoning: one paragraph, max 80 words, no bullet points. - recommended actions: max 3 concrete next steps for an analyst. - false positive indicators: specific facts suggesting benign activity.""" def triage alert ctx: AlertContext, client: OpenAI, model: str = "gpt-4o-mini" - TriageResult: payload = json.dumps asdict ctx , indent=2, default=str response = client.beta.chat.completions.parse model=model, messages= {"role": "system", "content": TRIAGE SYSTEM PROMPT}, {"role": "user", "content": f"Triage this alert:\n\n{payload}"} , response format=TriageResult, temperature=0.1, return response.choices 0 .message.parsed Point at a local model to avoid sending alert data externally client = OpenAI base url="http://localhost:11434/v1", api key="ollama" sample alert = { "alert id": "A-2026-04421", "rule": "Multiple Failed SSH Logins", "username": "j.martin", "hostname": "prod-db-01", "src ip": "185.220.101.47", "timestamp": "2026-07-28T02:14:33+00:00", "failed attempts": 12, } user db = {"j.martin": {"role": "DBA", "department": "Engineering"}} asset db = {"prod-db-01": {"criticality": "critical"}} ctx = enrich alert sample alert, user db, asset db result = triage alert ctx, client print f" {result.severity.upper } → {result.disposition}" print f"Confidence: {result.confidence:.0%}" print f"Reasoning: {result.reasoning}" for action in result.recommended actions: print f" • {action}" For this sample alert — Tor exit node, DBA account, production database, 2 AM — a properly prompted model outputs severity=critical, disposition=escalate with clear reasoning about the combination of off-hours timing, privileged account, and known malicious IP source. Individual triage is useful; batch processing is where it delivers ROI. Process the overnight alert queue before the morning shift arrives: python import asyncio async def batch triage alerts: list dict , client: OpenAI, user db: dict, asset db: dict, concurrency: int = 5, - list tuple dict, TriageResult : semaphore = asyncio.Semaphore concurrency async def triage one alert: dict - tuple dict, TriageResult : async with semaphore: ctx = enrich alert alert, user db, asset db loop = asyncio.get event loop result = await loop.run in executor None, triage alert, ctx, client return alert, result return await asyncio.gather triage one a for a in alerts async def main : alerts = load alerts from siem Your SIEM API or export results = await batch triage alerts, client, user db, asset db to escalate = a, r for a, r in results if r.disposition == Disposition.ESCALATE to close = a, r for a, r in results if r.disposition == Disposition.CLOSE print f"Total: {len alerts }" print f"Escalate: {len to escalate } {len to escalate /len alerts :.0%} " print f"Auto-close: {len to close } {len to close /len alerts :.0%} " for alert, result in to escalate: create ticket alert, result for alert, result in to close: auto close alert alert, result asyncio.run main The concurrency=5 cap prevents rate limiting on the LLM API. Tune it based on your provider's limits or the hardware running your local model. Don't deploy this without a feedback loop. Track three numbers: Feed disagreements back into your system prompt as concrete examples. After two weeks of tuning on your environment's specific noise profile, 80% auto-close rates with under 1% false negative rate are realistic — this matches results from teams running similar pipelines in production. For a baseline of what "good" triage looks like per alert category, the security hardening checklists https://ayinedjimi-consultants.fr/checklists we publish include SOC triage playbooks you can adapt directly as prompt templates. LLM-powered alert triage is one of the highest-ROI applications of language models in security operations right now. The reason is straightforward: alert triage is fundamentally a classification and reasoning task over structured context, which is exactly what these models do well. The implementation here runs entirely on a local model if you can't send alert data to an external API — just point base url at your Ollama or vLLM endpoint. Start with a pilot on a single rule category like failed logins, measure results for two weeks, then expand. The hard part isn't the code. It's building the enrichment layer with meaningful context, writing a system prompt that reflects your actual triage playbooks, and creating the feedback loop that catches model mistakes before they become missed incidents. I run AYI NEDJIMI Consultants, a cybersecurity consulting firm. We publish free security hardening checklists — PDF and Excel.