Pavel SkvortsovPavel Skvortsov
← Назад
05

RAG Knowledge Base

Telegram-бот, который превращает загруженные PDF в поисковую базу знаний. Задавай вопросы обычным текстом - бот находит релевантные фрагменты и генерирует обоснованные ответы с указанием источников.

PythonLangChainChromaDBOpenAIDocker

"Загрузи PDF. Задай вопрос. Получи ответ с указанием страниц."

1000
символов на чанк, перекрытие 200
top 4
чанка извлекается на вопрос
1536-мерный
вектор эмбеддинга
локально
ChromaDB - без внешней векторной БД
Как это работает
Загрузка PDF
Извлечение PyMuPDF
Чанкинг + перекрытие
text-embedding-3-small
ChromaDB
GPT-4o-mini
Ответ + источники
Структура проекта
rag/ingest.pyPDF → PyMuPDF → чанки → эмбеддинги → ChromaDB
rag/retriever.pyвопрос → similarity search → GPT-4o-mini → ответ + источники
bot/handlers.pyTelegram хэндлеры: /start /list /clear, загрузка PDF, вопросы
data/chroma/персистентное хранилище ChromaDB - монтируется как Docker volume
Важная деталь

Модель явно инструктируется отвечать только по извлечённому контексту - не по обучающим данным. Это привязывает каждый ответ к твоим документам и делает галлюцинации структурно невозможными для вопросов вне контекста.

Код
# retriever.py - полный RAG-пайплайн в одной функции
  def ask(question: str) -> str:
      # 1. Эмбеддинг вопроса в том же векторном пространстве что и чанки
      docs = vectorstore.similarity_search(question, k=TOP_K)
  
      if not docs:
          return "Релевантные документы не найдены."
  
      # 2. Контекст из топ-4 извлечённых чанков
      context = "\n\n".join(d.page_content for d in docs)
  
      # 3. GPT-4o-mini отвечает ТОЛЬКО по контексту - не по обучающим данным
      response = openai.chat.completions.create(
          model="gpt-4o-mini",
          messages=[
              {"role": "system", "content":
                  "Отвечай только на основе контекста ниже. "
                  "Если ответа нет в контексте, так и скажи."},
              {"role": "user", "content":
                  f"Контекст:\n{context}\n\nВопрос: {question}"}
          ]
      )
  
      # 4. Дедупликация источников и добавление к ответу
      sources = list({
          f"{d.metadata['source']} (стр. {d.metadata['page']})"
          for d in docs
      })
      return response.choices[0].message.content + "\n\n📎 Источники:\n" + "\n".join(f" • {s}" for s in sources)
Скриншоты
demo
Функции
  • Загрузка любого текстового PDF прямо в Telegram - без веб-интерфейса
  • Умный чанкинг с перекрытием - контекст сохраняется на границах чанков
  • Семантический поиск по всем проиндексированным документам одновременно
  • GPT-4o-mini отвечает строго по извлечённому контексту - без галлюцинаций из обучающих данных
  • Каждый ответ содержит имя файла-источника и номер страницы
  • Поддержка нескольких документов - вопросы охватывают все PDF сразу
  • Определение дубликатов - один и тот же файл не индексируется дважды
  • Защита от сканов, файлов 500+ страниц и не-PDF загрузок
  • Команды /list и /clear для управления базой знаний
  • ChromaDB сохраняется через Docker volume - данные не теряются при перезапуске