P-Karthik-Mohan commited on
Commit
1454e23
·
1 Parent(s): 152f7d7

Add expert tasks 6-8

Browse files
Files changed (2) hide show
  1. inference.py +4 -5
  2. main.py +129 -63
inference.py CHANGED
@@ -7,20 +7,19 @@ import json
7
  import time
8
  import requests
9
  from openai import OpenAI
10
- from dotenv import load_dotenv
11
- load_dotenv()
12
 
13
  ENV_BASE_URL = "https://p-karthik-mohan-sql-analyst-env.hf.space"
14
  MAX_ATTEMPTS = 5
15
- TASK_IDS = [1, 2, 3, 4, 5]
16
 
17
  API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.groq.com/openai/v1")
18
  MODEL_NAME = os.environ.get("MODEL_NAME", "llama-3.1-8b-instant")
19
- HF_TOKEN = os.environ.get("HF_TOKEN", "")
20
 
21
  client = OpenAI(
22
  base_url=API_BASE_URL,
23
- api_key=HF_TOKEN if HF_TOKEN else "no-key-needed",
 
24
  )
25
 
26
  def env_reset(task_id: int) -> dict:
 
7
  import time
8
  import requests
9
  from openai import OpenAI
 
 
10
 
11
  ENV_BASE_URL = "https://p-karthik-mohan-sql-analyst-env.hf.space"
12
  MAX_ATTEMPTS = 5
13
+ TASK_IDS = [1, 2, 3, 4, 5, 6, 7, 8]
14
 
15
  API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.groq.com/openai/v1")
16
  MODEL_NAME = os.environ.get("MODEL_NAME", "llama-3.1-8b-instant")
17
+ HF_TOKEN = os.environ.get("HF_TOKEN","")
18
 
19
  client = OpenAI(
20
  base_url=API_BASE_URL,
21
+
22
+ api_key="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",
23
  )
24
 
25
  def env_reset(task_id: int) -> dict:
main.py CHANGED
@@ -1,15 +1,14 @@
1
  import sqlite3
2
  import os
3
- import json
4
  import re
5
  from datetime import datetime
6
- from typing import Any, Optional
7
 
8
  from fastapi import FastAPI, HTTPException
9
  from pydantic import BaseModel
10
 
11
  DB_PATH = os.path.join("data", "ecommerce.db")
12
- app = FastAPI(title="SQL Analyst OpenEnv", version="1.0.0")
13
 
14
  class StepRequest(BaseModel):
15
  action: str
