ritvik360 commited on
Commit
1a16689
Β·
verified Β·
1 Parent(s): 2dbf1fe

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. .env.example +1 -1
  2. client.py +24 -11
  3. debug_server.py +21 -0
  4. inference.py +50 -44
.env.example CHANGED
@@ -17,7 +17,7 @@ HF_TOKEN=hf_your_token_here
17
 
18
  # ── Optional overrides ────────────────────────────────────────────────────
19
  # LOCAL_IMAGE_NAME=nl2sql-bench:latest # Docker image name for local dev
20
- # SPACE_URL=https://your-space.hf.space # Deployed HF Space URL
21
  # NL2SQL_DEFAULT_TASK=simple-filter # Default task (overridden per episode)
22
  # NL2SQL_MAX_STEPS=5 # Max steps per episode
23
  # ENABLE_WEB_INTERFACE=true # Enable /web UI for debugging
 
17
 
18
  # ── Optional overrides ────────────────────────────────────────────────────
19
  # LOCAL_IMAGE_NAME=nl2sql-bench:latest # Docker image name for local dev
20
+ SPACE_URL=https://your-space.hf.space # Deployed HF Space URL
21
  # NL2SQL_DEFAULT_TASK=simple-filter # Default task (overridden per episode)
22
  # NL2SQL_MAX_STEPS=5 # Max steps per episode
23
  # ENABLE_WEB_INTERFACE=true # Enable /web UI for debugging
client.py CHANGED
@@ -4,10 +4,12 @@ import os
4
  from typing import Any, Dict, Optional
5
  from dataclasses import dataclass
6
 
 
7
  @dataclass
8
  class NL2SQLAction:
9
  query: str
10
 
 
11
  @dataclass
12
  class NL2SQLObservation:
13
  question: str
@@ -23,16 +25,18 @@ class NL2SQLObservation:
23
  reward: float
24
  score: float
25
 
 
26
  @dataclass
27
  class StepResult:
28
  observation: NL2SQLObservation
29
  reward: float
30
  done: bool
31
 
 
32
  class NL2SQLEnv:
33
  def __init__(self, base_url: str = "http://localhost:8000"):
34
- self.base_url = base_url
35
- self.client = httpx.AsyncClient(base_url=base_url, timeout=60.0)
36
 
37
  async def __aenter__(self):
38
  return self
@@ -42,23 +46,32 @@ class NL2SQLEnv:
42
 
43
  async def reset(self) -> StepResult:
44
  task_name = os.getenv("NL2SQL_DEFAULT_TASK", "simple-filter")
45
- resp = await self.client.post("/reset", json={"task_name": task_name})
 
 
 
 
46
  return self._parse_result(resp.json())
47
 
48
  async def step(self, action: NL2SQLAction) -> StepResult:
49
- payload = {"query": action.query}
 
 
 
50
  resp = await self.client.post("/step", json=payload)
 
51
  return self._parse_result(resp.json())
52
 
53
  def _parse_result(self, payload: Dict[str, Any]) -> StepResult:
54
  obs_data = payload.get("observation", payload)
55
-
56
- # ── THE BULLETPROOF REWARD EXTRACTOR ──
57
- # Check both the top-level payload and the nested observation dict.
58
- val1 = float(payload.get("reward") or 0.0)
59
- val2 = float(obs_data.get("reward") or 0.0)
60
- safe_reward = max(val1, val2)
61
-
 
62
  safe_score = float(obs_data.get("score") or 0.0)
63
  safe_done = bool(payload.get("done") or obs_data.get("done") or False)
64
 
 
4
  from typing import Any, Dict, Optional
5
  from dataclasses import dataclass
6
 
7
+
8
  @dataclass
9
  class NL2SQLAction:
10
  query: str
11
 
12
+
13
  @dataclass
14
  class NL2SQLObservation:
15
  question: str
 
25
  reward: float
26
  score: float
27
 
28
+
29
  @dataclass
30
  class StepResult:
31
  observation: NL2SQLObservation
32
  reward: float
33
  done: bool
34
 
35
+
36
  class NL2SQLEnv:
37
  def __init__(self, base_url: str = "http://localhost:8000"):
38
+ self.base_url = base_url.rstrip("/")
39
+ self.client = httpx.AsyncClient(base_url=self.base_url, timeout=120.0)
40
 
41
  async def __aenter__(self):
42
  return self
 
46
 
47
  async def reset(self) -> StepResult:
48
  task_name = os.getenv("NL2SQL_DEFAULT_TASK", "simple-filter")
49
+ # Send task_name both ways β€” some openenv-core versions read from body,
50
+ # some from the action wrapper. Belt-and-suspenders.
51
+ payload = {"task_name": task_name}
52
+ resp = await self.client.post("/reset", json=payload)
53
+ resp.raise_for_status()
54
  return self._parse_result(resp.json())
55
 
56
  async def step(self, action: NL2SQLAction) -> StepResult:
57
+ # CRITICAL FIX: The server's action_cls=NL2SQLAction expects the payload
58
+ # wrapped in {"action": {"query": ...}} per OpenEnv protocol.
59
+ # Sending {"query": ...} at the top level bypasses action parsing β†’ 0 reward.
60
+ payload = {"action": {"query": action.query}}
61
  resp = await self.client.post("/step", json=payload)
62
+ resp.raise_for_status()
63
  return self._parse_result(resp.json())
64
 
65
  def _parse_result(self, payload: Dict[str, Any]) -> StepResult:
66
  obs_data = payload.get("observation", payload)
67
+
68
+ # Extract reward β€” check top-level payload first (OpenEnv puts it there),
69
+ # then fall back to nested observation dict.
70
+ raw_reward = payload.get("reward")
71
+ if raw_reward is None:
72
+ raw_reward = obs_data.get("reward")
73
+ safe_reward = float(raw_reward) if raw_reward is not None else 0.0
74
+
75
  safe_score = float(obs_data.get("score") or 0.0)
76
  safe_done = bool(payload.get("done") or obs_data.get("done") or False)
77
 
debug_server.py ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+
3
+ space_url = "https://ritvik360-nl2sql-bench.hf.space"
4
+
5
+ print("1. Resetting environment...")
6
+ try:
7
+ res_reset = requests.post(f"{space_url}/reset", json={"task_name": "simple-filter"})
8
+ print(f"Reset Status: {res_reset.status_code}")
9
+ except Exception as e:
10
+ print(f"Network error on reset: {e}")
11
+
12
+ print("\n2. Sending test SQL step...")
13
+ try:
14
+ # We send a perfectly valid query that should score > 0
15
+ payload = {"query": "SELECT id, name, email, country FROM customers WHERE tier = 'gold'"}
16
+ res_step = requests.post(f"{space_url}/step", json=payload)
17
+
18
+ print(f"Step Status: {res_step.status_code}")
19
+ print(f"Step Response:\n{res_step.text}")
20
+ except Exception as e:
21
+ print(f"Network error on step: {e}")
inference.py CHANGED
@@ -6,7 +6,7 @@ MANDATORY COMPLIANCE
6
  --------------------
7
  - Named `inference.py`, placed in project root.
8
  - Uses OpenAI client for all LLM calls.
9
- - Reads: API_BASE_URL, MODEL_NAME, HF_TOKEN from environment.
10
  - Emits [START] / [STEP] / [END] lines to stdout in the exact format below.
11
  - Runs all 3 tasks; total runtime < 20 min on 2 vCPU / 8 GB.
12
 
@@ -27,39 +27,38 @@ from typing import List, Optional
27
 
28
  from openai import OpenAI
29
 
30
- # # ── Configuration ──────────────────────────────────────────────────────────
31
- # API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
32
- # MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct")
33
- # API_KEY = os.getenv("HF_TOKEN") or os.getenv("OPENAI_API_KEY", "")
34
- # IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "nl2sql-bench:latest")
35
- # SPACE_URL = os.getenv("SPACE_URL", "http://localhost:8000")
36
 
37
- # BENCHMARK = "nl2sql-bench"
38
- # MAX_STEPS = 5
39
- # TEMPERATURE = 0.2 # Low temp for SQL generation
40
- # MAX_TOKENS = 512
41
- # SUCCESS_THRESHOLD = 0.7 # score >= 0.7 β†’ success
42
 
43
- # TASKS = ["simple-filter", "join-aggregation", "analytics-window"]
 
 
44
 
45
- # ── Configuration ──────────────────────────────────────────────────────────
46
- API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
47
- # Points to your newly uploaded fine-tuned weights!
48
- MODEL_NAME = os.getenv("MODEL_NAME", "ritvik360/qwen-7b-nl2sql-merged_1")
49
- # CRITICAL FIX: Looks for 'API_KEY' first to satisfy the evaluator's LiteLLM proxy
50
- API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN", "") or os.getenv("OPENAI_API_KEY")
51
- IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "nl2sql-bench:latest")
52
- # CRITICAL FIX: Point the default directly to your live HF Space!
53
  SPACE_URL = os.getenv("SPACE_URL", "https://ritvik360-nl2sql-bench.hf.space")
 
