diff --git a/.env.example b/.env.example new file mode 100644 index 0000000000000000000000000000000000000000..8ffe8d898ebf07aefcad96ceef9f463f93f37556 --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# Google Gemini API Key (get from https://aistudio.google.com/apikey) +GOOGLE_API_KEY=your_google_api_key_here + +# Embedding model (default: all-MiniLM-L6-v2 from sentence-transformers, runs locally) +EMBEDDING_MODEL=all-MiniLM-L6-v2 + +# LLM model (default: gemini-2.0-flash) +LLM_MODEL=gemini-2.0-flash + +# Vector store path +VECTOR_STORE_PATH=data/vector_store + +# PDF source directory +PDF_DIR=data/pdfs + +# Chunk size and overlap for text splitting +CHUNK_SIZE=1000 +CHUNK_OVERLAP=200 diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..1a458815e58d41ad45b9e2f6349b608fbd22f8ac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,36 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_10.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_11.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_12.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_13.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_14.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_15.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_2.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_4.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_5.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_6.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/chapter_7.pdf filter=lfs diff=lfs merge=lfs -text +data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf filter=lfs diff=lfs merge=lfs -text +data/vector_store/index.faiss filter=lfs diff=lfs merge=lfs -text +logo.png filter=lfs diff=lfs merge=lfs -text diff --git a/.python-version b/.python-version new file mode 100644 index 0000000000000000000000000000000000000000..c8cfe3959183f8e9a50f83f54cd723f2dc9c252d --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.10 diff --git a/.railwayignore b/.railwayignore new file mode 100644 index 0000000000000000000000000000000000000000..6ebde1c167874359f49bf86419c53bae39f6df99 --- /dev/null +++ b/.railwayignore @@ -0,0 +1,13 @@ +.venv/ +venv/ +.env +data/pdfs/ +__pycache__/ +*.pyc +*.pyo +.vscode/ +.idea/ +.DS_Store +Thumbs.db +.ipynb_checkpoints/ +*.zip diff --git a/COLAB_GUIDE.md b/COLAB_GUIDE.md new file mode 100644 index 0000000000000000000000000000000000000000..fd67764a481c87e288eeb89ab3fb88f8c7c4c298 --- /dev/null +++ b/COLAB_GUIDE.md @@ -0,0 +1,112 @@ +# ๐ŸŽ“ Study Ocean RAG โ€” Google Colab Guide + +> Your laptop stays cool. Colab's **T4 GPU** does all the heavy lifting. + +--- + +## ๐Ÿ“‹ One-Time Setup (First Time Only) + +### 1. Upload ZIP to Google Drive +1. Go to [drive.google.com](https://drive.google.com) +2. Create a folder: `MyDrive/StudyOcean/` +3. Upload `StudyOceanPDFs.zip` into that folder +4. โณ Wait for upload to complete (6 GB โ€” may take 15โ€“30 min on slow wifi) + +### 2. Get a Gemini API Key (Free) +1. Go to [aistudio.google.com/apikey](https://aistudio.google.com/apikey) +2. Click **Create API Key** +3. Copy the key โ€” you'll paste it into the notebook + +### 3. Open the Notebook in Colab +1. Go to [colab.research.google.com](https://colab.research.google.com) +2. Click **File โ†’ Upload notebook** +3. Upload `StudyOcean_RAG_Colab.ipynb` from this folder + +### 4. Set T4 GPU Runtime +> **This is critical โ€” without it, ingestion will be very slow!** +1. In Colab: **Runtime โ†’ Change runtime type** +2. Set Hardware accelerator = **T4 GPU** +3. Click **Save** + +--- + +## ๐Ÿš€ Running the Pipeline + +### First Time (Fresh Ingestion) +Run cells in order: **Steps 1 โ†’ 2 โ†’ 3 โ†’ 4 โ†’ 5 โ†’ 6 โ†’ 7 โ†’ 8 โ†’ 9 โ†’ 10** + +| Step | What it does | Time | +|------|-------------|------| +| 1 | Verify T4 GPU + mount Drive | 30s | +| 2 | Install packages | 2โ€“3 min | +| 3 | Configure settings (paste API key here!) | instant | +| 4 | Extract PDFs from ZIP | 5โ€“10 min | +| 5 | Ingest PDFs & build vector store | **30โ€“60 min** | +| 6 | Save vector store to Drive | 2โ€“5 min | +| 7 | Test retrieval | instant | +| 8 | Build RAG chain | instant | +| 9 | Test full pipeline | 5s | +| 10 | Launch Gradio chat UI | instant | + +### Future Sessions (Vector Store Already Built) +Run cells: **Steps 1 โ†’ 2 โ†’ 3 โ†’ 11 โ†’ 8 โ†’ 10** + +| Step | What it does | Time | +|------|-------------|------| +| 11 | Load saved vector store from Drive | 1โ€“2 min | +| 8 | Build RAG chain | instant | +| 10 | Launch Gradio chat UI | instant | + +--- + +## ๐Ÿ’ป Running Locally (After Ingestion) + +Once the vector store is built, you can run the chat locally: + +### Option A โ€” Download via Colab (Step 12) +- Run **Step 12** in the notebook +- It downloads `vector_store_export.zip` to your computer +- Extract it to: `Study_Ocean Rag/data/vector_store/` + +### Option B โ€” Download from Google Drive +- Go to Drive โ†’ `StudyOcean/vector_store/` +- Download the folder +- Place it at: `Study_Ocean Rag/data/vector_store/` + +### Then run locally: +```bash +# Make sure .env has your GOOGLE_API_KEY +python main.py chat # Web UI at http://localhost:7860 +# OR +python main.py ask # Terminal Q&A +``` + +--- + +## โšก Performance on T4 GPU + +| Task | CPU (your laptop) | T4 GPU (Colab) | +|------|------------------|----------------| +| Embedding batch size | 64 | 256 | +| ~10,000 chunks | ~45 min | ~8 min | +| ~50,000 chunks | ~4 hours | ~35 min | +| ~100,000 chunks | ~8 hours | ~70 min | + +--- + +## โ“ Troubleshooting + +**"ZIP not found" error** +โ†’ Update `ZIP_DRIVE_PATH` in Step 3 to match your actual Drive path + +**"CUDA not available"** +โ†’ You forgot to set T4 GPU runtime! Runtime โ†’ Change runtime type โ†’ T4 GPU + +**Colab session disconnects during ingestion** +โ†’ The vector store saves in batches. Re-run from Step 5 โ€” FAISS will rebuild from scratch. Save to Drive often. + +**"Invalid API key"** +โ†’ Make sure you pasted the full Gemini API key in Step 3 + +**Gradio link doesn't work** +โ†’ Gradio public links expire after 72 hours. Re-run Step 10 to get a new link. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7e21c3f0ae9a8dfc18eae0978f275cfcdb69fcd2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,24 @@ +# Use an official Python runtime as a parent image +FROM python:3.11-slim + +# Hugging Face Spaces require running as a non-root user +RUN useradd -m -u 1000 user +USER user +ENV PATH="/home/user/.local/bin:$PATH" + +# Set the working directory +WORKDIR /app + +# Copy requirements and install dependencies +COPY --chown=user ./requirements.txt requirements.txt +RUN pip install --no-cache-dir --upgrade pip && \ + pip install --no-cache-dir -r requirements.txt + +# Copy the rest of the application +COPY --chown=user . /app + +# Expose the port that Hugging Face Spaces expects (7860) +EXPOSE 7860 + +# Command to run the FastAPI app +CMD ["uvicorn", "api:app", "--host", "0.0.0.0", "--port", "7860"] diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000000000000000000000000000000000..351a3f0dfa54201e99e93622aa27e6c056ea709d --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: uvicorn api:app --host 0.0.0.0 --port $PORT diff --git a/README.md b/README.md index 9f4ef1175b50301d033fb1464ea46758c1be752b..2c1ec74d8644dc82fb85c84faeb041c254e062da 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,8 @@ --- -title: Study-Ocean-AI Mentor -emoji: ๐Ÿ“š -colorFrom: green -colorTo: blue +title: Study Ocean RAG +emoji: ๐ŸŒŠ +colorFrom: blue +colorTo: indigo sdk: docker -pinned: false +app_port: 7860 --- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference diff --git a/StudyOcean_RAG_Colab.ipynb b/StudyOcean_RAG_Colab.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..5cd3163a6d43c31b04d898089f6112c6f9d66907 --- /dev/null +++ b/StudyOcean_RAG_Colab.ipynb @@ -0,0 +1,743 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "gpuType": "T4", + "name": "StudyOcean_RAG_Colab.ipynb" + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + }, + "accelerator": "GPU" + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# ๐ŸŽ“ Study Ocean AI Mentor โ€” Google Colab Pipeline\n", + "\n", + "> **Run this notebook on Colab with T4 GPU** \n", + "> Runtime โ†’ Change runtime type โ†’ **T4 GPU**\n", + "\n", + "### What this notebook does:\n", + "1. โœ… Mounts your Google Drive\n", + "2. โœ… Installs all dependencies (with `faiss-gpu` for T4 acceleration)\n", + "3. โœ… Extracts PDFs from `StudyOceanPDFs.zip`\n", + "4. โœ… Runs the ingestion pipeline (GPU-accelerated embeddings)\n", + "5. โœ… Saves the FAISS vector store back to Google Drive\n", + "6. โœ… Launches the Gradio chat UI with a public shareable link\n", + "\n", + "---\n", + "### ๐Ÿ“‹ Before you start:\n", + "- Upload `StudyOceanPDFs.zip` to your **Google Drive** (e.g., `MyDrive/StudyOcean/StudyOceanPDFs.zip`)\n", + "- Get your **Google Gemini API key** from https://aistudio.google.com/apikey\n", + "- Set runtime to **T4 GPU**: Runtime โ†’ Change runtime type โ†’ T4 GPU\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ”ง Step 1 โ€” Verify GPU & Mount Google Drive" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Verify T4 GPU is available โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "import subprocess\n", + "result = subprocess.run(['nvidia-smi'], capture_output=True, text=True)\n", + "print(result.stdout if result.returncode == 0 else \"โš ๏ธ No GPU found! Go to Runtime โ†’ Change runtime type โ†’ T4 GPU\")\n", + "\n", + "import torch\n", + "print(f\"\\n๐Ÿ”ฅ PyTorch CUDA available: {torch.cuda.is_available()}\")\n", + "if torch.cuda.is_available():\n", + " print(f\" GPU: {torch.cuda.get_device_name(0)}\")\n", + " print(f\" VRAM: {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Mount Google Drive โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "from google.colab import drive\n", + "drive.mount('/content/drive')\n", + "print(\"โœ… Google Drive mounted at /content/drive\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“ฆ Step 2 โ€” Install Dependencies" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Install all packages โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "# Using faiss-gpu for T4 GPU acceleration during similarity search\n", + "!pip install -q \\\n", + " langchain \\\n", + " langchain-community \\\n", + " langchain-core \\\n", + " langchain-google-genai \\\n", + " langchain-huggingface \\\n", + " pymupdf \\\n", + " faiss-gpu \\\n", + " sentence-transformers \\\n", + " google-generativeai \\\n", + " python-dotenv \\\n", + " gradio \\\n", + " tiktoken \\\n", + " accelerate\n", + "\n", + "print(\"\\nโœ… All packages installed!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## โš™๏ธ Step 3 โ€” Configure Settings\n", + "\n", + "> **Edit the variables in this cell before running!**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "\n", + "# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "# โœ๏ธ EDIT THESE SETTINGS\n", + "# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "\n", + "# Your Google Gemini API key โ€” get it from https://aistudio.google.com/apikey\n", + "GOOGLE_API_KEY = \"\" # โ† PASTE YOUR KEY HERE\n", + "\n", + "# Path to StudyOceanPDFs.zip in your Google Drive\n", + "ZIP_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/StudyOceanPDFs.zip\"\n", + "\n", + "# Where to save the vector store in Google Drive (so it persists!)\n", + "VECTOR_STORE_DRIVE_PATH = \"/content/drive/MyDrive/StudyOcean/vector_store\"\n", + "\n", + "# Embedding model โ€” 'cuda' will use the T4 GPU!\n", + "EMBEDDING_MODEL = \"all-MiniLM-L6-v2\" # Fast & accurate\n", + "EMBEDDING_DEVICE = \"cuda\" # โ† T4 GPU acceleration\n", + "\n", + "# Gemini LLM model\n", + "LLM_MODEL = \"gemini-2.0-flash\"\n", + "\n", + "# Text splitting config\n", + "CHUNK_SIZE = 1000\n", + "CHUNK_OVERLAP = 200\n", + "\n", + "# RAG retrieval\n", + "TOP_K = 5\n", + "\n", + "# โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•\n", + "\n", + "# Local working paths (inside Colab VM)\n", + "PDF_DIR = \"/content/studyocean_pdfs\"\n", + "VECTOR_STORE_LOCAL = \"/content/vector_store\"\n", + "\n", + "os.makedirs(PDF_DIR, exist_ok=True)\n", + "os.makedirs(VECTOR_STORE_LOCAL, exist_ok=True)\n", + "\n", + "# Set the API key as env var for LangChain\n", + "os.environ[\"GOOGLE_API_KEY\"] = GOOGLE_API_KEY\n", + "\n", + "print(\"โœ… Configuration set!\")\n", + "print(f\" ZIP source: {ZIP_DRIVE_PATH}\")\n", + "print(f\" PDF extract: {PDF_DIR}\")\n", + "print(f\" Vector store: {VECTOR_STORE_LOCAL}\")\n", + "print(f\" Drive backup: {VECTOR_STORE_DRIVE_PATH}\")\n", + "print(f\" Embedding GPU: {EMBEDDING_DEVICE}\")\n", + "print(f\" LLM: {LLM_MODEL}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ“‚ Step 4 โ€” Extract PDFs from ZIP" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import zipfile\n", + "import time\n", + "\n", + "# Check if zip exists\n", + "if not os.path.exists(ZIP_DRIVE_PATH):\n", + " print(f\"โŒ ZIP not found at: {ZIP_DRIVE_PATH}\")\n", + " print(\" Please upload StudyOceanPDFs.zip to your Google Drive\")\n", + " print(\" Then update ZIP_DRIVE_PATH in Step 3.\")\n", + " raise FileNotFoundError(f\"ZIP not found: {ZIP_DRIVE_PATH}\")\n", + "\n", + "zip_size_gb = os.path.getsize(ZIP_DRIVE_PATH) / 1e9\n", + "print(f\"๐Ÿ“ฆ Found ZIP: {zip_size_gb:.2f} GB\")\n", + "print(f\" Extracting to {PDF_DIR} ...\")\n", + "print(\" (This may take a few minutes for large archives)\")\n", + "\n", + "start = time.time()\n", + "with zipfile.ZipFile(ZIP_DRIVE_PATH, 'r') as zf:\n", + " zf.extractall(PDF_DIR)\n", + "elapsed = time.time() - start\n", + "\n", + "# Count extracted PDFs\n", + "pdf_count = sum(\n", + " 1 for root, _, files in os.walk(PDF_DIR)\n", + " for f in files if f.lower().endswith('.pdf')\n", + ")\n", + "\n", + "print(f\"\\nโœ… Extraction complete in {elapsed:.1f}s\")\n", + "print(f\" Found {pdf_count} PDF files\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ” Step 5 โ€” PDF Ingestion Pipeline (GPU-Accelerated)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import fitz # PyMuPDF\n", + "from langchain.text_splitter import RecursiveCharacterTextSplitter\n", + "from langchain_community.vectorstores import FAISS\n", + "from langchain_huggingface import HuggingFaceEmbeddings\n", + "from langchain.docstore.document import Document\n", + "\n", + "\n", + "def collect_pdf_paths(pdf_dir):\n", + " \"\"\"Recursively collect all PDF paths.\"\"\"\n", + " paths = []\n", + " for root, _, files in os.walk(pdf_dir):\n", + " for f in files:\n", + " if f.lower().endswith('.pdf'):\n", + " paths.append(os.path.join(root, f))\n", + " return sorted(paths)\n", + "\n", + "\n", + "def extract_text(pdf_path):\n", + " \"\"\"Extract text from a PDF using PyMuPDF.\"\"\"\n", + " text = \"\"\n", + " try:\n", + " doc = fitz.open(pdf_path)\n", + " for page in doc:\n", + " text += page.get_text()\n", + " doc.close()\n", + " except Exception as e:\n", + " print(f\" โš  Error reading {os.path.basename(pdf_path)}: {e}\")\n", + " return text\n", + "\n", + "\n", + "def build_documents(pdf_paths):\n", + " \"\"\"Extract and chunk all PDFs into LangChain Documents.\"\"\"\n", + " splitter = RecursiveCharacterTextSplitter(\n", + " chunk_size=CHUNK_SIZE,\n", + " chunk_overlap=CHUNK_OVERLAP,\n", + " separators=[\"\\n\\n\", \"\\n\", \". \", \" \", \"\"],\n", + " )\n", + " all_docs = []\n", + " skipped = 0\n", + "\n", + " for i, path in enumerate(pdf_paths, 1):\n", + " rel = os.path.relpath(path, PDF_DIR)\n", + " parts = rel.replace(\"\\\\\", \"/\").split(\"/\")\n", + " subject = parts[0] if len(parts) > 1 else \"General\"\n", + "\n", + " if i % 50 == 0 or i == 1:\n", + " print(f\" [{i}/{len(pdf_paths)}] {rel}\")\n", + "\n", + " text = extract_text(path)\n", + " if not text.strip():\n", + " skipped += 1\n", + " continue\n", + "\n", + " chunks = splitter.create_documents(\n", + " texts=[text],\n", + " metadatas=[{\"source\": rel, \"filename\": os.path.basename(path), \"subject\": subject}]\n", + " )\n", + " all_docs.extend(chunks)\n", + "\n", + " print(f\"\\n Skipped (image/scanned PDFs): {skipped}\")\n", + " return all_docs\n", + "\n", + "\n", + "# โ”€โ”€ Step 5a: Collect PDFs โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "print(\"=\" * 60)\n", + "print(\"๐Ÿ“„ PHASE 1: Collecting & chunking PDFs\")\n", + "print(\"=\" * 60)\n", + "pdf_paths = collect_pdf_paths(PDF_DIR)\n", + "print(f\" {len(pdf_paths)} PDFs found\")\n", + "\n", + "t0 = time.time()\n", + "documents = build_documents(pdf_paths)\n", + "t_extract = time.time() - t0\n", + "\n", + "print(f\"\\nโœ… Created {len(documents)} chunks in {t_extract:.1f}s\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Step 5b: Load Embedding Model onto T4 GPU โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "print(\"=\" * 60)\n", + "print(f\"๐Ÿง  PHASE 2: Loading embedding model on {EMBEDDING_DEVICE.upper()}\")\n", + "print(\"=\" * 60)\n", + "\n", + "import torch\n", + "device = EMBEDDING_DEVICE if torch.cuda.is_available() else \"cpu\"\n", + "if device == \"cpu\" and EMBEDDING_DEVICE == \"cuda\":\n", + " print(\"โš ๏ธ CUDA not available, falling back to CPU\")\n", + "\n", + "embeddings = HuggingFaceEmbeddings(\n", + " model_name=EMBEDDING_MODEL,\n", + " model_kwargs={\"device\": device},\n", + " encode_kwargs={\"normalize_embeddings\": True, \"batch_size\": 256}, # Larger batch on GPU!\n", + ")\n", + "\n", + "print(f\"โœ… Embedding model loaded on {device.upper()}\")\n", + "print(f\" Model: {EMBEDDING_MODEL}\")\n", + "print(f\" Batch size: 256 (optimized for T4)\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Step 5c: Build FAISS Vector Store โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "print(\"=\" * 60)\n", + "print(f\"๐Ÿ“Š PHASE 3: Building FAISS vector store ({len(documents)} chunks)\")\n", + "print(\"=\" * 60)\n", + "\n", + "BATCH_SIZE = 1000 # Larger batches since we have GPU\n", + "vector_store = None\n", + "t0 = time.time()\n", + "\n", + "total_batches = (len(documents) + BATCH_SIZE - 1) // BATCH_SIZE\n", + "for i in range(0, len(documents), BATCH_SIZE):\n", + " batch = documents[i : i + BATCH_SIZE]\n", + " batch_num = (i // BATCH_SIZE) + 1\n", + "\n", + " elapsed = time.time() - t0\n", + " rate = (i + 1) / elapsed if elapsed > 0 else 0\n", + " eta = (len(documents) - i) / rate if rate > 0 else 0\n", + " print(f\" Batch {batch_num:3d}/{total_batches} | {len(batch)} chunks | \"\n", + " f\"ETA: {eta/60:.1f} min\")\n", + "\n", + " if vector_store is None:\n", + " vector_store = FAISS.from_documents(batch, embeddings)\n", + " else:\n", + " batch_store = FAISS.from_documents(batch, embeddings)\n", + " vector_store.merge_from(batch_store)\n", + "\n", + "t_embed = time.time() - t0\n", + "print(f\"\\nโœ… Vector store built in {t_embed/60:.1f} minutes\")\n", + "print(f\" Total vectors: {vector_store.index.ntotal}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿ’พ Step 6 โ€” Save Vector Store to Google Drive" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shutil\n", + "\n", + "# Save locally first\n", + "print(\"๐Ÿ’พ Saving vector store locally...\")\n", + "vector_store.save_local(VECTOR_STORE_LOCAL)\n", + "print(f\" โœ… Saved to {VECTOR_STORE_LOCAL}\")\n", + "\n", + "# Copy to Google Drive for persistence\n", + "print(\"\\nโ˜๏ธ Copying to Google Drive...\")\n", + "os.makedirs(VECTOR_STORE_DRIVE_PATH, exist_ok=True)\n", + "\n", + "for fname in os.listdir(VECTOR_STORE_LOCAL):\n", + " src = os.path.join(VECTOR_STORE_LOCAL, fname)\n", + " dst = os.path.join(VECTOR_STORE_DRIVE_PATH, fname)\n", + " shutil.copy2(src, dst)\n", + " size = os.path.getsize(dst) / 1e6\n", + " print(f\" โœ… {fname} ({size:.1f} MB)\")\n", + "\n", + "print(f\"\\n๐ŸŽ‰ Vector store saved to Google Drive!\")\n", + "print(f\" Path: {VECTOR_STORE_DRIVE_PATH}\")\n", + "print(f\" Total vectors: {vector_store.index.ntotal}\")\n", + "print(\"\\n๐Ÿ“Œ You can now download this folder to your laptop\")\n", + "print(\" and place it at: Study_Ocean Rag/data/vector_store/\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿงช Step 7 โ€” Test the Vector Store (Quick Sanity Check)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Quick retrieval test โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "test_question = \"What is Newton's second law of motion?\"\n", + "print(f\"๐Ÿ” Test query: '{test_question}'\\n\")\n", + "\n", + "results = vector_store.similarity_search(test_question, k=3)\n", + "for i, doc in enumerate(results, 1):\n", + " print(f\"--- Result {i} ---\")\n", + " print(f\"Subject : {doc.metadata.get('subject', 'N/A')}\")\n", + " print(f\"File : {doc.metadata.get('filename', 'N/A')}\")\n", + " print(f\"Preview : {doc.page_content[:200]}...\")\n", + " print()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿค– Step 8 โ€” Build the RAG Chain (Gemini + FAISS)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from langchain_google_genai import ChatGoogleGenerativeAI\n", + "from langchain.chains import RetrievalQA\n", + "from langchain.prompts import PromptTemplate\n", + "\n", + "# Validate API key\n", + "if not GOOGLE_API_KEY or GOOGLE_API_KEY == \"\":\n", + " print(\"โŒ GOOGLE_API_KEY is not set! Go back to Step 3 and paste your key.\")\n", + " raise ValueError(\"Missing GOOGLE_API_KEY\")\n", + "\n", + "# โ”€โ”€ Student Mentor Prompt โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "MENTOR_PROMPT = PromptTemplate(\n", + " input_variables=[\"context\", \"question\"],\n", + " template=\"\"\"You are **Study Ocean AI Mentor** โ€” a friendly, patient, and knowledgeable tutor who helps students understand their study material deeply.\n", + "\n", + "Your role:\n", + "- Solve student doubts with clear, step-by-step explanations\n", + "- Use simple language that students can easily understand\n", + "- Provide examples and analogies when helpful\n", + "- If the answer is in the context, use it; if not, say so honestly\n", + "- Encourage students and make learning enjoyable\n", + "\n", + "**Guidelines:**\n", + "1. Start with a brief, direct answer, then explain in detail\n", + "2. Use bullet points, numbered steps, or tables for clarity\n", + "3. For math/science problems, show the solution step by step\n", + "4. Mention which topic/chapter the information relates to when possible\n", + "5. If the topic isn't covered, say: \"This isn't in your study materials, but here's what I know...\"\n", + "\n", + "---\n", + "๐Ÿ“š **Relevant Study Material:**\n", + "{context}\n", + "\n", + "---\n", + "๐Ÿ™‹ **Student's Question:** {question}\n", + "\n", + "---\n", + "๐ŸŽ“ **Mentor's Response:**\n", + "\"\"\"\n", + ")\n", + "\n", + "# โ”€โ”€ Initialize LLM โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "llm = ChatGoogleGenerativeAI(\n", + " model=LLM_MODEL,\n", + " google_api_key=GOOGLE_API_KEY,\n", + " temperature=0.3,\n", + " max_output_tokens=2048,\n", + " convert_system_message_to_human=True,\n", + ")\n", + "\n", + "# โ”€โ”€ Build RAG Chain โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "retriever = vector_store.as_retriever(\n", + " search_type=\"similarity\",\n", + " search_kwargs={\"k\": TOP_K},\n", + ")\n", + "\n", + "rag_chain = RetrievalQA.from_chain_type(\n", + " llm=llm,\n", + " chain_type=\"stuff\",\n", + " retriever=retriever,\n", + " return_source_documents=True,\n", + " chain_type_kwargs={\"prompt\": MENTOR_PROMPT},\n", + ")\n", + "\n", + "print(\"โœ… RAG Chain ready!\")\n", + "print(f\" LLM: {LLM_MODEL}\")\n", + "print(f\" Retriever: top-{TOP_K} chunks\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐Ÿงช Step 9 โ€” Test the Full RAG Pipeline" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def ask(question):\n", + " \"\"\"Ask the mentor a question.\"\"\"\n", + " print(f\"\\n๐Ÿ™‹ Question: {question}\")\n", + " print(\"๐Ÿ” Searching study material...\\n\")\n", + " result = rag_chain.invoke({\"query\": question})\n", + " print(\"๐ŸŽ“ Mentor's Answer:\")\n", + " print(\"โ”€\" * 60)\n", + " print(result[\"result\"])\n", + " print(\"โ”€\" * 60)\n", + " print(\"\\n๐Ÿ“– Sources:\")\n", + " seen = set()\n", + " for doc in result.get(\"source_documents\", []):\n", + " key = doc.metadata.get(\"source\", \"?\")\n", + " if key not in seen:\n", + " seen.add(key)\n", + " print(f\" [{doc.metadata.get('subject','?')}] {doc.metadata.get('filename','?')}\")\n", + "\n", + "# Test it!\n", + "ask(\"Explain Newton's laws of motion with examples\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## ๐ŸŒ Step 10 โ€” Launch Gradio Chat UI (Public Link)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import gradio as gr\n", + "\n", + "CUSTOM_CSS = \"\"\"\n", + ".gradio-container {\n", + " max-width: 900px !important;\n", + " margin: auto !important;\n", + " font-family: 'Inter', 'Segoe UI', sans-serif !important;\n", + "}\n", + ".header-section {\n", + " text-align: center;\n", + " padding: 20px 0 10px;\n", + " background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n", + " border-radius: 16px;\n", + " margin-bottom: 20px;\n", + " color: white;\n", + "}\n", + ".header-section h1 { font-size: 2em; margin: 0; color: white !important; }\n", + ".header-section p { opacity: 0.9; margin: 5px 0 0; font-size: 1.05em; color: #e8e8ff !important; }\n", + ".chatbot { border-radius: 12px !important; min-height: 450px !important; }\n", + ".primary-btn {\n", + " background: linear-gradient(135deg, #667eea, #764ba2) !important;\n", + " border: none !important; color: white !important;\n", + " border-radius: 10px !important; font-weight: 600 !important;\n", + "}\n", + ".primary-btn:hover { opacity: 0.9 !important; transform: translateY(-1px); }\n", + "\"\"\"\n", + "\n", + "def format_sources(docs):\n", + " if not docs: return \"\"\n", + " seen, lines = set(), [\"\\n\\n---\\n๐Ÿ“– **Sources Referenced:**\\n\"]\n", + " for doc in docs:\n", + " key = f\"{doc.metadata.get('subject','?')}/{doc.metadata.get('source','?')}\"\n", + " if key not in seen:\n", + " seen.add(key)\n", + " lines.append(f\"**โ€ข** `{doc.metadata.get('subject','?')}` โ†’ _{doc.metadata.get('filename','?')}_\")\n", + " return \"\\n\".join(lines)\n", + "\n", + "def respond(message, history):\n", + " if not message.strip():\n", + " return \"\", history\n", + " result = rag_chain.invoke({\"query\": message})\n", + " answer = result[\"result\"] + format_sources(result.get(\"source_documents\", []))\n", + " history.append((message, answer))\n", + " return \"\", history\n", + "\n", + "def clear_chat():\n", + " return [], \"\"\n", + "\n", + "with gr.Blocks(css=CUSTOM_CSS, title=\"Study Ocean AI Mentor\", theme=gr.themes.Soft()) as demo:\n", + " gr.HTML(\"\"\"\n", + "
\n", + "

