junaid0600 commited on
Commit
7b55b4f
Β·
verified Β·
1 Parent(s): e299a66

Update training/train_agent.py

Browse files
Files changed (1) hide show
  1. training/train_agent.py +116 -42
training/train_agent.py CHANGED
@@ -126,7 +126,7 @@ def parse_action(text: str) -> dict | None:
126
  return None
127
 
128
 
129
- # ── Local reward using DatabaseSimulator ──────────────────────
130
  def compute_reward(action: dict, scenario: dict) -> tuple:
131
  """
132
  Compute reward LOCALLY β€” no HTTP, no shared state, deterministic.
@@ -142,6 +142,28 @@ def compute_reward(action: dict, scenario: dict) -> tuple:
142
  if action_type == "create_index":
143
  result = sim.apply_action("create_index", payload)
144
  delta = result.get("delta", 0.0)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
  elif action_type == "rewrite_query":
146
  result = sim.apply_action("rewrite_query", payload)
147
  delta = result.get("delta", 0.0)
@@ -156,10 +178,12 @@ def compute_reward(action: dict, scenario: dict) -> tuple:
156
  elif action_type == "submit_report":
157
  delta = max(0, sim.get_performance_score() - baseline)
158
 
159
- final = sim.get_performance_score()
160
- improvement = max(0.0, final - baseline)
 
 
161
  max_possible = max(1.0, 100.0 - baseline)
162
- ratio = improvement / max_possible
163
 
164
  # Step reward
165
  step_r = {
@@ -175,24 +199,28 @@ def compute_reward(action: dict, scenario: dict) -> tuple:
175
  # Delta reward β€” key signal
176
  delta_r = min(0.65, ratio * 0.65)
177
 
178
- # Milestone bonus
179
- milestone = 0.0
180
- milestone_str = ""
 
 
 
 
 
 
181
  if ratio >= 0.75:
182
- milestone = 0.40
183
- milestone_str = "🎯 75% milestone!"
184
- elif ratio >= 0.50:
185
- milestone = 0.25
186
- milestone_str = "🎯 50% milestone!"
187
- elif ratio >= 0.25:
188
- milestone = 0.15
189
- milestone_str = "🎯 25% milestone!"
190
-
191
- # Wrong index penalty
192
- wrong_pen = -0.15 if (action_type == "create_index" and delta <= 0.0) else 0.0
193
 
194
  total = max(0.001, min(0.999, step_r + delta_r + milestone + wrong_pen))
195
- desc = f"+{improvement:.1f}pts delta={delta:.1f} {milestone_str}"
 
196
 
197
  return total, improvement, milestone, desc
198
 
@@ -270,7 +298,14 @@ def build_dataset() -> Dataset:
270
 
271
 
272
  # ── Generate loss + reward plots ──────────────────────────────
 
 
 
 
 
 
273
  def generate_plots(trainer):
 
274
  import matplotlib
275
  matplotlib.use("Agg")
276
  import matplotlib.pyplot as plt
@@ -280,40 +315,77 @@ def generate_plots(trainer):
280
  print("⚠️ No logs to plot")
281
  return
282
 
283
- steps = [l.get("step", i) for i,l in enumerate(logs)]
284
- losses = [l.get("loss", 0) for l in logs]
285
  rewards = [l.get("reward", 0) for l in logs]
286
 
287
- fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))
288
- fig.suptitle("GRPO Training β€” SQL Database Engineer Agent",
 
 
 
 
 
 
 
 
289
  fontsize=13, fontweight="bold")
290
 
291
- ax1.plot(steps, losses, "b-o", lw=2, ms=4, label="Loss")
 
 
 
 
292
  ax1.set_xlabel("Training Step")
293
  ax1.set_ylabel("Loss")
294
- ax1.set_title("Training Loss (↓ = model learning)")
 
295
  ax1.grid(True, alpha=0.3)
 
 
296
  if losses:
297
- ax1.annotate(f"Start: {losses[0]:.4f}", xy=(steps[0], losses[0]),
298
- xytext=(steps[0]+1, losses[0]*1.1), fontsize=8, color="red")
299
- ax1.annotate(f"End: {losses[-1]:.4f}", xy=(steps[-1], losses[-1]),
300
- xytext=(steps[-1]-8, losses[-1]*1.15), fontsize=8, color="green")
301
-
302
- ax2.plot(steps, rewards, "g-o", lw=2, ms=4, label="Avg Reward")
 
 
 
 
 
 
 
 
 
 
303
  ax2.set_xlabel("Training Step")
304
- ax2.set_ylabel("Reward")
305
- ax2.set_title("Reward During Training (↑ = improving)")
306
  ax2.grid(True, alpha=0.3)
307
-
308
- plt.tight_layout()
 
 
 
 
 
 
 
 
 
 
 
 
309
  plt.savefig("loss_curve.png", dpi=150, bbox_inches="tight")
310
  print("βœ… loss_curve.png saved")
311
  if losses:
312
- print(f" Loss: {losses[0]:.4f} β†’ {losses[-1]:.4f}")
313
  if rewards:
314
  valid = [r for r in rewards if r > 0]
315
  if valid:
316
- print(f" Reward: {valid[0]:.4f} β†’ {valid[-1]:.4f}")
317
 
318
 
319
  # ── Main ──────────────────────────────────────────────────────
@@ -369,10 +441,12 @@ def train():
369
 
370
  print(f"πŸ‹οΈ GRPO training β€” {MAX_STEPS} steps")
371
  print("Expected rewards:")
372
- print(" inspect_query (always): ~0.10")
373
- print(" create_index (wrong columns): ~0.001")
374
- print(" create_index (right columns): ~0.75-0.99")
375
- print(" GRPO learns: right create_index >> everything else\n")
 
 
376
 
377
  trainer.train()
378
  print("\nβœ… Training complete!")
@@ -394,4 +468,4 @@ def train():
394
 
395
 
396
  if __name__ == "__main__":
397
- train()
 
126
  return None
127
 
128
 
129
+
130
  def compute_reward(action: dict, scenario: dict) -> tuple:
131
  """
