File size: 5,054 Bytes
06aac03
 
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
 
f9cf02d
 
06aac03
 
 
 
75ca235
06aac03
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
711b9b5
06aac03
 
 
 
711b9b5
 
 
 
 
 
 
 
 
 
 
06aac03
 
 
75ca235
cfe0896
06aac03
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
75ca235
06aac03
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"""
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