Internal QA harness for the schema-retrieval pipeline. Runs on every commit, catches regressions before they ship.
For each natural-language question in the suite:
- Run
TfIdfRetriever.rank(question, top_k=K)against the test schema. - Walk the FK graph one hop in each direction (
expand_via_fks). - Check whether the must-retrieve tables (hand-labeled per question) are present in the output.
A question passes if every must-retrieve table is in the final post-FK-expansion set. Partial coverage still contributes to recall@K metrics.
Retrieval is the headline differentiator. If the retriever misses the relevant tables, no amount of LLM cleverness will produce correct SQL — so retrieval quality dominates end-to-end accuracy. Isolating it from the LLM also removes nondeterminism: the eval is pure-Python, free, and runs in <100 ms for the full suite.
The full end-to-end eval (LLM generation + execution comparison against reference SQL) is intentionally not here. It requires a live Postgres + an API key, and the signal is muddied by LLM variance. It's planned for a separate eval/end_to_end.py once we add Docker fixtures.
From the repo root:
.venv/bin/python -m eval.retrievalOptions:
| Flag | Default | Meaning |
|---|---|---|
--top-k |
10 | Top-K used by the TF-IDF retriever |
--max-tables |
20 | Cap after FK expansion |
--quiet |
off | Only print the summary table |
--fail-under <f> |
none | Exit non-zero if pass rate < f (for CI gating) |
--fixture <path> |
fixtures/shop.schema.json |
Alternate schema |
Wire it into CI as a regression gate:
python -m eval.retrieval --quiet --fail-under 0.85- Schema: hand-crafted 14-table e-commerce shop (
fixtures/shop.sqlis the DDL;fixtures/shop.schema.jsonis the pre-introspected fixture the eval consumes). - Questions: 25 hand-written, in
questions/shop.py, spanning easy → hard difficulty, single-table → 4-table joins, including window functions and time-range buckets.
Open questions/shop.py and append a Question(...). Required fields:
text— what the user typesmust_retrieve— tuple of tables the retriever MUST surface (the contract)reference_sql— a correct answer (used by the future end-to-end eval; not scored here)difficulty—"easy"/"medium"/"hard"tags— free-form for failure-mode analysis
If your question targets a domain not yet in the schema, add the tables to fixtures/shop.sql AND fixtures/shop.schema.json together, then re-run the eval.
To benchmark a different schema:
- Build the DDL → save to
fixtures/<name>.sql. - Either spin up a Postgres + run
promptquery.schema.introspect()and dumpto_dict()as JSON, or hand-craft the JSON. Save tofixtures/<name>.schema.json. - Author a
questions/<name>.pyexporting aQUESTIONSlist. - Add a CLI flag or a new module under
eval/for it.
- End-to-end suite — Docker Postgres + Pagila/shop, runs the full pipeline, scores via result-set equality. Requires API key.
- Scaled retrieval suite — inject N=100/500/1000 noise tables into the fixture to test retrieval-at-scale (the headline pitch). Pass criteria becomes "must-retrieve still in top 20 with 1000-table haystack."
- Cross-tool comparison — adapters that run Vanna, WrenAI, LangChain SQLDatabaseChain, and LlamaIndex against the same suite. The public benchmark post writes itself.