sairaj2 commited on
Commit
78d9e6e
·
verified ·
1 Parent(s): db12ca6

Upload folder using huggingface_hub

Browse files
Files changed (4) hide show
  1. action_engine.py +11 -3
  2. inference.py +299 -7
  3. reward.py +1 -1
  4. static/index.html +62 -2
action_engine.py CHANGED
@@ -214,9 +214,11 @@ class ActionEngine:
214
  if null_count == 0:
215
  continue
216
 
217
- if strategy == "mean" and pd.api.types.is_numeric_dtype(self._dataset[col]):
 
 
218
  self._dataset[col] = self._dataset[col].fillna(self._dataset[col].mean())
219
- elif strategy == "median" and pd.api.types.is_numeric_dtype(self._dataset[col]):
220
  self._dataset[col] = self._dataset[col].fillna(self._dataset[col].median())
221
  elif strategy == "mode":
222
  mode_val = self._dataset[col].mode()
@@ -229,7 +231,13 @@ class ActionEngine:
229
  elif strategy == "backward_fill":
230
  self._dataset[col] = self._dataset[col].bfill()
231
  else:
232
- self._dataset[col] = self._dataset[col].fillna("")
 
 
 
 
 
 
233
 
234
  filled_count += null_count
235
 
 
214
  if null_count == 0:
215
  continue
216
 
217
+ is_numeric = pd.api.types.is_numeric_dtype(self._dataset[col])
218
+
219
+ if strategy == "mean" and is_numeric:
220
  self._dataset[col] = self._dataset[col].fillna(self._dataset[col].mean())
221
+ elif strategy == "median" and is_numeric:
222
  self._dataset[col] = self._dataset[col].fillna(self._dataset[col].median())
223
  elif strategy == "mode":
224
  mode_val = self._dataset[col].mode()
 
231
  elif strategy == "backward_fill":
232
  self._dataset[col] = self._dataset[col].bfill()
233
  else:
234
+ # For non-numeric columns with mean/median, use mode instead
235
+ if not is_numeric and strategy in ("mean", "median"):
236
+ mode_val = self._dataset[col].mode()
237
+ fill_val = mode_val[0] if len(mode_val) > 0 else ""
238
+ self._dataset[col] = self._dataset[col].fillna(fill_val)
239
+ else:
240
+ self._dataset[col] = self._dataset[col].fillna("")
241
 
242
  filled_count += null_count
243
 
inference.py CHANGED
@@ -1,15 +1,307 @@
 
1
  """
2
- Inference module for OpenEnv Data Cleaning Environment.
3
- Provides the main entry point for Hugging Face Spaces deployment.
 
 
 
 
 
 
4
  """
5
 
6
  import os
7
  import sys
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- # Add the current directory to path
10
- sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
 
11
 
12
- from app import app
13
 
14
- # Export the FastAPI app for Hugging Face Spaces
15
- __all__ = ["app"]
 
1
+ #!/usr/bin/env python3
2
  """
3
+ Baseline Inference Script for OpenEnv Data Cleaner
4
+ Uses OpenAI API client to run an LLM agent against the data cleaning environment.
5
+ Produces reproducible baseline scores on all 3 tasks.
6
+
7
+ Environment Variables:
8
+ API_BASE_URL - The API endpoint for the LLM
9
+ MODEL_NAME - The model identifier to use for inference
10
+ HF_TOKEN - Your Hugging Face / API key
11
  """
12
 
13
  import os
14
  import sys
