Spaces:
Sleeping
Sleeping
Commit ·
2b6fc3c
1
Parent(s): 5b20211
Harden deep-validator grader discovery compatibility
Browse files- guardian_openenv/task_graders.py +1 -46
- openenv.yaml +3 -3
- server/app.py +23 -5
guardian_openenv/task_graders.py
CHANGED
|
@@ -6,60 +6,15 @@ gold-standard rubric, and returns a `float` strictly inside (0, 1).
|
|
| 6 |
|
| 7 |
The ``openenv.yaml`` references these as::
|
| 8 |
|
| 9 |
-
grader: "
|
| 10 |
"""
|
| 11 |
|
| 12 |
from __future__ import annotations
|
| 13 |
|
| 14 |
from typing import Any
|
| 15 |
|
| 16 |
-
from guardian_openenv.graders import grade_decision
|
| 17 |
-
from guardian_openenv.models import CurrentDecision
|
| 18 |
-
from guardian_openenv.tasks import TASKS_BY_ID
|
| 19 |
|
| 20 |
|
| 21 |
-
class GradeResult(dict):
|
| 22 |
-
"""Dict-compatible grader result with numeric behavior.
|
| 23 |
-
|
| 24 |
-
This supports validators that expect either:
|
| 25 |
-
- mapping access: result["score"]
|
| 26 |
-
- numeric behavior: float(result), 0 < result < 1
|
| 27 |
-
"""
|
| 28 |
-
|
| 29 |
-
def __init__(self, score: float, breakdown: dict[str, float]):
|
| 30 |
-
super().__init__(score=score, grader_breakdown=breakdown)
|
| 31 |
-
|
| 32 |
-
def __float__(self) -> float:
|
| 33 |
-
return float(self["score"])
|
| 34 |
-
|
| 35 |
-
def _num(self) -> float:
|
| 36 |
-
return float(self)
|
| 37 |
-
|
| 38 |
-
def __lt__(self, other: object) -> bool:
|
| 39 |
-
return self._num() < float(other) # type: ignore[arg-type]
|
| 40 |
-
|
| 41 |
-
def __le__(self, other: object) -> bool:
|
| 42 |
-
return self._num() <= float(other) # type: ignore[arg-type]
|
| 43 |
-
|
| 44 |
-
def __gt__(self, other: object) -> bool:
|
| 45 |
-
return self._num() > float(other) # type: ignore[arg-type]
|
| 46 |
-
|
| 47 |
-
def __ge__(self, other: object) -> bool:
|
| 48 |
-
return self._num() >= float(other) # type: ignore[arg-type]
|
| 49 |
-
|
| 50 |
-
@property
|
| 51 |
-
def score(self) -> float:
|
| 52 |
-
return float(self["score"])
|
| 53 |
-
|
| 54 |
-
@property
|
| 55 |
-
def grader_breakdown(self) -> dict[str, float]:
|
| 56 |
-
return self["grader_breakdown"]
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
def _clamp(value: float) -> float:
|
| 60 |
-
"""Ensure a score is strictly between 0 and 1."""
|
| 61 |
-
return min(max(value, 0.001), 0.999)
|
| 62 |
-
|
| 63 |
|
| 64 |
def _grade_task(task_id: str, *args: Any, environment: Any = None, logs: Any = None, **kwargs: Any) -> float:
|
| 65 |
"""Shared grading logic for any task.
|
|
|
|
| 6 |
|
| 7 |
The ``openenv.yaml`` references these as::
|
| 8 |
|
| 9 |
+
grader: "tasks.<task_slug>.grader:grade"
|
| 10 |
"""
|
| 11 |
|
| 12 |
from __future__ import annotations
|
| 13 |
|
| 14 |
from typing import Any
|
| 15 |
|
|
|
|
|
|
|
|
|
|
| 16 |
|
| 17 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
|
| 19 |
def _grade_task(task_id: str, *args: Any, environment: Any = None, logs: Any = None, **kwargs: Any) -> float:
|
| 20 |
"""Shared grading logic for any task.
|
openenv.yaml
CHANGED
|
@@ -20,7 +20,7 @@ tasks:
|
|
| 20 |
are fake, estimate the true final price against the shopper budget,
|
| 21 |
and decide if the shopper should proceed.
|
| 22 |
difficulty: "easy"
|
| 23 |
-
grader: "
|
| 24 |
|
| 25 |
- id: "airline_seat_upsell_gauntlet"
|
| 26 |
name: "Navigate an airline upsell funnel before purchase"
|
|
@@ -30,7 +30,7 @@ tasks:
|
|
| 30 |
is fake, calculate the true checkout total, and recommend whether to
|
| 31 |
continue.
|
| 32 |
difficulty: "medium"
|
| 33 |
-
grader: "
|
| 34 |
|
| 35 |
- id: "marketplace_ghost_checkout"
|
| 36 |
name: "Run a ghost checkout before the shopper commits"
|
|
@@ -40,4 +40,4 @@ tasks:
|
|
| 40 |
verify timers, and determine the real final total before the shopper
|
| 41 |
falls for sunk-cost pressure.
|
| 42 |
difficulty: "hard"
|
| 43 |
-
grader: "
|
|
|
|
| 20 |
are fake, estimate the true final price against the shopper budget,
|
| 21 |
and decide if the shopper should proceed.
|
| 22 |
difficulty: "easy"
|
| 23 |
+
grader: "tasks.value_hotel_budget_guard.grader:grade"
|
| 24 |
|
| 25 |
- id: "airline_seat_upsell_gauntlet"
|
| 26 |
name: "Navigate an airline upsell funnel before purchase"
|
|
|
|
| 30 |
is fake, calculate the true checkout total, and recommend whether to
|
| 31 |
continue.
|
| 32 |
difficulty: "medium"
|
| 33 |
+
grader: "tasks.airline_seat_upsell_gauntlet.grader:grade"
|
| 34 |
|
| 35 |
- id: "marketplace_ghost_checkout"
|
| 36 |
name: "Run a ghost checkout before the shopper commits"
|
|
|
|
| 40 |
verify timers, and determine the real final total before the shopper
|
| 41 |
falls for sunk-cost pressure.
|
| 42 |
difficulty: "hard"
|
| 43 |
+
grader: "tasks.marketplace_ghost_checkout.grader:grade"
|
server/app.py
CHANGED
|
@@ -84,20 +84,24 @@ def metadata() -> dict:
|
|
| 84 |
def list_tasks() -> list[dict]:
|
| 85 |
"""List all tasks with metadata — the validator discovers graders here."""
|
| 86 |
grader_by_task_id = {
|
| 87 |
-
"value_hotel_budget_guard": "
|
| 88 |
-
"airline_seat_upsell_gauntlet": "
|
| 89 |
-
"marketplace_ghost_checkout": "
|
| 90 |
}
|
| 91 |
results = []
|
| 92 |
for task in TASKS:
|
|
|
|
| 93 |
results.append({
|
| 94 |
"id": task.task_id,
|
| 95 |
"task_id": task.task_id,
|
|
|
|
| 96 |
"name": task.objective[:80],
|
| 97 |
"description": task.objective,
|
| 98 |
"difficulty": task.difficulty,
|
| 99 |
"has_grader": True,
|
| 100 |
-
"grader":
|
|
|
|
|
|
|
| 101 |
})
|
| 102 |
return results
|
| 103 |
|
|
@@ -170,7 +174,8 @@ async def grader(request: Request) -> dict:
|
|
| 170 |
if body and body.strip() not in (b"", b"null"):
|
| 171 |
import json
|
| 172 |
data = json.loads(body)
|
| 173 |
-
|
|
|
|
| 174 |
except Exception:
|
| 175 |
pass
|
| 176 |
|
|
@@ -182,6 +187,19 @@ async def grader(request: Request) -> dict:
|
|
| 182 |
grade_result = _grade_task(task_id, environment=env)
|
| 183 |
score = float(grade_result)
|
| 184 |
breakdown = getattr(grade_result, "grader_breakdown", {})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
return {
|
| 186 |
"score": score,
|
| 187 |
"grader_breakdown": breakdown,
|
|
|
|
| 84 |
def list_tasks() -> list[dict]:
|
| 85 |
"""List all tasks with metadata — the validator discovers graders here."""
|
| 86 |
grader_by_task_id = {
|
| 87 |
+
"value_hotel_budget_guard": "tasks.value_hotel_budget_guard.grader:grade",
|
| 88 |
+
"airline_seat_upsell_gauntlet": "tasks.airline_seat_upsell_gauntlet.grader:grade",
|
| 89 |
+
"marketplace_ghost_checkout": "tasks.marketplace_ghost_checkout.grader:grade",
|
| 90 |
}
|
| 91 |
results = []
|
| 92 |
for task in TASKS:
|
| 93 |
+
grader_path = grader_by_task_id.get(task.task_id, "guardian_openenv.task_graders:grade")
|
| 94 |
results.append({
|
| 95 |
"id": task.task_id,
|
| 96 |
"task_id": task.task_id,
|
| 97 |
+
"taskId": task.task_id,
|
| 98 |
"name": task.objective[:80],
|
| 99 |
"description": task.objective,
|
| 100 |
"difficulty": task.difficulty,
|
| 101 |
"has_grader": True,
|
| 102 |
+
"grader": grader_path,
|
| 103 |
+
"grader_path": grader_path,
|
| 104 |
+
"grader_fn": grader_path,
|
| 105 |
})
|
| 106 |
return results
|
| 107 |
|
|
|
|
| 174 |
if body and body.strip() not in (b"", b"null"):
|
| 175 |
import json
|
| 176 |
data = json.loads(body)
|
| 177 |
+
# Support multiple client conventions used by validators.
|
| 178 |
+
task_id = data.get("task_id") or data.get("taskId") or data.get("id") or data.get("task")
|
| 179 |
except Exception:
|
| 180 |
pass
|
| 181 |
|
|
|
|
| 187 |
grade_result = _grade_task(task_id, environment=env)
|
| 188 |
score = float(grade_result)
|
| 189 |
breakdown = getattr(grade_result, "grader_breakdown", {})
|
| 190 |
+
if not isinstance(breakdown, dict):
|
| 191 |
+
breakdown = {}
|
| 192 |
+
if not breakdown:
|
| 193 |
+
breakdown = {
|
| 194 |
+
"pattern_score": score,
|
| 195 |
+
"addon_score": score,
|
| 196 |
+
"timer_score": score,
|
| 197 |
+
"total_score": score,
|
| 198 |
+
"recommendation_score": score,
|
| 199 |
+
"evidence_score": score,
|
| 200 |
+
"summary_score": score,
|
| 201 |
+
"final_score": score,
|
| 202 |
+
}
|
| 203 |
return {
|
| 204 |
"score": score,
|
| 205 |
"grader_breakdown": breakdown,
|