--- title: Ask GreenMetric β€” Sustainable University Rankings Q&A emoji: πŸ“š colorFrom: green colorTo: blue sdk: gradio sdk_version: "6.14.0" app_file: app.py pinned: false --- # πŸ“š RAG β€” Multi Source Information Retrieval > A hybrid Retrieval-Augmented Generation (RAG) project built to learn and implement context injection using the DeepSeek API. The system answers complex queries about the UI GreenMetric Sustainable University Rankings by combining unstructured narrative guidelines with structured tabular appendices. **v1.0** Β· Python 3.12.13 Β· [Live Demo](https://fortunius-rag-uigreenmetric.hf.space) --- ## πŸ› οΈ Tech Stack | Layer | Component | |---|---| | Language | Python 3.12.13 | | Embedding | `BAAI/bge-m3` (SentenceTransformers, 1024-dim) | | Retrieval | RAG Fusion: paraphrase Γ—3 + ChromaDB (cosine, top-k=10) + RRF (k=60), top-n=7 | | Data | `pandas`, `openpyxl` | | LLM (pipeline) | DeepSeek API via OpenAI SDK (`v4-pro` generation, `v4-flash` routing/paraphrase) | | LLM (evaluation) | `deepseek-v4-flash` with thinking disabled | | UI | Gradio | | Evaluation | DeepEval 4.0.4 | --- ## πŸ—‚οΈ Data Sources The UI GreenMetric guidelines document is split into **7 files** β€” 1 narrative document and 6 structured tables. This separation preserves each piece's natural format and lets the retriever target the right structure per query. | File | Format | Content | |---|---|---| | `guidelines_markdown.md` | Markdown | Full narrative: methodology, categories, indicators, evidence rules, coordinator info | | `appendix1_questionnairemasterandscoring.csv` | CSV (question-grouped) | 118 indicators across 7 categories with answer options and calculated scores | | `appendix2_listofgreenbuildingelements.csv` | CSV (category-grouped) | Green building elements for existing buildings and new construction | | `appendix3_listanddescriptionofsmartbuildingrequirements.csv` | CSV (field-code-grouped) | Smart building requirements (Automation, Safety, Energy, Water, Indoor, Lighting) | | `table1_nationalcoordinators.csv` | CSV (country-grouped) | 35 national coordinator universities across 30 countries | | `table2_categoriesusedandweighting.csv` | CSV (category-grouped) | 7 categories with their percentage weights | | `table4_greenhousegasemissionsources.csv` | CSV (scope-grouped) | Emission sources classified by Scope 1, 2, and 3 | **Why split tables out of the PDF?** Tables extracted during PDF-to-markdown conversion lose their row-column structure. By storing them as separate CSVs, the retriever can fetch data by natural grouping keys (question number, category, country) instead of parsing broken table text from paragraphs. --- ## 🧱 Source Code | File | Role | |---|---| | `src/chunker.py` | Splits markdown (heading-level) and CSV tables (grouped by column) into embeddable chunks | | `src/embedder.py` | Loads BGE-M3, encodes text into vectors, persists to ChromaDB | | `src/retriever.py` | Single-query retrieval + multi-query RRF merge; dispatches by source (pdf/csv/both) and query type (lookup/aggregate) | | `src/router.py` | LLM-based query classifier β€” routes to source (PDF/CSV/Both/None) and query type (lookup/aggregate); generates paraphrase variants for RAG Fusion | | `src/generator.py` | Formats context + calls DeepSeek to produce answers; flags low-confidence results | | `src/pipeline.py` | Orchestrator β€” wires route β†’ paraphrase β†’ multi-query RRF β†’ (opt-in reranker) β†’ generate | | `src/budget.py` | Token budget tracking with HF Datasets persistence; guards against API overspend | | `src/conversation.py` | Logs user prompts + responses to HF Datasets for quality monitoring | | `src/reranker.py` | BGE V2-M3 cross-encoder reranker (enabled by default) | | `src/evaluate.py` | Pre-computes routes + paraphrases, runs pipeline, DeepEval batch scorer, 5-section report with context debugging and per-case timing | | `build_collection.py` | Chunks all 7 sources, prints sanity check (317 expected), builds ChromaDB collection | | `app.py` | Gradio chat UI | --- ## πŸ› οΈ Dependencies Key libraries beyond the standard Python data stack: | Package | Version | Purpose | |---|---|---| | `chromadb` | 1.5.9 | Vector database | | `sentence-transformers` | 5.5.1 | Embedding model (BGE-M3) | | `openai` | 2.38.0 | DeepSeek API client | | `gradio` | 6.14.0 | Web UI | | `deepeval` | 4.0.4 | Evaluation metrics | | `huggingface-hub` | β‰₯0.20 | HF Datasets storage for budget + conversation logs | | `transformers` | 4.57.6 | LLM model loading | | `FlagEmbedding` | 1.4.0 | BGE reranker + BGE-M3 embeddings | --- ## βš™οΈ Pipeline Architecture 1. **Ingestion:** Markdown β†’ Heading-Level Chunking, CSV β†’ Group-Based Chunking (118 question groups, 6 green building categories, 6 smart building fields, 30 coordinator countries, 7 category weights, 3 emission scopes) β†’ Embed with BGE-M3 β†’ Store in ChromaDB. 2. **Retrieval & Generation:** User Query β†’ Budget Guard β†’ Router (LLM) β†’ Paraphrase (3 variants via DeepSeek) β†’ Multi-query ChromaDB search (top-k=10 each) β†’ RRF (k=60) β†’ top 7 chunks β†’ (reranker, enabled by default) β†’ Context Concatenation β†’ DeepSeek LLM Generation. --- ## πŸ“Š Evaluation (DeepEval) | Metric | v0.5 (MiniLM) | v0.6 (BGE-M3) | v1.0 (BGE-M3 + BGE) | |---|---|---|---| | Faithfulness | 0.91 | 0.93 | **0.96** | | Contextual Recall | 0.74 | 0.81 | **0.83** | | Contextual Precision (NDCG@K) | 0.45 | 0.56 | **0.72** | | G-Eval Correctness | 0.43 | 0.51 | **0.59** | | Router Accuracy | 80.0% | 77.5% | **91.5%** | *47 test cases. LLM-as-judge Β±0.05-0.08 variance. See `test_cases/RERANKER_REPORT_V2.md` for full benchmark.* --- ## 🧠 Design Decisions | Decision | Reason | |---|---| | **Cosine similarity** | Matches the training metric of the embedding model | | **BGE-M3 over Qwen3** | Better CP on structured CSV + markdown data (0.60 solo vs 0.44). Native dense+sparse training signal. No instruction prompt needed. Qwen3 evaluated and reverted. | | **RAG Fusion (paraphrase Γ—3 + RRF)** | Resolves vocabulary mismatches that cosine search alone misses | | **Question-grouped CSV chunks** | Prevents partial/orphaned indicators β€” the LLM always sees a complete criterion | | **Formula injection in chunks** | Embedding formulas directly into chunk text reduces hallucination on calculation questions | | **No cosine distance threshold** | Removed 0.5 threshold β€” was discarding relevant chunks; RRF handles quality ordering | | **Single ChromaDB collection** | At 317 chunks, per-source collections add complexity with no performance gain | | **No conversation history** | Degrades router accuracy β€” few-shot training uses single queries, and prior-turn vocabulary pulls the router toward stale sources | | **BGE reranker enabled** | Evaluated 5 rerankers across 2 embedders. BGE V2-M3 + BGE-M3 embedder gives best CP (0.72) and CR (0.83). Qwen3+reranker hits 0.61 CP. Reranker enabled by default. | --- ## πŸ—ΊοΈ v0.5 β†’ v1.0 Roadmap - [x] Embedding model upgrade to **BGE-M3** (re-adopted after Qwen3 evaluation) - [x] Rebuild ChromaDB collection (317 chunks, 1024-dim) - [x] Embedding model upgrade to **Qwen3-Embedding-0.6B** (evaluated, CP inferior, reverted to BGE-M3) - [x] Implement **RAG Fusion** (paraphrase + multi-query RRF) - [x] Evaluate 5 rerankers β€” BGE V2-M3 adopted, enabled by default - [x] Budget management for API spending - [x] Deploy on HuggingFace Spaces (`fortunius/rag-uigreenmetric`) - [x] Aggregate query optimization (metadata-driven stats, zero LLM, token reduction 15Kβ†’2K) - [x] Router tuned to 91-94% with 26 few-shot examples β€” LLM variance (Β±5-8%), some queries span both sources - [x] ~~Dense + sparse hybrid retrieval using BGE-M3 flag embeddings~~ | Tested, result in table below: | Metric | Dense | Hybrid | Ξ” | |---|---|---|---| | Faithfulness | 0.96 | 0.92 | -0.04 | | Contextual Recall | 0.83 | 0.84 | +0.01 | | Contextual Precision | 0.72 | 0.71 | -0.01 | | G-Eval | 0.59 | 0.51 | -0.08 | | Router Accuracy | 91.5% | 89.4% | -2.1% | | Avg latency | 10,188ms | 12,444ms | +2,256ms | Sparse added 2.2s latency with no CP gain and degraded G-Eval. Not worth the cost. --- ## πŸ—ΊοΈ v1.5 Roadmap - [ ] Show retrieved context (collapsible view of chunks used per answer) - [ ] Source citation (display which data source answered the query) - [ ] Token cost per query (exact usage and estimated cost under each answer) - [ ] Pipeline timing (response generation time displayed) - [ ] Route badge color-coding (green/blue/purple/gray for pdf/csv/both/none) - [ ] Markdown rendering (properly formatted lists, tables, bold text) - [ ] Copy answer button (clipboard copy on each response) - [ ] Welcome + example chips (clickable sample questions on empty chat) - [ ] Feedback thumbs (Β±1 per answer, logged for future evaluation) - [ ] Dark mode toggle (OS preference detection + manual toggle) ## πŸš€ v2.0 Roadmap - [ ] FastAPI backend (`POST /ask`, `GET /budget`, `GET /health`) - [ ] Streaming generator (DeepSeek `stream=True` β†’ `StreamingResponse`) - [ ] Conversation history (generator-only, router stays single-query) - [ ] Dockerize backend (`Dockerfile` + `docker-compose.yml`) - [ ] Svelte chat UI with Vercel AI SDK (replaces Gradio) - [ ] Strip Gradio (`app.py`, `gradio` from requirements) --- ## ⚠️ Known Limitations - **Router accuracy ~91-94%:** Improved with few-shot tuning but 1-4 cases still misrouted per run due to LLM variance (Β±5-8%). Some queries genuinely span both PDF and CSV sources β€” neither route is wrong, just incomplete. - **CP bottleneck (0.72):** Contextual Precision remains the weakest metric, improving from 0.60 (BGE-M3 solo) to 0.72 (BGE-M3 + reranker + aggregate stats). Sparse hybrid retrieval tested and rejected. Further gains likely require embedder fine-tuning on domain-specific data. - **G-Eval language sensitivity:** Scoring dips when the answer and ground truth differ in language (EN ↔ ID) despite being semantically equivalent. - **RAG Fusion latency:** Paraphrase LLM call + 4Γ— embeddings adds ~1-2s per query vs single-query retrieval. ---