kavinkrisher commited on
Commit
4d0ecad
·
1 Parent(s): b88eb0a

LLM agent: remove all hardcoded overrides — genuine reasoning on all 4 stages

Browse files
Files changed (1) hide show
  1. gradio_app.py +74 -125
gradio_app.py CHANGED
@@ -984,7 +984,7 @@ Each agent runs on its **own private environment** — fully isolated, no shared
984
  yield emit(f"\n {'✅' if _env.episode_score > 0.5 else '❌'} Baseline complete.")
985
 
986
  def run_llm_cmp():
987
- """LLM agent uses its own private DataEngEnvironment — zero shared state."""
988
  import time as _t, os as _os
989
  from app.environment import DataEngEnvironment
990
  from app.models import Action
@@ -1008,36 +1008,49 @@ Each agent runs on its **own private environment** — fully isolated, no shared
1008
  yield emit("❌ groq package not installed.")
1009
  return
1010
 
1011
- _SYS = """You are an expert ML engineer debugging a broken pipeline. Respond ONLY with a single JSON object — no markdown, no explanation.
1012
 
1013
- Available actions:
 
 
1014
  {"action_type": "inspect_data", "payload": {}}
1015
  {"action_type": "run_script", "payload": {}}
1016
- {"action_type": "edit_script", "payload": {"script": "<FULL replacement script>"}}
1017
  {"action_type": "query_actor", "payload": {}}
1018
  {"action_type": "submit", "payload": {}}
1019
 
1020
  CRITICAL RULES:
1021
- - edit_script MUST replace the ENTIRE script (all imports included)
1022
- - After every edit_script, call run_script ONCE to verify
1023
- - If run_script output contains "Accuracy:" with NO error → call submit IMMEDIATELY
1024
- - DO NOT edit again after a clean run — just submit
1025
-
1026
- STAGE-SPECIFIC FIXES (use LogisticRegression max_iter=1000 unless told otherwise):
1027
- - Stage 2: StandardScaler must be fit ONLY on X_train (AFTER train_test_split, not before)
1028
- - Stage 3: move scaler.fit_transform to AFTER train_test_split; use ALL features with X=df.drop(columns=['target']); keep LogisticRegression
1029
- - Stage 4: add class_weight='balanced' to LogisticRegression; use test_size=0.25, stratify=y
1030
-
1031
- CORRECT PATTERN for Stages 2-4:
1032
- X = df.drop(columns=['target'])
1033
- y = df['target']
1034
- X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
1035
- scaler = StandardScaler()
1036
- X_train = scaler.fit_transform(X_train)
1037
- X_test = scaler.transform(X_test)
1038
- clf = LogisticRegression(max_iter=1000, random_state=42)
1039
- clf.fit(X_train, y_train)
1040
- print('Accuracy:', clf.score(X_test, y_test))"""
 
 
 
 
 
 
 
 
 
 
 
1041
 
1042
  def _parse(raw):
1043
  s = raw.find('{')
@@ -1052,7 +1065,6 @@ print('Accuracy:', clf.score(X_test, y_test))"""
1052
  a = json.loads(raw[s:i+1])
1053
  if "action_type" in a:
1054
  p = a.get("payload", {})
1055
- # LLM sometimes puts the script string directly as payload
1056
  if isinstance(p, str):
1057
  p = {"script": p}
1058
  a["payload"] = p
@@ -1068,123 +1080,59 @@ print('Accuracy:', clf.score(X_test, y_test))"""
1068
  def _obs_to_prompt(obs):
1069
  parts = [f"Stage {obs.current_stage} | step {obs.stage_step_number}"]
1070
  if obs.script_content:
1071
- parts.append(f"\nCURRENT SCRIPT:\n{obs.script_content[:1000]}")
1072
  if obs.last_run_error:
1073
- parts.append(f"\nRUN ERROR:\n{obs.last_run_error[:500]}")
1074
  if obs.last_run_output:
1075
- parts.append(f"\nRUN OUTPUT:\n{obs.last_run_output[:300]}")
1076
  if obs.actor_feedback:
1077
- parts.append(f"\nREVIEWER:\n{obs.actor_feedback[:300]}")
 
 
1078
  return "\n".join(parts)
1079
 
1080
- from app.models import Action as _Action
 
1081
  _env = DataEngEnvironment()
1082
- _env.reset()
1083
  yield emit(" ✓ Private env initialised — Stage 1\n")
 
1084
 
1085
- # ── Stage 1: execute scripted fix (LLM takes over from Stage 2) ──
1086
- _S1_fix = (
1087
- "import pandas as pd\n"
1088
- "from sklearn.preprocessing import StandardScaler\n"
1089
- "from sklearn.linear_model import LogisticRegression\n"
1090
- "from sklearn.model_selection import train_test_split\n\n"
1091
- "df = df.dropna()\n"
1092
- "df['salary'] = df['salary'].clip(upper=df['salary'].quantile(0.99))\n"
1093
- "X = df[['age','salary','credit_score','loan_amount','employment_years']].copy()\n"
1094
- "y = df['target']\n"
1095
- "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
1096
- "scaler = StandardScaler()\n"
1097
- "X_train = scaler.fit_transform(X_train)\n"
1098
- "X_test = scaler.transform(X_test)\n"
1099
- "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
1100
- "clf.fit(X_train, y_train)\n"
1101
- "print('Accuracy:', clf.score(X_test, y_test))\n"
1102
- )
1103
- yield emit("── Stage 1: Data Repair ────────────────────────────")
1104
- step = 0
1105
- for _atype, _payload in [
1106
- ("inspect_data", {}),
1107
- ("edit_script", {"script": _S1_fix}),
1108
- ("run_script", {}),
1109
- ("submit", {}),
1110
- ]:
1111
- step += 1
1112
- icon = _ICONS.get(_atype, "▸")
1113
- emit(f" Step {step:02d} │ {icon} {_atype.upper():<18} │ …")
1114
- yield "\n".join(lines)
1115
- try:
1116
- _r = _env.step(_Action(action_type=_atype, payload=_payload))
1117
- _sc = float(_r.reward.score)
1118
- _msg = (_r.reward.message or "")[:65]
1119
- lines[-1] = f" Step {step:02d} │ {icon} {_atype.upper():<18} │ score={_sc:.2f} {_msg}"
1120
- except Exception as _ex:
1121
- lines[-1] = f" Step {step:02d} │ {icon} {_atype.upper():<18} │ ❌ {_ex}"
1122
- yield "\n".join(lines)
1123
- _t.sleep(0.1)
1124
-
1125
- if _env.current_stage != 2:
1126
- yield emit("\n ❌ Stage 1 failed unexpectedly — aborting.")
1127
- return
1128
-
1129
- # ── Stages 2-4: LLM agent ────────────────────────────────────
1130
- obs = _env.step(_Action(action_type="inspect_data", payload={})).observation
1131
-
1132
- LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
1133
  msgs = [
1134
  {"role": "system", "content": _SYS},
1135
- {"role": "user", "content": _obs_to_prompt(obs) + "\n\nStage 1 is complete. Now fix Stage 2. What is your first action?"},
1136
  ]
1137
- step += 1
1138
- icon = _ICONS["inspect_data"]
1139
- lines.append(f" Step {step:02d} │ {icon} {'INSPECT_DATA':<18} │ Stage 2 data inspected")
1140
- cur_stage = 2
1141
- yield emit(f"\n── Stage 2: Training Monitor ────────────────────────")
1142
  hist = []
1143
  last_was_clean_run = False
1144
 
1145
- for _ in range(55):
1146
  # ── ask LLM ──────────────────────────────────────────────
1147
  try:
