AmitSJ/github-issue-triage
0
1# env/models.py2# Typed Pydantic models — defines what the agent SEEs and DOES3 4from pydantic import BaseModel, Field5from typing import Optional, List6from enum import Enum7 8 9# ─────────────────────────────────────────────10# ENUMS — fixed allowed values11# ─────────────────────────────────────────────12 13class IssueType(str, Enum):14 bug = "bug"15 feature = "feature"16 question = "question"17 documentation = "documentation"18 duplicate = "duplicate"19 20 21class Priority(str, Enum):22 P1 = "P1" # Critical — production down, data loss23 P2 = "P2" # High — major feature broken24 P3 = "P3" # Medium — minor feature broken25 P4 = "P4" # Low — cosmetic, nice-to-have26 27 28class Team(str, Enum):29 backend = "backend"30 frontend = "frontend"31 devops = "devops"32 docs = "documentation"33 security = "security"34 support = "support"35 36 37class Effort(str, Enum):38 small = "small" # < 1 day39 medium = "medium" # 1–3 days40 large = "large" # > 3 days41 42 43class TaskDifficulty(str, Enum):44 easy = "task_1"45 medium = "task_2"46 hard = "task_3"47 48 49# ─────────────────────────────────────────────50# OBSERVATION — what the agent SEES each step51# ─────────────────────────────────────────────52 53class IssueObservation(BaseModel):54 """55 Everything the AI agent sees when it receives a GitHub issue.56 Think of this as the 'screen' the agent reads before taking action.57 """58 issue_id: str = Field(..., description="Unique issue ID e.g. issue_042")59 title: str = Field(..., description="Issue title written by user")60 body: str = Field(..., description="Full issue description")61 repo: str = Field(..., description="Repository name e.g. react, vscode")62 author_type: str = Field(..., description="first-time-contributor / regular / maintainer")63 user_reports: int = Field(..., description="How many users reported this same problem")64 existing_labels: List[str] = Field(default=[], description="Labels already on the issue")65 open_issues_count: int = Field(..., description="Total open issues in the repo")66 task_id: str = Field(..., description="Which task: task_1 / task_2 / task_3")67 step_number: int = Field(..., description="Which step in the current episode")68 69 70# ─────────────────────────────────────────────71# ACTION — what the agent DOES (its decision)72# ─────────────────────────────────────────────73 74class TriageAction(BaseModel):75 """76 The triage decision the AI agent submits.77 Task 1 uses only issue_type.78 Task 2 uses issue_type + priority.79 Task 3 uses all fields.80 """81 issue_type: IssueType = Field(..., description="Type of issue")82 priority: Priority = Field(..., description="Urgency level P1–P4")83 team: Team = Field(..., description="Which team should handle this")84 estimated_effort: Effort = Field(..., description="How long will this take to fix")85 86 87# ─────────────────────────────────────────────88# STEP RESULT — what step() returns89# ─────────────────────────────────────────────90 91class StepResult(BaseModel):92 """Returned by step() after agent submits an action."""93 observation: Optional[IssueObservation] = Field(None, description="Next issue to triage (None if episode ended)")94 reward: float = Field(..., description="Score for this step: 0.0 to 1.0")95 is_done: bool = Field(..., description="True = episode finished")96 feedback: str = Field(..., description="Human-readable explanation of the reward")97 step_number: int = Field(..., description="Current step count")98 99#STATE—metadata about current episode100class EpisodeState(BaseModel):101 """Returned by state() — metadata about where we are in the episode."""102 episode_id: str = Field(..., description="Unique episode identifier")103 current_task: str = Field(..., description="Which task is active: task_1/task_2/task_3")104 step_count: int = Field(..., description="Steps taken so far")105 max_steps: int = Field(..., description="Maximum steps in this episode")106 total_reward: float = Field(..., description="Cumulative reward so far")107 is_active: bool = Field(..., description="Is an episode currently running?")108 109 110#RESET RESULT—what reset() returns111class ResetResult(BaseModel):112 """Returned by reset() when a new episode starts."""113 observation: IssueObservation = Field(..., description="First issue of the new episode")114 episode_id: str = Field(..., description="ID of the new episode")115 task_id: str = Field(..., description="Which task this episode runs")116 message: str = Field(..., description="Welcome message")117 