@@ -130,6 +129,101 @@ TASKS = {
130
  WHERE p.category = 'Clothing'
131
  """,
132
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
  }
134
 
135
  session = {
@@ -183,7 +277,7 @@ def compute_expected():
183
  session["expected_rows"] = rows
184
  session["expected_columns"] = columns
185
 
186
- def compute_reward(agent_rows: list[dict], agent_cols: list[str]) -> tuple[float, dict]:
187
  expected_rows = session["expected_rows"]
188
  expected_cols = session["expected_columns"]
189
  details = {}
@@ -192,9 +286,9 @@ def compute_reward(agent_rows: list[dict], agent_cols: list[str]) -> tuple[float
192
  expected_cols_lower = [c.lower() for c in expected_cols]
193
  col_matches = sum(1 for c in expected_cols_lower if c in agent_cols_lower)
194
  col_score = (col_matches / len(expected_cols_lower)) * 0.30 if expected_cols_lower else 0.0
195
- details["column_score"] = round(col_score, 3)
196
  details["expected_columns"] = expected_cols
197
- details["agent_columns"] = agent_cols
198
 
199
  expected_count = len(expected_rows)
200
  agent_count = len(agent_rows)
@@ -203,7 +297,7 @@ def compute_reward(agent_rows: list[dict], agent_cols: list[str]) -> tuple[float
203
  else:
204
  row_ratio = min(agent_count, expected_count) / max(agent_count, expected_count)
205
  row_score = row_ratio * 0.30
206
- details["row_score"] = round(row_score, 3)
207
  details["expected_row_count"] = expected_count
208
  details["agent_row_count"] = agent_count
209
 
@@ -220,7 +314,6 @@ def compute_reward(agent_rows: list[dict], agent_cols: list[str]) -> tuple[float
220
 
221
  matched_cells = 0
222
  total_cells = len(expected_rows) * len(expected_cols_lower)
223
-
224
  for exp_row, agt_row in zip(expected_rows, agent_rows):
225
  for col in expected_cols_lower:
226
  exp_val = normalize(exp_row.get(col) or exp_row.get(col.upper()))
@@ -228,37 +321,29 @@ def compute_reward(agent_rows: list[dict], agent_cols: list[str]) -> tuple[float
228
  if not agt_val:
229
  exp_idx = expected_cols_lower.index(col)
230
  if exp_idx < len(agent_cols):
231
- pos_col = agent_cols[exp_idx]
232
- agt_val = normalize(agt_row.get(pos_col))
233
  if exp_val == agt_val:
234
  matched_cells += 1
235
-
236
  value_score = (matched_cells / total_cells) * 0.40 if total_cells > 0 else 0.0
237
- details["value_score"] = round(value_score, 3)
238
-
239
- total = round(col_score + row_score + value_score, 3)
240
- details["total_reward"] = total
241
- return total, details
242
 
243
  @app.post("/reset", response_model=ResetResponse)
244
  def reset(req: ResetRequest):
245
  if req.task_id not in TASKS:
246
- raise HTTPException(status_code=400, detail="task_id must be 1, 2, 3, 4, or 5")
247
-
248
  session["task_id"] = req.task_id
249
  session["task"] = TASKS[req.task_id]
250
  session["attempts"] = 0
251
  session["best_reward"] = 0.0
252
  session["history"] = []
253
-
254
  compute_expected()
255
-
256
- schema = get_schema_info()
257
  observation = {
258
  "task_id": req.task_id,
259
  "difficulty": session["task"]["difficulty"],
260
  "task_description": session["task"]["description"],
261
- "schema": schema,
262
  "hint": session["task"]["hint"],
263
  }
264
  return ResetResponse(
@@ -269,66 +354,48 @@ def reset(req: ResetRequest):
269
  @app.post("/step", response_model=StepResponse)
270
  def step(req: StepRequest):
271
  if session["task_id"] is None:
272
- raise HTTPException(status_code=400, detail="Call /reset first to load a task.")
273
-
274
  session["attempts"] += 1
275
  sql = req.action.strip()
276
 
277
  if not re.match(r"^\s*(SELECT|WITH)\b", sql, re.IGNORECASE):
278
  return StepResponse(
279
- observation={"error": "Only SELECT or WITH (CTE) statements are allowed."},
280
- reward=0.0,
281
- done=False,
282
- info={"attempt": session["attempts"], "message": "Rejected: not a SELECT/WITH query."}
283
  )
284
 
285
  try:
286
  agent_rows, agent_cols = run_query(sql)
287
  except Exception as e:
288
- entry = {
289
- "attempt": session["attempts"],
290
- "sql": sql,
291
- "reward": 0.0,
292
- "error": str(e),
293
- }
294
- session["history"].append(entry)
295
  return StepResponse(
296
- observation={"error": str(e), "sql_submitted": sql},
297
- reward=0.0,
298
- done=False,
299
- info={"attempt": session["attempts"], "message": "SQL execution error."}
300
  )
301
 
302
  reward, details = compute_reward(agent_rows, agent_cols)
303
  session["best_reward"] = max(session["best_reward"], reward)
304
  done = reward >= 1.0
305
-
306
- entry = {
307
- "attempt": session["attempts"],
308
- "sql": sql,
309
- "reward": reward,
310
- "details": details,
311
  "timestamp": datetime.now().isoformat(),
312
- }
313
- session["history"].append(entry)
314
-
315
- observation = {
316
- "task_id": session["task_id"],
317
- "task_description": session["task"]["description"],
318
- "sql_submitted": sql,
319
- "result_preview": agent_rows[:5],
320
- "result_row_count": len(agent_rows),
321
- "reward_breakdown": details,
322
- }
323
 
324
  return StepResponse(
325
- observation=observation,
326
- reward=reward,
327
- done=done,
 
 
 
 
 
328
  info={
329
  "attempt": session["attempts"],
330
  "best_reward": session["best_reward"],
331
- "message": "Perfect score! Task complete." if done else "Keep refining your query.",
332
  }
333
  )
334
 
@@ -336,7 +403,6 @@ def step(req: StepRequest):
336
  def state():
337
  if session["task_id"] is None:
338
  raise HTTPException(status_code=400, detail="No active task. Call /reset first.")
339
-
340
  return StateResponse(
341
  task_id=session["task_id"],
342
  task_description=session["task"]["description"],
@@ -350,9 +416,9 @@ def state():
350
  def root():
351
  return {
352
  "name": "SQL Analyst OpenEnv",
353
- "version": "1.0.0",
354
  "tasks": {k: {"difficulty": v["difficulty"], "description": v["description"]} for k, v in TASKS.items()},
355
- "endpoints": ["/reset", "/step", "/state"],
356
  }
357
 
358
  @app.get("/health")
 
1
  import sqlite3
2
  import os
 
3
  import re
4
  from datetime import datetime
5
+ from typing import Optional
6
 
7
  from fastapi import FastAPI, HTTPException
8
  from pydantic import BaseModel
9
 
10
  DB_PATH = os.path.join("data", "ecommerce.db")
11
+ app = FastAPI(title="SQL Analyst OpenEnv", version="2.0.0")
12
 
13
  class StepRequest(BaseModel):
14
  action: str
 
129
  WHERE p.category = 'Clothing'
130
  """,
131
  },
