sravaniamere's picture
fix folder structure
3425ada
Raw
History Blame
4.86 kB
import random
from typing import Optional
from sql_env.models import (
SQLObservation, SQLAction, SQLTask, StepResult
)
from sql_env.grader import grade, generate_feedback
from sql_env.tasks import ALL_TASKS
class SQLCorrectionEnv:
"""
OpenEnv-compliant SQL Query Correction Environment.
The agent receives a broken SQL query and must return the corrected version.
Reward is shaped across the full trajectory β€” partial credit is given for
incremental improvements, penalizing stagnation and infinite loops.
"""
def __init__(self, difficulty: str = "easy", task_index: Optional[int] = None):
if difficulty not in ALL_TASKS:
raise ValueError(f"difficulty must be one of {list(ALL_TASKS.keys())}")
self.difficulty = difficulty
self.task_index = task_index
self._task: Optional[SQLTask] = None
self._step_count: int = 0
self._done: bool = False
self._previous_attempt: Optional[str] = None
self._last_feedback: Optional[str] = None
self._last_reward: float = 0.0
self._stagnation_count: int = 0
# ── OpenEnv Interface ─────────────────────────────────────
async def reset(self) -> SQLObservation:
"""Reset the environment and return the initial observation."""
tasks = ALL_TASKS[self.difficulty]
if self.task_index is not None:
self._task = tasks[self.task_index % len(tasks)]
else:
self._task = random.choice(tasks)
self._step_count = 0
self._done = False
self._previous_attempt = None
self._last_feedback = None
self._last_reward = 0.0
self._stagnation_count = 0
return self._make_observation()
async def step(self, action: SQLAction) -> StepResult:
"""
Take one step: grade the agent's corrected query and return
(observation, reward, done, info).
"""
if self._done:
raise RuntimeError("Episode is done. Call reset() to start a new episode.")
if self._task is None:
raise RuntimeError("Environment not initialized. Call reset() first.")
self._step_count += 1
# grade the action
reward_model = grade(action, self._task)
reward = reward_model.value
# detect stagnation (same reward twice in a row) β€” penalize
if abs(reward - self._last_reward) < 0.01 and self._step_count > 1:
self._stagnation_count += 1
if self._stagnation_count >= 2:
reward = max(0.0, reward - 0.1) # stagnation penalty
else:
self._stagnation_count = 0
self._last_reward = reward
# generate feedback for the next observation
feedback = generate_feedback(action, self._task, reward_model)
self._last_feedback = feedback
self._previous_attempt = action.corrected_query
# episode ends on perfect score or max steps reached
done = reward_model.value == 1.0 or self._step_count >= self._task.max_steps
self._done = done
obs = self._make_observation()
return StepResult(
observation=obs,
reward=round(reward, 4),
done=done,
info={
"grader_reason": reward_model.reason,
"step": self._step_count,
"max_steps": self._task.max_steps,
"task_id": self._task.task_id,
}
)
async def state(self) -> dict:
"""Return the current internal state of the environment."""
if self._task is None:
return {"status": "not_initialized"}
return {
"task_id": self._task.task_id,
"difficulty": self.difficulty,
"step_count": self._step_count,
"done": self._done,
"last_reward": self._last_reward,
"max_steps": self._task.max_steps,
"previous_attempt": self._previous_attempt,
}
async def close(self):
"""Clean up resources."""
self._task = None
self._done = True
# ── Internal ──────────────────────────────────────────────
def _make_observation(self) -> SQLObservation:
assert self._task is not None
return SQLObservation(
task_id=self._task.task_id,
broken_query=self._task.broken_query,
schema_context=self._task.schema_context,
error_hint=self._task.error_hint if self.difficulty == "easy" else None,
step_number=self._step_count,
previous_attempt=self._previous_attempt,
feedback=self._last_feedback,
)