CoBeDigger commited on
Commit
2cf9a43
·
1 Parent(s): a3d500f

fix: anti-loop logic and guided prompts for LLM agent

Browse files
Files changed (1) hide show
  1. baseline/run_baseline.py +116 -63
baseline/run_baseline.py CHANGED
@@ -10,84 +10,84 @@ import os, time, json, re, requests
10
  BASE_URL = os.environ.get("DATAENGENV_URL", "http://localhost:8000")
11
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
12
 
13
- SYSTEM_PROMPT = """You are a senior Data Engineer debugging a broken ML pipeline.
14
- The pipeline has 4 stages. You must fix each stage by submitting a correct fix.
15
 
16
- You will receive observations containing:
17
- - script_content: the current Python script
18
- - last_run_error: any error from the last run
19
- - last_run_output: stdout from the last run
20
- - data_preview: dataset info (columns, nulls, stats)
21
- - actor_feedback: feedback from the MLOps reviewer bot
22
 
23
- Available actions (respond with ONLY valid JSON, nothing else):
24
 
25
- 1. Inspect data first:
26
- {"action_type": "inspect_data", "payload": {}}
 
 
 
 
27
 
28
- 2. Check column schema:
29
- {"action_type": "check_schema", "payload": {}}
 
 
 
 
 
30
 
31
- 3. Run the script to see errors/output:
32
- {"action_type": "run_script", "payload": {}}
33
-
34
- 4. Edit the script (find and replace):
35
- {"action_type": "edit_script", "payload": {"old": "exact text to find", "new": "replacement text"}}
36
-
37
- 5. Ask the MLOps reviewer for feedback:
38
- {"action_type": "query_actor", "payload": {}}
39
-
40
- 6. Submit when you believe the fix is correct:
41
- {"action_type": "submit", "payload": {}}
42
-
43
- STRATEGY:
44
- - Start by inspecting data OR running the script to see the error
45
- - Read the error carefully and diagnose the root cause
46
- - Use edit_script to fix the bug (the "old" text must EXACTLY match the script)
47
- - Run the script again to verify your fix works
48
- - Submit once the script runs without errors
49
-
50
- IMPORTANT: Respond with ONLY a JSON object. No explanation, no markdown, no extra text."""
51
 
52
 
53
  def parse_action(raw_text: str) -> dict:
54
  """Extract a JSON action from the LLM's response."""
55
- # Try to find JSON in the response
56
- match = re.search(r'\{[^{}]*\}', raw_text, re.DOTALL)
57
  if match:
58
  try:
59
- action = json.loads(match.group())
 
 
 
60
  if "action_type" in action:
 
 
61
  return action
62
  except json.JSONDecodeError:
63
  pass
64
- # Fallback
65
- return {"action_type": "inspect_data", "payload": {}}
 
 
 
 
 
66
 
67
 
68
  def format_observation(obs: dict, reward: dict = None) -> str:
69
- """Format the environment observation into a readable prompt for the LLM."""
70
- parts = []
71
- parts.append(f"Current Stage: {obs.get('current_stage', '?')}")
72
- parts.append(f"Step: {obs.get('stage_step_number', '?')}")
73
 
74
- if obs.get("last_run_error"):
75
- parts.append(f"\nERROR:\n{str(obs['last_run_error'])[:500]}")
 
76
 
77
- if obs.get("last_run_output"):
78
- parts.append(f"\nOUTPUT:\n{str(obs['last_run_output'])[:500]}")
 
79
 
80
- if obs.get("data_preview"):
81
- parts.append(f"\nDATA PREVIEW:\n{str(obs['data_preview'])[:600]}")
 
82
 
83
- if obs.get("actor_feedback"):
84
- parts.append(f"\nREVIEWER FEEDBACK:\n{obs['actor_feedback'][:400]}")
 
85
 
86
- if obs.get("script_content"):
87
- parts.append(f"\nCURRENT SCRIPT:\n{obs['script_content'][:1000]}")
 
88
 
89
  if reward:
90
- parts.append(f"\nREWARD: {reward.get('score', 0)} — {reward.get('message', '')}")
91
 
92
  return "\n".join(parts)
93
 
@@ -102,10 +102,15 @@ def run_episode():
102
  obs = reset_resp if "current_stage" in reset_resp else reset_resp.get("observation", reset_resp)
103
 
104
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
105
- messages.append({"role": "user", "content": format_observation(obs)})
 
 
 
106
 
107
  total_steps = 0
108
  final_score = 0.0
 
 
109
 
110
  print(f"\n{'='*60}")
111
  print(f" PIPELINEOPS ARENA — LLM AGENT (Llama 3.1 8B)")
@@ -114,19 +119,58 @@ def run_episode():
114
  for step in range(60):
115
  # Ask the LLM what to do
116
  try:
117
- # Keep context manageable — system + last 6 messages
118
- trimmed = [messages[0]] + messages[-6:]
 
 
 
 
119
  response = client.chat.completions.create(
120
  model="llama-3.1-8b-instant",
121
  messages=trimmed,
122
- max_tokens=300,
123
- temperature=0.1,
124
  )
