Spaces:
Sleeping
Sleeping
I766446 commited on
Commit ·
aa17113
1
Parent(s): 9ba6dc5
Add per-task grader.py/task.py structure; update /tasks and /grade endpoints
Browse files- openenv.yaml +6 -6
- server.py +20 -15
- tasks/easy/__init__.py +0 -0
- tasks/easy/grader.py +49 -0
- tasks/easy/task.py +19 -0
- tasks/hard/__init__.py +0 -0
- tasks/hard/grader.py +47 -0
- tasks/hard/task.py +19 -0
- tasks/medium/__init__.py +0 -0
- tasks/medium/grader.py +47 -0
- tasks/medium/task.py +19 -0
openenv.yaml
CHANGED
|
@@ -151,8 +151,8 @@ tasks:
|
|
| 151 |
n_scenarios: 5
|
| 152 |
grader:
|
| 153 |
enabled: true
|
| 154 |
-
class: tasks.
|
| 155 |
-
|
| 156 |
score_range: [0.0, 1.0]
|
| 157 |
weights: { decision: 0.50, evidence: 0.25, reasoning: 0.15, efficiency: 0.10 }
|
| 158 |
ideal_max_steps: 8
|
|
@@ -168,8 +168,8 @@ tasks:
|
|
| 168 |
n_scenarios: 5
|
| 169 |
grader:
|
| 170 |
enabled: true
|
| 171 |
-
class: tasks.
|
| 172 |
-
|
| 173 |
score_range: [0.0, 1.0]
|
| 174 |
weights: { decision: 0.35, evidence: 0.35, reasoning: 0.20, efficiency: 0.10 }
|
| 175 |
ideal_max_steps: 12
|
|
@@ -185,8 +185,8 @@ tasks:
|
|
| 185 |
n_scenarios: 5
|
| 186 |
grader:
|
| 187 |
enabled: true
|
| 188 |
-
class: tasks.
|
| 189 |
-
|
| 190 |
score_range: [0.0, 1.0]
|
| 191 |
weights: { decision: 0.30, evidence: 0.30, reasoning: 0.30, efficiency: 0.10 }
|
| 192 |
ideal_max_steps: 18
|
|
|
|
| 151 |
n_scenarios: 5
|
| 152 |
grader:
|
| 153 |
enabled: true
|
| 154 |
+
class: tasks.easy.grader.grade
|
| 155 |
+
fn: tasks.easy.grader.grade
|
| 156 |
score_range: [0.0, 1.0]
|
| 157 |
weights: { decision: 0.50, evidence: 0.25, reasoning: 0.15, efficiency: 0.10 }
|
| 158 |
ideal_max_steps: 8
|
|
|
|
| 168 |
n_scenarios: 5
|
| 169 |
grader:
|
| 170 |
enabled: true
|
| 171 |
+
class: tasks.medium.grader.grade
|
| 172 |
+
fn: tasks.medium.grader.grade
|
| 173 |
score_range: [0.0, 1.0]
|
| 174 |
weights: { decision: 0.35, evidence: 0.35, reasoning: 0.20, efficiency: 0.10 }
|
| 175 |
ideal_max_steps: 12
|
|
|
|
| 185 |
n_scenarios: 5
|
| 186 |
grader:
|
| 187 |
enabled: true
|
| 188 |
+
class: tasks.hard.grader.grade
|
| 189 |
+
fn: tasks.hard.grader.grade
|
| 190 |
score_range: [0.0, 1.0]
|
| 191 |
weights: { decision: 0.30, evidence: 0.30, reasoning: 0.30, efficiency: 0.10 }
|
| 192 |
ideal_max_steps: 18
|
server.py
CHANGED
|
@@ -312,10 +312,12 @@ async def list_tasks():
|
|
| 312 |
"scenario_ids": [s["id"] for s in task.get_all_scenarios()],
|
| 313 |
"grader": {
|
| 314 |
"enabled": True,
|
| 315 |
-
"class": f"tasks.
|
| 316 |
-
"
|
| 317 |
-
"score_range": [0.0, 1.0]
|
| 318 |
},
|
|
|
|
|
|
|
| 319 |
"grader_weights": {
|
| 320 |
"decision": grader.W_DECISION,
|
| 321 |
"evidence": grader.W_EVIDENCE,
|
|
@@ -539,23 +541,26 @@ class GradeRequest(BaseModel):
|
|
| 539 |
async def grade_episode(req: GradeRequest):
|
| 540 |
"""
|
| 541 |
Grade a completed episode for a specific task.
|
| 542 |
-
|
|
|
|
| 543 |
"""
|
| 544 |
-
|
| 545 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 546 |
try:
|
| 547 |
-
grader
|
| 548 |
-
|
| 549 |
-
|
| 550 |
return {
|
| 551 |
-
"task_id":
|
| 552 |
-
"score":
|
| 553 |
-
"passed":
|
| 554 |
"score_range": [0.0, 1.0],
|
| 555 |
-
"grader":
|
| 556 |
}
|
| 557 |
-
except ValueError as e:
|
| 558 |
-
raise HTTPException(status_code=400, detail=str(e))
|
| 559 |
except Exception as e:
|
| 560 |
logger.error("Grading failed: %s", traceback.format_exc())
|
| 561 |
raise HTTPException(status_code=500, detail=f"Grading failed: {str(e)}")
|
|
|
|
| 312 |
"scenario_ids": [s["id"] for s in task.get_all_scenarios()],
|
| 313 |
"grader": {
|
| 314 |
"enabled": True,
|
| 315 |
+
"class": f"tasks.{difficulty}.grader.grade",
|
| 316 |
+
"fn": f"tasks.{difficulty}.grader.grade",
|
| 317 |
+
"score_range": [0.0, 1.0],
|
| 318 |
},
|
| 319 |
+
"grader_fn": f"tasks.{difficulty}.grader.grade",
|
| 320 |
+
"score_range": [0.0, 1.0],
|
| 321 |
"grader_weights": {
|
| 322 |
"decision": grader.W_DECISION,
|
| 323 |
"evidence": grader.W_EVIDENCE,
|
|
|
|
| 541 |
async def grade_episode(req: GradeRequest):
|
| 542 |
"""
|
| 543 |
Grade a completed episode for a specific task.
|
| 544 |
+
Accepts an optional episode dict; if omitted, probes the grader with empty input.
|
| 545 |
+
Returns {"score", "passed", "score_range", "grader"}.
|
| 546 |
"""
|
| 547 |
+
import importlib
|
| 548 |
+
|
| 549 |
+
task_id = req.task_id.lower().strip()
|
| 550 |
+
if task_id not in ("easy", "medium", "hard"):
|
| 551 |
+
raise HTTPException(status_code=400, detail=f"Unknown task_id '{task_id}'")
|
| 552 |
+
|
| 553 |
try:
|
| 554 |
+
# Use per-task grader module (tasks/{task_id}/grader.py)
|
| 555 |
+
mod = importlib.import_module(f"tasks.{task_id}.grader")
|
| 556 |
+
result = mod.grade_episode(req.episode or {})
|
| 557 |
return {
|
| 558 |
+
"task_id": task_id,
|
| 559 |
+
"score": result["score"],
|
| 560 |
+
"passed": result["passed"],
|
| 561 |
"score_range": [0.0, 1.0],
|
| 562 |
+
"grader": f"tasks.{task_id}.grader.grade",
|
| 563 |
}
|
|
|
|
|
|
|
| 564 |
except Exception as e:
|
| 565 |
logger.error("Grading failed: %s", traceback.format_exc())
|
| 566 |
raise HTTPException(status_code=500, detail=f"Grading failed: {str(e)}")
|
tasks/easy/__init__.py
ADDED
|
File without changes
|
tasks/easy/grader.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/easy/grader.py
|
| 3 |
+
====================
|
| 4 |
+
Grader for the easy content-moderation task.
|
| 5 |
+
|
| 6 |
+
Exposes a module-level ``grade(output, expected)`` function that the
|
| 7 |
+
OpenEnv submission platform calls to verify this task has a working grader.
|
| 8 |
+
|
| 9 |
+
Also delegates to EasyGrader for full episode scoring.
|
| 10 |
+
"""
|
| 11 |
+
|
| 12 |
+
from __future__ import annotations
|
| 13 |
+
|
| 14 |
+
from tasks.graders import EasyGrader
|
| 15 |
+
|
| 16 |
+
_grader = EasyGrader()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def grade(output: str, expected: str) -> dict:
|
| 20 |
+
"""
|
| 21 |
+
Grade a single moderation decision against the expected label.
|
| 22 |
+
|
| 23 |
+
Parameters
|
| 24 |
+
----------
|
| 25 |
+
output : agent's decision ("allow" | "flag" | "remove")
|
| 26 |
+
expected : ground-truth ("allow" | "flag" | "remove")
|
| 27 |
+
|
| 28 |
+
Returns
|
| 29 |
+
-------
|
| 30 |
+
{"score": float, "passed": bool}
|
| 31 |
+
"""
|
| 32 |
+
out = str(output).strip().lower()
|
| 33 |
+
exp = str(expected).strip().lower()
|
| 34 |
+
|
| 35 |
+
if out == exp:
|
| 36 |
+
score = 1.0
|
| 37 |
+
elif (out, exp) in {("flag", "remove"), ("remove", "flag"),
|
| 38 |
+
("flag", "allow"), ("allow", "flag")}:
|
| 39 |
+
score = 0.5
|
| 40 |
+
else:
|
| 41 |
+
score = 0.0
|
| 42 |
+
|
| 43 |
+
return {"score": score, "passed": score > 0.0}
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def grade_episode(episode: dict) -> dict:
|
| 47 |
+
"""Grade a full episode dict and return score + passed."""
|
| 48 |
+
score = _grader.grade(episode)
|
| 49 |
+
return {"score": score, "passed": True}
|
tasks/easy/task.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/easy/task.py
|
| 3 |
+
==================
|
| 4 |
+
Task definition for the easy difficulty tier.
|
| 5 |
+
|
| 6 |
+
Clear-cut misinformation: overwhelming contradicting evidence from
|
| 7 |
+
authoritative sources (WHO, CDC, NASA). Correct decision is unambiguous.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
TASK_ID = "easy"
|
| 11 |
+
DESCRIPTION = (
|
| 12 |
+
"Content moderation — easy difficulty. "
|
| 13 |
+
"Agent must verify claims against authoritative sources and make a "
|
| 14 |
+
"moderation decision (allow / flag / remove). "
|
| 15 |
+
"Correct decisions are clear-cut with overwhelming evidence."
|
| 16 |
+
)
|
| 17 |
+
EXPECTED_DECISION_EXAMPLES = ["remove", "flag", "allow"]
|
| 18 |
+
SCORE_RANGE = [0.0, 1.0]
|
| 19 |
+
N_SCENARIOS = 5
|
tasks/hard/__init__.py
ADDED
|
File without changes
|
tasks/hard/grader.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/hard/grader.py
|
| 3 |
+
====================
|
| 4 |
+
Grader for the hard content-moderation task.
|
| 5 |
+
|
| 6 |
+
Exposes a module-level ``grade(output, expected)`` function that the
|
| 7 |
+
OpenEnv submission platform calls to verify this task has a working grader.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
from tasks.graders import HardGrader
|
| 13 |
+
|
| 14 |
+
_grader = HardGrader()
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def grade(output: str, expected: str) -> dict:
|
| 18 |
+
"""
|
| 19 |
+
Grade a single moderation decision against the expected label.
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
output : agent's decision ("allow" | "flag" | "remove")
|
| 24 |
+
expected : ground-truth ("allow" | "flag" | "remove")
|
| 25 |
+
|
| 26 |
+
Returns
|
| 27 |
+
-------
|
| 28 |
+
{"score": float, "passed": bool}
|
| 29 |
+
"""
|
| 30 |
+
out = str(output).strip().lower()
|
| 31 |
+
exp = str(expected).strip().lower()
|
| 32 |
+
|
| 33 |
+
if out == exp:
|
| 34 |
+
score = 1.0
|
| 35 |
+
elif (out, exp) in {("flag", "remove"), ("remove", "flag"),
|
| 36 |
+
("flag", "allow"), ("allow", "flag")}:
|
| 37 |
+
score = 0.5
|
| 38 |
+
else:
|
| 39 |
+
score = 0.0
|
| 40 |
+
|
| 41 |
+
return {"score": score, "passed": score > 0.0}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def grade_episode(episode: dict) -> dict:
|
| 45 |
+
"""Grade a full episode dict and return score + passed."""
|
| 46 |
+
score = _grader.grade(episode)
|
| 47 |
+
return {"score": score, "passed": True}
|
tasks/hard/task.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/hard/task.py
|
| 3 |
+
==================
|
| 4 |
+
Task definition for the hard difficulty tier.
|
| 5 |
+
|
| 6 |
+
Conflicting evidence from credible sources; genuinely contested claims.
|
| 7 |
+
Requires nuanced multi-claim reasoning and tolerance for ambiguity.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
TASK_ID = "hard"
|
| 11 |
+
DESCRIPTION = (
|
| 12 |
+
"Content moderation — hard difficulty. "
|
| 13 |
+
"Agent must reason over conflicting evidence from credible sources and "
|
| 14 |
+
"make a moderation decision (allow / flag / remove). "
|
| 15 |
+
"Ground truth is ambiguous — all scoring dimensions count equally."
|
| 16 |
+
)
|
| 17 |
+
EXPECTED_DECISION_EXAMPLES = ["flag"]
|
| 18 |
+
SCORE_RANGE = [0.0, 1.0]
|
| 19 |
+
N_SCENARIOS = 5
|
tasks/medium/__init__.py
ADDED
|
File without changes
|
tasks/medium/grader.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/medium/grader.py
|
| 3 |
+
======================
|
| 4 |
+
Grader for the medium content-moderation task.
|
| 5 |
+
|
| 6 |
+
Exposes a module-level ``grade(output, expected)`` function that the
|
| 7 |
+
OpenEnv submission platform calls to verify this task has a working grader.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
from tasks.graders import MediumGrader
|
| 13 |
+
|
| 14 |
+
_grader = MediumGrader()
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def grade(output: str, expected: str) -> dict:
|
| 18 |
+
"""
|
| 19 |
+
Grade a single moderation decision against the expected label.
|
| 20 |
+
|
| 21 |
+
Parameters
|
| 22 |
+
----------
|
| 23 |
+
output : agent's decision ("allow" | "flag" | "remove")
|
| 24 |
+
expected : ground-truth ("allow" | "flag" | "remove")
|
| 25 |
+
|
| 26 |
+
Returns
|
| 27 |
+
-------
|
| 28 |
+
{"score": float, "passed": bool}
|
| 29 |
+
"""
|
| 30 |
+
out = str(output).strip().lower()
|
| 31 |
+
exp = str(expected).strip().lower()
|
| 32 |
+
|
| 33 |
+
if out == exp:
|
| 34 |
+
score = 1.0
|
| 35 |
+
elif (out, exp) in {("flag", "remove"), ("remove", "flag"),
|
| 36 |
+
("flag", "allow"), ("allow", "flag")}:
|
| 37 |
+
score = 0.5
|
| 38 |
+
else:
|
| 39 |
+
score = 0.0
|
| 40 |
+
|
| 41 |
+
return {"score": score, "passed": score > 0.0}
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def grade_episode(episode: dict) -> dict:
|
| 45 |
+
"""Grade a full episode dict and return score + passed."""
|
| 46 |
+
score = _grader.grade(episode)
|
| 47 |
+
return {"score": score, "passed": True}
|
tasks/medium/task.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
tasks/medium/task.py
|
| 3 |
+
====================
|
| 4 |
+
Task definition for the medium difficulty tier.
|
| 5 |
+
|
| 6 |
+
Partially true or misleading content with mixed evidence. Requires
|
| 7 |
+
careful claim-by-claim verification and evidence weighing.
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
TASK_ID = "medium"
|
| 11 |
+
DESCRIPTION = (
|
| 12 |
+
"Content moderation — medium difficulty. "
|
| 13 |
+
"Agent must verify claims, evaluate mixed evidence sources, and make a "
|
| 14 |
+
"moderation decision (allow / flag / remove). "
|
| 15 |
+
"Content is partially true or misleading — nuanced reasoning required."
|
| 16 |
+
)
|
| 17 |
+
EXPECTED_DECISION_EXAMPLES = ["flag", "allow"]
|
| 18 |
+
SCORE_RANGE = [0.0, 1.0]
|
| 19 |
+
N_SCENARIOS = 5
|