When you ask "what's the average order value by region?", the LLM just needs to know there's an order_value column (decimal) and a region column (varchar). It generates SQL from that. DuckDB-WASM executes the query locally in your browser. The AI never sees a single row.
Architecture: - Layer 1: DuckDB compiled to WebAssembly runs in a Web Worker. Files load via the File API — no upload, no network request. - Layer 2: Schema extraction (DESCRIBE table + information_schema.columns) — column names and types are all that leaves the browser. - Layer 3: LLM generates SQL from schema + your question. SQL goes back to Layer 1 and runs locally.
For the free tier, even the AI runs locally (WebLLM/Ollama), so the entire pipeline is air-gapped.
The hard part was making multi-step analysis work. A single SQL query rarely answers a real question. So I built a LangGraph agent that chains queries — runs one, inspects the results, decides what to investigate next. "Why did churn spike in Q4?" might trigger 4-5 queries before it pieces together the answer.
What I got wrong initially: schema alone isn't always enough context when column names are cryptic (col_a, val_1). The fix was auto-profiling — basic stats like distinct counts, min/max, sample values — metadata about the data, not the data itself.
You can verify the privacy claim yourself: load a file, open Chrome DevTools → Network tab, and confirm no requests carrying your data.
Live demo with sample data (no signup): https://app.queryveil.com/demo
Happy to answer any questions about the architecture, DuckDB-WASM performance limits, or the schema-only prompting approach.