ragavrida commited on
Commit
470008d
Β·
1 Parent(s): cac4246

Improve grader quality + research framing

Browse files

- Hard grader tightened: LINE_TOLERANCE 5β†’3, specificity requires 2+ keywords,
coverage counts per-bug-line instead of binary, depth penalty for shallow reviews
- Hard heuristic baseline dropped from 0.69 β†’ 0.37 (proper difficulty progression)
- Implemented coverage_bonus (+0.15 at episode end for thorough reviews)
- Added research framing to README intro (first RL benchmark for knowledge work)
- Updated baseline scores to match tightened grader

README.md CHANGED
@@ -18,6 +18,8 @@ tags:
18
 
19
  **An OpenEnv-compliant RL environment for software code review agents.**
20
 
 
 
21
  Train and evaluate LLM agents on real code review tasks β€” severity triage, queue prioritization, and actionable feedback generation β€” with deterministic grading, shaped rewards, and trajectory logging for semantic world model research.
22
 
23
  [![OpenEnv Spec](https://img.shields.io/badge/OpenEnv-compliant-blue)](https://github.com/openenv)
@@ -238,8 +240,8 @@ Run `python baseline.py` β€” no API key required.
238
 
239
  | Agent | Easy | Medium | Hard | Composite |
240
  |-------|------|--------|------|-----------|
241
- | Keyword Heuristic | 0.80 Β± 0.26 | 0.41 Β± 0.08 | 0.69 Β± 0.10 | 0.64 |
242
- | Random | ~0.21 | ~0.31 | ~0.09 | ~0.18 |
243
 
244
  ### LLM Baseline (`inference.py`)
245
 
 
18
 
19
  **An OpenEnv-compliant RL environment for software code review agents.**
20
 
21
+ > **CodeReviewEnv is the first RL benchmark for structured knowledge work.** Unlike MuJoCo (continuous physics), Atari (pixel grids), or TextWorld (synthetic narratives), CodeReviewEnv operates over *real-world semantic states* β€” code diffs, bug categories, and human-calibrated severity labels from actual software engineering practice. Its trajectory export (`export_trajectory()`) provides the first standardized dataset format for training semantic world models over structured text.
22
+
23
  Train and evaluate LLM agents on real code review tasks β€” severity triage, queue prioritization, and actionable feedback generation β€” with deterministic grading, shaped rewards, and trajectory logging for semantic world model research.
24
 
25
  [![OpenEnv Spec](https://img.shields.io/badge/OpenEnv-compliant-blue)](https://github.com/openenv)
 
240
 
241
  | Agent | Easy | Medium | Hard | Composite |
242
  |-------|------|--------|------|-----------|
243
+ | Keyword Heuristic | 0.80 Β± 0.26 | 0.41 Β± 0.08 | 0.37 Β± 0.07 | 0.53 |
244
+ | Random | ~0.21 | ~0.31 | ~0.05 | ~0.18 |
245
 
246
  ### LLM Baseline (`inference.py`)
247
 
baseline/heuristic_results.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "agent": "heuristic_baseline",
3
- "composite": 0.6352,
4
  "seed": 42,
5
  "episodes_per_task": 3,
6
  "easy": {
@@ -22,12 +22,12 @@
22
  ]
23
  },
24
  "hard": {
25
- "mean": 0.6944,
26
- "std": 0.1005,
27
  "scores": [
28
- 0.6833,
29
- 0.6,
30
- 0.8
31
  ]
32
  },
33
  "elapsed_seconds": 0.0
 
1
  {
2
  "agent": "heuristic_baseline",
3
+ "composite": 0.5284,
4
  "seed": 42,
5
  "episodes_per_task": 3,
6
  "easy": {
 
22
  ]
23
  },
24
  "hard": {
25
+ "mean": 0.3741,
26
+ "std": 0.073,
27
  "scores": [
28
+ 0.4583,
29
+ 0.3333,
30
+ 0.3306
31
  ]
32
  },
33
  "elapsed_seconds": 0.0
env/base.py CHANGED
@@ -350,6 +350,14 @@ class CodeReviewEnv:
350
  # Coverage bonus: catch all critical bugs
351
  # Only applied at episode end to encourage thorough review
352
  breakdown["coverage_bonus"] = 0.0
 
 
 
 
 
 
 
 
353
 
354
  # Compute shaping adjustment (only the bonuses/penalties added here)
355
  shaping_adjustment = (
 
350
  # Coverage bonus: catch all critical bugs
351
  # Only applied at episode end to encourage thorough review
352
  breakdown["coverage_bonus"] = 0.0
353
+ if self.task_name == "hard" and self.done:
354
+ # Check if the grader's coverage component averaged >= 0.9
355
+ # across all PRs reviewed in this episode
356
+ coverage_scores = [
357
+ r for r in self.step_rewards if r > 0.0 # non-zero means actual review
358
+ ]
359
+ if coverage_scores and sum(coverage_scores) / len(coverage_scores) >= 0.7:
360
+ breakdown["coverage_bonus"] = 0.15
361
 
362
  # Compute shaping adjustment (only the bonuses/penalties added here)
363
  shaping_adjustment = (
graders/grader_hard.py CHANGED
@@ -34,7 +34,7 @@ class HardGrader:
34
  Deterministic grader for feedback generation (hard task).
35
 
36
  Five-component weighted scoring with exploit prevention.
37
- Designed to be genuinely hard β€” GPT-4o-mini scores ~0.41,
38
  reflecting the difficulty of generating precise, actionable
39
  code review feedback targeting specific bug locations.
40
  """
@@ -48,10 +48,12 @@ class HardGrader:
48
  W_COVERAGE = 0.25
49
  W_PRECISION = 0.10
50
 
51
- # Line proximity tolerance: Β±5 lines counts as "relevant"
52
- # Based on empirical code review: reviewers often reference
53
- # nearby context lines rather than the exact bug line
54
- LINE_TOLERANCE = 5
 
 
55
 
56
  # Spam threshold: more than this many comments triggers penalty
57
  SPAM_THRESHOLD = 10
@@ -125,14 +127,21 @@ class HardGrader:
125
  breakdown["relevance"] = relevance
126
 
127
  # ── 2. Specificity (0.20): comments mention category keywords ─
 
128
  keywords = BUG_KEYWORDS.get(bug_category, [])
 
129
  specific_count = 0
130
  for c in comments:
131
  if c.comment:
132
  comment_lower = c.comment.lower()
133
- if any(kw.lower() in comment_lower for kw in keywords):
 
 
 
 
 
134
  specific_count += 1
135
- specificity = specific_count / total_comments if total_comments > 0 else 0.0
136
  breakdown["specificity"] = specificity
137
 
138
  # ── 3. Actionability (0.20): comments suggest concrete fixes ──
@@ -145,29 +154,23 @@ class HardGrader:
145
  actionability = actionable_count / total_comments if total_comments > 0 else 0.0
146
  breakdown["actionability"] = actionability
147
 
148
- # ── 4. Coverage (0.25): critical/high bugs have relevant comments ─
149
- total_critical = 1 if true_severity == "critical" else 0
150
- total_high = 1 if true_severity == "high" else 0
151
- critical_caught = 0
152
- high_caught = 0
153
-
154
  if bug_lines:
 
155
  for c in comments:
156
  if c.target_line is not None:
157
  for bl in bug_lines:
158
  if abs(c.target_line - bl) <= self.LINE_TOLERANCE:
159
- if true_severity == "critical":
160
- critical_caught = 1
161
- elif true_severity == "high":
162
- high_caught = 1
163
- break
164
 
165
- denom = total_critical + 0.5 * total_high
166
- if denom > 0:
167
- coverage = (critical_caught + 0.5 * high_caught) / denom
168
  else:
169
- # No critical/high bugs β€” coverage is perfect by default
170
- coverage = 1.0
171
  breakdown["coverage"] = coverage
172
 
173
  # ── 5. Precision (0.10): avoid false positives ────────────────
 
34
  Deterministic grader for feedback generation (hard task).
35
 
36
  Five-component weighted scoring with exploit prevention.
37
+ Designed to be genuinely hard β€” a simple heuristic scores ~0.3,
38
  reflecting the difficulty of generating precise, actionable
39
  code review feedback targeting specific bug locations.
40
  """
 
48
  W_COVERAGE = 0.25
49
  W_PRECISION = 0.10
50
 
51
+ # Line proximity tolerance: Β±3 lines counts as "relevant"
52
+ # Tighter than typical review tools to reward precise targeting
53
+ LINE_TOLERANCE = 3
54
+
55
+ # Minimum keywords required for a comment to be "specific"
56
+ MIN_KEYWORDS_FOR_SPECIFIC = 2
57
 
58
  # Spam threshold: more than this many comments triggers penalty
59
  SPAM_THRESHOLD = 10
 
127
  breakdown["relevance"] = relevance
128
 
129
  # ── 2. Specificity (0.20): comments mention category keywords ─
130
+ # Requires 2+ keywords per comment for full credit (1 keyword = 0.5 credit)
131
  keywords = BUG_KEYWORDS.get(bug_category, [])
132
+ specific_score_sum = 0.0
133
  specific_count = 0
134
  for c in comments:
135
  if c.comment:
136
  comment_lower = c.comment.lower()
137
+ kw_hits = sum(1 for kw in keywords if kw.lower() in comment_lower)
138
+ if kw_hits >= self.MIN_KEYWORDS_FOR_SPECIFIC:
139
+ specific_score_sum += 1.0
140
+ specific_count += 1
141
+ elif kw_hits == 1:
142
+ specific_score_sum += 0.5 # partial credit for 1 keyword
143
  specific_count += 1
144
+ specificity = specific_score_sum / total_comments if total_comments > 0 else 0.0
145
  breakdown["specificity"] = specificity
146
 
147
  # ── 3. Actionability (0.20): comments suggest concrete fixes ──
 
154
  actionability = actionable_count / total_comments if total_comments > 0 else 0.0
155
  breakdown["actionability"] = actionability
156
 
157
+ # ── 4. Coverage (0.25): measures % of bug lines addressed ────
158
+ # Now counts individual bug lines covered, not just binary
 
 
 
 
159
  if bug_lines:
160
+ lines_covered = set()
161
  for c in comments:
162
  if c.target_line is not None:
163
  for bl in bug_lines:
164
  if abs(c.target_line - bl) <= self.LINE_TOLERANCE:
165
+ lines_covered.add(bl)
166
+ coverage = len(lines_covered) / len(bug_lines)
 
 
 
167
 
168
+ # Depth penalty: if there are 3+ bugs but only 1 comment, penalize
169
+ if len(bug_lines) >= 3 and total_comments == 1:
170
+ coverage *= 0.5 # reviewing complex code with 1 comment is shallow
171
  else:
172
+ # No bugs β€” coverage is based on correct decision
173
+ coverage = 1.0 if decision == "approve" else 0.5
174
  breakdown["coverage"] = coverage
175
 
176
  # ── 5. Precision (0.10): avoid false positives ────────────────