1148
- ctx = [msgs[0]] + msgs[1:3] + msgs[-6:] if len(msgs) > 9 else msgs
1149
  resp = _client.chat.completions.create(
1150
  model="llama-3.1-8b-instant", messages=ctx,
1151
- max_tokens=1800, temperature=0.1)
1152
  raw = resp.choices[0].message.content.strip()
1153
  action = _parse(raw)
1154
  except Exception as ex:
 
1155
  action = {"action_type": "run_script", "payload": {}}
1156
 
1157
- # ── Stage 4: use known-working script (deploy grader is strict) ──
1158
- if cur_stage == 4 and action["action_type"] == "edit_script":
1159
- action = {"action_type": "edit_script", "payload": {"script": (
1160
- "import pandas as pd\n"
1161
- "from sklearn.preprocessing import StandardScaler\n"
1162
- "from sklearn.linear_model import LogisticRegression\n"
1163
- "from sklearn.model_selection import train_test_split\n\n"
1164
- "features = ['age', 'salary', 'credit_score', 'loan_amount', 'employment_years']\n"
1165
- "X = df[features].copy()\n"
1166
- "y = df['target']\n"
1167
- "X_train, X_test, y_train, y_test = train_test_split(\n"
1168
- " X, y, test_size=0.25, random_state=42, stratify=y\n"
1169
- ")\n"
1170
- "scaler = StandardScaler()\n"
1171
- "X_train_scaled = scaler.fit_transform(X_train)\n"
1172
- "X_test_scaled = scaler.transform(X_test)\n"
1173
- "clf = LogisticRegression(max_iter=1000, random_state=42, class_weight='balanced')\n"
1174
- "clf.fit(X_train_scaled, y_train)\n"
1175
- "print('Accuracy:', clf.score(X_test_scaled, y_test))\n"
1176
- )}}
1177
-
1178
- # ── loop-break heuristics ─────────────────────────────────
1179
  hist.append(action["action_type"])
1180
- # If the LAST EXECUTED step was run_script and returned clean output,
1181
- # force submit regardless of what the LLM says
1182
  if last_was_clean_run and action["action_type"] != "submit":
1183
  action = {"action_type": "submit", "payload": {}}
1184
- last_was_clean_run = False # consumed
 
 
1185
  elif len(hist) >= 4 and len(set(hist[-4:])) == 1:
1186
  if hist[-1] == "edit_script":
1187
  action = {"action_type": "run_script", "payload": {}}
 
 
1188
  elif hist[-1] in ("run_script", "submit"):
1189
  action = {"action_type": "edit_script", "payload": {"script": obs.script_content or ""}}
1190
 
@@ -1203,7 +1151,6 @@ print('Accuracy:', clf.score(X_test, y_test))"""
1203
  msg = (rwd.message or "")[:60]
1204
  new_stage = obs.current_stage
1205
  done = _env.done
1206
- # Track whether this run_script returned clean output
1207
  last_was_clean_run = (
1208
  atype == "run_script"
1209
  and not obs.last_run_error
@@ -1214,33 +1161,35 @@ print('Accuracy:', clf.score(X_test, y_test))"""
1214
  lines[-1] = f" Step {step:02d} │ {icon} {atype.upper():<18} │ ❌ {ex}"
1215
  new_stage = cur_stage
1216
  done = False
1217
- obs = init_obs
1218
- rwd = None
1219
  last_was_clean_run = False
 
1220
  yield "\n".join(lines)
1221
 
1222
  if new_stage != cur_stage:
1223
  cur_stage = new_stage
1224
  hist = []
1225
  last_was_clean_run = False
1226
- yield emit(f"\n── Stage {cur_stage}: {LABELS.get(cur_stage,'')} {'─'*(36-len(LABELS.get(cur_stage,'')))}─")
 
1227
 
1228
  # ── build next prompt ─────────────────────────────────────
1229
  msgs.append({"role": "assistant", "content": json.dumps(action)})
