Большинство RAG-туториалов останавливаются на схеме «нарезал PDF, положил эмбеддинги, спросил чат». Этого хватает для демо на одном файле и ломается, как только появляются уточняющие вопросы, несколько тем в одном запросе и модель, которая отвечает «из головы», не открыв индекс. Репозиторий agentic-rag-for-dummies как раз про этот разрыв: агентный RAG на LangGraph, с иерархическими чанками, гибридным поиском и паузой, если запрос слишком мутный.
Автор: Giovanni Pasqualino (GiovanniPasq). Лицензия MIT, копирайт 2025. Репозиторий создан 13 октября 2025 года. Последний пуш на момент материала: 25 июля 2026, коммит обновления зависимостей. 21 июня 2026 туда же ушли память диалога, многошаговое уточнение, трассировка и оценка через RAGAS. На 21 августа 2026 GitHub показывает 3937 звёзд и 509 форков. Это учебный стенд с расширяемой раскладкой модулей, не коробочный SaaS.
Чем агентный RAG отличается от «эмбеддинги плюс prompt»
Обычный RAG один раз достаёт k ближайших кусков и скармливает их модели. Агентный RAG решает, нужно ли искать, что именно искать, достаточно ли найденного и не стоит ли спросить человека. В этом проекте цепочка такая:
запрос → сводка диалога → переписывание → уточнение → параллельные агенты → агрегация ответа
На практике это закрывает три бытовых сценария. «Как обновить это?» после вопроса про SQL: память должна понять, что «это» = SQL. «Расскажи про ту штуку»: граф обязан остановиться и спросить, а не галлюцинировать. «Что такое JavaScript? Что такое Python?»: запрос режется на подвопросы и уходит в параллельные подграфы (map-reduce).
Официальный учебник LangGraph тоже строит retrieval-агента, который сам решает, звать векторное хранилище или ответить сразу. См. Build a custom RAG agent with LangGraph. Разница репозитория GiovanniPasq: готовый конвейер PDF → Markdown → parent/child → Qdrant, Gradio-интерфейс и ноутбуки, а не только каркас графа.
Как устроен поиск: маленькие чанки ищут, большие отвечают
Документы режутся дважды. Parent: крупные куски по Markdown-заголовкам H1–H3, с целевым размером 2000–4000 символов. Child: мелкие куски по 500 символов с overlap 100, связанные с родителем. В индекс Qdrant кладут детей (точное попадание), а в ответ подтягивают родителя (контекст). Это стандартный parent-child трюк, который в «наивном» RAG часто пропускают: маленький чанк хорошо матчится, но в нём нет соседних абзацев.
Поиск гибридный: плотные эмбеддинги Qwen/Qwen3-Embedding-0.6B плюс разреженный BM25 через Qdrant/bm25. Порог релевантности по умолчанию 0.4, число дочерних чанков DEFAULT_RETRIEVAL_K = 7. Родители лежат не в векторе, а в файловом store (JSON). Векторная база: локальный Qdrant по пути qdrant_db, не облако.
Инструменты агента в коде названы прямо: search_child_chunks и retrieve_parent_chunks. Если детей мало или они слабые, агент может перезапросить (self-correction). Чтобы граф не зациклился, стоят жёсткие потолки: максимум 8 вызовов инструментов, 10 итераций, recursion limit 50.
Два входа: ноутбук для учёбы, project/ для сборки
В README это разделено явно.
Учебный путь: Jupyter/Colab, notebooks/agentic_rag.ipynb. Рядом лежат pdf_to_markdown.ipynb, observability.ipynb и evaluation.ipynb (метрики RAGAS по фактическому контексту, который агент взял из инструментов). На обычном hosted Colab нет Ollama: ячейку с локальной моделью нужно заменить на облачный провайдер из документации.
Рабочий путь: каталог project/. Точка входа project/app.py, настройки в project/config.py, граф в project/rag_agent/, Gradio в project/ui/. Приложение по умолчанию Ollama-first. В README есть примеры подмены на OpenAI, Anthropic и Google Gemini через LangChain. Имена облачных моделей в примерах быстро устаревают: это нужно сверять с документацией провайдера, а не копировать строку навсегда.
Как поднять стенд локально
Требования из project/README: Python 3.11+ и либо локальный Ollama, либо ключи облачного провайдера. В requirements.txt на коммите от 25 июля 2026 зафиксированы, среди прочего, LangGraph 1.2.9, Gradio 6.20.0, langchain-qdrant 1.1.0, ragas 0.4.3, pymupdf4llm 1.28.0. Бейдж README говорит LangGraph 1.2+.
Локальная модель по умолчанию:
ollama pull granite4.1:8b
Клон и зависимости (как в корневом README):
git clone https://github.com/GiovanniPasq/agentic-rag-for-dummies cd agentic-rag-for-dummies python -m venv .venv source .venv/bin/activate pip install -r requirements.txt python project/app.py
Интерфейс Gradio по умолчанию слушает http://localhost:7860 (в README также указан пример http://127.0.0.1:7860). Документы загружаются в UI. Автор прямо пишет: in-memory демо рассчитано на одного локального пользователя. Если поднимать это на нескольких сессиях, каждой нужен свой LangGraph thread ID. Без этого память и уточнения поедут между людьми.
Альтернатива через uv:
uv venv .venv source .venv/bin/activate uv pip install -r requirements.txt
Docker есть в project/Dockerfile. Сборка из корня репозитория:
docker build -t agentic-rag -f project/Dockerfile . docker run --name rag-assistant -p 7860:7860 agentic-rag
Официальная оговорка по памяти: минимум 8 ГБ RAM, выделенных Docker, 12 ГБ рекомендуют, если одновременно крутятся эмбеддинги Qwen и локальная Ollama. На Windows и macOS Docker идёт через Linux VM, индексация PDF может быть заметно медленнее, чем на голом Linux. GPU: флаг --gpus all, только NVIDIA, если драйвер и runtime это умеют.
Что крутить в config.py, не расползаясь по всему коду
Центральный файл: project/config.py. Менять провайдера «навсегда» недостаточно одной строки: для облака ещё правят инициализацию LLM в project/core/rag_system.py и ставят SDK. Для эмбеддингов достаточно сменить DENSE_MODEL, но коллекцию Qdrant нужно пересобрать. Старый индекс с другой размерностью вектора не совпадёт с новой моделью.
Значения по умолчанию из текущего config.py:
DENSE_MODEL = "Qwen/Qwen3-Embedding-0.6B" SPARSE_MODEL = "Qdrant/bm25" LLM_MODEL = "granite4.1:8b" JUDGE_MODEL = "ministral-3:3b-instruct-2512-q8_0" LLM_TEMPERATURE = 0 RETRIEVAL_SCORE_THRESHOLD = 0.4 DEFAULT_RETRIEVAL_K = 7 MAX_TOOL_CALLS = 8 MAX_ITERATIONS = 10 CHILD_CHUNK_SIZE = 500 CHILD_CHUNK_OVERLAP = 100 MIN_PARENT_SIZE = 2000 MAX_PARENT_SIZE = 4000
Судья JUDGE_MODEL нужен оценке RAGAS, не чату. Температура 0: детерминированнее, меньше «творчества» в цитатах из документации. После смены эмбеддингов в учебном UI: Clear All, при необходимости перезапуск, затем заново залить PDF. Уже проиндексированные файлы специально пропускаются, чтобы не плодить дубли.
Где обычно ломается
Это не список из головы, а сводка из troubleshooting корневого README и project/README.
- Модель меньше 8B и без нормального tool calling: игнорирует инструкцию «иди в поиск», врёт агрегацию, не вызывает retrieval. Автор прямо советует 8B+.
- Ollama не запущена или имя модели не совпало с
LLM_MODEL: ошибка «model not found». - Сменили эмбеддинги, не пересобрали коллекцию: mismatch размерности вектора.
- Пустой retrieval: PDF не прошли в Markdown, другое имя
CHILD_COLLECTION, порог 0.4 отсёк слабые совпадения. - Ответ «из модели», без документов: системный промпт недостаточно жёстко требует поиск. Правится в
project/rag_agent/prompts.py. - Агент крутится слишком долго или сдаётся рано:
MAX_TOOL_CALLS/MAX_ITERATIONS. - После сжатия контекста пропали детали: поднять
BASE_TOKEN_THRESHOLDили ослабить сжатие (TOKEN_GROWTH_FACTOR). - Несколько пользователей на одном thread: память диалога и HITL-уточнения смешаются. Для продакшена это не «включить HTTPS», а завести идентификатор сессии на каждого.
Отдельно: PDF → Markdown идёт через pymupdf4llm. Сканы без текстового слоя, кривые таблицы и колонтитулы дадут плохой Markdown, а дальше уже любой RAG будет искать мусор. Для разбора нарезки автор предлагает соседний инструмент Chunky.
Как понять, что стенд живой
После python project/app.py в браузере должна открыться Gradio на порту 7860. Загрузите PDF, дождитесь индексации, задайте вопрос, ответ на который есть только в файле, не в параметрах модели. Затем задайте follow-up с местоимением («а как это обновить?»). Если агент потерял тему, смотрите память и rewrite, не «ещё раз увеличьте k».
Опционально включают Langfuse (LANGFUSE_ENABLED=true плюс ключи). В трассе видны узлы summarize_history, rewrite_query, orchestrator, compress_context, aggregate_answers и вызовы двух retrieval-инструментов. Без ключей приложение стартует так же: трассировка выключена по умолчанию.
Оценку качества не путайте с «мне понравилось». Ноутбук evaluation.ipynb считает RAGAS по тем child/parent кускам, которые агент реально прочитал. Для судьи в config прописана отдельная локальная модель, не тот же granite, что отвечает в чате.
Кому это имеет смысл, а кому нет
Имеет смысл, если нужно понять агентный RAG руками: документация, внутренние PDF, база знаний, черновик бота по регламентам. Стек знакомый админам и питонистам: venv или Docker, Ollama на той же машине, Qdrant файлом на диске, порт 7860. Для сайта на WordPress это не замена поиска по постам и не плагин. Это отдельный Python-контур рядом, если вы отвечаете по своим PDF, а не по wp_posts.
Не стоит ждать из коробки мультитенантности, ACL на документы, очереди индексации и SLA. Parent store на JSON и in-memory thread годятся для учёбы и стенда. На проде те же идеи (parent-child, гибридный поиск, HITL, лимиты инструментов) обычно переносят в свой сервис, а не выставляют учебный Gradio в интернет.
Итог короткий. Наивный RAG заканчивается на «нашли 5 чанков». Этот репозиторий показывает следующий слой: граф, уточнение, параллельные подвопросы и контроль, чтобы агент не крутился вечно. Поднять можно за вечер на Python 3.11 и Ollama. Превращать в публичный ассистент без сессий, лимитов и нормального хранения родителей рано.