satyamshahi/etl_debugger_env
ETLDebugger
An OpenEnv-compliant reinforcement learning environment where agents learn to debug broken data pipelines.
    
Problem Statement
Data engineers spend a significant portion of their time debugging ETL pipelines — wrong column types loaded from CSVs, silent join failures due to key mismatches, and aggregation bugs that produce plausible-looking but incorrect results. These are real, high-stakes errors that cost companies millions in downstream decisions made on bad data.
ETLDebugger provides a structured, episodic environment where an agent must inspect a broken DataFrame, reason about what went wrong, and apply a sequence of corrective actions to restore it to ground truth.
The Solution
ETLDebugger wraps three carefully designed data corruption scenarios into a fully OpenEnv-compliant environment. The agent receives a broken pandas DataFrame as an observation, chooses from a structured action space (cast types, rename columns, fix values, drop rows, etc.), and receives a dense reward signal after each step based on how close the current DataFrame is to the ground truth.
No external APIs, no databases, no network calls — all fixtures are generated deterministically in code, making the environment fully reproducible and lightweight enough to run on a laptop.
System Architecture
┌─────────────────────────────────────────────────────────┐
│ RL Agent / Inference │
│ (OpenAI client via inference.py) │
└───────────────────────┬─────────────────────────────────┘
│ HTTP (reset / step / state)
▼
┌─────────────────────────────────────────────────────────┐
│ FastAPI Server (server/app.py) │
│ │
│ POST /reset ──► PipelineEnvironment.reset() │
│ POST /step ──► PipelineEnvironment.step() │
│ GET /state ──► PipelineEnvironment.state │
│ GET /health ──► { status: ok } │
└───────────────────────┬─────────────────────────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
Task Fixture Action Reward
(fixtures.py) Dispatcher Function
(actions.py) (reward.py)
│
├── easy_broken_df ──► grader_easy.py
├── medium_broken_df ──► grader_medium.py
└── hard_broken_df ──► grader_hard.pyTasks
ETLDebugger ships with three tasks of increasing difficulty, each with a deterministic grader scoring 0.0 – 1.0.
Task 1 — Type Mismatch easy
What broke: The revenue column was loaded from CSV as a currency-formatted string ("$1,234.56") instead of float64. The pipeline never cast it.
What the agent must do: Strip the $ and commas, then cast to float.
Optimal sequence:
{"action_type": "fix_column", "column": "revenue", "params": {"transform": "strip_currency"}}
{"action_type": "cast_type", "column": "revenue", "params": {"dtype": "float64"}}
{"action_type": "done"}Llama-3.1-8b-instant score: 1.00
Task 2 — Join Key Mismatch medium
What broke: A left-join between orders and users failed because the key column is user_id in orders but userId (camelCase) in users. The result has a userId artifact column and name/tier columns that are entirely NULL.
What the agent must do: Identify the camelCase mismatch, rename userId → user_id, and reorder columns to match the expected schema.
Optimal sequence:
{"action_type": "rename_column", "column": "userId", "params": {"new_name": "user_id"}}
{"action_type": "reorder_columns", "params": {"order": ["user_id","name","tier","amount","status"]}}
{"action_type": "done"}Llama-3.1-8b-instant score: 0.76
Task 3 — Silent Data Corruption hard
What broke: A date-range filter used > instead of >=, silently dropping the boundary row. Additionally, all daily_total values for category D were zeroed out in the aggregation step. No exception is raised — the output looks plausible.
What the agent must do: Audit row counts, detect the statistical anomaly in category D, and identify both bugs without any error message to guide it.
Llama-3.1-8b-instant score: 0.69
Reward Function
Every step() returns a dense reward in [-0.30, 1.0]. The reward is embedded directly on the Observation object per the OpenEnv spec.
reward = 0.40 × schema_match # column names + dtypes vs ground truth
+ 0.30 × row_correctness # % of cell values matching ground truth
+ 0.20 × null_handling # null distribution vs ground truth
+ 0.10 × efficiency # penalises redundant repeated actions
− 0.30 (loop penalty) # flat penalty if last 3 actions are identicalWhy dense rewards matter: Binary end-of-episode rewards give the agent no signal until it solves the task completely. ETLDebugger rewards every meaningful step — fixing the dtype gives +0.12 immediately, even if nulls are still wrong. This makes learning tractable for RL algorithms.
Loop penalty: If the agent repeats the same action three times in a row, it receives a flat −0.30 penalty. This prevents the degenerate policy of spamming one action.
Graders
Each task has a standalone deterministic grader in graders/. Graders are called at the end of an episode and return a float in [0.0, 1.0].
All graders are deterministic and reproducible.
Fixtures
All task data is generated programmatically in tasks/fixtures.py using a seeded random number generator (numpy.random.default_rng(seed)). This means:
- No external CSV files to maintain
- Fully reproducible across machines
- Each task's broken DataFrame is a precise, controlled corruption of the ground truth
Action Space
Observation Space
Each step() and reset() returns a PipelineObservation:
Tech Stack
Repo Structure
etl-debugger/
├── inference.py # Baseline inference script (required)
├── validate.py # Spec compliance test runner (44 checks)
├── openenv.yaml # OpenEnv metadata
├── pyproject.toml # Package config + entry points
├── requirements.txt # Dependencies
├── README.md
├── setup.sh # One-command setup script
├── __init__.py # Exports PipelineAction, PipelineObservation
├── models.py # Typed Action / Observation / State dataclasses
├── client.py # HTTPEnvClient subclass
├── server/
│ ├── app.py # FastAPI server (reset / step / state / health)
│ ├── pipeline_environment.py # Core environment logic
│ ├── actions.py # Action dispatcher
│ ├── reward.py # Dense reward function
│ ├── Dockerfile # openenv-base:latest
│ └── requirements.txt # Server-specific deps
├── tasks/
│ └── fixtures.py # Generates all 3 task fixtures deterministically
└── graders/
├── grader_easy.py
├── grader_medium.py
└── grader_hard.pySetup & Usage
Local
git clone https://github.com/YOUR_USERNAME/etl-debugger
cd etl-debuggerOr manually:
pip install -r requirements.txt
uv sync
uvicorn server.app:app --reloadValidate spec compliance
python validate.py
# Results: 44/44 passed — all tests passed ✓API endpoints
# Reset episode
curl -X POST http://localhost:8000/reset
# Take an action
curl -X POST http://localhost:8000/step \
-H "Content-Type: application/json" \
-d '{"action_type":"fix_column","column":"revenue","params":{"transform":"strip_currency"}}'
# Get episode state
curl http://localhost:8000/state
# Interactive docs
open http://localhost:8000/docsRun inference
API_BASE_URL=https://api.openai.com/v1 \
MODEL_NAME=gpt-4o \
HF_TOKEN=sk-... \
python inference.pyDocker
docker build -f server/Dockerfile -t etl-debugger .
docker run -p 8000:8000 -e TASK_ID=easy etl-debuggerOpenEnv Compliance
Author
Satyam Kumar
License
MIT
