Spaces:
Sleeping
Sleeping
Commit ·
f812d5b
0
Parent(s):
Initial commit
Browse files- .env.example +0 -0
- Dockerfile +0 -0
- README.md +0 -0
- api/__init__.py +0 -0
- api/server.py +0 -0
- baseline.py +0 -0
- dataset/easy_cases.json +125 -0
- dataset/hard_cases.json +172 -0
- dataset/medium_cases.json +136 -0
- env/__init__.py +0 -0
- env/__pycache__/__init__.cpython-312.pyc +0 -0
- env/__pycache__/models.cpython-312.pyc +0 -0
- env/environment.py +0 -0
- env/graders.py +0 -0
- env/models.py +195 -0
- env/reward.py +0 -0
- env/tasks.py +183 -0
- openenv.yaml +0 -0
- requirements.txt +0 -0
- tests/test_environment.py +0 -0
- tests/test_graders.py +0 -0
.env.example
ADDED
|
File without changes
|
Dockerfile
ADDED
|
File without changes
|
README.md
ADDED
|
File without changes
|
api/__init__.py
ADDED
|
File without changes
|
api/server.py
ADDED
|
File without changes
|
baseline.py
ADDED
|
File without changes
|
dataset/easy_cases.json
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "easy_001",
|
| 4 |
+
"category": "syntax",
|
| 5 |
+
"description": "Fix the SQL syntax error: missing comma in SELECT clause.",
|
| 6 |
+
"buggy_query": "SELECT id name email FROM users WHERE active = 1",
|
| 7 |
+
"fixed_query": "SELECT id, name, email FROM users WHERE active = 1",
|
| 8 |
+
"error_message": "ERROR: syntax error at or near 'name'",
|
| 9 |
+
"database_schema": {
|
| 10 |
+
"users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)", "active INT"]
|
| 11 |
+
},
|
| 12 |
+
"expected_output": [
|
| 13 |
+
{"id": 1, "name": "Alice", "email": "alice@example.com"},
|
| 14 |
+
{"id": 2, "name": "Bob", "email": "bob@example.com"}
|
| 15 |
+
],
|
| 16 |
+
"error_type": "syntax",
|
| 17 |
+
"error_location": "SELECT clause",
|
| 18 |
+
"fix_description": "Add missing commas between column names in SELECT clause",
|
| 19 |
+
"estimated_fix_steps": 2,
|
| 20 |
+
"partial_credit_hints": {
|
| 21 |
+
"identifies_location": 0.2,
|
| 22 |
+
"identifies_error_type": 0.2,
|
| 23 |
+
"correct_fix": 0.5,
|
| 24 |
+
"explanation": 0.1
|
| 25 |
+
}
|
| 26 |
+
},
|
| 27 |
+
{
|
| 28 |
+
"id": "easy_002",
|
| 29 |
+
"category": "syntax",
|
| 30 |
+
"description": "Fix the SQL syntax error: missing WHERE keyword.",
|
| 31 |
+
"buggy_query": "SELECT * FROM orders id = 101",
|
| 32 |
+
"fixed_query": "SELECT * FROM orders WHERE id = 101",
|
| 33 |
+
"error_message": "ERROR: syntax error at or near 'id'",
|
| 34 |
+
"database_schema": {
|
| 35 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL", "status VARCHAR(20)"]
|
| 36 |
+
},
|
| 37 |
+
"expected_output": [
|
| 38 |
+
{"id": 101, "user_id": 5, "total": 250.00, "status": "shipped"}
|
| 39 |
+
],
|
| 40 |
+
"error_type": "syntax",
|
| 41 |
+
"error_location": "FROM clause",
|
| 42 |
+
"fix_description": "Add missing WHERE keyword before the filter condition",
|
| 43 |
+
"estimated_fix_steps": 2,
|
| 44 |
+
"partial_credit_hints": {
|
| 45 |
+
"identifies_location": 0.2,
|
| 46 |
+
"identifies_error_type": 0.2,
|
| 47 |
+
"correct_fix": 0.5,
|
| 48 |
+
"explanation": 0.1
|
| 49 |
+
}
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"id": "easy_003",
|
| 53 |
+
"category": "syntax",
|
| 54 |
+
"description": "Fix the SQL syntax error: unclosed string literal.",
|
| 55 |
+
"buggy_query": "SELECT * FROM products WHERE category = 'electronics",
|
| 56 |
+
"fixed_query": "SELECT * FROM products WHERE category = 'electronics'",
|
| 57 |
+
"error_message": "ERROR: unterminated quoted string at or near \"'electronics\"",
|
| 58 |
+
"database_schema": {
|
| 59 |
+
"products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "category VARCHAR(50)", "price DECIMAL"]
|
| 60 |
+
},
|
| 61 |
+
"expected_output": [
|
| 62 |
+
{"id": 3, "name": "Laptop", "category": "electronics", "price": 999.99}
|
| 63 |
+
],
|
| 64 |
+
"error_type": "syntax",
|
| 65 |
+
"error_location": "WHERE clause string literal",
|
| 66 |
+
"fix_description": "Close the single quote at the end of 'electronics'",
|
| 67 |
+
"estimated_fix_steps": 1,
|
| 68 |
+
"partial_credit_hints": {
|
| 69 |
+
"identifies_location": 0.2,
|
| 70 |
+
"identifies_error_type": 0.2,
|
| 71 |
+
"correct_fix": 0.5,
|
| 72 |
+
"explanation": 0.1
|
| 73 |
+
}
|
| 74 |
+
},
|
| 75 |
+
{
|
| 76 |
+
"id": "easy_004",
|
| 77 |
+
"category": "syntax",
|
| 78 |
+
"description": "Fix the SQL syntax error: wrong keyword ORDER used instead of ORDER BY.",
|
| 79 |
+
"buggy_query": "SELECT id, name, price FROM products ORDER price DESC",
|
| 80 |
+
"fixed_query": "SELECT id, name, price FROM products ORDER BY price DESC",
|
| 81 |
+
"error_message": "ERROR: syntax error at or near 'price'",
|
| 82 |
+
"database_schema": {
|
| 83 |
+
"products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "category VARCHAR(50)", "price DECIMAL"]
|
| 84 |
+
},
|
| 85 |
+
"expected_output": [
|
| 86 |
+
{"id": 7, "name": "TV", "price": 1200.00},
|
| 87 |
+
{"id": 3, "name": "Laptop", "price": 999.99}
|
| 88 |
+
],
|
| 89 |
+
"error_type": "syntax",
|
| 90 |
+
"error_location": "ORDER clause",
|
| 91 |
+
"fix_description": "Replace ORDER with ORDER BY",
|
| 92 |
+
"estimated_fix_steps": 1,
|
| 93 |
+
"partial_credit_hints": {
|
| 94 |
+
"identifies_location": 0.2,
|
| 95 |
+
"identifies_error_type": 0.2,
|
| 96 |
+
"correct_fix": 0.5,
|
| 97 |
+
"explanation": 0.1
|
| 98 |
+
}
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"id": "easy_005",
|
| 102 |
+
"category": "syntax",
|
| 103 |
+
"description": "Fix the SQL syntax error: GROUP used instead of GROUP BY.",
|
| 104 |
+
"buggy_query": "SELECT department, COUNT(*) FROM employees GROUP department",
|
| 105 |
+
"fixed_query": "SELECT department, COUNT(*) FROM employees GROUP BY department",
|
| 106 |
+
"error_message": "ERROR: syntax error at or near 'department'",
|
| 107 |
+
"database_schema": {
|
| 108 |
+
"employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
|
| 109 |
+
},
|
| 110 |
+
"expected_output": [
|
| 111 |
+
{"department": "Engineering", "count": 12},
|
| 112 |
+
{"department": "Marketing", "count": 8}
|
| 113 |
+
],
|
| 114 |
+
"error_type": "syntax",
|
| 115 |
+
"error_location": "GROUP clause",
|
| 116 |
+
"fix_description": "Replace GROUP with GROUP BY",
|
| 117 |
+
"estimated_fix_steps": 1,
|
| 118 |
+
"partial_credit_hints": {
|
| 119 |
+
"identifies_location": 0.2,
|
| 120 |
+
"identifies_error_type": 0.2,
|
| 121 |
+
"correct_fix": 0.5,
|
| 122 |
+
"explanation": 0.1
|
| 123 |
+
}
|
| 124 |
+
}
|
| 125 |
+
]
|
dataset/hard_cases.json
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "hard_001",
|
| 4 |
+
"category": "performance",
|
| 5 |
+
"description": "Detect and fix N+1 query pattern: query fetches all users then runs a subquery per user inside SELECT, causing O(n) database hits.",
|
| 6 |
+
"buggy_query": "SELECT id, name, (SELECT COUNT(*) FROM orders WHERE user_id = u.id) as order_count, (SELECT SUM(total) FROM orders WHERE user_id = u.id) as total_spent FROM users u",
|
| 7 |
+
"fixed_query": "SELECT u.id, u.name, COUNT(o.id) as order_count, COALESCE(SUM(o.total), 0) as total_spent FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
|
| 8 |
+
"error_message": "No error thrown — query is functionally correct but causes severe performance degradation at scale (O(n) subqueries)",
|
| 9 |
+
"database_schema": {
|
| 10 |
+
"users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)"],
|
| 11 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT REFERENCES users(id)", "total DECIMAL", "status VARCHAR(20)"]
|
| 12 |
+
},
|
| 13 |
+
"performance_issue": {
|
| 14 |
+
"type": "N+1 query",
|
| 15 |
+
"impact": "With 10,000 users: 20,001 database round trips vs 1",
|
| 16 |
+
"execution_time_buggy_ms": 8500,
|
| 17 |
+
"execution_time_fixed_ms": 45
|
| 18 |
+
},
|
| 19 |
+
"expected_output": [
|
| 20 |
+
{"id": 1, "name": "Alice", "order_count": 3, "total_spent": 750.00},
|
| 21 |
+
{"id": 2, "name": "Bob", "order_count": 0, "total_spent": 0.00}
|
| 22 |
+
],
|
| 23 |
+
"error_type": "performance",
|
| 24 |
+
"error_location": "Correlated subqueries in SELECT clause",
|
| 25 |
+
"fix_description": "Replace correlated subqueries with a single LEFT JOIN + GROUP BY aggregation using COALESCE for null safety",
|
| 26 |
+
"estimated_fix_steps": 6,
|
| 27 |
+
"scoring_rubric": {
|
| 28 |
+
"identifies_n_plus_1_pattern": 0.15,
|
| 29 |
+
"identifies_correlated_subquery_cause": 0.1,
|
| 30 |
+
"proposes_join_solution": 0.1,
|
| 31 |
+
"correct_left_join": 0.1,
|
| 32 |
+
"correct_aggregation": 0.1,
|
| 33 |
+
"coalesce_null_safety": 0.05,
|
| 34 |
+
"explanation_quality": 0.1,
|
| 35 |
+
"confidence": 0.05
|
| 36 |
+
},
|
| 37 |
+
"frontier_model_expected_score": 0.15
|
| 38 |
+
},
|
| 39 |
+
{
|
| 40 |
+
"id": "hard_002",
|
| 41 |
+
"category": "performance",
|
| 42 |
+
"description": "Fix missing index causing full table scan: query filters on non-indexed column in 10M row table, fix by identifying missing index and rewriting with index-friendly pattern.",
|
| 43 |
+
"buggy_query": "SELECT * FROM logs WHERE YEAR(created_at) = 2024 AND MONTH(created_at) = 3",
|
| 44 |
+
"fixed_query": "SELECT * FROM logs WHERE created_at >= '2024-03-01' AND created_at < '2024-04-01'",
|
| 45 |
+
"error_message": "No error thrown — runs but causes full table scan because function on indexed column prevents index use",
|
| 46 |
+
"database_schema": {
|
| 47 |
+
"logs": ["id BIGINT PRIMARY KEY", "user_id INT", "action VARCHAR(100)", "created_at TIMESTAMP", "INDEX(created_at)"]
|
| 48 |
+
},
|
| 49 |
+
"performance_issue": {
|
| 50 |
+
"type": "Function on indexed column prevents index use (full table scan)",
|
| 51 |
+
"impact": "10M row table: full scan 12s vs index seek 8ms",
|
| 52 |
+
"execution_time_buggy_ms": 12000,
|
| 53 |
+
"execution_time_fixed_ms": 8,
|
| 54 |
+
"rows_scanned_buggy": 10000000,
|
| 55 |
+
"rows_scanned_fixed": 85000
|
| 56 |
+
},
|
| 57 |
+
"expected_output": "Same rows — functionally equivalent but uses index",
|
| 58 |
+
"error_type": "performance",
|
| 59 |
+
"error_location": "WHERE clause: YEAR() and MONTH() functions prevent index usage on created_at",
|
| 60 |
+
"fix_description": "Replace function-wrapped column with range comparison using explicit date literals so the index on created_at is used",
|
| 61 |
+
"estimated_fix_steps": 5,
|
| 62 |
+
"scoring_rubric": {
|
| 63 |
+
"identifies_function_on_column_issue": 0.15,
|
| 64 |
+
"identifies_index_not_used": 0.15,
|
| 65 |
+
"correct_range_rewrite": 0.15,
|
| 66 |
+
"correct_date_boundaries": 0.1,
|
| 67 |
+
"explanation_quality": 0.1,
|
| 68 |
+
"confidence": 0.05
|
| 69 |
+
},
|
| 70 |
+
"frontier_model_expected_score": 0.12
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"id": "hard_003",
|
| 74 |
+
"category": "performance",
|
| 75 |
+
"description": "Fix implicit cartesian product: missing JOIN condition between two large tables causes cross join — billions of rows.",
|
| 76 |
+
"buggy_query": "SELECT c.name, o.total FROM customers c, orders o WHERE o.total > 1000",
|
| 77 |
+
"fixed_query": "SELECT c.name, o.total FROM customers c JOIN orders o ON c.id = o.customer_id WHERE o.total > 1000",
|
| 78 |
+
"error_message": "No error thrown — query returns astronomically wrong result set (cartesian product)",
|
| 79 |
+
"database_schema": {
|
| 80 |
+
"customers": ["id INT PRIMARY KEY", "name VARCHAR(100)", "country VARCHAR(50)"],
|
| 81 |
+
"orders": ["id INT PRIMARY KEY", "customer_id INT REFERENCES customers(id)", "total DECIMAL", "status VARCHAR(20)"]
|
| 82 |
+
},
|
| 83 |
+
"performance_issue": {
|
| 84 |
+
"type": "Implicit cartesian product (missing JOIN condition)",
|
| 85 |
+
"impact": "50K customers × 200K orders = 10 billion intermediate rows",
|
| 86 |
+
"execution_time_buggy_ms": 999999,
|
| 87 |
+
"execution_time_fixed_ms": 120
|
| 88 |
+
},
|
| 89 |
+
"expected_output": [
|
| 90 |
+
{"name": "Alice Corp", "total": 15000.00},
|
| 91 |
+
{"name": "Bob Ltd", "total": 3200.00}
|
| 92 |
+
],
|
| 93 |
+
"error_type": "performance",
|
| 94 |
+
"error_location": "FROM clause — two tables listed with comma but no JOIN condition",
|
| 95 |
+
"fix_description": "Replace implicit comma join with explicit JOIN ON c.id = o.customer_id to eliminate cartesian product",
|
| 96 |
+
"estimated_fix_steps": 5,
|
| 97 |
+
"scoring_rubric": {
|
| 98 |
+
"identifies_cartesian_product": 0.15,
|
| 99 |
+
"identifies_missing_join_condition": 0.15,
|
| 100 |
+
"correct_explicit_join": 0.15,
|
| 101 |
+
"correct_join_condition": 0.1,
|
| 102 |
+
"explanation_quality": 0.1,
|
| 103 |
+
"confidence": 0.05
|
| 104 |
+
},
|
| 105 |
+
"frontier_model_expected_score": 0.18
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"id": "hard_004",
|
| 109 |
+
"category": "performance",
|
| 110 |
+
"description": "Fix SELECT * in JOIN query causing unnecessary data transfer and preventing covering index: rewrite to select only needed columns.",
|
| 111 |
+
"buggy_query": "SELECT * FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id WHERE o.status = 'pending'",
|
| 112 |
+
"fixed_query": "SELECT o.id, o.total, o.status, u.name as user_name, u.email, p.name as product_name, p.price FROM orders o JOIN users u ON o.user_id = u.id JOIN products p ON o.product_id = p.id WHERE o.status = 'pending'",
|
| 113 |
+
"error_message": "No error — functionally correct but causes over-fetching, prevents covering index, increases memory pressure",
|
| 114 |
+
"database_schema": {
|
| 115 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT", "product_id INT", "total DECIMAL", "status VARCHAR(20)", "created_at TIMESTAMP", "updated_at TIMESTAMP", "notes TEXT"],
|
| 116 |
+
"users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)", "password_hash VARCHAR(255)", "created_at TIMESTAMP"],
|
| 117 |
+
"products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "price DECIMAL", "stock INT", "description TEXT", "image_url VARCHAR(500)"]
|
| 118 |
+
},
|
| 119 |
+
"performance_issue": {
|
| 120 |
+
"type": "SELECT * with JOINs causes over-fetching and prevents covering index",
|
| 121 |
+
"impact": "Fetches 15+ columns per row including TEXT blobs vs 7 needed columns. 3x memory overhead.",
|
| 122 |
+
"execution_time_buggy_ms": 890,
|
| 123 |
+
"execution_time_fixed_ms": 210
|
| 124 |
+
},
|
| 125 |
+
"error_type": "performance",
|
| 126 |
+
"error_location": "SELECT * across 3-table JOIN",
|
| 127 |
+
"fix_description": "Replace SELECT * with explicit column list selecting only the 7 columns actually needed by the application",
|
| 128 |
+
"estimated_fix_steps": 5,
|
| 129 |
+
"scoring_rubric": {
|
| 130 |
+
"identifies_select_star_issue": 0.15,
|
| 131 |
+
"identifies_over_fetching": 0.1,
|
| 132 |
+
"identifies_covering_index_benefit": 0.1,
|
| 133 |
+
"correct_column_selection": 0.15,
|
| 134 |
+
"correct_table_aliases": 0.1,
|
| 135 |
+
"explanation_quality": 0.1,
|
| 136 |
+
"confidence": 0.05
|
| 137 |
+
},
|
| 138 |
+
"frontier_model_expected_score": 0.20
|
| 139 |
+
},
|
| 140 |
+
{
|
| 141 |
+
"id": "hard_005",
|
| 142 |
+
"category": "performance",
|
| 143 |
+
"description": "Fix window function misuse: ROW_NUMBER() applied without PARTITION causing global ranking instead of per-department ranking, and without ORDER BY making results non-deterministic.",
|
| 144 |
+
"buggy_query": "SELECT id, name, department, salary, ROW_NUMBER() OVER () as rank FROM employees WHERE ROW_NUMBER() OVER () <= 3",
|
| 145 |
+
"fixed_query": "SELECT id, name, department, salary, rank FROM (SELECT id, name, department, salary, ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank FROM employees) ranked WHERE rank <= 3",
|
| 146 |
+
"error_message": "ERROR: window functions are not allowed in WHERE clause",
|
| 147 |
+
"database_schema": {
|
| 148 |
+
"employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL", "hire_date DATE"]
|
| 149 |
+
},
|
| 150 |
+
"expected_output": [
|
| 151 |
+
{"id": 3, "name": "Carol", "department": "Engineering", "salary": 120000, "rank": 1},
|
| 152 |
+
{"id": 7, "name": "Dave", "department": "Engineering", "salary": 110000, "rank": 2},
|
| 153 |
+
{"id": 12, "name": "Eve", "department": "Marketing", "salary": 95000, "rank": 1}
|
| 154 |
+
],
|
| 155 |
+
"error_type": "performance",
|
| 156 |
+
"error_location": "Window function in WHERE clause and missing PARTITION BY + ORDER BY",
|
| 157 |
+
"fix_description": "Wrap in subquery to filter on window function result, add PARTITION BY department and ORDER BY salary DESC for correct per-department ranking",
|
| 158 |
+
"estimated_fix_steps": 7,
|
| 159 |
+
"scoring_rubric": {
|
| 160 |
+
"identifies_window_in_where_error": 0.1,
|
| 161 |
+
"identifies_missing_partition_by": 0.1,
|
| 162 |
+
"identifies_missing_order_by": 0.1,
|
| 163 |
+
"correct_subquery_wrapper": 0.1,
|
| 164 |
+
"correct_partition_by": 0.1,
|
| 165 |
+
"correct_order_by": 0.1,
|
| 166 |
+
"correct_where_on_subquery": 0.1,
|
| 167 |
+
"explanation_quality": 0.1,
|
| 168 |
+
"confidence": 0.05
|
| 169 |
+
},
|
| 170 |
+
"frontier_model_expected_score": 0.10
|
| 171 |
+
}
|
| 172 |
+
]
|
dataset/medium_cases.json
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"id": "medium_001",
|
| 4 |
+
"category": "logic",
|
| 5 |
+
"description": "Fix the wrong JOIN type: INNER JOIN excludes users with no orders. Should be LEFT JOIN to include all users.",
|
| 6 |
+
"buggy_query": "SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u INNER JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
|
| 7 |
+
"fixed_query": "SELECT u.id, u.name, COUNT(o.id) as order_count FROM users u LEFT JOIN orders o ON u.id = o.user_id GROUP BY u.id, u.name",
|
| 8 |
+
"error_message": "No error thrown — query runs but returns wrong results (missing users with 0 orders)",
|
| 9 |
+
"database_schema": {
|
| 10 |
+
"users": ["id INT PRIMARY KEY", "name VARCHAR(100)", "email VARCHAR(100)"],
|
| 11 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT REFERENCES users(id)", "total DECIMAL", "status VARCHAR(20)"]
|
| 12 |
+
},
|
| 13 |
+
"expected_output": [
|
| 14 |
+
{"id": 1, "name": "Alice", "order_count": 3},
|
| 15 |
+
{"id": 2, "name": "Bob", "order_count": 0},
|
| 16 |
+
{"id": 3, "name": "Carol", "order_count": 1}
|
| 17 |
+
],
|
| 18 |
+
"actual_buggy_output": [
|
| 19 |
+
{"id": 1, "name": "Alice", "order_count": 3},
|
| 20 |
+
{"id": 3, "name": "Carol", "order_count": 1}
|
| 21 |
+
],
|
| 22 |
+
"error_type": "logic",
|
| 23 |
+
"error_location": "JOIN type",
|
| 24 |
+
"fix_description": "Change INNER JOIN to LEFT JOIN so users with zero orders are included",
|
| 25 |
+
"estimated_fix_steps": 3,
|
| 26 |
+
"partial_credit_hints": {
|
| 27 |
+
"identifies_missing_rows": 0.15,
|
| 28 |
+
"identifies_join_type_issue": 0.2,
|
| 29 |
+
"correct_join_fix": 0.4,
|
| 30 |
+
"explanation": 0.15,
|
| 31 |
+
"confidence": 0.1
|
| 32 |
+
}
|
| 33 |
+
},
|
| 34 |
+
{
|
| 35 |
+
"id": "medium_002",
|
| 36 |
+
"category": "logic",
|
| 37 |
+
"description": "Fix the wrong JOIN condition: joining on wrong column causes cartesian-like explosion of results.",
|
| 38 |
+
"buggy_query": "SELECT o.id, o.total, p.name FROM orders o JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.id = p.id",
|
| 39 |
+
"fixed_query": "SELECT o.id, o.total, p.name FROM orders o JOIN order_items oi ON o.id = oi.order_id JOIN products p ON oi.product_id = p.id",
|
| 40 |
+
"error_message": "No error thrown — query runs but returns incorrect product associations",
|
| 41 |
+
"database_schema": {
|
| 42 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL"],
|
| 43 |
+
"order_items": ["id INT PRIMARY KEY", "order_id INT", "product_id INT", "quantity INT"],
|
| 44 |
+
"products": ["id INT PRIMARY KEY", "name VARCHAR(100)", "price DECIMAL"]
|
| 45 |
+
},
|
| 46 |
+
"expected_output": [
|
| 47 |
+
{"id": 101, "total": 250.00, "name": "Laptop"},
|
| 48 |
+
{"id": 102, "total": 89.99, "name": "Mouse"}
|
| 49 |
+
],
|
| 50 |
+
"error_type": "logic",
|
| 51 |
+
"error_location": "Second JOIN condition — oi.id should be oi.product_id",
|
| 52 |
+
"fix_description": "Change ON oi.id = p.id to ON oi.product_id = p.id",
|
| 53 |
+
"estimated_fix_steps": 3,
|
| 54 |
+
"partial_credit_hints": {
|
| 55 |
+
"identifies_wrong_column": 0.2,
|
| 56 |
+
"identifies_join_condition": 0.2,
|
| 57 |
+
"correct_fix": 0.4,
|
| 58 |
+
"explanation": 0.15,
|
| 59 |
+
"confidence": 0.05
|
| 60 |
+
}
|
| 61 |
+
},
|
| 62 |
+
{
|
| 63 |
+
"id": "medium_003",
|
| 64 |
+
"category": "logic",
|
| 65 |
+
"description": "Fix the aggregation logic: HAVING clause filters before GROUP BY aggregation is complete, wrong column used.",
|
| 66 |
+
"buggy_query": "SELECT department, AVG(salary) as avg_salary FROM employees WHERE AVG(salary) > 70000 GROUP BY department",
|
| 67 |
+
"fixed_query": "SELECT department, AVG(salary) as avg_salary FROM employees GROUP BY department HAVING AVG(salary) > 70000",
|
| 68 |
+
"error_message": "ERROR: aggregate functions are not allowed in WHERE",
|
| 69 |
+
"database_schema": {
|
| 70 |
+
"employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
|
| 71 |
+
},
|
| 72 |
+
"expected_output": [
|
| 73 |
+
{"department": "Engineering", "avg_salary": 95000.00},
|
| 74 |
+
{"department": "Data Science","avg_salary": 88000.00}
|
| 75 |
+
],
|
| 76 |
+
"error_type": "logic",
|
| 77 |
+
"error_location": "WHERE clause — aggregate function used in WHERE instead of HAVING",
|
| 78 |
+
"fix_description": "Move AVG(salary) > 70000 from WHERE to HAVING clause, placed after GROUP BY",
|
| 79 |
+
"estimated_fix_steps": 3,
|
| 80 |
+
"partial_credit_hints": {
|
| 81 |
+
"identifies_where_vs_having": 0.2,
|
| 82 |
+
"identifies_aggregate_misuse": 0.2,
|
| 83 |
+
"correct_fix": 0.4,
|
| 84 |
+
"explanation": 0.15,
|
| 85 |
+
"confidence": 0.05
|
| 86 |
+
}
|
| 87 |
+
},
|
| 88 |
+
{
|
| 89 |
+
"id": "medium_004",
|
| 90 |
+
"category": "logic",
|
| 91 |
+
"description": "Fix the subquery logic: correlated subquery compares wrong column, returning incorrect filtered set.",
|
| 92 |
+
"buggy_query": "SELECT id, name, salary FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.id)",
|
| 93 |
+
"fixed_query": "SELECT id, name, salary FROM employees e WHERE salary > (SELECT AVG(salary) FROM employees WHERE department = e.department)",
|
| 94 |
+
"error_message": "No error thrown — returns incorrect rows because subquery correlates on wrong column",
|
| 95 |
+
"database_schema": {
|
| 96 |
+
"employees": ["id INT PRIMARY KEY", "name VARCHAR(100)", "department VARCHAR(50)", "salary DECIMAL"]
|
| 97 |
+
},
|
| 98 |
+
"expected_output": [
|
| 99 |
+
{"id": 3, "name": "Carol", "salary": 110000},
|
| 100 |
+
{"id": 7, "name": "Dave", "salary": 98000}
|
| 101 |
+
],
|
| 102 |
+
"error_type": "logic",
|
| 103 |
+
"error_location": "Correlated subquery WHERE clause — e.id should be e.department",
|
| 104 |
+
"fix_description": "Change WHERE department = e.id to WHERE department = e.department",
|
| 105 |
+
"estimated_fix_steps": 4,
|
| 106 |
+
"partial_credit_hints": {
|
| 107 |
+
"identifies_correlated_subquery": 0.15,
|
| 108 |
+
"identifies_wrong_correlation": 0.2,
|
| 109 |
+
"correct_fix": 0.45,
|
| 110 |
+
"explanation": 0.15,
|
| 111 |
+
"confidence": 0.05
|
| 112 |
+
}
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"id": "medium_005",
|
| 116 |
+
"category": "logic",
|
| 117 |
+
"description": "Fix the DISTINCT misuse: DISTINCT applied to wrong scope, counting duplicates incorrectly.",
|
| 118 |
+
"buggy_query": "SELECT COUNT(DISTINCT *) FROM orders WHERE status = 'completed'",
|
| 119 |
+
"fixed_query": "SELECT COUNT(DISTINCT id) FROM orders WHERE status = 'completed'",
|
| 120 |
+
"error_message": "ERROR: COUNT(DISTINCT) with multiple columns requires explicit column list",
|
| 121 |
+
"database_schema": {
|
| 122 |
+
"orders": ["id INT PRIMARY KEY", "user_id INT", "total DECIMAL", "status VARCHAR(20)"]
|
| 123 |
+
},
|
| 124 |
+
"expected_output": [{"count": 47}],
|
| 125 |
+
"error_type": "logic",
|
| 126 |
+
"error_location": "COUNT(DISTINCT *) — wildcard not valid with DISTINCT",
|
| 127 |
+
"fix_description": "Replace COUNT(DISTINCT *) with COUNT(DISTINCT id) to count unique order IDs",
|
| 128 |
+
"estimated_fix_steps": 2,
|
| 129 |
+
"partial_credit_hints": {
|
| 130 |
+
"identifies_distinct_misuse": 0.25,
|
| 131 |
+
"correct_fix": 0.5,
|
| 132 |
+
"explanation": 0.15,
|
| 133 |
+
"confidence": 0.1
|
| 134 |
+
}
|
| 135 |
+
}
|
| 136 |
+
]
|
env/__init__.py
ADDED
|
File without changes
|
env/__pycache__/__init__.cpython-312.pyc
ADDED
|
Binary file (134 Bytes). View file
|
|
|
env/__pycache__/models.cpython-312.pyc
ADDED
|
Binary file (9.17 kB). View file
|
|
|
env/environment.py
ADDED
|
File without changes
|
env/graders.py
ADDED
|
File without changes
|
env/models.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from pydantic import BaseModel, Field, field_validator
|
| 2 |
+
from typing import Optional, Any
|
| 3 |
+
from enum import Enum
|
| 4 |
+
import time
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# ENUMS
|
| 8 |
+
|
| 9 |
+
class DifficultyLevel(str, Enum):
|
| 10 |
+
EASY = "easy"
|
| 11 |
+
MEDIUM = "medium"
|
| 12 |
+
HARD = "hard"
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class ActionType(str, Enum):
|
| 16 |
+
IDENTIFY_ERROR = "identify_error"
|
| 17 |
+
PROPOSE_FIX = "propose_fix"
|
| 18 |
+
SUBMIT_ANSWER = "submit_answer"
|
| 19 |
+
REQUEST_HINT = "request_hint"
|
| 20 |
+
EXPLAIN_ISSUE = "explain_issue"
|
| 21 |
+
OPTIMIZE_QUERY = "optimize_query"
|
| 22 |
+
|
| 23 |
+
# CORE MODELS
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
class Observation(BaseModel):
|
| 27 |
+
task_id: str = Field(..., description="Unique task identifier")
|
| 28 |
+
task_description: str = Field(..., description="What the agent must do")
|
| 29 |
+
current_context: dict = Field(..., description="What the agent currently sees")
|
| 30 |
+
step_count: int = Field(default=0, ge=0, description="Steps taken so far")
|
| 31 |
+
difficulty: DifficultyLevel = Field(..., description="Task difficulty level")
|
| 32 |
+
max_steps: int = Field(default=20, description="Maximum steps allowed")
|
| 33 |
+
hints_used: int = Field(default=0, description="Number of hints used")
|
| 34 |
+
previous_actions: list[str] = Field(default_factory=list, description="History of action types taken")
|
| 35 |
+
metadata: dict = Field(default_factory=dict, description="Extra task metadata")
|
| 36 |
+
|
| 37 |
+
model_config = {"json_schema_extra": {
|
| 38 |
+
"example": {
|
| 39 |
+
"task_id": "easy_001",
|
| 40 |
+
"task_description": "Fix the SQL syntax error in the query below.",
|
| 41 |
+
"current_context": {
|
| 42 |
+
"buggy_query": "SELECT id, name FROM users WHERE id = 1 AND",
|
| 43 |
+
"error_message": "SyntaxError: unexpected end of input",
|
| 44 |
+
"database_schema": "users(id INT, name VARCHAR, email VARCHAR)"
|
| 45 |
+
},
|
| 46 |
+
"step_count": 0,
|
| 47 |
+
"difficulty": "easy",
|
| 48 |
+
"max_steps": 20,
|
| 49 |
+
"hints_used": 0,
|
| 50 |
+
"previous_actions": [],
|
| 51 |
+
"metadata": {"category": "syntax", "estimated_fix_steps": 2}
|
| 52 |
+
}
|
| 53 |
+
}}
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
class Action(BaseModel):
|
| 57 |
+
action_type: ActionType = Field(..., description="Type of action the agent is taking")
|
| 58 |
+
payload: dict = Field(..., description="Action-specific data")
|
| 59 |
+
|
| 60 |
+
@field_validator("payload")
|
| 61 |
+
@classmethod
|
| 62 |
+
def payload_must_not_be_empty(cls, v):
|
| 63 |
+
if v is None:
|
| 64 |
+
raise ValueError("Payload cannot be None")
|
| 65 |
+
return v
|
| 66 |
+
|
| 67 |
+
@field_validator("payload")
|
| 68 |
+
@classmethod
|
| 69 |
+
def truncate_long_strings(cls, v):
|
| 70 |
+
# Edge case: extremely long agent output — truncate gracefully
|
| 71 |
+
def truncate(obj, max_len=5000):
|
| 72 |
+
if isinstance(obj, str) and len(obj) > max_len:
|
| 73 |
+
return obj[:max_len] + "...[truncated]"
|
| 74 |
+
if isinstance(obj, dict):
|
| 75 |
+
return {k: truncate(val, max_len) for k, val in obj.items()}
|
| 76 |
+
return obj
|
| 77 |
+
return truncate(v)
|
| 78 |
+
|
| 79 |
+
model_config = {"json_schema_extra": {
|
| 80 |
+
"example": {
|
| 81 |
+
"action_type": "submit_answer",
|
| 82 |
+
"payload": {
|
| 83 |
+
"fixed_query": "SELECT id, name FROM users WHERE id = 1",
|
| 84 |
+
"explanation": "Removed the trailing AND which caused a syntax error",
|
| 85 |
+
"error_type": "syntax",
|
| 86 |
+
"confidence": 0.95
|
| 87 |
+
}
|
| 88 |
+
}
|
| 89 |
+
}}
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class Reward(BaseModel):
|
| 93 |
+
score: float = Field(..., ge=-1.0, le=1.0, description="Reward score between -1.0 and 1.0")
|
| 94 |
+
breakdown: dict = Field(..., description="Partial credit details per dimension")
|
| 95 |
+
feedback: str = Field(..., description="Human-readable explanation of the reward")
|
| 96 |
+
|
| 97 |
+
@field_validator("score")
|
| 98 |
+
@classmethod
|
| 99 |
+
def clamp_score(cls, v):
|
| 100 |
+
return max(-1.0, min(1.0, round(v, 4)))
|
| 101 |
+
|
| 102 |
+
model_config = {"json_schema_extra": {
|
| 103 |
+
"example": {
|
| 104 |
+
"score": 0.75,
|
| 105 |
+
"breakdown": {
|
| 106 |
+
"correct_answer": 0.5,
|
| 107 |
+
"explanation": 0.2,
|
| 108 |
+
"confidence": 0.05,
|
| 109 |
+
"step_efficiency": 0.0
|
| 110 |
+
},
|
| 111 |
+
"feedback": "Correct fix applied. Good explanation provided. Minor efficiency penalty."
|
| 112 |
+
}
|
| 113 |
+
}}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
# EPISODE STATE (used by state() endpoint)
|
| 117 |
+
|
| 118 |
+
class EpisodeState(BaseModel):
|
| 119 |
+
task_id: Optional[str] = Field(default=None)
|
| 120 |
+
difficulty: Optional[DifficultyLevel] = Field(default=None)
|
| 121 |
+
step_count: int = Field(default=0)
|
| 122 |
+
total_reward: float = Field(default=0.0)
|
| 123 |
+
done: bool = Field(default=False)
|
| 124 |
+
hints_used: int = Field(default=0)
|
| 125 |
+
previous_actions: list[str] = Field(default_factory=list)
|
| 126 |
+
action_counts: dict[str, int] = Field(default_factory=dict)
|
| 127 |
+
started_at: Optional[float] = Field(default=None)
|
| 128 |
+
last_reward: float = Field(default=0.0)
|
| 129 |
+
initialized: bool = Field(default=False)
|
| 130 |
+
|
| 131 |
+
model_config = {"json_schema_extra": {
|
| 132 |
+
"example": {
|
| 133 |
+
"task_id": "medium_002",
|
| 134 |
+
"difficulty": "medium",
|
| 135 |
+
"step_count": 3,
|
| 136 |
+
"total_reward": 0.45,
|
| 137 |
+
"done": False,
|
| 138 |
+
"hints_used": 1,
|
| 139 |
+
"previous_actions": ["identify_error", "request_hint", "propose_fix"],
|
| 140 |
+
"action_counts": {"identify_error": 1, "request_hint": 1, "propose_fix": 1},
|
| 141 |
+
"started_at": 1700000000.0,
|
| 142 |
+
"last_reward": 0.25,
|
| 143 |
+
"initialized": True
|
| 144 |
+
}
|
| 145 |
+
}}
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
# API REQUEST / RESPONSE WRAPPERS
|
| 149 |
+
|
| 150 |
+
class StepResponse(BaseModel):
|
| 151 |
+
observation: Observation
|
| 152 |
+
reward: Reward
|
| 153 |
+
done: bool
|
| 154 |
+
info: dict
|
| 155 |
+
|
| 156 |
+
class ResetResponse(BaseModel):
|
| 157 |
+
observation: Observation
|
| 158 |
+
|
| 159 |
+
class TaskInfo(BaseModel):
|
| 160 |
+
id: str
|
| 161 |
+
difficulty: DifficultyLevel
|
| 162 |
+
description: str
|
| 163 |
+
action_schema: dict # REQUIRED by validator — field definitions not just names
|
| 164 |
+
|
| 165 |
+
class TaskListResponse(BaseModel):
|
| 166 |
+
tasks: list[TaskInfo]
|
| 167 |
+
total: int
|
| 168 |
+
action_types: list[str]
|
| 169 |
+
|
| 170 |
+
class BaselineResult(BaseModel):
|
| 171 |
+
task_id: str
|
| 172 |
+
difficulty: DifficultyLevel
|
| 173 |
+
score: float
|
| 174 |
+
steps: int
|
| 175 |
+
feedback: str
|
| 176 |
+
|
| 177 |
+
class BaselineResponse(BaseModel):
|
| 178 |
+
results: list[BaselineResult]
|
| 179 |
+
average_score: float
|
| 180 |
+
completed_at: float = Field(default_factory=time.time)
|
| 181 |
+
|
| 182 |
+
class GraderRequest(BaseModel):
|
| 183 |
+
task_id: str
|
| 184 |
+
action: Optional[Action] = None
|
| 185 |
+
episode: Optional[dict] = None
|
| 186 |
+
|
| 187 |
+
class GraderResponse(BaseModel):
|
| 188 |
+
score: float
|
| 189 |
+
feedback: str
|
| 190 |
+
breakdown: dict
|
| 191 |
+
|
| 192 |
+
class HealthResponse(BaseModel):
|
| 193 |
+
status: str = "ok"
|
| 194 |
+
version: str = "1.0.0"
|
| 195 |
+
uptime: float = Field(default_factory=time.time)
|
env/reward.py
ADDED
|
File without changes
|
env/tasks.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import random
|
| 3 |
+
from pathlib import Path
|
| 4 |
+
from env.models import DifficultyLevel, TaskInfo
|
| 5 |
+
|
| 6 |
+
# LOAD DATASETS
|
| 7 |
+
|
| 8 |
+
BASE_DIR = Path(__file__).parent.parent / "dataset"
|
| 9 |
+
|
| 10 |
+
def _load(filename: str) -> list[dict]:
|
| 11 |
+
path = BASE_DIR / filename
|
| 12 |
+
with open(path, "r", encoding="utf-8") as f:
|
| 13 |
+
return json.load(f)
|
| 14 |
+
|
| 15 |
+
EASY_CASES = _load("easy_cases.json")
|
| 16 |
+
MEDIUM_CASES = _load("medium_cases.json")
|
| 17 |
+
HARD_CASES = _load("hard_cases.json")
|
| 18 |
+
|
| 19 |
+
ALL_CASES: dict[str, list[dict]] = {
|
| 20 |
+
DifficultyLevel.EASY: EASY_CASES,
|
| 21 |
+
DifficultyLevel.MEDIUM: MEDIUM_CASES,
|
| 22 |
+
DifficultyLevel.HARD: HARD_CASES,
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
# ACTION SCHEMA (required by /tasks validator)
|
| 26 |
+
|
| 27 |
+
ACTION_SCHEMA = {
|
| 28 |
+
"identify_error": {
|
| 29 |
+
"description": "Identify where and what the error is without fixing it yet",
|
| 30 |
+
"payload_fields": {
|
| 31 |
+
"error_location": {"type": "string", "required": True, "description": "Where in the query the error occurs"},
|
| 32 |
+
"error_type": {"type": "string", "required": True, "description": "Type: syntax | logic | performance"},
|
| 33 |
+
"explanation": {"type": "string", "required": False, "description": "Brief explanation of the error"}
|
| 34 |
+
}
|
| 35 |
+
},
|
| 36 |
+
"propose_fix": {
|
| 37 |
+
"description": "Propose a fix without submitting as final answer",
|
| 38 |
+
"payload_fields": {
|
| 39 |
+
"fixed_query": {"type": "string", "required": True, "description": "The proposed corrected SQL query"},
|
| 40 |
+
"change_made": {"type": "string", "required": True, "description": "What specifically was changed"},
|
| 41 |
+
"confidence": {"type": "float", "required": False, "description": "Confidence score 0.0-1.0"}
|
| 42 |
+
}
|
| 43 |
+
},
|
| 44 |
+
"submit_answer": {
|
| 45 |
+
"description": "Submit the final fixed query as the definitive answer",
|
| 46 |
+
"payload_fields": {
|
| 47 |
+
"fixed_query": {"type": "string", "required": True, "description": "Final corrected SQL query"},
|
| 48 |
+
"explanation": {"type": "string", "required": True, "description": "Full explanation of what was wrong and how it was fixed"},
|
| 49 |
+
"error_type": {"type": "string", "required": False, "description": "Type: syntax | logic | performance"},
|
| 50 |
+
"confidence": {"type": "float", "required": False, "description": "Confidence score 0.0-1.0"}
|
| 51 |
+
}
|
| 52 |
+
},
|
| 53 |
+
"request_hint": {
|
| 54 |
+
"description": "Request a hint — costs 0.05 reward penalty per hint",
|
| 55 |
+
"payload_fields": {
|
| 56 |
+
"hint_type": {"type": "string", "required": False, "description": "Type of hint wanted: location | error_type | fix_direction"}
|
| 57 |
+
}
|
| 58 |
+
},
|
| 59 |
+
"explain_issue": {
|
| 60 |
+
"description": "Explain the issue in detail — earns partial credit even without fixing",
|
| 61 |
+
"payload_fields": {
|
| 62 |
+
"explanation": {"type": "string", "required": True, "description": "Detailed explanation of the SQL problem"},
|
| 63 |
+
"impact": {"type": "string", "required": False, "description": "What impact the bug has on query results or performance"},
|
| 64 |
+
"root_cause": {"type": "string", "required": False, "description": "Root cause analysis"}
|
| 65 |
+
}
|
| 66 |
+
},
|
| 67 |
+
"optimize_query": {
|
| 68 |
+
"description": "Submit an optimized version of the query (used for hard/performance tasks)",
|
| 69 |
+
"payload_fields": {
|
| 70 |
+
"optimized_query": {"type": "string", "required": True, "description": "The performance-optimized SQL query"},
|
| 71 |
+
"optimization_type": {"type": "string", "required": True, "description": "What optimization was applied"},
|
| 72 |
+
"expected_improvement":{"type": "string", "required": False, "description": "Expected performance gain description"},
|
| 73 |
+
"explanation": {"type": "string", "required": False, "description": "Why this optimization works"},
|
| 74 |
+
"confidence": {"type": "float", "required": False, "description": "Confidence 0.0-1.0"}
|
| 75 |
+
}
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
# TASK MANAGER
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class TaskManager:
|
| 82 |
+
"""
|
| 83 |
+
Manages task selection, hint generation, and task metadata.
|
| 84 |
+
All tasks are loaded from JSON datasets — no hardcoded tasks.
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
def __init__(self):
|
| 88 |
+
self._used_ids: set[str] = set()
|
| 89 |
+
|
| 90 |
+
def get_task(self, difficulty: DifficultyLevel, task_id: str | None = None) -> dict:
|
| 91 |
+
"""
|
| 92 |
+
Returns a task dict for the given difficulty.
|
| 93 |
+
If task_id is provided, returns that specific task.
|
| 94 |
+
Otherwise picks randomly, avoiding recently used tasks.
|
| 95 |
+
"""
|
| 96 |
+
pool = ALL_CASES[difficulty]
|
| 97 |
+
|
| 98 |
+
if task_id:
|
| 99 |
+
for case in pool:
|
| 100 |
+
if case["id"] == task_id:
|
| 101 |
+
return case
|
| 102 |
+
raise ValueError(f"Task '{task_id}' not found in {difficulty} pool")
|
| 103 |
+
|
| 104 |
+
# Avoid repeating recently used tasks
|
| 105 |
+
available = [c for c in pool if c["id"] not in self._used_ids]
|
| 106 |
+
if not available:
|
| 107 |
+
self._used_ids.clear()
|
| 108 |
+
available = pool
|
| 109 |
+
|
| 110 |
+
task = random.choice(available)
|
| 111 |
+
self._used_ids.add(task["id"])
|
| 112 |
+
return task
|
| 113 |
+
|
| 114 |
+
def get_random_task(self) -> dict:
|
| 115 |
+
"""Pick a random task from any difficulty."""
|
| 116 |
+
difficulty = random.choice(list(DifficultyLevel))
|
| 117 |
+
return self.get_task(difficulty)
|
| 118 |
+
|
| 119 |
+
def build_observation_context(self, task: dict) -> dict:
|
| 120 |
+
"""
|
| 121 |
+
Build the current_context dict for the Observation.
|
| 122 |
+
CRITICAL: Must NOT leak the fixed_query (ground truth) to the agent.
|
| 123 |
+
"""
|
| 124 |
+
context = {
|
| 125 |
+
"buggy_query": task["buggy_query"],
|
| 126 |
+
"error_message": task["error_message"],
|
| 127 |
+
"database_schema": task["database_schema"],
|
| 128 |
+
"error_type_hint": task["error_type"],
|
| 129 |
+
"category": task["category"],
|
| 130 |
+
"estimated_steps": task["estimated_fix_steps"],
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
# For performance tasks include extra context
|
| 134 |
+
if task.get("performance_issue"):
|
| 135 |
+
context["performance_issue"] = {
|
| 136 |
+
"type": task["performance_issue"]["type"],
|
| 137 |
+
"impact": task["performance_issue"]["impact"],
|
| 138 |
+
# Do NOT include timing numbers — agent must figure it out
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
# Include expected output shape (but not the fixed query!)
|
| 142 |
+
if task.get("expected_output") and isinstance(task["expected_output"], list):
|
| 143 |
+
context["expected_output_sample"] = task["expected_output"][:1]
|
| 144 |
+
|
| 145 |
+
return context
|
| 146 |
+
|
| 147 |
+
def get_hint(self, task: dict, hint_number: int) -> str:
|
| 148 |
+
"""
|
| 149 |
+
Returns progressive hints. Each hint gives more info.
|
| 150 |
+
Hints cost -0.05 reward each (handled in reward.py).
|
| 151 |
+
"""
|
| 152 |
+
hints = [
|
| 153 |
+
f"Hint 1: The error is in the {task.get('error_location', 'query')}.",
|
| 154 |
+
f"Hint 2: This is a {task.get('error_type', 'unknown')} type error. Category: {task.get('category')}.",
|
| 155 |
+
f"Hint 3: Fix description — {task.get('fix_description', 'Review the query carefully.')}",
|
| 156 |
+
]
|
| 157 |
+
idx = min(hint_number - 1, len(hints) - 1)
|
| 158 |
+
return hints[idx]
|
| 159 |
+
|
| 160 |
+
def list_all_tasks(self) -> list[TaskInfo]:
|
| 161 |
+
"""Returns TaskInfo list for the /tasks endpoint."""
|
| 162 |
+
result = []
|
| 163 |
+
for difficulty, cases in ALL_CASES.items():
|
| 164 |
+
for case in cases:
|
| 165 |
+
result.append(TaskInfo(
|
| 166 |
+
id=case["id"],
|
| 167 |
+
difficulty=difficulty,
|
| 168 |
+
description=case["description"],
|
| 169 |
+
action_schema=ACTION_SCHEMA
|
| 170 |
+
))
|
| 171 |
+
return result
|
| 172 |
+
|
| 173 |
+
def get_ground_truth(self, task_id: str) -> dict | None:
|
| 174 |
+
"""Returns the full ground truth for a task (used by grader only)."""
|
| 175 |
+
for cases in ALL_CASES.values():
|
| 176 |
+
for case in cases:
|
| 177 |
+
if case["id"] == task_id:
|
| 178 |
+
return case
|
| 179 |
+
return None
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
# Singleton instance
|
| 183 |
+
task_manager = TaskManager()
|
openenv.yaml
ADDED
|
File without changes
|
requirements.txt
ADDED
|
File without changes
|
tests/test_environment.py
ADDED
|
File without changes
|
tests/test_graders.py
ADDED
|
File without changes
|