AI

Agentic RAG: поиск с агентной логикой

Обычный RAG отвечает на один запрос. Agentic RAG планирует поиск, переформулирует запросы, проверяет результаты и итерирует.

📊 Продвинутый⏱ 15 мин

# 1. ОБЫЧНЫЙ RAG vs AGENTIC RAG

# ОБЫЧНЫЙ RAG: одношаговая стратегия
# Запрос → Retrieve → Augment → Generate
def naive_rag(query):
    docs = vectorstore.similarity_search(query, k=3)
    context = "\n".join([d.page_content for d in docs])
    return llm.invoke(f"Ответь на основе: {context}\nВопрос: {query}")

# AGENTIC RAG: многошаговая итеративная стратегия
# Plan → Retrieve → Evaluate → Replan → Generate
def agentic_rag(query):
    plan = planner.decompose(query)  # Разбить на подзадачи
    context = []
    for sub_query in plan:
        docs = retrieve_and_grade(sub_query)
        if not is_relevant(docs):
            sub_query = rewrite_query(sub_query)  # Переформулировать
            docs = retrieve_and_grade(sub_query)
        context.extend(docs)
    return synthesize(context, query)

# 2. БАЗОВЫЙ RAG НА LANGCHAIN

# Установка: pip install langchain langchain-community chromadb pypdf
from langchain_community.document_loaders import PyPDFLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.embeddings import OllamaEmbeddings
from langchain_community.llms import Ollama

# Загрузка PDF и создание векторного индекса
loader = PyPDFLoader("docs/report.pdf")
docs = loader.load()

splitter = RecursiveCharacterTextSplitter(chunk_size=500, chunk_overlap=50)
chunks = splitter.split_documents(docs)

vectorstore = Chroma.from_documents(
    chunks,
    OllamaEmbeddings(model="nomic-embed-text")
)

# Retrieval chain — 3 строки!
retriever = vectorstore.as_retriever(search_kwargs={"k": 3})
llm = Ollama(model="llama3.1")
result = retriever.invoke("Какие KPI в Q1 2025?")
print(result)

# 3. AGENTIC RAG С ИТЕРАТИВНЫМ ПОИСКОМ

# Агент сам решает: достаточно ли контекста? Если нет — переформулирует запрос
from langchain.agents import create_react_agent, AgentExecutor
from langchain.tools import Tool

class IterativeRetriever:
    def __init__(self, vectorstore, llm):
        self.vs = vectorstore
        self.llm = llm
        self.max_iterations = 3

    def search(self, query):
        context = []
        current_query = query
        for i in range(self.max_iterations):
            docs = self.vs.similarity_search(current_query, k=3)
            context.extend(docs)
            # LLM оценивает: достаточно ли информации?
            sufficiency = self._grade_sufficiency(context, query)
            if sufficiency == "sufficient":
                break
            # Переформулировка: "search for: X" → "search for: X related to Y"
            current_query = self._rewrite(current_query, context)
        return self._generate_answer(context, query)

    def _grade_sufficiency(self, context, query):
        prompt = f"Достаточно ли контекста для ответа?\nВопрос: {query}\nКонтекст: {context}\nОтветь: sufficient/insufficient"
        return self.llm.invoke(prompt).strip().lower()

# 4. SELF-QUERY + МЕТАДАННЫЕ

# Агент извлекает фильтры из вопроса: "покажи статьи 2024" → filter year=2024
from langchain.retrievers import SelfQueryRetriever
from langchain.chains.query_constructor.base import AttributeInfo

# Описываем метаданные документов
metadata_field_info = [
    AttributeInfo(name="year", description="Год публикации", type="integer"),
    AttributeInfo(name="author", description="Автор документа", type="string"),
    AttributeInfo(name="category", description="Категория: tech, finance, legal", type="string"),
]

# Self-query retriever — LLM сам построит фильтр
self_query_retriever = SelfQueryRetriever.from_llm(
    llm=Ollama(model="llama3.1"),
    vectorstore=vectorstore,
    document_contents="Научные статьи и отчёты",
    metadata_field_info=metadata_field_info,
)

# Запрос: "статьи Петрова за 2024 про нейросети"
# Агент автоматически построит фильтр: year=2024, author="Петров"
docs = self_query_retriever.invoke(
    "статьи Петрова за 2024 год про нейросети в категории tech"
)
print(f"Найдено документов: {len(docs)}")

# 5. MULTI-HOP REASONING

# Агент разбивает сложный вопрос на подвопросы, ищет каждый, объединяет ответы
def multi_hop_qa(complex_query, retriever, llm):
    # Шаг 1: Декомпозиция вопроса на подвопросы
    decompose_prompt = f"""Разбей сложный вопрос на простые подвопросы (по одному на строку):
Вопрос: {complex_query}
Подвопросы:"""
    sub_questions = llm.invoke(decompose_prompt).strip().split("\n")

    # Шаг 2: Поиск ответа на каждый подвопрос
    intermediate_answers = {}
    for sq in sub_questions:
        docs = retriever.invoke(sq)
        context = "\n".join([d.page_content for d in docs])
        answer = llm.invoke(f"Контекст: {context}\nВопрос: {sq}\nКраткий ответ:")
        intermediate_answers[sq] = answer

    # Шаг 3: Синтез финального ответа из промежуточных
    synthesis_prompt = f"Объедини ответы в один связный ответ на вопрос: {complex_query}\n\n"
    for q, a in intermediate_answers.items():
        synthesis_prompt += f"Q: {q}\nA: {a}\n\n"
    return llm.invoke(synthesis_prompt)

# Пример: "Сравни цены на GPU у поставщика X и Y и скажи, у кого дешевле"
answer = multi_hop_qa(
    "Сравни цены на A100 у NVIDIA и AMD. Кто предлагает лучшую цену за терафлопс?",
    retriever, llm
)

# 6. ОЦЕНКА КАЧЕСТВА RAG С RAGAS

# pip install ragas
from ragas import evaluate
from ragas.metrics import faithfulness, answer_relevancy, context_precision
from datasets import Dataset

# Подготовка датасета для оценки
eval_data = {
    "question": ["Какие KPI в Q1?", "Кто CEO компании?", "Бюджет на 2025?"],
    "answer": ["KPI: выручка +15%...", "CEO: Иванов И.И.", "Бюджет: 50 млн..."],
    "contexts": [["отчёт Q1 2025"], ["страница команды"], ["финплан 2025"]],
    "ground_truth": ["Выручка выросла на 15% в Q1 2025", "CEO Иванов Иван", "50 млн рублей"],
}
dataset = Dataset.from_dict(eval_data)

# Расчёт метрик
result = evaluate(
    dataset,
    metrics=[faithfulness, answer_relevancy, context_precision],
)
print(f"Faithfulness: {result['faithfulness']:.2%}")
print(f"Answer Relevancy: {result['answer_relevancy']:.2%}")
print(f"Context Precision: {result['context_precision']:.2%}")

🔗 Полезные ссылки

📖 LangChain RAG Tutorial📖 ChromaDB Docs📖 RAGAS Documentation📖 LangGraph