"""Build the `fewshot_qsql` Chroma collection from BIRD train. Source: `data/bird_train.parquet`, downloaded from ``huggingface.co/datasets/xu3kev/BIRD-SQL-data-train`` (9 428 rows over 69 dbs, none of which overlap with BIRD Mini-Dev's 11 dev dbs — verified by construction). Usage:: uv run python scripts/build_fewshot_index.py uv run python scripts/build_fewshot_index.py --limit 1000 # sanity slice uv run python scripts/build_fewshot_index.py --persist chroma_data/ The script embeds each question via Mistral `mistral-embed` and upserts into the `fewshot_qsql` collection. Embeddings are cached, so re-running is free after the first pass. The schema chunks live in the same Chroma client but in a different collection, so this script is safe to run on top of an existing index. """ from __future__ import annotations import argparse import sys import time from collections.abc import Sequence from pathlib import Path import chromadb import pyarrow.parquet as pq from nl_sql.config import get_settings from nl_sql.eval.dataset import load_bird_mini_dev from nl_sql.llm.cache import CachingEmbeddingProvider from nl_sql.llm.providers.mistral import MistralProvider from nl_sql.schema_index.indexer import FewShotExample, SchemaIndex def _load_train_examples( parquet_path: Path, *, limit: int | None = None, ) -> list[FewShotExample]: table = pq.read_table(parquet_path) df = table.to_pandas() if limit: df = df.head(limit) examples: list[FewShotExample] = [] for idx, row in df.iterrows(): examples.append( FewShotExample( example_id=f"bird_train_{idx}", db_id=str(row["db_id"]), question=str(row["question"]), sql=str(row["SQL"]), intent="", ) ) return examples def _assert_no_dev_leakage( examples: Sequence[FewShotExample], *, bird_root: Path, ) -> None: """Hard guard against leakage even though train/dev partition by db_id.""" dev = load_bird_mini_dev(bird_root) dev_questions = {e.question.strip().lower() for e in dev} overlap = [e for e in examples if e.question.strip().lower() in dev_questions] if overlap: msg = ( f"FATAL: {len(overlap)} fewshot examples overlap with BIRD Mini-Dev. " "First: " + overlap[0].question[:120] ) raise SystemExit(msg) def main(argv: list[str] | None = None) -> int: parser = argparse.ArgumentParser() parser.add_argument( "--parquet", type=Path, default=Path("data/bird_train.parquet"), help="path to BIRD train parquet (default: data/bird_train.parquet)", ) parser.add_argument( "--bird-root", type=Path, default=Path("data/bird_mini_dev/MINIDEV"), help="BIRD Mini-Dev root for the leakage check", ) parser.add_argument( "--persist", type=Path, default=Path("chroma_data"), help="Chroma persist dir (must match build_index.py)", ) parser.add_argument( "--limit", type=int, default=None, help="cap on rows (default: full 9 428)", ) parser.add_argument( "--embed-batch", type=int, default=16, help="batch size for embedding requests (default: 16)", ) args = parser.parse_args(argv) settings = get_settings() if not settings.mistral_api_key: print("[error] MISTRAL_API_KEY not set in .env", file=sys.stderr) return 2 if not args.parquet.is_file(): print( f"[error] parquet not found: {args.parquet}. Download from " "https://huggingface.co/datasets/xu3kev/BIRD-SQL-data-train", file=sys.stderr, ) return 3 examples = _load_train_examples(args.parquet, limit=args.limit) print(f"[info] loaded {len(examples)} fewshot examples from {args.parquet}") _assert_no_dev_leakage(examples, bird_root=args.bird_root) print("[info] leakage check passed (zero dev-question overlap)") embedder: CachingEmbeddingProvider = CachingEmbeddingProvider( MistralProvider( api_key=settings.mistral_api_key, gen_model=settings.mistral_gen_model, embed_model=settings.mistral_embed_model, base_url=settings.mistral_base_url, ), cache_dir=settings.llm_cache_dir, size_limit_gb=settings.llm_cache_size_limit_gb, ) args.persist.mkdir(parents=True, exist_ok=True) client = chromadb.PersistentClient(path=str(args.persist)) index = SchemaIndex( persist_dir=args.persist, embedder=embedder, client=client, embed_batch=args.embed_batch, ) started = time.perf_counter() indexed = index.index_fewshots(examples) elapsed = time.perf_counter() - started print(f"[done] indexed {indexed} fewshot examples in {elapsed:.1f}s") print(f"[info] persist dir: {args.persist}/") return 0 if __name__ == "__main__": sys.exit(main())