"""Retrieval-Augmented Generation system for Francis Botcon.""" from typing import List, Tuple, Dict from src.vector_db import VectorDatabase from src.logger import LoggerSetup logger = LoggerSetup.setup().getChild(__name__) class RAGSystem: """Retrieval-Augmented Generation system combining vector search and LLM.""" def __init__(self, db_type: str = "chromadb", db_path: str = None): """Initialize RAG system. Args: db_type: Type of vector database db_path: Path to vector database """ self.vector_db = VectorDatabase(db_type=db_type, db_path=db_path) logger.info("✓ RAG System initialized") def retrieve_context(self, query: str, top_k: int = 5) -> List[Dict]: """Retrieve relevant context for a query. Args: query: User query top_k: Number of documents to retrieve Returns: List of relevant documents with metadata """ logger.debug(f"Retrieving context for query: {query[:100]}...") results = self.vector_db.search(query, top_k=top_k) context_docs = [] for text, similarity, metadata in results: context_docs.append({ "text": text, "similarity": float(similarity), "metadata": metadata }) logger.debug(f"Retrieved {len(context_docs)} relevant documents") return context_docs def build_prompt( self, query: str, context_docs: List[Dict], system_prompt: str = None ) -> str: """Build prompt for LLM with retrieved context. Args: query: User query context_docs: Retrieved context documents system_prompt: Optional system prompt Returns: Formatted prompt for LLM """ if system_prompt is None: system_prompt = self._get_default_system_prompt() # Format context context_text = self._format_context(context_docs) # Build final prompt prompt = f"""{system_prompt} ## Relevant Passages from Your Works: {context_text} ## Question: {query} ## Response (in the character of Francis Bacon): """ return prompt def _get_default_system_prompt(self) -> str: """Get default system prompt for Francis Bacon.""" return """You are Francis Bacon (1561-1626), the renowned English philosopher, statesman, and scientist. You should respond in his distinctive voice, maintaining his 17th-century perspective and philosophical style. Reference your actual works when appropriate, maintain intellectual rigor, and use the rhetorical style characteristic of your essays and philosophical treatises. Be thoughtful, measured, and explore ideas thoroughly. When appropriate, cite specific passages from your works.""" def _format_context(self, context_docs: List[Dict]) -> str: """Format retrieved context documents. Args: context_docs: Retrieved documents Returns: Formatted context string """ formatted = [] for i, doc in enumerate(context_docs, 1): text = doc["text"][:500] # Limit length metadata = doc["metadata"] source_info = f"From '{metadata.get('title', 'Unknown Work')}'" if metadata.get("source"): source_info += f" ({metadata['source']})" formatted.append(f"[{i}] {text}...\n {source_info}") return "\n\n".join(formatted) if formatted else "[No relevant passages found in your works]" def generate_response( self, query: str, llm, top_k: int = 5, system_prompt: str = None, **generation_kwargs ) -> Dict: """Generate response using RAG. Args: query: User query llm: Language model for generation top_k: Number of context documents system_prompt: Optional system prompt **generation_kwargs: Additional generation parameters Returns: Dict with response and metadata """ logger.info(f"Generating response for query: {query[:100]}...") # Retrieve context context_docs = self.retrieve_context(query, top_k=top_k) # Build prompt prompt = self.build_prompt(query, context_docs, system_prompt) # Generate response response_text = llm.generate(prompt, **generation_kwargs) return { "query": query, "response": response_text, "context": context_docs, "prompt_used": prompt }