{ "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", "
Ask any doubt from your study material โ I'll explain it step by step!
\n", "