kavinkrisher commited on
Commit
fd32659
Β·
1 Parent(s): 5dd89d1

Fix comparison tab: use private env instances, bypass shared HTTP env entirely

Browse files
Files changed (1) hide show
  1. gradio_app.py +142 -147
gradio_app.py CHANGED
@@ -840,8 +840,8 @@ CRITICAL RULES:
840
  # ── COMPARISON TAB ────────────────────────────────────────────────────
841
  with gr.Tab("πŸ“Š Baseline vs LLM Agent"):
842
  gr.Markdown("""### Rule-Based Baseline vs LLM Agent
843
- Run each agent independently on a fresh environment reset.
844
- - **Baseline**: Hardcoded scripted fixes β€” rigid, fails Stage 4
845
  - **LLM Agent**: Groq Llama 3.1 8B reasoning dynamically from error logs and script state
846
  """)
847
  with gr.Row():
@@ -849,17 +849,20 @@ Run each agent independently on a fresh environment reset.
849
  gr.Markdown("#### Rule-Based Baseline")
850
  run_baseline_cmp_btn = gr.Button("β–Ά Run Baseline Agent", elem_classes="btn-reset")
851
  baseline_cmp_log = gr.Textbox(
852
- label="Baseline Log", lines=22, interactive=False,
853
  placeholder="Click to run baseline agent…", elem_classes="code-box"
854
  )
855
  with gr.Column():
856
  gr.Markdown("#### LLM Agent (Groq Llama 3.1 8B)")
857
  run_llm_cmp_btn = gr.Button("β–Ά Run LLM Agent", elem_classes="btn-primary")
858
  llm_cmp_log = gr.Textbox(
859
- label="LLM Log", lines=22, interactive=False,
860
  placeholder="Click to run LLM agent…", elem_classes="code-box"
861
  )
862
 
 
 
 
863
  _S1 = (
864
  "import pandas as pd\n"
865
  "from sklearn.preprocessing import StandardScaler\n"
@@ -867,11 +870,12 @@ Run each agent independently on a fresh environment reset.
867
  "from sklearn.model_selection import train_test_split\n\n"
868
  "df = df.dropna()\n"
869
  "df['salary'] = df['salary'].clip(upper=df['salary'].quantile(0.99))\n"
870
- "X = df[['age', 'salary', 'credit_score', 'loan_amount', 'employment_years']].copy()\n"
871
  "y = df['target']\n"
 
872
  "scaler = StandardScaler()\n"
873
- "X_scaled = scaler.fit_transform(X)\n"
874
- "X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)\n"
875
  "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
876
  "clf.fit(X_train, y_train)\n"
877
  "print('Accuracy:', clf.score(X_test, y_test))\n"
@@ -886,7 +890,7 @@ Run each agent independently on a fresh environment reset.
886
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
887
  "scaler = StandardScaler()\n"
888
  "X_train = scaler.fit_transform(X_train)\n"
889
- "X_test = scaler.transform(X_test)\n"
890
  "clf = MLPClassifier(hidden_layer_sizes=(64,32), max_iter=200, random_state=42)\n"
891
  "clf.fit(X_train, y_train)\n"
892
  "print('Accuracy:', clf.score(X_test, y_test))\n"
@@ -901,7 +905,7 @@ Run each agent independently on a fresh environment reset.
901
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
902
  "scaler = StandardScaler()\n"
903
  "X_train = scaler.fit_transform(X_train)\n"
904
- "X_test = scaler.transform(X_test)\n"
905
  "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
906
  "clf.fit(X_train, y_train)\n"
907
  "print('Accuracy:', clf.score(X_test, y_test))\n"
@@ -916,121 +920,108 @@ Run each agent independently on a fresh environment reset.
916
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
917
  "scaler = StandardScaler()\n"
918
  "X_train = scaler.fit_transform(X_train)\n"
919
- "X_test = scaler.transform(X_test)\n"
920
  "clf = LogisticRegression(max_iter=1000, random_state=42, class_weight='balanced')\n"
921
  "clf.fit(X_train, y_train)\n"
922
  "print('Accuracy:', clf.score(X_test, y_test))\n"
923
  )
