Download src/retrieval/rag.py from Snapkitty/sovereign-engine-v2: direct link, hf CLI and curl.
- Browser
- Download file 9.94 kB
-
https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/src/retrieval/rag.py
- Command line
-
hf download hf://Snapkitty/sovereign-engine-v2/src/retrieval/rag.py
-
curl -L -o rag.py https://huggingface.co/Snapkitty/sovereign-engine-v2/resolve/main/src/retrieval/rag.py
9.94 kB
| """ | |
| RAG Pipeline | |
| Part of SOVEREIGN PYTHON LLM ENGINE | |
| Complete Retrieval-Augmented Generation pipeline. | |
| """ | |
| from typing import AsyncIterator | |
| from dataclasses import dataclass | |
| from datetime import datetime | |
| from ..models.entities import Message, MessageRole, RetrievalRequest, RetrievalResult | |
| from ..core.protocols import Model, VectorStore | |
| from ..tools.embeddings.encode import EmbeddingsTool | |
| from .parallel import ParallelRetriever | |
| from ..core.evidence import WORMLedger | |
| class RAGConfig: | |
| """Configuration for RAG pipeline""" | |
| top_k: int = 5 | |
| score_threshold: float = 0.7 | |
| use_reranking: bool = True | |
| context_window: int = 8000 # Max tokens for context | |
| log_to_worm: bool = True | |
| class RAGPipeline: | |
| """ | |
| Complete RAG (Retrieval-Augmented Generation) pipeline. | |
| Steps: | |
| 1. Embed query | |
| 2. Retrieve top-K documents | |
| 3. Optionally rerank | |
| 4. Format context | |
| 5. Generate answer with LLM | |
| """ | |
| def __init__( | |
| self, | |
| model: Model, | |
| retriever: ParallelRetriever, | |
| embeddings: EmbeddingsTool, | |
| worm_ledger: WORMLedger | None = None, | |
| config: RAGConfig | None = None | |
| ): | |
| """ | |
| Initialize RAG pipeline. | |
| Args: | |
| model: LLM for generation | |
| retriever: Parallel retriever | |
| embeddings: Embeddings tool | |
| worm_ledger: Optional WORM ledger | |
| config: Configuration | |
| """ | |
| self.model = model | |
| self.retriever = retriever | |
| self.embeddings = embeddings | |
| self.worm_ledger = worm_ledger | |
| self.config = config or RAGConfig() | |
| async def query( | |
| self, | |
| question: str, | |
| filters: dict | None = None, | |
| stream: bool = False | |
| ) -> str | AsyncIterator[str]: | |
| """ | |
| Query the RAG system. | |
| Args: | |
| question: User question | |
| filters: Optional metadata filters | |
| stream: Whether to stream response | |
| Returns: | |
| Answer string (or stream of chunks) | |
| """ | |
| # 1. Retrieve relevant documents | |
| retrieval_request = RetrievalRequest( | |
| query=question, | |
| limit=self.config.top_k, | |
| score_threshold=self.config.score_threshold, | |
| filters=filters | |
| ) | |
| results = await self.retriever.retrieve(retrieval_request) | |
| # 2. Format context | |
| context = self._format_context(results) | |
| # 3. Build prompt | |
| prompt = self._build_prompt(question, context) | |
| # 4. Generate answer | |
| messages = [ | |
| {"role": "system", "content": "You are a helpful assistant that answers questions based on provided context."}, | |
| {"role": "user", "content": prompt} | |
| ] | |
| if stream: | |
| # Streaming not implemented in base Model protocol yet | |
| # Return async generator wrapper | |
| answer = await self.model.generate( | |
| messages=messages, | |
| temperature=0.3, | |
| max_tokens=2048 | |
| ) | |
| # Log to WORM | |
| if self.worm_ledger and self.config.log_to_worm: | |
| await self._log_to_worm(question, results, answer) | |
| async def stream_wrapper(): | |
| yield answer | |
| return stream_wrapper() | |
| else: | |
| answer = await self.model.generate( | |
| messages=messages, | |
| temperature=0.3, | |
| max_tokens=2048 | |
| ) | |
| # Log to WORM | |
| if self.worm_ledger and self.config.log_to_worm: | |
| await self._log_to_worm(question, results, answer) | |
| return answer | |
| async def query_with_citations( | |
| self, | |
| question: str, | |
| filters: dict | None = None | |
| ) -> tuple[str, list[RetrievalResult]]: | |
| """ | |
| Query with source citations. | |
| Args: | |
| question: User question | |
| filters: Optional filters | |
| Returns: | |
| (answer, source_documents) tuple | |
| """ | |
| # Retrieve | |
| retrieval_request = RetrievalRequest( | |
| query=question, | |
| limit=self.config.top_k, | |
| score_threshold=self.config.score_threshold, | |
| filters=filters | |
| ) | |
| results = await self.retriever.retrieve(retrieval_request) | |
| # Format context | |
| context = self._format_context(results) | |
| # Build prompt with citation instruction | |
| prompt = f"""Answer the question based on the provided context. | |
| Cite sources using [Source N] notation. | |
| Context: | |
| {context} | |
| Question: {question} | |
| Answer:""" | |
| # Generate | |
| messages = [ | |
| {"role": "system", "content": "You are a helpful assistant that answers questions and cites sources."}, | |
| {"role": "user", "content": prompt} | |
| ] | |
| answer = await self.model.generate( | |
| messages=messages, | |
| temperature=0.3, | |
| max_tokens=2048 | |
| ) | |
| # Log to WORM | |
| if self.worm_ledger and self.config.log_to_worm: | |
| await self._log_to_worm(question, results, answer) | |
| return answer, results | |
| def _format_context(self, results: list[RetrievalResult]) -> str: | |
| """ | |
| Format retrieval results into context string. | |
| Args: | |
| results: Retrieved documents | |
| Returns: | |
| Formatted context | |
| """ | |
| if not results: | |
| return "No relevant documents found." | |
| context_parts = [] | |
| for i, result in enumerate(results, 1): | |
| source_info = "" | |
| if result.metadata: | |
| source_info = f" ({result.metadata.get('source', 'Unknown')})" | |
| context_parts.append( | |
| f"[Source {i}]{source_info}\n{result.content}\n" | |
| ) | |
| return "\n".join(context_parts) | |
| def _build_prompt(self, question: str, context: str) -> str: | |
| """ | |
| Build RAG prompt. | |
| Args: | |
| question: User question | |
| context: Retrieved context | |
| Returns: | |
| Formatted prompt | |
| """ | |
| return f"""Answer the question based on the following context. | |
| If the context doesn't contain enough information, say so. | |
| Context: | |
| {context} | |
| Question: {question} | |
| Answer:""" | |
| async def _log_to_worm( | |
| self, | |
| question: str, | |
| results: list[RetrievalResult], | |
| answer: str | |
| ) -> None: | |
| """Log RAG query to WORM ledger""" | |
| if not self.worm_ledger: | |
| return | |
| await self.worm_ledger.append({ | |
| "event": "rag_query", | |
| "question": question, | |
| "retrieved_docs": len(results), | |
| "top_score": results[0].score if results else None, | |
| "answer_length": len(answer), | |
| "timestamp": datetime.utcnow().isoformat() | |
| }) | |
| class ConversationalRAG: | |
| """ | |
| RAG with conversation history. | |
| Maintains context across multiple turns. | |
| """ | |
| def __init__( | |
| self, | |
| rag_pipeline: RAGPipeline, | |
| max_history: int = 10 | |
| ): | |
| """ | |
| Initialize conversational RAG. | |
| Args: | |
| rag_pipeline: Base RAG pipeline | |
| max_history: Maximum conversation turns to keep | |
| """ | |
| self.rag = rag_pipeline | |
| self.max_history = max_history | |
| self.conversation_history: list[Message] = [] | |
| async def query( | |
| self, | |
| question: str, | |
| filters: dict | None = None | |
| ) -> str: | |
| """ | |
| Query with conversation context. | |
| Args: | |
| question: User question | |
| filters: Optional filters | |
| Returns: | |
| Answer | |
| """ | |
| # Add user message to history | |
| self.conversation_history.append( | |
| Message( | |
| role=MessageRole.USER, | |
| content=question, | |
| created_at=datetime.utcnow() | |
| ) | |
| ) | |
| # Retrieve using current question | |
| retrieval_request = RetrievalRequest( | |
| query=question, | |
| limit=self.rag.config.top_k, | |
| score_threshold=self.rag.config.score_threshold, | |
| filters=filters | |
| ) | |
| results = await self.rag.retriever.retrieve(retrieval_request) | |
| # Format context | |
| context = self.rag._format_context(results) | |
| # Build messages with history | |
| messages = [ | |
| {"role": "system", "content": "You are a helpful assistant. Use the provided context and conversation history to answer questions."} | |
| ] | |
| # Add conversation history (limited) | |
| history_messages = self.conversation_history[-(self.max_history * 2):] | |
| for msg in history_messages: | |
| messages.append({ | |
| "role": msg.role.value, | |
| "content": msg.content | |
| }) | |
| # Add current context | |
| messages.append({ | |
| "role": "user", | |
| "content": f"""Context: | |
| {context} | |
| Question: {question} | |
| Answer:""" | |
| }) | |
| # Generate | |
| answer = await self.rag.model.generate( | |
| messages=messages, | |
| temperature=0.3, | |
| max_tokens=2048 | |
| ) | |
| # Add assistant message to history | |
| self.conversation_history.append( | |
| Message( | |
| role=MessageRole.ASSISTANT, | |
| content=answer, | |
| created_at=datetime.utcnow() | |
| ) | |
| ) | |
| return answer | |
| def clear_history(self) -> None: | |
| """Clear conversation history""" | |
| self.conversation_history.clear() | |