CoolFace
Apppublic

AmitSJ/github-issue-triage

sourceHugging Facemitupdated 6mo agoView on Hugging Face
0likes
app.py186 linesDownload Raw Back to root
1# app.py2# The FastAPI web server — exposes the environment over HTTP3#4# This file does 3 things:5#   1. Creates the FastAPI application6#   2. Creates ONE shared instance of GitHubTriageEnvironment7#   3. Defines HTTP endpoints: /reset, /step, /state8#9# Once this runs, any AI agent can connect via HTTP and use the environment10# It also auto-generates a Swagger UI at /docs for visual testing11 12from fastapi import FastAPI, HTTPException13from fastapi.staticfiles import StaticFiles14from fastapi.responses import FileResponse, JSONResponse, RedirectResponse15from fastapi.middleware.cors import CORSMiddleware16from pydantic import BaseModel17import os18 19from env.environment import GitHubTriageEnvironment20from env.models import TriageAction, ResetResult, StepResult, EpisodeState21 22# ─────────────────────────────────────────────23# Create the FastAPI app24# The title and description appear in Swagger UI at /docs25# ─────────────────────────────────────────────26 27app = FastAPI(28    title       = "GitHub Issue Triage Environment",29    description = (30        "An OpenEnv-compatible reinforcement learning environment "31        "where AI agents learn to triage GitHub issues. "32        "Agents classify issue types, assign priorities, route to teams, "33        "and estimate effort — earning rewards for correct decisions.\n\n"34        "**Tasks:**\n"35        "- task_1 (Easy): Issue Classification\n"36        "- task_2 (Medium): Priority Assignment\n"37        "- task_3 (Hard): Full Triage\n\n"38        "**Usage:** Call /reset to start, /step to submit actions, /state for metadata."39    ),40    version     = "1.0.0",41)42 43# ─────────────────────────────────────────────44# ONE shared environment instance45# All HTTP requests use this same instance46# This way the episode state is preserved across calls47# (reset() sets state, step() reads it, state() reads it)48# ─────────────────────────────────────────────49 50env = GitHubTriageEnvironment()51 52# Allow all origins so automated checkers and agents can access the API53app.add_middleware(54    CORSMiddleware,55    allow_origins=["*"],56    allow_methods=["*"],57    allow_headers=["*"],58)59 60 61# ─────────────────────────────────────────────62# Request body model for /reset63# Agent sends: { "task_id": "task_1" }64# ─────────────────────────────────────────────65 66class ResetRequest(BaseModel):67    task_id: str = "task_1"   # Default to easiest task if not specified68 69 70# ─────────────────────────────────────────────71# ENDPOINTS72# ─────────────────────────────────────────────73 74@app.get("/", summary="Welcome", include_in_schema=False)75def root():76    """Redirects to the visual dashboard."""77    return RedirectResponse(url="/dashboard")78 79 80@app.post("/reset", response_model=ResetResult, summary="Start a new episode")81def reset(request: ResetRequest = None):82    """83    **Start a new training episode.**84    - Clears all previous episode state85    - Returns the first GitHub issue for the agent to triage86    - Body is optional — defaults to task_1 if not provided87 88    **Request body (optional):**89    ```json90    { "task_id": "task_1" }91    ```92    task_id options: task_1 (easy) | task_2 (medium) | task_3 (hard)93    """94    try:95        task_id = request.task_id if request else "task_1"96        result = env.reset(task_id=task_id)97        return result98    except ValueError as e:99        raise HTTPException(status_code=400, detail=str(e))100 101 102@app.post("/step", response_model=StepResult, summary="Submit a triage action")103def step(action: TriageAction):104    """105    **Submit the agent's triage decision for the current issue.**106 107    - Scores the action against the correct answer108    - Returns reward (0.0–1.0) + feedback + next issue109    - Returns is_done=True when episode ends110 111    **Request body:**112    ```json113    {114      "issue_type"       : "bug",115      "priority"         : "P1",116      "team"             : "backend",117      "estimated_effort" : "small"118    }119    ```120    """121    try:122        result = env.step(action)123        return result124    except RuntimeError as e:125        # step() called before reset()126        raise HTTPException(status_code=400, detail=str(e))127 128 129@app.get("/state", response_model=EpisodeState, summary="Get current episode state")130def state():131    """132    **Get metadata about the current episode.**133 134    Returns information like:135    - episode_id, current_task136    - step_count and max_steps137    - total_reward accumulated so far138    - is_active (whether an episode is running)139 140    Does NOT advance the episode — purely informational.141    """142    return env.state()143 144 145@app.get("/health", summary="Health check")146def health():147    """Simple health check endpoint — used by Docker and Hugging Face to verify server is running."""148    return {"status": "healthy", "environment": "github-issue-triage"}149 150 151# ─────────────────────────────────────────────152# Dashboard endpoint — serves the visual HTML UI153# We will build static/dashboard.html in Phase 4154# ─────────────────────────────────────────────155 156@app.get("/dashboard", summary="Visual dashboard", include_in_schema=False)157def dashboard():158    """Serves the visual web dashboard for demo purposes."""159    dashboard_path = os.path.join("static", "dashboard.html")160    if os.path.exists(dashboard_path):161        return FileResponse(dashboard_path)162    return JSONResponse(163        status_code = 200,164        content     = {"message": "Dashboard coming soon! Use /docs for now."}165    )166 167 168# ─────────────────────────────────────────────169# Run the server170# Only executes when file is run directly: python app.py171# (Not when imported by other files)172# ─────────────────────────────────────────────173 174def main():175    """Named entry point for pyproject.toml scripts — runs the server."""176    import uvicorn177    uvicorn.run(178        "app:app",179        host   = "0.0.0.0",180        port   = 7860,181        reload = False,182    )183 184if __name__ == "__main__":185    main()186