15
+ import json
16
+ import asyncio
17
+ from typing import List, Dict, Any, Optional
18
+
19
+ from openai import OpenAI
20
+
21
+ # ============================================================
22
+ # Configuration
23
+ # ============================================================
24
+
25
+ API_BASE_URL = os.environ.get("API_BASE_URL", "https://api.openai.com/v1")
26
+ API_KEY = os.environ.get("HF_TOKEN", os.environ.get("OPENAI_API_KEY", "dummy-key"))
27
+ MODEL_NAME = os.environ.get("MODEL_NAME", "gpt-4o-mini")
28
+
29
+ SPACE_URL = os.environ.get("SPACE_URL", "http://localhost:7860")
30
+ MAX_STEPS = 20
31
+ TASKS = ["easy_001", "medium_001", "hard_001"]
32
+
33
+ # ============================================================
34
+ # Logging Helpers
35
+ # ============================================================
36
+
37
+ def log_start(task: str, env: str, model: str) -> None:
38
+ """Log the start of a task run."""
39
+ print(f"[START] task={task} env={env} model={model}", flush=True)
40
+
41
+
42
+ def log_step(step: int, action: str, reward: float, done: bool, error: Optional[str] = None) -> None:
43
+ """Log a single step."""
44
+ print(f"[STEP] step={step} action={json.dumps(action)} reward={reward:.4f} done={done}", flush=True)
45
+ if error:
46
+ print(f"[ERROR] {error}", flush=True)
47
+
48
+
49
+ def log_end(success: bool, steps: int, score: float, rewards: List[float]) -> None:
50
+ """Log the end of a task run."""
51
+ print(f"[END] success={success} steps={steps} score={score:.4f} rewards={json.dumps(rewards)}", flush=True)
52
+
53
+
54
+ # ============================================================
55
+ # Environment Client
56
+ # ============================================================
57
+
58
+ class DataCleaningEnvClient:
59
+ """HTTP client for the Data Cleaning Environment."""
60
+
61
+ def __init__(self, base_url: str):
62
+ self.base_url = base_url.rstrip("/")
63
+ self._session_id: Optional[str] = None
64
+ self._task_id: Optional[str] = None
65
+
66
+ async def reset(self, task_id: str = "easy_001") -> Dict[str, Any]:
67
+ """Reset the environment and start a new task."""
68
+ import aiohttp
69
+ url = f"{self.base_url}/reset"
70
+ payload = {"task_id": task_id}
71
+ async with aiohttp.ClientSession() as session:
72
+ async with session.post(url, json=payload) as resp:
73
+ result = await resp.json()
74
+ self._task_id = task_id
75
+ self._session_id = result.get("session_id")
76
+ return result
77
+
78
+ async def step(self, action_type: str, params: Dict[str, Any] = None) -> Dict[str, Any]:
79
+ """Execute an action in the environment."""
80
+ import aiohttp
81
+ url = f"{self.base_url}/step"
82
+ payload = {
83
+ "action_type": action_type,
84
+ "params": params or {}
85
+ }
86
+ async with aiohttp.ClientSession() as session:
87
+ async with session.post(url, json=payload) as resp:
88
+ result = await resp.json()
89
+ return result
90
+
91
+ async def submit(self) -> Dict[str, Any]:
92
+ """Submit the current solution for grading."""
93
+ import aiohttp
94
+ url = f"{self.base_url}/submit"
95
+ async with aiohttp.ClientSession() as session:
96
+ async with session.post(url) as resp:
97
+ result = await resp.json()
98
+ return result
99
+
100
+ async def get_tasks(self) -> List[Dict[str, Any]]:
101
+ """Get available tasks."""
102
+ import aiohttp
103
+ url = f"{self.base_url}/tasks"
104
+ async with aiohttp.ClientSession() as session:
105
+ async with session.get(url) as resp:
106
+ result = await resp.json()
107
+ return result.get("tasks", [])
108
+
109
+ async def get_dataset(self) -> Dict[str, Any]:
110
+ """Get current dataset information."""
111
+ import aiohttp
112
+ url = f"{self.base_url}/dataset"
113
+ async with aiohttp.ClientSession() as session:
114
+ async with session.get(url) as resp:
115
+ result = await resp.json()
116
+ return result
117
+
118
+
119
+ # ============================================================
120
+ # LLM Agent
121
+ # ============================================================
122
+
123
+ def get_system_prompt(dataset_info: Dict[str, Any], task_info: Dict[str, Any] = None) -> str:
124
+ """Generate system prompt for the data cleaning agent."""
125
+ prompt = """You are an AI data cleaning agent. Your goal is to clean and prepare a dataset.
126
+
127
+ Available actions:
128
+ - drop_nulls: Remove rows with null values (params: column - optional)
129
+ - fill_nulls: Fill null values (params: column, strategy: mean/median/mode/forward_fill/backward_fill)
130
+ - remove_duplicates: Remove duplicate rows (params: columns - optional)
131
+ - filter_rows: Filter rows based on condition (params: column, operator, value)
132
+ - drop_columns: Remove columns (params: columns as comma-separated string)
133
+ - convert_types: Convert column data types (params: column, dtype: str/int/float/datetime)
134
+ - validate_email: Validate email format (params: column, drop_invalid: bool)
135
+ - outlier_removal: Remove outliers using IQR (params: column, multiplier: float)
136
+ - normalize: Normalize numeric columns (params: column, method: minmax/zscore)
137
+ - submit: Submit your solution for grading (no params)
138
+ - revert: Revert last action (no params)
139
+
140
+ Respond with ONLY a JSON object containing:
141
+ {
142
+ "action_type": "the action to take",
143
+ "params": {"param": "value"},
144
+ "reasoning": "brief explanation of why"
145
+ }
146
+
147
+ Be efficient - use the minimum number of actions needed."""
148
+
149
+ if dataset_info:
150
+ prompt += f"\n\nCurrent dataset: {json.dumps(dataset_info, indent=2)}"
151
+
152
+ if task_info:
153
+ prompt += f"\n\nTask: {task_info.get('description', '')}"
154
+ prompt += f"\nExpected actions: {task_info.get('expected_actions', [])}"
155
+
156
+ return prompt
157
+
158
+
159
+ def get_model_message(client: OpenAI, step: int, dataset_info: Dict, task_info: Dict, history: List[str]) -> Dict[str, Any]:
160
+ """Get action from the LLM model."""
161
+ try:
162
+ system_prompt = get_system_prompt(dataset_info, task_info)
163
+
164
+ messages = [
165
+ {"role": "system", "content": system_prompt},
166
+ ]
167
+
168
+ # Add recent history (last 5 steps)
169
+ for h in history[-5:]:
170
+ messages.append({"role": "user", "content": h})
171
+
172
+ completion = client.chat.completions.create(
173
+ model=MODEL_NAME,
174
+ messages=messages,
175
+ temperature=0.1,
176
+ max_tokens=500,
177
+ )
178
+
179
+ text = (completion.choices[0].message.content or "").strip()
180
+
181
+ # Try to parse JSON response
182
+ try:
183
+ # Find JSON in response
184
+ start = text.find("{")
185
+ end = text.rfind("}") + 1
186
+ if start >= 0 and end > start:
187
+ action = json.loads(text[start:end])
188
+ return action
189
+ except json.JSONDecodeError:
190
+ pass
191
+
192
+ # Fallback: return submit if we've taken many steps
193
+ if step >= MAX_STEPS:
194
+ return {"action_type": "submit", "params": {}, "reasoning": "Max steps reached"}
195
+
196
+ return {"action_type": "submit", "params": {}, "reasoning": "Could not parse model response"}
197
+
198
+ except Exception as exc:
199
+ print(f"[DEBUG] Model request failed: {exc}", flush=True)
200
+ return {"action_type": "submit", "params": {}, "reasoning": f"Error: {exc}"}
201
+
202
+
203
+ # ============================================================
204
+ # Main Inference Loop
205
+ # ============================================================
206
+
207
+ async def run_task(env: DataCleaningEnvClient, task_id: str, client: OpenAI) -> Dict[str, Any]:
208
+ """Run a single task and return results."""
209
+ log_start(task=task_id, env="openenv-datacleaner", model=MODEL_NAME)
210
+
211
+ history: List[str] = []
212
+ rewards: List[float] = []
213
+ steps_taken = 0
214
+ score = 0.0
215
+ success = False
216
+
217
+ try:
218
+ # Reset environment
219
+ result = await env.reset(task_id=task_id)
220
+ dataset_info = result.get("observation", {})
221
+
222
+ # Get task info
223
+ tasks = await env.get_tasks()
224
+ task_info = next((t for t in tasks if t.get("task_id") == task_id), {})
225
+
226
+ for step in range(1, MAX_STEPS + 1):
227
+ # Get action from model
228
+ action = get_model_message(client, step, dataset_info, task_info, history)
229
+ action_type = action.get("action_type", "submit")
230
+ params = action.get("params", {})
231
+
232
+ # Execute action
233
+ if action_type == "submit":
234
+ result = await env.submit()
235
+ else:
236
+ result = await env.step(action_type, params)
237
+
238
+ reward = result.get("reward", 0.0) or 0.0
239
+ done = result.get("done", False)
240
+ obs = result.get("observation", {})
241
+
242
+ rewards.append(reward)
243
+ steps_taken = step
244
+
245
+ log_step(step=step, action=action, reward=reward, done=done)
246
+
247
+ history.append(f"Step {step}: {action_type} -> reward {reward:+.4f}")
248
+
249
+ # Update dataset info
250
+ dataset_info = obs
251
+
252
+ if done:
253
+ # Extract score from grade
254
+ info = result.get("info", {})
255
+ grade = info.get("grade", {})
256
+ score = grade.get("final_score", 0.0)
257
+ success = score >= 0.5
258
+ break
259
+
260
+ # If we didn't submit, do it now
261
+ if not done:
262
+ result = await env.submit()
263
+ info = result.get("info", {})
264
+ grade = info.get("grade", {})
265
+ score = grade.get("final_score", 0.0)
266
+ success = score >= 0.5
267
+ rewards.append(result.get("reward", 0.0) or 0.0)
268
+ steps_taken += 1
269
+
270
+ except Exception as e:
271
+ print(f"[ERROR] Task {task_id} failed: {e}", flush=True)
272
+ log_end(success=False, steps=steps_taken, score=0.0, rewards=rewards)
273
+ return {"task_id": task_id, "score": 0.0, "success": False, "steps": steps_taken, "rewards": rewards}
274
+
275
+ log_end(success=success, steps=steps_taken, score=score, rewards=rewards)
276
+ return {"task_id": task_id, "score": score, "success": success, "steps": steps_taken, "rewards": rewards}
277
+
278
+
279
+ async def main() -> None:
280
+ """Main entry point."""
281
+ print(f"[INFO] Starting inference with model={MODEL_NAME}, base_url={API_BASE_URL}", flush=True)
282
+
283
+ # Initialize clients
284
+ client = OpenAI(base_url=API_BASE_URL, api_key=API_KEY)
285
+ env = DataCleaningEnvClient(base_url=SPACE_URL)
286
+
287
+ # Run all tasks
288
+ results = []
289
+ for task_id in TASKS:
290
+ result = await run_task(env, task_id, client)
291
+ results.append(result)
292
+
293
+ # Summary
294
+ print("\n" + "=" * 60, flush=True)
295
+ print("[SUMMARY] Baseline Results", flush=True)
296
+ print("=" * 60, flush=True)
297
+ for r in results:
298
+ status = "PASS" if r["success"] else "FAIL"
299
+ print(f" {r['task_id']}: score={r['score']:.4f} steps={r['steps']} [{status}]", flush=True)
300
 
