Natural-language-to-SQL analytics pipeline: ask a business question in plain English, get back an SQL query, a result table, and an AI-generated explanation
query.mp4
Model your database relations intuitively, enrich with semantic meaning from documentation
builder.mp4
Monitor performance
Change LLM provider configurations
User question (natural language)
→ LLM Extractor → QuerySchema (structured intent)
→ Vector Search → Column resolution (FAISS over schema metadata)
→ SQL Generator → SQL query (SELECT, JOINs, WHERE, GROUP BY, ORDER BY, LIMIT)
→ DuckDB → pandas DataFrame
→ LLM Explainer → Business insight (optional, currently disabled)
Interface: a **FastAPI backend** serving a **React/Vite frontend**.
Before starting, ensure you have the following installed and running based on your preferred deployment method:
- Docker Desktop: Required if you plan to use the Docker Quick Start.
- Python ≥ 3.13 and Node.js ≥ 18: Required if you plan to run the services natively for development.
- Ollama: Required if you are using local LLMs. It must be installed and running on your host machine.
- Cloud / Remote API Keys: Optional. Required if you use cloud services instead of local Ollama (e.g. Groq for chat LLMs or Jina AI for vector embeddings).
The easiest way to run TinyBI is using Docker, which packages both the FastAPI backend and React frontend into a single container.
- Build and start the application:
docker compose up --build -d app- Access the web interface: Open http://localhost:4510 in your browser.
Note: If you are using local LLMs, ensure you have pulled the required models via Ollama on your host machine (e.g.,
ollama pull nomic-embed-textandollama pull granite4:3b). See the Local Development Setup section for a full list of supported models.
TinyBI tightly integrates with Ollama to provide a 100% local, privacy-first analytics pipeline. If you aren't using remote APIs (like OpenAI or Groq), Ollama must be running on your host machine.
macOS / Linux:
curl -fsSL https://ollama.com/install.sh | sh
ollama serveWindows:
Download and run the installer from ollama.com/download/windows. Ollama starts automatically as a system tray service (no manual serve command needed).
- Running Natively (Local Dev): TinyBI expects Ollama at
http://127.0.0.1:11434. - Running via Docker: TinyBI automatically resolves
localhosttohost.docker.internalso the Docker container can reach Ollama on your host machine. You do not need to configure this manually. - Custom Endpoint: If you host Ollama on a different machine or port, set the
OLLAMA_BASE_URLenvironment variable (e.g.,OLLAMA_BASE_URL="http://192.168.1.100:11434").
TinyBI requires two types of models to function: an embedding model (for vector search) and a chat model (for text-to-SQL extraction).
# 1. Embedding Model (Required for vector search and indexing)
ollama pull qwen3-embedding:0.6b
# 2. Chat Model (Required for text-to-SQL extraction)
ollama pull granite4:3bBecause TinyBI delegates directly to Ollama, you can use any model available in the Ollama library. The models below are simply the ones we've tested and recommend for SQL generation.
By default, the backend falls back to ibm/granite4:3b if no model is specified. You can use a different model by passing its name in the model field of the JSON payload when calling the /query API.
| Model | Size | Notes |
|---|---|---|
ibm/granite4.1:3b |
2 GB | Default. Good balance of speed and SQL quality |
gemma3:4b |
2.5 GB | Google's lightweight model |
llama3.2:3b |
2 GB | Meta's compact model |
phi4-mini:3.8b |
2.4 GB | Microsoft's small model |
qwen2.5:7b |
4.7 GB | Highest quality local option (slower) |
By default, TinyBI uses qwen3-embedding:0.6b. If you change the embedding model, you must rebuild the vector index using the Vector Index Builder UI (done automatically via the config menu).
| Model | Source | Notes |
|---|---|---|
qwen3-embedding:0.6b |
Ollama | Default. Fast and lightweight |
nomic-embed-text |
Ollama | 768-dim alternative |
bge-m3:567m |
Ollama | Multilingual support |
jina-embeddings-v5-text-small |
Jina AI | Fast cloud embeddings — requires Jina API key |
text-embedding-3-small |
OpenAI | Remote — requires OPENAI_API_KEY |
If you do not want to run local models with Ollama, you can use cloud providers for both LLMs and vector embeddings.
- Groq (Chat LLM): Create an account at console.groq.com/home and generate an API key.
- Jina AI (Vector Embeddings): Create an account at jina.ai and generate an API key.
- Start the application (
docker compose up -d app) and open http://localhost:4510 in your browser. - Click the Setup tab in the top navigation.
- Select Remote LLM and paste your Groq API key (
gsk_...). - Select a model,
llama-3.1-8b-instantis recommended - Select Jina AI under Embeddings, set
base_urltohttps://api.jina.ai/v1, and paste your Jina API key. - Select an embedding model,
jina-embeddings-v3is recommended - Click Save & Connect. The backend tests the connection and automatically re-embeds the vector index.
If you prefer to run the services natively for development (Requires Python ≥ 3.13 and Node.js ≥ 18):
uv venv
uv pip install -r requirements.txtcd frontend && npm install && cd ..Create a .env file in the project root. e.g.
OPENAI_API_KEY=sk-...Terminal 1 — Backend (FastAPI):
uv run uvicorn src.api.main:app --reload --port 8000Terminal 2 — Frontend (Vite dev server):
cd frontend && npm run devThe frontend opens at http://localhost:5173. It proxies /api/* to the backend at http://127.0.0.1:8000.
The vector index maps natural language concepts to database columns. It is pre-built and included in the repo (data/app_data/columns.faiss + columns.json) — no action needed.
To rebuild it (e.g. after changing the database schema or the embedding model):
Use the frontend's Vector Index Builder — navigate to /vector-index, load the columns from data/app_data/columns.json, verify them, and click "Submit Index Batch".
curl -X POST http://127.0.0.1:8000/vector/index-entries/batch \
-H "Content-Type: application/json" \
-d @data/app_data/columns.jsonThe index is persisted to
data/app_data/columns.faissand reused across restarts.
uv run pytest -m "not integration"cd frontend && npm testPromptfoo runs in a Docker container to prevent dependency conflicts on the host machine.
Note: The repository includes a pre-seeded evaluation database (
data/promptfoo_store/promptfoo.db) with the 2 most recent runs. You do not need to run new evaluations to view the dashboard unless you want to generate fresh benchmark data.
docker compose build promptfoo-evaluv run python scripts/prepare_db.py \
--duckdb data/intermediate/financial.duckdb \
--sqlite data/minidev_raw/financial/financial.sqlite./scripts/run_insight_eval.shThis script executes the Promptfoo evaluation in Docker. It scores pipeline responses using RAGAS FactualCorrectness against reference answers in src/eval/tests.yaml. Execute this evaluation on machines with dedicated GPUs for faster inference.
After running evaluations, run the pruning script to keep the database size small (~18 MB) before committing changes:
uv run python scripts/prune_promptfoo_db.pyYou can inspect the evaluation results in two ways:
Option A: Via the TinyBI Web UI
- Open the System Health & Evaluation page at
http://localhost:4510. - Click the Promptfoo Dashboard button.
- Follow the modal instructions to start the viewer container.
Option B: Via Terminal
- Start the viewer container:
docker compose up promptfoo-view -d- Open
http://localhost:15500in your web browser.
To stop the viewer container:
docker compose stop promptfoo-view.
├── data/
│ ├── app_data/ # FAISS vector index + column metadata
│ │ ├── columns.faiss # pre-built column embeddings
│ │ └── columns.json # column entries (descriptions, FK refs, etc.)
│ ├── intermediate/ # Cleaned datasets
│ └── minidev_raw/ # Raw database files
│ ├── financial/ # The financial database (SQLite + DuckDB + CSVs)
│ └── debit_card_specializing/ # Alternative database
│
├── notebooks/ # Jupyter notebooks (EDA)
├── frontend/ # React + Vite + TypeScript
│ └── src/
│ ├── pages/
│ │ ├── DashboardHome.tsx
│ │ ├── QueryPage.tsx # Natural language query UI
│ │ └── VectorIndexBuilderPage.tsx # Build/rebuild vector index
│ ├── components/ui/ # shadcn/ui components
│ └── lib/api.ts # API client types + fetch wrappers
│
├── src/ # Python backend
│ ├── main.py # Application entry point
│ ├── agent.py # LangGraph agent (multi-turn NLP → SQL orchestrator)
│ ├── config.py # Path configuration
│ ├── api/
│ │ ├── main.py # FastAPI app (lifespan, health, routers)
│ │ └── routes/
│ │ ├── query.py # POST /query (NL → SQL → DataFrame → explanation)
│ │ └── vector.py # GET/POST /vector (FAISS index management)
│ ├── llms/
│ │ ├── extractor.py # LLM chain: user question → QuerySchema
│ │ ├── explainer.py # LLM chain: results → business insight
│ │ └── main_pipeline.py # Orchestrator (extract → resolve → query → explain)
│ ├── tools/
│ │ └── query_tool.py # QuerySchema + vector search → SQL generation + execution
│ └── utils/
│ ├── database.py # DuckDB wrapper (SQLite import, query, singleton)
│ ├── models.py # LLM model definitions + factory functions
│ ├── prompts.py # EXTRACTOR_PROMPT and EXPLAINER_PROMPT
│ ├── pydantic_models.py # Pydantic schemas (QuerySchema, ColumnVectorIndexEntry, etc.)
│ ├── sql_normaliser.py # Maps structured query → SQL clauses
│ ├── rag/
│ │ ├── vector_controller.py # High-level vector search logic
│ │ └── vector_index.py # FAISS index (build, persist, search)
│ └── value_resolution/
│ ├── column_resolver.py # Pick best column per intent via schema graph
│ ├── db_schema_graph.py # NetworkX graph of FK relationships
│ ├── join_resolution.py # BFS join path resolution
│ └── value_resolver.py # Fuzzy literal matching against column categories
│
├── tests/ # Python test suite
├── requirements.txt
└── pyproject.toml
GET /health → {"status": "ok"}
| Method | Path | Description |
|---|---|---|
POST |
/vector/index-entries/batch |
Build/replace the FAISS index with the default embedding model |
POST |
/vector/index-entries/batch/by-model/{key} |
Build/replace the FAISS index with a specific embedding model (nomic, qwen3, bge-m3, openai-small) |
GET |
/vector/index-entries/current |
Retrieve the current index metadata |
| Env variable | Default | Description |
|---|---|---|
OPENAI_API_KEY |
— | Required for remote extraction with GPT-4o |
GROQ_API_KEY |
— | Groq cloud provider API key |
JINA_API_KEY |
— | Jina AI embeddings API key |
LOCAL_API_BASE |
— | Base URL for custom or cloud embedding API |
VITE_BACKEND_URL |
http://127.0.0.1:8000 |
Backend URL for the frontend proxy |
Database paths are configured in src/config.py.
- The explainer agent is currently commented out (returns
null/empty). To re-enable, uncomment the marked blocks insrc/api/routes/query.pyandsrc/llms/main_pipeline.py. - The vector index must be rebuilt if you change the database schema or the embedding model.