Skip to main content

QA-SQL SDK

Local-first Text-to-SQL engine for enterprise deployment.

All processing happens locally - sensitive database schemas never leave your network.

Python 3.10+ License: MIT


Table of Contents


Features

  • Privacy-First: Use local LLMs (Ollama) - zero data leaves your network
  • Multi-Strategy Generation: Generates 4-5 SQL candidates using different approaches
  • Automatic Schema Discovery: Extracts and profiles database structure
  • Smart Selection: LLM-as-a-Judge picks the best SQL candidate
  • Database Support: SQLite and PostgreSQL
  • Flexible LLM Support: Ollama (local), Anthropic Claude, OpenAI GPT

Installation

Step 1: Install the SDK

# From source (development)
cd qasql-sdk
pip install -e .

# Or from PyPI (after publish)
pip install qasql

Step 2: Install Optional Dependencies

# For Terminal UI (recommended)
pip install qasql[ui]

# For Anthropic Claude
pip install qasql[anthropic]

# For OpenAI
pip install qasql[openai]

# For PostgreSQL
pip install qasql[postgres]

# All extras
pip install qasql[all]

Step 3: Setup LLM Provider

# Install Ollama
curl -fsSL https://ollama.ai/install.sh | sh

# Start Ollama server (keep running in terminal)
ollama serve

# In another terminal, pull a model
ollama pull llama3.2

# Or for better SQL generation
ollama pull codellama:13b

Option B: Anthropic API

export ANTHROPIC_API_KEY='your-anthropic-api-key'

Option C: OpenAI API

export OPENAI_API_KEY='your-openai-api-key'

Quick Start

1. Test Schema Extraction (No LLM Required)

cd qasql-sdk/examples
python test_schema_only.py

2. Full Text-to-SQL Test (Requires LLM)

# Make sure Ollama is running first
ollama serve

# Then run the test
cd qasql-sdk/examples
python test_california_schools.py

3. Interactive Demo

cd qasql-sdk/examples
python interactive_demo.py --db-uri sqlite:///../../app/california_schools.sqlite

Terminal UI

Interactive terminal interface similar to Claude Code.

Install with UI Support

pip install qasql[ui]

Launch Terminal UI

# Start UI (configure LLM and database interactively)
python -m qasql.tui

# Or connect to database on startup
python -m qasql.tui --db ./database.sqlite

# With different LLM providers
python -m qasql.tui --provider ollama --model llama3.2:3b
python -m qasql.tui --provider anthropic --api-key sk-ant-xxx
python -m qasql.tui --provider openai --api-key sk-xxx

# Use remote Ollama server
python -m qasql.tui --base-url http://192.168.1.100:11434

Terminal UI Preview

╔═══════════════════════════════════════════════════════════════╗
║   ██████╗  █████╗ ███████╗ ██████╗ ██╗                       ║
║  ██╔═══██╗██╔══██╗██╔════╝██╔═══██╗██║                       ║
║  ██║   ██║███████║███████╗██║   ██║██║                       ║
║  ╚██████╔╝██║  ██║███████║╚██████╔╝███████╗                  ║
║   Text-to-SQL Engine - Local & Private                       ║
╚═══════════════════════════════════════════════════════════════╝

qasql> /db ./california_schools.sqlite
✓ Connected to: california_schools (3 tables)

qasql> How many schools are there?
┌─────────────────────────────────────────┐
│ Generated SQL (confidence: 85%)         │
├─────────────────────────────────────────┤
│ SELECT COUNT(*) FROM schools;           │
└─────────────────────────────────────────┘

Terminal UI Commands

Connection Commands:

Command Description
/llm Show current LLM configuration
/llm ollama [model] Use local Ollama
/llm ollama [model] [url] Use Ollama at custom URL
/llm anthropic [api_key] Use Claude API
/llm openai [api_key] Use OpenAI API
/db Show current database
/db <path_or_uri> Connect to database

Query Commands:

Command Description
/tables List all tables
/schema <table> Show table schema
/hint <text> Set hint for next query
/sql <query> Execute raw SQL
/history Show query history

Other Commands:

Command Description
/status Show current configuration
/clear Clear screen
/help Show help
/quit Exit