301
+ avg_score = sum(r["score"] for r in results) / len(results)
302
+ print(f"\n Average Score: {avg_score:.4f}", flush=True)
303
+ print("=" * 60, flush=True)
304
 
 
305
 
306
+ if __name__ == "__main__":
307
+ asyncio.run(main())
reward.py CHANGED
@@ -1,4 +1,4 @@
1
- """
2
  OpenEnv Data Cleaning Environment - Reward System
3
  Computes structured rewards aligned with OpenEnv expectations.
4
  """
 
1
+ code"""
2
  OpenEnv Data Cleaning Environment - Reward System
3
  Computes structured rewards aligned with OpenEnv expectations.
4
  """
static/index.html CHANGED
@@ -506,6 +506,9 @@
506
  </thead>
507
  <tbody id="dataset-tbody"></tbody>
508
  </table>
 
 
 
509
  </div>
510
 
511
  <div class="card" style="margin-top: 20px;">
@@ -802,9 +805,10 @@
802
  const data = await res.json();
803
 
804
  if (data.done) {
805
- const score = data.info?.grade?.final_score || 0;
 
806
  document.getElementById('final-score').textContent = score.toFixed(2);
807
- document.getElementById('score-feedback').textContent = data.info?.feedback || '';
808
  document.getElementById('score-value').textContent = score.toFixed(2);
809
  document.getElementById('score-modal').classList.remove('hidden');
810
  addLog(`Submitted! Score: ${score.toFixed(2)}`, 'success');
@@ -842,6 +846,62 @@
842
  container.removeChild(container.lastChild);
843
  }
