Medical-RAG-Chatbot / src /ingest.py
bs01338's picture
Upload 20 files
b1ec669 verified
Raw
History Blame Contribute Delete
2.93 kB
"""Step 3 — Ingestion (the "retrieval" half of RAG).
Pipeline:
1. Load every PDF in data/
2. Split the text into overlapping chunks
3. Embed each chunk into a vector (sentence-transformers)
4. Store the vectors in a persistent ChromaDB database
Run once after adding/changing documents:
python -m src.ingest
"""
from __future__ import annotations
import shutil
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_chroma import Chroma
from src.config import (
DATA_DIR, CHROMA_DIR, CHUNK_SIZE, CHUNK_OVERLAP,
EMBED_MODEL, COLLECTION_NAME,
)
def load_documents() -> list:
"""Load all PDFs from data/ into LangChain Document objects (one per page)."""
pdf_paths = sorted(DATA_DIR.glob("*.pdf"))
if not pdf_paths:
raise FileNotFoundError(
f"No PDFs found in {DATA_DIR}. "
f"Run `python -m src.download_sources` first, or add your own PDFs."
)
docs = []
for path in pdf_paths:
print(f" loading {path.name} ...")
pages = PyPDFLoader(str(path)).load()
# Keep a clean source label for citations later.
for page in pages:
page.metadata["source"] = path.name
docs.extend(pages)
print(f"Loaded {len(docs)} pages from {len(pdf_paths)} PDF(s).")
return docs
def split_documents(docs: list) -> list:
"""Split pages into overlapping chunks for fine-grained retrieval."""
splitter = RecursiveCharacterTextSplitter(
chunk_size=CHUNK_SIZE,
chunk_overlap=CHUNK_OVERLAP,
# Try to split on natural boundaries first (paragraphs, then lines).
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_documents(docs)
print(f"Split into {len(chunks)} chunks "
f"(size={CHUNK_SIZE}, overlap={CHUNK_OVERLAP} chars).")
return chunks
def build_vector_store(chunks: list, reset: bool = True) -> Chroma:
"""Embed chunks and persist them in ChromaDB."""
if reset and CHROMA_DIR.exists():
print("Removing previous vector store ...")
shutil.rmtree(CHROMA_DIR)
print(f"Loading embedding model '{EMBED_MODEL}' (first run downloads it) ...")
embeddings = HuggingFaceEmbeddings(model_name=EMBED_MODEL)
print("Embedding chunks and writing to ChromaDB ...")
store = Chroma.from_documents(
documents=chunks,
embedding=embeddings,
collection_name=COLLECTION_NAME,
persist_directory=str(CHROMA_DIR),
)
print(f"Vector store ready at {CHROMA_DIR}")
return store
def main() -> None:
print("=== Ingestion ===")
docs = load_documents()
chunks = split_documents(docs)
build_vector_store(chunks)
print("Done. You can now run the chatbot.")
if __name__ == "__main__":
main()