""" 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()