Nine agent architectures, two models, 41 verified questions, one 48 GB GPU — and a scoring harness built because none of the standard evaluation methods fit.
Kayla Queenazima · @kaylaque · github.com/matinnuhamunada/chatbgc_agentic_rag
Typical request, in the corridor:
"Do we already have anything on X?"
Answering it means writing SQL across 65 tables. The bioinformatician stops, writes the query, and returns a few hours later.
The biology question is simple: "which gene clusters are in these genomes, and what do they encode?" Everything hard comes from the constraints around it.
| CONSTRAINT | WHY IT'S TRUE HERE | THE DESIGN DECISION IT FORCES | SAME FOR YOU IF |
|---|---|---|---|
| Data can't leave | Unpublished genomes. A hosted API is not an option. | Self-hosted inference. Everything runs on one box. | Health records, financial data, NDA work |
| Low concurrency | A research group, or one person. Not a public service. | Optimise single-request latency, not throughput. No batching to hide prefill behind. | Internal tools, CLIs, notebooks |
| The model has never seen this schema | 65 tables. regions and bgc_types join only through a junction table. |
Domain knowledge must be retrieved, not assumed — look the schema up before writing SQL (that’s the RAG part). | Any internal schema or API |
| Wrong answers look right | A wrong-but-valid join returns rows. No error, no warning. | Correctness cannot be judged by "did it run". → the whole evaluation stack. | Anything generating queries or code |
--arch swaps the topology over it and --route
picks a model tier per call. Both are benchmark axes, not user-facing settings.Sebastian Raschka's four-way map of LLM evaluation. Identifying which category this task falls into determined the design.
| APPROACH | WHAT IT ACTUALLY SCORES | ITS FAILURE MODE | FIT FOR A TEXT-TO-SQL AGENT |
|---|---|---|---|
| Multiple choice MMLU-style |
Picking from options | Tests recall, not use | NO There are no options — the output is a query |
| Verifiers deterministic check |
Free output, checked by code | Needs a checkable domain, and ignores how you got there | YES SQL returns the right rows or it doesn't. The backbone. |
| Leaderboards Elo / Bradley–Terry |
Which one people prefer | Preference, not correctness | NO No user pool, and preference isn't the question |
| LLM-as-a-judge rubric scored |
Another model grades it | Only as good as the judge, and not reproducible | ONE LAYER Phrasing only, and kept optional |
| LAYER | CHECK | A SCORE LOOKS LIKE | COST | WHAT IT CATCHES |
|---|---|---|---|---|
| 1 Does it run | EXPLAIN + deny-list | 1 or 0 | FREE | Syntax errors, writes, unknown columns |
| 2 Right rows | gold vs predicted result sets | 1 or 0 | FREE | Wrong joins, wrong filters |
| 3 Right nothing | declared negative cases only | 1 or 0 | FREE | Rows invented for entities that don't exist |
| 4 Right method | schema prefix · junction join · SELECT-only | 0.75 = 3 of 4 | FREE | Right rows by luck — the trajectory layer the four standard methods don't have |
| 5 Answer matches rows | exact numbers + named entities | 0.60 | FREE | Correct query, invented summary |
| 6 Answer is sensible | LLM judge, rubric + reference | 0.83 | 1 CALL | Phrasing the deterministic checks can't score |
| 7 Nothing crashed | no pipeline error | 1 or 0 | FREE | Timeouts, rate limits — kept off the accuracy axis |
| ROLE | USED HERE | WHY THIS ONE |
|---|---|---|
| Serving | vLLM | Continuous batching, strong prefix-cache support |
| Local model | Qwen3.6-27B-FP8 | FP8 fits the card with room for context |
| Comparison model | deepseek-v4-flash-free | Hosted free tier — the realistic alternative |
| SQL specialist | XiYanSQL-7B-AWQ | Local, 8k context — a purpose-built text-to-SQL model |
| Orchestration | LangGraph | Typed state, swappable topologies — the --arch axis |
| Retrieval | LlamaIndex + ChromaDB | BM25 + vector fused; keyword alone misses paraphrase |
| Reranker | BAAI/bge-reranker-base | Re-scores the shortlist by reading query and doc together |
| Embeddings | nomic-embed-text-v1.5 | Runs alongside the 27B on the same card |
| Warehouse | DuckDB | Single-file, analytical, no server |
| Tracing / eval | Langfuse · promptfoo + runner | One span per phase, one per model call |
| MODEL | EXECUTION ACCURACY | ANSWER QUALITY | TOK / RUN | UNSCORED |
|---|---|---|---|---|
| Qwen3.6-27B-FP8 local · 27B generalist |
0.767 |
0.695 | 10.8k | 6 / 369 |
| deepseek-v4-flash-free hosted · free tier |
0.645 |
0.707 | 29.1k | 4 / 369 |
| XiYanSQL-7B-AWQ local · 7B SQL specialist |
0.612 |
0.398 | 11.8k | 54 / 369 |
| MODEL | EXEC | P50 LATENCY |
|---|---|---|
| Qwen 27B | 0.796 | 22.6 s |
| XiYanSQL 7B | 0.758 | 3.0 s |
| deepseek | 0.598 | 33.2 s |
t1 fails 41 of 41: it injects the whole schema, no retrieval| STRUCTURE | DEEPSEEK | QWEN | XIYAN |
|---|---|---|---|
| simple | 0.673 | 0.827 | 0.683 |
| join | 0.577 | 0.543 | 0.213 |
| aggregation | 0.487 | 0.486 | 0.400 |
And it can't stand still. The product changes, the schema changes, the questions change — so the benchmark moves too, or it starts measuring the past.
Thanks to Matin Nuhamunada and the BEAMS Lab, Faculty of Biology, UGM.
I organise PyLadies Yogyakarta — find me afterwards if you'd like to get involved.
SELECT r.*, m.*, bgc.* FROM antismash.regions r JOIN antismash.modules m ON r.region_id = m.region_id JOIN antismash.rel_regions_types rt ON r.region_id = rt.region_id JOIN antismash.bgc_types bgc ON rt.bgc_type_id = bgc.bgc_type_id WHERE bgc.term ILIKE '%PKS%' AND m.trans_at = true;
regions and bgc_types have no direct foreign key. Go through the junction table or get nothing.antismash. and the query fails outright — which is the good case.