feliksKdm commited on
Commit
dccf890
·
verified ·
1 Parent(s): e81e365

SQL Copilot: Qwen2.5-Coder-7B on ZeroGPU

Browse files
Files changed (4) hide show
  1. README.md +16 -6
  2. __pycache__/app.cpython-314.pyc +0 -0
  3. app.py +102 -0
  4. requirements.txt +3 -0
README.md CHANGED
@@ -1,13 +1,23 @@
1
  ---
2
- title: Sql Copilot
3
- emoji: 📚
4
- colorFrom: green
5
  colorTo: blue
6
  sdk: gradio
7
- sdk_version: 6.29.1
8
- python_version: '3.12'
9
  app_file: app.py
10
  pinned: false
 
 
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: SQL Copilot
3
+ emoji: 🧮
4
+ colorFrom: purple
5
  colorTo: blue
6
  sdk: gradio
7
+ sdk_version: 5.50.0
 
8
  app_file: app.py
9
  pinned: false
10
+ license: apache-2.0
11
+ short_description: Plain-English questions to DuckDB SQL (Qwen2.5-Coder-7B)
12
  ---
13
 
14
+ # SQL Copilot
15
+
16
+ Natural-language to DuckDB SQL for the Data Lab on Feliks Altymyshov's
17
+ portfolio, using `Qwen/Qwen2.5-Coder-7B-Instruct` on ZeroGPU.
18
+
19
+ - **Privacy:** the site sends only table schemas and a few sample rows, never
20
+ the full data. Queries run in the visitor's browser.
21
+ - **API:** `/ask` takes `(question, schema)` and returns `[sql, error]`.
22
+
23
+ Redeploy: `hf upload feliksKdm/sql-copilot hf-spaces/sql-copilot . --type space`
__pycache__/app.cpython-314.pyc ADDED
Binary file (7.19 kB). View file
 
app.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SQL Copilot — plain-English questions to DuckDB SQL with Qwen2.5-Coder-7B on
3
+ ZeroGPU. The portfolio's Data Lab sends table schemas (plus a few sample rows)
4
+ and runs the returned SQL locally in DuckDB-WASM. Errors travel as data.
5
+ """
6
+
7
+ import re
8
+ import traceback
9
+
10
+ import gradio as gr
11
+ import spaces
12
+ import torch
13
+ from transformers import AutoModelForCausalLM, AutoTokenizer
14
+
15
+ MODEL = "Qwen/Qwen2.5-Coder-7B-Instruct"
16
+ MAX_QUESTION = 500
17
+ MAX_SCHEMA = 8000
18
+
19
+ tokenizer = AutoTokenizer.from_pretrained(MODEL)
20
+ model = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype=torch.bfloat16).to("cuda")
21
+
22
+ SYSTEM = """You are an expert data analyst who writes DuckDB SQL.
23
+ Rules:
24
+ - Answer with ONE DuckDB SQL query and nothing else: no explanation, no comments.
25
+ - Use only the tables and columns in the schema. Quote identifiers with double quotes when they contain capitals, spaces or symbols.
26
+ - Prefer readable column aliases, ORDER BY for rankings, and LIMIT 100 for row listings.
27
+ - For dates use DuckDB functions such as date_trunc, strftime, extract.
28
+ - Round averages and percentages to 2 decimals.
29
+ - Read-only: never write CREATE, INSERT, UPDATE, DELETE, DROP, ALTER, COPY, ATTACH or INSTALL."""
30
+
31
+ WRITE = re.compile(r"\b(create|insert|update|delete|drop|alter|copy|attach|install|load|pragma|export)\b", re.I)
32
+
33
+
34
+ @spaces.GPU(duration=20)
35
+ def complete(messages):
36
+ text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
37
+ inputs = tokenizer([text], return_tensors="pt").to("cuda")
38
+ with torch.inference_mode():
39
+ out = model.generate(**inputs, max_new_tokens=400, do_sample=False, pad_token_id=tokenizer.eos_token_id)
40
+ return tokenizer.decode(out[0][inputs.input_ids.shape[1]:], skip_special_tokens=True)
41
+
42
+
43
+ def extract_sql(reply):
44
+ m = re.search(r"```(?:sql)?\s*(.*?)```", reply, re.S | re.I)
45
+ sql = (m.group(1) if m else reply).strip().rstrip(";").strip()
46
+ return sql
47
+
48
+
49
+ def ask(question, schema):
50
+ """API: returns (sql, error)."""
51
+ try:
52
+ question = (question or "").strip()
53
+ schema = (schema or "").strip()
54
+ if not question:
55
+ return "", "Ask a question about your data first."
56
+ if len(question) > MAX_QUESTION:
57
+ return "", f"Keep the question under {MAX_QUESTION} characters."
58
+ if not schema:
59
+ return "", "Load a table first."
60
+ schema = schema[:MAX_SCHEMA]
61
+ reply = complete([
62
+ {"role": "system", "content": SYSTEM},
63
+ {"role": "user", "content": f"Schema:\n{schema}\n\nQuestion: {question}"},
64
+ ])
65
+ sql = extract_sql(reply)
66
+ if not sql or not re.match(r"^\s*(with|select|from|summarize|describe|pivot|unpivot)\b", sql, re.I):
67
+ return "", "Couldn't turn that into a query. Try rephrasing it."
68
+ if WRITE.search(re.sub(r"'[^']*'", "''", sql)):
69
+ return "", "The copilot only writes read-only queries."
70
+ return sql, ""
71
+ except gr.Error as exc:
72
+ return "", str(exc.message)
73
+ except Exception as exc:
74
+ traceback.print_exc()
75
+ return "", f"The copilot failed ({type(exc).__name__}). Please try again."
76
+
77
+
78
+ def ui_ask(question, schema):
79
+ sql, error = ask(question, schema)
80
+ if error:
81
+ raise gr.Error(error)
82
+ return sql
83
+
84
+
85
+ with gr.Blocks(title="SQL Copilot") as demo:
86
+ gr.Markdown(
87
+ "# 🧮 SQL Copilot\nPlain-English questions to DuckDB SQL with Qwen2.5-Coder-7B. Part of "
88
+ "[Feliks Altymyshov's](https://github.com/feliksKdm) portfolio lab."
89
+ )
90
+ schema = gr.Textbox(label="Schema", lines=6, placeholder='orders(order_id BIGINT, order_date DATE, store VARCHAR, revenue DOUBLE)')
91
+ question = gr.Textbox(label="Question", placeholder="Which store had the highest revenue last month?")
92
+ btn = gr.Button("Write SQL", variant="primary")
93
+ out = gr.Code(label="SQL", language="sql")
94
+ btn.click(ui_ask, [question, schema], out, api_name=False)
95
+
96
+ with gr.Group(visible=False):
97
+ a_q, a_s, a_sql, a_err = gr.Textbox(), gr.Textbox(), gr.Textbox(), gr.Textbox()
98
+ a_btn = gr.Button()
99
+ a_btn.click(ask, [a_q, a_s], [a_sql, a_err], api_name="ask")
100
+
101
+ if __name__ == "__main__":
102
+ demo.queue(default_concurrency_limit=2).launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ torch
2
+ transformers>=4.46
3
+ accelerate