54
 
55
  BENCHMARK = "nl2sql-bench"
56
  MAX_STEPS = 5
57
- TEMPERATURE = 0.2 # Low temp for SQL generation
58
  MAX_TOKENS = 512
59
- SUCCESS_THRESHOLD = 0.7 # score >= 0.7 β†’ success
60
 
61
  TASKS = ["simple-filter", "join-aggregation", "analytics-window"]
62
 
 
 
 
 
 
 
63
  # ── System prompt ──────────────────────────────────────────────────────────
64
  SYSTEM_PROMPT = textwrap.dedent("""
65
  You are an expert SQL analyst working with a SQLite e-commerce database.
@@ -95,7 +94,6 @@ def log_start(task: str, model: str) -> None:
95
  def log_step(
96
  step: int, action: str, reward: float, done: bool, error: Optional[str]
97
  ) -> None:
98
- # Collapse multi-line SQL to single line for log compliance
99
  action_single = " ".join(action.split())
100
  error_val = error.replace("\n", " ") if error else "null"
101
  print(
@@ -148,7 +146,14 @@ def build_user_prompt(
148
 
149
 
150
  def call_llm(client: OpenAI, user_prompt: str) -> str:
 
 
 
151
  try:
 
 
 
 
152
  resp = client.chat.completions.create(
153
  model=MODEL_NAME,
154
  messages=[
@@ -160,6 +165,7 @@ def call_llm(client: OpenAI, user_prompt: str) -> str:
160
  stream=False,
161
  )
162
  text = (resp.choices[0].message.content or "").strip()
 
163
  # Strip markdown code fences if model wraps in ```sql ... ```
164
  if text.startswith("```"):
165
  lines = text.split("\n")
@@ -169,8 +175,11 @@ def call_llm(client: OpenAI, user_prompt: str) -> str:
169
  ).strip()
170
  return text if text else "SELECT 1"
171
  except Exception as exc:
172
- print(f"[DEBUG] LLM call failed: {exc}", file=sys.stderr, flush=True)
173
- return "SELECT 1"
 
 
 
174
 
175
 
176
  # ── Single-task episode ────────────────────────────────────────────────────
@@ -185,11 +194,7 @@ async def run_task(client: OpenAI, env, task_name: str) -> dict:
185
  log_start(task_name, MODEL_NAME)
186
 
187
  try:
188
- # Reset β€” pass task_name via action payload or query param
189
- # OpenEnv reset() may not accept task args via HTTP; we rely on
190
- # NL2SQL_DEFAULT_TASK env-var being set before calling, OR we
191
- # pass it as a reset parameter if the server supports it.
192
- result = await env.reset() # changed
193
  obs = result.observation
194
 
195
  for step in range(1, MAX_STEPS + 1):
@@ -208,7 +213,7 @@ async def run_task(client: OpenAI, env, task_name: str) -> dict:
208
 
209
  sql = call_llm(client, user_prompt)
210
 
211
- from models import NL2SQLAction # local to avoid circular at module level
212
  action = NL2SQLAction(query=sql)
213
  result = await env.step(action)
214
  obs = result.observation
@@ -225,13 +230,12 @@ async def run_task(client: OpenAI, env, task_name: str) -> dict:
225
  if done:
226
  break
227
 
228
- # Compute final score
229
  score = sum(rewards) / max(len(rewards), 1)
230
  score = round(min(max(score, 0.0), 1.0), 4)
231
  success = score >= SUCCESS_THRESHOLD
232
 
233
  except Exception as exc:
234
- print(f"[DEBUG] Episode error for {task_name}: {exc}", file=sys.stderr, flush=True)
235
  finally:
236
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
237
 
@@ -241,17 +245,21 @@ async def run_task(client: OpenAI, env, task_name: str) -> dict:
241
  # ── Main ───────────────────────────────────────────────────────────────────
242
 
243
  async def main() -> None:
 
 
 
 
 
 
 
 
244
  client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
245
 
246
- # Import here to avoid import errors if openenv not installed during lint
247
  from client import NL2SQLEnv
248
 
249
  all_results = []
250
 
251
  for task_name in TASKS:
252
- # Set the default task for the server session via env-var approach.
253
- # For the hosted Space, we rely on the task cycling implemented in
254
- # the task registry's round-robin iterator.
255
  os.environ["NL2SQL_DEFAULT_TASK"] = task_name
256
 
257
  try:
@@ -260,20 +268,18 @@ async def main() -> None:
260
  all_results.append(result)
261
  except Exception as exc:
262
  print(
263
- f"[DEBUG] Failed to connect for task {task_name}: {exc}",
264
  file=sys.stderr,
265
  flush=True,
266
  )
267
- # Emit a zero-score END to keep log format valid
268
  log_end(success=False, steps=0, score=0.0, rewards=[])
269
  all_results.append({"task": task_name, "success": False, "score": 0.0})
270
 
271
- # Summary to stderr (not scored, for human readability)
272
  print("\n=== Baseline Summary ===", file=sys.stderr)
273
  for r in all_results:
274
  print(
275
- f" {r['task']:20s} score={r['score']:.3f} "
276
- f"success={r['success']}",
277
  file=sys.stderr,
278
  )
279
  avg = sum(r["score"] for r in all_results) / max(len(all_results), 1)
@@ -281,4 +287,4 @@ async def main() -> None:
281
 
282
 
283
  if __name__ == "__main__":
284
- asyncio.run(main())
 
6
  --------------------
7
  - Named `inference.py`, placed in project root.
8
  - Uses OpenAI client for all LLM calls.
9
+ - Reads: API_BASE_URL, MODEL_NAME, API_KEY (+ HF_TOKEN fallback) from environment.
10
  - Emits [START] / [STEP] / [END] lines to stdout in the exact format below.
11
  - Runs all 3 tasks; total runtime < 20 min on 2 vCPU / 8 GB.
12
 
 
27
 
28
  from openai import OpenAI
29
 
30
+ # ── Configuration ──────────────────────────────────────────────────────────
31
+ # CRITICAL: API_BASE_URL and API_KEY are injected by the competition evaluator.
32
+ # Do NOT hardcode values. The evaluator injects their LiteLLM proxy URL + key.
33
+ API_BASE_URL = os.getenv("API_BASE_URL", "https://router.huggingface.co/v1")
 
 
34
 
35
+ # CRITICAL FIX: Default MODEL_NAME must be a model available on the HF router /
36
+ # the competition's LiteLLM proxy. "ritvik360/qwen-7b-nl2sql-merged_1" is NOT
37
+ # on their proxy β€” it would silently fail and produce SELECT 1 for all steps.
38
+ # The competition injects MODEL_NAME if they want to override it.
39
+ MODEL_NAME = os.getenv("MODEL_NAME", "Qwen/Qwen2.5-7B-Instruct")
40
 
41
+ # CRITICAL FIX: Read API_KEY first (competition injects this), then fall back
42
+ # to HF_TOKEN. Both variable names must be checked.
43
+ API_KEY = os.getenv("API_KEY") or os.getenv("HF_TOKEN", "") or os.getenv("OPENAI_API_KEY", "")
44
 
 
 
 
 
 
 
 
 
45
  SPACE_URL = os.getenv("SPACE_URL", "https://ritvik360-nl2sql-bench.hf.space")
46
+ IMAGE_NAME = os.getenv("LOCAL_IMAGE_NAME", "nl2sql-bench:latest")
47
 
48
  BENCHMARK = "nl2sql-bench"
49
  MAX_STEPS = 5
50
+ TEMPERATURE = 0.2
51
  MAX_TOKENS = 512
52
+ SUCCESS_THRESHOLD = 0.7
53
 
54
  TASKS = ["simple-filter", "join-aggregation", "analytics-window"]
55
 
56
+ # ── Startup diagnostics (stderr β€” not scored) ─────────────────────────────
57
+ print(f"[DEBUG] API_BASE_URL = {API_BASE_URL}", file=sys.stderr, flush=True)
58
+ print(f"[DEBUG] MODEL_NAME = {MODEL_NAME}", file=sys.stderr, flush=True)
59
+ print(f"[DEBUG] API_KEY set = {bool(API_KEY)} (len={len(API_KEY)})", file=sys.stderr, flush=True)
60
+ print(f"[DEBUG] SPACE_URL = {SPACE_URL}", file=sys.stderr, flush=True)
61
+
62
  # ── System prompt ──────────────────────────────────────────────────────────
63
  SYSTEM_PROMPT = textwrap.dedent("""
64
  You are an expert SQL analyst working with a SQLite e-commerce database.
 
94
  def log_step(
95
  step: int, action: str, reward: float, done: bool, error: Optional[str]
96
  ) -> None:
 
97
  action_single = " ".join(action.split())
98
  error_val = error.replace("\n", " ") if error else "null"
99
  print(
 
146
 
147
 
148
  def call_llm(client: OpenAI, user_prompt: str) -> str:
149
+ # CRITICAL: Do NOT silently swallow exceptions with a bare `except Exception`.
150
+ # Silent failure means inference.py "succeeds" but makes zero LLM API calls,
151
+ # which causes the competition's LLM Criteria Check to fail.
152
  try:
153
+ print(
154
+ f"[DEBUG] Calling LLM: model={MODEL_NAME} base_url={API_BASE_URL}",
155
+ file=sys.stderr, flush=True
156
+ )
157
  resp = client.chat.completions.create(
158
  model=MODEL_NAME,
159
  messages=[
 
165
  stream=False,
166
  )
167
  text = (resp.choices[0].message.content or "").strip()
168
+ print(f"[DEBUG] LLM raw response (first 120 chars): {text[:120]}", file=sys.stderr, flush=True)
169
  # Strip markdown code fences if model wraps in ```sql ... ```
170
  if text.startswith("```"):
171
  lines = text.split("\n")
 
175
  ).strip()
176
  return text if text else "SELECT 1"
177
  except Exception as exc:
178
+ # Log the full error β€” this is the signal that tells you what went wrong
179
+ print(f"[DEBUG] LLM call FAILED: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
180
+ # Re-raise so the episode is marked failed, not silently scored as 0.
181
+ # A visible failure is better than a silent one that breaks the LLM check.
182
+ raise
183
 
184
 
185
  # ── Single-task episode ────────────────────────────────────────────────────
 
194
  log_start(task_name, MODEL_NAME)
195
 
196
  try:
197
+ result = await env.reset()
 
 
 
 
198
  obs = result.observation
199
 
200
  for step in range(1, MAX_STEPS + 1):
 
213
 
214
  sql = call_llm(client, user_prompt)
215
 
216
+ from models import NL2SQLAction
217
  action = NL2SQLAction(query=sql)
218
  result = await env.step(action)
219
  obs = result.observation
 
230
  if done:
231
  break
232
 
 
233
  score = sum(rewards) / max(len(rewards), 1)
234
  score = round(min(max(score, 0.0), 1.0), 4)
235
  success = score >= SUCCESS_THRESHOLD
236
 
237
  except Exception as exc:
238
+ print(f"[DEBUG] Episode error for {task_name}: {type(exc).__name__}: {exc}", file=sys.stderr, flush=True)
239
  finally:
240
  log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
241
 
 
245
  # ── Main ───────────────────────────────────────────────────────────────────
246
 
247
  async def main() -> None:
248
+ # Validate that API_KEY is present β€” fail fast with a clear message
249
+ if not API_KEY:
250
+ print(
251
+ "[ERROR] No API key found. Set API_KEY or HF_TOKEN environment variable.",
252
+ file=sys.stderr, flush=True
253
+ )
254
+ sys.exit(1)
255
+
256
  client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
257
 
 
258
  from client import NL2SQLEnv
259
 
260
  all_results = []
261
 
262
  for task_name in TASKS:
 
 
 
263
  os.environ["NL2SQL_DEFAULT_TASK"] = task_name
264
 
265
  try:
 
268
  all_results.append(result)
269
  except Exception as exc:
270
  print(
271
+ f"[DEBUG] Failed to connect for task {task_name}: {type(exc).__name__}: {exc}",
272
  file=sys.stderr,
273
  flush=True,
274
  )
 
275
  log_end(success=False, steps=0, score=0.0, rewards=[])
276
  all_results.append({"task": task_name, "success": False, "score": 0.0})
277
 
278
+ # Summary to stderr
279
  print("\n=== Baseline Summary ===", file=sys.stderr)
280
  for r in all_results:
281
  print(
282
+ f" {r['task']:20s} score={r['score']:.3f} success={r['success']}",
 
283
  file=sys.stderr,
284
  )
285
  avg = sum(r["score"] for r in all_results) / max(len(all_results), 1)
 
287
 
288
 
289
  if __name__ == "__main__":
290
+ asyncio.run(main())