# Corrective RAG (CRAG) Pipeline Documentation This document outlines the architecture, execution workflow, node contracts, and core configuration settings for the Corrective RAG (`CRAG`) engine in Study Buddy. --- ## 1. Pipeline Graph
crag_graph
(Click graph to open full resolution)
View raw Mermaid specification ```mermaid graph TD; __start__([__start__]) triage(triage) reply_chitchat(reply_chitchat) reply_refusal(reply_refusal) init_state(init_state) router(router) retrieve(retrieve) normalize(normalize) merge_pool(merge_pool) rerank(rerank) evaluator(evaluator) retry_check(retry_check) generate(generate) fallback(fallback) __end__([__end__]) __start__ --> triage; triage -.->|chitchat| reply_chitchat; triage -.->|unsafe| reply_refusal; triage -.->|study query| init_state; reply_chitchat --> __end__; reply_refusal --> __end__; init_state --> router; router --> retrieve; retrieve --> normalize; normalize --> merge_pool; merge_pool --> rerank; rerank -.->|top_score >= 0.8| generate; rerank -.->|0.2 < score < 0.8| evaluator; rerank -.->|top_score <= 0.2| retry_check; evaluator -.->|correct| generate; evaluator -.->|ambiguous / incorrect| retry_check; retry_check -.->|retries <= 2| router; retry_check -.->|retries > 2| fallback; generate --> __end__; fallback --> __end__; ```
--- ## 2. End-to-End Workflow The pipeline processes user queries through three distinct pathways: 1. **Zero-Retrieval Fast Paths (Bypass)**: - Evaluated at `triage` via Tier 0 (regex/rules, `<1ms`) and Tier 1 (fast LLM classification, line-based output). - If `chitchat`: routes directly to `reply_chitchat` with localized canned greetings/thanks and exits. - If `unsafe`: routes directly to `reply_refusal` with policy refusals (gibberish, NSFW, politics, religion) and exits. 2. **Standard RAG Pipeline (Happy Path)**: - Initialized at `init_state` (passes `intent` and sub-queries, sets `retries=0`). - `router` selects search sources and formulates query plans (defaults to curated Supabase materials on pass 0). - `retrieve` executes external tool queries (Supabase vector store, Wikipedia, ArXiv, Web). - `normalize` cleans text into structured `Chunk` objects and computes SHA-256 content IDs. - `merge_pool` deduplicates passages and merges them into the cumulative pool. - `rerank` scores chunks using a cross-encoder (`jina-reranker-v2-base-multilingual`), pinning top Supabase passages. - If top reranker score $\ge 0.8$ (`HIGH_THRESHOLD`), `score_gate` short-circuits directly to `generate` (skipping the evaluator LLM call). - `generate` drafts an educational answer strictly cited with chunk numbers `[n]`. 3. **Evaluation & Corrective Feedback Loop**: - If rerank score is between $0.2$ and $0.8$, `evaluator` inspects relevance and coverage (`correct`, `ambiguous`, `incorrect`). - If evaluated as `correct`, proceeds to `generate`. - If `ambiguous` or `incorrect`, `retry_check` tracks attempt count. - If `retries <= MAX_RETRIES (2)`, loops back to `router` to re-query (`bad_query` rewrites query targeting `missing`; sources remain fixed with no tool escalation). - If retries are exhausted, routes to `fallback` (synthesizes hybrid response or general-knowledge answer with `low_confidence=True`). --- ## 3. Node Specifications & Outputs ### 1. `triage` - **Purpose**: Classifies user input into intent (`chitchat`, `unsafe`, `simple`, `complex`) and decomposes complex queries into 1–3 sub-queries. Runs Tier 0 regex heuristics first, falling back to a structured line-based LLM call. - **Output fields**: - `intent`: `"chitchat" | "unsafe" | "simple" | "complex"` - `sub_questions`: `list[str]` (non-empty only for complex) - `reply`: canned string for chitchat/unsafe, otherwise `None` ### 2. `reply_chitchat` - **Purpose**: Emits immediate conversational response in English or Arabic without touching vector stores or tools. - **Output fields**: - `answer`: localized greeting/thanks message - `citations`: `[]` - `low_confidence`: `False` ### 3. `reply_refusal` - **Purpose**: Emits standardized safety refusal for out-of-scope queries (gibberish, adult, politics, religion). - **Output fields**: - `answer`: standardized refusal string - `citations`: `[]` - `low_confidence`: `False` ### 4. `init_state` - **Purpose**: Initializes graph state variables for legitimate study queries. - **Output fields**: - `original_q`: validated question string - `sources_to_use`: `["supabase"]` (default first pass) - `retries`: `0`, `pool`: `[]`, `ranked`: `[]`, `verdict`: `None` ### 5. `router` - **Purpose**: Selects target retrieval sources and constructs retrieval queries. - **Pass 0**: If triage `intent == "complex"`, queries **all tools concurrently** (`supabase`, `arxiv`, `wikipedia`, `web`) with zero LLM delay. If `intent == "simple"`, runs **automatic tool calling** via LangChain agentic tool selection. - **Retries**: Keeps existing sources fixed (no adding tools / no tool escalation); strictly performs **requerying** by rewriting queries targeting evaluator `missing` feedback. - **Output fields**: - `sources_to_use`: list of sources (`["supabase", "arxiv", "wikipedia", "web"]`) - `search_query`: optimized search keywords - `tool_queries`: mapping of source to specific search query ### 6. `retrieve` - **Purpose**: Calls registered tool functions concurrently across designated sources. - **Output fields**: - `raw_results`: raw documents/payloads returned by tool APIs ### 7. `normalize` - **Purpose**: Parses varied tool response formats, splits text into target chunk lengths, and computes deterministic content hashes. - **Output fields**: - `new_chunks`: list of validated `Chunk` instances - `tried_sources`: updated cumulative list of queried sources ### 8. `merge_pool` - **Purpose**: Accumulates incoming chunks into the existing pool, preserves scores, and ensures top-tier document slots are retained without unbounded growth. - **Output fields**: - `pool`: deduped, bounded chunk list (capped at `POOL_MAX`) ### 9. `rerank` - **Purpose**: Scores chunks against `original_q` using a cross-encoder model. Retains top Supabase chunks to preserve course material grounding. - **Output fields**: - `ranked`: top candidate chunks sorted by descending score (length `FINAL_CONTEXT_N`) ### 10. `evaluator` - **Purpose**: Fast LLM judge checking context relevance and completeness against the question. Emits line-based key-values (`VERDICT`, `CAUSE`, `MISSING`). The only cause for retrying is `bad_query` (requerying). - **Output fields**: - `verdict`: `"correct" | "ambiguous" | "incorrect"` - `likely_cause`: `"none" | "bad_query"` - `missing`: description of missing factual concepts ### 11. `retry_check` - **Purpose**: Increments retry counter and records diagnostic logs. - **Output fields**: - `retries`: updated integer counter (`retries + 1`) ### 12. `generate` - **Purpose**: Synthesizes the final educational answer strictly grounded in ranked context chunks cleanly without bracketed numbers (`[1]`, `[2]`). - **Output fields**: - `answer`: synthesized text - `citations`: list of cited source titles/URLs - `low_confidence`: `False` ### 13. `fallback` - **Purpose**: Failsafe generator triggered when retries are exhausted or retrieval fails. Generates hybrid context-assisted or knowledge-based explanations. - **Output fields**: - `answer`: fallback educational text - `citations`: list of citations (if partial context was used) - `low_confidence`: `True` --- ## 4. Conditional Edge Gates - **`triage_gate`**: - `intent == "chitchat"` $\rightarrow$ `reply_chitchat` - `intent == "unsafe"` $\rightarrow$ `reply_refusal` - default $\rightarrow$ `init_state` - **`score_gate`**: - **For Simple Queries (`intent == "simple"`)**: - `top_score > SIMPLE_THRESHOLD (0.35)` $\rightarrow$ `generate` (bypasses evaluator entirely to eliminate token waste). - `top_score <= SIMPLE_THRESHOLD (0.35)` $\rightarrow$ `retry_check` $\rightarrow$ retries with **all tools** (`supabase`, `arxiv`, `wikipedia`, `web`) using keyword-style search queries. - **For Complex Queries (`intent == "complex"`)**: - `top_score >= HIGH_THRESHOLD (0.8)` $\rightarrow$ `generate` (high confidence shortcut) - `top_score <= LOW_THRESHOLD (0.2)` $\rightarrow$ `retry_check` (immediate retry) - `LOW_THRESHOLD < top_score < HIGH_THRESHOLD` $\rightarrow$ `evaluator` (ambiguous zone inspection) - **`evaluator_gate`**: - `verdict == "correct"` $\rightarrow$ `generate` - `verdict in {"ambiguous", "incorrect"}` $\rightarrow$ `retry_check` - **`retry_check_gate`**: - `retries <= MAX_RETRIES (2)` $\rightarrow$ `router` (retry requerying missing facts) - `retries > MAX_RETRIES (2)` $\rightarrow$ `fallback` (exhausted retries) --- ## 5. Key Configuration & Constants All runtime thresholds and parameters are defined in `src/rag/constants.py` and validated via Pydantic in `src/rag/config.py`: | Parameter | Default | Purpose / Behavior | | :--- | :--- | :--- | | `HIGH_THRESHOLD` | `0.8` | Quality cutoff. If top rerank score $\ge 0.8$, skips evaluator and answers immediately. | | `LOW_THRESHOLD` | `0.2` | Minimum viability floor for complex queries. If top score $\le 0.2$, skips evaluator and triggers corrective search. | | `SIMPLE_THRESHOLD` | `0.35` | Quality cutoff floor for simple queries. If top score $> 0.35$, bypasses evaluator and generates; otherwise retries. | | `KEEP_THRESHOLD` | `0.5` | Threshold for carrying over candidate chunks into subsequent retry pools. | | `MAX_RETRIES` | `2` | Maximum corrective search iterations before falling back to graceful synthesis. | | `TOP_K` | `5` | Base number of chunks retrieved per tool call. | | `RETRIEVE_MULTIPLIER`| `3` | Over-retrieval factor (retrieves `TOP_K * MULTIPLIER` candidates for reranker pool). | | `FINAL_CONTEXT_N` | `8` | Maximum number of top reranked chunks passed into generation prompt context. | | `POOL_MAX` | `50` | Hard cap on total deduped candidate chunks retained in pool across passes. | | `CARRY_MAX` | `4` | Maximum high-scoring chunks carried forward across retry iterations. | | `TRIAGE_TIMEOUT_S` | `15.0` | Max timeout allowed for Tier 1 triage classification before failing to complex path. | | `MAX_SUBQUERIES` | `3` | Upper bound on sub-queries generated for complex multi-part questions. | | `LLM_MODEL` | `ministral-8b-latest` | Default primary LLM for triage, evaluation, and final generation. | | `RERANKER_MODEL` | `jina-reranker-v2-base-multilingual` | Via Jina API Key (Tier 1 cascade) |