Spaces:
Runtime error
Runtime error
Deploy CodeBERT inference Space
Browse files- README.md +1 -1
- README_INFERENCE_SPACE.md +1 -1
- app.py +28 -8
- config/codebert_labels.yaml +2 -0
- config/error_categories.yaml +3 -0
- src/codebert_formatting.py +12 -0
- src/hf_metrics.py +1 -1
- src/hf_predict_codebert.py +27 -4
- src/sql_templates.py +7 -0
README.md
CHANGED
|
@@ -25,7 +25,7 @@ Model: [nishu08/sql-codebert-classifier](https://huggingface.co/nishu08/sql-code
|
|
| 25 |
## Labels
|
| 26 |
|
| 27 |
`JOIN_ERROR`, `AGGREGATION_ERROR`, `FILTER_ERROR`, `WINDOW_FUNCTION_ERROR`,
|
| 28 |
-
`SUBQUERY_ERROR`, `NULL_HANDLING_ERROR`, `PERFORMANCE_ERROR`, `LOGICAL_ERROR`, `SYNTAX_ERROR`
|
| 29 |
|
| 30 |
## Secrets (optional)
|
| 31 |
|
|
|
|
| 25 |
## Labels
|
| 26 |
|
| 27 |
`JOIN_ERROR`, `AGGREGATION_ERROR`, `FILTER_ERROR`, `WINDOW_FUNCTION_ERROR`,
|
| 28 |
+
`SUBQUERY_ERROR`, `NULL_HANDLING_ERROR`, `PERFORMANCE_ERROR`, `LOGICAL_ERROR`, `SYNTAX_ERROR`, `NO_ERROR`
|
| 29 |
|
| 30 |
## Secrets (optional)
|
| 31 |
|
README_INFERENCE_SPACE.md
CHANGED
|
@@ -25,7 +25,7 @@ Model: [nishu08/sql-codebert-classifier](https://huggingface.co/nishu08/sql-code
|
|
| 25 |
## Labels
|
| 26 |
|
| 27 |
`JOIN_ERROR`, `AGGREGATION_ERROR`, `FILTER_ERROR`, `WINDOW_FUNCTION_ERROR`,
|
| 28 |
-
`SUBQUERY_ERROR`, `NULL_HANDLING_ERROR`, `PERFORMANCE_ERROR`, `LOGICAL_ERROR`, `SYNTAX_ERROR`
|
| 29 |
|
| 30 |
## Secrets (optional)
|
| 31 |
|
|
|
|
| 25 |
## Labels
|
| 26 |
|
| 27 |
`JOIN_ERROR`, `AGGREGATION_ERROR`, `FILTER_ERROR`, `WINDOW_FUNCTION_ERROR`,
|
| 28 |
+
`SUBQUERY_ERROR`, `NULL_HANDLING_ERROR`, `PERFORMANCE_ERROR`, `LOGICAL_ERROR`, `SYNTAX_ERROR`, `NO_ERROR`
|
| 29 |
|
| 30 |
## Secrets (optional)
|
| 31 |
|
app.py
CHANGED
|
@@ -42,6 +42,13 @@ EXAMPLES = [
|
|
| 42 |
"SELECT students.name, departments.name FROM students INNER JOIN departments ON students.department_id = departments.id",
|
| 43 |
0.5,
|
| 44 |
],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
]
|
| 46 |
|
| 47 |
|
|
@@ -55,14 +62,27 @@ def classify(question, schema, student_sql, correct_sql, threshold):
|
|
| 55 |
correct_sql=correct_sql.strip(),
|
| 56 |
threshold=threshold,
|
| 57 |
)
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
return summary, probs
|
| 67 |
|
| 68 |
|
|
|
|
| 42 |
"SELECT students.name, departments.name FROM students INNER JOIN departments ON students.department_id = departments.id",
|
| 43 |
0.5,
|
| 44 |
],
|
| 45 |
+
[
|
| 46 |
+
"What is the average score of students in each department?",
|
| 47 |
+
"students(id, name, score, department_id) | departments(id, name)",
|
| 48 |
+
"SELECT department_id, AVG(score) FROM students GROUP BY department_id",
|
| 49 |
+
"SELECT department_id, AVG(score) FROM students GROUP BY department_id",
|
| 50 |
+
0.5,
|
| 51 |
+
],
|
| 52 |
]
|
| 53 |
|
| 54 |
|
|
|
|
| 62 |
correct_sql=correct_sql.strip(),
|
| 63 |
threshold=threshold,
|
| 64 |
)
|
| 65 |
+
if result["primary_label"] == "NO_ERROR":
|
| 66 |
+
if result.get("match_detected"):
|
| 67 |
+
summary = (
|
| 68 |
+
"### No error\n"
|
| 69 |
+
"Student SQL matches the correct answer — no mistake to classify."
|
| 70 |
+
)
|
| 71 |
+
else:
|
| 72 |
+
summary = (
|
| 73 |
+
"### No error\n"
|
| 74 |
+
f"No label exceeded the {threshold:.0%} threshold."
|
| 75 |
+
)
|
| 76 |
+
probs = "_All probabilities below threshold._"
|
| 77 |
+
else:
|
| 78 |
+
summary = (
|
| 79 |
+
f"### {result['primary_label']}\n"
|
| 80 |
+
f"Confidence: **{result['primary_confidence']:.1%}**\n\n"
|
| 81 |
+
f"**Active labels:** {', '.join(result['error_labels']) or 'none'}"
|
| 82 |
+
)
|
| 83 |
+
probs = "\n".join(
|
| 84 |
+
f"- **{k}**: {v:.1%}" for k, v in result["probabilities"].items()
|
| 85 |
+
)
|
| 86 |
return summary, probs
|
| 87 |
|
| 88 |
|
config/codebert_labels.yaml
CHANGED
|
@@ -9,6 +9,7 @@ labels:
|
|
| 9 |
- PERFORMANCE_ERROR
|
| 10 |
- LOGICAL_ERROR
|
| 11 |
- SYNTAX_ERROR
|
|
|
|
| 12 |
|
| 13 |
# Map dataset label_name values → one or more CodeBERT labels (multi-label)
|
| 14 |
alias_map:
|
|
@@ -27,3 +28,4 @@ alias_map:
|
|
| 27 |
TABLE_REFERENCE_ERROR: [SYNTAX_ERROR]
|
| 28 |
DATA_TYPE_ERROR: [SYNTAX_ERROR]
|
| 29 |
DUPLICATE_RECORD_ERROR: [FILTER_ERROR]
|
|
|
|
|
|
| 9 |
- PERFORMANCE_ERROR
|
| 10 |
- LOGICAL_ERROR
|
| 11 |
- SYNTAX_ERROR
|
| 12 |
+
- NO_ERROR
|
| 13 |
|
| 14 |
# Map dataset label_name values → one or more CodeBERT labels (multi-label)
|
| 15 |
alias_map:
|
|
|
|
| 28 |
TABLE_REFERENCE_ERROR: [SYNTAX_ERROR]
|
| 29 |
DATA_TYPE_ERROR: [SYNTAX_ERROR]
|
| 30 |
DUPLICATE_RECORD_ERROR: [FILTER_ERROR]
|
| 31 |
+
NO_ERROR: [NO_ERROR]
|
config/error_categories.yaml
CHANGED
|
@@ -44,3 +44,6 @@ categories:
|
|
| 44 |
- id: 14
|
| 45 |
name: FILTERING_ERROR
|
| 46 |
description: Incorrect WHERE clause
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
- id: 14
|
| 45 |
name: FILTERING_ERROR
|
| 46 |
description: Incorrect WHERE clause
|
| 47 |
+
- id: 15
|
| 48 |
+
name: NO_ERROR
|
| 49 |
+
description: Student SQL matches the correct answer
|
src/codebert_formatting.py
CHANGED
|
@@ -2,12 +2,24 @@
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
|
|
|
|
|
|
| 5 |
QUESTION_TAG = "QUESTION:"
|
| 6 |
SCHEMA_TAG = "SCHEMA:"
|
| 7 |
STUDENT_TAG = "STUDENT_SQL:"
|
| 8 |
CORRECT_TAG = "CORRECT_SQL:"
|
| 9 |
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
def format_cross_encoder_input(
|
| 12 |
question: str,
|
| 13 |
schema: str,
|
|
|
|
| 2 |
|
| 3 |
from __future__ import annotations
|
| 4 |
|
| 5 |
+
import re
|
| 6 |
+
|
| 7 |
QUESTION_TAG = "QUESTION:"
|
| 8 |
SCHEMA_TAG = "SCHEMA:"
|
| 9 |
STUDENT_TAG = "STUDENT_SQL:"
|
| 10 |
CORRECT_TAG = "CORRECT_SQL:"
|
| 11 |
|
| 12 |
|
| 13 |
+
def normalize_sql(sql: str) -> str:
|
| 14 |
+
"""Normalize SQL for equality checks (whitespace, case, trailing semicolon)."""
|
| 15 |
+
text = sql.strip().rstrip(";")
|
| 16 |
+
return re.sub(r"\s+", " ", text).lower()
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
def sql_queries_equivalent(student_sql: str, correct_sql: str) -> bool:
|
| 20 |
+
return normalize_sql(student_sql) == normalize_sql(correct_sql)
|
| 21 |
+
|
| 22 |
+
|
| 23 |
def format_cross_encoder_input(
|
| 24 |
question: str,
|
| 25 |
schema: str,
|
src/hf_metrics.py
CHANGED
|
@@ -12,7 +12,7 @@ from sklearn.metrics import (
|
|
| 12 |
precision_score,
|
| 13 |
recall_score,
|
| 14 |
)
|
| 15 |
-
|
| 16 |
|
| 17 |
def sigmoid(x: np.ndarray) -> np.ndarray:
|
| 18 |
return 1.0 / (1.0 + np.exp(-x))
|
|
|
|
| 12 |
precision_score,
|
| 13 |
recall_score,
|
| 14 |
)
|
| 15 |
+
|
| 16 |
|
| 17 |
def sigmoid(x: np.ndarray) -> np.ndarray:
|
| 18 |
return 1.0 / (1.0 + np.exp(-x))
|
src/hf_predict_codebert.py
CHANGED
|
@@ -11,7 +11,7 @@ import numpy as np
|
|
| 11 |
import torch
|
| 12 |
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
| 13 |
|
| 14 |
-
from src.codebert_formatting import format_cross_encoder_input
|
| 15 |
from src.device_utils import get_device
|
| 16 |
from src.codebert_labels import load_codebert_labels, multihot_to_label_names
|
| 17 |
from src.hf_metrics import sigmoid
|
|
@@ -74,6 +74,21 @@ class CodeBERTSQLErrorClassifier:
|
|
| 74 |
threshold: Optional[float] = None,
|
| 75 |
top_k: int = 5,
|
| 76 |
) -> dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 77 |
text = format_cross_encoder_input(
|
| 78 |
question=question,
|
| 79 |
schema=schema,
|
|
@@ -92,7 +107,6 @@ class CodeBERTSQLErrorClassifier:
|
|
| 92 |
logits = self.model(**encoded).logits.cpu().numpy()[0]
|
| 93 |
|
| 94 |
probs = sigmoid(logits)
|
| 95 |
-
thr = threshold if threshold is not None else self.threshold
|
| 96 |
predicted = multihot_to_label_names(probs, self.label_list, threshold=thr)
|
| 97 |
|
| 98 |
ranked = sorted(
|
|
@@ -101,14 +115,23 @@ class CodeBERTSQLErrorClassifier:
|
|
| 101 |
reverse=True,
|
| 102 |
)[:top_k]
|
| 103 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
return {
|
| 105 |
"error_labels": predicted,
|
| 106 |
"probabilities": {name: float(p) for name, p in ranked},
|
| 107 |
"top_k": [
|
| 108 |
{"label": name, "probability": float(p)} for name, p in ranked
|
| 109 |
],
|
| 110 |
-
"primary_label":
|
| 111 |
-
"primary_confidence":
|
|
|
|
| 112 |
}
|
| 113 |
|
| 114 |
def predict_batch(
|
|
|
|
| 11 |
import torch
|
| 12 |
from transformers import AutoModelForSequenceClassification, AutoTokenizer
|
| 13 |
|
| 14 |
+
from src.codebert_formatting import format_cross_encoder_input, sql_queries_equivalent
|
| 15 |
from src.device_utils import get_device
|
| 16 |
from src.codebert_labels import load_codebert_labels, multihot_to_label_names
|
| 17 |
from src.hf_metrics import sigmoid
|
|
|
|
| 74 |
threshold: Optional[float] = None,
|
| 75 |
top_k: int = 5,
|
| 76 |
) -> dict:
|
| 77 |
+
thr = threshold if threshold is not None else self.threshold
|
| 78 |
+
|
| 79 |
+
if sql_queries_equivalent(student_sql, correct_sql):
|
| 80 |
+
return {
|
| 81 |
+
"error_labels": [],
|
| 82 |
+
"probabilities": {name: 0.0 for name in self.label_list},
|
| 83 |
+
"top_k": [
|
| 84 |
+
{"label": name, "probability": 0.0}
|
| 85 |
+
for name in self.label_list[:5]
|
| 86 |
+
],
|
| 87 |
+
"primary_label": "NO_ERROR",
|
| 88 |
+
"primary_confidence": 1.0,
|
| 89 |
+
"match_detected": True,
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
text = format_cross_encoder_input(
|
| 93 |
question=question,
|
| 94 |
schema=schema,
|
|
|
|
| 107 |
logits = self.model(**encoded).logits.cpu().numpy()[0]
|
| 108 |
|
| 109 |
probs = sigmoid(logits)
|
|
|
|
| 110 |
predicted = multihot_to_label_names(probs, self.label_list, threshold=thr)
|
| 111 |
|
| 112 |
ranked = sorted(
|
|
|
|
| 115 |
reverse=True,
|
| 116 |
)[:top_k]
|
| 117 |
|
| 118 |
+
top_label, top_prob = ranked[0]
|
| 119 |
+
if top_prob >= thr:
|
| 120 |
+
primary_label = top_label
|
| 121 |
+
primary_confidence = float(top_prob)
|
| 122 |
+
else:
|
| 123 |
+
primary_label = "NO_ERROR"
|
| 124 |
+
primary_confidence = 1.0 - float(top_prob)
|
| 125 |
+
|
| 126 |
return {
|
| 127 |
"error_labels": predicted,
|
| 128 |
"probabilities": {name: float(p) for name, p in ranked},
|
| 129 |
"top_k": [
|
| 130 |
{"label": name, "probability": float(p)} for name, p in ranked
|
| 131 |
],
|
| 132 |
+
"primary_label": primary_label,
|
| 133 |
+
"primary_confidence": primary_confidence,
|
| 134 |
+
"match_detected": False,
|
| 135 |
}
|
| 136 |
|
| 137 |
def predict_batch(
|
src/sql_templates.py
CHANGED
|
@@ -223,6 +223,12 @@ def inject_performance_error(rng: random.Random, exercise: Exercise) -> Tuple[st
|
|
| 223 |
return rng.choice(variants), "inefficient query: SELECT * or cartesian join detected"
|
| 224 |
|
| 225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
def inject_filtering_error(rng: random.Random, exercise: Exercise) -> Tuple[str, str]:
|
| 227 |
sql = exercise.correct_query
|
| 228 |
col = _pick(rng, list(exercise.columns))
|
|
@@ -255,4 +261,5 @@ ERROR_INJECTORS: Dict[int, Callable[[random.Random, Exercise], Tuple[str, str]]]
|
|
| 255 |
12: inject_logical_error,
|
| 256 |
13: inject_performance_error,
|
| 257 |
14: inject_filtering_error,
|
|
|
|
| 258 |
}
|
|
|
|
| 223 |
return rng.choice(variants), "inefficient query: SELECT * or cartesian join detected"
|
| 224 |
|
| 225 |
|
| 226 |
+
def inject_no_error(rng: random.Random, exercise: Exercise) -> Tuple[str, str]:
|
| 227 |
+
"""Correct submission: student SQL equals the reference answer."""
|
| 228 |
+
sql = exercise.correct_query.strip()
|
| 229 |
+
return sql, ""
|
| 230 |
+
|
| 231 |
+
|
| 232 |
def inject_filtering_error(rng: random.Random, exercise: Exercise) -> Tuple[str, str]:
|
| 233 |
sql = exercise.correct_query
|
| 234 |
col = _pick(rng, list(exercise.columns))
|
|
|
|
| 261 |
12: inject_logical_error,
|
| 262 |
13: inject_performance_error,
|
| 263 |
14: inject_filtering_error,
|
| 264 |
+
15: inject_no_error,
|
| 265 |
}
|