Tarkeshwar Claude Opus 4.6 (1M context) commited on
Commit
c2ad419
·
1 Parent(s): bb2fc43

Improve inference agent for better scores

Browse files

- Increase MAX_TOKENS from 150 to 300 for more complex responses
- Two-phase strategy: auto-inspect all columns first, then LLM fixes
- Better system prompt with explicit validation rules and deletion strategy
- Increase message history from 20 to 40 to preserve context on hard task
- Preserve initial inspection summary in conversation context

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files changed (2) hide show
  1. data_clean_env/inference.py +72 -33
  2. inference.py +70 -27
data_clean_env/inference.py CHANGED
@@ -14,6 +14,7 @@ MANDATORY
14
  import json
15
  import os
16
  import re
 
17
  import textwrap
18
  from typing import Any, Dict, List
19
 
@@ -28,7 +29,7 @@ MODEL_NAME = os.getenv("MODEL_NAME", "")
28
  ENV_URL = os.getenv("ENV_URL", "http://localhost:8000")
29
 
30
  TEMPERATURE = 0.0
31
- MAX_TOKENS = 150
32
 
33
  TASKS = ["customer_contacts", "sales_records", "employee_records"]
34
 
@@ -36,8 +37,8 @@ TASKS = ["customer_contacts", "sales_records", "employee_records"]
36
  # System prompt
37
  # ---------------------------------------------------------------------------
