Case study · 2025 · Personal project

A natural-language-to-SQL chatbot for MS SQL Server: schema pruning, two LLMs and the guard rails it still needs

Ask "which five customers ordered the most last month?" and get a sentence back, not a table. The pipeline is small — the interesting part is what has to happen before the question reaches the model, and what must never reach the database.

LLMSQLMS SQL ServerPythonOpenRouterGroqPrompt engineering
Role
Sole developer
Stack
Python · pyodbc · MS SQL Server · OpenRouter (DeepSeek) · Groq (Mixtral)
Shape
~360 lines, four classes, three endpoints
Status
Working prototype; not exposed to the internet

The pipeline

Four small classes carry it: a SchemaLoader that parses the schema file once, a QueryProcessor that finds relevant tables and builds the prompt, an AIAgent that talks to the two providers, and a ChatHistory. The version documented here is the first one, a Flask app with a server-rendered chat page.

The decisions

Prune the schema before prompting. A real schema does not fit a prompt, and even when it does, an LLM given forty tables picks the wrong join. Only tables whose column names appear in the question are sent. It is crude, and it is enough to keep the prompt small and the joins plausible.

Two models, two jobs. Writing correct T-SQL and writing a friendly sentence are different skills with different latency budgets. The SQL step uses a code-capable model through OpenRouter; the answer step uses a fast model on Groq, because the user is already waiting on the database round trip.

Force TOP 5 in the prompt. Without it, "list customers" returns the table. With it, every answer stays readable and the second model never sees more rows than it can summarise.

Extract SQL from a fenced block, or refuse. The model is told to answer in a ```sql block; the code takes exactly that block with a regex and errors if it is missing. Free-text SQL extraction is where injection and hallucinated columns sneak in.

Greetings never touch the database. A handful of exact-match phrases ("hello", "thanks"…) short-circuit the whole pipeline.

What I would harden before exposing it

I would rather be precise about this than pretend the prototype is production:

Why it is still on this page

Because the shape of the problem is the one I keep meeting: put a language model between a person and a system, and most of the engineering is in what you feed the model and what you refuse to let it do. The prototype answers real questions on a real database; the list above is exactly what separates it from something I would hand to a client.

Keep reading