Or just type a natural language question to generate SQL!

Database Connection Formats

Type Example
SQLite (file path) /db ./database.sqlite
SQLite (URI) /db sqlite:///path/to/db.sqlite
PostgreSQL /db postgresql://user:pass@localhost:5432/dbname

CLI Usage

Using python -m qasql

cd qasql-sdk

# List tables
python -m qasql tables --db-uri sqlite:///path/to/database.sqlite

# Setup database (extract schema)
python -m qasql setup --db-uri sqlite:///path/to/database.sqlite

# Generate SQL from question
python -m qasql query --db-uri sqlite:///path/to/database.sqlite \
    --question "How many customers are there?"

# Generate SQL with hint (enables SME strategy)
python -m qasql query --db-uri sqlite:///path/to/database.sqlite \
    --question "What is the total revenue?" \
    --hint "revenue = sum(amount) from orders table"

# Execute the generated SQL
python -m qasql query --db-uri sqlite:///path/to/database.sqlite \
    --question "List all products" \
    --execute

# Show verbose output with timings
python -m qasql query --db-uri sqlite:///path/to/database.sqlite \
    --question "Count orders by status" \
    --verbose

CLI Options

Global Options:
  --config, -c       Path to config file (JSON)
  --db-uri           Database URI (sqlite:/// or postgresql://)
  --provider         LLM provider: ollama, anthropic, openai (default: ollama)
  --model            LLM model name (default: llama3.2)
  --ollama-url       Ollama server URL (default: http://localhost:11434)
  --output-dir, -o   Output directory (default: ./qasql_output)

Commands:
  setup              Extract schema and generate descriptions
    --readable-names   Path to readable names mapping file
    --force, -f        Force regeneration

  query              Generate SQL from natural language
    --question, -q     Natural language question (required)
    --hint             SME hint for better accuracy
    --execute, -e      Execute the generated SQL
    --verbose, -v      Show timing information
    --json             Output as JSON

  tables             List database tables

Python SDK

Basic Usage

from qasql import QASQLEngine

# Initialize engine
engine = QASQLEngine(
    db_uri="sqlite:///path/to/database.sqlite",
    llm_provider="ollama",      # or "anthropic", "openai"
    llm_model="llama3.2",       # model name
    output_dir="./qasql_output"
)

# One-time setup (extracts schema, generates column descriptions)
setup_result = engine.setup()
print(f"Tables found: {setup_result.tables_found}")

# Query WITHOUT hint → generates 4 candidates
result = engine.query("How many customers are there?")
print(result.sql)
print(result.confidence)

# Query WITH hint → generates 5 candidates (includes SME strategy)
result = engine.query(
    question="What is the total revenue by month?",
    hint="revenue = sum(order_amount), use orders table"
)
print(result.sql)
print(result.reasoning)

# Execute SQL directly
rows, columns = engine.execute_sql(result.sql)
print(columns)
print(rows)

With Configuration File

from qasql import QASQLEngine

# Load from config file
engine = QASQLEngine(config_file="qasql.config.json")
engine.setup()
result = engine.query("Show all orders")

Inspect Schema

# Get table list
tables = engine.get_tables()
print(tables)  # ['customers', 'orders', 'products']

# Get full schema
schema = engine.get_schema()
for table_name, info in schema.items():
    print(f"{table_name}: {len(info['columns'])} columns, {info['row_count']} rows")

# Get column descriptions
profile = engine.get_profile()

Inspect Query Results

result = engine.query("Show top 10 customers by revenue")

# Access all fields
print(result.sql)           # Generated SQL
print(result.confidence)    # 0.0 - 1.0
print(result.reasoning)     # Why this SQL was selected
print(result.question)      # Original question
print(result.hint)          # Hint if provided

# Candidate details
print(f"Candidates: {result.successful_candidates}/{result.total_candidates}")
for candidate in result.candidates:
    print(f"  [{candidate.strategy}] {candidate.success} - {candidate.sql[:50]}...")

# Timing information
for stage, ms in result.metadata.get("timings", {}).items():
    print(f"  {stage}: {ms:.0f}ms")

# Convert to dictionary
result_dict = result.to_dict()

Configuration

Config File (qasql.config.json)

{
  "database": {
    "type": "sqlite",
    "uri": "./database.sqlite"
  },
  "llm": {
    "provider": "ollama",
    "model": "llama3.2",
    "base_url": "http://localhost:11434"
  },
  "options": {
    "readable_names": "mappings.json",
    "relevance_threshold": 0.5,
    "query_timeout": 30,
    "output_dir": "./output"
  }
}

PostgreSQL Configuration

{
  "database": {
    "type": "postgresql",
    "uri": "postgresql://user:password@localhost:5432/mydb"
  },
  "llm": {
    "provider": "ollama",
    "model": "llama3.2"
  }
}

Environment Variables

export QASQL_DB_URI="sqlite:///database.sqlite"
export QASQL_DB_TYPE="sqlite"
export QASQL_LLM_PROVIDER="ollama"
export QASQL_LLM_MODEL="llama3.2"
export QASQL_OLLAMA_URL="http://localhost:11434"

# For cloud providers
export ANTHROPIC_API_KEY="sk-ant-..."
export OPENAI_API_KEY="sk-..."

Readable Names Mapping

If your database has cryptic column names, provide a mapping file:

JSON format:

{
  "tbl_cust_01": {
    "table_readable_name": "Customers",
    "columns": {
      "col_a": "Customer Name",
      "col_b": "Email Address",
      "col_c": "Registration Date"
    }
  },
  "tbl_ord_02": {
    "table_readable_name": "Orders",
    "columns": {
      "ord_id": "Order ID",
      "amt_val": "Order Amount"
    }
  }
}

CSV format:

table,column,readable_name
tbl_cust_01,col_a,Customer Name
tbl_cust_01,col_b,Email Address
tbl_ord_02,amt_val,Order Amount

How It Works

Architecture

┌─────────────────────────────────────────────────────────────┐
│                        QA-SQL SDK                            │
├─────────────────────────────────────────────────────────────┤
│                                                              │
│  ┌──────────┐    ┌──────────────┐    ┌──────────────────┐  │
│  │ Database │───▶│ QASQLEngine  │───▶│ LLM Provider     │  │
│  │ SQLite/  │    │              │    │ Ollama/Anthropic │  │
│  │ Postgres │◀───│              │◀───│ /OpenAI          │  │
│  └──────────┘    └──────────────┘    └──────────────────┘  │
│                                                              │
└─────────────────────────────────────────────────────────────┘

Two-Phase Flow

Phase 1: Setup (One-time)

Database → Schema Extraction → Column Descriptions → Ready

Phase 2: Query (Runtime)

Question → Schema Agent → Candidate Generation → Execution → Judge → SQL
           (Map-Reduce)   (4-5 strategies)       (retry)     (select best)

Candidate Generation Strategies

Strategy Description With Hint Without Hint
full_schema Complete database schema ✓ ✓
sme_metadata Schema + domain expert hints ✓ ✗ (skipped)
minimal_profile Column names only ✓ ✓
focused_schema Relevant tables only ✓ ✓
full_profile Schema + descriptions ✓ ✓
Total 5 4

When no hint is provided, the SME strategy is skipped since it requires domain knowledge.


Examples

Example 1: Simple Query

from qasql import QASQLEngine

engine = QASQLEngine(db_uri="sqlite:///sales.sqlite")
engine.setup()

result = engine.query("How many orders were placed last month?")
print(result.sql)
# SELECT COUNT(*) FROM orders WHERE order_date >= date('now', '-1 month')

Example 2: Query with Hint

result = engine.query(
    question="What is the average order value by customer segment?",
    hint="order value = quantity * unit_price, segment is in customers table"
)
print(result.sql)
print(result.confidence)  # Higher confidence with hint

Example 3: Execute and Display Results

result = engine.query("List top 5 customers by total purchases")

if result.sql:
    rows, columns = engine.execute_sql(result.sql)

    # Print as table
    print(" | ".join(columns))
    print("-" * 50)
    for row in rows:
        print(" | ".join(str(v) for v in row))

Example 4: Using Anthropic

import os
os.environ["ANTHROPIC_API_KEY"] = "your-key"

engine = QASQLEngine(
    db_uri="sqlite:///mydb.sqlite",
    llm_provider="anthropic",
    llm_model="claude-sonnet-4-5-20250929"
)

API Reference

QASQLEngine

class QASQLEngine:
    def __init__(
        self,
        db_uri: str = None,              # Database URI
        db_type: str = None,             # "sqlite" or "postgresql"
        llm_provider: str = "ollama",    # "ollama", "anthropic", "openai"
        llm_model: str = "llama3.2",     # Model name
        llm_base_url: str = "http://localhost:11434",
        readable_names: str = None,      # Path to mappings file
        output_dir: str = "./qasql_output",
        config_file: str = None,         # Path to config JSON
    ): ...

    def setup(self, force: bool = False) -> SetupResult: ...
    def query(self, question: str, hint: str = None) -> QueryResult: ...
    def execute_sql(self, sql: str) -> tuple[list, list]: ...
    def get_tables(self) -> list[str]: ...
    def get_schema(self) -> dict: ...
    def get_profile(self) -> dict: ...

QueryResult

@dataclass
class QueryResult:
    sql: str                    # Generated SQL
    confidence: float           # 0.0 - 1.0
    question: str               # Original question
    hint: str | None            # Provided hint
    reasoning: str              # Selection reasoning
    candidates: list            # All candidates
    successful_candidates: int  # Count of successful
    total_candidates: int       # Total count
    metadata: dict              # Timings, etc.

    def to_dict(self) -> dict: ...

SetupResult

@dataclass
class SetupResult:
    success: bool
    database_name: str
    tables_found: int
    schema_path: str | None
    descriptions_path: str | None
    errors: list[str]

Troubleshooting

"command not found: qasql"

Use python -m qasql instead:

python -m qasql tables --db-uri sqlite:///mydb.sqlite

"Cannot connect to Ollama"

Make sure Ollama is running:

# Terminal 1
ollama serve

# Terminal 2
ollama pull llama3.2

"ANTHROPIC_API_KEY not found"

Set the environment variable:

export ANTHROPIC_API_KEY='your-key'

"Database not found"

Check the path is correct:

# Use absolute path
python -m qasql tables --db-uri sqlite:////absolute/path/to/db.sqlite

# Or relative path
python -m qasql tables --db-uri sqlite:///./relative/path/db.sqlite

"No module named 'qasql'"

Install the package:

cd qasql-sdk
pip install -e .

Data Privacy

Provider Data Location Recommendation
Ollama 100% Local Enterprise / Sensitive data
Anthropic Cloud (Anthropic servers) Development / Non-sensitive
OpenAI Cloud (OpenAI servers) Development / Non-sensitive

With Ollama, zero data leaves your network.


License

MIT License - see LICENSE for details.


Support

Metadata

Release files for qasql 1.0.4

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for qasql 1.0.4
File Size Uploaded
qasql-1.0.4.tar.gz 42.5 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for qasql 1.0.4
File Interpreter ABI Platform
qasql-1.0.4-py3-none-any.whl Python 3 none any Details

Total release size: 84.0 kB

Release files / qasql-1.0.4.tar.gz

Download URL qasql-1.0.4.tar.gz
Size 42.5 kB
Tags Source
SHA-256 checksum
How to use checksums
b3f7c6915fd1aaf2fd4a7f5ffcee30deb3e67d8cb1436f2e96db1ec80d9070f1
BLAKE2b-256 checksum
How to use checksums
36f8ffc6db99a8af1d103fb7d1686f6484bd8c830d0892cc4412a5fae2607194
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Release files / qasql-1.0.4-py3-none-any.whl

Download URL qasql-1.0.4-py3-none-any.whl
Size 41.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
33ad1a0f3f8eb4cf5b5b0552234fe2adcbaa951c896ac0259e8ed12b65d66702
BLAKE2b-256 checksum
How to use checksums
74240cafb2756732fb33e1af9e2ae93bee65075156dca5fa812d2397c2edc473
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Release history Release notifications | RSS feed

This release

1.0.4 This release

2 release files

1.0.3

2 release files

1.0.2

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page