๐ŸŽ“ Study Ocean AI Mentor

\n", + "

Ask any doubt from your study material โ€” I'll explain it step by step!

\n", + "
\n", + " \"\"\")\n", + " chatbot = gr.Chatbot(\n", + " label=\"๐Ÿ’ฌ Chat with Your Mentor\",\n", + " elem_classes=[\"chatbot\"],\n", + " height=480,\n", + " show_copy_button=True,\n", + " bubble_full_width=False,\n", + " avatar_images=(None, \"https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1\"),\n", + " )\n", + " with gr.Row():\n", + " msg = gr.Textbox(\n", + " placeholder=\"Type your doubt here... e.g., 'Explain Newton's third law'\",\n", + " scale=5, lines=1, max_lines=3, show_label=False, container=False,\n", + " )\n", + " submit_btn = gr.Button(\"Ask ๐Ÿš€\", elem_classes=[\"primary-btn\"], scale=1)\n", + " with gr.Row():\n", + " clear_btn = gr.Button(\"๐Ÿ—‘๏ธ Clear Chat\", size=\"sm\")\n", + "\n", + " gr.Examples(\n", + " examples=[\n", + " \"What are Newton's laws of motion? Explain with examples.\",\n", + " \"Explain the difference between mitosis and meiosis.\",\n", + " \"What is the Pythagorean theorem and how do I apply it?\",\n", + " \"Explain the concept of supply and demand in economics.\",\n", + " \"What are the key features of the Indian Constitution?\",\n", + " ],\n", + " inputs=msg, label=\"๐Ÿ’ก Try these example questions:\",\n", + " )\n", + "\n", + " msg.submit(respond, [msg, chatbot], [msg, chatbot])\n", + " submit_btn.click(respond, [msg, chatbot], [msg, chatbot])\n", + " clear_btn.click(clear_chat, outputs=[chatbot, msg])\n", + "\n", + "# Launch with share=True to get a public Gradio URL!\n", + "demo.launch(share=True, debug=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## ๐Ÿ’ป Step 11 โ€” Use Pre-built Vector Store (Skip Re-ingestion)\n", + "\n", + "> **Run this cell INSTEAD of Steps 4โ€“6 if you already have a saved vector store on Drive**\n", + "> This lets you skip the ~1 hour ingestion on future sessions!" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# โ”€โ”€ Load existing vector store from Drive โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\n", + "# โš ๏ธ Only run this cell if you've already done ingestion before!\n", + "# Skip this if running ingestion fresh (Steps 4-6).\n", + "\n", + "from langchain_community.vectorstores import FAISS\n", + "from langchain_huggingface import HuggingFaceEmbeddings\n", + "import torch, os\n", + "\n", + "device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n", + "print(f\"๐Ÿ”ง Loading embeddings on {device.upper()}...\")\n", + "\n", + "embeddings = HuggingFaceEmbeddings(\n", + " model_name=EMBEDDING_MODEL,\n", + " model_kwargs={\"device\": device},\n", + " encode_kwargs={\"normalize_embeddings\": True},\n", + ")\n", + "\n", + "# Try Drive path first, fall back to local\n", + "store_path = VECTOR_STORE_DRIVE_PATH if os.path.exists(VECTOR_STORE_DRIVE_PATH) else VECTOR_STORE_LOCAL\n", + "print(f\"๐Ÿ“‚ Loading vector store from: {store_path}\")\n", + "\n", + "vector_store = FAISS.load_local(\n", + " store_path,\n", + " embeddings,\n", + " allow_dangerous_deserialization=True,\n", + ")\n", + "\n", + "print(f\"โœ… Loaded! Total vectors: {vector_store.index.ntotal}\")\n", + "print(\" Now run Steps 8 โ†’ 10 to start chatting!\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "## ๐Ÿ“ฅ Step 12 โ€” Download Vector Store to Your Laptop\n", + "\n", + "> Run this to zip and download the vector store directly to your PC (no Drive needed)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import shutil\n", + "from google.colab import files\n", + "\n", + "# Zip the vector store\n", + "print(\"๐Ÿ“ฆ Zipping vector store...\")\n", + "zip_path = \"/content/vector_store_export.zip\"\n", + "shutil.make_archive(\"/content/vector_store_export\", 'zip', VECTOR_STORE_LOCAL)\n", + "\n", + "size_mb = os.path.getsize(zip_path) / 1e6\n", + "print(f\" ZIP size: {size_mb:.1f} MB\")\n", + "\n", + "# Download to local machine\n", + "print(\"โฌ‡๏ธ Downloading to your computer...\")\n", + "files.download(zip_path)\n", + "print(\"\\nโœ… Download started!\")\n", + "print(\" Extract to: Study_Ocean Rag/data/vector_store/\")\n", + "print(\" Then run locally: python main.py chat\")" + ] + } + ] +} diff --git a/api.py b/api.py new file mode 100644 index 0000000000000000000000000000000000000000..ca5b84551bd279d4d503d02b298cfa5e90a2da03 --- /dev/null +++ b/api.py @@ -0,0 +1,65 @@ +import os + +# memory saving trick for Render 512MB limit! +os.environ["OMP_NUM_THREADS"] = "1" +os.environ["MKL_NUM_THREADS"] = "1" +os.environ["OPENBLAS_NUM_THREADS"] = "1" +os.environ["VECLIB_MAXIMUM_THREADS"] = "1" +os.environ["NUMEXPR_NUM_THREADS"] = "1" + +from fastapi import FastAPI +from fastapi.middleware.cors import CORSMiddleware +from fastapi.responses import FileResponse +from pydantic import BaseModel +from rag_chain import get_chain, ask_question +import uvicorn + + +app = FastAPI(title="Study Ocean RAG API") + +# Allow requests from your frontend (CORS) +app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # In production, replace with your frontend URL + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], +) + +@app.get("/") +def serve_frontend(): + return FileResponse("frontend_demo.html") + +@app.get("/logo.png") +def serve_logo(): + if os.path.exists("logo.png"): + return FileResponse("logo.png") + return {"error": "Logo not found"} + +print("๐Ÿ”„ Initializing Study Ocean AI Mentor...") +chain = get_chain() +print("โœ… API Ready to answer student doubts!") + +class Query(BaseModel): + question: str + +@app.post("/api/ask") +def ask(query: Query): + try: + # Pass the question to your LangChain RAG + result = ask_question(chain, query.question) + return { + "answer": result["answer"], + "sources": result["sources"] + } + except Exception as e: + import traceback + traceback.print_exc() + return { + "answer": f"Backend Error: {str(e)}", + "sources": [] + } + +if __name__ == "__main__": + # Run the API on port 8000 + uvicorn.run(app, host="0.0.0.0", port=8000) diff --git a/app.py b/app.py new file mode 100644 index 0000000000000000000000000000000000000000..da4c4b1cd852d2dc533aa93c85dc69dc3911e99f --- /dev/null +++ b/app.py @@ -0,0 +1,203 @@ +""" +Study Ocean AI Mentor โ€” Gradio Web Interface + +A beautiful, student-friendly chat interface for the RAG-based +doubt-solving mentor. Built with Gradio for easy deployment. +""" + +import gradio as gr +from rag_chain import get_chain, ask_question + +# โ”€โ”€โ”€ Custom CSS for Premium Look โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +CUSTOM_CSS = """ +/* Global */ +.gradio-container { + max-width: 900px !important; + margin: auto !important; + font-family: 'Inter', 'Segoe UI', sans-serif !important; +} + +/* Header */ +.header-section { + text-align: center; + padding: 20px 0 10px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 16px; + margin-bottom: 20px; + color: white; +} + +.header-section h1 { + font-size: 2em; + margin: 0; + color: white !important; +} + +.header-section p { + opacity: 0.9; + margin: 5px 0 0; + font-size: 1.05em; + color: #e8e8ff !important; +} + +/* Chat area */ +.chatbot { + border-radius: 12px !important; + border: 1px solid #e2e8f0 !important; + min-height: 450px !important; +} + +/* Source cards */ +.source-card { + background: #f8fafc; + border: 1px solid #e2e8f0; + border-radius: 8px; + padding: 10px 14px; + margin: 4px 0; + font-size: 0.9em; +} + +.source-card strong { + color: #6366f1; +} + +/* Override dark mode */ +.dark .source-card { + background: #1e293b; + border-color: #334155; +} + +/* Buttons */ +.primary-btn { + background: linear-gradient(135deg, #667eea, #764ba2) !important; + border: none !important; + color: white !important; + border-radius: 10px !important; + font-weight: 600 !important; + padding: 10px 24px !important; +} + +.primary-btn:hover { + opacity: 0.9 !important; + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4) !important; +} +""" + +# โ”€โ”€โ”€ Initialize chain on startup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +print("๐Ÿ”„ Initializing Study Ocean AI Mentor...") +chain = get_chain() +print("โœ… Ready to answer student doubts!\n") + + +def format_sources(sources: list) -> str: + """Format source documents into a readable string.""" + if not sources: + return "" + + lines = ["\n\n---\n๐Ÿ“– **Sources Referenced:**\n"] + for i, src in enumerate(sources, 1): + lines.append(f"**{i}.** `{src['subject']}` โ†’ _{src['file']}_") + return "\n".join(lines) + + +def respond(message: str, chat_history: list) -> tuple: + """Handle a student's question and return the mentor's response.""" + if not message.strip(): + return "", chat_history + + # Get answer from RAG chain + result = ask_question(chain, message) + answer = result["answer"] + sources = result["sources"] + + # Append source references to the answer + answer_with_sources = answer + format_sources(sources) + + # Update chat history + chat_history.append((message, answer_with_sources)) + + return "", chat_history + + +def clear_chat(): + """Clear the chat history.""" + return [], "" + + +# โ”€โ”€โ”€ Build Gradio Interface โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +with gr.Blocks(title="Study Ocean AI Mentor") as app: + + # Header + gr.HTML(""" +
+

