Kushal Shah commited on
Commit ·
00da95b
1
Parent(s): cd04de2
Add Agentic Incident Response gradio app
Browse files- .gitignore +4 -0
- README.md +17 -1
- agents.py +92 -0
- app.py +101 -0
- data/docs/runbook.md +16 -0
- data/logs/server_logs.txt +8 -0
- data/tickets/incident_ticket.json +8 -0
- requirements.txt +3 -0
- tools.py +26 -0
.gitignore
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.env
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.pyc
|
| 4 |
+
.venv/
|
README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
---
|
| 2 |
title: Autonomous System Diagnostics
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: red
|
| 6 |
sdk: gradio
|
|
@@ -10,4 +10,20 @@ app_file: app.py
|
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
|
| 1 |
---
|
| 2 |
title: Autonomous System Diagnostics
|
| 3 |
+
emoji: 🛰️
|
| 4 |
colorFrom: indigo
|
| 5 |
colorTo: red
|
| 6 |
sdk: gradio
|
|
|
|
| 10 |
pinned: false
|
| 11 |
---
|
| 12 |
|
| 13 |
+
# Autonomous System Diagnostics & Remediation
|
| 14 |
+
|
| 15 |
+
A multi-agent incident response system powered by Gemini. Paste in server logs,
|
| 16 |
+
an incident ticket, and a runbook, and a chain of specialized agents will:
|
| 17 |
+
|
| 18 |
+
1. **Log Analysis** – surface anomalies and error patterns
|
| 19 |
+
2. **Incident Correlation** – check the logs against the reported ticket
|
| 20 |
+
3. **Root Cause Analysis** – pinpoint the cause using the runbook
|
| 21 |
+
4. **Resolution** – propose a step-by-step remediation plan
|
| 22 |
+
5. **Reporting** – generate a post-incident report (PIR)
|
| 23 |
+
|
| 24 |
+
## Setup
|
| 25 |
+
|
| 26 |
+
This Space requires a `GEMINI_API_KEY`. Add it under
|
| 27 |
+
**Settings → Variables and secrets** as a repository secret.
|
| 28 |
+
|
| 29 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
agents.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import google.generativeai as genai
|
| 3 |
+
from dotenv import load_dotenv
|
| 4 |
+
|
| 5 |
+
# Load env variables
|
| 6 |
+
load_dotenv()
|
| 7 |
+
|
| 8 |
+
class IncidentAgents:
|
| 9 |
+
def __init__(self):
|
| 10 |
+
# Configure Gemini
|
| 11 |
+
api_key = os.getenv("GEMINI_API_KEY")
|
| 12 |
+
if not api_key:
|
| 13 |
+
raise ValueError("GEMINI_API_KEY not found in .env file")
|
| 14 |
+
|
| 15 |
+
genai.configure(api_key=api_key)
|
| 16 |
+
|
| 17 |
+
# We use Gemini 1.5 Flash for speed and efficiency
|
| 18 |
+
self.model_name = "gemini-2.5-flash"
|
| 19 |
+
|
| 20 |
+
def _call_llm(self, role_description, user_content):
|
| 21 |
+
"""Helper to call Gemini API"""
|
| 22 |
+
|
| 23 |
+
# Initialize the model with a system instruction (Role)
|
| 24 |
+
model = genai.GenerativeModel(
|
| 25 |
+
model_name=self.model_name,
|
| 26 |
+
system_instruction=role_description
|
| 27 |
+
)
|
| 28 |
+
|
| 29 |
+
# Generate content
|
| 30 |
+
try:
|
| 31 |
+
response = model.generate_content(user_content)
|
| 32 |
+
return response.text
|
| 33 |
+
except Exception as e:
|
| 34 |
+
return f"Error communicating with Gemini: {e}"
|
| 35 |
+
|
| 36 |
+
# --- AGENT 1: Log Analyst ---
|
| 37 |
+
def log_analysis_agent(self, logs):
|
| 38 |
+
print("🔎 Log Analysis Agent Working...")
|
| 39 |
+
role = """
|
| 40 |
+
You are a Senior Site Reliability Engineer (SRE).
|
| 41 |
+
Your task is to analyze server logs.
|
| 42 |
+
Identify patterns of errors, timestamps of failure start, and specific error messages.
|
| 43 |
+
Output a concise summary of the anomalies found.
|
| 44 |
+
"""
|
| 45 |
+
return self._call_llm(role, f"Analyze these logs:\n{logs}")
|
| 46 |
+
|
| 47 |
+
# --- AGENT 2: Incident Correlator ---
|
| 48 |
+
def incident_correlator_agent(self, ticket, log_analysis):
|
| 49 |
+
print("🔗 Incident Correlator Agent Working...")
|
| 50 |
+
role = """
|
| 51 |
+
You are an Incident Commander.
|
| 52 |
+
Correlate the user-reported incident ticket with the technical log analysis.
|
| 53 |
+
Confirm if the logs support the ticket description.
|
| 54 |
+
"""
|
| 55 |
+
content = f"Ticket:\n{ticket}\n\nLog Analysis:\n{log_analysis}"
|
| 56 |
+
return self._call_llm(role, content)
|
| 57 |
+
|
| 58 |
+
# --- AGENT 3: Root Cause Analyst (RCA) ---
|
| 59 |
+
def root_cause_agent(self, correlation_findings, runbook):
|
| 60 |
+
print("🧠 Root Cause Agent Working...")
|
| 61 |
+
role = """
|
| 62 |
+
You are a Root Cause Analysis Expert.
|
| 63 |
+
Using the incident correlation and the provided Engineering Runbook, determine the most likely root cause.
|
| 64 |
+
Cite the specific section of the runbook that matches the symptoms.
|
| 65 |
+
"""
|
| 66 |
+
content = f"Findings:\n{correlation_findings}\n\nRunbook:\n{runbook}"
|
| 67 |
+
return self._call_llm(role, content)
|
| 68 |
+
|
| 69 |
+
# --- AGENT 4: Resolution Agent ---
|
| 70 |
+
def resolution_agent(self, root_cause_analysis, runbook):
|
| 71 |
+
print("🛠️ Resolution Agent Working...")
|
| 72 |
+
role = """
|
| 73 |
+
You are a DevOps Automation Engineer.
|
| 74 |
+
Based on the identified root cause and the runbook, generate a step-by-step remediation plan.
|
| 75 |
+
If the runbook has specific commands, include them.
|
| 76 |
+
"""
|
| 77 |
+
content = f"RCA:\n{root_cause_analysis}\n\nRunbook Content:\n{runbook}"
|
| 78 |
+
return self._call_llm(role, content)
|
| 79 |
+
|
| 80 |
+
# --- AGENT 5: Report Generator ---
|
| 81 |
+
def report_agent(self, ticket, rca, resolution):
|
| 82 |
+
print("📝 Report Agent Working...")
|
| 83 |
+
role = """
|
| 84 |
+
You are a Technical Writer.
|
| 85 |
+
Generate a professional Post-Incident Report (PIR) in Markdown format.
|
| 86 |
+
Include:
|
| 87 |
+
1. Executive Summary
|
| 88 |
+
2. Root Cause
|
| 89 |
+
3. Remediation Taken/Suggested
|
| 90 |
+
"""
|
| 91 |
+
content = f"Ticket: {ticket}\nRCA: {rca}\nResolution: {resolution}"
|
| 92 |
+
return self._call_llm(role, content)
|
app.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
import json
|
| 4 |
+
from dotenv import load_dotenv
|
| 5 |
+
from agents import IncidentAgents
|
| 6 |
+
from tools import read_log_file, read_ticket_data, read_runbook
|
| 7 |
+
|
| 8 |
+
# Load environment variables
|
| 9 |
+
load_dotenv()
|
| 10 |
+
|
| 11 |
+
# --- DEFAULT DATA (sample incident, used to pre-fill the UI) ---
|
| 12 |
+
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
| 13 |
+
|
| 14 |
+
DEFAULT_LOGS = read_log_file(os.path.join(BASE_DIR, "data", "logs", "server_logs.txt"))
|
| 15 |
+
DEFAULT_TICKET = json.dumps(
|
| 16 |
+
read_ticket_data(os.path.join(BASE_DIR, "data", "tickets", "incident_ticket.json")),
|
| 17 |
+
indent=2
|
| 18 |
+
)
|
| 19 |
+
DEFAULT_RUNBOOK = read_runbook(os.path.join(BASE_DIR, "data", "docs", "runbook.md"))
|
| 20 |
+
|
| 21 |
+
# --- 2. THE CORE WORKFLOW FUNCTION ---
|
| 22 |
+
def run_incident_response(logs_input, ticket_input, runbook_input):
|
| 23 |
+
"""
|
| 24 |
+
This function connects the UI inputs to the Agent Logic.
|
| 25 |
+
"""
|
| 26 |
+
try:
|
| 27 |
+
# Initialize Agents
|
| 28 |
+
agents = IncidentAgents()
|
| 29 |
+
|
| 30 |
+
# Step A: Log Analysis
|
| 31 |
+
log_analysis = agents.log_analysis_agent(logs_input)
|
| 32 |
+
yield log_analysis, "...", "...", "...", "..." # Stream updates to UI
|
| 33 |
+
|
| 34 |
+
# Step B: Correlation
|
| 35 |
+
correlation = agents.incident_correlator_agent(ticket_input, log_analysis)
|
| 36 |
+
yield log_analysis, correlation, "...", "...", "..."
|
| 37 |
+
|
| 38 |
+
# Step C: Root Cause Analysis
|
| 39 |
+
rca = agents.root_cause_agent(correlation, runbook_input)
|
| 40 |
+
yield log_analysis, correlation, rca, "...", "..."
|
| 41 |
+
|
| 42 |
+
# Step D: Resolution
|
| 43 |
+
resolution = agents.resolution_agent(rca, runbook_input)
|
| 44 |
+
yield log_analysis, correlation, rca, resolution, "..."
|
| 45 |
+
|
| 46 |
+
# Step E: Final Report
|
| 47 |
+
final_report = agents.report_agent(ticket_input, rca, resolution)
|
| 48 |
+
yield log_analysis, correlation, rca, resolution, final_report
|
| 49 |
+
|
| 50 |
+
except Exception as e:
|
| 51 |
+
error_msg = f"Error: {str(e)}"
|
| 52 |
+
yield error_msg, error_msg, error_msg, error_msg, error_msg
|
| 53 |
+
|
| 54 |
+
# --- 3. BUILD THE GRADIO UI ---
|
| 55 |
+
with gr.Blocks(title="Agentic Incident Response") as demo:
|
| 56 |
+
|
| 57 |
+
# Header
|
| 58 |
+
gr.Markdown("# AI Agent Incident Response System")
|
| 59 |
+
gr.Markdown("Enter system logs, an incident ticket, and a runbook. The Multi-Agent System will analyze, correlate, and fix the issue.")
|
| 60 |
+
|
| 61 |
+
# Input Section
|
| 62 |
+
with gr.Row():
|
| 63 |
+
with gr.Column():
|
| 64 |
+
logs_box = gr.Textbox(label="1. System Logs", value=DEFAULT_LOGS, lines=8, placeholder="Paste logs here...")
|
| 65 |
+
with gr.Column():
|
| 66 |
+
ticket_box = gr.Textbox(label="2. Incident Ticket (JSON)", value=DEFAULT_TICKET, lines=8, placeholder="Paste ticket JSON here...")
|
| 67 |
+
with gr.Column():
|
| 68 |
+
runbook_box = gr.Textbox(label="3. Runbook (Markdown)", value=DEFAULT_RUNBOOK, lines=8, placeholder="Paste runbook here...")
|
| 69 |
+
|
| 70 |
+
# Action Button
|
| 71 |
+
run_btn = gr.Button("Run Automated Response Workflow", variant="primary")
|
| 72 |
+
|
| 73 |
+
# Output Section (The "Agent Minds")
|
| 74 |
+
gr.Markdown("### Agent Reasoning Chain")
|
| 75 |
+
|
| 76 |
+
with gr.Accordion("Step 1: Log Analysis Agent", open=False):
|
| 77 |
+
out_logs = gr.Markdown()
|
| 78 |
+
|
| 79 |
+
with gr.Accordion("Step 2: Incident Correlator Agent", open=False):
|
| 80 |
+
out_correlation = gr.Markdown()
|
| 81 |
+
|
| 82 |
+
with gr.Accordion("Step 3: Root Cause Analysis (RCA) Agent", open=False):
|
| 83 |
+
out_rca = gr.Markdown()
|
| 84 |
+
|
| 85 |
+
with gr.Accordion("Step 4: Resolution Agent", open=True):
|
| 86 |
+
out_resolution = gr.Markdown()
|
| 87 |
+
|
| 88 |
+
# Final Report Section
|
| 89 |
+
gr.Markdown("### Final Post-Incident Report")
|
| 90 |
+
out_final_report = gr.Markdown()
|
| 91 |
+
|
| 92 |
+
# Click Event
|
| 93 |
+
run_btn.click(
|
| 94 |
+
fn=run_incident_response,
|
| 95 |
+
inputs=[logs_box, ticket_box, runbook_box],
|
| 96 |
+
outputs=[out_logs, out_correlation, out_rca, out_resolution, out_final_report]
|
| 97 |
+
)
|
| 98 |
+
|
| 99 |
+
# Launch
|
| 100 |
+
if __name__ == "__main__":
|
| 101 |
+
demo.launch(theme=gr.themes.Soft())
|
data/docs/runbook.md
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Engineering Runbook: Database Connection Issues
|
| 2 |
+
|
| 3 |
+
## Error: ConnectionPoolTimeoutError
|
| 4 |
+
**Symptoms:** - API returns 500 status codes.
|
| 5 |
+
- Logs show "Timeout waiting for connection from pool".
|
| 6 |
+
- Latency spikes on endpoints /api/v1/users.
|
| 7 |
+
|
| 8 |
+
## Root Causes
|
| 9 |
+
1. **Traffic Spike:** Sudden increase in concurrent users exceeding `MAX_CONNECTIONS`.
|
| 10 |
+
2. **Zombie Connections:** Application not closing connections properly (missing `.close()` or `try/finally` blocks).
|
| 11 |
+
3. **Database Maintenance:** DB is undergoing patches or backups.
|
| 12 |
+
|
| 13 |
+
## Remediation Steps
|
| 14 |
+
1. **Immediate Mitigation:** Restart the application service (`sudo systemctl restart api-service`) to flush the pool.
|
| 15 |
+
2. **Investigation:** Check `pg_stat_activity` for idle transactions.
|
| 16 |
+
3. **Long-term Fix:** Increase `MAX_CONNECTIONS` in `config.yaml` or implement connection pooling proxy (PgBouncer).
|
data/logs/server_logs.txt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
2024-10-24 14:28:10 [INFO] Request received: GET /api/v1/health - 200 OK
|
| 2 |
+
2024-10-24 14:29:15 [INFO] Worker process started (PID 4021)
|
| 3 |
+
2024-10-24 14:30:05 [WARN] Connection pool usage at 85%
|
| 4 |
+
2024-10-24 14:30:45 [WARN] Connection pool usage at 95% - approaching limit
|
| 5 |
+
2024-10-24 14:31:02 [ERROR] [PID 4021] Failed to acquire connection. Error: sqlalchemy.exc.TimeoutError: QueuePool limit of size 10 overflow 10 reached, connection timed out, timeout 30.
|
| 6 |
+
2024-10-24 14:31:02 [ERROR] Request failed: GET /api/v1/users/login - 500 Internal Server Error
|
| 7 |
+
2024-10-24 14:31:05 [ERROR] [PID 4021] Failed to acquire connection.
|
| 8 |
+
2024-10-24 14:31:10 [CRITICAL] Service health check failing. Database unreachable.
|
data/tickets/incident_ticket.json
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ticket_id": "INC-2024-001",
|
| 3 |
+
"priority": "P1",
|
| 4 |
+
"status": "OPEN",
|
| 5 |
+
"title": "API returning 500 errors on User Service",
|
| 6 |
+
"description": "Customer reports unable to login. Monitoring dashboard shows 500 error rate spike starting at 14:30 UTC.",
|
| 7 |
+
"reported_at": "2024-10-24T14:35:00Z"
|
| 8 |
+
}
|
requirements.txt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
google-generativeai
|
| 2 |
+
python-dotenv
|
| 3 |
+
gradio
|
tools.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import json
|
| 3 |
+
|
| 4 |
+
def read_log_file(filepath):
|
| 5 |
+
"""Reads the raw log file."""
|
| 6 |
+
try:
|
| 7 |
+
with open(filepath, 'r') as f:
|
| 8 |
+
return f.read()
|
| 9 |
+
except FileNotFoundError:
|
| 10 |
+
return "Error: Log file not found."
|
| 11 |
+
|
| 12 |
+
def read_ticket_data(filepath):
|
| 13 |
+
"""Reads the JSON incident ticket."""
|
| 14 |
+
try:
|
| 15 |
+
with open(filepath, 'r') as f:
|
| 16 |
+
return json.load(f)
|
| 17 |
+
except FileNotFoundError:
|
| 18 |
+
return {"error": "Ticket file not found."}
|
| 19 |
+
|
| 20 |
+
def read_runbook(filepath):
|
| 21 |
+
"""Reads the markdown runbook."""
|
| 22 |
+
try:
|
| 23 |
+
with open(filepath, 'r') as f:
|
| 24 |
+
return f.read()
|
| 25 |
+
except FileNotFoundError:
|
| 26 |
+
return "Error: Runbook not found."
|