Self-hosted RAG на локальных моделях: с чего начать
Когда облачные API нельзя, а искать по своим документам нужно. Минимальный стек и порядок шагов.
Облачные API удобны, пока не появляется требование держать данные внутри периметра. Тогда собирают self-hosted RAG: поиск по своим документам плюс локальная модель, которая отвечает по найденным фрагментам.
Из чего состоит
- Хранилище документов. Папка с файлами и скрипт, который их читает.
- Разбиение на фрагменты. 500–800 токенов с перекрытием в 100–150: мельче — теряется контекст, крупнее — поиск находит не то.
- Эмбеддинги. Локальная модель эмбеддингов, которая работает на процессоре или видеокарте.
- Векторный индекс. Любая локальная база: даже SQLite с расширением хватает на десятки тысяч фрагментов.
- Генерация. Локальная LLM, которой в промпт кладут найденные фрагменты и вопрос.
Порядок шагов
Сначала соберите корпус и проверьте поиск без модели: введите вопрос и посмотрите, какие фрагменты находятся. Если нужного фрагмента нет в топ-5, генерация не спасёт — чините разбиение и запросы.

Минимальный каркас поиска выглядит так:
import sqlite3, numpy as np
def top_k(question_vec, k=5):
con = sqlite3.connect("rag.db")
rows = con.execute("select id, text, vec from chunks").fetchall()
scored = [(float(np.dot(question_vec, np.frombuffer(v, dtype="float32"))), i, t)
for i, t, v in rows]
scored.sort(reverse=True)
return scored[:k]

Потом подключите модель и заставьте её отвечать только по фрагментам, с указанием номера. Ответ без ссылки на фрагмент считайте неподтверждённым.
Частые ошибки
- Один индекс на всё. Регламенты и переписка плохо ищутся вместе: разные языки, разная длина, разные синонимы.
- Промпт без ограничений. Без фразы «если в фрагментах нет ответа — скажи об этом» модель додумывает.
- Замер на трёх вопросах. Нужен набор из 30–50 реальных вопросов с известными ответами, иначе улучшения нечем измерять.