SQL Analyst Agent
A natural language interface to a PostgreSQL database that checks its own work. The database is an objective oracle, so the agent can tell whether its query was wrong and why, which is what makes the repair loop something other than a retry with a prayer.
No hosted demo yet. It runs locally, as a CLI, and as an MCP server over stdio for clients like Claude Desktop.
Text-to-SQL demos are common and mostly the same: a question goes in, a query comes out, and nobody checks it. They fail silently, they have nothing to measure, and there is no reason for the model to get a second try.
The database changes that. Syntax errors, missing columns, type mismatches and timeouts are all objectively detectable, so the agent can read the actual failure and rewrite. That is what justifies a loop. It also needs a stopping rule, because an agent that loops forever on an impossible question is worse than one that fails fast.
Select the schema
Hand-written table descriptions are embedded with bge-small-en-v1.5 and searched semantically, so 3 to 5 relevant tables from a 15-table schema reach the prompt instead of all of them. The descriptions say what each table is not for, which stops irrelevant joins.
Validate before executing
sqlglot parses the query to an AST and rejects multiple statements, any write anywhere in the tree, unknown tables and deny-listed functions, then forces a LIMIT. Only then does it run, on a SELECT-only role inside a read-only transaction with a 5 second timeout that always rolls back.
Route the error, or stop
Each SQLSTATE maps to its own repair: 42703 widens schema retrieval to 9 tables, 42702 forces column aliasing, 57014 simplifies the query, and 42501 is never repaired. State carries the full history so the model cannot regenerate a query that already failed.
| Measure | Result | Method |
|---|---|---|
| Execution accuracy, 30 answerable | 73.3% → 76.7% | Result sets, not SQL text |
| Hard and ambiguous subset | 5/10 → 6/10 | Same 40-question set |
| Rescued by the repair loop | 1 of 1 | Two strategies in sequence |
| Unsafe SQL reaching the database | 0 | Parser plus read-only role |
- Python 3.12
- LangGraph 0.2
- MCP
- FastAPI
- PostgreSQL 17
- sqlglot
- Groq
- bge-small-en-v1.5
- Langfuse v4
- React 19
- Vite
- pytest
Not hosted yet. It runs locally behind FastAPI, as a CLI, and as an MCP server over stdio, so the same four tools work from Claude Desktop or any MCP client. Safety lives in the tool layer rather than the agent, which means it holds no matter what calls it. Three separate database credentials enforce least privilege: the agent's role can only SELECT, a second role can only INSERT into the query log and cannot read it back, and admin rights exist only in setup scripts. Every question is traced in Langfuse with cost computed from real token counts, and a scheduled check diffs the live schema against a snapshot to catch drift.
- Treat the accuracy gain as unproven. It is one question on a 30-question set, which carries roughly an 8 point binomial spread, so the improvement is causally explained but not statistically significant. A larger set is the only fix.
- Get an evaluation set written by someone else. The current 40 questions were authored by the same person who built the schema, so they are not independent.
- Wire up Sentry and UptimeRobot, which are planned rather than done.
- Add conversation memory for follow-up questions, and support a second database dialect.