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 CHANGED
@@ -151,8 +151,8 @@ tasks:
151
  n_scenarios: 5
152
  grader:
153
  enabled: true
154
- class: tasks.graders.EasyGrader
155
- method: 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,8 +168,8 @@ tasks:
168
  n_scenarios: 5
169
  grader:
170
  enabled: true
171
- class: tasks.graders.MediumGrader
172
- method: 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,8 +185,8 @@ tasks:
185
  n_scenarios: 5
186
  grader:
187
  enabled: true
188
- class: tasks.graders.HardGrader
189
- method: 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
 
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.graders.{grader.__class__.__name__}",
316
- "method": "grade",
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
- This endpoint allows the OpenEnv validator to test graders.
 
543
  """
544
- from tasks.graders import GraderFactory
545
-
 
 
 
 
546
  try:
547
- grader = GraderFactory.get(req.task_id)
548
- score = grader.grade(req.episode or {})
549
-
550
  return {
551
- "task_id": req.task_id,
552
- "score": score,
553
- "passed": True, # grader ran successfully and returned a valid score
554
  "score_range": [0.0, 1.0],
555
- "grader": f"tasks.graders.{grader.__class__.__name__}"
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