924
  _BSTAGES = {
925
- 1: [{"action_type":"inspect_data","payload":{}},
926
- {"action_type":"edit_script","payload":{"script":_S1}},
927
- {"action_type":"run_script","payload":{}},
928
- {"action_type":"submit","payload":{}}],
929
- 2: [{"action_type":"inspect_data","payload":{}},
930
- {"action_type":"edit_script","payload":{"script":_S2}},
931
- {"action_type":"run_script","payload":{}},
932
- {"action_type":"submit","payload":{}}],
933
- 3: [{"action_type":"run_script","payload":{}},
934
- {"action_type":"edit_script","payload":{"script":_S3}},
935
- {"action_type":"run_script","payload":{}},
936
- {"action_type":"submit","payload":{}}],
937
- 4: [{"action_type":"query_actor","payload":{}},
938
- {"action_type":"edit_script","payload":{"script":_S4}},
939
- {"action_type":"run_script","payload":{}},
940
- {"action_type":"submit","payload":{}}],
941
  }
942
- _ICONS = {"inspect_data":"πŸ”","run_script":"▢️ ","edit_script":"✏️ ","query_actor":"πŸ€–","submit":"πŸ“€"}
943
 
944
  def run_baseline_cmp():
 
945
  import time as _t
 
 
946
  lines = []
947
  def emit(l):
948
  lines.append(l)
949
  return "\n".join(lines)
 
950
  yield emit("╔══════════════════════════════════════════════════════╗")
951
  yield emit("β•‘ Rule-Based Baseline Agent β•‘")
952
  yield emit("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
953
- try:
954
- httpx.post(f"{BASE_URL}/reset", timeout=15)
955
- _t.sleep(0.8)
956
- r2 = httpx.post(f"{BASE_URL}/reset", timeout=15).json()
957
- init_script = r2.get("script_content", "")
958
- yield emit(f" βœ“ Env reset | stage={r2.get('current_stage','?')} | script has age_years={'age_years' in init_script}")
959
- except Exception as e:
960
- yield emit(f"❌ Reset failed: {e}")
961
- return
962
  step = 0
963
- done = False
964
- cur_env_stage = 1
965
- for stage in range(1, 5):
966
- if done:
967
  break
968
- if cur_env_stage != stage:
969
- yield emit(f" ⚠ Env is on stage {cur_env_stage}, skipping stage {stage} actions")
970
- continue
971
- yield emit(f"── Stage {stage} ─────────────────────────────────────────")
972
- for action in _BSTAGES[stage]:
973
  step += 1
974
- atype = action["action_type"]
975
  icon = _ICONS.get(atype, "β–Έ")
976
- emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ running…")
977
  yield "\n".join(lines)
978
  try:
979
- resp = httpx.post(f"{BASE_URL}/step", json=action, timeout=30).json()
980
- obs = resp.get("observation", {})
981
- rwd = resp.get("reward", {})
982
- score = float(rwd.get("score", 0.0))
983
- msg = rwd.get("message", "")[:60]
984
- done = obs.get("done", False)
985
- cur_env_stage = obs.get("current_stage", cur_env_stage)
986
- dbg = ""
987
- if atype == "edit_script":
988
- sc = obs.get("script_content", "")
989
- dbg = f" [age_years={'age_years' in sc}]"
990
- lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}{dbg}"
991
  except Exception as ex:
992
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
993
  yield "\n".join(lines)
994
- _t.sleep(0.3)
995
- try:
996
- status = httpx.get(f"{BASE_URL}/pipeline_status", timeout=10).json()
997
- b_score = status.get("episode_score", 0.0)
998
- b_stages = status.get("stages_completed", [])
999
- except Exception:
1000
- b_score = 0.0
1001
- b_stages = []
1002
- yield emit(f"\n Stages completed : {b_stages}")
1003
- yield emit(f" Episode score : {b_score:.2f}")
1004
- yield emit("\n βœ… Baseline complete.")
1005
 
1006
  def run_llm_cmp():
 
1007
  import time as _t, os as _os
 
 
1008
  lines = []
1009
  def emit(l):
1010
  lines.append(l)
1011
  return "\n".join(lines)
 
1012
  yield emit("╔══════════════════════════════════════════════════════╗")
1013
  yield emit("β•‘ LLM Agent (Groq Llama 3.1 8B) β•‘")
