Repository navigation
feat(labs): production-harden adaptive RAG router #77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| name: RAG router eval gate | ||
|
|
||
| # Blocking check: routing accuracy of the trained classifier on the held-out eval | ||
| # set. Deterministic (classifier only, no LLM), so the gate is stable and free. | ||
| # Runs only when the router, its training data, the corpus, or the eval set change. | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - "labs/33-graph-rag-from-scratch/corpus/**" | ||
| - "labs/34-rag-pattern-head-to-head/eval_set.jsonl" | ||
| - "labs/36-training-the-router/router_trainset.jsonl" | ||
| - "labs/37-rag-eval-gates/eval_gate.py" | ||
| workflow_dispatch: {} | ||
|
|
||
| jobs: | ||
| routing-gate: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 15 | ||
| steps: | ||
| - uses: actions/checkout@v4 | ||
|
|
||
| - uses: actions/setup-python@v5 | ||
| with: | ||
| python-version: "3.11" | ||
| cache: pip | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| python -m pip install --upgrade pip | ||
| pip install "scikit-learn>=1.4" "sentence-transformers>=5.0,<6.0" numpy | ||
|
|
||
| - name: Self-test the gate logic (fast, no model) | ||
| run: python labs/37-rag-eval-gates/eval_gate.py --self-test | ||
|
|
||
| - name: Routing-accuracy gate | ||
| run: python labs/37-rag-eval-gates/eval_gate.py --route-min 0.85 | ||
|
|
||
| # Note: answer faithfulness via the LLM judge is intentionally NOT run here. It is | ||
| # noisy and costs API calls; run it on a nightly schedule (schedule: cron) as an | ||
| # informational job that posts a trend, and alert on regression rather than blocking | ||
| # the build. See labs/37-rag-eval-gates/ for the judge. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| # Lab 36: Training and hardening the router | ||
|
|
||
| > 🔴 Advanced · ⏱ ~100–130 min · 📚 Builds on Lab 35 | ||
|
|
||
| ## 🎯 Goal | ||
|
|
||
| Take Lab 35's prompt-based router and make it production-shaped. Replace the per-query LLM classification call with a **trained classifier** (query embeddings + logistic regression on a labeled set), add a **confidence gate** from `predict_proba`, and route low-confidence queries to an **agentic fallback** that tries the top-2 candidate strategies and verifies before answering. | ||
|
|
||
| By the end you should be able to: | ||
|
|
||
| - Train and cross-validate a query-type classifier on embeddings, and read the reliable accuracy on a small set. | ||
| - Compare a trained router to a prompt-based one on both accuracy and per-query cost/latency. | ||
| - Use classifier confidence to gate an expensive fallback so it only runs on the uncertain tail. | ||
| - Reason about the failure mode that matters most here — calibration, not raw accuracy. | ||
|
|
||
| ## 📋 Prerequisites | ||
|
|
||
| **Complete first:** | ||
|
|
||
| - 🧪 [Lab 35: Adaptive RAG router](../35-adaptive-rag-router/) — this lab trains the classifier that Lab 35 prompted for, and reuses its dispatch targets. | ||
| - 🧪 [Lab 34: Head-to-head evaluation](../34-rag-pattern-head-to-head/) — supplies the held-out eval set used to compare routers. | ||
|
|
||
| **Assumed background:** logistic regression, train/test splitting, cross-validation, class imbalance, and calibration. This is a applied-ML lab; the RAG parts are scaffolding around a classification problem. | ||
|
|
||
| **Setup:** Python 3.11+ with the repo environment, `scikit-learn>=1.4`, `sentence-transformers`, `numpy`. Your LLM provider key (for the prompt-router comparison and the fallback's verifier). | ||
|
|
||
| ## 🛠 Tools and versions | ||
|
|
||
| | Library | Version | Notes | | ||
| |---|---|---| | ||
| | `scikit-learn` | `>=1.4` | `LogisticRegression`, `StratifiedKFold` | | ||
| | `sentence-transformers` | `>=5.0,<6.0` | Query embeddings (features) | | ||
| | `numpy` | `>=1.26` | Arrays | | ||
| | `openai` *or* `anthropic` | from prior labs | Prompt-router baseline + fallback verifier | | ||
|
|
||
| ## What you'll build | ||
|
|
||
| A labeled query set (`router_trainset.jsonl`, 81 queries × 5 routes) → embeddings → a `LogisticRegression` classifier with cross-validated accuracy; `route_with_confidence` (returns route + confidence + top-2); a trained-vs-prompt comparison on the eval set; and `adaptive_rag_v2` — a confidence gate that dispatches directly when confident and calls `agentic_fallback` (try top-2 strategies, self-check, prefer a clean abstention over a failed guess) when not. | ||
|
|
||
| ## The data and a leakage warning | ||
|
|
||
| `router_trainset.jsonl` ships with the lab: 81 queries grounded in Lab 33's corpus, labeled with one of `parametric / global / multihop / off_corpus_risk / specific`. **It's small.** Several queries are near-paraphrases, so a single random split can leak a paraphrase across the train/test boundary and flatter the score. The lab reports stratified cross-validation for that reason — and a real deployment needs far more data, refreshed as the query distribution drifts. | ||
|
|
||
| ## Steps | ||
|
|
||
| 1. **Setup** (0). | ||
| 2. **Labeled set + embeddings** (1): features for the classifier. | ||
| 3. **Train** (2): stratified CV is the number to trust; in-sample is optimistic. | ||
| 4. **Confidence** (3): `predict_proba` max as the gate input. | ||
| 5. **Trained vs prompt** (4): accuracy and cost. | ||
| 6. **Dispatch targets** (5): the strategies the router selects among. | ||
| 7. **Confidence gate + fallback** (6): the new control flow. | ||
| 8. **When the fallback helps** (7): the low-confidence tail. | ||
|
|
||
| ## Design choices and tradeoffs | ||
|
|
||
| - **Why logistic regression.** Small labeled set, an interpretable linear boundary, and — the load-bearing reason — `predict_proba` gives the confidence the gate needs. A linear SVM or small MLP are alternatives; plain SVM lacks calibrated probabilities without extra work. | ||
| - **Trained vs prompt classifier.** The trained router pays a one-time labeling + training cost, then classifies for near-zero per-query cost and latency, and you can monitor its confidence distribution for drift. The prompt router needs no labels but bills an LLM call every query and is harder to watch. Neither is strictly better — it's a data-availability and ops decision. | ||
| - **Why gate the fallback on confidence.** The multi-strategy fallback costs several calls. Running it on every query would erase the trained router's cost advantage. The gate spends that budget only where the model is unsure. | ||
|
|
||
| ## Common gotchas | ||
|
|
||
| - **Calibration, not accuracy, is the real risk.** A confidently wrong route skips the gate entirely. Monitor confidence *against correctness* (a reliability curve), not just top-line accuracy — an over-confident classifier defeats the fallback. | ||
| - **Tiny-data overfitting.** With 81 examples, a single split is noisy. Trust cross-validation, and resist tuning hyperparameters against the eval set (that leaks the eval set into model selection). | ||
| - **Embedding-model coupling.** The classifier's features come from a specific embedder. Swap the embedder and you must retrain. Pin it, and version the trainset with it. | ||
| - **Distribution shift.** The trainset queries are clean prototypes; real user queries are messier. Expect deployed accuracy below your CV number and plan to collect real queries for a second training round. | ||
|
|
||
| ## 🧮 Going deeper | ||
|
|
||
| - 📖 [SOTA RAG patterns](../../concepts/rag/sota-rag-patterns.md) — Adaptive RAG in context. | ||
| - 🧮 [Retrieval and ranking metrics](../../math-foundations/14-retrieval-ranking-metrics.md) — for the downstream answer evaluation. | ||
|
|
||
| ## What comes next | ||
|
|
||
| - 🧪 [Lab 37: Evaluation gates for RAG](../37-rag-eval-gates/) — wrap this router in a CI gate so routing regressions fail the build, and swap Lab 34's token-presence scoring for an LLM judge. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.