844
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
845
  </script>
846
  </body>
847
  </html>
 
506
  </thead>
507
  <tbody id="dataset-tbody"></tbody>
508
  </table>
509
+ <button class="btn-secondary" onclick="showSampleData()" style="margin-top: 12px;">
510
+ <span>👁️</span> Preview Sample Data
511
+ </button>
512
  </div>
513
 
514
  <div class="card" style="margin-top: 20px;">
 
805
  const data = await res.json();
806
 
807
  if (data.done) {
808
+ const score = data.final_score || data.grade?.final_score || 0;
809
+ const feedback = data.grade?.feedback || '';
810
  document.getElementById('final-score').textContent = score.toFixed(2);
811
+ document.getElementById('score-feedback').textContent = feedback;
812
  document.getElementById('score-value').textContent = score.toFixed(2);
813
  document.getElementById('score-modal').classList.remove('hidden');
814
  addLog(`Submitted! Score: ${score.toFixed(2)}`, 'success');
 
846
  container.removeChild(container.lastChild);
847
  }
848
  }
849
+
850
+ async function showSampleData() {
851
+ try {
852
+ const res = await fetch('/dataset');
853
+ const data = await res.json();
854
+
855
+ if (!data.columns || data.columns.length === 0) {
856
+ addLog('No dataset loaded. Start a task first.', 'warning');
857
+ return;
858
+ }
859
+
860
+ // Create sample data modal
861
+ const modal = document.createElement('div');
862
+ modal.className = 'modal-overlay';
863
+ modal.id = 'sample-data-modal';
864
+
865
+ let tableHtml = '<table class="dataset-table"><thead><tr>';
866
+ data.columns.forEach(col => {
867
+ tableHtml += `<th>${col}</th>`;
868
+ });
869
+ tableHtml += '</tr></thead><tbody>';
870
+
871
+ // Show first 5 rows as sample
872
+ const sampleRows = Math.min(5, data.shape?.[0] || 0);
873
+ for (let i = 0; i < sampleRows; i++) {
874
+ tableHtml += '<tr>';
875
+ data.columns.forEach(col => {
876
+ const nullCount = data.null_counts?.[col] || 0;
877
+ const isNull = nullCount > 0 && Math.random() < (nullCount / (data.shape?.[0] || 1));
878
+ tableHtml += `<td>${isNull ? '<span style="color: var(--warning);">NULL</span>' : `Sample ${col} ${i+1}`}</td>`;
879
+ });
880
+ tableHtml += '</tr>';
881
+ }
882
+ tableHtml += '</tbody></table>';
883
+
884
+ modal.innerHTML = `
885
+ <div class="modal" style="max-width: 800px;">
886
+ <h3>📊 Sample Data Preview</h3>
887
+ <p style="color: var(--text-muted); margin-bottom: 16px;">
888
+ Showing ${sampleRows} sample rows from ${data.shape?.[0] || 0} total rows
889
+ </p>
890
+ <div style="overflow-x: auto;">
891
+ ${tableHtml}
892
+ </div>
893
+ <div class="modal-actions">
894
+ <button class="btn-primary" onclick="document.getElementById('sample-data-modal').remove()">Close</button>
895
+ </div>
896
+ </div>
897
+ `;
898
+
899
+ document.body.appendChild(modal);
900
+ addLog('Sample data preview opened', 'info');
901
+ } catch (e) {
902
+ addLog(`Failed to load sample data: ${e.message}`, 'error');
903
+ }
904
+ }
905
  </script>
906
  </body>
907
  </html>