Сам инструмент тут: https://johngear.dev/n-gpt_rag_llm_service/
1. Препроцессор (видео -> текст).
Представим, что у нас есть какой-то большой пласт информации в формате видео-лекций. Например, это видео с YouTube, которые мы тщательно отбирали в процессе изучения machine learning
Чтобы получить из этих видео текстовую информацию, нам нужно выдернуть из них субтитры. Я выбрал наиболее подходящий OSS-инструмент по соотношению ресурсы/качество — Faster Whisper.
Faster Whisper хорошо работает даже на CPU, не требует какой-то сверхтонкой настройки и спустя 5 минут после разворота в вашем виртуальном окружении готов «воевать» с видео.
Итог на этом этапе: посекундная транскрипция всех наших видео в формате txt документов.
2. Нормализация данных.
Теперь, когда у нас есть данные в виде каких-то txt-документов, нам нужно “причесать” их и привести к единому формату. Это нужно, чтобы в дальнейшем корректно разбить такие данные на чанки.
На этом этапе, принзаюсь, я сильно не заморачивался: были удалены таймкоды, текст соединён в единую строку (удалены переносы) и удалены некоторые технические символы (запятые и точки не трогал).
3. Чанкинг.
Нам надо взять все наши тексты и разбить на чанки какого-то размера (подробнее об этом писал в статье: https://johngear.dev/articles/chankovanie-v-rag.html)
У меня получилось 1667 чанков. Резал текст до CHUNK_SIZE = 1600 символов (минимально от 1000) по нахождению первой точки < CHUNK_SIZE (т.к. точка в моих текстах это окончание предложения). OVERLAP использовал в размере 250 символов. Для бейслайна считаю это удачным решением на старте.
4. Переводим текст в embedding.
Вот здесь началось самое интересное. Сначала я полез на опенсорсную bge-m3, которую поставил локально. Перевел чанки в embeddings и сохранил в БД. Но потом понял, что bge-m3 медленно работает на CPU, а т.к. нам в проде тоже придется переводить вопросы пользователей в embeddings, модель просто будет тормозить на моем простеньком линукс сервере. Ошибка архитектуры получается.
Но решение я нашел быстро: я взял по API в OpenRouter (вы можете использовать OpenAI) эмбеддинг-модель text-embedding-3-small от OpenAI. Да, несмотря на то, что это стоит денег, но в реальности решает две серьезных проблемы: скорость - text-embedding-3-small по API работает в десятки раз быстрее, чем bge-m3 работала у меня на локальном компе, цена - $0.00002 за 1000 токенов и даже мои 1,5 млн символов обошлись мне в виде embedding-векторов в 0.75 цента.
А т.к. мой конечный инференс — это копеечный сервер (2 CPU, 4 ГБ ОЗУ)то там такая архитектура летает просто на ура.
5. Vector DB.
Тут всё достаточно стандартно, и придумывать велосипед я не стал. Qdrant и точка.
6. Retriever.
В целом тут тоже всё просто. distance=Distance.COSINE. Мне важно было сделать максимально простой бейслайн без всяких заморочек, чтобы понять качество работы «из коробки». И в целом меня это качество устраивает.

7. Генерирующая модель.
По логике с embedding-моделью решил и LLM брать через API OpenRouter, а именно gpt-4o-mini.
Плюсы те же. Модель качественная и API достаточно стабильное, а стоит копейки: Input $0.15 за 1 млн токенов, Output $0.60 за 1 млн токенов. Плюс в OpenRouter всегда доступны два провайдера Azure или Fireworks и если один из провайдеров простаивает, иногда цена на токены падает дешевле чем в API OpenAI (используйте этот лайфхак).
С промтингом модели немного поэкспериментировал. Впрочем вы можете сами посмотреть на гитхабе N_gpt/src/rag_service.py
8. Ну и конечно API.
Без старого доброго дедушки Flask не обошлось. Упаковал всё в API. Стандартно: @app.get("/health"), @app.post("/ask") для вопросов и еще прикрутил @app.get("/logger") логирование, чтобы можно было «побаловаться» и показать пользователю как “ответ” путешествовал по пути к нему.
По итогу, всё засунул в Docker и закинул себе на сервер.
Итак итоговая схема получилась достаточно простая
embedding → Qdrant → top_k chunks → prompt → LLM → answer
Что сознательно не делал - Reranker. Да, cross-encoder reranker заметно улучшает финальную выдачу. Но в моем проекте он излишний. У меня так устроена структура информации, что она идёт последовательно от темы к теме и повторений там минимальное количество. Плюс чанков не так много - всего 1667. В процессе тестов я понял, что reranker мне не нужен и top-N кандидаты выглядят уже достойно на этапе поиска по косинусному сходству.