veun / rag_integration.py
Kunalv's picture
Upload folder using huggingface_hub
24647cd verified
Raw
History Blame Contribute Delete
17.8 kB
# app/rag_integration.py
import os
import logging
from typing import Optional, List, Dict, Union
from langchain_community.vectorstores import FAISS
from langchain_community.embeddings import HuggingFaceEmbeddings
from langchain.docstore.document import Document
from langchain.text_splitter import RecursiveCharacterTextSplitter
import pickle
from datetime import datetime
import json
# Set up logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Configuration
RAG_FAISS_PATH = os.path.join(os.getcwd(), "rag_data")
RAG_METADATA_PATH = os.path.join(RAG_FAISS_PATH, "metadata.pkl")
RAG_DEBUG_PATH = os.path.join(RAG_FAISS_PATH, "debug_info.json")
os.makedirs(RAG_FAISS_PATH, exist_ok=True)
# Initialize text splitter for large documents
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=1000,
chunk_overlap=200,
length_function=len,
separators=["\n\n", "\n", " ", ""]
)
# Initialize embeddings
embedding = None
vectorstore = None
metadata_store = {}
debug_info = {"initialization_attempts": 0, "last_error": None, "documents_added": 0}
def initialize_embeddings():
"""Initialize HuggingFace embeddings with error handling."""
global embedding
try:
embedding = HuggingFaceEmbeddings(
model_name="sentence-transformers/all-MiniLM-L6-v2",
model_kwargs={'device': 'cpu'}
)
logger.info("HuggingFace embeddings initialized successfully")
return True
except Exception as e:
logger.error(f"Failed to initialize embeddings: {e}")
debug_info["last_error"] = f"Embedding initialization failed: {str(e)}"
return False
def initialize_vectorstore():
"""Initialize the FAISS vectorstore and metadata store with debugging."""
global vectorstore, metadata_store, debug_info
debug_info["initialization_attempts"] += 1
if not initialize_embeddings():
return False
try:
# Load metadata store first
if os.path.exists(RAG_METADATA_PATH):
with open(RAG_METADATA_PATH, 'rb') as f:
metadata_store = pickle.load(f)
logger.info(f"Metadata store loaded with {len(metadata_store)} entries")
else:
metadata_store = {}
logger.info("New metadata store created")
# Check if vectorstore files exist
faiss_index_path = os.path.join(RAG_FAISS_PATH, "index.faiss")
faiss_pkl_path = os.path.join(RAG_FAISS_PATH, "index.pkl")
if os.path.exists(faiss_index_path) and os.path.exists(faiss_pkl_path):
try:
vectorstore = FAISS.load_local(
RAG_FAISS_PATH,
embeddings=embedding,
allow_dangerous_deserialization=True
)
logger.info(f"Existing FAISS vectorstore loaded with {vectorstore.index.ntotal} vectors")
# Add some sample data if vectorstore is empty
if vectorstore.index.ntotal <= 1: # Only has dummy document
add_sample_data()
except Exception as load_error:
logger.warning(f"Could not load existing vectorstore: {load_error}")
vectorstore = create_new_vectorstore()
else:
logger.info("No existing vectorstore found, creating new one")
vectorstore = create_new_vectorstore()
# Save debug info
debug_info["vectorstore_documents"] = vectorstore.index.ntotal if vectorstore else 0
debug_info["last_initialization"] = datetime.now().isoformat()
debug_info["last_error"] = None
save_debug_info()
return True
except Exception as e:
error_msg = f"Failed to initialize vectorstore: {e}"
logger.error(error_msg)
debug_info["last_error"] = error_msg
save_debug_info()
return False
def create_new_vectorstore():
"""Create a new FAISS vectorstore with sample data."""
global vectorstore
try:
# Create sample documents for initialization
sample_docs = [
Document(
page_content="This is the RAG knowledge system for AI Video Chat Assistant.",
metadata={"type": "system", "content_type": "initialization", "timestamp": datetime.now().isoformat()}
),
Document(
page_content="The system can analyze videos and answer questions about their content.",
metadata={"type": "system", "content_type": "capability", "timestamp": datetime.now().isoformat()}
)
]
vectorstore = FAISS.from_documents(sample_docs, embedding)
logger.info(f"New FAISS vectorstore created with {len(sample_docs)} sample documents")
# Save immediately
save_vectorstore()
return vectorstore
except Exception as e:
logger.error(f"Failed to create new vectorstore: {e}")
raise e
def add_sample_data():
"""Add sample data to help with testing."""
sample_entries = [
{
"text": "Video analysis example: A user uploaded a video showing a cat playing with a toy mouse. The video had good lighting and clear audio.",
"content_type": "video_analysis",
"session_id": "sample_session"
},
{
"text": "User frequently asks about video quality, object detection, and scene analysis in uploaded content.",
"content_type": "user_pattern",
"session_id": "sample_session"
},
{
"text": "The AI assistant can identify objects, analyze scenes, describe actions, and answer questions about video content using computer vision.",
"content_type": "capability",
"session_id": "global"
}
]
for entry in sample_entries:
add_to_rag_vectorstore(
text=entry["text"],
session_id=entry["session_id"],
content_type=entry["content_type"],
source="sample_data"
)
logger.info(f"Added {len(sample_entries)} sample entries to vectorstore")
def save_vectorstore():
"""Save the vectorstore and metadata to disk."""
try:
if vectorstore is not None:
vectorstore.save_local(RAG_FAISS_PATH)
logger.debug("Vectorstore saved successfully")
with open(RAG_METADATA_PATH, 'wb') as f:
pickle.dump(metadata_store, f)
debug_info["last_save"] = datetime.now().isoformat()
save_debug_info()
return True
except Exception as e:
error_msg = f"Failed to save vectorstore: {e}"
logger.error(error_msg)
debug_info["last_error"] = error_msg
save_debug_info()
return False
def save_debug_info():
"""Save debug information."""
try:
with open(RAG_DEBUG_PATH, 'w') as f:
json.dump(debug_info, f, indent=2)
except Exception as e:
logger.error(f"Failed to save debug info: {e}")
def add_to_rag_vectorstore(
text: str,
session_id: Optional[str] = None,
content_type: str = "general",
source: str = "chat",
chunk_text: bool = True
) -> bool:
"""Add text to the RAG vectorstore with enhanced metadata and debugging."""
global debug_info
if vectorstore is None:
logger.error("Vectorstore not initialized")
debug_info["last_error"] = "Add operation failed: vectorstore not initialized"
return False
if not text or not text.strip():
logger.warning("Empty text provided, skipping")
return False
try:
# Prepare metadata
metadata = {
"session_id": session_id or "global",
"content_type": content_type,
"source": source,
"timestamp": datetime.now().isoformat(),
"char_count": len(text)
}
# Split text into chunks if needed
if chunk_text and len(text) > 500:
chunks = text_splitter.split_text(text)
documents = []
for i, chunk in enumerate(chunks):
chunk_metadata = metadata.copy()
chunk_metadata["chunk_id"] = i
chunk_metadata["total_chunks"] = len(chunks)
documents.append(Document(page_content=chunk, metadata=chunk_metadata))
else:
documents = [Document(page_content=text, metadata=metadata)]
# Add to vectorstore
vectorstore.add_documents(documents)
# Update metadata store
doc_id = f"{session_id}_{datetime.now().timestamp()}"
metadata_store[doc_id] = {
"metadata": metadata,
"document_count": len(documents),
"text_preview": text[:100] + "..." if len(text) > 100 else text
}
# Update debug info
debug_info["documents_added"] += len(documents)
debug_info["total_documents"] = vectorstore.index.ntotal
debug_info["last_add_operation"] = datetime.now().isoformat()
# Save to disk
save_vectorstore()
logger.info(f"Successfully added {len(documents)} document(s) to RAG vectorstore")
return True
except Exception as e:
error_msg = f"Failed to add to vectorstore: {e}"
logger.error(error_msg)
debug_info["last_error"] = error_msg
save_debug_info()
return False
def query_rag_vectorstore(
query: str,
session_id: Optional[str] = None,
k: int = 5,
content_type_filter: Optional[str] = None,
similarity_threshold: float = 0.0
) -> List[Document]:
"""Query the RAG vectorstore with enhanced filtering and debugging."""
global debug_info
if vectorstore is None:
logger.error("Vectorstore not initialized for query")
debug_info["last_error"] = "Query failed: vectorstore not initialized"
return []
if not query or not query.strip():
logger.warning("Empty query provided")
return []
try:
logger.info(f"Querying vectorstore with query: '{query[:50]}...' (total docs: {vectorstore.index.ntotal})")
# First try a simple similarity search without filters
all_results = vectorstore.similarity_search_with_score(query, k=k*2)
if not all_results:
logger.warning("No results found for query")
debug_info["last_query_results"] = 0
return []
logger.info(f"Found {len(all_results)} initial results")
# Apply filters manually since FAISS filtering can be unreliable
filtered_results = []
for doc, score in all_results:
doc_metadata = doc.metadata
# Apply session filter
if session_id and doc_metadata.get("session_id") != session_id:
continue
# Apply content type filter
if content_type_filter and doc_metadata.get("content_type") != content_type_filter:
continue
# Apply similarity threshold
if score < similarity_threshold:
continue
filtered_results.append(doc)
if len(filtered_results) >= k:
break
debug_info["last_query"] = query[:100]
debug_info["last_query_results"] = len(filtered_results)
debug_info["last_query_time"] = datetime.now().isoformat()
save_debug_info()
logger.info(f"Retrieved {len(filtered_results)} filtered documents for query")
return filtered_results
except Exception as e:
error_msg = f"Failed to query vectorstore: {e}"
logger.error(error_msg)
debug_info["last_error"] = error_msg
save_debug_info()
return []
def get_vectorstore_stats() -> Dict:
"""Get comprehensive statistics about the vectorstore."""
try:
stats = {
"status": "operational" if vectorstore is not None else "failed",
"total_documents": vectorstore.index.ntotal if vectorstore else 0,
"total_entries": len(metadata_store),
"debug_info": debug_info.copy()
}
if metadata_store:
# Count by session and content type
session_counts = {}
content_type_counts = {}
for doc_id, data in metadata_store.items():
metadata = data.get('metadata', {})
session = metadata.get('session_id', 'unknown')
content_type = metadata.get('content_type', 'unknown')
session_counts[session] = session_counts.get(session, 0) + data.get('document_count', 1)
content_type_counts[content_type] = content_type_counts.get(content_type, 0) + data.get('document_count', 1)
stats.update({
"sessions": len(session_counts),
"session_breakdown": session_counts,
"content_type_breakdown": content_type_counts,
})
stats.update({
"vectorstore_path": RAG_FAISS_PATH,
"embedding_model": "sentence-transformers/all-MiniLM-L6-v2",
"files_exist": {
"index.faiss": os.path.exists(os.path.join(RAG_FAISS_PATH, "index.faiss")),
"index.pkl": os.path.exists(os.path.join(RAG_FAISS_PATH, "index.pkl")),
"metadata.pkl": os.path.exists(RAG_METADATA_PATH)
}
})
return stats
except Exception as e:
return {"error": str(e), "debug_info": debug_info.copy()}
def debug_add_test_data():
"""Add test data for debugging purposes."""
test_entries = [
"Test entry 1: This is a sample video analysis about a cooking tutorial.",
"Test entry 2: User asked about ingredients in the recipe video.",
"Test entry 3: The AI identified tomatoes, onions, and garlic in the cooking video.",
"Test entry 4: Analysis of a nature documentary showing wildlife behavior.",
"Test entry 5: User inquiry about animal species identification in nature videos."
]
success_count = 0
for i, entry in enumerate(test_entries):
if add_to_rag_vectorstore(
text=entry,
session_id=f"test_session_{i % 2}",
content_type="test_data",
source="debug"
):
success_count += 1
logger.info(f"Debug: Added {success_count}/{len(test_entries)} test entries")
return success_count
def force_reinitialize():
"""Force reinitialize the vectorstore (useful for debugging)."""
global vectorstore, metadata_store, debug_info
logger.info("Force reinitializing RAG system...")
# Clear current state
vectorstore = None
metadata_store = {}
debug_info["force_reinit_count"] = debug_info.get("force_reinit_count", 0) + 1
# Reinitialize
success = initialize_vectorstore()
if success:
# Add test data
debug_add_test_data()
logger.info("Force reinitialization completed successfully")
else:
logger.error("Force reinitialization failed")
return success
# Initialize vectorstore on module import
logger.info("Initializing RAG integration module...")
initialize_success = initialize_vectorstore()
if not initialize_success:
logger.error("Failed to initialize RAG vectorstore. Attempting force reinitialization...")
initialize_success = force_reinitialize()
if initialize_success:
logger.info("RAG integration module loaded successfully")
else:
logger.error("RAG integration module failed to load properly. Some features may not work.")
# Convenience functions remain the same...
def add_video_analysis(video_filename: str, analysis: str, session_id: str) -> bool:
"""Convenience function to add video analysis to RAG."""
content = f"Video Analysis for '{video_filename}': {analysis}"
return add_to_rag_vectorstore(
text=content,
session_id=session_id,
content_type="video_analysis",
source="video"
)
def get_context_for_query(query: str, session_id: str) -> str:
"""Get formatted context for a query."""
try:
# Get session-specific context
session_docs = query_rag_vectorstore(query, session_id, k=3)
# Get global context
global_docs = query_rag_vectorstore(query, None, k=2)
context_parts = []
if session_docs:
session_context = "\n".join([doc.page_content for doc in session_docs])
context_parts.append(f"Session Context:\n{session_context}")
if global_docs:
global_context = "\n".join([doc.page_content for doc in global_docs])
context_parts.append(f"Global Knowledge:\n{global_context}")
if context_parts:
return "\n---\n".join(context_parts) + "\n---\n"
return ""
except Exception as e:
logger.error(f"Failed to get context for query: {e}")
return ""