132
+ 6: {
133
+ "description": (
134
+ "Calculate the month-over-month revenue growth percentage for completed orders in 2024. "
135
+ "For each month show total revenue and percentage change vs previous month. "
136
+ "Return columns: month, total_revenue, prev_revenue, growth_pct. "
137
+ "Order by month ascending. Round growth_pct to 2 decimal places. "
138
+ "For the first month, prev_revenue and growth_pct should be NULL."
139
+ ),
140
+ "difficulty": "expert",
141
+ "hint": "Use LAG() window function to get previous month revenue, then calculate (current - prev) / prev * 100",
142
+ "answer_query": """
143
+ WITH monthly AS (
144
+ SELECT STRFTIME('%m', order_date) AS month,
145
+ ROUND(SUM(total_amount), 2) AS total_revenue
146
+ FROM orders
147
+ WHERE status = 'completed'
148
+ AND order_date LIKE '2024%'
149
+ GROUP BY month
150
+ )
151
+ SELECT month,
152
+ total_revenue,
153
+ LAG(total_revenue) OVER (ORDER BY month) AS prev_revenue,
154
+ ROUND(
155
+ (total_revenue - LAG(total_revenue) OVER (ORDER BY month))
156
+ / LAG(total_revenue) OVER (ORDER BY month) * 100,
157
+ 2) AS growth_pct
158
+ FROM monthly
159
+ ORDER BY month ASC
160
+ """,
161
+ },
162
+ 7: {
163
+ "description": (
164
+ "For each city, find the single best-selling product by total quantity sold "
165
+ "from completed orders. "
166
+ "Return columns: city, product_name, total_quantity. "
167
+ "Order by city ascending. "
168
+ "If two products tie, return the one with the lower product_id."
169
+ ),
170
+ "difficulty": "expert",
171
+ "hint": "Use RANK() OVER (PARTITION BY city ORDER BY total_quantity DESC, product_id ASC) in a CTE, then filter WHERE rank = 1",
172
+ "answer_query": """
173
+ WITH city_product AS (
174
+ SELECT c.city,
175
+ p.product_name,
176
+ p.product_id,
177
+ SUM(o.quantity) AS total_quantity,
178
+ RANK() OVER (
179
+ PARTITION BY c.city
180
+ ORDER BY SUM(o.quantity) DESC, p.product_id ASC
181
+ ) AS rnk
182
+ FROM orders o
183
+ JOIN customers c ON o.customer_id = c.customer_id
184
+ JOIN products p ON o.product_id = p.product_id
185
+ WHERE o.status = 'completed'
186
+ GROUP BY c.city, p.product_id
187
+ )
188
+ SELECT city, product_name, total_quantity
189
+ FROM city_product
190
+ WHERE rnk = 1
191
+ ORDER BY city ASC
192
+ """,
193
+ },
194
+ 8: {
195
+ "description": (
196
+ "Find customers whose total spending in the second half of 2024 (July-December) "
197
+ "was strictly greater than their total spending in the first half of 2024 (January-June). "
198
+ "Only consider completed orders. "
199
+ "Return columns: customer_id, first_name, last_name, h1_revenue, h2_revenue. "
200
+ "Order by h2_revenue descending."
201
+ ),
202
+ "difficulty": "expert",
203
+ "hint": "Use conditional SUM with CASE WHEN to split spending by half-year, then filter WHERE h2 > h1",
204
+ "answer_query": """
205
+ WITH half_year AS (
206
+ SELECT c.customer_id,
207
+ c.first_name,
208
+ c.last_name,
209
+ ROUND(SUM(CASE
210
+ WHEN STRFTIME('%m', o.order_date) BETWEEN '01' AND '06'
211
+ THEN o.total_amount ELSE 0 END), 2) AS h1_revenue,
212
+ ROUND(SUM(CASE
213
+ WHEN STRFTIME('%m', o.order_date) BETWEEN '07' AND '12'
214
+ THEN o.total_amount ELSE 0 END), 2) AS h2_revenue
215
+ FROM orders o
216
+ JOIN customers c ON o.customer_id = c.customer_id
217
+ WHERE o.status = 'completed'
218
+ AND o.order_date LIKE '2024%'
219
+ GROUP BY c.customer_id
220
+ )
221
+ SELECT customer_id, first_name, last_name, h1_revenue, h2_revenue
222
+ FROM half_year
223
+ WHERE h2_revenue > h1_revenue
224
+ ORDER BY h2_revenue DESC
225
+ """,
226
+ },
227
  }
