A security-focused natural-language analytics workbench for a local music-store database. It turns a question into SQL, validates the SQL with an AST policy, executes it through a read-only SQLite connection, and produces a grounded answer.
The application runs entirely offline with a deterministic fake provider, or uses Google Gemini for real language-model generation. No Azure service is required.
The screenshot is the deployed application answering a live Gemini question: generated SQL, AST validation, read-only execution, and a grounded answer taken from the returned rows.
Text-to-SQL can make analytics accessible to non-SQL users, but executing model-generated code creates a serious trust problem. This project treats the model as untrusted: generated SQL must cross an application policy and a database-enforced read-only boundary before it can run.
flowchart LR
Q["Natural-language question"] --> V["Input validation"]
V --> S["Schema introspection"]
S --> E["Deterministic example selection"]
E --> L["Gemini or fake provider"]
L --> N["SQL normalization"]
N --> G{"SQLGlot guard"}
G -->|invalid| R["One bounded repair"]
R --> G
G -->|valid| D["Read-only SQLite adapter"]
D --> A["Grounded answer generation"]
A --> U["Streamlit results and trust trace"]
The provider, database, validation, and UI layers are independently testable. See the architecture guide for the component boundaries.
- SQL is parsed as an AST; keyword matching is not the security boundary.
- Only one read-only
SELECTorWITH ... SELECTstatement is accepted. - Referenced tables must be present in the introspected application schema.
- System catalogs, write operations, locking clauses, and data-modifying CTEs are rejected.
- A row limit is injected or clamped.
- SQLite is opened in read-only and query-only modes with a fail-closed authorizer.
- Credentials are runtime-only, masked in configuration representations, and excluded from Git and Docker build contexts.
- Logs use request IDs and safe categories without full prompts, rows, local paths, or provider secrets.
These controls reduce risk; they do not turn arbitrary LLM output into trusted code. Read SECURITY.md and docs/security.md before adapting the project to sensitive data.
fake is the default. It is deterministic, credential-free, and used by tests, CI,
screenshots, and the reproducible functional evaluation.
gemini uses the official Google Gen AI Python SDK behind the same asynchronous
provider protocol. It supports structured SQL generation, a bounded repair attempt,
grounded answer generation, request timeouts, bounded retries, and sanitized failures.
Prerequisites: Python 3.11 or 3.12.
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.lock
streamlit run app.pyOn Windows PowerShell, activate with .venv\Scripts\Activate.ps1. The app opens at
http://localhost:8501.
The synthetic database is generated on first start, so no setup command is required.
It is created only when missing, verified against the expected schema, and never
overwritten once valid. Run python scripts/initialize_demo_db.py to create it ahead
of time, or add --force to rebuild it.
Try:
How many customers are in the USA?Find the top 5 customers by total spendingWhich country generated the highest revenue?Count how many tracks exist in each genre
Copy .env.example to .env, then set:
LLM_PROVIDER=gemini
GEMINI_API_KEY=your_gemini_api_key_here
GEMINI_MODEL=gemini-3.6-flashGoogle retires model identifiers for new API projects, and a retired model returns a
404 rather than degrading gracefully. If Gemini mode reports that the model is
unavailable, list the models your key can call and set GEMINI_MODEL to a current one:
python scripts/list_gemini_models.pyNever commit .env. If Gemini mode is selected without a key, the UI returns a safe
configuration message and does not silently switch providers. Questions, generated
SQL, and bounded result context are sent to the selected provider; do not use Gemini
mode for data that your provider policy does not permit.
ruff check .
ruff format --check .
mypy src tests scripts app.py
pytest
python scripts/run_evaluation.py
python scripts/verify_release.pyAll automated tests and the default evaluation are offline. Gemini tests mock the SDK and never require a key or network access.
evaluation/cases.json covers filters, aggregations, joins, grouping, ordering, nested
queries, CTEs, window functions, unsupported questions, prompt injection, and
destructive requests. Run:
python scripts/run_evaluation.pyThe fake-provider result is a deterministic functional verification—not an LLM
accuracy score. Output is written to the ignored evaluation/results/ directory. See
docs/evaluation.md.
docker compose up --buildThe non-root container initializes the synthetic database during the image build and
starts in fake mode. Gemini can be enabled by injecting environment variables at
runtime; the image never contains .env. See docs/docker.md.
The application is deployed on Streamlit Community Cloud at the
live demo and runs on
any container platform that supports a configurable PORT. The demo database is
generated at startup, and Gemini credentials are supplied through Streamlit Secrets. See
docs/deployment.md.
The demo database is generated locally by scripts/initialize_demo_db.py. Its records
are fictional and authored for this portfolio project. It follows a compact
music-store analytical structure; it does not redistribute rows from the Chinook
sample database. The 25 reviewed question/SQL examples are stored in
data/examples.json.
- The lexical example selector is deterministic, not semantic vector retrieval.
- SQLite is the only implemented database adapter.
- Schema changes require regenerating or reviewing examples.
- The SQL policy is intentionally conservative and may reject valid advanced SQL.
- LLM output can still be wrong; users must review generated queries and results.
- The evaluation verifies known functional cases and does not establish production accuracy, latency, or safety guarantees.
- Add an isolated PostgreSQL adapter with a dedicated read-only role.
- Add schema-aware evaluation for live provider runs.
- Add optional query-cost controls and audit persistence.
- Evaluate multilingual questions, including Arabic, with a reviewed benchmark.
- Local development
- Architecture
- Security controls
- Evaluation methodology
- Docker
- Deployment
- Contributing
Code and the authored synthetic demo data are available under the MIT License.
Built by Abdallah Hosni.
