Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🌊 QueryCove

A small multi-agent AI data assistant — ask a question in plain English, get a safe SQL answer; drop in a messy file, get it cleaned and loaded.

Built with Python, PostgreSQL, and LangChain/LangGraph. No frontend, no bloat — a single FastAPI endpoint backed by three cooperating agents.

                        ┌───────────────┐
   "How many orders     │  Orchestrator │
    did we get in       │   (router)    │
    March?"        ───► └───────┬───────┘
                                 │
                    ┌────────────┴────────────┐
                    ▼                         ▼
            ┌───────────────┐         ┌───────────────┐
            │  SQL Expert    │         │  ETL Expert    │
            │  (LangGraph)   │         │ (ReAct agent)  │
            │                │         │                │
            │ NL -> safe     │         │ extract ->     │
            │ read-only SQL  │         │ clean ->       │
            │ -> answer      │         │ load           │
            └───────┬───────┘         └───────┬───────┘
                    │                         │
                    ▼                         ▼
              ┌─────────────────────────────────┐
              │           PostgreSQL             │
              └─────────────────────────────────┘

Why QueryCove

Managed "chat with your data" products are expensive and opaque. QueryCove is a compact, from-scratch reference implementation of the same idea — routing, safe SQL generation, and ETL — that you can actually read end to end.

  • Orchestrator — a lightweight LLM router that reads the request and decides whether it's a question about existing data or an instruction to load new data.
  • SQL Expert agent — turns a plain-English question into read-only SQL, executes it, and answers in natural language.
  • ETL Expert agent — a ReAct agent that extracts data from a file, cleans it (column mapping, date parsing, deduping), and loads it into Postgres.

Safety, by design

This is the part most "AI + database" demos skip, so it's worth calling out:

  • Belt and suspenders on SQL. The SQL agent connects through a dedicated read-only Postgres role (sql_agent_ro) that is physically incapable of INSERT/UPDATE/DELETE/DROP — enforced by Postgres itself, not just a prompt. Generated SQL is also independently checked (single-statement, SELECT-only, keyword-denylist) before it's ever executed.
  • Explicit refusal, not silent rewriting. If a request asks the SQL agent to change data, it refuses outright via a structured refused flag — it never quietly substitutes a SELECT for what you actually asked.
  • Parameterized ETL, not arbitrary code. The ETL agent's transform_data tool only ever does column renaming, date parsing, email normalization, and deduplication — the model picks the parameters, never arbitrary transform logic.

Quickstart

Requires uv and Docker.

# 1. Install dependencies
uv sync

# 2. Configure environment
cp .env.example .env
# then fill in a real OPENAI_API_KEY

# 3. Start Postgres (auto-seeds sample e-commerce data + the read-only role)
docker compose up -d db

# 4. Run the API
uv run uvicorn app.main:app --reload

To reset and re-seed from scratch: docker compose down -v && docker compose up -d db (the init scripts only run against an empty data volume).

Usage

# Health check (also verifies DB connectivity)
curl -s localhost:8000/health

# Ask a question -> routed to the SQL agent
curl -s -X POST localhost:8000/query -H 'Content-Type: application/json' \
  -d '{"request": "Which 3 customers have spent the most money in total?"}' | jq

# Ask to load new data -> routed to the ETL agent
curl -s -X POST localhost:8000/query -H 'Content-Type: application/json' \
  -d '{"request": "Load the new customers file into the database"}' | jq

How the ETL path works

There's no chat/file-upload UI on purpose — drop a file into data/incoming/ (e.g. the included new_customers.csv, which is deliberately messy: mixed-case emails, US-style dates, a duplicate, a missing field) before sending the request. The ETL agent looks in that folder itself; the request text is just an instruction, not a file upload.

Project layout

app/
├── orchestrator/   # LLM router: decides "sql" vs "etl"
├── agents/
│   ├── sql_agent/  # NL -> SQL -> answer, with safety.py as the SELECT-only gate
│   └── etl_agent/  # ReAct loop: extract, transform_data, load
├── api/            # FastAPI routes + schemas (POST /query, GET /health)
└── db.py           # SQLAlchemy engines (read-write + read-only)
db/
├── init/           # Schema + role creation SQL, run on first container start
└── seed/           # Sample e-commerce CSVs (customers, products, orders, order_items)

Tests

uv run pytest
  • test_sql_safety.py, test_seed_data.py — pure unit tests, no external dependencies.
  • test_sql_agent.py, test_etl_agent.py, test_orchestrator_routing.py — integration tests against a real OpenAI API and a running Postgres; they auto-skip unless both OPENAI_API_KEY is a real key and docker compose up -d db is running.

Design notes / scope decisions

  • Migrations: plain SQL init scripts (db/init/*.sql), not Alembic — there's no existing schema to migrate and no production deployment target for this project. Revisit with Alembic if the schema starts evolving after initial development.
  • SQL safety: enforced twice — once at the database role level, once at the application level (app/agents/sql_agent/safety.py) — so a bug in either layer alone can't lead to a write.
  • ETL transforms are parameterized, not arbitrary code, trading some flexibility for predictability and safety.

Stack

Python 3.12 · FastAPI · LangChain / LangGraph · PostgreSQL · Docker · uv

About

Multi-agent AI data assistant — ask questions in plain English, get safe read-only SQL answers; drop in a file, get it cleaned and loaded into Postgres.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages