I766446 commited on
Commit
791a54b
·
1 Parent(s): 71b519d

Rewrite graders: grade(episode)->float, no external deps, difficulty progression

Browse files
Files changed (4) hide show
  1. server.py +6 -6
  2. tasks/easy/grader.py +45 -37
  3. tasks/hard/grader.py +52 -31
  4. tasks/medium/grader.py +52 -30
server.py CHANGED
@@ -546,13 +546,13 @@ async def grade_episode(req: GradeRequest):
546
  raise HTTPException(status_code=400, detail=f"Unknown task_id '{task_id}'")
547
 
548
  try:
549
- # Use per-task grader module (tasks/{task_id}/grader.py)
550
- mod = importlib.import_module(f"tasks.{task_id}.grader")
551
- result = mod.grade_episode(req.episode or {})
552
  return {
553
- "task_id": task_id,
554
- "score": result["score"],
555
- "passed": result["passed"],
556
  "score_range": [0.0, 1.0],
557
  "grader": f"tasks.{task_id}.grader.grade",
558
  }
 
546
  raise HTTPException(status_code=400, detail=f"Unknown task_id '{task_id}'")
547
 
548
  try:
549
+ mod = importlib.import_module(f"tasks.{task_id}.grader")
550
+ score = mod.grade(req.episode or {}) # grade(episode) -> float
551
+ score = float(score)
552
  return {
553
+ "task_id": task_id,
554
+ "score": round(max(0.005, min(0.995, score)), 4),
555
+ "passed": True,
556
  "score_range": [0.0, 1.0],
557
  "grader": f"tasks.{task_id}.grader.grade",
558
  }
tasks/easy/grader.py CHANGED
@@ -1,39 +1,47 @@
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) -> float:
20
- out = str(output).strip().lower()
21
- exp = str(expected).strip().lower()
22
-
23
- if out == exp:
24
- score = 1.0
25
- elif (out, exp) in {
26
- ("flag", "remove"), ("remove", "flag"),
27
- ("flag", "allow"), ("allow", "flag")
28
- }:
29
- score = 0.5
30
- else:
31
- score = 0.0
32
-
33
- return float(score)
34
-
35
-
36
- def grade_episode(episode: dict) -> dict:
37
- """Grade a full episode dict and return score + passed."""
38
- score = _grader.grade(episode)
39
- return {"score": score, "passed": True}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """tasks/easy/grader.py — OpenEnv-compliant grader for the easy task."""
 
 
 
 
 
 
 
 
 
 
2
  from __future__ import annotations
3
 
4
+ _CREDIT = {
5
+ ("allow", "allow"): 1.0,
6
+ ("flag", "flag"): 1.0,
7
+ ("remove", "remove"): 1.0,
8
+ ("flag", "remove"): 0.5,
9
+ ("remove", "flag"): 0.5,
10
+ ("flag", "allow"): 0.5,
11
+ ("allow", "flag"): 0.5,
12
+ ("allow", "remove"): 0.0,
13
+ ("remove", "allow"): 0.0,
14
+ }
15
+
16
+
17
+ def grade(episode: dict) -> float:
18
+ """
19
+ OpenEnv grader — easy difficulty.
20
+ Returns a float strictly in (0.0, 1.0).
21
+
22
+ Weights: decision 50% | evidence 25% | reasoning 15% | efficiency 10%
23
+ """
24
+ decision = (episode.get("final_decision") or
25
+ episode.get("decision") or "none").lower().strip()
26
+ ground_truth = (episode.get("ground_truth") or
27
+ episode.get("correct_decision") or "none").lower().strip()
28
+
29
+ decision_score = _CREDIT.get((decision, ground_truth), 0.0)
30
+
31
+ all_src = episode.get("all_source_ids") or []
32
+ checked = episode.get("sources_checked") or []
33
+ evidence_score = min(1.0, len(set(checked)) / len(all_src)) if all_src else 1.0
34
+
35
+ all_clm = episode.get("all_claim_ids") or []
36
+ verified = episode.get("claims_verified") or []
37
+ reasoning_score = min(1.0, len(set(verified)) / len(all_clm)) if all_clm else 1.0
38
+
39
+ steps = int(episode.get("step_count") or 0)
40
+ efficiency_score = 1.0 if steps <= 8 else max(0.0, 1.0 - 0.1 * (steps - 8))
41
+
42
+ raw = (0.50 * decision_score +
43
+ 0.25 * evidence_score +
44
+ 0.15 * reasoning_score +
45
+ 0.10 * efficiency_score)
46
+
47
+ return round(max(0.005, min(0.995, raw)), 4)
tasks/hard/grader.py CHANGED
@@ -1,37 +1,58 @@
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) -> float:
18
- out = str(output).strip().lower()
19
- exp = str(expected).strip().lower()
20
-
21
- if out == exp:
22
- score = 1.0
23
- elif (out, exp) in {
24
- ("flag", "remove"), ("remove", "flag"),
25
- ("flag", "allow"), ("allow", "flag")
26
- }:
27
- score = 0.5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  else:
29
- score = 0.0
 
 
 
 
 
 
30
 
