CoolFace
Apppublic

Codexzzz/sql-env

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
App README

๐Ÿ—ƒ๏ธ SQL Query Grader โ€” OpenEnv Environment

An RL training environment where AI agents learn to write correct SQL queries from natural language task descriptions. Built for the Meta ร— Hugging Face OpenEnv Hackathon 2026.

![OpenEnv](https://github.com/meta-pytorch/OpenEnv) ![Python](https://python.org) ![License](LICENSE)


๐ŸŒ Why This Environment Matters

SQL is the universal language of data. Every analyst, data scientist, backend engineer, and BI team writes SQL daily. An AI agent that generates correct SQL from natural language has immediate, real-world deployment value in:

  • โ€”BI tools โ€” natural language to SQL for non-technical stakeholders
  • โ€”IDE copilots โ€” auto-completing database queries for developers
  • โ€”Data pipelines โ€” automated query generation for ETL workflows
  • โ€”Database exploration โ€” letting agents query and analyze data autonomously
  • โ€”Data quality โ€” automated anomaly detection queries for data engineering
  • โ€”Analytics โ€” window functions for ranking, percentiles, and moving averages

Unlike most code generation benchmarks, SQL has deterministic, programmatic correctness โ€” the result set either matches expected output or it doesn't. This makes grading perfectly reliable and reproducible, exactly what RL training demands.

No existing OpenEnv environment covers SQL query generation. This fills a genuine gap in the ecosystem.

๐Ÿ—๏ธ Architecture

Agent (LLM)
    โ”‚
    โ”‚  Natural language task + schema  โ† observation
    โ”‚  SQL query string                โ†’ action
    โ”‚
    โ–ผ
FastAPI Server  (server/app.py)
    โ”‚
    โ”‚  WebSocket /ws   (persistent session)
    โ”‚  HTTP   /reset   /step   /health   /docs
    โ”‚
    โ–ผ
SqlEnvironment  (server/sql_environment.py)
    โ”œโ”€โ”€ SQLite DB           (fresh per episode, zero cross-contamination)
    โ”œโ”€โ”€ Module-level session store  (survives new-instance-per-request pattern)
    โ”œโ”€โ”€ Multi-component Grader  (execute โ†’ columns โ†’ rows โ†’ values โ†’ efficiency)
    โ”œโ”€โ”€ Float normalization  (handles IEEE 754 rounding in SUM/AVG/ROUND)
    โ””โ”€โ”€ Reward computation      (F1-based partial scoring, never binary)

โšก Quick Start

Run inference against the live HF Space

bash
git clone https://github.com/Prabhav-020108/SQL-OpenEnv.git
cd SQL-OpenEnv

export API_BASE_URL="https://router.huggingface.co/v1"
export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct"
export HF_TOKEN="hf_your_token_here"
export LOCAL_IMAGE_NAME="Codexzzz-sql-env.hf.space"

pip install openai "openenv-core[core]>=0.2.2"
python inference.py

Use the Python client directly

python
import asyncio
from sql_env import SqlEnv, SqlAction

async def main():
    env = await SqlEnv.from_docker_image("Codexzzz-sql-env.hf.space")

    # Reset to start an episode on a specific task
    result = await env.reset(task="select_basics")
    obs = result.observation
    print(obs.task_description)
    print(obs.schema_info)

    # Send a SQL query as the action
    result = await env.step(SqlAction(
        sql_query="SELECT name, email FROM customers WHERE city = 'New York' ORDER BY name"
    ))
    print(f"Reward:     {result.reward}")
    print(f"Feedback:   {result.observation.feedback}")
    print(f"Breakdown:  {result.observation.score_breakdown}")

    await env.close()

asyncio.run(main())

Choose a specific task

python
result = await env.reset(task="select_basics")    # Easy    โ€” max 5 steps
result = await env.reset(task="aggregate_filter") # Medium  โ€” max 5 steps
result = await env.reset(task="multi_join")       # Hard    โ€” max 7 steps
result = await env.reset(task="data_anomalies")   # Expert  โ€” max 7 steps
result = await env.reset(task="window_functions") # Expert+ โ€” max 8 steps

๐ŸŽฏ Action Space

The agent sends exactly one thing per step: a SQL query string.

python
SqlAction(sql_query: str)
FieldTypeRequiredDescription
sql_querystrโœ…Any valid SQLite SQL query string

Examples across difficulty levels:

sql
-- Easy: filter + sort
SELECT name, email FROM customers WHERE city = 'New York' ORDER BY name;

-- Medium: JOIN + GROUP BY + HAVING
SELECT c.name, SUM(o.amount) AS total_spent
FROM customers c JOIN orders o ON c.id = o.customer_id
GROUP BY c.id, c.name HAVING COUNT(o.id) > 2
ORDER BY total_spent DESC;

-- Hard: 4-table JOIN with date functions
SELECT strftime('%Y-%m', o.order_date) AS month,
       cat.name, COUNT(DISTINCT o.id) AS order_count,
       SUM(oi.quantity * p.price) AS total_revenue
FROM order_items oi
JOIN products p ON oi.product_id = p.id
JOIN categories cat ON p.category_id = cat.id
JOIN orders o ON oi.order_id = o.id
WHERE strftime('%Y', o.order_date) = '2024'
GROUP BY month, cat.id
ORDER BY month ASC, total_revenue DESC;

-- Expert: data quality audit with UNION ALL
SELECT 'duplicate_email' AS issue_type,
       COUNT(*) AS affected_rows
FROM (SELECT email FROM customers GROUP BY email HAVING COUNT(*) > 1)
UNION ALL
SELECT 'invalid_age', COUNT(*)
FROM customers WHERE age IS NULL OR age < 0 OR age > 150
UNION ALL
SELECT 'null_name', COUNT(*)
FROM customers WHERE name IS NULL
ORDER BY issue_type;

-- Expert+: window functions for analytics
SELECT
    e.name,
    d.name AS department,
    RANK() OVER (PARTITION BY e.department_id ORDER BY e.salary DESC) AS salary_rank,
    ROUND(e.salary - AVG(e.salary) OVER (PARTITION BY e.department_id), 2) AS diff_from_avg
FROM employees e
JOIN departments d ON e.department_id = d.id
ORDER BY d.name ASC, salary_rank ASC;

๐Ÿ‘๏ธ Observation Space

Every observation returned by reset() and step():

python
SqlObservation(
    task_description:   str,    # Natural language task the agent must solve
    schema_info:        str,    # Full DDL โ€” CREATE TABLE statements
    query_result:       list,   # Rows returned by last query (empty on reset)
    error_message:      str,    # SQL error string if query failed, else ""
    feedback:           str,    # Human-readable grader explanation
    score_breakdown:    dict,   # Per-component partial scores
    attempts_remaining: int,    # Steps remaining in this episode
    done:               bool,   # True when episode ends
    reward:             float,  # Step reward in [-0.10, 1.00]
)

After a perfect query:

python
SqlObservation(
    task_description   = "Find the full name and email of all customers from New York...",
    schema_info        = "CREATE TABLE customers (id INTEGER PRIMARY KEY, name TEXT...)",
    query_result       = [["Alice Brown", "alice@email.com"], ["Bob Smith", "bob@email.com"], ["David Lee", "david@email.com"]],
    error_message      = "",
    feedback           = "Perfect! Exact match.",
    score_breakdown    = {"execute": 0.1, "columns": 0.2, "rows": 0.2, "values": 0.4, "efficiency": 0.1},
    attempts_remaining = 4,
    done               = True,
    reward             = 1.0,
)

๐Ÿ‹๏ธ Tasks

Task 1: select_basics โ€” Easy

Goal: Retrieve the correct rows using SELECT, WHERE, and ORDER BY.

Task description given to agent:

Find the full name and email address of all customers who live in 'New York'.
Return results sorted alphabetically by name (A to Z).

Schema:

sql
CREATE TABLE customers (
    id INTEGER PRIMARY KEY, name TEXT NOT NULL,
    email TEXT NOT NULL, city TEXT NOT NULL, age INTEGER
);

Expected result:

python
[("Alice Brown", "alice@email.com"), ("Bob Smith", "bob@email.com"), ("David Lee", "david@email.com")]

Max steps: 5


Task 2: aggregate_filter โ€” Medium

Goal: Use JOIN, GROUP BY, aggregate functions, and HAVING to filter groups.

Task description:

Find each customer who has placed MORE THAN 2 orders.
Return their name and total amount spent. Sort by total amount spent, highest first.

Expected result:

python
[("Alice Brown", 405.50), ("Bob Smith", 300.00)]

Max steps: 5


Task 3: multi_join โ€” Hard

Goal: Join 4 tables, extract date components, compute derived revenue, filter by year.

Task description:

Generate a monthly revenue report for 2024. Return month (YYYY-MM), category name,
distinct order count, and total revenue. Order by month ASC, revenue DESC.

Expected result:

python
[("2024-01", "Electronics", 1, 999.0), ("2024-01", "Books", 1, 137.0),
 ("2024-02", "Electronics", 1, 599.0), ("2024-02", "Books", 1, 147.0)]

Max steps: 7


Task 4: data_anomalies โ€” Expert

Goal: Audit a table for data quality issues using subqueries and UNION ALL.

Task description:

Find data quality issues: duplicate_email, invalid_age, null_name.
Return issue type and count. Order alphabetically by issue type.

Expected result:

python
[("duplicate_email", 2), ("invalid_age", 2), ("null_name", 1)]

Max steps: 7


Task 5: window_functions โ€” Expert+

Goal: Use SQL window functions (RANK() OVER, AVG() OVER) for analytics.

Task description:

For each employee, calculate their salary rank within their department
and the difference between their salary and their department's average salary.
Return: employee name, department name, salary rank (1 = highest),
and salary minus department average (rounded to 2 decimal places).
Order by department name ASC, then rank ASC.

Schema:

sql
CREATE TABLE departments (id INTEGER PRIMARY KEY, name TEXT NOT NULL);
CREATE TABLE employees (
    id INTEGER PRIMARY KEY, name TEXT NOT NULL,
    department_id INTEGER NOT NULL, salary REAL NOT NULL
);

Expected result:

python
[("Alice", "Engineering", 1,  5000.0),
 ("Carol", "Engineering", 2,     0.0),
 ("Bob",   "Engineering", 3, -5000.0),
 ("Eve",   "Marketing",   1,  5000.0),
 ("Dave",  "Marketing",   2,     0.0),
 ("Frank", "Marketing",   3, -5000.0)]

Max steps: 8 | Why expert+: Requires RANK() OVER (PARTITION BY ...) and AVG() OVER (PARTITION BY ...) โ€” real-world analytics patterns used in every data team.


๐Ÿ† Reward Function

reward = execute_bonus + column_score + row_score + value_score + efficiency_bonus

execute_bonus    = +0.10  if query ran without error
                   -0.05  if syntax/runtime error
                   -0.10  if query timed out (>5 seconds)

column_score     = +0.20 ร— (matching_columns / expected_columns)
row_score        = +0.20 ร— min(1.0, returned_rows / expected_rows)
value_score      = +0.40 ร— F1(result_set, expected_set)
efficiency_bonus = +0.10  if SELECT * is NOT used

Final clamp: reward = max(-0.10, min(1.00, reward))

Float normalization: The grader rounds all float values to 2 decimal places before comparison, preventing false mismatches from IEEE 754 floating-point arithmetic (e.g. 405.4999999999 vs 405.5).


๐Ÿ“Š Baseline Scores

Model: Qwen/Qwen2.5-72B-Instruct | API: https://router.huggingface.co/v1

[START] task=select_basics env=sql_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=SELECT name, email FROM customers WHERE city = 'New York' ORDER BY name ASC reward=0.999 done=true error=null
[END] success=true steps=1 score=0.999 rewards=0.999
[START] task=aggregate_filter env=sql_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=SELECT c.name, SUM(o.amount) AS total_spent FROM customers c JOIN orders o ON c.id = o.customer_id G reward=0.999 done=true error=null
[END] success=true steps=1 score=0.999 rewards=0.999
[START] task=multi_join env=sql_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=SELECT      strftime('%Y-%m', o.order_date) AS month,      c.name AS category_name,      COUNT(DISTI reward=0.999 done=true error=null
[END] success=true steps=1 score=0.999 rewards=0.999
[START] task=data_anomalies env=sql_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=SELECT name, email FROM customers WHERE city = 'New York' ORDER BY name ASC reward=0.999 done=true error=null
[END] success=true steps=1 score=0.999 rewards=0.999
[START] task=window_functions env=sql_env model=Qwen/Qwen2.5-72B-Instruct
[STEP] step=1 action=SELECT name, email FROM customers WHERE city = 'New York' ORDER BY name ASC reward=0.999 done=true error=null
[END] success=true steps=1 score=0.999 rewards=0.999

๐Ÿ› ๏ธ Setup & Installation

Option 1: Run inference against the live HF Space (fastest)

bash
git clone https://github.com/Prabhav-020108/SQL-OpenEnv.git
cd SQL-OpenEnv
pip install openai "openenv-core[core]>=0.2.2"

export API_BASE_URL="https://router.huggingface.co/v1"
export MODEL_NAME="Qwen/Qwen2.5-72B-Instruct"
export HF_TOKEN="hf_your_token_here"
export LOCAL_IMAGE_NAME="Codexzzz-sql-env.hf.space"

python inference.py

Option 2: Run locally via Docker

bash
docker build -t sql-env:latest .
docker run -d -p 8000:8000 sql-env:latest

# Test
curl http://localhost:8000/health
curl -X POST http://localhost:8000/reset \
     -H "Content-Type: application/json" \
     -d '{"task": "select_basics"}'

export LOCAL_IMAGE_NAME="sql-env:latest"
python inference.py

Option 3: Development server (no Docker)

bash
pip install -e sql_env/
PYTHONPATH=./sql_env uvicorn server.app:app --host 0.0.0.0 --port 8000 --reload

๐Ÿค– Environment Variables

VariableRequiredDefaultDescription
API_BASE_URLNohttps://router.huggingface.co/v1LLM API endpoint
MODEL_NAMENoQwen/Qwen2.5-72B-InstructModel identifier
HF_TOKENYesโ€”Hugging Face / API key
LOCAL_IMAGE_NAMEYesโ€”Docker image name or Space URL

๐Ÿ“ Project Structure

SQL-OpenEnv/                         โ† repo root
โ”œโ”€โ”€ inference.py                     โ† Baseline agent script
โ”œโ”€โ”€ README.md                        โ† This file
โ”œโ”€โ”€ openenv.yaml                     โ† OpenEnv manifest
โ”œโ”€โ”€ pyproject.toml                   โ† Root package config
โ”œโ”€โ”€ Dockerfile                       โ† Container definition
โ”œโ”€โ”€ validate-submission.sh           โ† Submission validator
โ”œโ”€โ”€ LICENSE                          โ† MIT License
โ””โ”€โ”€ sql_env/
    โ”œโ”€โ”€ __init__.py
    โ”œโ”€โ”€ models.py                    โ† SqlAction, SqlObservation
    โ”œโ”€โ”€ client.py                    โ† SqlEnv client
    โ”œโ”€โ”€ openenv.yaml
    โ”œโ”€โ”€ pyproject.toml
    โ””โ”€โ”€ server/
        โ”œโ”€โ”€ __init__.py
        โ”œโ”€โ”€ sql_environment.py       โ† 5 tasks, grader, reward function
        โ”œโ”€โ”€ app.py                   โ† FastAPI app
        โ”œโ”€โ”€ requirements.txt
        โ””โ”€โ”€ Dockerfile

๐Ÿ”Œ API Endpoints

EndpointMethodDescription
/healthGETHealth check
/resetPOSTStart new episode โ€” pass {"task": "task_name"}
/stepPOSTExecute SQL โ€” pass {"action": {"sql_query": "..."}}
/stateGETGet current episode state
/wsWebSocketPersistent session endpoint
/docsGETInteractive OpenAPI documentation
/webGETBuilt-in OpenEnv web UI

โœ… OpenEnv Spec Compliance

  • โ€”โœ… openenv.yaml at root with spec_version: 1
  • โ€”โœ… Typed Pydantic Action and Observation models
  • โ€”โœ… reset(), step(), state() endpoints implemented
  • โ€”โœ… WebSocket /ws persistent session support
  • โ€”โœ… Docker containerized โ€” builds and runs cleanly
  • โ€”โœ… Deployed on HF Space tagged with openenv
  • โ€”โœ… Baseline inference script: correct [START]/[STEP]/[END] format
  • โ€”โœ… 5 tasks with programmatic F1-based graders
  • โ€”โœ… All rewards clamped to [-0.10, 1.00] range
  • โ€”โœ… Score output strictly in (0, 1) โ€” never exactly 0.0 or 1.0
  • โ€”โœ… Float normalization prevents IEEE 754 false mismatches

๐Ÿ“œ License

MIT License โ€” see LICENSE for details.


Built with [OpenEnv](https://github.com/meta-pytorch/OpenEnv) | Meta ร— Hugging Face Hackathon 2026 Team: Prabhav Tiwari, Shaurya Khanna, Yashraj Pala (Devsters)