| from __future__ import annotations |
|
|
| import json |
| import os |
| from dataclasses import dataclass |
| from typing import Any, Dict, List, Optional |
|
|
| from graders import grade_action |
|
|
|
|
| @dataclass |
| class EnvironmentState: |
| task_id: str |
| step_count: int |
| max_steps: int |
| score_so_far: float |
| done: bool |
|
|
|
|
| class SolidityGuardEnv: |
| def __init__(self, data_path: str = "data/manifest.json") -> None: |
| self.data_path = data_path |
| self._load_manifest() |
| self._index = 0 |
| self._current_sample: Optional[Dict[str, Any]] = None |
| self._state: Optional[EnvironmentState] = None |
|
|
| def _load_manifest(self) -> None: |
| if not os.path.exists(self.data_path): |
| raise FileNotFoundError(f"Manifest not found: {self.data_path}") |
| with open(self.data_path, "r", encoding="utf-8") as handle: |
| self._manifest = json.load(handle) |
| if not isinstance(self._manifest, list) or not self._manifest: |
| raise ValueError("Manifest must be a non-empty list") |
|
|
| def reset(self, task_id: Optional[str] = None) -> Dict[str, Any]: |
| if task_id: |
| candidates = [item for item in self._manifest if item["task_id"] == task_id] |
| else: |
| candidates = self._manifest |
|
|
| if not candidates: |
| raise ValueError("No samples available for the requested task") |
|
|
| self._current_sample = candidates[self._index % len(candidates)] |
| self._index += 1 |
|
|
| source_path = self._current_sample["source_path"] |
| with open(source_path, "r", encoding="utf-8") as handle: |
| source_code = handle.read() |
|
|
| observation = { |
| "source_code": source_code, |
| "metadata": self._current_sample.get("metadata", {}), |
| "task_id": self._current_sample["task_id"], |
| } |
|
|
| self._state = EnvironmentState( |
| task_id=self._current_sample["task_id"], |
| step_count=0, |
| max_steps=1, |
| score_so_far=0.0, |
| done=False, |
| ) |
|
|
| return observation |
|
|
| def step(self, action: List[Dict[str, Any]]) -> Dict[str, Any]: |
| if self._current_sample is None or self._state is None: |
| raise RuntimeError("Call reset() before step().") |
|
|
| if self._state.done: |
| return { |
| "reward": self._state.score_so_far, |
| "done": True, |
| "details": {"message": "Episode already completed"}, |
| } |
|
|
| expected = self._current_sample.get("labels", []) |
| reward, details = grade_action(action, expected) |
|
|
| self._state.step_count += 1 |
| self._state.score_so_far = reward |
| self._state.done = True |
|
|
| return { |
| "reward": reward, |
| "done": True, |
| "details": details, |
| } |
|
|
| def state(self) -> Dict[str, Any]: |
| if self._state is None: |
| raise RuntimeError("Call reset() before state().") |
| return { |
| "task_id": self._state.task_id, |
| "step_count": self._state.step_count, |
| "max_steps": self._state.max_steps, |
| "score_so_far": self._state.score_so_far, |
| "done": self._state.done, |
| } |
|
|