Spaces:
Sleeping
Sleeping
File size: 6,982 Bytes
a2ae9c3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | from typing import Dict, Any
from env.models import Observation, Action, EpisodeState
from env.tasks import TASKS
from env.grader import grade_identify, grade_fix
def _clamp(reward: float) -> float:
"""Clamp reward strictly to [0.0, 1.0] per OpenEnv spec."""
return round(max(0.0, min(reward, 1.0)), 4)
class CodeReviewEnvironment:
"""
OpenEnv-compliant code review environment.
Agent reads buggy code, identifies issues, and suggests fixes.
3 tasks: easy (syntax) β medium (logic) β hard (performance)
All rewards are clamped to [0.0, 1.0] before being returned.
"""
def __init__(self):
self._tasks = TASKS
self._current_task_index = 0
self._current_step = 0
self._history = []
self._phase = "identify" # "identify" β "fix"
self._identify_reward = 0.0
self._total_reward = 0.0
self._done = False
# ------------------------------------------------------------------
# OpenEnv required: reset()
# ------------------------------------------------------------------
def reset(self) -> Observation:
self._current_task_index = 0
self._current_step = 0
self._history = []
self._phase = "identify"
self._identify_reward = 0.0
self._total_reward = 0.0
self._done = False
return self._make_observation()
# ------------------------------------------------------------------
# OpenEnv required: step(action)
# ------------------------------------------------------------------
def step(self, action: Action) -> Dict[str, Any]:
if self._done:
return {
"observation": self._make_observation(),
"reward": 0.0,
"done": True,
"info": {"error": "Episode already done. Call reset()."},
}
task = self._current_task()
reward = 0.0
info = {}
self._current_step += 1
# ββ IDENTIFY phase ββββββββββββββββββββββββββββββββββββββββββββββ
if action.action_type == "identify":
if self._phase == "fix":
# Repeated identify after fix phase β penalise (clamped to 0.0)
reward = 0.0
info["warning"] = "Already in fix phase. Skipping repeated identify."
else:
reward = grade_identify(task["identify_keywords"], action.content)
self._identify_reward = reward
self._phase = "fix"
info["phase_transition"] = "identify β fix"
info["identify_score"] = reward
# ββ FIX phase βββββββββββββββββββββββββββββββββββββββββββββββββββ
elif action.action_type == "fix":
if self._phase == "identify":
# Jumped straight to fix without identifying β partial credit only
fix_score = grade_fix(task["fixed_code"], action.content)
reward = fix_score * 0.5 # halved because no identify step
info["warning"] = "Skipped identify phase. Partial fix credit."
else:
fix_score = grade_fix(task["fixed_code"], action.content)
# Identify bonus for continuous signal β final reward clamped to 1.0
bonus = 0.1 if self._identify_reward >= 0.4 else 0.0
reward = fix_score + bonus
info["fix_score"] = fix_score
info["identify_bonus"] = bonus
# Clamp before recording and returning
reward = _clamp(reward)
self._total_reward += reward
# Move to next task or end episode
done = self._advance_task()
self._phase = "identify"
self._identify_reward = 0.0
obs = self._make_observation()
self._history.append(
f"[task={task['id']}] action={action.action_type} reward={reward:.2f}"
)
info["task_completed"] = task["id"]
info["task_difficulty"] = task["difficulty"]
return {
"observation": obs,
"reward": reward,
"done": done,
"info": info,
}
else:
# Unknown action type β penalise, clamped to 0.0
reward = 0.0
info["error"] = f"Unknown action_type '{action.action_type}'. Use 'identify' or 'fix'."
reward = _clamp(reward)
self._total_reward += reward
self._history.append(
f"[task={task['id']}] action={action.action_type} reward={reward:.2f}"
)
return {
"observation": self._make_observation(),
"reward": reward,
"done": self._done,
"info": info,
}
# ------------------------------------------------------------------
# OpenEnv required: state (property)
# ------------------------------------------------------------------
@property
def state(self) -> EpisodeState:
task = self._current_task()
return EpisodeState(
current_task_id=task["id"],
current_task_difficulty=task["difficulty"],
current_task_category=task["category"],
phase=self._phase,
step=self._current_step,
total_reward=round(self._total_reward, 4),
done=self._done,
history=list(self._history),
)
# ------------------------------------------------------------------
# Internal helpers
# ------------------------------------------------------------------
def _current_task(self) -> dict:
# Clamp to last task when episode is done to avoid index error
idx = min(self._current_task_index, len(self._tasks) - 1)
return self._tasks[idx]
def _make_observation(self) -> Observation:
task = self._current_task()
task_prompt = (
f"[{task['difficulty'].upper()} | {task['category']}] {task['title']}\n"
f"{task['description']}\n\n"
f"Phase: {self._phase.upper()}\n"
f"{'Identify the bug.' if self._phase == 'identify' else 'Fix the code.'}"
)
return Observation(
code=task["code"],
task=task_prompt,
history=list(self._history),
task_id=task["id"],
language=task["language"],
difficulty=task["difficulty"],
category=task["category"],
)
def _advance_task(self) -> bool:
"""Move to next task. Returns True if episode is done."""
self._current_task_index += 1
if self._current_task_index >= len(self._tasks):
self._done = True
return True
return False |