perf: stream SQL to UI, trim few-shots 7→4, GPU duration 30s
Browse files- handle_query is now a generator: tokens stream into the SQL panel
within seconds instead of a blank wait
- prompts.py: drop 3 redundant few-shot examples (simple patterns
covered by remaining 4) to cut bnb-4bit prefill time
- spaces.GPU(duration=30): shorter durations get ZeroGPU queue priority
- max_tokens 192 (observed outputs are ~140-170 chars)
- app.py +21 -12
- prompts.py +0 -12
app.py
CHANGED
|
@@ -12,11 +12,13 @@ import gradio as gr
|
|
| 12 |
# spaces.GPU is only available on HF Spaces — use a no-op locally
|
| 13 |
try:
|
| 14 |
import spaces
|
| 15 |
-
|
|
|
|
|
|
|
| 16 |
except ImportError:
|
| 17 |
_gpu_decorator = lambda fn: fn # no-op for local dev
|
| 18 |
|
| 19 |
-
from model_inference import load_model,
|
| 20 |
from data_engine import create_session, execute_safe, QueryTimeoutError
|
| 21 |
|
| 22 |
# ── Startup ───────────────────────────────────────────────────────────
|
|
@@ -60,32 +62,39 @@ EXAMPLE_QUERIES = [
|
|
| 60 |
@_gpu_decorator
|
| 61 |
def handle_query(user_question: str):
|
| 62 |
"""
|
| 63 |
-
Process an admin's question end-to-end.
|
| 64 |
|
| 65 |
-
1.
|
| 66 |
2. Execute validated SQL on a fresh per-request DB
|
| 67 |
-
3.
|
| 68 |
"""
|
| 69 |
if not user_question or not user_question.strip():
|
| 70 |
-
|
|
|
|
| 71 |
|
|
|
|
| 72 |
try:
|
| 73 |
-
|
|
|
|
|
|
|
|
|
|
| 74 |
except Exception as e:
|
| 75 |
-
|
|
|
|
| 76 |
|
| 77 |
try:
|
|
|
|
| 78 |
conn = create_session()
|
| 79 |
clean_sql, df = execute_safe(conn, raw_output, timeout_sec=30)
|
| 80 |
conn.close()
|
| 81 |
row_count = len(df)
|
| 82 |
-
|
| 83 |
except ValueError as e:
|
| 84 |
-
|
| 85 |
except QueryTimeoutError as e:
|
| 86 |
-
|
| 87 |
except Exception as e:
|
| 88 |
-
|
| 89 |
|
| 90 |
# ── UI ─────────────────────────────────────────────────────────────────
|
| 91 |
|
|
|
|
| 12 |
# spaces.GPU is only available on HF Spaces — use a no-op locally
|
| 13 |
try:
|
| 14 |
import spaces
|
| 15 |
+
# Short duration = higher priority in the ZeroGPU queue. Generation
|
| 16 |
+
# takes ~5s; 30s leaves ample headroom.
|
| 17 |
+
_gpu_decorator = spaces.GPU(duration=30)
|
| 18 |
except ImportError:
|
| 19 |
_gpu_decorator = lambda fn: fn # no-op for local dev
|
| 20 |
|
| 21 |
+
from model_inference import load_model, generate_sql_streaming
|
| 22 |
from data_engine import create_session, execute_safe, QueryTimeoutError
|
| 23 |
|
| 24 |
# ── Startup ───────────────────────────────────────────────────────────
|
|
|
|
| 62 |
@_gpu_decorator
|
| 63 |
def handle_query(user_question: str):
|
| 64 |
"""
|
| 65 |
+
Process an admin's question end-to-end, streaming SQL as it generates.
|
| 66 |
|
| 67 |
+
1. Stream SQL tokens from the LLM into the SQL panel (live)
|
| 68 |
2. Execute validated SQL on a fresh per-request DB
|
| 69 |
+
3. Yield (sql_text, dataframe, status_message)
|
| 70 |
"""
|
| 71 |
if not user_question or not user_question.strip():
|
| 72 |
+
yield "", None, "⚠️ Please enter a question."
|
| 73 |
+
return
|
| 74 |
|
| 75 |
+
raw_output = ""
|
| 76 |
try:
|
| 77 |
+
yield "", None, "🤖 Generating SQL…"
|
| 78 |
+
for accumulated in generate_sql_streaming(user_question, llm=llm, max_tokens=192):
|
| 79 |
+
raw_output = accumulated
|
| 80 |
+
yield raw_output, None, "🤖 Generating SQL…"
|
| 81 |
except Exception as e:
|
| 82 |
+
yield raw_output, None, f"❌ Model error: {e}"
|
| 83 |
+
return
|
| 84 |
|
| 85 |
try:
|
| 86 |
+
yield raw_output, None, "🦆 Running query…"
|
| 87 |
conn = create_session()
|
| 88 |
clean_sql, df = execute_safe(conn, raw_output, timeout_sec=30)
|
| 89 |
conn.close()
|
| 90 |
row_count = len(df)
|
| 91 |
+
yield clean_sql, df, f"✅ Done — {row_count} row{'s' if row_count != 1 else ''} returned"
|
| 92 |
except ValueError as e:
|
| 93 |
+
yield raw_output, None, f"⚠️ Validation: {e}"
|
| 94 |
except QueryTimeoutError as e:
|
| 95 |
+
yield raw_output, None, f"⏱️ Timeout: {e}"
|
| 96 |
except Exception as e:
|
| 97 |
+
yield raw_output, None, f"❌ Error: {e}"
|
| 98 |
|
| 99 |
# ── UI ─────────────────────────────────────────────────────────────────
|
| 100 |
|
prompts.py
CHANGED
|
@@ -105,14 +105,6 @@ FEW_SHOT_EXAMPLES = [
|
|
| 105 |
"question": "Show total enrollment per school for 2024-2025, sorted highest first.",
|
| 106 |
"sql": "SELECT school_name, SUM(student_count) AS total_enrollment\nFROM enrollment\nWHERE school_year = '2024-2025'\nGROUP BY school_name\nORDER BY total_enrollment DESC;",
|
| 107 |
},
|
| 108 |
-
{
|
| 109 |
-
"question": "What percentage of students at Lincoln Elementary were chronically absent in 2023-2024?",
|
| 110 |
-
"sql": "SELECT\n COUNT(CASE WHEN is_chronically_absent THEN 1 END) * 100.0 / COUNT(*) AS chronic_absence_pct\nFROM attendance\nWHERE school_year = '2023-2024' AND school_name = 'Lincoln Elementary';",
|
| 111 |
-
},
|
| 112 |
-
{
|
| 113 |
-
"question": "Show me the enrollment trend for all schools since 2021.",
|
| 114 |
-
"sql": "SELECT school_year, SUM(student_count) AS total_enrollment\nFROM enrollment\nWHERE school_year >= '2021-2022'\nGROUP BY school_year\nORDER BY school_year;",
|
| 115 |
-
},
|
| 116 |
{
|
| 117 |
"question": "How many chronically absent students are English Learners in 2023-2024?",
|
| 118 |
"sql": "SELECT COUNT(DISTINCT a.student_id) AS chronic_ell_count\nFROM attendance a\nJOIN students s ON a.student_id = s.student_id\nWHERE a.school_year = '2023-2024'\n AND a.is_chronically_absent = TRUE\n AND s.english_learner = TRUE;",
|
|
@@ -121,10 +113,6 @@ FEW_SHOT_EXAMPLES = [
|
|
| 121 |
"question": "What's the average GPA for chronically absent students vs non-chronic students in 2023-2024?",
|
| 122 |
"sql": "SELECT\n a.is_chronically_absent,\n ROUND(AVG(g.gpa), 2) AS avg_gpa\nFROM attendance a\nJOIN grades g ON a.student_id = g.student_id AND a.school_year = g.school_year\nWHERE a.school_year = '2023-2024'\nGROUP BY a.is_chronically_absent;",
|
| 123 |
},
|
| 124 |
-
{
|
| 125 |
-
"question": "How many discipline incidents were recorded at each school in 2023-2024?",
|
| 126 |
-
"sql": "SELECT school_name, COUNT(*) AS incident_count\nFROM discipline\nWHERE school_year = '2023-2024'\nGROUP BY school_name\nORDER BY incident_count DESC;",
|
| 127 |
-
},
|
| 128 |
]
|
| 129 |
|
| 130 |
|
|
|
|
| 105 |
"question": "Show total enrollment per school for 2024-2025, sorted highest first.",
|
| 106 |
"sql": "SELECT school_name, SUM(student_count) AS total_enrollment\nFROM enrollment\nWHERE school_year = '2024-2025'\nGROUP BY school_name\nORDER BY total_enrollment DESC;",
|
| 107 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 108 |
{
|
| 109 |
"question": "How many chronically absent students are English Learners in 2023-2024?",
|
| 110 |
"sql": "SELECT COUNT(DISTINCT a.student_id) AS chronic_ell_count\nFROM attendance a\nJOIN students s ON a.student_id = s.student_id\nWHERE a.school_year = '2023-2024'\n AND a.is_chronically_absent = TRUE\n AND s.english_learner = TRUE;",
|
|
|
|
| 113 |
"question": "What's the average GPA for chronically absent students vs non-chronic students in 2023-2024?",
|
| 114 |
"sql": "SELECT\n a.is_chronically_absent,\n ROUND(AVG(g.gpa), 2) AS avg_gpa\nFROM attendance a\nJOIN grades g ON a.student_id = g.student_id AND a.school_year = g.school_year\nWHERE a.school_year = '2023-2024'\nGROUP BY a.is_chronically_absent;",
|
| 115 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
]
|
| 117 |
|
| 118 |
|