Convert natural language questions into SQL queries using Claude AI β built with progressive prompting techniques from basic to self-improving.
Text-to-SQL is a project that demonstrates how to build a natural language database interface using Claude AI. Instead of writing SQL manually, you ask questions in plain English and the system generates and executes the SQL for you.
This project is structured as a learning progression β five approaches, each building on the last, from a simple one-shot prompt to a fully self-correcting pipeline.
uv run main.py --approach self_improvement --query "Which departments have the highest salary ratio?" Attempt 1 of 3
Claude's Reasoning:
1. I need MAX and MIN salary per department...
2. I'll use HAVING to filter ratios above 3...
Generated SQL:
SELECT d.name, MAX(e.salary) / MIN(e.salary) AS salary_ratio
FROM employees e
JOIN departments d ON e.department_id = d.id
GROUP BY d.name
HAVING MAX(e.salary) / MIN(e.salary) > 3;
β
Success on attempt 1!
department salary_ratio
Engineering 3.11
Finance 4.47
HR 4.54
...
- 5 prompting approaches β from basic to self-improving, all runnable from a single CLI
- Pluggable architecture β every approach is self-contained and independently runnable
- RAG-powered schema retrieval β only relevant schema columns are sent to Claude, scales to large databases
- Self-correcting pipeline β Claude sees its own errors and fixes them automatically
- Chain-of-thought reasoning β Claude explains its thinking before writing SQL
- Persistent vector index β embeddings are built once and reused across runs
| # | Approach | Key Technique | Best For |
|---|---|---|---|
| 01 | Basic | Schema + question β SQL | Simple queries, quick prototyping |
| 02 | Few-Shot | Add examples to guide style | Consistent output formatting |
| 03 | Chain of Thought | Step-by-step reasoning | Complex multi-table queries |
| 04 | RAG | Semantic schema retrieval | Large databases with many tables |
| 05 | Self Improvement | Automatic retry on failure | Production reliability |
Each approach builds on the previous one β RAG uses CoT, Self Improvement uses RAG.
text-to-sql/
βββ .env β API keys (not committed)
βββ config.py β Central environment config
βββ main.py β Single CLI entry point
βββ pyproject.toml β Project dependencies (uv)
β
βββ db/
β βββ setup.py β Database creation + shared utilities
β (get_schema_info, run_sql)
β
βββ src/
β βββ 01_basic.py β Approach 1: Basic prompt
β βββ 02_few_shot.py β Approach 2: Few-shot examples
β βββ 03_chain_of_thought.py β Approach 3: CoT reasoning
β βββ 04_rag.py β Approach 4: RAG retrieval
β βββ 05_self_improvement.py β Approach 5: Self-correcting loop
β
βββ data/
βββ data.db β SQLite database (auto-created)
βββ vector_db.pkl β VoyageAI vector index (auto-created)
- Python 3.12+
- uv package manager
- Anthropic API key β console.anthropic.com
- VoyageAI API key β dash.voyageai.com (free tier available)
# Clone the repository
git clone https://github.com/yourusername/text-to-sql.git
cd text-to-sql
# Install dependencies
uv sync
# Set up environment variables
cp .env.example .env
# Edit .env and add your API keysCreate a .env file in the project root:
ANTHROPIC_API_KEY=sk-ant-xxxxxxxx
VOYAGE_API_KEY=pa-xxxxxxxxuv run db/setup.pyThis creates data/data.db with:
- 10 departments across US cities
- 200 employees with names, ages, salaries, and hire dates
uv run main.py --approach <approach> --query "<your question>"# Basic
uv run main.py --approach basic \
--query "Who are the 5 highest paid employees?"
# Few-Shot
uv run main.py --approach few_shot \
--query "What is the total salary expenditure per department?"
# Chain of Thought
uv run main.py --approach cot \
--query "Which departments have an average salary above 120000?"
# RAG
uv run main.py --approach rag \
--query "Which city location has the highest average employee salary?"
# Self Improvement
uv run main.py --approach self_improvement \
--query "Show salary ratio per department where ratio exceeds 3"Each file is independently runnable with its own test queries:
uv run src/03_chain_of_thought.py
uv run src/04_rag.pyThe shared foundation. Two utilities used by every approach:
get_schema_info()β reads and formats the database schema for Clauderun_sql()β executes generated SQL and returns a pandas DataFrame
The simplest possible pipeline. Give Claude the full schema and the question, ask it to return SQL wrapped in <sql> tags.
Schema + Question β Claude β <sql>...</sql> β Run β Results
Add handcrafted examples of question β SQL pairs to the prompt. Claude learns preferred SQL style β table aliases, JOIN patterns, aggregation formatting.
Ask Claude to reason step by step inside <thought_process> tags before writing SQL. Forces deliberate reasoning, significantly improves accuracy on complex queries.
Uses VoyageAI embeddings to find which schema columns are semantically relevant to the question. Only those columns are sent to Claude β keeping prompts small and focused regardless of database size.
Question β VoyageAI embedding β similarity search β top columns β Claude
The vector index is built once and saved to data/vector_db.pkl for reuse.
Wraps a retry loop around RAG + CoT. If the SQL fails, the error is sent back to Claude with the original question and failed SQL. Claude debugs and corrects itself, up to MAX_ATTEMPTS times.
Generate SQL β Run β β
Done
β β Send error back to Claude β Generate fixed SQL β Run β ...
| Tool | Purpose |
|---|---|
| Claude (claude-sonnet-4-5) | SQL generation and reasoning |
| VoyageAI | Text embeddings for RAG |
| SQLite | Lightweight local database |
| pandas | Query result formatting |
| NumPy | Vector similarity computation |
| uv | Python package management |
This project is based on the Text to SQL with Claude cookbook by Mahesh Murag at Anthropic.
MIT License β see LICENSE for details.