sravaniamere commited on
Commit
95707b2
·
1 Parent(s): a785d13

fix /tasks endpoint format for validator

Browse files
server.log ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ nohup: ignoring input
2
+ ERROR: Error loading ASGI app. Could not import module "app".
sql_env/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (494 Bytes). View file
 
sql_env/__pycache__/env.cpython-314.pyc ADDED
Binary file (6.89 kB). View file
 
sql_env/__pycache__/grader.cpython-314.pyc ADDED
Binary file (6.66 kB). View file
 
sql_env/__pycache__/models.cpython-314.pyc ADDED
Binary file (3.68 kB). View file
 
sql_env/__pycache__/server.cpython-314.pyc ADDED
Binary file (6.72 kB). View file
 
sql_env/env.py CHANGED
@@ -69,7 +69,7 @@ class SQLCorrectionEnv:
69
  if abs(reward - self._last_reward) < 0.01 and self._step_count > 1:
70
  self._stagnation_count += 1
71
  if self._stagnation_count >= 2:
72
- reward = max(0.0, reward - 0.1) # stagnation penalty
73
  else:
74
  self._stagnation_count = 0
75
 
@@ -81,7 +81,7 @@ class SQLCorrectionEnv:
81
  self._previous_attempt = action.corrected_query
82
 
83
  # episode ends on perfect score or max steps reached
84
- done = reward_model.value == 1.0 or self._step_count >= self._task.max_steps
85
  self._done = done
86
 
87
  obs = self._make_observation()
 
69
  if abs(reward - self._last_reward) < 0.01 and self._step_count > 1:
70
  self._stagnation_count += 1
71
  if self._stagnation_count >= 2:
72
+ reward = max(0.001, reward - 0.1) # stagnation penalty
73
  else:
74
  self._stagnation_count = 0
75
 
 
81
  self._previous_attempt = action.corrected_query
82
 
83
  # episode ends on perfect score or max steps reached
84
+ done = reward_model.value >= 0.99 or self._step_count >= self._task.max_steps
85
  self._done = done
86
 
87
  obs = self._make_observation()
sql_env/grader.py CHANGED
@@ -2,6 +2,11 @@ import re
2
  from sql_env.models import SQLAction, SQLTask, SQLReward
3
 
4
 
 
 
 
 
 
5
  def _normalize(query: str) -> str:
6
  """Uppercase, collapse whitespace, strip trailing semicolons."""
7
  q = query.strip().upper()
@@ -11,7 +16,9 @@ def _normalize(query: str) -> str:
11
 
12
 
13
  def _tokenize(query: str) -> set:
14
- return set(re.findall(r"[A-Z0-9_'*.=><]+", _normalize(query)))
 
 
15
 
16
 
17
  def _sql_keywords_present(query: str) -> set:
@@ -33,26 +40,29 @@ def _sql_keywords_present(query: str) -> set:
33
  def grade(action: SQLAction, task: SQLTask) -> SQLReward:
34
  """
35
  4-level grader with partial progress signals.
36
-
37
  0.99 — exact normalized match
38
- 0.7 — same tokens, minor whitespace/alias differences
39
- 0.4 — key SQL keywords all present and correct table/column names
40
- 0.2 — basic SELECT/FROM structure present
41
  0.01 — completely wrong
 
42
  """
43
  agent = _normalize(action.corrected_query)
44
  correct = _normalize(task.canonical_answer)
45
 
46
  # ── Level 1: Exact match ─────────────────────────────────
47
  if agent == correct:
48
- return SQLReward(value=0.99, reason="Exact match — perfect correction.")
 
 
 
49
 
50
  # ── Level 2: Same token set (right words, minor ordering) ─
51
  agent_tokens = _tokenize(action.corrected_query)
52
  correct_tokens = _tokenize(task.canonical_answer)
53
  if agent_tokens == correct_tokens:
54
  return SQLReward(
55
- value=0.7,
56
  reason="All correct tokens present but structure differs slightly."
57
  )
58
 
@@ -60,34 +70,42 @@ def grade(action: SQLAction, task: SQLTask) -> SQLReward:
60
  correct_kws = _sql_keywords_present(task.canonical_answer)
61
  agent_kws = _sql_keywords_present(action.corrected_query)