132
  Compute reward LOCALLY β€” no HTTP, no shared state, deterministic.
 
142
  if action_type == "create_index":
143
  result = sim.apply_action("create_index", payload)
144
  delta = result.get("delta", 0.0)
145
+
146
+ # ── Hint-match fallback ────────────────────────────────────────────
147
+ # When the simulator returns delta=0 (wrong/partial columns), check
148
+ # the scenario's missing_index_hints and simulate a proportional delta.
149
+ # This gives GRPO a gradient signal early in training before the model
150
+ # has learned the exact right column names.
151
+ if delta <= 0.0:
152
+ hints = scenario.get("missing_index_hints", [])
153
+ p_table = payload.get("table", "")
154
+ p_cols = set(str(c).lower() for c in payload.get("columns", []))
155
+ best_match = 0.0
156
+ for hint in hints:
157
+ h_table = hint.get("table", "")
158
+ h_cols = set(str(c).lower() for c in hint.get("columns", []))
159
+ if not h_cols:
160
+ continue
161
+ table_ok = 1.0 if h_table == p_table else 0.3
162
+ col_overlap = len(p_cols & h_cols) / max(len(h_cols), 1)
163
+ best_match = max(best_match, table_ok * 0.4 + col_overlap * 0.6)
164
+ if best_match > 0:
165
+ max_gap = max(1.0, 100.0 - baseline)
166
+ delta = best_match * max_gap * 0.65 # up to 65% of gap on perfect match β†’ reaches 25% milestone
167
  elif action_type == "rewrite_query":
168
  result = sim.apply_action("rewrite_query", payload)
169
  delta = result.get("delta", 0.0)
 
178
  elif action_type == "submit_report":