31
- return float(score)
 
 
32
 
 
 
 
 
33
 
34
- def grade_episode(episode: dict) -> dict:
35
- """Grade a full episode dict and return score + passed."""
36
- score = _grader.grade(episode)
37
- return {"score": score, "passed": True}
 
1
+ """tasks/hard/grader.py — OpenEnv-compliant grader for the hard task."""
 
 
 
 
 
 
 
 
2
  from __future__ import annotations
3
 
4
+ _CREDIT = {
5
+ ("allow", "allow"): 1.0,
6
+ ("flag", "flag"): 1.0,
7
+ ("remove", "remove"): 1.0,
8
+ ("flag", "remove"): 0.5,
9
+ ("remove", "flag"): 0.5,
10
+ ("flag", "allow"): 0.5,
11
+ ("allow", "flag"): 0.5,
12
+ ("allow", "remove"): 0.25, # hard: never 0 for non-trivially wrong
13
+ ("remove", "allow"): 0.25,
14
+ }
15
+
16
+
17
+ def grade(episode: dict) -> float:
18
+ """
19
+ OpenEnv grader — hard difficulty.
20
+ Returns a float strictly in (0.0, 1.0).
21
+
22
+ Weights: decision 30% | evidence 30% | reasoning 30% | efficiency 10%
23
+ All claims must be verified for full reasoning score.
24
+ """
25
+ decision = (episode.get("final_decision") or
26
+ episode.get("decision") or "none").lower().strip()
27
+ ground_truth = (episode.get("ground_truth") or
28
+ episode.get("correct_decision") or "none").lower().strip()
29
+
30
+ decision_score = _CREDIT.get((decision, ground_truth), 0.0)
31
+
32
+ all_src = episode.get("all_source_ids") or []
33
+ checked = episode.get("sources_checked") or []
34
+ frac_src = (len(set(checked)) / len(all_src)) if all_src else 1.0
35
+ # Hard requires 60% minimum for any credit
36
+ if frac_src >= 1.0:
37
+ evidence_score = 1.0
38
+ elif frac_src >= 0.60:
39
+ evidence_score = 0.5 + 0.5 * (frac_src - 0.60) / 0.40
40
  else:
41
+ evidence_score = frac_src / 0.60 * 0.5
42
+
43
+ all_clm = episode.get("all_claim_ids") or []
44
+ verified = episode.get("claims_verified") or []
45
+ frac_clm = (len(set(verified)) / len(all_clm)) if all_clm else 1.0
46
+ # Hard requires ALL claims verified for full score
47
+ reasoning_score = min(1.0, 0.6 + 0.4 * frac_clm) if frac_clm >= 0.5 else frac_clm * 0.6 / 0.5
48
 
49
+ steps = int(episode.get("step_count") or 0)
50
+ # Hard: efficiency barely penalised — thorough exploration is rewarded
51
+ efficiency_score = 1.0 if steps <= 18 else max(0.0, 1.0 - 0.05 * (steps - 18))
52
 