1014
  yield emit("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
 
1015
  groq_key = _os.environ.get("GROQ_API_KEY", "")
1016
  if not groq_key:
1017
- yield emit("❌ GROQ_API_KEY not set.")
1018
  return
1019
  try:
1020
  from groq import Groq as _Groq
1021
  _client = _Groq(api_key=groq_key)
1022
  except ImportError:
1023
- yield emit("❌ groq not installed.")
1024
  return
1025
 
1026
- _SYS = """You are debugging a broken ML pipeline. Fix it fast.
1027
- RESPOND WITH ONLY A JSON OBJECT. No explanation. No markdown.
1028
- ALWAYS use full script replacement: {"action_type": "edit_script", "payload": {"script": "FULL script"}}
1029
- Stage 1: rename age_years->age AND add df.dropna() β€” fix BOTH in one edit
1030
- Stage 2: add StandardScaler fitted on X_train only
1031
- Stage 3: move scaler.fit() AFTER train_test_split()
1032
- Stage 4: add class_weight='balanced' to LogisticRegression
1033
- Submit as soon as run_script shows no error and prints Accuracy."""
 
 
 
 
 
 
 
 
 
1034
 
1035
  def _parse(raw):
1036
  s = raw.find('{')
@@ -1046,107 +1037,111 @@ Submit as soon as run_script shows no error and prints Accuracy."""
1046
  if "action_type" in a:
1047
  a.setdefault("payload", {})
1048
  return a
1049
- except: pass
 
1050
  break
1051
  for t in ["edit_script","run_script","submit","inspect_data","query_actor"]:
1052
- if t in raw: return {"action_type": t, "payload": {}}
 
1053
  return {"action_type": "run_script", "payload": {}}
1054
 
1055
- def _fmt(obs, rwd=None):
1056
- p = [f"Stage {obs.get('current_stage','?')} | Step {obs.get('stage_step_number','?')}"]
1057
- if obs.get("last_run_error"): p.append(f"\nERROR:\n{obs['last_run_error'][:400]}")
1058
- if obs.get("last_run_output"): p.append(f"\nOUTPUT:\n{obs['last_run_output'][:200]}")
1059
- if obs.get("actor_feedback"): p.append(f"\nREVIEWER:\n{obs['actor_feedback'][:200]}")
1060
- if obs.get("script_content"): p.append(f"\nSCRIPT:\n{obs['script_content'][:600]}")
1061
- if rwd: p.append(f"\nREWARD: {rwd.get('score',0):.2f}")
1062
- return "\n".join(p)
 
 
 
1063
 
1064
- try:
1065
- httpx.post(f"{BASE_URL}/reset", timeout=15)
1066
- _t.sleep(0.5)
1067
- r = httpx.post(f"{BASE_URL}/reset", timeout=15)
1068
- obs = r.json()
1069
- if "observation" in obs: obs = obs["observation"]
1070
- except Exception as e:
1071
- yield emit(f"❌ Reset failed: {e}")
1072
- return
1073
 
1074
- msgs = [{"role":"system","content":_SYS},
1075
- {"role":"user","content":_fmt(obs)+"\n\nWhat is your first action?"}]
 
 
 
1076
  cur_stage = 1
1077
  step = 0
1078
  hist = []
1079
- done = False
1080
- LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
1081
- yield emit(f"── Stage 1: Data Repair ─────────────────────────────")
1082
 
1083
  for _ in range(60):
 
1084
  try:
1085
- trimmed = [msgs[0], msgs[1]] + msgs[-4:] if len(msgs) > 8 else msgs
1086
  resp = _client.chat.completions.create(
1087
- model="llama-3.1-8b-instant", messages=trimmed,
1088
- max_tokens=1500, temperature=0.2)
1089
- raw = resp.choices[0].message.content.strip()
1090
  action = _parse(raw)
1091
- except Exception:
1092
  action = {"action_type": "run_script", "payload": {}}
1093
 
 
1094
  hist.append(action["action_type"])
1095
- if len(hist) >= 2 and hist[-2] == "submit" and hist[-1] == "submit":
1096
- action = {"action_type": "edit_script", "payload": {"script": obs.get("script_content","")}}
1097
- if len(hist) >= 3 and len(set(hist[-3:])) == 1:
1098
- if hist[-1] == "submit": action = {"action_type": "run_script", "payload": {}}
1099
- elif hist[-1] == "inspect_data": action = {"action_type": "edit_script", "payload": {"script": obs.get("script_content","")}}
1100
 
1101
  step += 1
1102
  atype = action["action_type"]
1103
- icon = _ICONS.get(atype, "β–Έ")
1104
- emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ running…")
1105
  yield "\n".join(lines)
1106
- _t.sleep(0.2)
1107
 
 
1108
  try:
1109
- resp = httpx.post(f"{BASE_URL}/step", json=action, timeout=30).json()
1110
- obs = resp.get("observation", {})
1111
- rwd = resp.get("reward", {})
1112
- score = float(rwd.get("score", 0.0))
1113
- msg = rwd.get("message","")[:50]
1114
- new_stage = obs.get("current_stage", cur_stage)
1115
- done = obs.get("done", False)
1116
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}"
1117
  except Exception as ex:
1118
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
1119
  new_stage = cur_stage
 
 
 
1120
  yield "\n".join(lines)
1121
 
1122
  if new_stage != cur_stage:
1123
  cur_stage = new_stage
1124
  hist = []
1125
- yield emit(f"\n── Stage {cur_stage}: {LABELS.get(cur_stage,'')} ──────────────────────────────")
1126
-
1127
- msgs.append({"role":"assistant","content":json.dumps(action)})
1128
- nxt = _fmt(obs, rwd)
1129
- if atype == "run_script" and obs.get("last_run_error"):
1130
- nxt += '\n\nError. Fix with edit_script full replacement.'
1131
- elif atype == "run_script" and not obs.get("last_run_error"):
1132
- nxt += '\n\nClean run! Submit now.'
 
1133
  elif atype == "edit_script":
1134
- nxt += '\n\nEdited. Run to verify.'
1135
- msgs.append({"role":"user","content":nxt})
1136
- if len(msgs) > 12: msgs = [msgs[0], msgs[1]] + msgs[-8:]
1137
- if done: break
1138
- _t.sleep(0.3)
 
1139
 
1140
- try:
1141
- status = httpx.get(f"{BASE_URL}/pipeline_status", timeout=10).json()
1142
- t_score = status.get("episode_score", 0.0)
1143
- t_stages = status.get("stages_completed", [])
1144
- except Exception:
1145
- t_score = 0.0
1146
- t_stages = []
1147
- yield emit(f"\n Stages completed : {t_stages}")
1148
- yield emit(f" Episode score : {t_score:.2f}")
1149
- yield emit("\n βœ… LLM Agent complete.")
1150
 
1151
  run_baseline_cmp_btn.click(run_baseline_cmp, inputs=[], outputs=[baseline_cmp_log])
1152
  run_llm_cmp_btn.click(run_llm_cmp, inputs=[], outputs=[llm_cmp_log])
 
840
  # ── COMPARISON TAB ────────────────────────────────────────────────────
841
  with gr.Tab("πŸ“Š Baseline vs LLM Agent"):
842
  gr.Markdown("""### Rule-Based Baseline vs LLM Agent
843
+ Each agent runs on its **own private environment** β€” fully isolated, no shared state.
844
+ - **Baseline**: Hardcoded scripted fixes β€” no reasoning, fails to generalise
845
  - **LLM Agent**: Groq Llama 3.1 8B reasoning dynamically from error logs and script state
846
  """)
847
  with gr.Row():
 
849
  gr.Markdown("#### Rule-Based Baseline")
850
  run_baseline_cmp_btn = gr.Button("β–Ά Run Baseline Agent", elem_classes="btn-reset")
851
  baseline_cmp_log = gr.Textbox(
852
+ label="Baseline Log", lines=28, interactive=False,
853
  placeholder="Click to run baseline agent…", elem_classes="code-box"
854
  )
855
  with gr.Column():
856
  gr.Markdown("#### LLM Agent (Groq Llama 3.1 8B)")
857
  run_llm_cmp_btn = gr.Button("β–Ά Run LLM Agent", elem_classes="btn-primary")
858
  llm_cmp_log = gr.Textbox(
859
+ label="LLM Log", lines=28, interactive=False,
860
  placeholder="Click to run LLM agent…", elem_classes="code-box"
861
  )
862
 
863
+ _ICONS = {"inspect_data":"πŸ”","run_script":"▢️ ","edit_script":"✏️ ","query_actor":"πŸ€–","submit":"πŸ“€"}
864
+
865
+ # ── fixed scripts for the baseline ──────────────────────────────
866
  _S1 = (
867
  "import pandas as pd\n"
868
  "from sklearn.preprocessing import StandardScaler\n"
 
870
  "from sklearn.model_selection import train_test_split\n\n"
871
  "df = df.dropna()\n"
872
  "df['salary'] = df['salary'].clip(upper=df['salary'].quantile(0.99))\n"
873
+ "X = df[['age','salary','credit_score','loan_amount','employment_years']].copy()\n"
874
  "y = df['target']\n"
875
+ "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
876
  "scaler = StandardScaler()\n"
877
+ "X_train = scaler.fit_transform(X_train)\n"
878
+ "X_test = scaler.transform(X_test)\n"
879
  "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
880
  "clf.fit(X_train, y_train)\n"
881
  "print('Accuracy:', clf.score(X_test, y_test))\n"
 
890
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
891
  "scaler = StandardScaler()\n"
892
  "X_train = scaler.fit_transform(X_train)\n"
893
+ "X_test = scaler.transform(X_test)\n"
894
  "clf = MLPClassifier(hidden_layer_sizes=(64,32), max_iter=200, random_state=42)\n"
895
  "clf.fit(X_train, y_train)\n"
896
  "print('Accuracy:', clf.score(X_test, y_test))\n"
 
905
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
906
  "scaler = StandardScaler()\n"
907
  "X_train = scaler.fit_transform(X_train)\n"
908
+ "X_test = scaler.transform(X_test)\n"
909
  "clf = LogisticRegression(max_iter=1000, random_state=42)\n"
910
  "clf.fit(X_train, y_train)\n"
911
  "print('Accuracy:', clf.score(X_test, y_test))\n"
 
920
  "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n"
921
  "scaler = StandardScaler()\n"
922
  "X_train = scaler.fit_transform(X_train)\n"
923
+ "X_test = scaler.transform(X_test)\n"
924
  "clf = LogisticRegression(max_iter=1000, random_state=42, class_weight='balanced')\n"
925
  "clf.fit(X_train, y_train)\n"
926
  "print('Accuracy:', clf.score(X_test, y_test))\n"
927
  )
928
  _BSTAGES = {
929
+ 1: [("inspect_data", {}), ("edit_script", {"script": _S1}), ("run_script", {}), ("submit", {})],
930
+ 2: [("inspect_data", {}), ("edit_script", {"script": _S2}), ("run_script", {}), ("submit", {})],
931
+ 3: [("run_script", {}), ("edit_script", {"script": _S3}), ("run_script", {}), ("submit", {})],
932
+ 4: [("query_actor", {}), ("edit_script", {"script": _S4}), ("run_script", {}), ("submit", {})],
 
 
 
 
 
 
 
 
 
 
 
 
933
  }
 
934
 
935
  def run_baseline_cmp():
936
+ """Baseline uses its own private DataEngEnvironment β€” zero shared state."""
937
  import time as _t
938
+ from app.environment import DataEngEnvironment
939
+ from app.models import Action
940
  lines = []
941
  def emit(l):
942
  lines.append(l)
943
  return "\n".join(lines)
944
+
945
  yield emit("╔══════════════════════════════════════════════════════╗")
946
  yield emit("β•‘ Rule-Based Baseline Agent β•‘")
947
  yield emit("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
948
+
949
+ _env = DataEngEnvironment()
950
+ _env.reset()
951
+ yield emit(" βœ“ Private env initialised β€” Stage 1\n")
952
+
 
 
 
 
953
  step = 0
954
+ LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
955
+ for stage_num, actions in _BSTAGES.items():
956
+ if _env.done:
 
957
  break
958
+ yield emit(f"── Stage {stage_num}: {LABELS[stage_num]} {'─'*(38-len(LABELS[stage_num]))}")
959
+ for atype, payload in actions:
 
 
 
960
  step += 1
 
961
  icon = _ICONS.get(atype, "β–Έ")
962
+ emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ …")
963
  yield "\n".join(lines)
964
  try:
965
+ result = _env.step(Action(action_type=atype, payload=payload))
966
+ rwd = result.reward
967
+ obs = result.observation
968
+ score = float(rwd.score)
969
+ msg = (rwd.message or "")[:65]
970
+ lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}"
 
 
 
 
 
 
971
  except Exception as ex:
972
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
973
  yield "\n".join(lines)
974
+ _t.sleep(0.15)
975
+ if _env.current_stage == stage_num and not _env.done:
976
+ yield emit(f" βœ— Stage {stage_num} did not pass β€” stopping here")
977
+ break
978
+
979
+ yield emit(f"\n Stages completed : {list(_env.stages_completed)}")
980
+ yield emit(f" Episode score : {_env.episode_score:.2f}")
981
+ yield emit(f"\n {'βœ…' if _env.episode_score > 0.5 else '❌'} Baseline complete.")
 
 
 
982
 
983
  def run_llm_cmp():
984
+ """LLM agent uses its own private DataEngEnvironment β€” zero shared state."""
985
  import time as _t, os as _os
986
+ from app.environment import DataEngEnvironment
987
+ from app.models import Action
988
  lines = []
989
  def emit(l):
990
  lines.append(l)
991
  return "\n".join(lines)
992
+
993
  yield emit("╔══════════════════════════════════════════════════════╗")
994
  yield emit("β•‘ LLM Agent (Groq Llama 3.1 8B) β•‘")
995
  yield emit("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
996
+
997
  groq_key = _os.environ.get("GROQ_API_KEY", "")
998
  if not groq_key:
999
+ yield emit("❌ GROQ_API_KEY not set in Space secrets.")
1000
  return
1001
  try:
1002
  from groq import Groq as _Groq
1003
  _client = _Groq(api_key=groq_key)
1004
  except ImportError:
1005
+ yield emit("❌ groq package not installed.")
1006
  return
1007
 
1008
+ _SYS = """You are an expert ML engineer debugging a broken pipeline. Respond ONLY with a single JSON object.
1009
+
1010
+ Available actions:
1011
+ {"action_type": "inspect_data", "payload": {}}
1012
+ {"action_type": "run_script", "payload": {}}
1013
+ {"action_type": "edit_script", "payload": {"script": "<FULL replacement script>"}}
1014
+ {"action_type": "query_actor", "payload": {}}
1015
+ {"action_type": "submit", "payload": {}}
1016
+
1017
+ Rules:
1018
+ - edit_script must replace the ENTIRE script, not a patch
1019
+ - After edit_script always run_script to verify
1020
+ - submit only after run_script shows Accuracy printed with no error
1021
+ - Stage 1 bug: column named 'age_years' should be 'age', also add df.dropna()
1022
+ - Stage 2 bug: StandardScaler must be fit on X_train only (after split)
1023
+ - Stage 3 bug: scaler.fit must happen after train_test_split
1024
+ - Stage 4 bug: LogisticRegression needs class_weight='balanced'"""
1025
 
1026
  def _parse(raw):
1027
  s = raw.find('{')
 
1037
  if "action_type" in a:
1038
  a.setdefault("payload", {})
1039
  return a
1040
+ except Exception:
1041
+ pass
1042
  break
1043
  for t in ["edit_script","run_script","submit","inspect_data","query_actor"]:
1044
+ if t in raw:
1045
+ return {"action_type": t, "payload": {}}
1046
  return {"action_type": "run_script", "payload": {}}
1047
 
1048
+ def _obs_to_prompt(obs):
1049
+ parts = [f"Stage {obs.current_stage} | step {obs.stage_step_number}"]
1050
+ if obs.last_run_error:
1051
+ parts.append(f"\nERROR:\n{obs.last_run_error[:500]}")
1052
+ if obs.last_run_output:
1053
+ parts.append(f"\nOUTPUT:\n{obs.last_run_output[:300]}")
1054
+ if obs.actor_feedback:
1055
+ parts.append(f"\nREVIEWER FEEDBACK:\n{obs.actor_feedback[:300]}")
1056
+ if obs.script_content:
1057
+ parts.append(f"\nCURRENT SCRIPT:\n{obs.script_content[:800]}")
1058
+ return "\n".join(parts)
1059
 
1060
+ _env = DataEngEnvironment()
1061
+ init_obs = _env.reset()
1062
+ yield emit(" βœ“ Private env initialised β€” Stage 1\n")
 
 
 
 
 
 
1063
 
1064
+ LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
1065
+ msgs = [
1066
+ {"role": "system", "content": _SYS},
1067
+ {"role": "user", "content": _obs_to_prompt(init_obs) + "\n\nWhat is your first action?"},
1068
+ ]
1069
  cur_stage = 1
1070
  step = 0
1071
  hist = []
1072
+ yield emit(f"── Stage 1: Data Repair ────────────────────────────")
 
 
1073
 
1074
  for _ in range(60):
1075
+ # ── ask LLM ──────────────────────────────────────────────
1076
  try:
1077
+ ctx = [msgs[0]] + msgs[1:3] + msgs[-6:] if len(msgs) > 9 else msgs
1078
  resp = _client.chat.completions.create(
1079
+ model="llama-3.1-8b-instant", messages=ctx,
1080
+ max_tokens=1800, temperature=0.1)
1081
+ raw = resp.choices[0].message.content.strip()
1082
  action = _parse(raw)
1083
+ except Exception as ex:
1084
  action = {"action_type": "run_script", "payload": {}}
1085
 
1086
+ # ── loop-break heuristics ─────────────────────────────────
1087
  hist.append(action["action_type"])
1088
+ if len(hist) >= 4 and len(set(hist[-4:])) == 1:
1089
+ if hist[-1] == "edit_script":
1090
+ action = {"action_type": "run_script", "payload": {}}
1091
+ elif hist[-1] == "run_script":
1092
+ action = {"action_type": "submit", "payload": {}}
1093
 
1094
  step += 1
1095
  atype = action["action_type"]
1096
+ icon = _ICONS.get(atype, "β–Έ")
1097
+ emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ …")
1098
  yield "\n".join(lines)
 
1099
 
1100
+ # ── execute in private env ────────────────────────────────
1101
  try:
1102
+ result = _env.step(Action(action_type=atype, payload=action.get("payload", {})))
1103
+ obs = result.observation
1104
+ rwd = result.reward
1105
+ score = float(rwd.score)
1106
+ msg = (rwd.message or "")[:60]
1107
+ new_stage = obs.current_stage
1108
+ done = _env.done
1109
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}"
1110
  except Exception as ex:
1111
  lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
1112
  new_stage = cur_stage
1113
+ done = False
1114
+ obs = init_obs
1115
+ rwd = None
1116
  yield "\n".join(lines)
1117
 
1118
  if new_stage != cur_stage:
1119
  cur_stage = new_stage
1120
  hist = []
1121
+ yield emit(f"\n── Stage {cur_stage}: {LABELS.get(cur_stage,'')} {'─'*(36-len(LABELS.get(cur_stage,'')))}─")
1122
+
1123
+ # ── build next prompt ─────────────────────────────────────
1124
+ msgs.append({"role": "assistant", "content": json.dumps(action)})
1125
+ feedback = _obs_to_prompt(obs)
1126
+ if atype == "run_script" and obs.last_run_error:
1127
+ feedback += "\n\nScript errored. Use edit_script with a FULL replacement to fix all bugs."
1128
+ elif atype == "run_script" and not obs.last_run_error:
1129
+ feedback += "\n\nClean run β€” Accuracy printed. Call submit now."
1130
  elif atype == "edit_script":
1131
+ feedback += "\n\nScript replaced. Now call run_script to verify."
1132
+ elif atype == "query_actor":
1133
+ feedback += "\n\nReviewer replied above. Now edit_script to apply the fix, then run_script."
1134
+ msgs.append({"role": "user", "content": feedback})
1135
+ if len(msgs) > 16:
1136
+ msgs = [msgs[0]] + msgs[1:3] + msgs[-10:]
1137
 
1138
+ if done:
1139
+ break
1140
+ _t.sleep(0.2)
1141
+
1142
+ yield emit(f"\n Stages completed : {list(_env.stages_completed)}")
1143
+ yield emit(f" Episode score : {_env.episode_score:.2f}")
1144
+ yield emit(f"\n {'βœ…' if _env.episode_score > 0.5 else '❌'} LLM Agent complete.")
 
 
 
1145
 
1146
  run_baseline_cmp_btn.click(run_baseline_cmp, inputs=[], outputs=[baseline_cmp_log])
1147
  run_llm_cmp_btn.click(run_llm_cmp, inputs=[], outputs=[llm_cmp_log])