A production-grade Agentic RAG system with LangGraph agent, Docling extraction, OpenSearch hybrid search, Cohere reranking, Redis caching, and RAGAS evaluation.

A recruiter visits my portfolio, clicks the AI assistant, and asks questions about my projects, skills, and experience. The system retrieves relevant information from my documents and generates accurate, grounded answers in seconds.
Unlike linear RAG pipelines, this system uses a LangGraph agent that reasons about which tools to call. The agent can search all documents, focus on specific categories, or look up a single file — then decide if it needs to retrieve again before generating an answer.
Prompt injection detection, harmful content filter, length validation
Blocks general knowledge — only answers questions about Adeesha's work
LLM decides which tools to call based on query intent. Max 2 retrieval rounds.
BM25 keyword search + k-NN dense vector search with RRF fusion
Top 10 results narrowed to top 3 most relevant
Primary LLM with automatic fallback to Gemini 2.5 Flash on rate limits
LLM response cache (1hr TTL) + conversation memory (24hr TTL)
Full execution tracing, prompt versioning, grading scores
| Tool | Description |
|---|---|
| search_all | Search all documents — default for most queries |
| search_projects | Focus on project documents only |
| search_skills | Focus on skills/resume documents |
| search_source | Search a specific file by name |
| list_documents | Show what's available in the index |
LangGraph StateGraph with tool routing and multi-step retrieval
2000 char parents (LLM context) + 500 char children (search precision)
Synonym-based expansion for better BM25 recall
X-API-Key header on all endpoints (health check open)
5 requests/min per IP
Groq primary, Gemini 2.5 Flash on rate limit/connection errors
3 attempts, exponential backoff on all external APIs
In-memory 5min TTL avoids redundant search + rerank calls
Delete existing chunks before re-indexing
logging module with per-module loggers (rag.graph, rag.tools, etc.)
/Health check (OpenSearch, MinIO, Redis)/queryAsk a question (body: {question, source_filter?})/upload?folder=Upload file to MinIO (max 20MB)/files?folder=List files (optional folder filter)/files/{folder}/{filename}Delete file + reindex/reindexFull reindex from MinIO/clear-memoryClear conversation history/clear-cacheClear LLM response cacheIs the answer grounded in context (no hallucination)?
Does the answer address the question?
Are retrieved docs focused and relevant?
Did we retrieve all needed information?
Is answer factually accurate vs ground truth?