๐ŸŽ“ Study Ocean AI Mentor

+

Ask any doubt from your study material โ€” I'll explain it step by step!

+
+ """) + + # Chatbot + chatbot = gr.Chatbot( + label="๐Ÿ’ฌ Chat with Your Mentor", + elem_classes=["chatbot"], + height=480, + avatar_images=(None, "https://api.dicebear.com/7.x/bottts/svg?seed=mentor&backgroundColor=6366f1"), + ) + + # Input area + with gr.Row(): + msg = gr.Textbox( + placeholder="Type your doubt here... e.g., 'Explain Newton's third law with examples'", + label="Your Question", + scale=5, + lines=1, + max_lines=3, + show_label=False, + container=False, + ) + submit_btn = gr.Button("Ask ๐Ÿš€", elem_classes=["primary-btn"], scale=1) + + # Action buttons + with gr.Row(): + clear_btn = gr.Button("๐Ÿ—‘๏ธ Clear Chat", size="sm") + + # Example questions + gr.Examples( + examples=[ + "What are Newton's laws of motion? Explain with examples.", + "Explain the difference between mitosis and meiosis.", + "What is the Pythagorean theorem and how do I use it?", + "Explain the concept of supply and demand in economics.", + "What are the key features of the Indian Constitution?", + "Solve: Find the derivative of f(x) = 3xยฒ + 2x - 5", + ], + inputs=msg, + label="๐Ÿ’ก Try these example questions:", + ) + + # Tips section + with gr.Accordion("๐Ÿ“Œ Tips for Better Answers", open=False): + gr.Markdown(""" + - **Be specific**: Instead of "explain physics", ask "explain projectile motion with examples" + - **Mention the topic**: "In organic chemistry, what is an SN2 reaction?" + - **Ask for steps**: "Solve step by step: integrate sin(x)cos(x) dx" + - **Request comparisons**: "Compare TCP and UDP protocols" + - **Ask for formulas**: "List all the trigonometric identities" + """) + + # Event handlers + msg.submit(respond, [msg, chatbot], [msg, chatbot]) + submit_btn.click(respond, [msg, chatbot], [msg, chatbot]) + clear_btn.click(clear_chat, outputs=[chatbot, msg]) + + +# โ”€โ”€โ”€ Launch โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +if __name__ == "__main__": + app.launch( + server_name="0.0.0.0", + server_port=7860, + share=False, + show_error=True, + css=CUSTOM_CSS, + theme=gr.themes.Soft(), + ) diff --git a/config.py b/config.py new file mode 100644 index 0000000000000000000000000000000000000000..cfd229319f0d971a476887cf73ceab951bc92a82 --- /dev/null +++ b/config.py @@ -0,0 +1,30 @@ +""" +Configuration module for Study Ocean RAG Student Mentor. +Loads environment variables and provides default settings. +""" + +import os +from dotenv import load_dotenv + +load_dotenv() + +# โ”€โ”€โ”€ API Keys โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "") + +# โ”€โ”€โ”€ Model Configuration โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "all-MiniLM-L6-v2") +LLM_MODEL = os.getenv("LLM_MODEL", "gemini-2.0-flash") + +# โ”€โ”€โ”€ Paths โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +BASE_DIR = os.path.dirname(os.path.abspath(__file__)) +PDF_DIR = os.path.join(BASE_DIR, os.getenv("PDF_DIR", "data/pdfs")) +VECTOR_STORE_PATH = os.path.join(BASE_DIR, os.getenv("VECTOR_STORE_PATH", "data/vector_store")) + +# โ”€โ”€โ”€ Text Splitting โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +CHUNK_SIZE = int(os.getenv("CHUNK_SIZE", "1000")) +CHUNK_OVERLAP = int(os.getenv("CHUNK_OVERLAP", "200")) + +# โ”€โ”€โ”€ RAG Parameters โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +TOP_K_RESULTS = 2 # Number of relevant chunks to retrieve (lowered for faster responses) +TEMPERATURE = 0.3 # LLM temperature (lower = more focused) +MAX_OUTPUT_TOKENS = 2048 # Max tokens in LLM response diff --git a/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf b/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf new file mode 100644 index 0000000000000000000000000000000000000000..b3cb23e88bb293c7997a47761ad4f2591e2c57fc --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/BIOLOGY_FLASHCARD_FOR_NEET.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbfe25b3be671a226d8d27b3eb34fb60911846b5918ac78e72aed009b7137092 +size 36021223 diff --git a/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf b/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf new file mode 100644 index 0000000000000000000000000000000000000000..e85b05121d4f854192e01250cca93f1c557def67 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/CBSE_2026_Class_12_Physics_Set_2_Question_Paper_With_Solutions_PDF.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ee3feeb5e2c6b2fb6eb414e9d4cc11c008a4e7ff53564f60f0c88bdfe66c705 +size 561121 diff --git a/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF b/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF new file mode 100644 index 0000000000000000000000000000000000000000..c1c3a272bb1acc2e8211096f7d0265a606e41a1e --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/CENGAGE_IOC_CRUX_BY_studyoceanbooks.PDF @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1fe208f6066c088482bfb970f53d969c67045459cd7fde203f2cdc1bd421aeda +size 6955370 diff --git a/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf b/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7dbbc7dfec4a29b3ec41c0dc136f8d4de02fe476 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/COM_COLLISION_Short_Notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac84e71f21e1293fbb3463f89d43b6cfb6f21d02995eca69f8febb80170f8031 +size 1507057 diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf new file mode 100644 index 0000000000000000000000000000000000000000..44e888d28b13161ec02cc458639b50170120398b --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Chemistry_Flash_Cards_Colourful_Complete.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:660f51ef507ec000388389a1660387d35d3a2f897807fa496a93a89394eeb187 +size 9544339 diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf new file mode 100644 index 0000000000000000000000000000000000000000..424e1ce31c316796edcfac39fdcec2a79b5b6f9a --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Chemistry_Flashcard_for_JEE_NEET.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:467be87f0dc909fc49f89786b890fc99e1c0b858c22ebe46b548fdebea80a77e +size 10429728 diff --git a/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf b/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3f5a551c49e84fcab7b35b43b6d57857e8066d5b --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Chemistry_Formula_Booklet_Hashlearn.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40214c132da21dffb625a043e616ac13b829a789c8f74ba3d1e12bfc0145554b +size 6484359 diff --git a/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..5fae94e5edc85f7dda0c2ce5a4613de18d608cc8 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Circular_dyanamic_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:87714aff5c3b6135f26fb58c59ef472fd1f4f7ff0985477513fd20b2cc0d3c11 +size 456696 diff --git a/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..07561a7612c863d320a63524c0df8157c1152755 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Circular_motion_in_kinematics_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14e351fa21a2cdfc7bad9177967118c7d153f32e7637cff0381d2c67c6c96604 +size 1128082 diff --git a/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..10b7998185ba4d6fee64062fbf2564caa5a21843 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Rotational_motion_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6b5ff18923f0413d3470b1c28186194938b9e2d74d336c5e42252ad22d38628 +size 2888938 diff --git a/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf b/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3f668295a0f6db2c6bbb3ab505b8959016f8b4f2 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Thermal_properties_of_matter_short_notes_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d4beba58867ed59e25b8a15193f991fb34fedbec21e20797cec6ac82450bc44 +size 2325417 diff --git a/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..081f59f4f6452f7a70adee290b5f9911ed39ff3b --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Units_measurements_and_errors_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2ed834b2629337279ff4793e8d771311ae2c110d3b6a4fd36db8a1a9ea0cf58c +size 2533427 diff --git a/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..98ddb4912038d1ec9e6f64307515a23629075d2f --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Vertical_circular_motion_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e5710614b9d68770e10f4792a0f8fbf3cf99a4af569cab6a094d9d555abbd508 +size 716852 diff --git a/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..44ebc3aa188913a940cbfeaedeec745637ed2773 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Wave_motion_short_notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3200903717291d721e29e4aba296c2493351c083b0b9675ff2dfc0e34e82a2a2 +size 2288200 diff --git a/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf b/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..359651dadeb9f497b0d30980e795799f95db829f --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/Work_Power_Energy_Short_Notes_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f6f14e982aff842c61a2f159aaf9a044742cd0aa3d07a7f721e3d08a7d7e20f +size 2026851 diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf new file mode 100644 index 0000000000000000000000000000000000000000..a0b2519ef00ee4ddf32b6a3e226c3bd04e6e87e1 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_1_question_paper_2026_1771575627702.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ec9e62a8630a7a359f36927f3b3fd8ec0b933a665b4d1c9935a81bbcadb2f753 +size 2405782 diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf new file mode 100644 index 0000000000000000000000000000000000000000..2b3d893fd14c2a32d633bc7c0a464789cbe5aa7a --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_2_question_paper_2026_1771575619105.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:862f05a505453e84746b31e88fd4d42dad7cbf518a985ae974f103c4f7dba24e +size 2364093 diff --git a/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cce63f35968b390b927fb98716ab9b57ced23fe8 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/cbse_class_12_physics_set_3_question_paper_2026_1771575610905.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2f3a677b0c4457ff58985f9dc6a56c65f294fe4bacfc420872774c81b71b47b6 +size 2475777 diff --git a/data/pdfs/StudyOceanPDFs/chapter_10.pdf b/data/pdfs/StudyOceanPDFs/chapter_10.pdf new file mode 100644 index 0000000000000000000000000000000000000000..34b776cb9adcba3409cacdb6678e60fbb7298df0 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_10.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:60502d7ef8f74d65cdef3b847d6610f4562e60cae90717a982a800785c6510f0 +size 1852257 diff --git a/data/pdfs/StudyOceanPDFs/chapter_11.pdf b/data/pdfs/StudyOceanPDFs/chapter_11.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9393807bf555d918fbd26391eb83f37ce05ac4f4 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_11.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f79bb617255b2a183aabf9ff4a34fc27afe91797ed80f098a1e6b4c767bf879f +size 1842601 diff --git a/data/pdfs/StudyOceanPDFs/chapter_12.pdf b/data/pdfs/StudyOceanPDFs/chapter_12.pdf new file mode 100644 index 0000000000000000000000000000000000000000..bd669f2628bec97f88401c6e83d5942e767e5a99 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_12.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4d33faddf847f9c6f05f32143f84ed7038fe3d2bf4f9462a07c7083b18b30258 +size 4467748 diff --git a/data/pdfs/StudyOceanPDFs/chapter_13.pdf b/data/pdfs/StudyOceanPDFs/chapter_13.pdf new file mode 100644 index 0000000000000000000000000000000000000000..17eef160f11cb8ec6c2f71ef56caa93f808c2c13 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_13.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8198c5c589362a88b1dbf5d39b37c1faa0eebbec02d14a039d8146563a4cadf5 +size 3530630 diff --git a/data/pdfs/StudyOceanPDFs/chapter_14.pdf b/data/pdfs/StudyOceanPDFs/chapter_14.pdf new file mode 100644 index 0000000000000000000000000000000000000000..692b61e49c5b26c12715069421cb5514fa4b0b5b --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_14.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:856a0e87419c68485bd1b852dedfdb19fdef9017ad278086b20f1485addb6950 +size 3381393 diff --git a/data/pdfs/StudyOceanPDFs/chapter_15.pdf b/data/pdfs/StudyOceanPDFs/chapter_15.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3826aab35e5c00eaad8e0feec07b959d9ddf08e4 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_15.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:40fa8f886030c1fa244779661159cacd06763d252e0f13d2ffd39000b856ee64 +size 2954844 diff --git a/data/pdfs/StudyOceanPDFs/chapter_2.pdf b/data/pdfs/StudyOceanPDFs/chapter_2.pdf new file mode 100644 index 0000000000000000000000000000000000000000..0cd396dec4c14bbd209fc7857b1c4fea254a0fcf --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_2.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:563cf1492644cf09e14573744e176230e1157a75bcbeeabe118d9ac9acc5e959 +size 1577447 diff --git a/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf b/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3c20167bd069b3dde3d5206b385bcd3305084ebd --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_3_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:93f99211db8fa7628a1cf092bc3b19f98124ae34208c30d0a99bb359788fbe2e +size 2022459 diff --git a/data/pdfs/StudyOceanPDFs/chapter_4.pdf b/data/pdfs/StudyOceanPDFs/chapter_4.pdf new file mode 100644 index 0000000000000000000000000000000000000000..7c583a636025f590007b78cdec2e008e650cba3a --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_4.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b26907e1916bf831705c9cfcfb32c1d8c92faee0180b107947be830f1116cdde +size 1700543 diff --git a/data/pdfs/StudyOceanPDFs/chapter_5.pdf b/data/pdfs/StudyOceanPDFs/chapter_5.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3c56f2e76407db6f30f826d33254e83d857af88c --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_5.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8c5c402f9d758771f4e22dc3f01cd6fa61c1c5b56a83c1210d2929bba739486f +size 987219 diff --git a/data/pdfs/StudyOceanPDFs/chapter_6.pdf b/data/pdfs/StudyOceanPDFs/chapter_6.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9c92c4394c3177ea9ce2ed85ee66e4aa0474e4c2 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_6.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:592a39941a139568374430fbaac2a277932baecc79b742937662a3087b7961df +size 3612728 diff --git a/data/pdfs/StudyOceanPDFs/chapter_7.pdf b/data/pdfs/StudyOceanPDFs/chapter_7.pdf new file mode 100644 index 0000000000000000000000000000000000000000..cb177193e1361703539d54fb923203dda95bae84 --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/chapter_7.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dbc4c8dbcbbf29a76295581a9d4f1e9084a6b7cfb5726af5559d8d3f34802ac8 +size 1028339 diff --git a/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf b/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf new file mode 100644 index 0000000000000000000000000000000000000000..c203f88d49a50a3ac409bc2166cc3c77b6ab0d6c --- /dev/null +++ b/data/pdfs/StudyOceanPDFs/vector_sheets_Cyan_notes_1_1_.pdf @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c2ab1f57ee66147454fcae197dadbfb8e4d718bb17eece72eb5d257ec97abce +size 903709 diff --git a/data/vector_store/index.faiss b/data/vector_store/index.faiss new file mode 100644 index 0000000000000000000000000000000000000000..87fe646fd61ef5a765c644d90462d6d88cf5f933 --- /dev/null +++ b/data/vector_store/index.faiss @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eabc69fd7afeee324561ec01698a1fbe0464ab7cade2790647f67b504074664a +size 18837549 diff --git a/data/vector_store/index.pkl b/data/vector_store/index.pkl new file mode 100644 index 0000000000000000000000000000000000000000..42ae991c1a0baf10d0274693b14b062fa9d5b80b --- /dev/null +++ b/data/vector_store/index.pkl @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e0a5e3e437a7b95670e59ebd02a2d16d648b1f0cd463c61369ff0bc910059b03 +size 10367491 diff --git a/frontend_demo.html b/frontend_demo.html new file mode 100644 index 0000000000000000000000000000000000000000..08da11fdfbede2da5b2f00f011d776e54c0ce597 --- /dev/null +++ b/frontend_demo.html @@ -0,0 +1,267 @@ + + + + + + Study Ocean | AI Mentor + + + + + + + + + + + + +
+
+ Study Ocean Logo +
+

Study Ocean Mentor

+

AI strictly trained on your PDFs

+
+
+ +
+
+ Hey! I'm your Study Ocean Mentor. Ask me any doubt from your materials, and I'll explain it clearly. ๐ŸŒŠ +
+
+ +
+ + +
+
+ + + + diff --git a/ingest.py b/ingest.py new file mode 100644 index 0000000000000000000000000000000000000000..d14806aa0f92b7f16ce1cc97f757fd845e16df43 --- /dev/null +++ b/ingest.py @@ -0,0 +1,171 @@ +""" +PDF Ingestion & Vector Store Builder for Study Ocean RAG. + +This module: +1. Recursively scans the PDF directory for all .pdf files +2. Extracts text from each PDF using PyMuPDF (faster & more reliable) +3. Splits text into overlapping chunks +4. Generates embeddings using a local sentence-transformer model +5. Stores embeddings in a FAISS vector store for fast retrieval +""" + +import os +import sys +import time +import fitz # PyMuPDF +from langchain.text_splitter import RecursiveCharacterTextSplitter +from langchain_community.vectorstores import FAISS +from langchain_huggingface import HuggingFaceEmbeddings + +import config + + +def collect_pdf_paths(pdf_dir: str) -> list[str]: + """Recursively collect all PDF file paths from the given directory.""" + pdf_paths = [] + for root, _, files in os.walk(pdf_dir): + for file in files: + if file.lower().endswith(".pdf"): + pdf_paths.append(os.path.join(root, file)) + return sorted(pdf_paths) + + +def extract_text_from_pdf(pdf_path: str) -> str: + """Extract all text from a PDF using PyMuPDF.""" + text = "" + try: + doc = fitz.open(pdf_path) + for page_num in range(len(doc)): + page = doc[page_num] + text += page.get_text() + doc.close() + except Exception as e: + print(f" โš  Error reading {os.path.basename(pdf_path)}: {e}") + return text + + +def build_documents(pdf_paths: list[str]) -> list: + """ + Extract text from all PDFs and split into chunks. + Each chunk carries metadata about its source PDF and page info. + """ + text_splitter = RecursiveCharacterTextSplitter( + chunk_size=config.CHUNK_SIZE, + chunk_overlap=config.CHUNK_OVERLAP, + separators=["\n\n", "\n", ". ", " ", ""], + length_function=len, + ) + + all_documents = [] + for i, pdf_path in enumerate(pdf_paths, 1): + filename = os.path.basename(pdf_path) + relative_path = os.path.relpath(pdf_path, config.PDF_DIR) + # Derive subject from folder structure + parts = relative_path.replace("\\", "/").split("/") + subject = parts[0] if len(parts) > 1 else "General" + + print(f" [{i}/{len(pdf_paths)}] Processing: {relative_path}") + + text = extract_text_from_pdf(pdf_path) + if not text.strip(): + print(f" โš  No text extracted (might be scanned/image PDF)") + continue + + # Create chunks with rich metadata + chunks = text_splitter.create_documents( + texts=[text], + metadatas=[{ + "source": relative_path, + "filename": filename, + "subject": subject, + }] + ) + all_documents.extend(chunks) + + return all_documents + + +def create_vector_store(documents: list, embeddings) -> FAISS: + """Create a FAISS vector store from documents.""" + print(f"\n๐Ÿ“Š Creating vector store with {len(documents)} chunks...") + + # Process in batches to avoid memory issues + batch_size = 500 + vector_store = None + + for i in range(0, len(documents), batch_size): + batch = documents[i:i + batch_size] + batch_num = (i // batch_size) + 1 + total_batches = (len(documents) + batch_size - 1) // batch_size + print(f" Embedding batch {batch_num}/{total_batches} ({len(batch)} chunks)...") + + if vector_store is None: + vector_store = FAISS.from_documents(batch, embeddings) + else: + batch_store = FAISS.from_documents(batch, embeddings) + vector_store.merge_from(batch_store) + + return vector_store + + +def run_ingestion(): + """Main ingestion pipeline.""" + print("=" * 60) + print("๐ŸŽ“ Study Ocean RAG โ€” PDF Ingestion Pipeline") + print("=" * 60) + + # 1. Collect PDFs + print(f"\n๐Ÿ“ Scanning for PDFs in: {config.PDF_DIR}") + pdf_paths = collect_pdf_paths(config.PDF_DIR) + + if not pdf_paths: + print("โŒ No PDF files found! Make sure your PDFs are in the data/pdfs directory.") + print(" Extract StudyOceanPDFs.zip into data/pdfs/ first.") + sys.exit(1) + + print(f" Found {len(pdf_paths)} PDF files\n") + + # 2. Extract and chunk + print("๐Ÿ“„ Extracting text and creating chunks...") + start_time = time.time() + documents = build_documents(pdf_paths) + extract_time = time.time() - start_time + print(f"\n โœ… Created {len(documents)} text chunks in {extract_time:.1f}s") + + if not documents: + print("โŒ No text could be extracted from any PDF. Exiting.") + sys.exit(1) + + # 3. Initialize embeddings + print(f"\n๐Ÿง  Loading embedding model: {config.EMBEDDING_MODEL}") + embeddings = HuggingFaceEmbeddings( + model_name=config.EMBEDDING_MODEL, + model_kwargs={"device": "cpu"}, + encode_kwargs={"normalize_embeddings": True, "batch_size": 64}, + ) + + # 4. Build vector store + start_time = time.time() + vector_store = create_vector_store(documents, embeddings) + embed_time = time.time() - start_time + print(f" โœ… Vector store created in {embed_time:.1f}s") + + # 5. Save to disk + os.makedirs(config.VECTOR_STORE_PATH, exist_ok=True) + print(f"\n๐Ÿ’พ Saving vector store to: {config.VECTOR_STORE_PATH}") + vector_store.save_local(config.VECTOR_STORE_PATH) + print(" โœ… Vector store saved successfully!") + + # Summary + print("\n" + "=" * 60) + print("โœ… INGESTION COMPLETE") + print(f" PDFs processed: {len(pdf_paths)}") + print(f" Chunks created: {len(documents)}") + print(f" Extract time: {extract_time:.1f}s") + print(f" Embedding time: {embed_time:.1f}s") + print(f" Store location: {config.VECTOR_STORE_PATH}") + print("=" * 60) + + +if __name__ == "__main__": + run_ingestion() diff --git a/logo.png b/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..efb99a504e064452cb4ab89d927b002123589f98 --- /dev/null +++ b/logo.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:67928e220f35fffadb9532a9694b8d878bfdad54d68d57614ee8ca2291a2695a +size 258677 diff --git a/main.py b/main.py new file mode 100644 index 0000000000000000000000000000000000000000..749acdb0e2b02626100423d77ca898be3eba2dd8 --- /dev/null +++ b/main.py @@ -0,0 +1,107 @@ +""" +Study Ocean RAG โ€” Main Entry Point + +Usage: + python main.py ingest # Ingest PDFs and build vector store + python main.py chat # Launch the Gradio web UI + python main.py ask # Ask a single question in the terminal +""" + +import sys + + +def print_banner(): + print(""" +โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— +โ•‘ ๐ŸŽ“ Study Ocean AI Mentor โ•‘ +โ•‘ RAG-based Student Doubt Solver โ•‘ +โ• โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•ฃ +โ•‘ โ•‘ +โ•‘ Commands: โ•‘ +โ•‘ python main.py ingest โ€” Process PDFs & build index โ•‘ +โ•‘ python main.py chat โ€” Launch web chat interface โ•‘ +โ•‘ python main.py ask โ€” Ask a question in terminal โ•‘ +โ•‘ โ•‘ +โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• + """) + + +def run_ingest(): + """Run the PDF ingestion pipeline.""" + from ingest import run_ingestion + run_ingestion() + + +def run_chat(): + """Launch the Gradio web interface.""" + from app import app + print("\n๐ŸŒ Starting Study Ocean AI Mentor web interface...") + print(" Open http://localhost:7860 in your browser\n") + app.launch( + server_name="0.0.0.0", + server_port=7860, + share=False, + show_error=True, + ) + + +def run_ask(): + """Interactive terminal Q&A mode.""" + from rag_chain import get_chain, ask_question + + print("\n๐ŸŽ“ Study Ocean AI Mentor โ€” Terminal Mode") + print(" Type your question and press Enter.") + print(" Type 'quit' or 'exit' to stop.\n") + + chain = get_chain() + + while True: + try: + question = input("\n๐Ÿ™‹ Your doubt: ").strip() + except (KeyboardInterrupt, EOFError): + print("\n\n๐Ÿ‘‹ Goodbye! Keep studying! ๐Ÿ“š") + break + + if question.lower() in ("quit", "exit", "q"): + print("\n๐Ÿ‘‹ Goodbye! Keep studying! ๐Ÿ“š") + break + + if not question: + print(" โš  Please type a question.") + continue + + print("\n๐Ÿ” Searching study material...\n") + result = ask_question(chain, question) + + print("โ”€" * 50) + print(f"\n๐ŸŽ“ Mentor's Answer:\n") + print(result["answer"]) + + if result["sources"]: + print(f"\n๐Ÿ“– Sources:") + for i, src in enumerate(result["sources"], 1): + print(f" {i}. [{src['subject']}] {src['file']}") + print("\n" + "โ”€" * 50) + + +def main(): + if len(sys.argv) < 2: + print_banner() + sys.exit(0) + + command = sys.argv[1].lower() + + if command == "ingest": + run_ingest() + elif command == "chat": + run_chat() + elif command == "ask": + run_ask() + else: + print(f"โŒ Unknown command: {command}") + print_banner() + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000000000000000000000000000000000..d178bb26003aa281f231c26c16c58caeee9078cc --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,23 @@ +[project] +name = "study-ocean-rag" +version = "0.1.0" +description = "AI Student Mentor - RAG-based doubt solver using study materials" +readme = "README.md" +requires-python = ">=3.13" +dependencies = [ + "ipykernel>=7.2.0", + "langchain>=0.3.0", + "langchain-community>=0.3.0", + "langchain-core>=0.3.0", + "langchain-google-genai>=2.1.0", + "langchain-huggingface>=0.1.0", + "pymupdf>=1.27.2.2", + "pypdf>=6.10.1", + "faiss-cpu>=1.9.0", + "chromadb>=0.6.0", + "sentence-transformers>=3.4.0", + "google-generativeai>=0.8.0", + "python-dotenv>=1.1.0", + "gradio>=5.0.0", + "tiktoken>=0.9.0", +] diff --git a/rag_chain.py b/rag_chain.py new file mode 100644 index 0000000000000000000000000000000000000000..9bf23ac8d7d364d7c4635138a135662646f1daeb --- /dev/null +++ b/rag_chain.py @@ -0,0 +1,154 @@ +""" +RAG Chain module for Study Ocean Student Mentor. +Reverted to stable RetrievalQA for compatibility with LangChain 0.2.x. +""" + +from langchain_community.vectorstores import FAISS +from langchain_huggingface import HuggingFaceEmbeddings +from langchain_google_genai import ChatGoogleGenerativeAI +from langchain.chains import RetrievalQA +from langchain.prompts import PromptTemplate + +import config + +# โ”€โ”€โ”€ System Prompt for Student Mentor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +STUDENT_MENTOR_PROMPT = PromptTemplate( + input_variables=["context", "question"], + template="""You are **Study Ocean AI Mentor** โ€” a friendly, patient, and knowledgeable tutor who helps students understand their study material deeply. + +Your role: +- You solve student doubts with clear, step-by-step explanations +- You use simple language that students can easily understand +- You provide examples and analogies when helpful +- You reference the study material when answering +- If the answer is in the provided context, use it; if not, say so honestly +- You encourage students and make learning enjoyable + +**Important Guidelines:** +1. Start with a brief, direct answer, then explain in detail +2. Use bullet points, numbered steps, or tables for clarity +3. If it's a math/science problem, show the solution step by step +4. If it's a theory question, break it into key points +5. Mention which topic/chapter the information relates to when possible +6. If the user is just saying "hi", "how are you", or making casual conversation, respond warmly and naturally without mentioning study materials. +7. If it's a genuine study question not covered in the context, say: "This isn't explicitly in your study materials, but here is the answer..." +8. ๐ŸŒŸ Use relevant emojis generously in every response to make learning fun and visually appealing for Gen-Z students! + +--- + +๐Ÿ“š **Relevant Study Material:** +{context} + +--- + +๐Ÿ™‹ **Student's Question:** +{question} + +--- + +๐ŸŽ“ **Mentor's Response:** +""" +) + + +def load_vector_store() -> FAISS: + """Load the pre-built FAISS vector store from disk. Fallback to ingestion if it fails.""" + embeddings = HuggingFaceEmbeddings( + model_name=config.EMBEDDING_MODEL, + model_kwargs={"device": "cpu"}, + encode_kwargs={"normalize_embeddings": True}, + ) + + try: + print("๐Ÿ“š Loading vector store...") + vector_store = FAISS.load_local( + config.VECTOR_STORE_PATH, + embeddings, + allow_dangerous_deserialization=True, + ) + print(" โœ… Vector store loaded!") + except Exception as e: + print(f" โš  Failed to load vector store: {e}") + print(" ๐Ÿ”„ Automatically rebuilding index from PDFs... (This may take a minute)") + from ingest import run_ingestion + run_ingestion() + + # Try loading again after ingestion + vector_store = FAISS.load_local( + config.VECTOR_STORE_PATH, + embeddings, + allow_dangerous_deserialization=True, + ) + print(" โœ… Vector store rebuilt and loaded!") + + return vector_store + + +def build_rag_chain(vector_store: FAISS) -> RetrievalQA: + """Build the RAG chain with retriever + LLM.""" + # Configure retriever + retriever = vector_store.as_retriever( + search_type="similarity", + search_kwargs={"k": config.TOP_K_RESULTS}, + ) + + # Initialize Gemini LLM + llm = ChatGoogleGenerativeAI( + model=config.LLM_MODEL, + google_api_key=config.GOOGLE_API_KEY, + temperature=config.TEMPERATURE, + max_output_tokens=config.MAX_OUTPUT_TOKENS, + convert_system_message_to_human=True, + ) + + # Build the RAG chain + chain = RetrievalQA.from_chain_type( + llm=llm, + chain_type="stuff", + retriever=retriever, + return_source_documents=True, + chain_type_kwargs={ + "prompt": STUDENT_MENTOR_PROMPT, + }, + ) + + return chain + + +def ask_question(chain: RetrievalQA, question: str) -> dict: + """ + Ask a question and get the mentor's response along with source references. + """ + result = chain.invoke({"query": question}) + + # Extract source info + sources = [] + seen = set() + for doc in result.get("source_documents", []): + source_name = doc.metadata.get("source", "Unknown") + subject = doc.metadata.get("subject", "General") + key = f"{subject}/{source_name}" + if key not in seen: + seen.add(key) + sources.append({ + "file": source_name, + "subject": subject, + "preview": doc.page_content[:150] + "..." if len(doc.page_content) > 150 else doc.page_content, + }) + + return { + "answer": result["result"], + "sources": sources, + } + + +# โ”€โ”€โ”€ Singleton pattern for reuse โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ +_chain_instance = None + +def get_chain(): + """Get or create the RAG chain (singleton).""" + global _chain_instance + if _chain_instance is None: + vector_store = load_vector_store() + _chain_instance = build_rag_chain(vector_store) + return _chain_instance diff --git a/render.yaml b/render.yaml new file mode 100644 index 0000000000000000000000000000000000000000..014b46529f7bc0a1ae41aaf58f7f5a12a5d09d46 --- /dev/null +++ b/render.yaml @@ -0,0 +1,9 @@ +services: + - type: web + name: study-ocean-rag-api + env: python + buildCommand: pip install -r requirements.txt + startCommand: uvicorn api:app --host 0.0.0.0 --port $PORT + envVars: + - key: PYTHON_VERSION + value: 3.11.0 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000000000000000000000000000000000000..64e389b411466e6ca513787cffcd6d52a013f68b --- /dev/null +++ b/requirements.txt @@ -0,0 +1,16 @@ +langchain==0.2.16 +langchain-community==0.2.12 +langchain-core==0.2.38 +langchain-google-genai==1.0.10 +langchain-huggingface==0.0.3 +faiss-cpu>=1.8.0 +sentence-transformers==3.0.1 +google-generativeai==0.7.2 +python-dotenv==1.0.1 +fastapi==0.115.0 +uvicorn==0.30.6 +pydantic==2.9.2 +pypdf==5.0.1 +pymupdf==1.24.10 +gunicorn==23.0.0 +gradio \ No newline at end of file