seer / rag_system.py
SakibAhmed's picture
Upload 3 files
4677869 verified
Raw History Blame Contribute Delete
6.48 kB
import os
import logging
import shutil
import json
import hashlib
from typing import Optional
from rag_components import KnowledgeRAG
from utils import download_and_unzip_gdrive_folder, prepare_rag_source_directory, validate_rag_source_csv
from config import (
GDRIVE_SOURCES_ENABLED, GDRIVE_FOLDER_ID_OR_URL, RAG_SOURCES_DIR, FAQ_SOURCES_DIR,
RAG_STORAGE_PARENT_DIR, RAG_FAISS_INDEX_SUBDIR_NAME, RAG_LOAD_INDEX_ON_STARTUP,
RAG_EMBEDDING_MODEL_NAME, RAG_EMBEDDING_USE_GPU,
RAG_CHUNK_SIZE, RAG_CHUNK_OVERLAP,
RAG_RERANKER_MODEL_NAME, RAG_RERANKER_ENABLED,
RAG_CHUNKED_SOURCES_FILENAME
)
logger = logging.getLogger(__name__)
def _approved_source_signature(source_dir: str, expected_kind: str) -> dict:
"""Tie each saved FAISS index to the approved CSV contents and filter version."""
expected_name = "faqs.csv" if expected_kind == "faq" else "quiz.csv"
file_path = os.path.join(source_dir, expected_name)
allowed, _ = validate_rag_source_csv(file_path, expected_name, "faq" if expected_kind == "faq" else "quiz")
signature = {"source_filter_version": 1, "kind": expected_kind, "files": {}}
if allowed:
digest = hashlib.sha256()
with open(file_path, "rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
signature["files"][expected_name] = digest.hexdigest()
if expected_kind == "url":
url_text_path = os.path.join(source_dir, "url_data.txt")
if os.path.isfile(url_text_path):
digest = hashlib.sha256()
with open(url_text_path, "rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
signature["files"]["url_data.txt"] = digest.hexdigest()
return signature
def initialize_and_get_rag_system(force_rebuild: bool = False, source_dir_override: Optional[str] = None, storage_dir_override: Optional[str] = None) -> Optional[KnowledgeRAG]:
logger.info("[RAG_SYSTEM_INIT] Initializing...")
source_dir_to_use = source_dir_override if source_dir_override else RAG_SOURCES_DIR
source_kind = ("faq" if source_dir_override is not None and os.path.abspath(source_dir_override) == os.path.abspath(FAQ_SOURCES_DIR)
else "url" if source_dir_override is not None else "quiz")
storage_dir_to_use = storage_dir_override if storage_dir_override else RAG_STORAGE_PARENT_DIR
# GDrive Logic
if GDRIVE_SOURCES_ENABLED and not source_dir_override and GDRIVE_FOLDER_ID_OR_URL:
logger.info("[RAG_SYSTEM_INIT] Downloading sources from GDrive...")
import tempfile
staged_sources = tempfile.mkdtemp(prefix="rag_gdrive_download_")
try:
if download_and_unzip_gdrive_folder(GDRIVE_FOLDER_ID_OR_URL, staged_sources):
if os.path.exists(RAG_SOURCES_DIR):
shutil.rmtree(RAG_SOURCES_DIR)
shutil.move(staged_sources, RAG_SOURCES_DIR)
else:
logger.error("[RAG_SYSTEM_INIT] Google Drive folder download failed; local sources retained.")
finally:
if os.path.isdir(staged_sources):
shutil.rmtree(staged_sources)
if source_kind == "quiz":
prepare_rag_source_directory(RAG_SOURCES_DIR, FAQ_SOURCES_DIR)
faiss_index_path = os.path.join(storage_dir_to_use, RAG_FAISS_INDEX_SUBDIR_NAME)
manifest_path = os.path.join(storage_dir_to_use, "approved_sources_manifest.json")
current_signature = _approved_source_signature(source_dir_to_use, source_kind)
manifest_matches = False
try:
with open(manifest_path, "r", encoding="utf-8") as handle:
manifest_matches = json.load(handle) == current_signature
except (OSError, ValueError):
pass
if not manifest_matches:
logger.info("[SOURCE_FILTER] %s source changed or prior index unverified; rebuilding to exclude disallowed files.", source_kind.upper())
force_rebuild = True
pre_chunked_file = os.path.join(storage_dir_to_use, RAG_CHUNKED_SOURCES_FILENAME)
if force_rebuild:
if os.path.exists(faiss_index_path):
logger.info("[RAG_SYSTEM_INIT] Force rebuild: deleting old index.")
shutil.rmtree(faiss_index_path)
if os.path.exists(pre_chunked_file):
logger.info("[RAG_SYSTEM_INIT] Force rebuild: deleting old pre-chunked file.")
os.remove(pre_chunked_file)
if os.path.exists(manifest_path):
os.remove(manifest_path)
try:
rag = KnowledgeRAG(
index_storage_dir=storage_dir_to_use,
embedding_model_name=RAG_EMBEDDING_MODEL_NAME,
use_gpu_for_embeddings=RAG_EMBEDDING_USE_GPU,
chunk_size=RAG_CHUNK_SIZE,
chunk_overlap=RAG_CHUNK_OVERLAP,
reranker_model_name=RAG_RERANKER_MODEL_NAME,
enable_reranker=RAG_RERANKER_ENABLED,
)
rag.allowed_source_kind = source_kind
loaded = False
if RAG_LOAD_INDEX_ON_STARTUP and not force_rebuild:
if rag.chunk_config_has_changed():
logger.warning("[RAG_SYSTEM_INIT] Chunk config changed — forcing index rebuild.")
if os.path.exists(faiss_index_path):
shutil.rmtree(faiss_index_path)
if os.path.exists(pre_chunked_file):
os.remove(pre_chunked_file)
else:
try:
rag.load_index_from_disk()
loaded = True
except Exception as e:
logger.warning(f"[RAG_SYSTEM_INIT] Load failed ({e}). Building new.")
if not loaded:
if not current_signature["files"]:
logger.warning(
f"[RAG_SYSTEM_INIT] No supported source files found in {source_dir_to_use}. "
"System empty for this index."
)
else:
rag.build_index_from_source_files(source_dir_to_use)
os.makedirs(storage_dir_to_use, exist_ok=True)
with open(manifest_path, "w", encoding="utf-8") as handle:
json.dump(current_signature, handle, sort_keys=True)
logger.info("[RAG_SYSTEM_INIT] Complete.")
return rag
except Exception as e:
logger.critical(f"[RAG_SYSTEM_INIT] FATAL: {e}", exc_info=True)
return None