38
  SYSTEM_PROMPT = textwrap.dedent("""\
39
- You are a data quality analyst. You are given a dataset with known issues.
40
- Your job is to find and fix all data quality problems.
41
 
42
  Available commands (respond with EXACTLY ONE command per turn, no explanation):
43
  inspect("column_name") — View column statistics and issues
@@ -45,28 +46,31 @@ SYSTEM_PROMPT = textwrap.dedent("""\
45
  delete(row_index) — Remove a duplicate/invalid row
46
  submit() — Finalize and get your score
47
 
48
- Strategy:
49
- 1. Start by inspecting columns to understand the data and find issues
50
- 2. Fix issues one by one using the fix() command
51
- 3. For duplicate rows, use delete() on the duplicate (usually the later row)
52
- 4. After fixing all issues, use submit()
53
-
54
- Rules for fixes:
55
- - Emails: must be user@domain.tld format
56
- - Phone numbers: digits and dashes only, at least 10 digits
57
- - Dates: must be YYYY-MM-DD format and valid
58
- - Empty/missing values: provide a reasonable non-empty value
59
- - Negative numbers (quantity/price): make them positive
60
- - Outliers: fix to a reasonable value within the stated range
61
- - Inconsistent names: use the exact canonical form stated in the task
62
- - Excess whitespace: trim and remove double spaces
63
- - Manager IDs: must reference an existing employee ID in the dataset
 
 
64
  - Performance scores: must be within 0.0-10.0
65
  - Salaries: must be within $20,000-$500,000
66
- - Termination dates: must be after hire date (or empty if active)
 
67
 
68
  IMPORTANT: After deleting a row, all subsequent row indices shift down by 1.
69
- Account for this when fixing or deleting later rows.
70
 
71
  Respond with ONLY the command. No explanation, no markdown, no extra text.
72
  """)
@@ -93,7 +97,7 @@ def env_step(command: str) -> Dict[str, Any]:
93
  """Execute a command in the environment."""
94
  resp = requests.post(
95
  f"{ENV_URL}/step",
96
- json={"command": command},
97
  timeout=30,
98
  )
99
  resp.raise_for_status()
@@ -152,10 +156,8 @@ def extract_action(response_text: str) -> str:
152
  # Strip "action:" prefix
153
  line = re.sub(r"^(?:action|next action)\s*[:\-]\s*", "", line, flags=re.IGNORECASE)
154
  if ACTION_RE.search(line):
155
- # Extract just the action
156
  m = ACTION_RE.search(line)
157
  start = m.start()
158
- # Find matching closing paren
159
  depth = 0
160
  for i in range(start, len(line)):
161
  if line[i] == "(":
@@ -164,10 +166,8 @@ def extract_action(response_text: str) -> str:
164
  depth -= 1
165
  if depth == 0:
166
  return line[start : i + 1]
167
- # No matching paren — return what we have plus )
168
  return line[start:] + ")"
169
 
170
- # Fallback: return submit
171
  return "submit()"
172
 
173
 
@@ -184,22 +184,58 @@ def main() -> None:
184
  print(f"{'=' * 60}")
185
 
186
  obs = env_reset(task_id)
187
- # The response might have observation nested or flat
188
  if "observation" in obs:
189
  obs = obs["observation"]
190
 
191
  done = obs.get("done", False)
192
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
193
 
 
 
 
 
 
 
 
 
 
 
 
194
  step_num = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
195
  while not done:
196
  step_num += 1
197
- user_msg = format_observation(obs, step_num)
198
- messages.append({"role": "user", "content": user_msg})
199
 
200
- # Keep conversation manageable — trim if too long
201
- if len(messages) > 20:
202
- messages = [messages[0]] + messages[-18:]
203
 
204
  try:
205
  completion = client.chat.completions.create(
@@ -226,9 +262,12 @@ def main() -> None:
226
  score = obs.get("current_score", 0.0)
227
 
228
  if done:
229
- reward = obs.get("reward", score)
230
  print(f" Done! Score: {score:.4f}")
231
  results[task_id] = score
 
 
 
 
232
 
233
  print(f" Final score for {task_id}: {results.get(task_id, 0.0):.4f}")
234
 
 
14
  import json
15
  import os
16
  import re
17
+ import sys
18
  import textwrap
19
  from typing import Any, Dict, List
20
 
 
29
  ENV_URL = os.getenv("ENV_URL", "http://localhost:8000")
30
 
31
  TEMPERATURE = 0.0
32
+ MAX_TOKENS = 300
33
 
34
  TASKS = ["customer_contacts", "sales_records", "employee_records"]
35
 
 
37
  # System prompt
38
  # ---------------------------------------------------------------------------
39
  SYSTEM_PROMPT = textwrap.dedent("""\
40
+ You are an expert data quality analyst. You are given a dataset with known issues.
41
+ Your job is to find and fix ALL data quality problems efficiently.
42
 
43
  Available commands (respond with EXACTLY ONE command per turn, no explanation):
44
  inspect("column_name") — View column statistics and issues
 
46
  delete(row_index) — Remove a duplicate/invalid row
47
  submit() — Finalize and get your score
48
 
49
+ STRATEGY (follow this order strictly):
50
+ 1. You will first receive inspection results for all columns read them carefully
51
+ 2. Look at the data table and identify ALL issues based on the inspection hints
52
+ 3. Fix ALL issues using fix() commands, one at a time
53
+ 4. Delete duplicate rows LAST (after all fixes), from highest row index to lowest
54
+ (this avoids row index shifting problems)
55
+ 5. Only call submit() when you believe ALL issues are fixed
56
+
57
+ VALIDATION RULES:
58
+ - Emails: must match user@domain.tld (no [at], no @@, no spaces)
59
+ - Phone numbers: digits, dashes, parens, spaces only; at least 10 digits
60
+ - Dates: YYYY-MM-DD format (e.g., 2024-03-25), must be valid calendar date
61
+ - Empty/missing values: provide a reasonable non-empty value matching context
62
+ - Negative numbers (quantity/price): make them positive (use absolute value)
63
+ - Price/salary outliers: fix to a reasonable value within the stated range
64
+ - Inconsistent region/department names: use the EXACT canonical form from the task description
65
+ - Excess whitespace: trim leading/trailing spaces, collapse double spaces to single
66
+ - Manager IDs: must reference an existing employee emp_id in the dataset
67
  - Performance scores: must be within 0.0-10.0
68
  - Salaries: must be within $20,000-$500,000
69
+ - Termination dates: must be AFTER hire date, or leave empty if employee is active
70
+ - Duplicate rows: rows that are exact or near-exact copies — delete the LATER one
71
 
72
  IMPORTANT: After deleting a row, all subsequent row indices shift down by 1.
73
+ Always delete from highest index to lowest to avoid this issue.
74
 
75
  Respond with ONLY the command. No explanation, no markdown, no extra text.
76
  """)
 
97
  """Execute a command in the environment."""
98
  resp = requests.post(
99
  f"{ENV_URL}/step",
100
+ json={"action": {"command": command}},
101
  timeout=30,
102
  )
103
  resp.raise_for_status()
 
156
  # Strip "action:" prefix
157
  line = re.sub(r"^(?:action|next action)\s*[:\-]\s*", "", line, flags=re.IGNORECASE)
158
  if ACTION_RE.search(line):
 
159
  m = ACTION_RE.search(line)
160
  start = m.start()
 
161
  depth = 0
162
  for i in range(start, len(line)):
163
  if line[i] == "(":
 
166
  depth -= 1
167
  if depth == 0:
168
  return line[start : i + 1]
 
169
  return line[start:] + ")"
170
 
 
171
  return "submit()"
172
 
173
 
 
184
  print(f"{'=' * 60}")
185
 
186
  obs = env_reset(task_id)
 
187
  if "observation" in obs:
188
  obs = obs["observation"]
189
 
190
  done = obs.get("done", False)
191
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
192
 
193
+ # --- Phase 1: Auto-inspect all columns ---
194
+ columns = []
195
+ col_info = obs.get("column_info", "")
196
+ for line in col_info.strip().splitlines():
197
+ line = line.strip()
198
+ if ":" in line:
199
+ col_name = line.split(":")[0].strip()
200
+ if col_name:
201
+ columns.append(col_name)
202
+
203
+ inspection_results = []
204
  step_num = 0
205
+ for col in columns:
206
+ if done:
207
+ break
208
+ step_num += 1
209
+ cmd = f'inspect("{col}")'
210
+ print(f" Step {step_num}: {cmd}")
211
+ obs = env_step(cmd)
212
+ if "observation" in obs:
213
+ obs = obs["observation"]
214
+ done = obs.get("done", False)
215
+ feedback = obs.get("feedback", "")
216
+ inspection_results.append(f"[{col}]: {feedback}")
217
+
218
+ if done:
219
+ score = obs.get("current_score", 0.0)
220
+ print(f" Done during inspection! Score: {score:.4f}")
221
+ results[task_id] = score
222
+ continue
223
+
224
+ # Build a summary of all inspections for the LLM
225
+ inspection_summary = "\n\n".join(inspection_results)
226
+ initial_context = format_observation(obs, step_num)
227
+ initial_context += f"\n\n--- INSPECTION SUMMARY (all columns) ---\n{inspection_summary}"
228
+ initial_context += "\n\n--- NOW FIX ALL ISSUES. Do fixes first, then deletions (highest index first). ---"
229
+
230
+ messages.append({"role": "user", "content": initial_context})
231
+
232
+ # --- Phase 2: LLM-driven fixes ---
233
  while not done:
234
  step_num += 1
 
 
235
 
236
+ # Keep conversation manageable — but preserve system + initial context
237
+ if len(messages) > 40:
238
+ messages = [messages[0], messages[1]] + messages[-36:]
239
 
240
  try:
241
  completion = client.chat.completions.create(
 
262
  score = obs.get("current_score", 0.0)
263
 
264
  if done:
 
265
  print(f" Done! Score: {score:.4f}")
266
  results[task_id] = score
267
+ else:
268
+ # Send updated observation as next user message
269
+ user_msg = format_observation(obs, step_num)
270
+ messages.append({"role": "user", "content": user_msg})
271
 
272
  print(f" Final score for {task_id}: {results.get(task_id, 0.0):.4f}")
273
 
inference.py CHANGED
@@ -29,7 +29,7 @@ MODEL_NAME = os.getenv("MODEL_NAME", "")
29
  ENV_URL = os.getenv("ENV_URL", "http://localhost:8000")
30
 
31
  TEMPERATURE = 0.0
32
- MAX_TOKENS = 150
33
 
34
  TASKS = ["customer_contacts", "sales_records", "employee_records"]
35
 
@@ -37,8 +37,8 @@ TASKS = ["customer_contacts", "sales_records", "employee_records"]
37
  # System prompt
38
  # ---------------------------------------------------------------------------
39
  SYSTEM_PROMPT = textwrap.dedent("""\
40
- You are a data quality analyst. You are given a dataset with known issues.
41
- Your job is to find and fix all data quality problems.
42
 
43
  Available commands (respond with EXACTLY ONE command per turn, no explanation):
44
  inspect("column_name") — View column statistics and issues
@@ -46,28 +46,31 @@ SYSTEM_PROMPT = textwrap.dedent("""\
46
  delete(row_index) — Remove a duplicate/invalid row
47
  submit() — Finalize and get your score
48
 
49
- Strategy:
50
- 1. Start by inspecting columns to understand the data and find issues
51
- 2. Fix issues one by one using the fix() command
52
- 3. For duplicate rows, use delete() on the duplicate (usually the later row)
53
- 4. After fixing all issues, use submit()
54
-
55
- Rules for fixes:
56
- - Emails: must be user@domain.tld format
57
- - Phone numbers: digits and dashes only, at least 10 digits
58
- - Dates: must be YYYY-MM-DD format and valid
59
- - Empty/missing values: provide a reasonable non-empty value
60
- - Negative numbers (quantity/price): make them positive
61
- - Outliers: fix to a reasonable value within the stated range
62
- - Inconsistent names: use the exact canonical form stated in the task
63
- - Excess whitespace: trim and remove double spaces
64
- - Manager IDs: must reference an existing employee ID in the dataset
 
 
65
  - Performance scores: must be within 0.0-10.0
66
  - Salaries: must be within $20,000-$500,000
67
- - Termination dates: must be after hire date (or empty if active)
 
68
 
69
  IMPORTANT: After deleting a row, all subsequent row indices shift down by 1.
70
- Account for this when fixing or deleting later rows.
71
 
72
  Respond with ONLY the command. No explanation, no markdown, no extra text.
73
  """)
@@ -187,15 +190,52 @@ def main() -> None:
187
  done = obs.get("done", False)
188
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
189
 
 
 
 
 
 
 
 
 
 
 
 
190
  step_num = 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  while not done:
192
  step_num += 1
193
- user_msg = format_observation(obs, step_num)
194
- messages.append({"role": "user", "content": user_msg})
195
 
196
- # Keep conversation manageable
197
- if len(messages) > 20:
198
- messages = [messages[0]] + messages[-18:]
199
 
200
  try:
201
  completion = client.chat.completions.create(
@@ -222,9 +262,12 @@ def main() -> None:
222
  score = obs.get("current_score", 0.0)
223
 
224
  if done:
225
- reward = obs.get("reward", score)
226
  print(f" Done! Score: {score:.4f}")
227
  results[task_id] = score
 
 
 
 
228
 
229
  print(f" Final score for {task_id}: {results.get(task_id, 0.0):.4f}")
230
 
 
29
  ENV_URL = os.getenv("ENV_URL", "http://localhost:8000")
30
 
31
  TEMPERATURE = 0.0
32
+ MAX_TOKENS = 300
33
 
34
  TASKS = ["customer_contacts", "sales_records", "employee_records"]
35
 
 
37
  # System prompt
38
  # ---------------------------------------------------------------------------
39
  SYSTEM_PROMPT = textwrap.dedent("""\
40
+ You are an expert data quality analyst. You are given a dataset with known issues.
41
+ Your job is to find and fix ALL data quality problems efficiently.
42
 
43
  Available commands (respond with EXACTLY ONE command per turn, no explanation):
44
  inspect("column_name") — View column statistics and issues
 
46
  delete(row_index) — Remove a duplicate/invalid row
47
  submit() — Finalize and get your score
48
 
49
+ STRATEGY (follow this order strictly):
50
+ 1. You will first receive inspection results for all columns read them carefully
51
+ 2. Look at the data table and identify ALL issues based on the inspection hints
52
+ 3. Fix ALL issues using fix() commands, one at a time
53
+ 4. Delete duplicate rows LAST (after all fixes), from highest row index to lowest
54
+ (this avoids row index shifting problems)
55
+ 5. Only call submit() when you believe ALL issues are fixed
56
+
57
+ VALIDATION RULES:
58
+ - Emails: must match user@domain.tld (no [at], no @@, no spaces)
59
+ - Phone numbers: digits, dashes, parens, spaces only; at least 10 digits
60
+ - Dates: YYYY-MM-DD format (e.g., 2024-03-25), must be valid calendar date
61
+ - Empty/missing values: provide a reasonable non-empty value matching context
62
+ - Negative numbers (quantity/price): make them positive (use absolute value)
63
+ - Price/salary outliers: fix to a reasonable value within the stated range
64
+ - Inconsistent region/department names: use the EXACT canonical form from the task description
65
+ - Excess whitespace: trim leading/trailing spaces, collapse double spaces to single
66
+ - Manager IDs: must reference an existing employee emp_id in the dataset
67
  - Performance scores: must be within 0.0-10.0
68
  - Salaries: must be within $20,000-$500,000
69
+ - Termination dates: must be AFTER hire date, or leave empty if employee is active
70
+ - Duplicate rows: rows that are exact or near-exact copies — delete the LATER one
71
 
72
  IMPORTANT: After deleting a row, all subsequent row indices shift down by 1.
73
+ Always delete from highest index to lowest to avoid this issue.
74
 
75
  Respond with ONLY the command. No explanation, no markdown, no extra text.
76
  """)
 
190
  done = obs.get("done", False)
191
  messages = [{"role": "system", "content": SYSTEM_PROMPT}]
192
 
193
+ # --- Phase 1: Auto-inspect all columns ---
194
+ columns = []
195
+ col_info = obs.get("column_info", "")
196
+ for line in col_info.strip().splitlines():
197
+ line = line.strip()
198
+ if ":" in line:
199
+ col_name = line.split(":")[0].strip()
200
+ if col_name:
201
+ columns.append(col_name)
202
+
203
+ inspection_results = []
204
  step_num = 0
205
+ for col in columns:
206
+ if done:
207
+ break
208
+ step_num += 1
209
+ cmd = f'inspect("{col}")'
210
+ print(f" Step {step_num}: {cmd}")
211
+ obs = env_step(cmd)
212
+ if "observation" in obs:
213
+ obs = obs["observation"]
214
+ done = obs.get("done", False)
215
+ feedback = obs.get("feedback", "")
216
+ inspection_results.append(f"[{col}]: {feedback}")
217
+
218
+ if done:
219
+ score = obs.get("current_score", 0.0)
220
+ print(f" Done during inspection! Score: {score:.4f}")
221
+ results[task_id] = score
222
+ continue
223
+
224
+ # Build a summary of all inspections for the LLM
225
+ inspection_summary = "\n\n".join(inspection_results)
226
+ initial_context = format_observation(obs, step_num)
227
+ initial_context += f"\n\n--- INSPECTION SUMMARY (all columns) ---\n{inspection_summary}"
228
+ initial_context += "\n\n--- NOW FIX ALL ISSUES. Do fixes first, then deletions (highest index first). ---"
229
+
230
+ messages.append({"role": "user", "content": initial_context})
231
+
232
+ # --- Phase 2: LLM-driven fixes ---
233
  while not done:
234
  step_num += 1
 
 
235
 
236
+ # Keep conversation manageable — but preserve system + initial context
237
+ if len(messages) > 40:
238
+ messages = [messages[0], messages[1]] + messages[-36:]
239
 
240
  try:
241
  completion = client.chat.completions.create(
 
262
  score = obs.get("current_score", 0.0)
263
 
264
  if done:
 
265
  print(f" Done! Score: {score:.4f}")
266
  results[task_id] = score
267
+ else:
268
+ # Send updated observation as next user message
269
+ user_msg = format_observation(obs, step_num)
270
+ messages.append({"role": "user", "content": user_msg})
271
 
272
  print(f" Final score for {task_id}: {results.get(task_id, 0.0):.4f}")
273