1515from eval_protocol .types import TerminationReason
1616
1717
18+ class ErrorInfo (BaseModel ):
19+ """
20+ AIP-193 ErrorInfo model for structured error details.
21+
22+ This model follows Google's AIP-193 standard for ErrorInfo:
23+ https://google.aip.dev/193#errorinfo
24+
25+ Attributes:
26+ reason (str): A short snake_case description of the cause of the error.
27+ domain (str): The logical grouping to which the reason belongs.
28+ metadata (Dict[str, Any]): Additional dynamic information as context.
29+ """
30+
31+ reason : str = Field (..., description = "Short snake_case description of the error cause" )
32+ domain : str = Field (..., description = "Logical grouping for the error reason" )
33+ metadata : Dict [str , Any ] = Field (default_factory = dict , description = "Additional dynamic information as context" )
34+
35+ def to_aip193_format (self ) -> Dict [str , Any ]:
36+ """Convert to AIP-193 format with @type field."""
37+ return {
38+ "@type" : "type.googleapis.com/google.rpc.ErrorInfo" ,
39+ "reason" : self .reason ,
40+ "domain" : self .domain ,
41+ "metadata" : self .metadata ,
42+ }
43+
44+ @classmethod
45+ def termination_reason (cls , reason : str ) -> "ErrorInfo" :
46+ """Create an ErrorInfo for termination reason."""
47+ return cls (reason = "TERMINATION_REASON" , domain = "evalprotocol.io" , metadata = {"termination_reason" : reason })
48+
49+ @classmethod
50+ def extra_info (cls , metadata : Dict [str , Any ]) -> "ErrorInfo" :
51+ """Create an ErrorInfo for extra information."""
52+ return cls (reason = "EXTRA_INFO" , domain = "evalprotocol.io" , metadata = metadata )
53+
54+ @classmethod
55+ def rollout_error (cls , metadata : Dict [str , Any ]) -> "ErrorInfo" :
56+ """Create an ErrorInfo for rollout errors."""
57+ return cls (reason = "ROLLOUT_ERROR" , domain = "evalprotocol.io" , metadata = metadata )
58+
59+ @classmethod
60+ def stopped_reason (cls , reason : str ) -> "ErrorInfo" :
61+ """Create an ErrorInfo for stopped reason."""
62+ return cls (reason = "STOPPED" , domain = "evalprotocol.io" , metadata = {"reason" : reason })
63+
64+
65+ class Status (BaseModel ):
66+ """
67+ AIP-193 compatible Status model for standardized error responses.
68+
69+ This model follows Google's AIP-193 standard for error handling:
70+ https://google.aip.dev/193
71+
72+ Attributes:
73+ code (int): The status code, must be the numeric value of one of the elements
74+ of google.rpc.Code enum (e.g., 5 for NOT_FOUND).
75+ message (str): Developer-facing, human-readable debug message in English.
76+ details (List[Dict[str, Any]]): Additional error information, each packed in
77+ a google.protobuf.Any message format.
78+ """
79+
80+ code : "Status.Code" = Field (..., description = "The status code from google.rpc.Code enum" )
81+ message : str = Field (..., description = "Developer-facing, human-readable debug message in English" )
82+ details : List [Dict [str , Any ]] = Field (
83+ default_factory = list ,
84+ description = "Additional error information, each packed in a google.protobuf.Any message format" ,
85+ )
86+
87+ # Convenience constants for common status codes
88+ class Code (int , Enum ):
89+ """Common gRPC status codes as defined in google.rpc.Code"""
90+
91+ OK = 0
92+ CANCELLED = 1
93+ UNKNOWN = 2
94+ INVALID_ARGUMENT = 3
95+ DEADLINE_EXCEEDED = 4
96+ NOT_FOUND = 5
97+ ALREADY_EXISTS = 6
98+ PERMISSION_DENIED = 7
99+ RESOURCE_EXHAUSTED = 8
100+ FAILED_PRECONDITION = 9
101+ ABORTED = 10
102+ OUT_OF_RANGE = 11
103+ UNIMPLEMENTED = 12
104+ INTERNAL = 13
105+ UNAVAILABLE = 14
106+ DATA_LOSS = 15
107+ UNAUTHENTICATED = 16
108+
109+ # Custom codes for rollout states (using higher numbers to avoid conflicts)
110+ FINISHED = 100 # Custom code for rollout finished
111+
112+ @classmethod
113+ def rollout_running (cls ) -> "Status" :
114+ """Create a status indicating the rollout is running."""
115+ return cls (code = cls .Code .OK , message = "Rollout is running" , details = [])
116+
117+ @classmethod
118+ def rollout_finished (
119+ cls , termination_reason : Optional [str ] = None , extra_info : Optional [Dict [str , Any ]] = None
120+ ) -> "Status" :
121+ """Create a status indicating the rollout finished."""
122+ details = []
123+ if termination_reason :
124+ details .append (ErrorInfo .termination_reason (termination_reason ).to_aip193_format ())
125+ if extra_info :
126+ details .append (ErrorInfo .extra_info (extra_info ).to_aip193_format ())
127+ return cls (code = cls .Code .FINISHED , message = "Rollout finished" , details = details )
128+
129+ @classmethod
130+ def rollout_error (cls , error_message : str , extra_info : Optional [Dict [str , Any ]] = None ) -> "Status" :
131+ """Create a status indicating the rollout failed with an error."""
132+ details = []
133+ if extra_info :
134+ details .append (ErrorInfo .rollout_error (extra_info ).to_aip193_format ())
135+ return cls .error (error_message , details )
136+
137+ @classmethod
138+ def error (cls , error_message : str , details : Optional [List [Dict [str , Any ]]] = None ) -> "Status" :
139+ """Create a status indicating the rollout failed with an error."""
140+ return cls (code = cls .Code .INTERNAL , message = error_message , details = details )
141+
142+ @classmethod
143+ def rollout_stopped (cls , reason : str = "Rollout stopped" ) -> "Status" :
144+ """Create a status indicating the rollout was stopped."""
145+ details = [ErrorInfo .stopped_reason (reason ).to_aip193_format ()]
146+ return cls (code = cls .Code .CANCELLED , message = reason , details = details )
147+
148+ @classmethod
149+ def with_termination_reason (cls , termination_reason : str , extra_info : Optional [Dict [str , Any ]] = None ) -> "Status" :
150+ """Create a status indicating the rollout finished with termination reason."""
151+ details = [ErrorInfo .termination_reason (termination_reason ).to_aip193_format ()]
152+
153+ if extra_info :
154+ details .append (ErrorInfo .extra_info (extra_info ).to_aip193_format ())
155+
156+ return cls (code = cls .Code .FINISHED , message = "Rollout finished" , details = details )
157+
158+ def is_running (self ) -> bool :
159+ """Check if the status indicates the rollout is running."""
160+ return self .code == self .Code .OK and self .message == "Rollout is running"
161+
162+ def is_finished (self ) -> bool :
163+ """Check if the status indicates the rollout finished successfully."""
164+ return self .code == self .Code .FINISHED
165+
166+ def is_error (self ) -> bool :
167+ """Check if the status indicates the rollout failed with an error."""
168+ return self .code == self .Code .INTERNAL
169+
170+ def is_stopped (self ) -> bool :
171+ """Check if the status indicates the rollout was stopped."""
172+ return self .code == self .Code .CANCELLED
173+
174+ def get_termination_reason (self ) -> Optional [str ]:
175+ """Extract termination reason from details if present."""
176+ for detail in self .details :
177+ if detail .get ("@type" ) == "type.googleapis.com/google.rpc.ErrorInfo" :
178+ metadata = detail .get ("metadata" , {})
179+ if detail .get ("reason" ) == "TERMINATION_REASON" and "termination_reason" in metadata :
180+ return metadata ["termination_reason" ]
181+ return None
182+
183+ def get_extra_info (self ) -> Optional [Dict [str , Any ]]:
184+ """Extract extra info from details if present."""
185+ for detail in self .details :
186+ if detail .get ("@type" ) == "type.googleapis.com/google.rpc.ErrorInfo" :
187+ metadata = detail .get ("metadata" , {})
188+ reason = detail .get ("reason" )
189+ # Skip termination_reason and stopped details, return other error info
190+ if reason not in ["TERMINATION_REASON" , "STOPPED" ]:
191+ return metadata
192+ return None
193+
194+ def __hash__ (self ) -> int :
195+ """Generate a hash for the Status object."""
196+ # Use a stable hash based on code, message, and details
197+ import hashlib
198+
199+ # Create a stable string representation
200+ hash_data = f"{ self .code } :{ self .message } :{ len (self .details )} "
201+
202+ # Add details content for more uniqueness
203+ for detail in sorted (self .details , key = lambda x : str (x )):
204+ hash_data += f":{ str (detail )} "
205+
206+ # Generate hash
207+ hash_obj = hashlib .sha256 (hash_data .encode ("utf-8" ))
208+ return int .from_bytes (hash_obj .digest ()[:8 ], byteorder = "big" )
209+
210+
18211class ChatCompletionContentPartTextParam (BaseModel ):
19212 text : str = Field (..., description = "The text content." )
20213 type : Literal ["text" ] = Field ("text" , description = "The type of the content part." )
@@ -289,27 +482,6 @@ class ExecutionMetadata(BaseModel):
289482 )
290483
291484
292- class RolloutStatus (BaseModel ):
293- """Status of the rollout."""
294-
295- """
296- running: Unfinished rollout which is still in progress.
297- finished: Rollout finished.
298- error: Rollout failed due to unexpected error. The rollout record should be discard.
299- """
300-
301- class Status (str , Enum ):
302- RUNNING = "running"
303- FINISHED = "finished"
304- ERROR = "error"
305-
306- status : Status = Field (Status .RUNNING , description = "Status of the rollout." )
307- termination_reason : Optional [TerminationReason ] = Field (
308- None , description = "reason of the rollout status, mapped to values in TerminationReason"
309- )
310- extra_info : Optional [Dict [str , Any ]] = Field (None , description = "Extra information about the rollout status." )
311-
312-
313485class EvaluationRow (BaseModel ):
314486 """
315487 Unified data structure for a single evaluation unit that contains messages,
@@ -334,9 +506,9 @@ class EvaluationRow(BaseModel):
334506 description = "Metadata related to the input (dataset info, model config, session data, etc.)." ,
335507 )
336508
337- rollout_status : RolloutStatus = Field (
338- default_factory = RolloutStatus ,
339- description = "The status of the rollout." ,
509+ rollout_status : Status = Field (
510+ default_factory = Status . rollout_running ,
511+ description = "The status of the rollout following AIP-193 standards ." ,
340512 )
341513
342514 # Ground truth reference (moved from EvaluateResult to top level)
@@ -381,6 +553,14 @@ def is_trajectory_evaluation(self) -> bool:
381553 and len (self .evaluation_result .step_outputs ) > 0
382554 )
383555
556+ def get_rollout_status (self ) -> Status :
557+ """Get the rollout status (backwards compatibility method)."""
558+ return self .rollout_status
559+
560+ def set_rollout_status (self , status : Status ) -> None :
561+ """Set the rollout status (backwards compatibility method)."""
562+ self .rollout_status = status
563+
384564 def get_conversation_length (self ) -> int :
385565 """Returns the number of messages in the conversation."""
386566 return len (self .messages )
0 commit comments