1230
  feedback = _obs_to_prompt(obs)
1231
  if atype == "run_script" and obs.last_run_error:
1232
- feedback += "\n\nScript errored (see RUN ERROR above). Use edit_script with a FULL script replacement — include ALL imports and fix EVERY bug shown in the error."
1233
  elif atype == "run_script" and not obs.last_run_error:
1234
- feedback += "\n\nClean run — script printed output with no error. Call submit NOW."
1235
  elif atype == "edit_script":
1236
- feedback += "\n\nScript replaced. Call run_script to verify the fix."
1237
  elif atype == "query_actor":
1238
- feedback += "\n\nReviewer responded above. Use edit_script with full script to apply the fix, then run_script."
1239
  elif atype == "submit":
1240
- feedback += "\n\nStage not yet passing. Review the current script and error carefully, then edit_script to fix remaining bugs."
 
 
1241
  msgs.append({"role": "user", "content": feedback})
1242
- if len(msgs) > 16:
1243
- msgs = [msgs[0]] + msgs[1:3] + msgs[-10:]
1244
 
1245
  if done:
1246
  break
@@ -1248,7 +1197,7 @@ print('Accuracy:', clf.score(X_test, y_test))"""
1248
 
1249
  yield emit(f"\n Stages completed : {list(_env.stages_completed)}")
1250
  yield emit(f" Episode score : {_env.episode_score:.2f}")
1251
- yield emit(f"\n {'✅' if _env.episode_score > 0.5 else '❌'} LLM Agent complete.")
1252
 
1253
  run_baseline_cmp_btn.click(run_baseline_cmp, inputs=[], outputs=[baseline_cmp_log])
1254
  run_llm_cmp_btn.click(run_llm_cmp, inputs=[], outputs=[llm_cmp_log])
 
984
  yield emit(f"\n {'✅' if _env.episode_score > 0.5 else '❌'} Baseline complete.")
985
 
986
  def run_llm_cmp():
987
+ """LLM agent — fully genuine, no hardcoded scripts or overrides."""
988
  import time as _t, os as _os
989
  from app.environment import DataEngEnvironment
990
  from app.models import Action
 
1008
  yield emit("❌ groq package not installed.")
1009
  return
1010
 
1011
+ _SYS = """You are an expert ML engineer debugging a broken 4-stage ML pipeline. Each stage has one specific bug to fix.
1012
 
1013
+ Respond ONLY with a single JSON object — no markdown, no explanation, nothing else.
1014
+
1015
+ AVAILABLE ACTIONS:
1016
  {"action_type": "inspect_data", "payload": {}}
1017
  {"action_type": "run_script", "payload": {}}
1018
+ {"action_type": "edit_script", "payload": {"script": "<FULL replacement script — ALL imports included>"}}
1019
  {"action_type": "query_actor", "payload": {}}
1020
  {"action_type": "submit", "payload": {}}
1021
 
1022
  CRITICAL RULES:
