A revolutionary AI-powered system that combines vector embeddings, fuzzy matching, and traditional SQL to answer any question about your database in natural language.
🎥 Watch the Full Tutorial on YouTube - Complete walkthrough and demonstration
Traditional database systems have fundamental limitations:
- ❌ Requires SQL expertise - Users must know complex syntax
- ❌ No typo tolerance - "Stephan King" returns 0 results instead of "Stephen King"
- ❌ Can't understand concepts - Searching for "dystopia" won't find books about totalitarianism
- ❌ Brittle and inflexible - One spelling mistake breaks everything
This system combines three complementary technologies to create truly intelligent database querying:
WHERE publication_date > '2010-01-01' AND retail_price < 20- ✅ Fast, indexed lookups
- ✅ Exact numeric/date comparisons
- ✅ Complex boolean logic
- Best for: Structured data (prices, dates, IDs, flags)
WHERE levenshtein(LOWER(author_name), LOWER('Orrwell')) <= 2- ✅ Handles spelling errors (1-3 character differences)
- ✅ Works with any VARCHAR field
- ✅ Ranks by similarity (closest matches first)
- Best for: Names, titles, categories when user might misspell
WHERE book_description_embed <-> embedding("dystopian themes") < 0.5- ✅ Understands meaning, not just keywords
- ✅ Finds conceptually similar content
- ✅ Works across languages and synonyms
- Best for: Thematic searches, similarity, recommendations
Most systems use only ONE approach. This system uses ALL THREE INTELLIGENTLY:
| System Type | Handles Typos? | Understands Concepts? | Exact Filters? | Real Example |
|---|---|---|---|---|
| Traditional SQL-only | ❌ No | ❌ No | ✅ Yes | "Find books WHERE author = 'Orwell'" (breaks if misspelled) |
| Embeddings-only | ✅ Yes | ❌ No | Can't filter "books under $20 published in 2020" | |
| Fuzzy-only | ✅ Yes | ❌ No | Can't find "books about dystopia" (concept) | |
| 🚀 THIS SYSTEM | ✅ Yes | ✅ Yes | ✅ Yes | Handles ANY question combining all three! |
User Question (in Italian):
"Trova libri distopici simili a 1984 di autori con nome che finisce in
'well', pubblicati dopo 2000 da editori inglesi, prezzo tra $12-$18,
con recensioni che parlano di libertà"
System Processing:
┌─────────────────────────────────────────────────────────────┐
│ 🌍 TRANSLATION (Multi-language support) │
│ "Trova" → "Find", "distopici" → "dystopian", etc. │
└───────────────────────────┬─────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 🧠 SEMANTIC UNDERSTANDING │
│ "similar to 1984" → embedding("totalitarian surveillance") │
│ "about freedom" → embedding("liberty freedom rights") │
└───────────────────────────┬─────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 🔍 FUZZY MATCHING │
│ "nome che finisce in 'well'" → LIKE '%well' │
│ (Tolerates typos in author names) │
└───────────────────────────┬─────────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 📊 SQL FILTERS │
│ publication_date > '2000-01-01' │
│ retail_price BETWEEN 12 AND 18 │
│ publisher.country = 'UK' │
└───────────────────────────┬─────────────────────────────────┘
▼
SINGLE OPTIMIZED QUERY
(Combines all strategies)
▼
Natural Language Answer:
"I found 2 dystopian books similar to '1984' by authors whose
names end in 'well': [results with details]..."
One question. One query. Perfect results. Even with typos, in another language, with complex criteria.
This system can answer virtually ANY question about your database:
| Question Type | Example | What Makes It Hard | How This System Handles It |
|---|---|---|---|
| Simple exact | "Books published in 2020" | None | ✅ SQL filter |
| With typos | "Books by Stephan King" | Spelling error | ✅ Levenshtein distance |
| Conceptual | "Books about artificial intelligence" | No exact keyword "AI" in data | ✅ Vector embedding similarity |
| Similarity | "Books like Harry Potter" | Subjective "like" | ✅ Embedding of HP themes |
| Fuzzy + Filters | "Books by Tolkein under $15" | Typo + price filter | ✅ Levenshtein + SQL |
| Semantic + Filters | "Dystopian books after 2010" | Concept + date | ✅ Embeddings + SQL |
| All combined | "Fantasy by Pratchet, UK publishers, $12-$18, with magic in reviews" | Everything at once | ✅ All three techniques! |
| Ultra-complex | "Compare books similar to both '1984' AND 'Brave New World', authors with 'well'/'ley' in name, literary publishers, $12-$18, reviews about social commentary, last 30 years" | Multiple embeddings, fuzzy patterns, filters, aggregations | ✅ Handles perfectly! |
The LLM automatically chooses the right technique for each part of your question:
- Recognizes when fuzzy matching is needed (typos)
- Detects semantic intent (concepts, themes, similarity)
- Applies exact filters where appropriate (numbers, dates)
- Combines them seamlessly in a single query
If a query fails (wrong syntax, missing GROUP BY, etc.):
- System captures the error
- Feeds it back to the LLM with full context
- LLM analyzes ALL previous failures
- Generates corrected query
- Up to 4 attempts until success
Real example:
Attempt 1: Missing GROUP BY → PostgreSQL error
Attempt 2: Added GROUP BY with vector column → Validation blocks it
Attempt 3: Removed vector, fixed all issues → SUCCESS ✓
Every query is validated before execution:
- ✅ Only SELECT queries (read-only)
- ✅ No SQL injection possible
- ✅ No write/delete/drop operations
- ✅ Structural validation (no invalid syntax reaches DB)
Ask in any language, get accurate results:
English: "Find science fiction books"
Italian: "Trova libri di fantascienza"
Spanish: "Busca libros de ciencia ficción"
French: "Trouve des livres de science-fiction"
→ All translate to: category = 'Science Fiction'
→ All return the same accurate results
With this system, users can ask questions like:
✅ "Show me books" → Simple list
✅ "Books by Orwell" → Author filter
✅ "Books by Orrwell" → Fuzzy match (typo)
✅ "Books about dystopia" → Semantic search
✅ "Books similar to 1984" → Embedding similarity
✅ "Dystopian books by Orrwell under $20" → Fuzzy + Semantic + Filter
✅ "Compare fantasy books similar to Harry Potter and Lord of the Rings, by British authors, published after 2000, under $25, with reviews mentioning magic" → Everything combined
No SQL knowledge needed. No exact spelling required. No question too complex.
This is database interaction reimagined for the AI era. 🚀
- Docker
- Docker Compose
- Python 3.8+ (for AI agent)
- OpenAI API Key
# Start the PostgreSQL container
docker-compose up -d
# Check if the container is running
docker-compose ps
# View logs
docker-compose logs -fConnection Details:
- Host: localhost
- Port: 5432
- Database: books_db
- Username: bookadmin
- Password: bookpass123
Using psql:
docker exec -it books_database psql -U bookadmin -d books_dbUsing connection string:
postgresql://bookadmin:bookpass123@localhost:5432/books_db
# Stop the container
docker-compose down
# Stop and remove data volumes (⚠️ deletes all data)
docker-compose down -v# Create virtual environment
python3 -m venv venv
# Activate virtual environment
source venv/bin/activate
# Upgrade pip
pip install --upgrade pip
# Install dependencies
pip install -r requirements.txtCreate a .env file in the project root:
# OpenAI API Key (required)
OPENAI_API_KEY=your_openai_api_key_here# Deactivate when done
deactivateThis project includes an advanced AI-powered Text-to-SQL Agent that converts natural language questions into SQL queries, executes them, and generates natural language answers.
The Text-to-SQL system is a complete pipeline that:
- Accepts natural language questions in multiple languages
- Generates optimized SQL queries using LLM
- Validates queries for security and correctness
- Executes queries against the PostgreSQL database
- Generates natural language answers from results
- Uses pgvector extension for similarity searches
- Supports OpenAI text-embedding-3-small (1536 dimensions)
- Semantic understanding of concepts, themes, and content
- Handles queries like: "Find books similar to '1984'"
- Tolerates typos and spelling errors in user queries
- Uses fuzzystrmatch PostgreSQL extension
- Applied to: titles, author names, publisher names, categories
- Handles queries like: "Find books by George Orrwell" (with typo)
- Automatically retries failed queries up to 4 times
- Provides comprehensive error feedback to the LLM
- Learns from previous failures to avoid repeating mistakes
- Distinguishes between security issues and fixable errors
- Uses sqlglot for SQL parsing and validation
- Blocks write operations (INSERT, UPDATE, DELETE, DROP, etc.)
- Prevents SQL injection attacks
- Validates query structure before execution
- Accepts queries in English, Italian, Spanish, French, etc.
- Automatically translates search terms to English (database language)
- Example: "Trova libri di fantascienza" → searches for "science fiction"
- Multi-table JOINs
- Aggregations (COUNT, AVG, SUM, MIN, MAX)
- Date range filters
- Price range filters
- Combined fuzzy + semantic + filters
The system requires these PostgreSQL extensions:
- vector - For semantic similarity search with embeddings
- fuzzystrmatch - For Levenshtein distance (typo tolerance)
- uuid-ossp - For UUID generation
All extensions are automatically installed via init-db.sql.
When you ask a question, the LLM analyzes:
- Intent: What is the user trying to find?
- Strategy: Should it use fuzzy matching, semantic search, or both?
- Filters: What conditions need to be applied?
Example:
User: "Find books by George Orrwell about dystopia"
Analysis:
- "George Orrwell" → Fuzzy match (typo in "Orwell")
- "about dystopia" → Semantic search (concept)
- Strategy: Combine both
The LLM generates SQL following strict rules:
For Fuzzy Matching (VARCHAR fields with typos):
WHERE levenshtein(LOWER(last_name), LOWER('Orrwell')) <= 2
ORDER BY levenshtein(LOWER(last_name), LOWER('Orrwell'))For Semantic Search (TEXT fields with concepts):
WHERE book_description_embed <-> %s::vector < 0.5
ORDER BY book_description_embed <-> %s::vector- Placeholder
%s::vectorwill be replaced with actual embedding
For Combined Queries:
SELECT b.title, a.last_name,
levenshtein(LOWER(a.last_name), LOWER('Orrwell')) AS name_distance,
b.book_description_embed <-> %s::vector AS content_similarity
FROM books b
JOIN authors a ON b.author_id = a.author_id
WHERE levenshtein(LOWER(a.last_name), LOWER('Orrwell')) <= 2
AND b.book_description_embed IS NOT NULL
ORDER BY name_distance, content_similarity
LIMIT 15;Before execution, every query is validated:
✅ Security Checks:
- Must be a SELECT statement
- No INSERT, UPDATE, DELETE, DROP, CREATE, etc.
- No multiple statements (SQL injection protection)
- No SELECT INTO operations
✅ Structural Checks:
- Parseable SQL syntax
- No vector columns in GROUP BY clause
- Proper use of aggregate functions
❌ Rejected Examples:
-- Rejected: Write operation
INSERT INTO books VALUES (...)
-- Rejected: Dangerous operation
DROP TABLE books;
-- Rejected: Vector in GROUP BY
SELECT title, AVG(rating)
FROM books
GROUP BY title, book_description_embed; -- ❌ _embed cannot be in GROUP BYIf the query needs semantic search:
- Extract search terms from user question
- Generate embeddings using OpenAI API
- Format as PostgreSQL vectors:
[0.123, 0.456, ...] - Substitute into query replacing
%s::vectorplaceholders
Example:
User: "Books about dystopia"
→ Embedding text: "dystopian totalitarian surveillance oppression"
→ Vector: [0.123, 0.456, ..., 0.789] (1536 dimensions)
→ Query: book_description_embed <-> '[0.123, 0.456, ..., 0.789]'::vector
The system:
- Connects to PostgreSQL database
- Substitutes parameters (manual substitution for complex queries)
- Executes query
- Fetches results as list of dictionaries
- Handles errors with detailed logging
If execution fails:
Attempt 1: Initial query generation
↓ [FAIL] → Error: "column X must appear in GROUP BY"
Attempt 2: Regenerate with error feedback
↓ [FAIL] → Error: "syntax error near Y"
Attempt 3: Regenerate with ALL previous errors
↓ [FAIL] → Error: "type mismatch"
Attempt 4: Final attempt with complete history
↓ [SUCCESS] ✓
The LLM receives:
- Original user request
- Complete history of all failed attempts
- Specific error messages for each failure
- Instructions to avoid repeating same mistakes
After successful execution:
- LLM receives the original question + query results
- Generates a clear, conversational answer
- Handles edge cases (no results, errors, etc.)
Example:
User: "How many books by Stephen King?"
Results: [{"count": 7}]
Answer: "There are 7 books by Stephen King in the database."
from text_to_sql_agent import AgentTextToSql
# Initialize agent
agent = AgentTextToSql()
# Process a question
result = agent.process_request_with_execution(
"Find books similar to 1984"
)
if result['success']:
print(f"Answer: {result['final_answer']}")
print(f"Retrieved {result['query_results']['row_count']} books")
else:
print(f"Error: {result['error']}")# Run interactive mode
python main.py --interactive
# Then ask questions in natural language:
📝 Your question: Find books by Terry Pratchet about fantasy
📝 Your question: Quanti libri ci sono di fantascienza?
📝 Your question: Books similar to Harry Potter under $20Simple Search:
"Find all books by George Orwell"
→ SQL: Uses fuzzy matching on author name
→ Result: Books by George Orwell
Semantic Search:
"Find books about artificial intelligence"
→ SQL: Uses vector embeddings for concept search
→ Result: Books discussing AI/ML themes
Combined Search:
"Find dystopian books by Margret Atwood under $20"
→ SQL: Fuzzy on name + semantic on content + price filter
→ Result: Atwood's dystopian novels under $20
Complex Multi-Criteria:
"Show highly rated fantasy books published after 2010
by UK publishers with reviews mentioning magic"
→ SQL: Multi-JOIN + semantic + filters + aggregations
→ Result: Filtered and ranked results
The system intelligently chooses strategies:
| User Query Type | Strategy | Example |
|---|---|---|
| Exact data (names, dates, IDs) | Traditional SQL | "Books published in 2020" |
| Text with possible typos | Levenshtein | "Books by Stephan King" |
| Concepts and themes | Semantic (embeddings) | "Books about dystopia" |
| Similarity requests | Semantic | "Books similar to 1984" |
| Mixed requirements | Combined | "Dystopian books by Orwell" |
The system enforces smart limits:
- Semantic searches: Default 15-20 results
- General lists: Default 50 results
- Hard maximum: 100 results (unless explicitly requested)
- User-specified: Honors explicit counts ("top 5 books")
Create a .env file in the project root:
# OpenAI API Key (required)
OPENAI_API_KEY=your_openai_api_key_hereDefault configuration (in text_to_sql_agent.py):
DEFAULT_DB_CONFIG = {
'host': 'localhost',
'port': 5432,
'database': 'books_db',
'user': 'bookadmin',
'password': 'bookpass123'
}# Initialize with custom settings
agent = AgentTextToSql(
model="gpt-4.1",
temperature=0.1, # Default: 0.1 (low for consistency)
db_config=custom_db_config # Optional custom DB config
)Before using semantic search, generate embeddings for the database:
# Activate virtual environment
source venv/bin/activate
# Generate embeddings for all text fields
python gen_embeddings.pyThis will:
- Scan all tables for
_embedcolumns - Generate embeddings for corresponding text fields
- Update the database with vector values
- Show progress and estimated cost
# Run predefined examples
python main.py
# Examples include:
# - Traditional SQL (no embeddings)
# - Semantic search (with embeddings)
# - Combined queries
# - Aggregations# Start interactive session
python main.py --interactive
# Ask questions in natural language
# Type 'quit' or 'exit' to stopThe system automatically handles query failures:
# If a query fails, the system:
1. Captures the error message
2. Sends it back to LLM with the original request
3. LLM analyzes the error and generates a corrected query
4. Repeats up to 4 times total
# Example retry scenario:
Attempt 1: GROUP BY missing required columns → FAIL
Attempt 2: Added GROUP BY but included vector column → FAIL
Attempt 3: Removed vector from GROUP BY, fixed syntax → SUCCESS# Italian query
"Trova libri di fantascienza pubblicati dopo il 2010"
# Translated internally to:
# "Find science fiction books published after 2010"
# SQL uses: category_name = 'Science Fiction'When queries reuse the same embedding:
-- Query uses same embedding twice
((book_embed <-> %s::vector) + (book_embed <-> %s::vector)) / 2
-- System automatically:
1. Detects 2 placeholders
2. Generates 1 embedding
3. Replicates it for both placeholders
4. Substitutes correctlySee QUESTIONS.md for 30 comprehensive test questions covering:
- Fuzzy matching with typos
- Semantic search for concepts
- Multi-table joins
- Aggregations and statistics
- Complex combined queries
| File | Purpose |
|---|---|
text_to_sql_agent.py |
Main agent class with pipeline logic |
prompt.py |
All LLM prompts and templates |
utils.py |
Database schema extraction utilities |
gen_embeddings.py |
Batch embedding generation script |
main.py |
Example usage and interactive mode |
init-db.sql |
Database schema with sample data |
QUESTIONS.md |
30 test questions for validation |
Embedding Generation:
- Cost: ~$0.020 per 1M tokens (text-embedding-3-small)
- Speed: ~300-500ms per embedding
- Batch generation recommended for initial setup
Query Execution:
- Simple queries: <100ms
- Semantic searches: 500ms-2s (including embedding generation)
- Complex queries: 1-5s (multiple embeddings + aggregations)
LLM API Calls:
- Query generation: ~2-5s
- Answer generation: ~1-3s
- Retry regeneration: ~2-5s per attempt
The system handles various error types:
Security Errors (immediate abort):
Error: Dangerous operation detected: INSERT
→ No retry, query rejected
Fixable Errors (retry with feedback):
Error: Vector columns cannot be in GROUP BY
→ Retry with feedback to LLM
→ LLM removes vector from GROUP BY
→ Success on retry
Execution Errors (retry with feedback):
Error: column "xyz" does not exist
→ Retry with feedback
→ LLM corrects column name
→ Success on retry
- Maximum 100 results by default (hard limit for performance)
- Complex queries may require multiple retry attempts
- Embeddings required for semantic search (run
gen_embeddings.pyfirst) - English database - queries in other languages are translated
- Read-only - only SELECT queries allowed
- Generate embeddings first before using semantic search
- Be specific in questions for better results
- Use natural language - the system understands conversational queries
- Check retry info if queries take multiple attempts
- Review generated SQL to understand the strategy used
Issue: "Embeddings not found"
# Solution: Generate embeddings
python gen_embeddings.pyIssue: "Query validation failed"
- Check if you're trying write operations (not allowed)
- Review the error message for specific issue
Issue: "Multiple retries, still failing"
- Query might be too complex
- Try simplifying the question
- Check
failed_attemptsin result for details
Issue: "No results found"
- Criteria might be too restrictive
- Try relaxing some filters
- Check if embeddings are generated
