import sys import types import datasets # Mock sentence_transformers trainer, training_args, cross_encoder, and sparse_encoder to bypass Trainer imports sys.modules['sentence_transformers.trainer'] = types.ModuleType('sentence_transformers.trainer') sys.modules['sentence_transformers.trainer'].SentenceTransformerTrainer = None sys.modules['sentence_transformers.training_args'] = types.ModuleType('sentence_transformers.training_args') sys.modules['sentence_transformers.training_args'].SentenceTransformerTrainingArguments = None sys.modules['sentence_transformers.training_args'].BatchSamplers = None sys.modules['sentence_transformers.training_args'].MultiDatasetBatchSamplers = None sys.modules['sentence_transformers.sparse_encoder'] = types.ModuleType('sentence_transformers.sparse_encoder') sys.modules['sentence_transformers.sparse_encoder'].SparseEncoder = None sys.modules['sentence_transformers.sparse_encoder'].SparseEncoderModelCardData = None sys.modules['sentence_transformers.sparse_encoder'].SparseEncoderTrainer = None sys.modules['sentence_transformers.sparse_encoder'].SparseEncoderTrainingArguments = None sys.modules['sentence_transformers.cross_encoder'] = types.ModuleType('sentence_transformers.cross_encoder') sys.modules['sentence_transformers.cross_encoder'].CrossEncoder = None sys.modules['sentence_transformers.cross_encoder'].CrossEncoderModelCardData = None sys.modules['sentence_transformers.cross_encoder'].CrossEncoderTrainer = None sys.modules['sentence_transformers.cross_encoder'].CrossEncoderTrainingArguments = None import logging import os import uuid from pathlib import Path from qdrant_client import QdrantClient, models from sentence_transformers import SentenceTransformer from dotenv import load_dotenv # Add project root to path to resolve custom modules correctly PROJECT_ROOT = Path(__file__).resolve().parent sys.path.append(str(PROJECT_ROOT)) from ingestion.pipeline import MultimodalIngestionPipeline from ingestion.config import IngestionSettings # Load environment variables load_dotenv() logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s") logger = logging.getLogger(__name__) COLLECTION_NAME = "conversational_rag" QDRANT_PATH = PROJECT_ROOT / "qdrant_db" def _stable_chunk_id(chunk_content: str) -> str: return str(uuid.uuid5(uuid.NAMESPACE_DNS, str(chunk_content))) def main(): pdf_path = PROJECT_ROOT / "Data" / "Pdf" / "World Development Report 2025.pdf" logger.info(f"Starting text-only PDF deployment to Qdrant for {pdf_path.name}...") # 1. Initialize pipeline with figure extraction and vision disabled (text-only) settings = IngestionSettings( extract_figures=False, use_vision=False ) pipeline = MultimodalIngestionPipeline(settings) # 2. Ingest the PDF to get text chunks logger.info("Ingesting PDF text chunks...") result = pipeline.ingest_sync(pdf_path) chunks = result.chunks logger.info(f"Successfully parsed {len(chunks)} PDF text chunks.") # 3. Setup Qdrant Client (without recreating the collection) client = QdrantClient(path=str(QDRANT_PATH)) if not client.collection_exists(COLLECTION_NAME): logger.error(f"Collection '{COLLECTION_NAME}' does not exist! Please run CSV deployment first.") sys.exit(1) # 4. Load Local SentenceTransformer Model logger.info("Loading local SentenceTransformer('all-MiniLM-L6-v2')...") model = SentenceTransformer('all-MiniLM-L6-v2') logger.info("Local SentenceTransformer model loaded successfully.") # 5. Generate Embeddings for all text chunks texts = [str(chunk.text).strip() for chunk in chunks] logger.info(f"Generating embeddings for {len(texts)} chunks locally...") embeddings = model.encode(texts, convert_to_numpy=True) logger.info("Embedding generation completed.") # 6. Construct Qdrant points points = [] for i, chunk in enumerate(chunks): text = str(chunk.text).strip() metadata = dict(chunk.metadata or {}) chunk_id = str(metadata.get("chunk_id") or _stable_chunk_id(text)) payload = { "text": text, "page_content": text, "source": str(metadata.get("source_file") or pdf_path.name), "image_path": metadata.get("image_path"), "contains_chart": bool(metadata.get("contains_chart")), "contains_table": bool(metadata.get("contains_table")), "contains_figure": bool(metadata.get("contains_figure")), "contains_image": bool(metadata.get("contains_image")), "contains_csv": bool(metadata.get("contains_csv")), "metadata": metadata, } # Keep any asset specific metadata mappings same for key in ["row_id", "columns"]: if metadata.get(key) not in ("", None, [], {}): payload[key] = metadata[key] dense_vector = [float(val) for val in embeddings[i]] points.append( models.PointStruct( id=str(uuid.uuid5(uuid.NAMESPACE_URL, chunk_id)), vector={ "dense": dense_vector, "sparse": models.SparseVector(indices=[], values=[]), }, payload=payload, ) ) # 7. Upsert points in batches (appending to the existing collection) batch_size = 64 uploaded = 0 total_points = len(points) for start in range(0, total_points, batch_size): batch = points[start : start + batch_size] client.upsert(collection_name=COLLECTION_NAME, points=batch, wait=True) uploaded += len(batch) logger.info(f"Upserted {uploaded}/{total_points} points.") # Retrieve final exact count of all points (including CSV) count = client.count(collection_name=COLLECTION_NAME, exact=True).count client.close() logger.info("PDF text-only deployment completed. Total points in collection '%s': %d", COLLECTION_NAME, count) print(f"\nPDF Text-only Ingestion Completed.") print(f"Upserted PDF Points: {uploaded}") print(f"Qdrant Exact Count (Total): {count}") if __name__ == "__main__": main()