Spaces:
Sleeping
Sleeping
Islam Mamedov commited on
Commit ·
b100e4f
1
Parent(s): 9e3e6cb
Complete ablation table: query rewriting 0.91/0.69, dense confirmed champion
Browse files
README.md
CHANGED
|
@@ -8,7 +8,146 @@ app_file: app.py
|
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
-
# FastAPI Codebase Q&A
|
| 12 |
|
| 13 |
-
|
| 14 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# ⚡ FastAPI Codebase Q&A — an eval-driven RAG system
|
| 12 |
|
| 13 |
+
Ask questions about the [FastAPI](https://github.com/fastapi/fastapi)
|
| 14 |
+
codebase and get answers grounded in its actual source code, documentation,
|
| 15 |
+
and GitHub issues — with citations linking to the exact lines, and honest
|
| 16 |
+
refusals when the corpus doesn't contain the answer.
|
| 17 |
+
|
| 18 |
+
**Live demo:** https://huggingface.co/spaces/Pace200413/fastapi-codebase-qa
|
| 19 |
+
|
| 20 |
+
Every architectural decision in this project was made by measuring, not
|
| 21 |
+
guessing: a hand-labeled 42-question eval set gates every change, and the
|
| 22 |
+
ablation results below include the things that *didn't* work and why.
|
| 23 |
+
|
| 24 |
+
## Architecture
|
| 25 |
+
|
| 26 |
+
```
|
| 27 |
+
GitHub repo ──► Ingestion ──► Chunking ──► Indexing ──► Retrieval ──► Generation
|
| 28 |
+
code clone AST-aware ChromaDB dense LLM with
|
| 29 |
+
docs (en) API pull (tree-sitter) bge-small (winner of citations +
|
| 30 |
+
issues resumable md-aware + BM25 6 ablations) refusal rules
|
| 31 |
+
issue-aware
|
| 32 |
+
│ │
|
| 33 |
+
└────── Eval harness ◄─────┘
|
| 34 |
+
recall@5 · MRR · faithfulness ·
|
| 35 |
+
correctness · refusal accuracy
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
- **Corpus:** 46 source files, 161 English docs, 175 closed issues →
|
| 39 |
+
**1,352 chunks** (363 code, 814 doc, 175 issue)
|
| 40 |
+
- **Chunking:** tree-sitter AST parsing — one chunk per function/class,
|
| 41 |
+
classes >150 lines split per-method, every chunk carries a context header
|
| 42 |
+
(`# File: fastapi/routing.py | Symbol: APIRouter`) and exact line numbers
|
| 43 |
+
for citations
|
| 44 |
+
- **Stack:** Python, ChromaDB, sentence-transformers (`bge-small-en-v1.5`),
|
| 45 |
+
rank-bm25, Groq (LLM), Streamlit — total cost: $0
|
| 46 |
+
|
| 47 |
+
## Evaluation
|
| 48 |
+
|
| 49 |
+
42 hand-labeled questions across four categories: **api_usage** (16),
|
| 50 |
+
**behavior** (11), **location** ("where is X defined", 8), and
|
| 51 |
+
**unanswerable** (7 — questions FastAPI's corpus genuinely can't answer,
|
| 52 |
+
to measure refusal). Gold labels mark which file/symbol contains the
|
| 53 |
+
answer; retrieval is scored by recall@5 and MRR, generation by an
|
| 54 |
+
LLM-as-judge (faithfulness, correctness) plus string-matched refusals.
|
| 55 |
+
|
| 56 |
+
### Retrieval ablations
|
| 57 |
+
|
| 58 |
+
| Configuration | recall@5 | MRR | Verdict |
|
| 59 |
+
|---|---|---|---|
|
| 60 |
+
| **dense (bge-small)** | **0.91** | **0.71** | **winner — shipped** |
|
| 61 |
+
| hybrid (dense + BM25, RRF) | 0.86 | 0.70 | BM25 noise displaces gold chunks |
|
| 62 |
+
| hybrid, weighted RRF 2:1 | 0.86 | 0.71 | weighting doesn't recover recall |
|
| 63 |
+
| dense + bge-reranker-base | 0.91 | 0.63 | reranker *hurts* ranking |
|
| 64 |
+
| dense + bge-reranker-v2-m3 | 0.83 | 0.67 | newer reranker also hurts |
|
| 65 |
+
| hybrid + bge-reranker-v2-m3 | 0.86 | 0.68 | — |
|
| 66 |
+
| dense + LLM query rewriting | 0.91 | 0.69 | no gain; noisy expansions add drift |
|
| 67 |
+
|
| 68 |
+
**Why hybrid lost:** diagnostic runs showed dense@20 = hybrid@20 = 0.97 —
|
| 69 |
+
BM25 contributed zero unique gold candidates on this corpus, while its
|
| 70 |
+
common-token matches (issue chunks full of "request", "body", "json")
|
| 71 |
+
crowded correct chunks out of the top 5.
|
| 72 |
+
|
| 73 |
+
**Why the rerankers lost:** both generations of cross-encoder demoted raw
|
| 74 |
+
code chunks on "where is X defined" questions. A code chunk *is* the
|
| 75 |
+
answer without *discussing* the answer — and rerankers are trained to
|
| 76 |
+
score passages that discuss. dense@20 recall of 0.97 vs reranked@5 recall
|
| 77 |
+
of 0.83–0.86 shows they were given the right candidates and ranked them
|
| 78 |
+
worse.
|
| 79 |
+
|
| 80 |
+
### Generation (dense, llama-4-scout via Groq)
|
| 81 |
+
|
| 82 |
+
| Metric | Score |
|
| 83 |
+
|---|---|
|
| 84 |
+
| Faithfulness (LLM judge) | 0.89 |
|
| 85 |
+
| Correctness (LLM judge) | 0.91 |
|
| 86 |
+
| Refusal accuracy (7 unanswerable questions) | 7/7 |
|
| 87 |
+
|
| 88 |
+
Refusal was the most fragile metric: it swung 6/7 → 0/7 → 7/7 across three
|
| 89 |
+
system-prompt variants. The fix was naming the failure mode in the prompt
|
| 90 |
+
("the context chunks are search results: they may be loosely RELATED to
|
| 91 |
+
the question without actually answering it"). Without the eval harness
|
| 92 |
+
this regression would have shipped silently.
|
| 93 |
+
|
| 94 |
+
A score-threshold refusal gate was also tested and **rejected**: top-1
|
| 95 |
+
similarity scores for answerable (0.74–0.89) and unanswerable (0.72–0.79)
|
| 96 |
+
questions overlap heavily — semantic similarity can't distinguish "related
|
| 97 |
+
to FastAPI" from "answerable from FastAPI's corpus".
|
| 98 |
+
|
| 99 |
+
## Failure analysis (what still breaks)
|
| 100 |
+
|
| 101 |
+
1. **Common-token queries.** "Where are the Query, Path, and Body parameter
|
| 102 |
+
functions defined?" (`fastapi/param_functions.py`) misses even at k=20
|
| 103 |
+
in every configuration — all its query tokens are ubiquitous. LLM query
|
| 104 |
+
rewriting that injects likely identifiers is the targeted fix.
|
| 105 |
+
2. **Docs without code.** FastAPI's docs embed examples via
|
| 106 |
+
`{* ../../docs_src/... *}` include-directives; the referenced snippets
|
| 107 |
+
aren't inlined into chunks, so some answers describe code they can't
|
| 108 |
+
show. Resolving the includes at chunking time is the top corpus
|
| 109 |
+
improvement.
|
| 110 |
+
3. **Judge strictness.** Spot-checking answers flagged `[incorrect]` showed
|
| 111 |
+
at least one (JWT auth) is actually correct — the LLM judge penalizes
|
| 112 |
+
answers referencing code the truncated context implies but doesn't
|
| 113 |
+
show. Judge scores are treated as directional, not absolute.
|
| 114 |
+
|
| 115 |
+
## Engineering notes
|
| 116 |
+
|
| 117 |
+
- **Idempotent, resumable ingestion** — issues already fetched are skipped,
|
| 118 |
+
so network failures mid-run just need a re-run
|
| 119 |
+
- **Everything cached** — LLM answers, judgments, and query rewrites are
|
| 120 |
+
cached to disk keyed by (model, prompt), making eval re-runs free on
|
| 121 |
+
Groq's rate-limited free tier
|
| 122 |
+
- **Rate-limit resilience** — retry with backoff; eval survived TPM/TPD
|
| 123 |
+
limits and a mid-run model deprecation (Groq retired llama-3.3 mid-sprint)
|
| 124 |
+
- **Self-building deploy** — the HF Space rebuilds the vector index from
|
| 125 |
+
committed `chunks.jsonl` on first boot; no binary artifacts in git
|
| 126 |
+
- **Unit-tested chunkers** and an eval suite that doubles as a regression
|
| 127 |
+
gate
|
| 128 |
+
|
| 129 |
+
## Run it yourself
|
| 130 |
+
|
| 131 |
+
```bash
|
| 132 |
+
git clone https://github.com/Pace200413/codebase-rag && cd codebase-rag
|
| 133 |
+
python -m venv .venv && source .venv/bin/activate
|
| 134 |
+
pip install -r requirements.txt PyGithub tree-sitter tree-sitter-python pytest
|
| 135 |
+
echo "GROQ_API_KEY=gsk_..." > .env # free key: console.groq.com
|
| 136 |
+
|
| 137 |
+
python src/ingest.py --repo fastapi/fastapi # needs GITHUB_TOKEN for issues
|
| 138 |
+
python src/chunk.py
|
| 139 |
+
python src/index.py
|
| 140 |
+
python src/ask.py "How do I return a custom status code?"
|
| 141 |
+
python src/eval.py --mode dense --answers # reproduce the numbers above
|
| 142 |
+
streamlit run app.py
|
| 143 |
+
```
|
| 144 |
+
|
| 145 |
+
## What I'd build next
|
| 146 |
+
|
| 147 |
+
- Resolve `docs_src` include-directives into doc chunks (highest-impact
|
| 148 |
+
corpus fix)
|
| 149 |
+
- Make ingestion repo-agnostic (`--repo any/repo`) — turn the project into
|
| 150 |
+
a tool
|
| 151 |
+
- Embedding-model ablation (bge-small vs bge-base vs code-specific models)
|
| 152 |
+
- Ingest GitHub Discussions, where most FastAPI Q&A actually lives
|
| 153 |
+
- CI job that fails the build if recall@5 drops below baseline
|