62
  kw_overlap = len(correct_kws & agent_kws) / max(len(correct_kws), 1)
63
-
64
  token_overlap = len(agent_tokens & correct_tokens) / max(len(correct_tokens), 1)
65
 
66
  if kw_overlap >= 0.85 and token_overlap >= 0.75:
67
  return SQLReward(
68
- value=0.4,
69
  reason=f"Most keywords correct ({kw_overlap:.0%} keyword match, {token_overlap:.0%} token match)."
70
  )
71
 
 
 
 
 
 
 
 
72
  # ── Level 4: Basic structure present ─────────────────────
73
  if 'SELECT' in agent and 'FROM' in agent:
74
  return SQLReward(
75
- value=0.2,
76
  reason="Basic SELECT/FROM structure present but significant errors remain."
77
  )
78
 
79
  # ── Level 0: No recognizable SQL ─────────────────────────
80
- return SQLReward(value=0.01, reason="Response is not valid SQL.")
81
 
82
 
83
  def generate_feedback(action: SQLAction, task: SQLTask, reward: SQLReward) -> str:
84
  """Human-readable feedback shown in next observation."""
85
- if reward.value == 1.0:
86
  return "Correct! Query matches perfectly."
87
  if reward.value >= 0.7:
88
  return "Very close — check spacing or minor clause differences."
89
  if reward.value >= 0.4:
90
  return "Good progress — most keywords are right, but check for typos in keywords or column names."
 
 
91
  if reward.value >= 0.2:
92
  return "Basic structure is there — look carefully at every SQL keyword for typos."
93
- return "The response doesn't look like valid SQL. Start with SELECT ... FROM ..."
 
2
  from sql_env.models import SQLAction, SQLTask, SQLReward
3
 
4
 
5
+ def _clamp(value: float) -> float:
6
+ """Ensure score is strictly between 0 and 1 (gt=0.0, lt=1.0)."""
7
+ return max(0.001, min(0.999, value))
8
+
9
+
10
  def _normalize(query: str) -> str:
11
  """Uppercase, collapse whitespace, strip trailing semicolons."""
12
  q = query.strip().upper()
 
16
 
17
 
18
  def _tokenize(query: str) -> set:
19
+ normed = _normalize(query)
20
+ normed = re.sub(r"'[^']*'", '__STR__', normed) # normalize string literals
21
+ return set(re.findall(r"[A-Z0-9_'*.=><]+", normed))
22
 
23
 
24
  def _sql_keywords_present(query: str) -> set:
 
40
  def grade(action: SQLAction, task: SQLTask) -> SQLReward:
41
  """
42
  4-level grader with partial progress signals.
 
43
  0.99 — exact normalized match
44
+ 0.7 — same tokens, minor whitespace/alias differences
45
+ 0.4 — key SQL keywords all present and correct table/column names
46
+ 0.2 — basic SELECT/FROM structure present
47
  0.01 — completely wrong
48
+ All scores are clamped to be strictly between 0.0 and 1.0.
49
  """
50
  agent = _normalize(action.corrected_query)
51
  correct = _normalize(task.canonical_answer)
52
 
53
  # ── Level 1: Exact match ─────────────────────────────────
54
  if agent == correct:
55
+ return SQLReward(
56
+ value=_clamp(0.99),
57
+ reason="Exact match — perfect correction."
58
+ )
59
 
60
  # ── Level 2: Same token set (right words, minor ordering) ─
61
  agent_tokens = _tokenize(action.corrected_query)
62
  correct_tokens = _tokenize(task.canonical_answer)
63
  if agent_tokens == correct_tokens:
64
  return SQLReward(
65
+ value=_clamp(0.7),
66
  reason="All correct tokens present but structure differs slightly."
67
  )
68
 
 
70
  correct_kws = _sql_keywords_present(task.canonical_answer)
71
  agent_kws = _sql_keywords_present(action.corrected_query)
72
  kw_overlap = len(correct_kws & agent_kws) / max(len(correct_kws), 1)
 
73
  token_overlap = len(agent_tokens & correct_tokens) / max(len(correct_tokens), 1)
74
 
75
  if kw_overlap >= 0.85 and token_overlap >= 0.75:
