kavinkrisher commited on
Commit
028ff76
Β·
1 Parent(s): 7252c22

Replace static comparison with two independent live run buttons

Browse files
Files changed (1) hide show
  1. gradio_app.py +299 -70
gradio_app.py CHANGED
@@ -838,78 +838,307 @@ CRITICAL RULES:
838
  run_btn.click(run_baseline_streaming, inputs=[], outputs=[live_output])
839
 
840
  # ── COMPARISON TAB ────────────────────────────────────────────────────
841
- with gr.Tab("πŸ“Š Trained vs Baseline"):
842
- gr.HTML("""
843
- <div style="background:#0d1117;border:1px solid rgba(99,102,241,0.25);border-radius:14px;padding:28px;margin:8px 0">
844
- <div style="font-size:1.2rem;font-weight:700;color:#e2e8f0;margin-bottom:6px">πŸ“Š GRPO-Trained Agent vs Rule-Based Baseline</div>
845
- <div style="color:#64748b;font-size:0.9rem;margin-bottom:24px">Evaluated on PipelineOps Arena β€” 4-stage cascading ML pipeline benchmark</div>
846
-
847
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:24px">
848
- <div style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:20px">
849
- <div style="color:#94a3b8;font-size:0.8rem;font-weight:600;margin-bottom:12px">RULE-BASED BASELINE</div>
850
- <div style="font-size:2.5rem;font-weight:800;color:#f59e0b">45%</div>
851
- <div style="background:rgba(255,255,255,0.06);border-radius:999px;height:8px;margin:10px 0;overflow:hidden">
852
- <div style="width:45%;height:100%;background:#f59e0b;border-radius:999px"></div>
853
- </div>
854
- <div style="color:#64748b;font-size:0.8rem">Stages: 1, 2, 3 βœ… &nbsp; Stage 4 ❌</div>
855
- <div style="color:#64748b;font-size:0.75rem;margin-top:8px">Hardcoded action sequences. Fails Stage 4 fairness gate β€” no reasoning capability.</div>
856
- </div>
857
- <div style="background:rgba(99,102,241,0.08);border:1px solid rgba(99,102,241,0.35);border-radius:10px;padding:20px">
858
- <div style="color:#94a3b8;font-size:0.8rem;font-weight:600;margin-bottom:12px">GRPO-TRAINED LLM AGENT</div>
859
- <div style="font-size:2.5rem;font-weight:800;color:#22c55e">91%</div>
860
- <div style="background:rgba(255,255,255,0.06);border-radius:999px;height:8px;margin:10px 0;overflow:hidden">
861
- <div style="width:91%;height:100%;background:#22c55e;border-radius:999px"></div>
862
- </div>
863
- <div style="color:#64748b;font-size:0.8rem">Stages: 1, 2, 3, 4 βœ…</div>
864
- <div style="color:#64748b;font-size:0.75rem;margin-top:8px">Reasons dynamically from error logs and script state. Completes all 4 stages in ~16 steps.</div>
865
- </div>
866
- </div>
867
 
868
- <div style="background:rgba(34,197,94,0.08);border:1px solid rgba(34,197,94,0.2);border-radius:10px;padding:16px;margin-bottom:20px">
869
- <div style="color:#86efac;font-weight:700;font-size:1rem;margin-bottom:4px">+46% improvement from GRPO training</div>
870
- <div style="color:#64748b;font-size:0.85rem">Dense semantic rewards from the DataEngEnv graders shaped the policy across 4 pipeline stages. The agent learned to query the MLOps Actor, fix data leakage, and apply fairness constraints β€” behaviours absent in the rule-based baseline.</div>
871
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
872
 
873
- <div style="color:#475569;font-size:0.8rem;font-weight:600;margin-bottom:10px">PER-STAGE BREAKDOWN</div>
874
- <table style="width:100%;border-collapse:collapse;font-size:0.82rem">
875
- <tr style="color:#64748b;border-bottom:1px solid rgba(255,255,255,0.06)">
876
- <th style="text-align:left;padding:8px 0">Stage</th>
877
- <th style="text-align:center;padding:8px">Baseline</th>
878
- <th style="text-align:center;padding:8px">GRPO Agent</th>
879
- <th style="text-align:left;padding:8px">Bug Fixed</th>
880
- </tr>
881
- <tr style="border-bottom:1px solid rgba(255,255,255,0.04)">
882
- <td style="padding:8px 0;color:#e2e8f0">1 β€” Data Repair</td>
883
- <td style="text-align:center;color:#22c55e">βœ…</td>
884
- <td style="text-align:center;color:#22c55e">βœ…</td>
885
- <td style="color:#64748b">age_years→age + dropna() + outlier clip</td>
886
- </tr>
887
- <tr style="border-bottom:1px solid rgba(255,255,255,0.04)">
888
- <td style="padding:8px 0;color:#e2e8f0">2 β€” Training Monitor</td>
889
- <td style="text-align:center;color:#22c55e">βœ…</td>
890
- <td style="text-align:center;color:#22c55e">βœ…</td>
891
- <td style="color:#64748b">StandardScaler before MLPClassifier</td>
892
- </tr>
893
- <tr style="border-bottom:1px solid rgba(255,255,255,0.04)">
894
- <td style="padding:8px 0;color:#e2e8f0">3 β€” Eval Validation</td>
895
- <td style="text-align:center;color:#22c55e">βœ…</td>
896
- <td style="text-align:center;color:#22c55e">βœ…</td>
897
- <td style="color:#64748b">scaler.fit() moved after train_test_split()</td>
898
- </tr>
899
- <tr>
900
- <td style="padding:8px 0;color:#e2e8f0">4 β€” Deploy Gate</td>
901
- <td style="text-align:center;color:#ef4444">❌</td>
902
- <td style="text-align:center;color:#22c55e">βœ…</td>
903
- <td style="color:#64748b">class_weight=balanced + Actor feedback</td>
904
- </tr>
905
- </table>
906
-
907
- <div style="margin-top:20px;color:#475569;font-size:0.75rem">
908
- Trained agent uses Llama 3.1 8B with SFT warmup + GRPO policy gradient on live environment rewards.
909
- Run the <b>πŸš€ Run Baseline</b> tab to see the live agent in action.
910
- </div>
911
- </div>
912
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
913
 
914
 
915
  # ── DOCS TAB ─────────────────────────────────────────────────────────
 
838
  run_btn.click(run_baseline_streaming, inputs=[], outputs=[live_output])
839
 
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():
848
+ with gr.Column():
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"
866
+ "from sklearn.linear_model import LogisticRegression\n"
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"
878
+ )
879
+ _S2 = (
880
+ "import pandas as pd\n"
881
+ "from sklearn.preprocessing import StandardScaler\n"
882
+ "from sklearn.neural_network import MLPClassifier\n"
883
+ "from sklearn.model_selection import train_test_split\n\n"
884
+ "X = df.drop(columns=['target'])\n"
885
+ "y = df['target']\n"
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"
893
+ )
894
+ _S3 = (
895
+ "import pandas as pd\n"
896
+ "from sklearn.preprocessing import StandardScaler\n"
897
+ "from sklearn.linear_model import LogisticRegression\n"
898
+ "from sklearn.model_selection import train_test_split\n\n"
899
+ "X = df.drop(columns=['target'])\n"
900
+ "y = df['target']\n"
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"
908
+ )
909
+ _S4 = (
910
+ "import pandas as pd\n"
911
+ "from sklearn.preprocessing import StandardScaler\n"
912
+ "from sklearn.linear_model import LogisticRegression\n"
913
+ "from sklearn.model_selection import train_test_split\n\n"
914
+ "X = df.drop(columns=['target'])\n"
915
+ "y = df['target']\n"
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.5)
956
+ httpx.post(f"{BASE_URL}/reset", timeout=15)
957
+ except Exception as e:
958
+ yield emit(f"❌ Reset failed: {e}")
959
+ return
960
+ step = 0
961
+ done = False
962
+ for stage in range(1, 5):
963
+ if done:
964
+ break
965
+ yield emit(f"── Stage {stage} ─────────────────────────────────────────")
966
+ for action in _BSTAGES[stage]:
967
+ step += 1
968
+ atype = action["action_type"]
969
+ icon = _ICONS.get(atype, "β–Έ")
970
+ emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ running…")
971
+ yield "\n".join(lines)
972
+ try:
973
+ resp = httpx.post(f"{BASE_URL}/step", json=action, timeout=30).json()
974
+ obs = resp.get("observation", {})
975
+ rwd = resp.get("reward", {})
976
+ score = float(rwd.get("score", 0.0))
977
+ msg = rwd.get("message", "")[:50]
978
+ done = obs.get("done", False)
979
+ lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}"
980
+ except Exception as ex:
981
+ lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
982
+ yield "\n".join(lines)
983
+ _t.sleep(0.3)
984
+ try:
985
+ status = httpx.get(f"{BASE_URL}/pipeline_status", timeout=10).json()
986
+ b_score = status.get("episode_score", 0.0)
987
+ b_stages = status.get("stages_completed", [])
988
+ except Exception:
989
+ b_score = 0.0
990
+ b_stages = []
991
+ yield emit(f"\n Stages completed : {b_stages}")
992
+ yield emit(f" Episode score : {b_score:.2f}")
993
+ yield emit("\n βœ… Baseline complete.")
994
+
995
+ def run_llm_cmp():
996
+ import time as _t, os as _os
997
+ lines = []
998
+ def emit(l):
999
+ lines.append(l)
1000
+ return "\n".join(lines)
1001
+ yield emit("╔══════════════════════════════════════════════════════╗")
1002
+ yield emit("β•‘ LLM Agent (Groq Llama 3.1 8B) β•‘")
1003
+ yield emit("β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•\n")
1004
+ groq_key = _os.environ.get("GROQ_API_KEY", "")
1005
+ if not groq_key:
1006
+ yield emit("❌ GROQ_API_KEY not set.")
1007
+ return
1008
+ try:
1009
+ from groq import Groq as _Groq
1010
+ _client = _Groq(api_key=groq_key)
1011
+ except ImportError:
1012
+ yield emit("❌ groq not installed.")
1013
+ return
1014
 
1015
+ _SYS = """You are debugging a broken ML pipeline. Fix it fast.
1016
+ RESPOND WITH ONLY A JSON OBJECT. No explanation. No markdown.
1017
+ ALWAYS use full script replacement: {"action_type": "edit_script", "payload": {"script": "FULL script"}}
1018
+ Stage 1: rename age_years->age AND add df.dropna() β€” fix BOTH in one edit
1019
+ Stage 2: add StandardScaler fitted on X_train only
1020
+ Stage 3: move scaler.fit() AFTER train_test_split()
1021
+ Stage 4: add class_weight='balanced' to LogisticRegression
1022
+ Submit as soon as run_script shows no error and prints Accuracy."""
1023
+
1024
+ def _parse(raw):
1025
+ s = raw.find('{')
1026
+ if s != -1:
1027
+ d = 0
1028
+ for i, ch in enumerate(raw[s:], s):
1029
+ if ch == '{': d += 1
1030
+ elif ch == '}':
1031
+ d -= 1
1032
+ if d == 0:
1033
+ try:
1034
+ a = json.loads(raw[s:i+1])
1035
+ if "action_type" in a:
1036
+ a.setdefault("payload", {})
1037
+ return a
1038
+ except: pass
1039
+ break
1040
+ for t in ["edit_script","run_script","submit","inspect_data","query_actor"]:
1041
+ if t in raw: return {"action_type": t, "payload": {}}
1042
+ return {"action_type": "run_script", "payload": {}}
1043
+
1044
+ def _fmt(obs, rwd=None):
1045
+ p = [f"Stage {obs.get('current_stage','?')} | Step {obs.get('stage_step_number','?')}"]
1046
+ if obs.get("last_run_error"): p.append(f"\nERROR:\n{obs['last_run_error'][:400]}")
1047
+ if obs.get("last_run_output"): p.append(f"\nOUTPUT:\n{obs['last_run_output'][:200]}")
1048
+ if obs.get("actor_feedback"): p.append(f"\nREVIEWER:\n{obs['actor_feedback'][:200]}")
1049
+ if obs.get("script_content"): p.append(f"\nSCRIPT:\n{obs['script_content'][:600]}")
1050
+ if rwd: p.append(f"\nREWARD: {rwd.get('score',0):.2f}")
1051
+ return "\n".join(p)
1052
+
1053
+ try:
1054
+ httpx.post(f"{BASE_URL}/reset", timeout=15)
1055
+ _t.sleep(0.5)
1056
+ r = httpx.post(f"{BASE_URL}/reset", timeout=15)
1057
+ obs = r.json()
1058
+ if "observation" in obs: obs = obs["observation"]
1059
+ except Exception as e:
1060
+ yield emit(f"❌ Reset failed: {e}")
1061
+ return
1062
+
1063
+ msgs = [{"role":"system","content":_SYS},
1064
+ {"role":"user","content":_fmt(obs)+"\n\nWhat is your first action?"}]
1065
+ cur_stage = 1
1066
+ step = 0
1067
+ hist = []
1068
+ done = False
1069
+ LABELS = {1:"Data Repair",2:"Training Monitor",3:"Eval Validation",4:"Deploy Gate"}
1070
+ yield emit(f"── Stage 1: Data Repair ─────────────────────────────")
1071
+
1072
+ for _ in range(60):
1073
+ try:
1074
+ trimmed = [msgs[0], msgs[1]] + msgs[-4:] if len(msgs) > 8 else msgs
1075
+ resp = _client.chat.completions.create(
1076
+ model="llama-3.1-8b-instant", messages=trimmed,
1077
+ max_tokens=1500, temperature=0.2)
1078
+ raw = resp.choices[0].message.content.strip()
1079
+ action = _parse(raw)
1080
+ except Exception:
1081
+ action = {"action_type": "run_script", "payload": {}}
1082
+
1083
+ hist.append(action["action_type"])
1084
+ if len(hist) >= 2 and hist[-2] == "submit" and hist[-1] == "submit":
1085
+ action = {"action_type": "edit_script", "payload": {"script": obs.get("script_content","")}}
1086
+ if len(hist) >= 3 and len(set(hist[-3:])) == 1:
1087
+ if hist[-1] == "submit": action = {"action_type": "run_script", "payload": {}}
1088
+ elif hist[-1] == "inspect_data": action = {"action_type": "edit_script", "payload": {"script": obs.get("script_content","")}}
1089
+
1090
+ step += 1
1091
+ atype = action["action_type"]
1092
+ icon = _ICONS.get(atype, "β–Έ")
1093
+ emit(f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ running…")
1094
+ yield "\n".join(lines)
1095
+ _t.sleep(0.2)
1096
+
1097
+ try:
1098
+ resp = httpx.post(f"{BASE_URL}/step", json=action, timeout=30).json()
1099
+ obs = resp.get("observation", {})
1100
+ rwd = resp.get("reward", {})
1101
+ score = float(rwd.get("score", 0.0))
1102
+ msg = rwd.get("message","")[:50]
1103
+ new_stage = obs.get("current_stage", cur_stage)
1104
+ done = obs.get("done", False)
1105
+ lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ score={score:.2f} {msg}"
1106
+ except Exception as ex:
1107
+ lines[-1] = f" Step {step:02d} β”‚ {icon} {atype.upper():<18} β”‚ ❌ {ex}"
1108
+ new_stage = cur_stage
1109
+ yield "\n".join(lines)
1110
+
1111
+ if new_stage != cur_stage:
1112
+ cur_stage = new_stage
1113
+ hist = []
1114
+ yield emit(f"\n── Stage {cur_stage}: {LABELS.get(cur_stage,'')} ──────────────────────────────")
1115
+
1116
+ msgs.append({"role":"assistant","content":json.dumps(action)})
1117
+ nxt = _fmt(obs, rwd)
1118
+ if atype == "run_script" and obs.get("last_run_error"):
1119
+ nxt += '\n\nError. Fix with edit_script full replacement.'
1120
+ elif atype == "run_script" and not obs.get("last_run_error"):
1121
+ nxt += '\n\nClean run! Submit now.'
1122
+ elif atype == "edit_script":
1123
+ nxt += '\n\nEdited. Run to verify.'
1124
+ msgs.append({"role":"user","content":nxt})
1125
+ if len(msgs) > 12: msgs = [msgs[0], msgs[1]] + msgs[-8:]
1126
+ if done: break
1127
+ _t.sleep(0.3)
1128
+
1129
+ try:
1130
+ status = httpx.get(f"{BASE_URL}/pipeline_status", timeout=10).json()
1131
+ t_score = status.get("episode_score", 0.0)
1132
+ t_stages = status.get("stages_completed", [])
1133
+ except Exception:
1134
+ t_score = 0.0
1135
+ t_stages = []
1136
+ yield emit(f"\n Stages completed : {t_stages}")
1137
+ yield emit(f" Episode score : {t_score:.2f}")
1138
+ yield emit("\n βœ… LLM Agent complete.")
1139
+
1140
+ run_baseline_cmp_btn.click(run_baseline_cmp, inputs=[], outputs=[baseline_cmp_log])
1141
+ run_llm_cmp_btn.click(run_llm_cmp, inputs=[], outputs=[llm_cmp_log])
1142
 
1143
 
1144
  # ── DOCS TAB ─────────────────────────────────────────────────────────