228
 
229
  session = {
 
277
  session["expected_rows"] = rows
278
  session["expected_columns"] = columns
279
 
280
+ def compute_reward(agent_rows, agent_cols):
281
  expected_rows = session["expected_rows"]
282
  expected_cols = session["expected_columns"]
283
  details = {}
 
286
  expected_cols_lower = [c.lower() for c in expected_cols]
287
  col_matches = sum(1 for c in expected_cols_lower if c in agent_cols_lower)
288
  col_score = (col_matches / len(expected_cols_lower)) * 0.30 if expected_cols_lower else 0.0
289
+ details["column_score"] = round(col_score, 3)
290
  details["expected_columns"] = expected_cols
291
+ details["agent_columns"] = agent_cols
292
 
293
  expected_count = len(expected_rows)
294
  agent_count = len(agent_rows)
 
297
  else:
298
  row_ratio = min(agent_count, expected_count) / max(agent_count, expected_count)
299
  row_score = row_ratio * 0.30
300
+ details["row_score"] = round(row_score, 3)
301
  details["expected_row_count"] = expected_count
302
  details["agent_row_count"] = agent_count
303
 
 
314
 
315
  matched_cells = 0
316
  total_cells = len(expected_rows) * len(expected_cols_lower)
 
317
  for exp_row, agt_row in zip(expected_rows, agent_rows):
318
  for col in expected_cols_lower:
319
  exp_val = normalize(exp_row.get(col) or exp_row.get(col.upper()))
 
321
  if not agt_val:
322
  exp_idx = expected_cols_lower.index(col)
323
  if exp_idx < len(agent_cols):
324
+ agt_val = normalize(agt_row.get(agent_cols[exp_idx]))
 
325
  if exp_val == agt_val:
326
  matched_cells += 1
 
327
  value_score = (matched_cells / total_cells) * 0.40 if total_cells > 0 else 0.0
328
+ details["value_score"] = round(value_score, 3)
329
+ details["total_reward"] = round(col_score + row_score + value_score, 3)
330
+ return details["total_reward"], details
 
 
331
 
332
  @app.post("/reset", response_model=ResetResponse)
333
  def reset(req: ResetRequest):
334
  if req.task_id not in TASKS:
335
+ raise HTTPException(status_code=400, detail="task_id must be 1–8")
 
336
  session["task_id"] = req.task_id
337
  session["task"] = TASKS[req.task_id]
338
  session["attempts"] = 0
339
  session["best_reward"] = 0.0
340
  session["history"] = []
 
341
  compute_expected()
 
 
342
  observation = {
343
  "task_id": req.task_id,
344
  "difficulty": session["task"]["difficulty"],
345
  "task_description": session["task"]["description"],
346
+ "schema": get_schema_info(),
347
  "hint": session["task"]["hint"],
348
  }
349
  return ResetResponse(
 
354
  @app.post("/step", response_model=StepResponse)
355
  def step(req: StepRequest):
356
  if session["task_id"] is None:
357
+ raise HTTPException(status_code=400, detail="Call /reset first.")
 
358
  session["attempts"] += 1
359
  sql = req.action.strip()
360
 
361
  if not re.match(r"^\s*(SELECT|WITH)\b", sql, re.IGNORECASE):
362
  return StepResponse(
363
+ observation={"error": "Only SELECT or WITH allowed."},
364
+ reward=0.0, done=False,
365
+ info={"attempt": session["attempts"], "message": "Rejected."}
 
366
  )
367
 
368
  try:
369
  agent_rows, agent_cols = run_query(sql)
370
  except Exception as e:
371
+ session["history"].append({"attempt": session["attempts"], "sql": sql, "reward": 0.0, "error": str(e)})
 
 
 
 
 
 
372
  return StepResponse(
373
+ observation={"error": str(e)}, reward=0.0, done=False,
374
+ info={"attempt": session["attempts"], "message": "SQL error."}
 
 
375
  )
376
 
377
  reward, details = compute_reward(agent_rows, agent_cols)
378
  session["best_reward"] = max(session["best_reward"], reward)
379
  done = reward >= 1.0
380
+ session["history"].append({
381
+ "attempt": session["attempts"], "sql": sql,
382
+ "reward": reward, "details": details,
 
 
 
383
  "timestamp": datetime.now().isoformat(),
384
+ })
 
 
 
 
 
 
 
 
 
 
385
 
386
  return StepResponse(
387
+ observation={
388
+ "task_id": session["task_id"],
389
+ "sql_submitted": sql,
390
+ "result_preview": agent_rows[:5],
391
+ "result_row_count": len(agent_rows),
392
+ "reward_breakdown": details,
393
+ },
394
+ reward=reward, done=done,
395
  info={
396
  "attempt": session["attempts"],
397
  "best_reward": session["best_reward"],
398
+ "message": "Perfect score!" if done else "Keep refining.",
399
  }
400
  )
401
 
 
403
  def state():
404
  if session["task_id"] is None:
405
  raise HTTPException(status_code=400, detail="No active task. Call /reset first.")
 
406
  return StateResponse(
407
  task_id=session["task_id"],
408
  task_description=session["task"]["description"],
 
416
  def root():
417
  return {
418
  "name": "SQL Analyst OpenEnv",
419
+ "version": "2.0.0",
420
  "tasks": {k: {"difficulty": v["difficulty"], "description": v["description"]} for k, v in TASKS.items()},
421
+ "endpoints": ["/reset", "/step", "/state", "/health"],
422
  }
423
 
424
  @app.get("/health")