|
|
|
|
| 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
|
|
|
|
|
| logging.basicConfig(level=logging.INFO)
|
| logger = logging.getLogger(__name__)
|
|
|
|
|
| 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)
|
|
|
|
|
| text_splitter = RecursiveCharacterTextSplitter(
|
| chunk_size=1000,
|
| chunk_overlap=200,
|
| length_function=len,
|
| separators=["\n\n", "\n", " ", ""]
|
| )
|
|
|
|
|
| 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:
|
|
|
| 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")
|
|
|
|
|
| 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")
|
|
|
|
|
| if vectorstore.index.ntotal <= 1:
|
| 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()
|
|
|
|
|
| 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:
|
|
|
| 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_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:
|
|
|
| metadata = {
|
| "session_id": session_id or "global",
|
| "content_type": content_type,
|
| "source": source,
|
| "timestamp": datetime.now().isoformat(),
|
| "char_count": len(text)
|
| }
|
|
|
|
|
| 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)]
|
|
|
|
|
| vectorstore.add_documents(documents)
|
|
|
|
|
| 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
|
| }
|
|
|
|
|
| debug_info["documents_added"] += len(documents)
|
| debug_info["total_documents"] = vectorstore.index.ntotal
|
| debug_info["last_add_operation"] = datetime.now().isoformat()
|
|
|
|
|
| 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})")
|
|
|
|
|
| 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")
|
|
|
|
|
| filtered_results = []
|
| for doc, score in all_results:
|
| doc_metadata = doc.metadata
|
|
|
|
|
| if session_id and doc_metadata.get("session_id") != session_id:
|
| continue
|
|
|
|
|
| if content_type_filter and doc_metadata.get("content_type") != content_type_filter:
|
| continue
|
|
|
|
|
| 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:
|
|
|
| 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...")
|
|
|
|
|
| vectorstore = None
|
| metadata_store = {}
|
| debug_info["force_reinit_count"] = debug_info.get("force_reinit_count", 0) + 1
|
|
|
|
|
| success = initialize_vectorstore()
|
|
|
| if success:
|
|
|
| debug_add_test_data()
|
| logger.info("Force reinitialization completed successfully")
|
| else:
|
| logger.error("Force reinitialization failed")
|
|
|
| return success
|
|
|
|
|
| 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.")
|
|
|
|
|
| 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:
|
|
|
| session_docs = query_rag_vectorstore(query, session_id, k=3)
|
|
|
| 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 "" |