179
  delta = max(0, sim.get_performance_score() - baseline)
180
 
181
+ final = sim.get_performance_score()
182
+ sim_improve = max(0.0, final - baseline)
183
+ # Use hint-simulated delta if simulator returned 0 (wrong columns early in training)
184
+ improvement = max(sim_improve, delta)
185
  max_possible = max(1.0, 100.0 - baseline)
186
+ ratio = improvement / max_possible
187
 
188
  # Step reward
189
  step_r = {
 
199
  # Delta reward β€” key signal
200
  delta_r = min(0.65, ratio * 0.65)
201
 
202
+ # Milestone bonus β€” cumulative, all thresholds crossed shown + rewarded
203
+ milestone = 0.0
204
+ milestone_parts = []
205
+ if ratio >= 0.25:
206
+ milestone += 0.15
207
+ milestone_parts.append("25%")
208
+ if ratio >= 0.50:
209
+ milestone += 0.25
210
+ milestone_parts.append("50%")
211
  if ratio >= 0.75:
212
+ milestone += 0.40
213
+ milestone_parts.append("75%")
214
+ milestone_str = ("🎯 " + "+".join(milestone_parts) + " milestone!") if milestone_parts else ""
215
+
216
+ # Wrong index penalty reduced: -0.05 instead of -0.15
217
+ # Old value exactly cancelled step_r (0.15 - 0.15 = 0 β†’ clamped to 0.001)
218
+ # Now wrong create_index still scores ~0.10 instead of 0.001
219
+ wrong_pen = -0.05 if (action_type == "create_index" and delta <= 0.0) else 0.0
 
 
 
220
 
221
  total = max(0.001, min(0.999, step_r + delta_r + milestone + wrong_pen))
222
+ src = "sim" if sim_improve > 0 else ("hint" if delta > 0 else "none")
223
+ desc = f"+{improvement:.1f}pts delta={delta:.1f}[{src}] {milestone_str}"
224
 
225
  return total, improvement, milestone, desc
226
 
 
298
 
299
 
300
  # ── Generate loss + reward plots ──────────────────────────────
301
+ # ──────────────────────────────────────────────────────────────
302
+ # REPLACE ONLY THIS FUNCTION in your existing train_agent.py
303
+ # Find: def generate_plots(trainer):
304
+ # Replace the entire function with this:
305
+ # ──────────────────────────────────────────────────────────────
306
+
307
  def generate_plots(trainer):
308
+ import json, numpy as np
309
  import matplotlib
310
  matplotlib.use("Agg")
311
  import matplotlib.pyplot as plt
 
315
  print("⚠️ No logs to plot")
316
  return
317
 
318
+ steps = [l.get("step", i) for i, l in enumerate(logs)]
319
+ losses = [l.get("loss", 0) for l in logs]
320
  rewards = [l.get("reward", 0) for l in logs]
321
 
322
+ # Save logs for generate_plots.py
323
+ import os, json
324
+ os.makedirs(OUTPUT_DIR, exist_ok=True)
325
+ with open(f"{OUTPUT_DIR}/training_logs.json", "w") as f:
326
+ json.dump(trainer.state.log_history, f)
327
+ print(f"βœ… Logs saved to {OUTPUT_DIR}/training_logs.json")
328
+
329
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 5))
330
+ fig.suptitle("GRPO Training β€” SQL Database Engineer Agent\n"
331
+ "Qwen2.5-1.5B fine-tuned with Unsloth + TRL",
332
  fontsize=13, fontweight="bold")
333
 
334
+ # ── LEFT: Loss with smoothing ──────────────────────────────
335
+ ax1.plot(steps, losses, "b-", lw=1.0, alpha=0.35, label="Raw loss")
336
+ if len(losses) >= 10:
337
+ smooth = np.convolve(losses, np.ones(10)/10, mode="valid")
338
+ ax1.plot(steps[9:], smooth, "b-", lw=2.5, label="10-step avg")
339
  ax1.set_xlabel("Training Step")
