Spaces:
Sleeping
Sleeping
File size: 21,418 Bytes
3551dcf | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | ---
title: SQL Quality Audit Agent Environment
emoji: ποΈ
colorFrom: green
colorTo: blue
sdk: docker
pinned: false
tags:
- openenv
- sql
- data-quality
- rl-training
- agent-evaluation
---
<div align="center">
# ποΈ SQL Quality Audit Agent Environment
**The first multi-step RL environment purpose-built for SQL data quality reasoning.**
*Train agents that think like senior data engineers β not SQL autocomplete engines.*
[](https://huggingface.co/spaces/Deadman922/sql-quality-env)
[](https://huggingface.co/spaces/Deadman922/sql-quality-env)
[](LICENSE)
[](#tasks)
</div>
---
## The Problem We Are Solving
Every data team faces the same invisible crisis: production databases that silently degrade. Columns drift from `REAL` to `TEXT`. Foreign keys become orphaned. Duplicate rows inflate join counts by 2Γ. Schema versioning splits the same business concept across incompatible tables.
Existing AI benchmarks evaluate SQL *syntax*. None evaluate SQL *reasoning under uncertainty* β the ability to investigate an unknown database, form a hypothesis, apply a targeted fix, and verify the result before committing.
**This environment changes that.**
---
## What Makes This Different
| Benchmark Approach | This Environment |
|---|---|
| Static query β grade output | Multi-step episode with dense reward shaping |
| Clean, predefined schema | Unknown database with planted defects |
| Syntax correctness check | Five-dimension trajectory grading |
| One attempt per task | Configurable attempt budget per task type |
| No process feedback | Per-step rewards for investigation quality |
The grading system rewards *how* an agent works, not just *what* it submits. An agent that systematically investigates before fixing scores higher than one that submits the correct SQL on the first try β because in production, the correct SQL on the first try is often luck.
---
## Architecture Overview
```
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β AGENT (inference.py) β
β LLM β generate_sql() β action_type + SQL/answer β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β POST /reset POST /step
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
β ENVIRONMENT (FastAPI server) β
β SQLQualityEnvironment β SQLite in-memory database β
β β
β QUERY βββ read-only SELECT on live db β
β PROFILE ββ column statistics (null rate, type dist) β
β FIX βββββ write SQL on isolated _fresh_test_db() copy β
β SUBMIT βββ compute_episode_score() β SQLReward β
ββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββββββ
β
ββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββββββ
β GRADER (graders.py) β
β D1 Diagnostic Β· D2 Hypothesis Β· D3 Fix Β· D4 Verify Β· D5 Genβ
β Composite score in [0.001, 0.999] β [END] stdout line β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
```
---
## Observation Space
Every `reset()` and `step()` returns a `SQLObservation` containing everything the agent needs to reason about the task:
```json
{
"task_id": "dedup_bug",
"task_description": "Find and repair the duplication bug inflating join result volumes",
"schema_info": [
{
"table_name": "order_items",
"columns": ["id (INTEGER)", "order_id (INTEGER)", "product_name (TEXT)", "quantity (INTEGER)", "price (REAL)"],
"row_count": 320
}
],
"sample_rows": {
"order_items": [{"id": 1, "order_id": 0, "product_name": "Product 1", "quantity": 2, "price": 19.99}]
},
"episode_id": "bf37e2ee-63e1-434f-9ff3-e0728518a81c",
"attempt_number": 1,
"max_attempts": 5,
"trajectory_history": [],
"available_actions": ["query", "profile", "fix", "submit"],
"investigation_hint": null
}
```
| Field | Type | Description |
|---|---|---|
| `task_id` | string | Active task identifier |
| `task_description` | string | Natural language description of the defect to find and fix |
| `schema_info` | list | Table names, typed column names, and row counts |
| `sample_rows` | dict | First 5 rows of each table as JSON records |
| `episode_id` | string | UUID identifying the current episode |
| `trajectory_history` | list | All previous actions and result previews in this episode |
| `available_actions` | list | Valid action types at this point |
| `investigation_hint` | string or null | Actionable feedback after each scored step; null on first step |
---
## Action Space
Four actions are available at every step, each with strict database access controls:
```
ββββββββββββ¬βββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββββ
β Action β Allowed SQL β Behavior β
ββββββββββββΌβββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ€
β QUERY β SELECT, WITH, EXPLAIN, β Read-only. Runs on live database. β
β β PRAGMA β Returns result preview. No state β
β β β change. Call as many times as neededβ
ββββββββββββΌβββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ€
β PROFILE β None (column name only) β Returns null rate, distinct count, β
β β β and type distribution for one β
β β β column. Shortcut for inspection. β
ββββββββββββΌβββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ€
β FIX β UPDATE, INSERT, DELETE, β Runs on isolated _fresh_test_db() β
β β CREATE, ALTER β copy. Scored but does NOT affect β
β β β subsequent QUERY results. β
ββββββββββββΌβββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββββ€
β SUBMIT β None β Closes the episode. Triggers full β
β β β trajectory scoring. Returns final β
β β β composite reward. β
ββββββββββββ΄βββββββββββββββββββββββββββ΄ββββββββββββββββββββββββββββββββββββββ
```
> **Key design decision:** FIX runs on `_fresh_test_db()` β an isolated in-memory copy that is discarded after scoring. The agent cannot verify its fix by re-querying the database afterward. It must reason about correctness from schema and sample data alone. This is precisely what makes D4 self-verification a non-trivial and RL-trainable behavior.
---
## The Five Tasks
### π΅ `null_audit` β Easy β 1 attempt allowed
> *"One column in this table has more than 50% missing values. Find it."*
A `users` table with 8 columns where one randomly-selected column carries exactly a 52% null rate. All other columns have approximately 10% nulls as deliberate distractors. The agent must identify and name the high-null column. No SQL fix is required β this is the only pure-identification task in the benchmark.
**Why it is non-trivial:** Agents that profile columns one by one will exhaust their step budget. The efficient strategy is to query null rates for all columns in a single statement and immediately submit.
```sql
-- Efficient investigation
SELECT
SUM(CASE WHEN email IS NULL THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS email_null_rate,
SUM(CASE WHEN city IS NULL THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS city_null_rate
FROM users;
```
**Grader:** `server.graders:grade_null_audit`
---
### π‘ `type_repair` β Medium β 3 attempts allowed
> *"Aggregate queries on this table return wrong totals. Something is wrong with the data types."*
A `transactions` table where the `amount` column is stored as `TEXT` containing numeric strings like `"12.50"`. SQLite aggregate functions silently return incorrect results because the column is treated as text. The fix must convert all 1,000 values to `REAL` without losing any rows. Data preservation is enforced at a 99.5% threshold.
**Why it is non-trivial:** SQLite does not support `ALTER COLUMN` syntax. Agents must use either `UPDATE ... SET amount = CAST(amount AS REAL)` or a full table recreation pattern. Many agents get stuck on invalid ALTER statements.
```sql
-- Correct approach
UPDATE transactions
SET amount = CAST(amount AS REAL)
WHERE typeof(amount) != 'real';
```
**Grader:** `server.graders:grade_type_repair`
---
### π΄ `dedup_bug` β Hard β 5 attempts allowed
> *"A JOIN between orders and order_items is returning twice the expected row count. Track down why."*
An `order_items` table with exactly 320 rows β 160 legitimate records plus 160 exact duplicates, one per `order_id`. When joined against the `orders` table, every order appears twice. The correct clean count after deduplication is 160. The SUBMIT action accepts `root_cause_table` and `offending_key` fields for partial credit independent of fix quality.
**Why it is non-trivial:** The agent must correctly identify the duplication key (`order_id`), not just delete arbitrary rows. A naive `DELETE` without GROUP BY will produce incorrect results or violate the preservation threshold.
```sql
-- Correct approach
DELETE FROM order_items
WHERE rowid NOT IN (
SELECT MIN(rowid) FROM order_items GROUP BY order_id
);
```
**Grader:** `server.graders:grade_dedup_bug`
---
### π΄ `fk_integrity` β Hard β 4 attempts allowed
> *"Customer reports indicate missing invoice data. Referential integrity may be broken."*
An `invoices` table with 1,200 rows where 180 records reference `customer_id` values in the range 10,001β11,000 β IDs that do not exist in the `customers` table. Valid customer IDs are 1β400. These orphan records must be removed while preserving all 1,020 valid invoices intact. The fix is validated against both the orphan count and the preservation ratio.
```sql
-- Correct approach
DELETE FROM invoices
WHERE customer_id NOT IN (SELECT id FROM customers);
```
**Grader:** `server.graders:grade_fk_integrity`
---
### π‘ `schema_drift` β Medium β 4 attempts allowed
> *"Two upstream data sources emit the same event concept but with completely different schemas. Build a canonical unified table."*
Two tables exist: `events_v1` (600 rows) with columns `[id, user_id, event_name, amount_text, event_time]` where `amount_text` is TEXT, and `events_v2` (600 rows) with columns `[event_id, account_id, event_type, amount, event_ts, source]` where `amount` is REAL. The agent must create a `canonical_events` table with 1,200 unified rows, correct column names, and consistent `REAL` type for the amount field.
**Why it is non-trivial:** Requires correctly mapping `idβevent_id`, `user_idβuser_id` (and `account_idβuser_id` for events_v2), `event_nameβevent_type`, `CAST(amount_text AS REAL)βamount`, `event_timeβevent_time` across both sources. Incorrect column mapping produces wrong schema overlap scores.
```sql
-- Correct approach
CREATE TABLE canonical_events AS
SELECT id AS event_id, user_id,
event_name AS event_type,
CAST(amount_text AS REAL) AS amount,
event_time
FROM events_v1
UNION ALL
SELECT event_id,
account_id AS user_id,
event_type,
amount,
event_ts AS event_time
FROM events_v2;
```
**Grader:** `server.graders:grade_schema_drift`
---
## Five-Dimension Scoring
```
composite = D1 Γ 0.20 + D2 Γ 0.20 + D3 Γ 0.35 + D4 Γ 0.10 + D5 Γ 0.15
```
**D1 β Diagnostic Efficiency (20%)**
Measures how quickly the agent identified the defective table and column. Finding the issue in β€2 queries scores 1.0. Three to four queries scores 0.7. Five to six queries scores 0.4. Never identifying the issue scores 0.0. This dimension directly penalizes shotgun exploration strategies.
**D2 β Hypothesis Quality (20%)**
Rewards structured investigation. Agents that inspect the table schema before drilling into specific columns score 1.0. Agents that query columns without understanding the table structure first score 0.6. This dimension trains broad-to-narrow reasoning habits.
**D3 β Fix Correctness (35%)**
The highest-weighted dimension. Evaluated with four severity tiers: PERFECT (issue fully resolved, all data preserved) = 1.0, ACCEPTABLE (issue resolved with minor data impact) = 0.6, DANGEROUS (partial fix that risks data loss) = 0.2, CATASTROPHIC (fix causes data destruction or no improvement) = 0.0. CATASTROPHIC severity overrides the entire composite and caps the final score at 0.05.
**D4 β Self-Verification (10%)**
A bonus dimension awarded when an agent submits a PROFILE action after a FIX to verify schemas and reason about correctness before calling SUBMIT. Because FIX runs on an isolated copy, the agent cannot re-query data, so it must perform reasoning-only checks. This behavior does not emerge from supervised learning on static SQL datasets. It is the primary RL-emergent behavior this environment is designed to cultivate. All zero-shot baseline agents score 0.0 here.
**D5 β Generalization (15%)**
The fix is silently tested against three schema variants: (1) a base copy of the original data, (2) the same schema with an additional column added, and (3) the same schema with a shifted data distribution. A fix that is brittle to schema changes scores 0.0 even if it perfectly resolves the original task.
### CATASTROPHIC Override
Any fix meeting one of the following conditions is immediately classified as CATASTROPHIC and caps the composite at **0.05** regardless of D1, D2, D4, and D5 scores:
- The fix contains `DROP TABLE` on the target table
- The fix results in zero rows remaining in the target table
- The fix deletes more than 50% of originally valid records
This prevents agents from gaming the reward signal through destructive operations.
### Score Boundaries
All scores are clamped to `[0.001, 0.999]`. Exact values of 0.0 and 1.0 are never returned. This design choice prevents RL training instability caused by absorbing boundary states.
---
## Setup and Installation
### Prerequisites
```bash
git clone https://huggingface.co/spaces/Deadman922/sql-quality-env
cd sql-quality-env
pip install -r requirements.txt
```
### Environment Variables
| Variable | Required | Default | Purpose |
|---|---|---|---|
| `API_BASE_URL` | Yes | `https://router.huggingface.co/v1` | LLM API endpoint |
| `MODEL_NAME` | Yes | `meta-llama/Llama-3.3-70B-Instruct` | Model identifier |
| `HF_TOKEN` | Yes | β | HuggingFace API token β no default, must be set explicitly |
| `LOCAL_IMAGE_NAME` | No | β | Docker image name, only required with `from_docker_image()` |
```bash
export API_BASE_URL="https://router.huggingface.co/v1"
export MODEL_NAME="meta-llama/Llama-3.3-70B-Instruct"
export HF_TOKEN="hf_your_token_here"
```
### Start the Server
```bash
# Python
python -m server.main
# Docker
docker build -t sql-quality-env .
docker run -p 7860:7860 \
-e API_BASE_URL=$API_BASE_URL \
-e MODEL_NAME=$MODEL_NAME \
-e HF_TOKEN=$HF_TOKEN \
sql-quality-env
```
### Run the Agent
```bash
# Single task
python inference.py --task null_audit --url http://localhost:7860
# Full benchmark run
for task in null_audit type_repair dedup_bug fk_integrity schema_drift; do
echo "--- Running $task ---"
python inference.py --task $task --url http://localhost:7860
done
```
### Verify the Environment
```bash
# Health check
curl http://localhost:7860/health
# List graded tasks
curl http://localhost:7860/tasks
# Start a manual episode
curl -X POST http://localhost:7860/reset \
-H "Content-Type: application/json" \
-d '{"task_id": "null_audit", "seed": 42}'
```
---
## Stdout Log Format
The inference agent emits exactly three structured line types. Any deviation in format causes evaluation failure at the validator:
```
[START] task=null_audit env=sql_quality_env model=meta-llama/Llama-3.3-70B-Instruct
[STEP] step=1 action=query:SELECT COUNT(*)... reward=0.01 done=false error=null
[STEP] step=2 action=submit:email reward=0.99 done=true error=null
[END] success=true steps=2 score=0.999 rewards=0.01,0.99
```
| Field | Format | Notes |
|---|---|---|
| `reward` per step | 2 decimal places | `0.01` not `0.0105` |
| `score` in END | 3 decimal places | `0.999` not `1.0` |
| `done`, `success` | lowercase boolean | `true` or `false` never `True` |
| `error` | raw string or `null` | never `None` or empty string |
| debug output | stderr only | never appears in stdout |
---
## Baseline Scores
Evaluated with `meta-llama/Llama-3.3-70B-Instruct`, temperature 0.7, max 200 tokens, seed 42, force-submit at step 7:
| Task | Score | D1 | D2 | D3 | D4 | D5 | Primary Bottleneck |
|---|---|---|---|---|---|---|---|
| `null_audit` | ~0.68 | 1.00 | 0.60 | ~0.70 | 0.00 | 0.00 | Lexical near-misses on column name spelling |
| `type_repair` | ~0.41 | 1.00 | 0.60 | ~0.30 | 0.00 | 0.00 | SQLite ALTER limitation requires table recreation |
| `dedup_bug` | ~0.82 | 1.00 | 0.60 | 1.00 | 0.00 | 1.00 | Reliable once duplicate pattern is identified |
| `fk_integrity` | ~0.82 | 1.00 | 0.60 | 1.00 | 0.00 | 1.00 | Reliable with LEFT JOIN orphan detection |
| `schema_drift` | ~0.50 | 0.00 | 0.00 | 1.00 | 0.00 | 1.00 | No investigation steps before fix |
D4 (self-verification) scores 0.0 for all zero-shot agents. This is the benchmark's primary training signal. The behavior is RL-emergent and cannot be obtained through imitation learning.
**Reproducibility configuration:**
- Model: `meta-llama/Llama-3.3-70B-Instruct`
- Temperature: 0.7 Β· Max tokens: 200 Β· Top-p: 1.0
- Seed: 42 per task Β· Max steps: 20 Β· Force submit at step 7
---
## API Reference
| Endpoint | Method | Request Body | Response |
|---|---|---|---|
| `/health` | GET | β | `{"status": "ok", "llm_available": bool, "provider": string}` |
| `/tasks` | GET | β | `{"tasks": [...], "total": 5, "graded": 5}` |
| `/metadata` | GET | β | Environment metadata with full task registry |
| `/reset` | POST | `{"task_id": string, "seed"?: int}` | `SQLObservation` |
| `/step` | POST | `SQLAction` | `{"observation", "reward", "done", "info"}` |
| `/state` | GET | β | `{"task_id", "episode_id", "attempt_number", "cumulative_reward", "done"}` |
---
## Grader Registry
| Task | Grader Function | Method |
|---|---|---|
| `null_audit` | `server.graders:grade_null_audit` | String match with lexical partial credit |
| `type_repair` | `server.graders:grade_type_repair` | Schema + row preservation on isolated copy |
| `dedup_bug` | `server.graders:grade_dedup_bug` | Row count + key identification + fix validation |
| `fk_integrity` | `server.graders:grade_fk_integrity` | Orphan elimination + valid record preservation |
| `schema_drift` | `server.graders:grade_schema_drift` | Column overlap ratio + row count alignment |
---
<div align="center">
*Built for the Meta Γ PyTorch Γ HuggingFace OpenEnv Hackathon*
**One Man Automation Β· Chennai, India**
[Live Space](https://huggingface.co/spaces/Deadman922/sql-quality-env) Β· [Report Issue](https://huggingface.co/spaces/Deadman922/sql-quality-env/discussions)
</div>
|