Spaces:
Running on Zero
Running on Zero
Download app/state.py from shareefmx/pdf-knowledge-assistant: direct link, hf CLI and curl.
- Browser
- Download file 6.47 kB
-
https://huggingface.co/spaces/shareefmx/pdf-knowledge-assistant/resolve/main/app/state.py
- Command line
-
hf download hf://spaces/shareefmx/pdf-knowledge-assistant/app/state.py
-
curl -L -o state.py https://huggingface.co/spaces/shareefmx/pdf-knowledge-assistant/resolve/main/app/state.py
6.47 kB
| """Application Shared State and Service Dependency Provider. | |
| Manages singletons for the vector store, embedding service, and RAG pipeline. | |
| """ | |
| from functools import lru_cache | |
| import logging | |
| from pathlib import Path | |
| from typing import Optional | |
| from app.config import Settings, get_settings | |
| from app.embeddings.embedding_service import EmbeddingService, get_embedding_service | |
| from app.generation.llm import LLMServiceFactory | |
| from app.ingestion.chunker import RecursiveChunker | |
| from app.ingestion.pdf_loader import PDFLoader | |
| from app.rag.pipeline import RAGPipeline | |
| from app.retrieval.query_rewriter import QueryRewriter | |
| from app.retrieval.reranker import CrossEncoderReranker | |
| from app.retrieval.retriever import DocumentRetriever | |
| from app.vectorstore.base import BaseVectorStore | |
| from app.vectorstore.chroma_store import ChromaVectorStore | |
| from app.vectorstore.faiss_store import FAISSVectorStore | |
| logger = logging.getLogger(__name__) | |
| class AppState: | |
| """Encapsulates active services and indices across the application lifetime.""" | |
| def __init__(self, settings: Optional[Settings] = None): | |
| self.settings = settings or get_settings() | |
| self.pdf_loader = PDFLoader() | |
| self.chunker = RecursiveChunker( | |
| chunk_size=self.settings.chunk_size, | |
| chunk_overlap=self.settings.chunk_overlap, | |
| ) | |
| # 1. Embedding Service | |
| self.embedding_service = EmbeddingService( | |
| model_name=self.settings.embedding_model | |
| ) | |
| # 2. Vector Database | |
| self.vector_store: BaseVectorStore = self._init_vector_store() | |
| # 3. Retriever | |
| self.retriever = DocumentRetriever( | |
| vector_store=self.vector_store, | |
| embedding_service=self.embedding_service, | |
| similarity_threshold=self.settings.similarity_threshold, | |
| default_top_k=self.settings.top_k, | |
| ) | |
| # 4. LLM Service | |
| self.llm_service = LLMServiceFactory.create( | |
| provider=self.settings.llm_provider, | |
| gemini_api_key=self.settings.gemini_api_key, | |
| nvidia_api_key=self.settings.nvidia_api_key, | |
| openrouter_api_key=self.settings.openrouter_api_key, | |
| model_name=self.settings.llm_model, | |
| nvidia_model=self.settings.nvidia_model, | |
| ) | |
| # 5. Optional Reranker | |
| self.reranker = None | |
| if self.settings.use_reranker: | |
| try: | |
| self.reranker = CrossEncoderReranker(model_name=self.settings.reranker_model) | |
| except Exception as e: | |
| logger.warning("Could not initialize reranker: %s", e) | |
| # 6. Optional Query Rewriter | |
| self.query_rewriter = None | |
| if self.settings.enable_query_rewriting: | |
| self.query_rewriter = QueryRewriter(llm_service=self.llm_service) | |
| # 7. RAG Pipeline | |
| self.pipeline = RAGPipeline( | |
| retriever=self.retriever, | |
| llm_service=self.llm_service, | |
| reranker=self.reranker, | |
| query_rewriter=self.query_rewriter, | |
| ) | |
| def _init_vector_store(self) -> BaseVectorStore: | |
| store_type = self.settings.vector_store.lower() | |
| store_dir = self.settings.get_vectorstore_path() | |
| if store_type == "chroma": | |
| logger.info("Initializing Chroma vector store at '%s'", store_dir) | |
| return ChromaVectorStore(persist_directory=store_dir) | |
| else: | |
| logger.info("Initializing FAISS vector store with dimension %d", self.embedding_service.dimension) | |
| faiss_store = FAISSVectorStore(dimension=self.embedding_service.dimension) | |
| # Attempt to restore if persisted files exist | |
| if (store_dir / "index.faiss").exists() and (store_dir / "metadata.json").exists(): | |
| try: | |
| faiss_store.load(store_dir) | |
| logger.info("Restored existing FAISS index with %d chunks", faiss_store.total_chunks()) | |
| except Exception as e: | |
| logger.warning("Could not restore FAISS index: %s", e) | |
| return faiss_store | |
| def index_pdf_file(self, file_path: Path) -> dict: | |
| """Processes a PDF file through extraction, cleaning, chunking, and embedding into the vector store.""" | |
| # Clean up existing index for the same filename if previously indexed | |
| for doc in self.vector_store.list_documents(): | |
| if doc["filename"] == file_path.name: | |
| logger.info("Replacing existing index for '%s' (ID: %s)", file_path.name, doc["document_id"]) | |
| self.vector_store.delete_document(doc["document_id"]) | |
| pages = self.pdf_loader.load(file_path) | |
| chunks = self.chunker.chunk_documents(pages) | |
| if not chunks: | |
| raise ValueError(f"No valid text chunks generated from '{file_path.name}'.") | |
| # Generate embeddings in batch | |
| texts = [c.text for c in chunks] | |
| embeddings = self.embedding_service.embed_documents(texts) | |
| # Store in vector database | |
| self.vector_store.add_chunks(chunks, embeddings) | |
| # Persist index | |
| try: | |
| self.vector_store.save(self.settings.get_vectorstore_path()) | |
| except Exception as e: | |
| logger.warning("Could not auto-save vector store: %s", e) | |
| return { | |
| "document_id": pages[0].document_id, | |
| "filename": file_path.name, | |
| "pages_extracted": len(pages), | |
| "chunks_created": len(chunks), | |
| "total_indexed_chunks": self.vector_store.total_chunks(), | |
| } | |
| def reload_vector_store_if_needed(self) -> None: | |
| """Reloads FAISS vector store from disk if persisted files exist and in-memory is empty.""" | |
| store_dir = self.settings.get_vectorstore_path() | |
| index_file = store_dir / "index.faiss" | |
| meta_file = store_dir / "metadata.json" | |
| if index_file.exists() and meta_file.exists(): | |
| try: | |
| self.vector_store.load(store_dir) | |
| logger.info("Synchronized FAISS index from disk (%d chunks)", self.vector_store.total_chunks()) | |
| except Exception as e: | |
| logger.warning("Could not sync FAISS index from disk: %s", e) | |
| # Global app state singleton | |
| _APP_STATE: Optional[AppState] = None | |
| def get_app_state() -> AppState: | |
| """Returns application state singleton.""" | |
| global _APP_STATE | |
| if _APP_STATE is None: | |
| _APP_STATE = AppState() | |
| return _APP_STATE | |