AI assistant answering questions about Tunisian labor law (Code du Travail), built with hybrid retrieval (BM25 + embeddings), reranking, and Groq/Llama
# ⚖️ Tunisian labor law ai assistant
An AI assistant that answers questions about the Tunisian Labor Law (Code du Travail, 2016 edition) in French, grounded strictly in the official text — no external knowledge, no hallucinated articles.
Built from scratch with a custom hybrid-retrieval RAG pipeline — deliberately without LangChain, in order to understand what happens under the hood at every stage (chunking, embeddings, hybrid retrieval, reranking, generation) — as a portfolio project demonstrating retrieval-augmented generation fundamentals end to end.
> ⚠️ This is a technical demonstration project and does **not** constitute legal advice.
## How it works
```
PDF (Code du Travail 2016)
│
▼
Ingestion → parses raw text into structured articles
│ (article number, livre/titre/chapitre/section,
│ modification history)
▼
Chunking → builds two indexes from the parsed articles:
│ • BM25 (lexical/keyword index)
│ • ChromaDB (dense vector index, bge-m3 embeddings)
▼
Retrieve → hybrid search at query time:
│ 1. BM25 search + vector search (top 20 each)
│ 2. Reciprocal Rank Fusion (RRF) merges both lists
│ 3. Cross-encoder reranks the fused candidates
│ 4. Top 5 most relevant articles are kept
▼
Generate → the retrieved articles are passed as context to
│ Groq/Llama, which answers strictly from that
│ context and cites the article numbers used
▼
Gradio chat UI
```
### Why hybrid retrieval?
- **BM25** catches exact legal terminology and article-number matches that embeddings can miss.
- **Dense vectors (bge-m3)** catch semantically related questions phrased differently from the text.
- **RRF** combines both ranked lists without needing to tune a weighting scheme.
- **Cross-encoder reranking (bge-reranker-base)** does a final, more expensive but more accurate pass over the fused candidates before they reach the LLM.
### Grounding & anti-hallucination
The system prompt instructs the model to answer *only* …