53
+ raw = (0.30 * decision_score +
54
+ 0.30 * evidence_score +
55
+ 0.30 * reasoning_score +
56
+ 0.10 * efficiency_score)
57
 
58
+ return round(max(0.005, min(0.995, raw)), 4)
 
 
 
tasks/medium/grader.py CHANGED
@@ -1,36 +1,58 @@
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) -> float:
18
- out = str(output).strip().lower()
19
- exp = str(expected).strip().lower()
 
20
 
21
- if out == exp:
22
- score = 1.0
23
- elif (out, exp) in {
24
- ("flag", "remove"), ("remove", "flag"),
25
- ("flag", "allow"), ("allow", "flag")
26
- }:
27
- score = 0.5
28
- else:
29
- score = 0.0
30
 
31
- return float(score)
 
 
 
32
 
33
- def grade_episode(episode: dict) -> dict:
34
- """Grade a full episode dict and return score + passed."""
35
- score = _grader.grade(episode)
36
- return {"score": score, "passed": True}
 
1
+ """tasks/medium/grader.py — OpenEnv-compliant grader for the medium task."""
 
 
 
 
 
 
 
 
2
  from __future__ import annotations
3
 
4
+ _CREDIT = {
5
+ ("allow", "allow"): 1.0,
6
+ ("flag", "flag"): 1.0,
7
+ ("remove", "remove"): 1.0,
8
+ ("flag", "remove"): 0.5,
9
+ ("remove", "flag"): 0.5,
10
+ ("flag", "allow"): 0.5,
11
+ ("allow", "flag"): 0.5,
12
+ ("allow", "remove"): 0.0,
13
+ ("remove", "allow"): 0.0,
14
+ }
15
+
16
+
17
+ def grade(episode: dict) -> float:
18
+ """
19
+ OpenEnv grader — medium difficulty.
20
+ Returns a float strictly in (0.0, 1.0).
21
+
22
+ Weights: decision 35% | evidence 35% | reasoning 20% | efficiency 10%
23
+ Higher evidence and reasoning requirements than easy.
24
+ """
25
+ decision = (episode.get("final_decision") or
26
+ episode.get("decision") or "none").lower().strip()
27
+ ground_truth = (episode.get("ground_truth") or
28
+ episode.get("correct_decision") or "none").lower().strip()
29
+
30
+ decision_score = _CREDIT.get((decision, ground_truth), 0.0)
31
+
32
+ all_src = episode.get("all_source_ids") or []
33
+ checked = episode.get("sources_checked") or []
34
+ frac_src = (len(set(checked)) / len(all_src)) if all_src else 1.0
35
+ # Medium requires 75% source coverage for full score
36
+ if frac_src >= 0.75:
37
+ evidence_score = 1.0
38
+ elif frac_src >= 0.50:
39
+ evidence_score = 0.6 + 0.4 * (frac_src - 0.50) / 0.25
40
+ elif frac_src >= 0.33:
41
+ evidence_score = 0.3 + 0.3 * (frac_src - 0.33) / 0.17
42
+ else:
43
+ evidence_score = frac_src / 0.33 * 0.3
44
 
45
+ all_clm = episode.get("all_claim_ids") or []
46
+ verified = episode.get("claims_verified") or []
47
+ frac_clm = (len(set(verified)) / len(all_clm)) if all_clm else 1.0
48
+ reasoning_score = min(1.0, frac_clm / 0.75) if frac_clm < 0.75 else 1.0
49
 
50
+ steps = int(episode.get("step_count") or 0)
51
+ efficiency_score = 1.0 if steps <= 12 else max(0.0, 1.0 - 0.1 * (steps - 12))
 
 
 
 
 
 
 
52
 
53
+ raw = (0.35 * decision_score +
54
+ 0.35 * evidence_score +
55
+ 0.20 * reasoning_score +
56
+ 0.10 * efficiency_score)
57
 
58
+ return round(max(0.005, min(0.995, raw)), 4)