Skip to content

About

Voice data analyst: ask Postgres questions out loud. LangGraph agent, DSPy MIPROv2-optimized Text-to-SQL, sqlglot + read-only guards, STT/TTS, auto charts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

voice-text2sql

CI Python LangGraph DSPy Postgres License

A voice data analyst. Hold a button, ask a question about a Postgres database out loud, and hear the answer back, with the SQL, the result table and an automatic chart on screen. A LangGraph agent orchestrates the pipeline, DSPy generates the SQL with prompts optimized by MIPROv2 against an execution-accuracy metric, and every query is checked twice for safety before it touches the database.

🎙  "Which genres made the most money?"
SQL  SELECT g.name AS genre, sum(il.unit_price * il.quantity) AS revenue FROM invoice_line il
     JOIN track t ON t.track_id = il.track_id JOIN genre g ON g.genre_id = t.genre_id
     GROUP BY g.name ORDER BY revenue DESC LIMIT 5
🔊  "Rock leads with about 827 dollars, followed by Latin, Metal, Alternative & Punk, and TV Shows."
📊  bar chart of revenue by genre

🎙  "And how much of that came from the USA?"     ← follow-up keeps context

Architecture

flowchart LR
    MIC([🎙 audio]) --> STT[speech-to-text]
    STT --> P[prepare]
    TXT([text]) --> P
    P --> G[generate SQL<br/>DSPy ChainOfThought<br/>MIPROv2-optimized]
    G --> V{validate<br/>sqlglot AST + EXPLAIN}
    V -->|error| F[repair<br/>DSPy FixSQL with the DB error]
    F --> V
    V -->|ok| X[execute<br/>read-only txn, timeout, row cap]
    X --> A[spoken answer<br/>DSPy]
    A --> C[chart inference]
    C --> TTS[text-to-speech]
    TTS --> OUT([🔊 answer + SQL + table + chart])
Loading
Concern Approach
SQL generation A DSPy ChainOfThought program over a typed signature (db_schema, history, question → sql). The schema is introspected live: columns, types, primary and foreign keys, and a few sample values per text column so the model uses real spellings.
Prompt optimization voice-sql optimize runs DSPy MIPROv2 (instruction proposals + bootstrapped few-shot demos) against 24 training questions, scores candidates by execution match on the real database, and saves the compiled program, which the agent loads at startup.
Self-correction Postgres errors go back to a separate DSPy FixSQL predictor with the failed query, up to a configurable number of attempts.
Safety, layer 1 sqlglot parses every query into an AST: exactly one statement, must be a SELECT/WITH, and no INSERT/UPDATE/DELETE/DDL/SELECT INTO anywhere, including inside CTEs.
Safety, layer 2 Every query runs in a READ ONLY transaction with a statement_timeout and a row cap, as a dedicated analyst role that only has SELECT and defaults to read-only transactions.
Conversation LangGraph checkpoints per thread_id; earlier successful questions and their SQL are passed as history, so "and only for 2024?" works. Failed turns are excluded from history.
Voice Speech-to-text and text-to-speech through any OpenAI-compatible audio API: OpenAI, Groq Whisper, or self-hosted (e.g. Speaches / Kokoro). If TTS fails, the answer still returns as text.
Charts Result shape decides the chart: one label column and numeric columns become a bar chart, or a line chart when the label is a date or period.
Observability LangSmith traces the graph, the DSPy calls, and the validate, execute, transcribe and synthesize steps.

Quickstart

git clone https://github.com/Alby-Rajesh/voice-text2sql.git
cd voice-text2sql
cp .env.example .env
docker compose up --build

Open http://localhost:8080, hold the mic button and ask. Compose starts Postgres 16 preloaded with the Chinook music-store database (artists, albums, tracks, customers, invoices) and the read-only analyst role. Point VSQL_LLM__* at any OpenAI-compatible model (vLLM, OpenAI, Groq …) and VSQL_SPEECH__* at an audio API.

Without Docker:

pip install -e ".[dev]"
make db
voice-sql chat
voice-sql serve