76
  return SQLReward(
77
+ value=_clamp(0.4),
78
  reason=f"Most keywords correct ({kw_overlap:.0%} keyword match, {token_overlap:.0%} token match)."
79
  )
80
 
81
+ # ── Level 3.5: Partial keyword and structure match ────────
82
+ if kw_overlap >= 0.7 and token_overlap >= 0.55:
83
+ return SQLReward(
84
+ value=_clamp(0.3),
85
+ reason=f"Partial keyword and structure match ({kw_overlap:.0%} keyword match, {token_overlap:.0%} token match)."
86
+ )
87
+
88
  # ── Level 4: Basic structure present ─────────────────────
89
  if 'SELECT' in agent and 'FROM' in agent:
90
  return SQLReward(
91
+ value=_clamp(0.2),
92
  reason="Basic SELECT/FROM structure present but significant errors remain."
93
  )
94
 
95
  # ── Level 0: No recognizable SQL ─────────────────────────
96
+ return SQLReward(value=_clamp(0.01), reason="Response is not valid SQL.")
97
 
98
 
99
  def generate_feedback(action: SQLAction, task: SQLTask, reward: SQLReward) -> str:
100
  """Human-readable feedback shown in next observation."""
101
+ if reward.value >= 0.99:
102
  return "Correct! Query matches perfectly."
103
  if reward.value >= 0.7:
104
  return "Very close — check spacing or minor clause differences."
105
  if reward.value >= 0.4:
106
  return "Good progress — most keywords are right, but check for typos in keywords or column names."
107
+ if reward.value >= 0.3:
108
+ return "Partial match — right direction but several keywords or columns are off."
109
  if reward.value >= 0.2:
110
  return "Basic structure is there — look carefully at every SQL keyword for typos."
111
+ return "The response doesn't look like valid SQL. Start with SELECT ... FROM ..."
sql_env/models.py CHANGED
@@ -1,6 +1,5 @@
1
  from pydantic import BaseModel, Field
2
- from typing import Optional, Any, Dict
3
-
4
 
5
  class SQLObservation(BaseModel):
6
  task_id: str
@@ -11,16 +10,13 @@ class SQLObservation(BaseModel):
11
  previous_attempt: Optional[str] = None
12
  feedback: Optional[str] = None
13
 
14
-
15
  class SQLAction(BaseModel):
16
  corrected_query: str
17
 
18
-
19
  class SQLReward(BaseModel):
20
- value: float = Field(ge=0.0, le=1.0)
21
  reason: str
22
 
23
-
24
  class SQLTask(BaseModel):
25
  task_id: str
26
  difficulty: str
@@ -29,10 +25,13 @@ class SQLTask(BaseModel):
29
  schema_context: Optional[str] = None
30
  error_hint: Optional[str] = None
31
  max_steps: int = 5
 
32
 
 
 
33
 
34
  class StepResult(BaseModel):
35
  observation: SQLObservation
36
  reward: float
37
  done: bool
38
- info: Dict[str, Any] = Field(default_factory=dict)
 
1
  from pydantic import BaseModel, Field
2
+ from typing import Optional, Any, Dict, Callable
 
3
 
4
  class SQLObservation(BaseModel):
5
  task_id: str
 
10
  previous_attempt: Optional[str] = None
11
  feedback: Optional[str] = None
12
 
 
13
  class SQLAction(BaseModel):
14
  corrected_query: str
15
 
 
16
  class SQLReward(BaseModel):
17
+ value: float = Field(gt=0.0, lt=1.0) # strictly between, not ge/le
18
  reason: str
19
 
 
20
  class SQLTask(BaseModel):
21
  task_id: str
22
  difficulty: str
 
25
  schema_context: Optional[str] = None
26
  error_hint: Optional[str] = None
27
  max_steps: int = 5
28
+ grader: Optional[Callable] = None # ← add this
29
 
30
+ class Config:
31
+ arbitrary_types_allowed = True # ← required for Callable in Pydantic
32
 
33
  class StepResult(BaseModel):
34
  observation: SQLObservation
35
  reward: float
36
  done: bool
37
+ info: Dict[str, Any] = Field(default_factory=dict)
sql_env/server.py CHANGED
@@ -1,7 +1,7 @@
1
  """
2
  FastAPI HTTP wrapper for SQLCorrectionEnv.
3
 
4
- Exposes the OpenEnv-required endpoints: /reset, /step, /state.
5
  """
6
 
7
  from contextlib import asynccontextmanager
@@ -12,6 +12,7 @@ from fastapi.middleware.cors import CORSMiddleware
12
  from pydantic import BaseModel
13
 
14
  from sql_env import SQLAction, SQLCorrectionEnv
 
15
 
16
 
17
  class ResetRequest(BaseModel):
@@ -96,21 +97,30 @@ async def health():
96
  return {"status": "ok", "service": "sql-correction-env"}
97
 
98
 
 
 
 
 
 
 
 
 
 
 
99
  @app.get("/")
100
  async def root():
101
  return {
102
  "name": "SQL Correction RL Environment",
103
  "version": "1.0.0",
104
- "endpoints": ["/reset", "/step", "/state", "/health"],
105
  "tasks": ["easy", "medium", "hard"],
106
  }
107
 
108
 
109
  def main():
110
  import uvicorn
111
-
112
  uvicorn.run(app, host="0.0.0.0", port=7860)
113
 
114
 
115
  if __name__ == "__main__":
116
- main()
 
1
  """
2
  FastAPI HTTP wrapper for SQLCorrectionEnv.
3
 
4
+ Exposes the OpenEnv-required endpoints: /reset, /step, /state + /tasks for validator.
5
  """
6
 
7
  from contextlib import asynccontextmanager
 
12
  from pydantic import BaseModel
13
 
14
  from sql_env import SQLAction, SQLCorrectionEnv
15
+ from sql_env.tasks import ALL_TASKS
16
 
17
 
18
  class ResetRequest(BaseModel):
 
97
  return {"status": "ok", "service": "sql-correction-env"}
98
 
99
 
100
+ @app.get("/tasks")
101
+ async def list_tasks():
102
+ """Return graded tasks by difficulty (RL validator format)."""
103
+ graded_tasks = {
104
+ diff: [task.__dict__ for task in tasks if task.grader is not None]
105
+ for diff, tasks in ALL_TASKS.items()
106
+ }
107
+ return graded_tasks # {"easy": [tasks], "medium": [tasks], "hard": [tasks]}
108
+
109
+
110
  @app.get("/")
111
  async def root():
112
  return {
113
  "name": "SQL Correction RL Environment",
114
  "version": "1.0.0",
115
+ "endpoints": ["/reset", "/step", "/state", "/health", "/tasks"],
116
  "tasks": ["easy", "medium", "hard"],
117
  }
118
 
119
 
120
  def main():
121
  import uvicorn
 
122
  uvicorn.run(app, host="0.0.0.0", port=7860)
123
 
124
 
125
  if __name__ == "__main__":
126
+ main()
sql_env/tasks/__pycache__/__init__.cpython-314.pyc ADDED
Binary file (436 Bytes). View file
 
sql_env/tasks/__pycache__/easy.cpython-314.pyc ADDED
Binary file (1.28 kB). View file
 
sql_env/tasks/__pycache__/hard.cpython-314.pyc ADDED
Binary file (2.78 kB). View file
 
sql_env/tasks/__pycache__/medium.cpython-314.pyc ADDED
Binary file (1.58 kB). View file
 
sql_env/tasks/easy.py CHANGED
@@ -1,4 +1,5 @@
1
  from sql_env.models import SQLTask
 
2
 
