English translation is not available yet. Showing Russian content.

RAG на 100 PDF

ТЕХНИЧЕСКОЕ ЗАДАНИЕ: RAG на 100 PDF

1. Цель задачи

Создать рабочую Retrieval-Augmented Generation (RAG) систему, способную отвечать на вопросы по коллекции из 100 PDF-документов. Система должна индексировать документы с помощью Docling и BGE-M3, хранить эмбеддинги в Qdrant, а генерацию ответов выполнять через Llama-3. Главный критерий успеха: на 10 заранее подготовленных вопросах модель даёт ответы, содержащие прямые цитаты из исходных PDF с указанием страницы.

Ключевой результат Воспроизводимый пайплайн RAG, демонстрирующий качественные ответы с цитатами на 10 вопросов.

2. Исходные данные

Перед началом необходимо подготовить:

Что нужноОткуда взять
100 PDF-файловОткрытые источники: arXiv (https://arxiv.org), технические отчёты, лицензионно-свободные книги (Project Gutenberg). Пример: 100 статей по NLP или AI.
Python 3.10+Официальный сайт Python или conda
DockerDocker Desktop (https://www.[docker](/wiki/Docker).com/products/docker-desktop)
Ollama (для Llama-3)https://ollama.com – скачать и установить, затем ollama pull llama3
Qdrant (векторная БД)Docker-образ Qdrant: docker pull qdrant/qdrant или облачный экземпляр (бесплатный tier)
Библиотеки Pythonpip install: docling, qdrant-client, sentence-transformers, llama-cpp-python (или openai если через Ollama API), torch, transformers, pypdf2 (резерв)

Если нет реального инструмента — симулируем:

  1. Нет 100 PDF – используем любые текстовые файлы (.txt) или конвертируем несколько PDF, размножив их копиями с разными названиями. Либо создать скрипт, который генерирует фейковые PDF через reportlab.
  2. Нет доступа к Qdrant – поднимаем локальный Docker-контейнер Qdrant по инструкции выше. Если Docker недоступен, используем SQLite с сохранением эмбеддингов (имитация векторной БД).
  3. Нет Ollama – используем API любой открытой LLM (например, через Hugging Face Inference API) или запускаем Llama-3 через llama.cpp локально.
  4. Docling не работает – извлекаем текст через PyMuPDF (fitz) или pdfplumber, а для изображений используем OCR (pytesseract).

3. Технологический стек

КомпонентИнструментыНазначение
Извлечение текста из PDFDocling (AI-парсер), PyMuPDF (резерв)Преобразование PDF в Markdown/текст с сохранением структуры (заголовки, таблицы)
Chunking (деление на чанки)RecursiveCharacterTextSplitter из langchain_text_splitters или DoclingНарезка документа на куски ~512 токенов с overlap 128
Embedding модельBGE-M3 (BAAI/bge-m3)Генерация эмбеддингов для чанков (размерность 1024)
Векторная база данныхQdrant (локально или облачно)Хранение эмбеддингов и быстрый поиск по косинусной близости
LLM для генерацииLlama-3 (8B или 70B через Ollama)Генерация ответа с цитатами на основе релевантных чанков
ОркестрацияPython (Jupyter notebook / скрипт)Пайплайн индексации, запросов, оценки

4. Этапы выполнения

Этап 1: Подготовка окружения и сбор данных (~2 часа)

Действия

  1. Установить Docker и запустить Qdrant
    docker pull qdrant/qdrant
    docker run -p 6333:6333 -p 6334:6334 qdrant/qdrant
    
    Проверить доступность: curl http://localhost:6333.
  2. Установить Ollama и скачать Llama-3
    curl -fsSL https://ollama.com/install.sh | sh
    ollama pull llama3
    
    Проверить: ollama run llama3 "Привет".
  3. Создать Python-окружение
    python -m venv rag_env
    source rag_env/bin/activate
    pip install docling qdrant-client sentence-transformers llama-cpp-python pypdf2
    
  4. Собрать 100 PDF
    • Скачать из arXiv через arxiv Python библиотеку (например, последние 100 статей по выбранной теме).
    • Или использовать готовый набор PDF из репозитория (например, https://github.com/example/pdf-collection).
    • Положить все файлы в папку ./pdfs.
  5. Создать файл с 10 тестовыми вопросами (questions.txt):
    • Вопросы должны требовать поиска точной информации (цифры, даты, имена).
    • Пример: "Какое количество слоёв в BERT-base?" (если есть статья про BERT).

Ожидаемый результат этапа Запущенный Qdrant, работающий Ollama, папка ./pdfs с 100 PDF, файл questions.txt.

Этап 2: Индексация документов (~3 часа)

Действия

  1. Написать скрипт index.py, который делает:
    • Для каждого PDF из ./pdfs:
      • Извлекает текст через Document.from_pdf() из Docling.
      • Сохраняет структуру (заголовки, таблицы) как Markdown.
      • Разбивает на чанки размером 512 токенов с overlap 128 с помощью RecursiveCharacterTextSplitter (использовать HuggingFaceTokenizer от BGE-M3 для корректного подсчёта).
    • Для каждого чанка вычисляет эмбеддинг моделью BGE-M3:
      from sentence_transformers import SentenceTransformer
      model = SentenceTransformer('BAAI/bge-m3', device='cuda')
      embeddings = model.encode([chunk.text], normalize_embeddings=True)
      
    • Загружает чанк и эмбеддинг в Qdrant:
      from qdrant_client import QdrantClient
      client = QdrantClient(host='localhost', port=6333)
      client.upsert(
          collection_name='pdf_rag',
          points=[
              PointStruct(
                  id=idx,
                  vector=embeddings[0].tolist(),
                  payload={
                      'text': chunk.text,
                      'source': pdf_name,
                      'page': chunk.metadata.page_number,
                      'chunk_id': chunk.chunk_id
                  }
              )
          ]
      )
      
    • Использовать батчинг для ускорения (batch_size=32).
  2. Создать коллекцию в Qdrant с настройками:
    • Размерность вектора: 1024 (BGE-M3).
    • Метрика: Cosine.
    • Индекс: HNSW (по умолчанию).
  3. Учесть обработку ошибок – если PDF битый или Docling не справляется, использовать резервный PyMuPDF.

Ожидаемый результат этапа В Qdrant создана коллекция pdf_rag с ~10 000–30 000 чанков (зависит от размера PDF). Скрипт выводит статистику: количество проиндексированных документов и чанков.

Этап 3: Реализация Retrieval (~1 час)

Действия

  1. Написать функцию retrieve(query, top_k=5):
    • Векторизовать запрос с помощью той же BGE-M3.
    • Выполнить поиск в Qdrant: client.search(collection_name='pdf_rag', query_vector=query_emb, limit=top_k).
    • Вернуть список чанков с текстом, источником и страницей.
  2. Выбрать оптимальный top_k – протестировать на 3 вопросах из questions.txt:
    • Попробовать top_k=3,5,10.
    • Оценить релевантность первых чанков (субъективно).
  3. Настроить фильтры – если необходимо ограничить поиск по определённым PDF (опционально).

Ожидаемый результат этапа Функция retrieve возвращает 5–10 чанков с хорошим покрытием информации для тестовых вопросов.

Этап 4: Интеграция с Llama-3 и генерация ответов (~2 часа)

Действия

  1. Написать функцию generate_answer(query, chunks):
    • Сформировать промпт, содержащий:
      • Инструкцию: «Ответь на вопрос, используя предоставленные фрагменты. Обязательно укажи цитаты из фрагментов и номер страницы. Если информации недостаточно, скажи "не знаю".»
      • Список чанков (текст + источник + страница).
      • Вопрос пользователя.
    • Пример промпта:
      Ты — эксперт, работающий с документами. Ниже приведены фрагменты из PDF-документов.
      Отвечай на вопрос на русском. Для каждого утверждения указывай ссылку на фрагмент и страницу.
      
      Фрагменты:
      [1] «...» (Источник: paper1.pdf, стр. 5)
      [2] «...» (Источник: paper2.pdf, стр. 12)
      
      Вопрос: {query}
      Ответ:
      
    • Отправить промпт в Llama-3 через Ollama API:
      import requests
      response = requests.post('http://localhost:11434/api/generate',
                               json={'model': 'llama3', 'prompt': prompt, 'stream': False})
      
    • Распарсить ответ.
  2. Протестировать на 10 вопросах – проверить, что ответы содержат цитаты со ссылками.
  3. Добавить пост-обработку – если цитаты отсутствуют, можно повторить генерацию с более строгим промптом.

Ожидаемый результат этапа Скрипт rag_pipeline.py, который на вход принимает вопрос, а на выходе даёт ответ с цитатами.

Этап 5: Тестирование и валидация (~1 час)

Действия

  1. Прогнать 10 вопросов из questions.txt – записать ответы в файл answers.md.
  2. Оценить качество по двум метрикам
    • Наличие цитат каждый ответ должен содержать хотя бы одну прямую цитату из PDF с указанием страницы.
    • Фактологическая точность сравнить ответ с истинным содержанием PDF (вручную).
  3. Зафиксировать результат – для каждого вопроса: PASS/FAIL.
  4. При необходимости итеративно улучшить – изменить промпт, увеличить top_k, улучшить chunking.

Ожидаемый результат этапа Файл answers.md с 10 вопросами и ответами, оценка PASS/FAIL для каждого. Система готова к демонстрации.

5. Критерии приемки (Definition of Done)

  • Все 100 PDF успешно проиндексированы в Qdrant (нет ошибок).
  • Количество чанков в коллекции > 5000.
  • Функция retrieve возвращает релевантные чанки для всех 10 тестовых запросов.
  • Для каждого из 10 вопросов получен ответ от Llama-3.
  • Каждый ответ содержит как минимум одну прямую цитату из исходного PDF.
  • Цитаты включают ссылку на имя файла PDF и номер страницы.
  • Ответы не содержат грубых фактических ошибок (проверено вручную).
  • Пайплайн (индексация + запрос) воспроизводим из README.md.
  • Код опубликован в Git-репозитории с лицензией MIT.
  • В репозитории присутствует файл requirements.txt и Docker-compose.yml (опционально).

6. Ожидаемый результат

  • Основной артефакт Папка проекта с файлами:
    • src/ – скрипты index.py, retrieve.py, generate.py, rag_pipeline.py.
    • data/pdfs/ – 100 PDF (или скрипт для их скачивания).
    • questions.txt – 10 тестовых вопросов.
    • answers.md – сгенерированные ответы с цитатами.
    • README.md – описание, инструкция по запуску, примеры.
    • requirements.txt – список зависимостей.
    • (Опционально) docker-compose.yml для Qdrant.
  • Содержание answers.md
    # Ответы RAG-системы
    
    ## Вопрос 1: Какое количество слоёв в BERT-base?
    **Ответ:** В архитектуре BERT-base используется 12 слоёв (трансформерных блоков).
    > Цитата: "BERT-base имеет 12 трансформерных слоёв" (bert_paper.pdf, стр. 3)
    
  • Дополнительно Jupyter notebook с демонстрацией полного пайплайна.

7. Возможные сложности и их решение

СложностьРешение
Docling не парсит PDF с изображениями (сканы)Использовать предварительный OCR (pytesseract) или пропустить такие файлы. Для чистоты эксперимента выбрать PDF с текстовым слоем.
BGE-M3 требует много памятиИспользовать device='cpu' или уменьшить batch_size. Модель можно загрузить в float16.
Qdrant падает при большой вставкеУвеличить лимиты памяти контейнера, использовать batch insert (до 1000 точек за раз).
Llama-3 галлюцинирует и не цитируетУжесточить промпт, добавить few-shot примеры. Проверить, что в чанках действительно есть ответ.
Медленная индексация 100 PDFРаспараллелить обработку (multiprocessing.Pool). Использовать GPU для эмбеддингов.
Вопросы не покрываются коллекциейДобавить более релевантные PDF или расширить список вопросов.

8. Бюджет времени (оценка)

ЭтапВремя
Этап 1: Подготовка окружения и сбор данных2 часа
Этап 2: Индексация документов3 часа
Этап 3: Реализация Retrieval1 час
Этап 4: Интеграция с Llama-32 часа
Этап 5: Тестирование и валидация1 час
Итого9 часов

Примечание Для первого раза заложите +3 часа на отладку и настройку окружения. Рекомендуется выполнять задачу в течение 2–3 дней по 4 часа.

9. Связанные вопросы из базы знаний

ВопросТема
10Что такое RAG?
45Как работают векторные базы данных?
89Сравнение embedding моделей (BGE-M3 vs Ada)
134Docling vs Unstructured для парсинга PDF
201Chunking strategies для RAG
278Подготовка данных для RAG (чистка, dedup)
312Llama-3 vs GPT-4 для генерации ответов
456Оценка качества RAG (RAGAS, Faithfulness)
590Оптимизация поиска в Qdrant (HNSW, quantization)
701Как реализовать автономный RAG-сервис с FastAPI

10. Чек-лист самопроверки

  • Я установил всё окружение (Docker, Ollama, Python) и запустил Qdrant.
  • Я успешно проиндексировал все 100 PDF без ошибок.
  • Я написал функцию retrieve, которая возвращает чанки с метаданными.
  • Я протестировал генерацию ответов на нескольких вопросах, и ответы содержат цитаты.
  • Я зафиксировал PASS/FAIL для 10 вопросов и исправил ошибки, если они были.
  • Я проверил, что код воспроизводим на чистом окружении (документация, requirements.txt).
  • Я выложил проект в репозиторий с README.