340
  ax1.set_ylabel("Loss")
341
+ ax1.set_title("Training Loss ↓ = model learning")
342
+ ax1.ticklabel_format(style="sci", axis="y", scilimits=(0,0))
343
  ax1.grid(True, alpha=0.3)
344
+ ax1.legend(fontsize=9)
345
+ # FIX 1: scientific notation annotations
346
  if losses:
347
+ ax1.annotate(f"Start: {losses[0]:.2e}",
348
+ xy=(steps[0], losses[0]),
349
+ xytext=(steps[0]+3, max(losses)*0.85),
350
+ fontsize=8, color="red",
351
+ arrowprops=dict(arrowstyle="->", color="red", lw=1))
352
+ ax1.annotate(f"End: {losses[-1]:.2e}",
353
+ xy=(steps[-1], losses[-1]),
354
+ xytext=(steps[-1]-12, max(losses)*0.65),
355
+ fontsize=8, color="green",
356
+ arrowprops=dict(arrowstyle="->", color="green", lw=1))
357
+
358
+ # ── RIGHT: Reward with smoothing ───────────────────────────
359
+ ax2.plot(steps, rewards, "g-", lw=1.0, alpha=0.35, label="Raw reward")
360
+ if len(rewards) >= 10:
361
+ smooth_r = np.convolve(rewards, np.ones(10)/10, mode="valid")
362
+ ax2.plot(steps[9:], smooth_r, "g-", lw=2.5, label="10-step avg")
363
  ax2.set_xlabel("Training Step")
364
+ ax2.set_ylabel("Avg Reward")
365
+ ax2.set_title("Reward During Training ↑ = improving")
366
  ax2.grid(True, alpha=0.3)
367
+ ax2.legend(fontsize=9)
368
+
369
+ # Bottom summary
370
+ if losses and rewards:
371
+ start_r = rewards[0]
372
+ end_r = rewards[-1]
373
+ pct = ((end_r-start_r)/max(abs(start_r),1e-9))*100
374
+ fig.text(0.5, 0.01,
375
+ f"Loss: {losses[0]:.2e} β†’ {losses[-1]:.2e} | "
376
+ f"Reward: {start_r:.3f} β†’ {end_r:.3f} ({'+'if pct>=0 else ''}{pct:.0f}%)",
377
+ ha="center", fontsize=10,
378
+ bbox=dict(boxstyle="round", facecolor="lightyellow", alpha=0.8))
379
+
380
+ plt.tight_layout(rect=[0, 0.07, 1, 1])
381
  plt.savefig("loss_curve.png", dpi=150, bbox_inches="tight")
382
  print("βœ… loss_curve.png saved")
383
  if losses:
384
+ print(f" Loss: {losses[0]:.2e} β†’ {losses[-1]:.2e}")
385
  if rewards:
386
  valid = [r for r in rewards if r > 0]
387
  if valid:
388
+ print(f" Reward: {valid[0]:.3f} β†’ {valid[-1]:.3f}")
389
 
390
 
391
  # ── Main ──────────────────────────────────────────────────────
 
441
 
442
  print(f"πŸ‹οΈ GRPO training β€” {MAX_STEPS} steps")
443
  print("Expected rewards:")
444
+ print(" inspect_query / analyze_indexes: ~0.10")
445
+ print(" create_index (no table/col match): ~0.10 (was 0.001)")
446
+ print(" create_index (partial hint match): ~0.20-0.45")
447
+ print(" create_index (perfect hint match): ~0.55-0.80")
448
+ print(" create_index (simulator confirms): ~0.75-0.99")
449
+ print(" Milestones: 25%=+0.15 50%=+0.25 75%=+0.40 (cumulative)\n")
450
 
451
  trainer.train()
452
  print("\nβœ… Training complete!")
 
468
 
469
 
470
  if __name__ == "__main__":
471
+ train()