fecb-rag / ingest.py
malaporte's picture
Upload folder using huggingface_hub
32bcdac verified
Raw
History Blame Contribute Delete
3.77 kB
"""
ingest.py β€” Build a FAISS vector index from a folder of PDFs.
Usage:
python ingest.py [--pdf-dir pdfs] [--index-dir faiss_index] [--chunk-size 800] [--chunk-overlap 100]
Environment variables:
EMBED_MODEL β€” Embedding model (default: BAAI/bge-small-en-v1.5)
PDF_DIR β€” Folder containing PDF files (default: pdfs)
INDEX_DIR β€” Where to save the FAISS index (default: faiss_index)
"""
import argparse
import json
import os
from pathlib import Path
import fitz # PyMuPDF
from langchain_community.vectorstores import FAISS
from langchain_huggingface import HuggingFaceEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_core.documents import Document
from tqdm import tqdm
EMBED_MODEL = os.getenv("EMBED_MODEL", "BAAI/bge-small-en-v1.5")
PDF_DIR = Path(os.getenv("PDF_DIR", "pdfs"))
INDEX_DIR = Path(os.getenv("INDEX_DIR", "faiss_index"))
META_FILE = Path("metadata.json")
def extract_text(pdf_path: Path) -> str:
doc = fitz.open(str(pdf_path))
return "\n".join(page.get_text() for page in doc)
def load_pdfs(pdf_dir: Path) -> list[Document]:
pdfs = sorted(pdf_dir.glob("**/*.pdf"))
if not pdfs:
raise FileNotFoundError(f"No PDF files found in {pdf_dir}")
print(f"Found {len(pdfs)} PDF(s) in {pdf_dir}")
docs = []
metadata_records = []
for pdf_path in tqdm(pdfs, desc="Reading PDFs"):
text = extract_text(pdf_path)
if not text.strip():
print(f" [WARN] No text extracted from {pdf_path.name}, skipping")
continue
doc_id = pdf_path.stem
docs.append(Document(
page_content=text,
metadata={"doc_id": doc_id, "filename": pdf_path.name, "source": str(pdf_path)},
))
metadata_records.append({"doc_id": doc_id, "filename": pdf_path.name})
with open(META_FILE, "w", encoding="utf-8") as f:
json.dump(metadata_records, f, ensure_ascii=False, indent=2)
print(f"Saved metadata for {len(metadata_records)} documents β†’ {META_FILE}")
return docs
def chunk_documents(docs: list[Document], chunk_size: int, chunk_overlap: int) -> list[Document]:
splitter = RecursiveCharacterTextSplitter(
chunk_size=chunk_size,
chunk_overlap=chunk_overlap,
separators=["\n\n", "\n", ". ", " ", ""],
)
chunks = splitter.split_documents(docs)
print(f"Split into {len(chunks)} chunks (chunk_size={chunk_size}, overlap={chunk_overlap})")
return chunks
def build_index(chunks: list[Document], embeddings: HuggingFaceEmbeddings, index_dir: Path) -> None:
print(f"Building FAISS index with {EMBED_MODEL}...")
vectorstore = FAISS.from_documents(chunks, embeddings)
index_dir.mkdir(parents=True, exist_ok=True)
vectorstore.save_local(str(index_dir))
print(f"FAISS index saved β†’ {index_dir}/")
def main():
parser = argparse.ArgumentParser(description="Ingest PDFs into a FAISS vector index")
parser.add_argument("--pdf-dir", type=Path, default=PDF_DIR)
parser.add_argument("--index-dir", type=Path, default=INDEX_DIR)
parser.add_argument("--chunk-size", type=int, default=1500)
parser.add_argument("--chunk-overlap",type=int, default=200)
args = parser.parse_args()
print(f"Loading embedding model: {EMBED_MODEL}")
embeddings = HuggingFaceEmbeddings(
model_name=EMBED_MODEL,
model_kwargs={"device": "cpu"},
encode_kwargs={"normalize_embeddings": True},
)
docs = load_pdfs(args.pdf_dir)
chunks = chunk_documents(docs, args.chunk_size, args.chunk_overlap)
build_index(chunks, embeddings, args.index_dir)
print("\nDone! You can now run: python app.py")
if __name__ == "__main__":
main()