sammoftah's picture
Add no-token fallback
640ce83 verified
"""
GitHub Issue Triager
Agentic workflow: Analyze β†’ Classify β†’ Prioritize β†’ Assign
"""
import gradio as gr
from huggingface_hub import InferenceClient
import json
import os
import sys
import time
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from shared.components import create_method_panel, create_premium_hero
# Initialize client
client = InferenceClient(model="meta-llama/Llama-3.3-70B-Instruct")
def triage_issue(issue_text: str) -> dict:
"""Analyze and triage a GitHub issue"""
if not os.getenv("HF_TOKEN"):
text = issue_text.lower()
labels = []
if any(word in text for word in ["crash", "error", "bug", "broken", "fails", "failure"]):
labels.append("bug")
if any(word in text for word in ["slow", "latency", "performance", "timeout"]):
labels.append("performance")
if any(word in text for word in ["docs", "readme", "documentation"]):
labels.append("documentation")
if any(word in text for word in ["feature", "request", "support", "add"]):
labels.append("feature")
if not labels:
labels = ["question"]
priority = "P1" if any(word in text for word in ["data loss", "security", "cannot login", "down", "crash"]) else "P2"
severity = "High" if priority == "P1" else "Medium"
title = issue_text.strip().splitlines()[0][:90] if issue_text.strip() else "Issue report"
return {
"title": title,
"labels": labels[:4],
"priority": priority,
"severity": severity,
"category": "Backend" if any(word in text for word in ["api", "database", "server"]) else "Frontend" if any(word in text for word in ["ui", "button", "page"]) else "Other",
"estimated_effort": "Medium (1-3 days)" if priority == "P1" else "Small (< 1 day)",
"suggested_assignee": "Maintainer review",
"reasoning": {
"label_reasoning": "Deterministic fallback based on issue keywords because HF_TOKEN is not configured.",
"priority_reasoning": "Priority estimated from impact words such as crash, security, or data loss.",
"effort_reasoning": "Fallback estimate; refine after reproduction."
},
"related_areas": ["triage", "reproduction"],
"next_steps": ["Reproduce the issue", "Confirm expected behavior", "Assign owner", "Add regression test if this is a bug"]
}
prompt = f"""You are a GitHub issue triager. Analyze this issue and provide structured triage information.
ISSUE:
{issue_text}
Provide a JSON response with:
{{
"title": "Brief title extracted from issue",
"labels": ["bug", "feature", "question", "documentation", "enhancement", "performance"],
"priority": "P0/P1/P2/P3",
"severity": "Critical/High/Medium/Low",
"category": "Frontend/Backend/DevOps/Documentation/Other",
"estimated_effort": "Small (< 1 day)/Medium (1-3 days)/Large (> 3 days)",
"suggested_assignee": "Team/person based on issue type",
"reasoning": {{
"label_reasoning": "Why these labels",
"priority_reasoning": "Why this priority (consider: impact, urgency, user-facing)",
"effort_reasoning": "Why this effort estimate"
}},
"related_areas": ["area1", "area2"],
"next_steps": ["Step 1", "Step 2"]
}}
Priority definitions:
- P0: Critical bug, service down, data loss
- P1: Major bug, significant feature broken, high user impact
- P2: Minor bug, feature request with clear value
- P3: Nice-to-have, low impact
Choose only the most relevant labels (2-4 max)."""
response = ""
for message in client.chat_completion(
messages=[{"role": "user", "content": prompt}],
max_tokens=1200,
stream=True,
):
response += message.choices[0].delta.content or ""
# Extract JSON
try:
if "```json" in response:
response = response.split("```json")[1].split("```")[0].strip()
elif "```" in response:
response = response.split("```")[1].split("```")[0].strip()
data = json.loads(response)
return data
except Exception as e:
return {
"title": "Error parsing issue",
"labels": [],
"priority": "P3",
"severity": "Low",
"category": "Other",
"estimated_effort": "Unknown",
"suggested_assignee": "TBD",
"reasoning": {
"label_reasoning": "Error occurred",
"priority_reasoning": "Error occurred",
"effort_reasoning": "Error occurred"
},
"related_areas": [],
"next_steps": []
}
def process_triage(issue_text: str, progress=gr.Progress()):
"""Main issue triage workflow"""
if not issue_text.strip():
return "Please paste an issue description."
# Step 1: Analyze issue
progress(0.2, desc="Analyzing issue content...")
time.sleep(0.3)
# Step 2: Classify and prioritize
progress(0.5, desc="Classifying and prioritizing...")
triage = triage_issue(issue_text)
# Step 3: Generate recommendations
progress(0.8, desc="Generating recommendations...")
time.sleep(0.3)
# Format output
priority_emoji = {
"P0": "πŸ”΄",
"P1": "🟠",
"P2": "🟑",
"P3": "🟒"
}
severity_emoji = {
"Critical": "🚨",
"High": "⚠️",
"Medium": "πŸ“Š",
"Low": "ℹ️"
}
output = f"# Issue Triage Report\n\n"
output += f"## {triage['title']}\n\n"
# Priority and Severity
output += "### 🎯 Priority & Severity\n\n"
p_emoji = priority_emoji.get(triage['priority'], "βšͺ")
s_emoji = severity_emoji.get(triage['severity'], "ℹ️")
output += f"- **Priority**: {p_emoji} **{triage['priority']}**\n"
output += f"- **Severity**: {s_emoji} {triage['severity']}\n"
output += f"- **Category**: {triage['category']}\n"
output += f"- **Estimated Effort**: {triage['estimated_effort']}\n\n"
# Labels
output += "### 🏷️ Suggested Labels\n\n"
for label in triage['labels']:
# Color code labels
label_colors = {
"bug": "πŸ”΄",
"feature": "🟦",
"question": "🟣",
"documentation": "πŸ“˜",
"enhancement": "✨",
"performance": "⚑"
}
emoji = label_colors.get(label, "πŸ”–")
output += f"- {emoji} `{label}`\n"
output += "\n"
# Assignment
output += "### πŸ‘€ Suggested Assignment\n\n"
output += f"**{triage['suggested_assignee']}**\n\n"
# Related Areas
if triage['related_areas']:
output += "### πŸ”— Related Areas\n\n"
for area in triage['related_areas']:
output += f"- {area}\n"
output += "\n"
# Reasoning
output += "### πŸ’‘ Reasoning\n\n"
output += f"**Labels**: {triage['reasoning']['label_reasoning']}\n\n"
output += f"**Priority**: {triage['reasoning']['priority_reasoning']}\n\n"
output += f"**Effort**: {triage['reasoning']['effort_reasoning']}\n\n"
# Next Steps
if triage['next_steps']:
output += "### βœ… Recommended Next Steps\n\n"
for i, step in enumerate(triage['next_steps'], 1):
output += f"{i}. {step}\n"
output += "\n"
output += "---\n*Generated by Issue Triager Agent*\n"
progress(1.0, desc="Complete!")
return output
# Gradio Interface
with gr.Blocks(theme=gr.themes.Soft(), title="GitHub Issue Triager") as demo:
create_premium_hero(
"GitHub Issue Triager Agent",
"Convert messy issue reports into labels, severity, priority, ownership suggestions, and reasoning.",
"🎫",
badge="Developer Operations",
highlights=["Structured triage", "Reasoned labels", "Agent workflow"],
)
create_method_panel({
"Workflow": "Issue text β†’ classification prompt β†’ JSON parsing β†’ priority and routing report.",
"What it proves": "You can turn LLM output into operational structure, not just prose.",
"HF capability": "Runs as a lightweight Space over Hub-hosted instruction models.",
})
with gr.Row():
with gr.Column():
issue_input = gr.Textbox(
label="GitHub Issue",
placeholder="Paste issue title and description...",
lines=12
)
triage_btn = gr.Button("πŸ” Triage Issue", variant="primary", size="lg")
gr.Examples(
examples=[
["""Title: App crashes on startup after login
When I try to log in, the app crashes immediately. This started happening after the latest update (v2.1.0). I'm on iPhone 14, iOS 17.
Steps to reproduce:
1. Open app
2. Enter credentials
3. Tap login
4. App crashes
Expected: Should show dashboard
Actual: App crashes to home screen
This is blocking all users from accessing the app."""],
["""Title: Add dark mode support
It would be great to have a dark mode option in the settings. Many users prefer dark mode for night-time usage and it reduces eye strain.
This has been requested by multiple users in our Discord community."""],
["""Title: Documentation unclear for API authentication
The docs for setting up API authentication are confusing. The example code doesn't work and there's no explanation of where to find the API key.
Could we update the docs with a clearer example?"""],
],
inputs=issue_input
)
with gr.Row():
output = gr.Markdown(label="Triage Report")
triage_btn.click(
fn=process_triage,
inputs=[issue_input],
outputs=[output]
)
if __name__ == "__main__":
demo.launch()