3
  EASY_TASKS = [
4
  SQLTask(
@@ -7,6 +8,7 @@ EASY_TASKS = [
7
  broken_query="SELECT * FORM users WHERE id = 1",
8
  canonical_answer="SELECT * FROM users WHERE id = 1",
9
  error_hint="There is a typo in a SQL keyword near the table name.",
 
10
  ),
11
  SQLTask(
12
  task_id="easy_002",
@@ -14,6 +16,7 @@ EASY_TASKS = [
14
  broken_query="SELECT name, age FORM employees WHERE department = 'HR'",
15
  canonical_answer="SELECT name, age FROM employees WHERE department = 'HR'",
16
  error_hint="There is a typo in a SQL keyword near the table name.",
 
17
  ),
18
  SQLTask(
19
  task_id="easy_003",
@@ -21,6 +24,7 @@ EASY_TASKS = [
21
  broken_query="SELECT * FROM products WEHRE price > 100",
22
  canonical_answer="SELECT * FROM products WHERE price > 100",
23
  error_hint="There is a typo in the filtering keyword.",
 
24
  ),
25
  SQLTask(
26
  task_id="easy_004",
@@ -28,6 +32,7 @@ EASY_TASKS = [
28
  broken_query="SELCT id, name FROM customers",
29
  canonical_answer="SELECT id, name FROM customers",
30
  error_hint="There is a typo in the first keyword of the query.",
 
31
  ),
32
  SQLTask(
33
  task_id="easy_005",
@@ -35,5 +40,6 @@ EASY_TASKS = [
35
  broken_query="SELECT COUNT(*) FORM orders WHERE status = 'pending'",
36
  canonical_answer="SELECT COUNT(*) FROM orders WHERE status = 'pending'",
37
  error_hint="There is a typo in a SQL keyword near the table name.",
 
38
  ),
39
- ]
 
1
  from sql_env.models import SQLTask
2
+ from sql_env.grader import grade
3
 
4
  EASY_TASKS = [
5
  SQLTask(
 
8
  broken_query="SELECT * FORM users WHERE id = 1",
9
  canonical_answer="SELECT * FROM users WHERE id = 1",
10
  error_hint="There is a typo in a SQL keyword near the table name.",
11
+ grader=grade,
12
  ),
13
  SQLTask(
14
  task_id="easy_002",
 
16
  broken_query="SELECT name, age FORM employees WHERE department = 'HR'",
17
  canonical_answer="SELECT name, age FROM employees WHERE department = 'HR'",
18
  error_hint="There is a typo in a SQL keyword near the table name.",
19
+ grader=grade,
20
  ),
21
  SQLTask(
22
  task_id="easy_003",
 
24
  broken_query="SELECT * FROM products WEHRE price > 100",
25
  canonical_answer="SELECT * FROM products WHERE price > 100",
26
  error_hint="There is a typo in the filtering keyword.",
27
+ grader=grade,
28
  ),
29
  SQLTask(
30
  task_id="easy_004",
 
32
  broken_query="SELCT id, name FROM customers",
33
  canonical_answer="SELECT id, name FROM customers",
34
  error_hint="There is a typo in the first keyword of the query.",
35
+ grader=grade,
36
  ),
37
  SQLTask(
38
  task_id="easy_005",
 
40
  broken_query="SELECT COUNT(*) FORM orders WHERE status = 'pending'",
41
  canonical_answer="SELECT COUNT(*) FROM orders WHERE status = 'pending'",
42
  error_hint="There is a typo in a SQL keyword near the table name.",
43
+ grader=grade,
44
  ),
45
+ ]
sql_env/tasks/hard.py CHANGED
@@ -1,4 +1,5 @@
1
  from sql_env.models import SQLTask
 
2
 
3
  HARD_TASKS = [
4
  SQLTask(
@@ -13,6 +14,7 @@ HARD_TASKS = [
13
  ),
14
  error_hint=None,
15
  max_steps=4,
 
16
  ),
17
  SQLTask(
18
  task_id="hard_002",
@@ -27,6 +29,7 @@ HARD_TASKS = [
27
  ),
28
  error_hint=None,
29
  max_steps=4,
 
30
  ),
31
  SQLTask(
32
  task_id="hard_003",
@@ -38,5 +41,6 @@ HARD_TASKS = [
38
  ),
39
  error_hint=None,
40
  max_steps=4,
 
41
  ),
42
- ]
 
1
  from sql_env.models import SQLTask
2
+ from sql_env.grader import grade
3
 
4
  HARD_TASKS = [
5
  SQLTask(
 
14
  ),
15
  error_hint=None,
16
  max_steps=4,
17
+ grader=grade,
18
  ),
19
  SQLTask(
20
  task_id="hard_002",
 
29
  ),
30
  error_hint=None,
31
  max_steps=4,
32
+ grader=grade,
33
  ),
34
  SQLTask(
35
  task_id="hard_003",
 
41
  ),
42
  error_hint=None,
43
  max_steps=4,
44
+ grader=grade,
45
  ),
46
+ ]
sql_env/tasks/medium.py CHANGED
@@ -1,4 +1,5 @@
1
  from sql_env.models import SQLTask
 
2
 
3
  MEDIUM_TASKS = [
4
  SQLTask(
@@ -7,6 +8,7 @@ MEDIUM_TASKS = [
7
  broken_query="SELECT name, SUM(salary) FORM employees GRUP BY department",
8
  canonical_answer="SELECT name, SUM(salary) FROM employees GROUP BY department",
9
  error_hint=None,
 
10
  ),
11
  SQLTask(
12
  task_id="medium_002",
@@ -14,6 +16,7 @@ MEDIUM_TASKS = [
14
  broken_query="SELECT * FROM orders WHER total > 500 AND status = 'active' ORDR BY total DESC",
15
  canonical_answer="SELECT * FROM orders WHERE total > 500 AND status = 'active' ORDER BY total DESC",
16
  error_hint=None,
 
17
  ),
18
  SQLTask(
19
  task_id="medium_003",
@@ -21,6 +24,7 @@ MEDIUM_TASKS = [
21
  broken_query="SELECT department, COUNT(*) AS emp_count FORM employees GROUP BY department HAVNG COUNT(*) > 5",
22
  canonical_answer="SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department HAVING COUNT(*) > 5",
23
  error_hint=None,
 
24
  ),
25
  SQLTask(
26
  task_id="medium_004",
@@ -28,6 +32,7 @@ MEDIUM_TASKS = [
28
  broken_query="SELCT product_id, SUM(quantity) FROM sales WEHRE year = 2024 GROUP BY product_id",
29
  canonical_answer="SELECT product_id, SUM(quantity) FROM sales WHERE year = 2024 GROUP BY product_id",
30
  error_hint=None,
 
31
  ),
32
  SQLTask(
33
  task_id="medium_005",
@@ -35,5 +40,6 @@ MEDIUM_TASKS = [
35
  broken_query="SELECT e.name, d.dept_name FORM employees e INNE JOIN departments d ON e.dept_id = d.id WHER e.salary > 50000",
36
  canonical_answer="SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id = d.id WHERE e.salary > 50000",
37
  error_hint=None,
 
38
  ),
39
- ]
 
1
  from sql_env.models import SQLTask
2
+ from sql_env.grader import grade
3
 
4
  MEDIUM_TASKS = [
5
  SQLTask(
 
8
  broken_query="SELECT name, SUM(salary) FORM employees GRUP BY department",
9
  canonical_answer="SELECT name, SUM(salary) FROM employees GROUP BY department",
10
  error_hint=None,
11
+ grader=grade,
12
  ),
13
  SQLTask(
14
  task_id="medium_002",
 
16
  broken_query="SELECT * FROM orders WHER total > 500 AND status = 'active' ORDR BY total DESC",
17
  canonical_answer="SELECT * FROM orders WHERE total > 500 AND status = 'active' ORDER BY total DESC",
18
  error_hint=None,
19
+ grader=grade,
20
  ),
21
  SQLTask(
22
  task_id="medium_003",
 
24
  broken_query="SELECT department, COUNT(*) AS emp_count FORM employees GROUP BY department HAVNG COUNT(*) > 5",
25
  canonical_answer="SELECT department, COUNT(*) AS emp_count FROM employees GROUP BY department HAVING COUNT(*) > 5",
26
  error_hint=None,
27
+ grader=grade,
28
  ),
29
  SQLTask(
30
  task_id="medium_004",
 
32
  broken_query="SELCT product_id, SUM(quantity) FROM sales WEHRE year = 2024 GROUP BY product_id",
33
  canonical_answer="SELECT product_id, SUM(quantity) FROM sales WHERE year = 2024 GROUP BY product_id",
34
  error_hint=None,
35
+ grader=grade,
36
  ),
37
  SQLTask(
38
  task_id="medium_005",
 
40
  broken_query="SELECT e.name, d.dept_name FORM employees e INNE JOIN departments d ON e.dept_id = d.id WHER e.salary > 50000",
41
  canonical_answer="SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id = d.id WHERE e.salary > 50000",
42
  error_hint=None,
43
+ grader=grade,
44
  ),
45
+ ]