API

curl -s localhost:8080/v1/ask -H 'content-type: application/json' \
  -d '{"question": "Who are the top 5 customers by spending?", "thread_id": "demo", "speak": false}'

curl -s localhost:8080/v1/voice -F audio=@question.webm -F thread_id=demo

Both return the transcript, answer, SQL, columns, rows, attempts, a chart spec and (for voice, or speak: true) base64 MP3 audio.

Prompt optimization with DSPy

voice-sql optimize --auto light
voice-sql evaluate

The gold set (data/chinook_eval.jsonl) has 40 hand-written questions with reference SQL, from simple counts to multi-join revenue rollups, self-joins and anti-joins, split into 24 train and 16 dev. Every reference query is executed in CI.

Metric: execution match. Both queries run on the database, and the prediction counts as correct when it returns the same rows: same row count, every reference value present in the matching row, order enforced only when the reference sorts. Extra columns and different aliases are allowed, because "Rock, 826.65" is right whether or not the model also returns genre_id.

optimize writes artifacts/sqlgen.json (the compiled program) and artifacts/optimization.json (baseline vs optimized dev accuracy). evaluate runs the whole agent, including self-correction, and reports execution accuracy, first-try accuracy, valid-query rate, attempts and latency.

Model Program Dev execution accuracy
Qwen2.5-Coder-7B-Instruct zero-shot run voice-sql optimize
Qwen2.5-Coder-7B-Instruct MIPROv2 (light)

Configuration

Environment variables with the VSQL_ prefix and __ for nesting (see settings.py):

Variable Default Purpose
VSQL_LLM__MODEL openai/Qwen/Qwen2.5-Coder-7B-Instruct DSPy/LiteLLM model id
VSQL_LLM__BASE_URL http://localhost:8000/v1 OpenAI-compatible endpoint
VSQL_DB__DSN postgresql://analyst:analyst@localhost:5432/chinook database; use a read-only role
VSQL_DB__STATEMENT_TIMEOUT_MS 5000 per-query timeout
VSQL_DB__ROW_LIMIT 200 rows returned per query
VSQL_SPEECH__BASE_URL – OpenAI-compatible audio endpoint
VSQL_SPEECH__STT_MODEL / TTS_MODEL / TTS_VOICE whisper-1 / tts-1 / alloy speech models
VSQL_AGENT__MAX_ATTEMPTS 3 generate → validate → repair limit
VSQL_AGENT__PROGRAM_PATH artifacts/sqlgen.json compiled DSPy program
VSQL_LANGSMITH__API_KEY – enables LangSmith tracing

Project structure

src/voice_sql/
├── agent.py        LangGraph state machine: generate → validate → repair → execute → answer
├── program.py      DSPy signatures and modules: GenerateSQL, FixSQL, SpokenAnswer
├── database.py     schema introspection (pg_catalog), sqlglot guard, read-only execution
├── evaluation.py   execution-match metric, MIPROv2 optimization, agent evaluation
├── speech.py       speech-to-text and text-to-speech over OpenAI-compatible audio APIs
├── chart.py        chart inference from result shape
├── api.py          FastAPI: /v1/ask, /v1/voice and the web app
├── web/index.html  push-to-talk UI with result table and Chart.js
└── cli.py          chat, serve, optimize, evaluate
db/                 Chinook schema + data and the read-only analyst role
data/               40-question gold set

Testing

make db
make test

Unit tests cover the agent graph (repair loop, retry limit, conversation history), the SQL guard, the metric, chart inference, the DSPy program (with DummyLM) and the voice API. Integration tests run against a real Chinook Postgres as the read-only role: schema introspection, error reporting, the read-only transaction, and every gold query. CI starts Postgres as a service and runs both.

License

MIT. The Chinook database is © Luis Rocha, MIT licensed.

About

Voice data analyst: ask Postgres questions out loud. LangGraph agent, DSPy MIPROv2-optimized Text-to-SQL, sqlglot + read-only guards, STT/TTS, auto charts.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages