-
Notifications
You must be signed in to change notification settings - Fork 53
feat: add status field to EvaluationOutput #359
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -118,9 +118,22 @@ class EvaluationOutput(BaseModel): | |
| test_pass: Whether the test pass or fail. | ||
| reason: The reason for the score for each test case. | ||
| label: The categorical label corresponding to the score. | ||
| status: The grading status of the evaluation output. Controls whether | ||
| this result is included in aggregate score computations. | ||
|
|
||
| - "graded" (default): The score and test_pass are real verdicts. | ||
| Included in all aggregations. Backward compatible with every | ||
| existing evaluator. | ||
| - "could_not_evaluate": The evaluator tried to grade but could not | ||
| (preconditions not met, harness failure, missing data). Excluded | ||
| from pass-rate and score aggregations. | ||
| - "informational": The evaluator does not pass/fail by design. It | ||
| surfaces content for human review but is excluded from numeric | ||
| aggregates. | ||
| """ | ||
|
|
||
| score: float | ||
| test_pass: bool | ||
| reason: str | None = None | ||
| label: str | None = None | ||
| status: str = "graded" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue (Important): Suggestion: Use a from typing import Literal
status: Literal["graded", "could_not_evaluate", "informational"] = "graded"This gives validation for free and makes the accepted values self-documenting. Aligns with the "obvious path is the happy path" tenet. |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -18,6 +18,9 @@ class EvaluationReport(BaseModel): | |
| the evaluator that produced that row. | ||
| test_passes: A list of booleans indicating whether the test pass or fail. | ||
| reasons: A list of reason for each test case. | ||
| statuses: A list of status strings for each test case. Controls whether | ||
| the result is included in overall_score aggregation. Values are | ||
| "graded", "could_not_evaluate", or "informational". | ||
| """ | ||
|
|
||
| overall_score: float | ||
|
|
@@ -28,6 +31,7 @@ class EvaluationReport(BaseModel): | |
| detailed_results: list[list[EvaluationOutput]] = [] | ||
| diagnoses: list[dict | None] = [] | ||
| recommendations: list[str | None] = [] | ||
| statuses: list[str] = [] | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Issue (Important + Medium): Two things on
Suggestion: Consider a Pydantic validator that pads |
||
|
|
||
| @classmethod | ||
| def flatten(cls, reports: list["EvaluationReport"]) -> "EvaluationReport": | ||
|
|
@@ -40,7 +44,7 @@ def flatten(cls, reports: list["EvaluationReport"]) -> "EvaluationReport": | |
| if not reports: | ||
| return cls(overall_score=0.0, scores=[], cases=[], test_passes=[]) | ||
|
|
||
| scores, cases, passes, reasons, detailed, diags, recs = [], [], [], [], [], [], [] | ||
| scores, cases, passes, reasons, detailed, diags, recs, statuses = [], [], [], [], [], [], [], [] | ||
|
|
||
| for report in reports: | ||
| for i, case in enumerate(report.cases): | ||
|
|
@@ -51,16 +55,20 @@ def flatten(cls, reports: list["EvaluationReport"]) -> "EvaluationReport": | |
| detailed.append(report.detailed_results[i] if i < len(report.detailed_results) else []) | ||
| diags.append(report.diagnoses[i] if i < len(report.diagnoses) else None) | ||
| recs.append(report.recommendations[i] if i < len(report.recommendations) else None) | ||
| statuses.append(report.statuses[i] if i < len(report.statuses) else "graded") | ||
|
|
||
| graded_scores = [s for s, st in zip(scores, statuses, strict=True) if st == "graded"] | ||
|
|
||
| return cls( | ||
| overall_score=sum(scores) / len(scores) if scores else 0.0, | ||
| overall_score=sum(graded_scores) / len(graded_scores) if graded_scores else 0.0, | ||
| scores=scores, | ||
| cases=cases, | ||
| test_passes=passes, | ||
| reasons=reasons, | ||
| detailed_results=detailed, | ||
| diagnoses=diags, | ||
| recommendations=recs, | ||
| statuses=statuses, | ||
| ) | ||
|
|
||
| @staticmethod | ||
|
|
@@ -134,6 +142,10 @@ def _display( | |
| details_dict["evaluator"] = self.cases[i]["evaluator"] | ||
| details_dict["score"] = f"{self.scores[i]:.2f}" | ||
| details_dict["test_pass"] = self.test_passes[i] | ||
| # Include status when it differs from the default "graded" | ||
| status = self.statuses[i] if i < len(self.statuses) else "graded" | ||
| if status != "graded": | ||
| details_dict["status"] = status | ||
| details_dict["reason"] = reason | ||
| if include_input: | ||
| details_dict["input"] = self.format_input_for_display(self.cases[i].get("input")) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Issue (Important): This nested conditional expression is hard to parse, and the roll-up rule is implicit: when no output is
graded, it falls back toevaluation_outputs[0].status, so a case mixingcould_not_evaluateandinformationalgets whichever happens to be first — an arbitrary tie-break that isn't documented anywhere.Suggestion: Extract a small named helper with an explicit precedence rule, e.g.:
This makes the intended behavior testable and removes the positional dependency. Please also confirm the desired precedence — the PR doesn't currently specify what a mixed non-graded case should roll up to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks —
_roll_up_status()is a nice improvement and the precedence is now explicit and documented. Keeping the first-non-graded-output tie-break is a reasonable call now that it's intentional rather than incidental.One tiny follow-up: the helper is annotated
evaluation_outputs: list(untyped element). SinceEvaluationOutputis already imported here,list[EvaluationOutput]would be more precise and consistent with the repo's typing conventions. Non-blocking.