125
  raw = response.choices[0].message.content.strip()
126
  action = parse_action(raw)
127
  except Exception as e:
128
  print(f" LLM error: {e}")
129
- action = {"action_type": "inspect_data", "payload": {}}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  # Take the action
132
  try:
@@ -135,15 +179,24 @@ def run_episode():
135
  reward = result.get("reward", {})
136
  total_steps += 1
137
 
138
- # Print what happened
139
  stage = obs.get("current_stage", "?")
140
  score = reward.get("score", 0)
141
  msg = reward.get("message", "")
142
  print(f" Step {total_steps:02d} | Stage {stage} | {action['action_type']:<15} | reward={score:.2f} | {msg[:50]}")
143
 
144
- # Add to conversation
145
  messages.append({"role": "assistant", "content": json.dumps(action)})
146
- messages.append({"role": "user", "content": format_observation(obs, reward)})
 
 
 
 
 
 
 
 
 
 
147
 
148
  if obs.get("done", False):
149
  final_score = reward.get("score", 0.0)
@@ -173,7 +226,7 @@ def run_episode():
173
 
174
 
175
  if __name__ == "__main__":
176
- print("Running PipelineOps Arena — LLM Agent (no hardcoded answers)...")
177
  results = run_episode()
178
 
179
  with open("baseline/results.json", "w") as f:
 
10
  BASE_URL = os.environ.get("DATAENGENV_URL", "http://localhost:8000")
11
  GROQ_API_KEY = os.environ.get("GROQ_API_KEY", "")
12
 
13
+ SYSTEM_PROMPT = """You are a senior Data Engineer. A broken ML pipeline must be fixed.
 
14
 
15
+ You will receive the script, error logs, and data info. You must fix the bug.
 
 
 
 
 
16
 
17
+ RESPOND WITH ONLY A JSON OBJECT. No explanation. No markdown.
18
 
19
+ Actions:
20
+ {"action_type": "inspect_data", "payload": {}}
21
+ {"action_type": "run_script", "payload": {}}
22
+ {"action_type": "edit_script", "payload": {"old": "exact text in script", "new": "replacement"}}
23
+ {"action_type": "query_actor", "payload": {}}
24
+ {"action_type": "submit", "payload": {}}
25
 
26
+ CRITICAL RULES:
27
+ - Use inspect_data ONLY ONCE per stage, then move on
28
+ - After inspecting, use edit_script to fix the bug
29
+ - The "old" field must contain text that EXACTLY exists in the script
30
+ - After editing, use run_script to verify
31
+ - After a clean run, use submit
32
+ - NEVER repeat the same action twice in a row
33
 
34
+ Common fixes:
35
+ - KeyError 'age_years': the column was renamed to 'age'. Replace 'age_years' with 'age'. Also add df.dropna(inplace=True)
36
+ - Training diverges / loss NaN: features are unscaled. Add StandardScaler before the classifier
37
+ - Suspiciously high accuracy (0.98+): data leakage. Move scaler.fit() to AFTER train_test_split()
38
+ - Fairness rejection: add class_weight='balanced' to the classifier"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
 
41
  def parse_action(raw_text: str) -> dict:
42
  """Extract a JSON action from the LLM's response."""
43
+ match = re.search(r'\{[^{}]*("payload"\s*:\s*\{[^{}]*\})?[^{}]*\}', raw_text, re.DOTALL)
 
44
  if match:
45
  try:
46
+ # Clean up common LLM mistakes
47
+ cleaned = match.group()
48
+ cleaned = cleaned.replace("'", '"')
49
+ action = json.loads(cleaned)
50
  if "action_type" in action:
51
+ if "payload" not in action:
52
+ action["payload"] = {}
53
  return action
54
  except json.JSONDecodeError:
55
  pass
56
+
57
+ # Try simpler extraction
58
+ for atype in ["edit_script", "run_script", "submit", "inspect_data", "query_actor"]:
59
+ if atype in raw_text:
60
+ return {"action_type": atype, "payload": {}}
61
+
62
+ return {"action_type": "run_script", "payload": {}}
63
 
64
 
65
  def format_observation(obs: dict, reward: dict = None) -> str:
66
+ """Format observation concisely for the LLM."""
67
+ parts = [f"Stage {obs.get('current_stage', '?')} | Step {obs.get('stage_step_number', '?')}"]
 
 
68
 
69
+ error = obs.get("last_run_error")
70
+ if error:
71
+ parts.append(f"\nERROR:\n{str(error)[:400]}")
72
 
73
+ output = obs.get("last_run_output")
74
+ if output:
75
+ parts.append(f"\nOUTPUT:\n{str(output)[:300]}")
76
 
77
+ preview = obs.get("data_preview")
78
+ if preview:
79
+ parts.append(f"\nDATA:\n{str(preview)[:400]}")
80
 
81
+ feedback = obs.get("actor_feedback")
82
+ if feedback:
83
+ parts.append(f"\nREVIEWER:\n{str(feedback)[:300]}")
84
 
