Самодельный RAG без LangChain: чанкинг, эмбеддинги, гибридный поиск (вектор + BM25 через RRF) и генерация ответа через локальную модель в Ollama. Реализация к статье «Мой первый RAG без LangChain» (article.md).
База знаний — двенадцать внутренних политик компании в data/policies/: отпуск, спорт, VPN,
инциденты, онбординг, техника, командировки, удалёнка, безопасность, обучение, больничный,
закупки. Рядом лежит data/golden.json — размеченный набор вопросов, на котором меряется
качество поиска.
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtДля генерации ответов нужен запущенный Ollama с моделью:
ollama pull qwen3:8b
ollama servepython cli.py "как получить впн"Скрипт напечатает найденные фрагменты с указанием документа, из которого они пришли, и финальный ответ модели со ссылками на фрагменты в квадратных скобках.
Настройки через переменные окружения:
| переменная | по умолчанию | зачем |
|---|---|---|
OLLAMA_HOST |
http://localhost:11434 |
адрес Ollama |
RAG_MODEL |
qwen3:8b |
модель генерации |
RAG_NUM_CTX |
8192 |
окно контекста; Ollama по умолчанию даёт 4096 и молча режет промпт |
RAG_THINK |
не задана | рассуждения thinking-моделей: 1 включить, 0 выключить. Если не задана, ключ think не отправляется вовсе — см. грабли ниже |
RAG_TIMEOUT |
300 |
таймаут запроса в секундах |
python eval.pyСчитает hit@1/3/5 и MRR@10 для трёх ретриверов: только BM25, только вектор и гибрид.
rag/
chunking.py — разбиение на чанки с перекрытием, Chunk хранит источник и позицию
embeddings.py — эмбеддинги через intfloat/multilingual-e5-large
search.py — BM25 со стеммингом, отсев нулевых score, Reciprocal Rank Fusion
generate.py — запрос к Ollama с промптом «отвечай только по фрагментам»
pipeline.py — MiniRAG: гибридный retrieve (вектор + BM25 через RRF)
corpus.py — загрузка документов и размеченного набора
data/policies/ — база знаний
data/golden.json — вопросы с разметкой правильного документа
cli.py — точка входа
eval.py — метрики поиска
tests/ — тесты
pip install -r requirements-dev.txt
pytestТесты в tests/test_retrieval.py поднимают настоящую модель эмбеддингов и по умолчанию
пропускаются. Чтобы прогнать и их:
RAG_SLOW_TESTS=1 pytest- Windows, Smart App Control. Если при импорте падает
ImportError: DLL load failed ... An Application Control policy has blocked this file, политика заблокировала свежий бинарник scipy. Помогает откат на предыдущую версию:pip install "scipy==1.16.2". - Кириллица в консоли Windows. Перед запуском стоит выставить
PYTHONIOENCODING=utf-8, иначе вывод превратится в кракозябры. - Первый запуск долгий. Модель эмбеддингов весит около 2 ГБ и скачивается с HuggingFace,
а индекс строится заново при каждом старте
cli.py. "think": falseв Ollama. На версии 0.32.15 сqwen3этот флаг не выключает рассуждения, а отменяет их отделение от ответа: весь ход мыслей приезжает в полеresponseвместе с висящим тегом</think>. Поэтому по умолчанию ключthinkне отправляется, а ответ дополнительно чистится вstrip_thinking().