Spaces:
Sleeping
Sleeping
| """ | |
| Pydantic models for the Bug Triage OpenEnv environment. | |
| """ | |
| from datetime import datetime | |
| from typing import Literal, Optional | |
| from pydantic import BaseModel, Field | |
| class TicketModel(BaseModel): | |
| """Represents a bug ticket in the system.""" | |
| ticket_id: str | |
| title: str | |
| description: str | |
| reporter_type: Literal["user", "qa", "monitoring"] | |
| service: str | |
| component_candidates: list[str] | |
| created_at: datetime | |
| customer_tier: Literal["free", "pro", "enterprise"] | |
| repro_steps_present: bool | |
| logs_present: bool | |
| attachments_count: int | |
| suspected_duplicate_ids: list[str] | |
| class TicketGroundTruth(BaseModel): | |
| """Hidden ground truth for a ticket.""" | |
| ticket_id: str | |
| true_severity: Literal["sev0", "sev1", "sev2", "sev3"] | |
| true_priority: Literal["p0", "p1", "p2", "p3"] | |
| true_component: str | |
| true_assignee_team: str | |
| duplicate_of: Optional[str] = None | |
| needs_more_info: bool | |
| class CurrentTicketModel(TicketModel): | |
| """Current ticket being focused on (inherits all fields from TicketModel).""" | |
| class QueueStatsModel(BaseModel): | |
| """Statistics about the ticket queue.""" | |
| remaining_count: int | |
| urgent_count: int | |
| sla_at_risk_count: int | |
| class ObservationModel(BaseModel): | |
| """Observation returned by the environment.""" | |
| current_ticket: Optional[CurrentTicketModel] = None | |
| queue_stats: QueueStatsModel | |
| last_action_result: Optional[str] = None | |
| available_teams: list[str] | |
| available_components: list[str] | |
| steps_used: int | |
| steps_remaining: int | |
| partial_score: Optional[float] = None | |
| class ClassifyAction(BaseModel): | |
| """Classify ticket with severity, priority, and component.""" | |
| severity: Literal["sev0", "sev1", "sev2", "sev3"] | |
| priority: Literal["p0", "p1", "p2", "p3"] | |
| component: str | |
| class AssignAction(BaseModel): | |
| """Assign ticket to a team.""" | |
| team: str | |
| class MarkDuplicateAction(BaseModel): | |
| """Mark ticket as duplicate of another.""" | |
| canonical_ticket_id: str | |
| class RequestInfoAction(BaseModel): | |
| """Request more information from reporter.""" | |
| info_type: Literal["repro_steps", "logs", "both"] | |
| class DeferAction(BaseModel): | |
| """Defer ticket to backlog.""" | |
| reason: str | |
| class CloseAction(BaseModel): | |
| """Close ticket with reason.""" | |
| reason: Literal["invalid", "wont_fix", "cannot_reproduce", "resolved"] | |
| class EscalateAction(BaseModel): | |
| """Escalate ticket as urgent incident.""" | |
| justification: str | |
| class NextTicketAction(BaseModel): | |
| """Move to next ticket.""" | |
| pass | |
| class ActionModel(BaseModel): | |
| """Action that can be taken in the environment.""" | |
| action_type: Literal[ | |
| "classify", | |
| "assign", | |
| "mark_duplicate", | |
| "request_info", | |
| "defer", | |
| "close", | |
| "escalate_incident", | |
| "next_ticket", | |
| ] | |
| classify: Optional[ClassifyAction] = None | |
| assign: Optional[AssignAction] = None | |
| mark_duplicate: Optional[MarkDuplicateAction] = None | |
| request_info: Optional[RequestInfoAction] = None | |
| defer: Optional[DeferAction] = None | |
| close: Optional[CloseAction] = None | |
| escalate_incident: Optional[EscalateAction] = None | |
| next_ticket: Optional[NextTicketAction] = None | |
| def model_post_init(self, __context): | |
| """Validate that the action payload matches the action type.""" | |
| action_map = { | |
| "classify": self.classify, | |
| "assign": self.assign, | |
| "mark_duplicate": self.mark_duplicate, | |
| "request_info": self.request_info, | |
| "defer": self.defer, | |
| "close": self.close, | |
| "escalate_incident": self.escalate_incident, | |
| "next_ticket": self.next_ticket, | |
| } | |
| expected_field = action_map.get(self.action_type) | |
| if expected_field is None and self.action_type != "next_ticket": | |
| raise ValueError(f"Missing payload for action_type '{self.action_type}'") | |
| unexpected_fields = [ | |
| field_name | |
| for field_name, payload in action_map.items() | |
| if field_name != self.action_type and payload is not None | |
| ] | |
| if unexpected_fields: | |
| extras = ", ".join(sorted(unexpected_fields)) | |
| raise ValueError( | |
| f"Unexpected payload field(s) for action_type '{self.action_type}': {extras}" | |
| ) | |
| class RewardModel(BaseModel): | |
| """Reward information for a step.""" | |
| step_reward: float = Field(..., ge=0.0, le=1.0) | |
| cumulative_reward: float | |
| reward_breakdown: dict[str, float] = Field(default_factory=dict) | |
| class TicketStateModel(BaseModel): | |
| """State of a single ticket in the triage process.""" | |
| ticket_id: str | |
| triaged: bool | |
| actions_taken: list[str] | |
| class StateModel(BaseModel): | |
| """Full state of the environment.""" | |
| current_task_id: str | |
| current_ticket_index: int | |
| total_tickets: int | |
| tickets_state: list[TicketStateModel] | |
| steps_used: int | |
| steps_remaining: int | |
| cumulative_reward: float | |
| episode_done: bool | |