85
+ script = obs.get("script_content")
86
+ if script:
87
+ parts.append(f"\nSCRIPT:\n{script[:800]}")
88
 
89
  if reward:
90
+ parts.append(f"\nREWARD: {reward.get('score', 0):.2f} | {reward.get('message', '')}")
91
 
92
  return "\n".join(parts)
93
 
 
102
  obs = reset_resp if "current_stage" in reset_resp else reset_resp.get("observation", reset_resp)
103
 
104
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
105
+
106
+ initial_prompt = format_observation(obs)
107
+ initial_prompt += "\n\nStart by inspecting the data, then fix the bug. What is your first action?"
108
+ messages.append({"role": "user", "content": initial_prompt})
109
 
110
  total_steps = 0
111
  final_score = 0.0
112
+ last_action_type = None
113
+ repeat_count = 0
114
 
115
  print(f"\n{'='*60}")
116
  print(f" PIPELINEOPS ARENA — LLM AGENT (Llama 3.1 8B)")
 
119
  for step in range(60):
120
  # Ask the LLM what to do
121
  try:
122
+ # Keep context manageable — system + first + last 4 messages
123
+ if len(messages) > 8:
124
+ trimmed = [messages[0], messages[1]] + messages[-4:]
125
+ else:
126
+ trimmed = messages
127
+
128
  response = client.chat.completions.create(
129
  model="llama-3.1-8b-instant",
130
  messages=trimmed,
131
+ max_tokens=400,
132
+ temperature=0.2,
133
  )
134
  raw = response.choices[0].message.content.strip()
135
  action = parse_action(raw)
136
  except Exception as e:
137
  print(f" LLM error: {e}")
138
+ action = {"action_type": "run_script", "payload": {}}
139
+
140
+ # Anti-loop: if repeating same action, force progression
141
+ if action["action_type"] == last_action_type:
142
+ repeat_count += 1
143
+ if repeat_count >= 2:
144
+ if last_action_type == "inspect_data":
145
+ # Already inspected, try running the script
146
+ action = {"action_type": "run_script", "payload": {}}
147
+ elif last_action_type == "run_script":
148
+ # Already ran, need to edit
149
+ # Give LLM one more chance with explicit instruction
150
+ messages.append({"role": "user", "content":
151
+ "STOP repeating. You must use edit_script now to fix the bug. "
152
+ "Look at the error and the script. Find the exact text to replace. "
153
+ "Respond with: {\"action_type\": \"edit_script\", \"payload\": {\"old\": \"...\", \"new\": \"...\"}}"
154
+ })
155
+ try:
156
+ response = client.chat.completions.create(
157
+ model="llama-3.1-8b-instant",
158
+ messages=[messages[0]] + messages[-3:],
159
+ max_tokens=400,
160
+ temperature=0.3,
161
+ )
162
+ raw = response.choices[0].message.content.strip()
163
+ action = parse_action(raw)
164
+ except:
165
+ action = {"action_type": "submit", "payload": {}}
166
+ elif last_action_type == "edit_script":
167
+ action = {"action_type": "run_script", "payload": {}}
168
+ else:
169
+ action = {"action_type": "submit", "payload": {}}
170
+ repeat_count = 0
171
+ else:
172
+ repeat_count = 0
173
+ last_action_type = action["action_type"]
174
 
175
  # Take the action
176
  try:
 
179
  reward = result.get("reward", {})
180
  total_steps += 1
181
 
 
182
  stage = obs.get("current_stage", "?")
183
  score = reward.get("score", 0)
184
  msg = reward.get("message", "")
185
  print(f" Step {total_steps:02d} | Stage {stage} | {action['action_type']:<15} | reward={score:.2f} | {msg[:50]}")
186
 
187
+ # Build next prompt based on what just happened
188
  messages.append({"role": "assistant", "content": json.dumps(action)})
189
+
190
+ next_prompt = format_observation(obs, reward)
191
+ if action["action_type"] == "inspect_data":
192
+ next_prompt += "\n\nYou have inspected the data. Now use edit_script to fix the bug. What exact text in the script needs to change?"
193
+ elif action["action_type"] == "run_script" and obs.get("last_run_error"):
194
+ next_prompt += "\n\nThe script has an error. Use edit_script to fix it. The 'old' field must match text exactly from the SCRIPT above."
195
+ elif action["action_type"] == "run_script" and not obs.get("last_run_error"):
196
+ next_prompt += "\n\nScript ran successfully. Use submit to submit your fix."
197
+ elif action["action_type"] == "edit_script":
198
+ next_prompt += "\n\nScript edited. Now use run_script to verify the fix works."
199
+ messages.append({"role": "user", "content": next_prompt})
200
 
201
  if obs.get("done", False):
202
  final_score = reward.get("score", 0.0)
 
226
 
227
 
228
  if __name__ == "__main__":
229
+ print("Running PipelineOps Arena — LLM Agent...")
230
  results = run_episode()
231
 
232
  with open("baseline/results.json", "w") as f: