Агентный RAG: динамические стратегии поиска и интеллектуальный синтез ответов
В этом руководстве показано, как построить агентную систему Retrieval-Augmented Generation (RAG), которая делает больше, чем просто поиск документов. Агент решает, нужно ли выполнять извлечение, выбирает подходящую стратегию поиска и синтезирует ответы с учётом контекста — сочетая эмбеддинги, индекс FAISS и LLM (здесь — мок) для демонстрации всей цепочки.
Компоненты агентности и мок-LLM
Начнём с определения упрощённого мок-LLM, перечисления стратегий поиска и dataclass для Document, чтобы структурировать базу знаний. Мок-LLM имитирует ответы на управляющие промпты, которые используются в пайплайне.
import numpy as np
import faiss
from sentence_transformers import SentenceTransformer
import json
import re
from typing import List, Dict, Any, Optional
from dataclasses import dataclass
from enum import Enum
class MockLLM:
def generate(self, prompt: str, max_tokens: int = 150) -> str:
prompt_lower = prompt.lower()
if "decide whether to retrieve" in prompt_lower:
if any(word in prompt_lower for word in ["specific", "recent", "data", "facts", "when", "who", "what"]):
return "RETRIEVE: The query requires specific factual information that needs to be retrieved."
else:
return "NO_RETRIEVE: This is a general question that can be answered with existing knowledge."
elif "choose retrieval strategy" in prompt_lower:
if "comparison" in prompt_lower or "versus" in prompt_lower:
return "STRATEGY: multi_query - Need to retrieve information about multiple entities for comparison."
elif "recent" in prompt_lower or "latest" in prompt_lower:
return "STRATEGY: temporal - Focus on recent information."
else:
return "STRATEGY: semantic - Standard semantic similarity search."
elif "synthesize" in prompt_lower and "context:" in prompt_lower:
return "Based on the retrieved information, here's a comprehensive answer that combines multiple sources and provides specific details with proper context."
return "This is a mock response. In practice, use a real LLM like OpenAI's GPT or similar."
class RetrievalStrategy(Enum):
SEMANTIC = "semantic"
MULTI_QUERY = "multi_query"
TEMPORAL = "temporal"
HYBRID = "hybrid"
@dataclass
class Document:
id: str
content: str
metadata: Dict[str, Any]
embedding: Optional[np.ndarray] = None
Инициализация эмбеддингов и индекса FAISS
Система кодирует содержимое документов с помощью SentenceTransformer, нормализует эмбеддинги и строит индекс FAISS для быстрого семантического поиска.
class AgenticRAGSystem:
def __init__(self, model_name: str = "all-MiniLM-L6-v2"):
self.encoder = SentenceTransformer(model_name)
self.llm = MockLLM()
self.documents: List[Document] = []
self.index: Optional[faiss.Index] = None
def add_documents(self, documents: List[Dict[str, Any]]) -> None:
print(f"Processing {len(documents)} documents...")
for i, doc in enumerate(documents):
doc_obj = Document(
id=doc.get('id', str(i)),
content=doc['content'],
metadata=doc.get('metadata', {})
)
self.documents.append(doc_obj)
contents = [doc.content for doc in self.documents]
embeddings = self.encoder.encode(contents, show_progress_bar=True)
for doc, embedding in zip(self.documents, embeddings):
doc.embedding = embedding
dimension = embeddings.shape[1]
self.index = faiss.IndexFlatIP(dimension)
faiss.normalize_L2(embeddings)
self.index.add(embeddings.astype('float32'))
print(f"Knowledge base built with {len(self.documents)} documents")
Принятие решений и выбор стратегии
Агент сначала оценивает, нужно ли извлекать информацию для данного запроса, затем выбирает стратегию поиска: семантическую, multi-query, temporal или гибридную. Это позволяет направлять запрос к наиболее релевантным данным.
def decide_retrieval(self, query: str) -> bool:
decision_prompt = f"""
Analyze the following query and decide whether to retrieve information:
Query: "{query}"
Decide whether to retrieve information from the knowledge base.
Consider if this needs specific facts, recent data, or can be answered generally.
Respond with either:
RETRIEVE: [reason] or NO_RETRIEVE: [reason]
"""
response = self.llm.generate(decision_prompt)
should_retrieve = response.startswith("RETRIEVE:")
print(f" Agent Decision: {'Retrieve' if should_retrieve else 'Direct Answer'}")
print(f" Reasoning: {response.split(':', 1)[1].strip() if ':' in response else response}")
return should_retrieve
def choose_strategy(self, query: str) -> RetrievalStrategy:
strategy_prompt = f"""
Choose the best retrieval strategy for this query:
Query: "{query}"
Available strategies:
- semantic: Standard similarity search
- multi_query: Multiple related queries (for comparisons)
- temporal: Focus on recent information
- hybrid: Combination approach
Choose retrieval strategy and explain why.
Respond with: STRATEGY: [strategy_name] - [reasoning]
"""
response = self.llm.generate(strategy_prompt)
if "multi_query" in response.lower():
strategy = RetrievalStrategy.MULTI_QUERY
elif "temporal" in response.lower():
strategy = RetrievalStrategy.TEMPORAL
elif "hybrid" in response.lower():
strategy = RetrievalStrategy.HYBRID
else:
strategy = RetrievalStrategy.SEMANTIC
print(f" Retrieval Strategy: {strategy.value}")
print(f" Reasoning: {response.split('-', 1)[1].strip() if '-' in response else response}")
return strategy
Подходы к извлечению и синтезу
Реализованы семантический поиск, multi-query для сравнения и temporal для переранжирования по дате. Результаты дедуплицируются и служат контекстом для синтеза ответа.
def retrieve_documents(self, query: str, strategy: RetrievalStrategy, k: int = 3) -> List[Document]:
if not self.index:
print(" No knowledge base available")
return []
if strategy == RetrievalStrategy.MULTI_QUERY:
queries = [query, f"advantages of {query}", f"disadvantages of {query}"]
all_docs = []
for q in queries:
docs = self._semantic_search(q, k=2)
all_docs.extend(docs)
seen_ids = set()
unique_docs = []
for doc in all_docs:
if doc.id not in seen_ids:
unique_docs.append(doc)
seen_ids.add(doc.id)
return unique_docs[:k]
elif strategy == RetrievalStrategy.TEMPORAL:
docs = self._semantic_search(query, k=k*2)
docs_with_dates = [(doc, doc.metadata.get('date', '1900-01-01')) for doc in docs]
docs_with_dates.sort(key=lambda x: x[1], reverse=True)
return [doc for doc, _ in docs_with_dates[:k]]
else:
return self._semantic_search(query, k=k)
def _semantic_search(self, query: str, k: int) -> List[Document]:
query_embedding = self.encoder.encode([query])
faiss.normalize_L2(query_embedding)
scores, indices = self.index.search(query_embedding.astype('float32'), k)
results = []
for score, idx in zip(scores[0], indices[0]):
if idx < len(self.documents):
results.append(self.documents[idx])
return results
def synthesize_response(self, query: str, retrieved_docs: List[Document]) -> str:
if not retrieved_docs:
return self.llm.generate(f"Answer this query: {query}")
context = "\n\n".join([f"Document {i+1}: {doc.content}"
for i, doc in enumerate(retrieved_docs)])
synthesis_prompt = f"""
Query: {query}
Context: {context}
Synthesize a comprehensive answer using the provided context.
Be specific and reference the information sources when relevant.
"""
return self.llm.generate(synthesis_prompt, max_tokens=200)
Конец-концовый поток запроса
Один вызов query объединяет решение о необходимости извлечения, выбор стратегии, извлечение и синтез, выводя промежуточные рассуждения и фрагменты контекста для прозрачности.
def query(self, query: str) -> str:
print(f"\n Processing Query: '{query}'")
print("=" * 50)
if not self.decide_retrieval(query):
print("\n Generating direct response...")
return self.llm.generate(f"Answer this query: {query}")
strategy = self.choose_strategy(query)
print(f"\n Retrieving documents using {strategy.value} strategy...")
retrieved_docs = self.retrieve_documents(query, strategy)
print(f" Retrieved {len(retrieved_docs)} documents")
print("\n Synthesizing response...")
response = self.synthesize_response(query, retrieved_docs)
if retrieved_docs:
print("\n Retrieved Context:")
for i, doc in enumerate(retrieved_docs[:2], 1):
print(f" {i}. {doc.content[:100]}...")
return response
Демо и пример базы знаний
Пример создаёт небольшую базу знаний, инициализирует систему и выполняет несколько запросов, демонстрируя поведение агента при разных типах запросов.
def create_sample_knowledge_base():
return [
{
"id": "ai_1",
"content": "Artificial Intelligence (AI) refers to computer systems that can perform tasks requiring human intelligence",
"metadata": {"topic": "AI basics", "date": "2024-01-15"}
},
{
"id": "ml_1",
"content": "ML is a subset of AI.",
"metadata": {"topic": "Machine Learning", "date": "2024-02-10"}
},
{
"id": "rag_1",
"content": "Retrieval-Augmented Generation (RAG) combines the power of large language models with external knowledge retrieval to provide more accurate and up-to-date responses.",
"metadata": {"topic": "RAG", "date": "2024-03-05"}
},
{
"id": "agents_1",
"content": "AI agents",
"metadata": {"topic": "AI Agents", "date": "2024-03-20"}
}
]
if __name__ == "__main__":
print(" Initializing Agentic RAG System...")
rag_system = AgenticRAGSystem()
docs = create_sample_knowledge_base()
rag_system.add_documents(docs)
demo_queries = [
"What is artificial intelligence?",
"How are you today?",
"Compare AI and Machine Learning",
]
for query in demo_queries:
response = rag_system.query(query)
print(f"\n Final Response: {response}")
print("\n" + "="*80)
print("\n Agentic RAG Tutorial Complete!")
print("\nKey Features Demonstrated:")
print("• Agent-driven retrieval decisions")
print("• Dynamic strategy selection")
print("• Multi-modal retrieval approaches")
print("• Transparent reasoning process")
Дальнейшие шаги и развитие
Эта база даёт исходную точку для практических экспериментов: замените мок-LLM на реальную модель, расширьте базу знаний, настраивайте стратегии поиска и добавьте гибридные подходы или внешние временные сигналы, чтобы подготовить систему к реальным задачам.