{"id":49165717,"url":"https://github.com/mj-deving/rag-hybrid-chatbot","last_synced_at":"2026-04-22T15:03:59.534Z","repository":{"id":351309472,"uuid":"1207361459","full_name":"mj-deving/rag-hybrid-chatbot","owner":"mj-deving","description":"Chat with your documents — Hybrid RAG chatbot combining Vector Search (Qdrant) + Knowledge Graph (NetworkX) with adaptive query routing and CRAG. Upload PDF/MD/TXT, ask questions, get cited answers.","archived":false,"fork":false,"pushed_at":"2026-04-22T06:41:39.000Z","size":307,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-22T08:33:12.514Z","etag":null,"topics":["ai-agents","chatbot","fastapi","knowledge-graph","llm","python","rag","vector-search"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mj-deving.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-10T21:23:11.000Z","updated_at":"2026-04-22T06:41:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/mj-deving/rag-hybrid-chatbot","commit_stats":null,"previous_names":["mj-deving/rag-hybrid-chatbot"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/mj-deving/rag-hybrid-chatbot","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mj-deving%2Frag-hybrid-chatbot","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mj-deving%2Frag-hybrid-chatbot/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mj-deving%2Frag-hybrid-chatbot/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mj-deving%2Frag-hybrid-chatbot/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mj-deving","download_url":"https://codeload.github.com/mj-deving/rag-hybrid-chatbot/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mj-deving%2Frag-hybrid-chatbot/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32141504,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-22T14:31:12.705Z","status":"ssl_error","status_checked_at":"2026-04-22T14:27:43.037Z","response_time":58,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agents","chatbot","fastapi","knowledge-graph","llm","python","rag","vector-search"],"created_at":"2026-04-22T15:03:58.726Z","updated_at":"2026-04-22T15:03:59.527Z","avatar_url":"https://github.com/mj-deving.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# RAG Hybrid Chatbot\n\n![Qdrant](https://img.shields.io/badge/-Qdrant-DC382D?style=flat-square) ![Python](https://img.shields.io/badge/-Python-3776AB?style=flat-square) ![Docker](https://img.shields.io/badge/-Docker-2496ED?style=flat-square)\n\n**Chat with your documents — upload PDFs, Markdown, or text files and ask questions with cited answers.**\n\nHybrid RAG (Retrieval-Augmented Generation) chatbot combining Vector Search (Qdrant) + Knowledge Graph (NetworkX) with adaptive query routing and CRAG. Local embeddings (no external API needed), Claude via OpenRouter for generation.\n\n![RAG Hybrid Chatbot Screenshot](docs/screenshot.png)\n\n## Table of Contents\n\n- [Quick Start (Docker)](#quick-start-docker) — one command, running\n- [Quick Start (Local)](#quick-start-local) — venv + pip\n- [Features](#features) — what the chatbot does\n- [Architecture](#architecture) — Upload Pipeline, Query Pipeline, System Overview\n- [Query Routing](#query-routing) — 4 routes compared\n- [API Endpoints](#api-endpoints) — REST API with examples\n- [Project Structure](#project-structure) — files and modules\n- [Tech Stack](#tech-stack) — versions and components\n- [Tests](#tests) — 61 tests, no API calls needed\n- [Configuration](#configuration) — environment variables\n- [License](#license)\n\n## Quick Start (Docker)\n\n```bash\n# 1. Clone\ngit clone https://github.com/mj-deving/rag-hybrid-chatbot.git\ncd rag-hybrid-chatbot\n\n# 2. Configure API key\necho 'OPENROUTER_API_KEY=sk-or-v1-...' \u003e .env\n\n# 3. Start\ndocker compose up --build\n# -\u003e http://localhost:8000\n```\n\nVector data is persisted in `data/qdrant/` and the knowledge graph in `data/graph.json` — both survive container restarts.\n\n## Quick Start (Local)\n\n```bash\n# 1. Clone\ngit clone https://github.com/mj-deving/rag-hybrid-chatbot.git\ncd rag-hybrid-chatbot\n\n# 2. Setup\npython3 -m venv venv\nsource venv/bin/activate\npip install -r requirements.txt\n\n# 3. Configure API key\necho 'OPENROUTER_API_KEY=sk-or-v1-...' \u003e\u003e ~/.claude/.env\n# Or: export OPENROUTER_API_KEY=sk-or-v1-...\n\n# 4. Start server\npython src/main.py\n# -\u003e http://localhost:8000\n```\n\n## Features\n\n- **Document Upload** — PDF, Markdown, TXT via drag-and-drop or API\n- **Automatic Chunking** — Recursive splitting (~500 tokens, 50 overlap)\n- **Local Embeddings** — fastembed (paraphrase-multilingual-MiniLM-L12-v2, 384-dim, ONNX) — no API key needed\n- **Vector Search** — Qdrant with Cosine Similarity, persistent file-based\n- **Graph RAG** — Knowledge graph of entities and relations (NetworkX), auto-extracted on upload\n- **Adaptive RAG** — 4-way query routing: simple, standard, complex, relational\n- **Corrective RAG (CRAG)** — Post-retrieval relevance check filters irrelevant chunks\n- **LLM Answers** — Claude via OpenRouter with source citations\n- **Chat UI** — Single-page HTML with dark theme, responsive\n- **REST API** — 4 endpoints with Swagger UI at `/docs`\n\n## Architecture\n\n### Upload Pipeline\n\n```mermaid\nflowchart TD\n    A[Browser] --\u003e|POST /upload| B[FastAPI]\n    B --\u003e C[document_processor.py]\n    B --\u003e E[entity_extractor.py]\n    C --\u003e|extract + chunk + embed| D[(Qdrant)]\n    E --\u003e|LLM Haiku| F[(Knowledge Graph)]\n\n    style B fill:#6c8cff,color:#0f1117,stroke:none\n    style D fill:#4ade80,color:#0f1117,stroke:none\n    style F fill:#a78bfa,color:#0f1117,stroke:none\n```\n\n### Query Pipeline\n\n```mermaid\nflowchart TD\n    Q[Question] --\u003e CL[query_classifier.py\\nAdaptive RAG]\n    CL --\u003e|simple| S[Direct LLM Answer]\n    CL --\u003e|standard| ST[Vector Search\\n+ CRAG Filter]\n    CL --\u003e|complex| CX[Multi-Query\\n+ Graph Context\\n+ CRAG Filter]\n    CL --\u003e|relational| RL[Graph Traversal\\n+ Vector Search]\n\n    S --\u003e OUT[Response + Sources\\n+ Graph Entities]\n    ST --\u003e GEN[Claude via OpenRouter]\n    CX --\u003e GEN\n    RL --\u003e GEN\n    GEN --\u003e OUT\n\n    style CL fill:#22d3ee,color:#0f1117,stroke:none\n    style GEN fill:#6c8cff,color:#0f1117,stroke:none\n    style OUT fill:#4ade80,color:#0f1117,stroke:none\n```\n\n### System Overview\n\n```mermaid\nflowchart TB\n    Browser --\u003e|HTTP| API[FastAPI\\nsrc/api.py]\n\n    subgraph Upload\n        API --\u003e|POST /upload| DP[document_processor.py]\n        DP --\u003e VS[(Qdrant)]\n        API --\u003e EE[entity_extractor.py]\n        EE --\u003e KG[(Knowledge Graph)]\n    end\n\n    subgraph Query\n        API --\u003e|POST /query| RE[rag_engine.py]\n        RE --\u003e QC[query_classifier.py]\n        QC --\u003e RE\n        RE --\u003e VS\n        RE --\u003e KG\n        RE --\u003e RC[relevance_checker.py]\n        RE --\u003e|generate| LLM[Claude via OpenRouter]\n    end\n\n    API --\u003e|GET /documents| VS\n    API --\u003e|DELETE /documents| VS\n\n    style API fill:#6c8cff,color:#0f1117,stroke:none\n    style VS fill:#4ade80,color:#0f1117,stroke:none\n    style KG fill:#a78bfa,color:#0f1117,stroke:none\n    style LLM fill:#fbbf24,color:#0f1117,stroke:none\n```\n\n## Query Routing\n\nThe classifier automatically decides which route to use based on the question:\n\n| Route | When | Retrieval | Graph | Example |\n|-------|------|-----------|-------|---------|\n| **simple** | General knowledge questions | — | — | \"What is RAG?\" |\n| **standard** | Document-specific questions | Vector Search + CRAG | — | \"What does the report say about Q4?\" |\n| **complex** | Comparative multi-aspect questions | Parallel sub-queries + CRAG | Context | \"Compare the RAG architectures\" |\n| **relational** | Relationships between entities | Fallback | Traversal | \"Who works with whom?\" |\n\n## API Endpoints\n\n| Method | Path | Description |\n|--------|------|-------------|\n| `POST` | `/upload` | Upload and index a document |\n| `POST` | `/query` | Ask a question, get answer with sources |\n| `GET` | `/documents` | List all indexed documents |\n| `DELETE` | `/documents/{id}` | Remove a document and its vectors |\n\n### `POST /upload`\n\n```bash\ncurl -X POST http://localhost:8000/upload -F \"file=@document.md\"\n```\n\n**Response:**\n```json\n{\n  \"document_id\": \"a1b2c3d4e5f6\",\n  \"filename\": \"document.md\",\n  \"chunks\": 3,\n  \"status\": \"indexed\"\n}\n```\n\n### `POST /query`\n\n```bash\ncurl -X POST http://localhost:8000/query \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\": \"What does the report say about Q4 results?\", \"top_k\": 5}'\n```\n\n**Response:**\n```json\n{\n  \"answer\": \"According to the document...\",\n  \"sources\": [\n    {\"document\": \"report.md\", \"chunk\": 2, \"relevance\": 0.8734}\n  ],\n  \"tokens_used\": 1250,\n  \"routing\": {\n    \"route\": \"standard\",\n    \"sub_queries\": [],\n    \"entity_names\": []\n  },\n  \"retrieval_quality\": {\n    \"chunks_retrieved\": 5,\n    \"chunks_relevant\": 3,\n    \"chunks_filtered\": 2,\n    \"fallback_triggered\": false\n  },\n  \"graph_entities\": null\n}\n```\n\n### `POST /query` (relational)\n\n```bash\ncurl -X POST http://localhost:8000/query \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"question\": \"Which organizations collaborate?\"}'\n```\n\n**Response (with graph entities):**\n```json\n{\n  \"answer\": \"The knowledge graph shows the following connections...\",\n  \"sources\": [],\n  \"tokens_used\": 800,\n  \"routing\": {\n    \"route\": \"relational\",\n    \"sub_queries\": [],\n    \"entity_names\": [\"Acme Corp\", \"TechStart GmbH\"]\n  },\n  \"graph_entities\": [\n    {\n      \"name\": \"Acme Corp\",\n      \"type\": \"organization\",\n      \"neighbors\": [\n        {\"entity\": \"TechStart GmbH\", \"relation\": \"collaborates_with\"}\n      ]\n    }\n  ]\n}\n```\n\n### Other Endpoints\n\n```bash\n# List documents\ncurl http://localhost:8000/documents\n\n# Delete a document\ncurl -X DELETE http://localhost:8000/documents/{document_id}\n```\n\n## Project Structure\n\n```\nrag-hybrid-chatbot/\n├── src/\n│   ├── api.py                 # FastAPI endpoints\n│   ├── llm_client.py          # Shared OpenRouter client + constants\n│   ├── query_classifier.py    # Adaptive RAG: 4-way query routing\n│   ├── relevance_checker.py   # CRAG: post-retrieval relevance check\n│   ├── document_processor.py  # Text extraction, chunking, embedding\n│   ├── vector_store.py        # Qdrant persistent storage\n│   ├── knowledge_graph.py     # Knowledge graph (NetworkX, JSON-persistent)\n│   ├── entity_extractor.py    # LLM-based entity extraction\n│   ├── rag_engine.py          # RAG orchestrator\n│   └── main.py                # Server startup\n├── static/\n│   └── index.html             # Chat UI (single-file, no build step)\n├── scripts/\n│   └── upload_test_docs.py    # Upload test docs + run a query\n├── tests/                     # 61 pytest tests\n├── Dockerfile                 # Python 3.12-slim\n├── docker-compose.yml         # One-command setup\n├── requirements.txt\n└── README.md\n```\n\n## Tech Stack\n\n| Component | Tool | Version |\n|-----------|------|---------|\n| Runtime | Python | 3.12+ |\n| API Framework | FastAPI + Uvicorn | 0.135 / 0.44 |\n| Vector DB | Qdrant (persistent file-based) | 1.17 |\n| Embeddings | fastembed / multilingual-MiniLM-L12-v2 (ONNX) | 0.8 |\n| Knowledge Graph | NetworkX (in-memory, JSON-persistent) | 3.6 |\n| LLM | Claude Sonnet via OpenRouter | openai 2.31 |\n| PDF Parsing | PyMuPDF | 1.27 |\n| Frontend | Vanilla HTML/CSS/JS | — |\n| Container | Docker + Compose | — |\n\n## Tests\n\nAll tests run locally without API calls (LLM is mocked):\n\n```bash\nsource venv/bin/activate\npytest tests/ -v\n```\n\n61 tests across 8 modules:\n\n| Module | Tests | Covers |\n|--------|-------|--------|\n| `test_document_processor.py` | 9 | Text extraction, chunking, embedding dimensions |\n| `test_vector_store.py` | 7 | Upsert, search, list, delete |\n| `test_knowledge_graph.py` | 14 | Graph CRUD, traversal, persistence, singleton |\n| `test_entity_extractor.py` | 5 | LLM extraction, error handling, parse failures |\n| `test_query_classifier.py` | 6 | All 4 routes + fallback |\n| `test_relevance_checker.py` | 5 | CRAG filter, confidence thresholds |\n| `test_api.py` | 14 | Upload, query, delete, auth, Swagger |\n| `test_main.py` | 1 | Env loading |\n\n## Configuration\n\nThe server reads API keys from `~/.claude/.env` or environment variables:\n\n| Variable | Purpose | Default |\n|----------|---------|---------|\n| `OPENROUTER_API_KEY` | LLM access (Claude via OpenRouter) | (required) |\n| `RAG_API_KEY` | Bearer token for API auth | (empty = auth disabled) |\n| `EMBEDDING_MODEL` | fastembed model name | `sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2` |\n| `EMBEDDING_DIM` | Vector dimension | `384` |\n| `QDRANT_PATH` | Path for Qdrant data | `data/qdrant/` |\n| `GRAPH_PATH` | Path for knowledge graph | `data/graph.json` |\n\nEmbeddings run locally — no additional API key needed.\nFor English-only corpora: `EMBEDDING_MODEL=BAAI/bge-small-en-v1.5`.\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmj-deving%2Frag-hybrid-chatbot","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmj-deving%2Frag-hybrid-chatbot","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmj-deving%2Frag-hybrid-chatbot/lists"}