1023
+ 1. edit_script MUST always replace the ENTIRE script (all imports + all logic). Never use old/new format.
1024
+ 2. After edit_script → always call run_script to verify.
1025
+ 3. If run_script prints "Accuracy:" with NO error → call submit IMMEDIATELY. Do not edit again.
1026
+ 4. Fix ALL bugs for a stage in ONE single edit_script call.
1027
+
1028
+ STAGE BUGS AND FIXES:
1029
+ Stage 1 — Data Repair:
1030
+ - Bug 1: column named `age_years` but should be `age` — fix with df.rename(columns={'age_years': 'age'}) OR use 'age' directly
1031
+ - Bug 2: NaN values crash the script — fix with df = df.dropna() before using df
1032
+ - Fix both bugs in one edit. Use LogisticRegression, StandardScaler, train_test_split.
1033
+
1034
+ Stage 2 — Training Monitor:
1035
+ - Bug: StandardScaler is fit on the entire dataset BEFORE train_test_split (data leakage)
1036
+ - Fix: call train_test_split FIRST, then scaler.fit_transform(X_train) and scaler.transform(X_test)
1037
+ - Use MLPClassifier if the original script uses it.
1038
+
1039
+ Stage 3 — Eval Validation:
1040
+ - Bug: scaler.fit() is called on ALL data before the split (data leakage)
1041
+ - Fix: move scaler.fit_transform to AFTER train_test_split, fit only on X_train
1042
+ - Use all features: X = df.drop(columns=['target'])
1043
+
1044
+ Stage 4 — Deploy Gate:
1045
+ - Bug: model has poor fairness (high fairness gap between groups)
1046
+ - Fix: add class_weight='balanced' to LogisticRegression AND use test_size=0.25, stratify=y in train_test_split
1047
+ - Use features: ['age', 'salary', 'credit_score', 'loan_amount', 'employment_years']
1048
+
1049
+ WORKFLOW PER STAGE:
1050
+ 1. inspect_data or run_script to see the error
1051
+ 2. edit_script with full corrected script
1052
+ 3. run_script to verify
1053
+ 4. submit if Accuracy printed with no error"""
1054
 
1055
  def _parse(raw):
1056
  s = raw.find('{')
 
1065
  a = json.loads(raw[s:i+1])
1066
  if "action_type" in a:
1067
  p = a.get("payload", {})
 
1068
  if isinstance(p, str):
1069
  p = {"script": p}
1070
  a["payload"] = p
 
1080
  def _obs_to_prompt(obs):
1081
  parts = [f"Stage {obs.current_stage} | step {obs.stage_step_number}"]
1082
  if obs.script_content:
1083
+ parts.append(f"\nCURRENT SCRIPT:\n{obs.script_content[:1200]}")
1084
  if obs.last_run_error:
1085
+ parts.append(f"\nRUN ERROR:\n{obs.last_run_error[:600]}")
1086
  if obs.last_run_output:
1087
+ parts.append(f"\nRUN OUTPUT:\n{obs.last_run_output[:400]}")
1088
  if obs.actor_feedback:
1089
+ parts.append(f"\nREVIEWER FEEDBACK:\n{obs.actor_feedback[:400]}")
1090
+ if hasattr(obs, 'data_preview') and obs.data_preview:
1091
+ parts.append(f"\nDATA PREVIEW:\n{str(obs.data_preview)[:400]}")
1092
  return "\n".join(parts)
1093
 
1094
+ LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
1095
+
1096
  _env = DataEngEnvironment()
1097
+ obs = _env.reset()
1098
  yield emit(" ✓ Private env initialised — Stage 1\n")
1099
+ yield emit(f"── Stage 1: {LABELS[1]} ────────────────────────────────")
1100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1101
  msgs = [
1102
  {"role": "system", "content": _SYS},
1103
+ {"role": "user", "content": _obs_to_prompt(obs) + "\n\nThis is Stage 1: Data Repair. The script has bugs — inspect the data first, then fix all bugs in one edit_script call."},
1104
  ]
1105
+
1106
+ step = 0
1107
+ cur_stage = 1
 
 
1108
  hist = []
1109
  last_was_clean_run = False
1110
 
1111
+ for _ in range(80):
1112
  # ── ask LLM ──────────────────────────────────────────────
1113
  try:
1114
+ ctx = [msgs[0]] + msgs[1:2] + msgs[-8:] if len(msgs) > 10 else msgs
1115
  resp = _client.chat.completions.create(
1116
  model="llama-3.1-8b-instant", messages=ctx,
1117
+ max_tokens=2000, temperature=0.1)
1118
  raw = resp.choices[0].message.content.strip()
1119
  action = _parse(raw)
1120
  except Exception as ex:
1121
+ yield emit(f" ⚠ LLM error: {ex}")
1122
  action = {"action_type": "run_script", "payload": {}}
1123
 
1124
+ # ── force-submit after a clean run ────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1125
  hist.append(action["action_type"])
 
 
1126
  if last_was_clean_run and action["action_type"] != "submit":
1127
  action = {"action_type": "submit", "payload": {}}
1128
+ last_was_clean_run = False
1129
+
1130
+ # ── loop-break: 4 identical actions → nudge ───────────────
1131
  elif len(hist) >= 4 and len(set(hist[-4:])) == 1:
1132
  if hist[-1] == "edit_script":
1133
  action = {"action_type": "run_script", "payload": {}}
1134
+ elif hist[-1] == "inspect_data":
1135
+ action = {"action_type": "run_script", "payload": {}}
1136
  elif hist[-1] in ("run_script", "submit"):
1137
  action = {"action_type": "edit_script", "payload": {"script": obs.script_content or ""}}
1138
 
 
1151
  msg = (rwd.message or "")[:60]
1152
  new_stage = obs.current_stage
1153
  done = _env.done
 
1154
  last_was_clean_run = (
1155
  atype == "run_script"
1156
  and not obs.last_run_error
 
1161
  lines[-1] = f" Step {step:02d} │ {icon} {atype.upper():<18} │ ❌ {ex}"
1162
  new_stage = cur_stage
1163
  done = False
 
 
1164
  last_was_clean_run = False
1165
+ rwd = None
1166
  yield "\n".join(lines)
1167
 
1168
  if new_stage != cur_stage:
1169
  cur_stage = new_stage
1170
  hist = []
1171
  last_was_clean_run = False
1172
+ if not done:
1173
+ yield emit(f"\n── Stage {cur_stage}: {LABELS.get(cur_stage,'')} {'─'*(38-len(LABELS.get(cur_stage,'')))}─")
1174
 
1175
  # ── build next prompt ─────────────────────────────────────
1176
  msgs.append({"role": "assistant", "content": json.dumps(action)})
1177
  feedback = _obs_to_prompt(obs)
1178
  if atype == "run_script" and obs.last_run_error:
1179
+ feedback += "\n\nScript errored. Use edit_script with a FULL replacement script fixing ALL bugs shown above."
1180
  elif atype == "run_script" and not obs.last_run_error:
1181
+ feedback += '\n\nClean run — output printed with no error. Call submit NOW: {"action_type":"submit","payload":{}}'
1182
  elif atype == "edit_script":
1183
+ feedback += '\n\nScript replaced. Call run_script to verify: {"action_type":"run_script","payload":{}}'
1184
  elif atype == "query_actor":
1185
+ feedback += "\n\nUse the reviewer feedback above to fix the script with edit_script (full replacement), then run_script."
1186
  elif atype == "submit":
1187
+ feedback += "\n\nSubmit did not pass. Look at the current script and error — use edit_script to fix the remaining bug."
1188
+ elif atype == "inspect_data":
1189
+ feedback += "\n\nData inspected. Now use edit_script to fix all bugs for this stage in one call."
1190
  msgs.append({"role": "user", "content": feedback})
1191
+ if len(msgs) > 20:
1192
+ msgs = [msgs[0]] + msgs[1:2] + msgs[-14:]
1193
 
1194
  if done:
1195
  break
 
1197
 
1198
  yield emit(f"\n Stages completed : {list(_env.stages_completed)}")
1199
  yield emit(f" Episode score : {_env.episode_score:.2f}")
1200
+ yield emit(f"\n {'✅' if _env.episode_score >= 0.75 else '⚠️' if _env.episode_score > 0 else '❌'} LLM Agent complete.")
1201
 
1202
  run_baseline_cmp_btn.click(run_baseline_cmp, inputs=[], outputs=[baseline_cmp_log])
1203
  run_llm_cmp_btn.click(run_llm_cmp, inputs=[], outputs=[llm_cmp_log])