Spaces:
Sleeping
Sleeping
| title: Dataclean Env | |
| emoji: π§Ή | |
| colorFrom: blue | |
| colorTo: green | |
| sdk: docker | |
| pinned: false | |
| tags: | |
| - openenv | |
| - data-cleaning | |
| - etl | |
| - real-world | |
| - tabular | |
| - data-drift | |
| - streaming | |
| # DataClean OpenEnv | |
| A **real-world data cleaning and ETL environment** for AI agent RL training, built on the [OpenEnv](https://meta-pytorch.org/OpenEnv/) framework by Meta-PyTorch and Hugging Face. | |
| Agents learn to fix messy tabular data β filling nulls, normalising inconsistent values, removing duplicates, filtering outliers, performing multi-table ETL joins, and handling **live data drift** (novel: fresh dirty rows injected mid-episode every 5 steps). | |
| --- | |
| ## Why This Environment | |
| Data cleaning is one of the most common, time-consuming tasks in real data engineering. Every company with a data pipeline does this daily. Training an RL agent on this task has immediate real-world value β unlike game-based environments. | |
| **What makes this different from other OpenEnv submissions:** | |
| - Task 4 (Data Drift) is genuinely novel β no existing OpenEnv environment simulates live streaming row injection mid-episode | |
| - Parallel baseline using `concurrent.futures.ThreadPoolExecutor` β all 4 tasks run simultaneously | |
| - Deterministic graders using seeded dataset generation β perfectly reproducible scores | |
| - Dense reward signal: reward = grader(new_state) β grader(prev_state) at every step | |
| --- | |
| ## Tasks | |
| | Task | Difficulty | Description | Max Steps | | |
| |------|-----------|-------------|-----------| | |
| | `task1` | Easy | Fix nulls + dtypes in 50-row customer CSV | 10 | | |
| | `task2` | Medium | Dedup + normalize strings + fix dates + fill nulls | 20 | | |
| | `task3` | Hard | Multi-table merge + outlier removal + derived column | 30 | | |
| | `task4_data_drift` | **Expert** | Live streaming table β 7 dirty rows injected every 5 steps | 40 | | |
| --- | |
| ## Action Space | |
| Every action is a JSON object with an `operation` field: | |
| ```json | |
| {"operation": "fill_nulls", "column": "age", "strategy": "median"} | |
| {"operation": "cast_column", "column": "age", "dtype": "int"} | |
| {"operation": "remove_duplicates"} | |
| {"operation": "normalize_values", "column": "country", "method": "upper"} | |
| {"operation": "cast_column", "column": "order_date", "dtype": "datetime"} | |
| {"operation": "filter_outliers", "column": "amount", "method": "iqr", "threshold": 1.5} | |
| {"operation": "merge_tables", "left_table": "orders", "right_table": "customers", "on": "customer_id", "output_table": "merged"} | |
| {"operation": "add_derived_column", "column_name": "order_year", "source_column": "order_date", "transform": "year_from_date", "table_name": "merged"} | |
| {"operation": "submit"} | |
| ``` | |
| ## Observation Space | |
| After each `reset()` / `step()` the agent receives: | |
| - `task_id`, `task_description`, `step_count`, `max_steps`, `message` | |
| - `tables` β dict of `{table_name β JSON string of df.head(10)}` | |
| - `column_dtypes`, `null_counts`, `duplicate_count`, `row_count` | |
| - `schema_errors` β list of detected problems to guide the agent | |
| - `reward`, `done`, `partial_score` β RL signals | |
| --- | |
| ## Reward Function | |
| ``` | |
| step_reward = grader(current_state) β grader(previous_state) # dense delta signal | |
| invalid_op = β0.02 # bad operation penalty | |
| terminal = final grader score on submit or max_steps | |
| ``` | |
| Partial credit per sub-dimension. Score range: `[0.0, 1.0]`. | |
| --- | |
| ## Baseline Scores (llama-3.3-70b-versatile, seed=42, parallel run) | |
| | Task | Score | Time | | |
| |------|-------|------| | |
| | task1 (easy) | 1.0000 | 11.8s | | |
| | task2 (medium) | 1.0000 | 34.1s | | |
| | task3 (hard) | 0.8000 | 22.4s | | |
| | task4_data_drift (expert) | 0.9297 | 26.6s | | |
| | **mean** | **0.9324** | **34.3s wall** | | |
| All 4 tasks run in parallel β wall time = slowest task, not sum. | |
| --- | |
| ## Setup & Local Run | |
| ```bash | |
| pip install -r requirements.txt | |
| uvicorn server.app:app --host 0.0.0.0 --port 7860 --reload | |
| ``` | |
| ```bash | |
| curl http://localhost:7860/health | |
| curl http://localhost:7860/tasks | |
| ``` | |
| ```bash | |
| export OPENAI_API_KEY=your_key | |
| export OPENAI_BASE_URL=https://api.groq.com/openai/v1 | |
| export BASELINE_MODEL=llama-3.3-70b-versatile | |
| python baseline.py | |
| ``` | |
| ## Docker | |
| ```bash | |
| docker build -t dataclean-env . | |
| docker run -p 7860:7860 -e OPENAI_API_KEY=your_key dataclean-env | |
| ``` | |
| --- | |
| ## API Endpoints | |
| | Method | Endpoint | Description | | |
| |--------|----------|-------------| | |
| | POST | `/reset` | Start new episode | | |
| | POST | `/step` | Execute one cleaning operation | | |
| | GET | `/state` | Current episode metadata | | |
| | GET | `/tasks` | All tasks + action schema | | |
| | GET | `/grader` | Score current episode state | | |
| | GET | `/baseline` | Run baseline agent on all tasks | | |
| | GET | `/health` | Liveness probe | | |
| | GET | `/docs` | Interactive Swagger UI | | |
| --- | |
| ## Project Structure | |
| ``` | |
| dataCleaningProject/ | |
| βββ server/ | |
| β βββ app.py # FastAPI server β all endpoints | |
| β βββ environment.py # Core env logic β reset/step/state + drift injection | |
| β βββ graders.py # Deterministic scoring for all 4 tasks | |
| β βββ dataset_factory.py # Seeded dirty+expected dataset generation + drift batches | |
| βββ models.py # Pydantic Action + Observation models | |
| βββ baseline.py # Parallel baseline (ThreadPoolExecutor, 4 tasks at once) | |
| βββ client.py # HTTP client | |
| βββ openenv.yaml # OpenEnv manifest | |
| βββ Dockerfile # Port 7860, Python 3.11-slim | |
| βββ requirements.txt | |
| ``` | |