Безопасность и правоHabr AI→ оригинал

Разработчик собрал RAG-систему на локальных моделях и поднял точность поиска с 20% до 53%

Разработчик (автор проекта на GitHub под ником tzar26) собрал полностью自хостинговую RAG-систему для анализа новостей — без единого обращения к OpenAI или Anthropic. Весь стек — локальные модели Qwen 2.5 14B и BGE-M3 через Ollama, база на PostgreSQL. Главная цель — независимость от внешних провайдеров и полный контроль над данными. Сначала поиск по 12 000 собранных статей работал только на векторных эмбеддингах, и на запрос «золото» система выдавала новости про фигуриста, который взял золото на соревнованиях, а не про цены на металл. Разработчик добавил классический текстовый поиск BM25 и склеил его с векторным через алгоритм RRF — в результате доля релевантных статей в топ-5 выдачи выросла с 20% до 53%. Отдельно переписали промпт для классификации тем: раньше модель из страха ошибиться сваливала 74% статей в мусорную категорию «other», после правки — только 0,03%, а ручная проверка подтвердила точность разметки в 85%. • Агент на LangGraph сам выбирает из трёх инструментов: поиск по тексту, SQL-агрегация метрик и поиск по упомянутым компаниям с тональностью • Переход с LangChain на прямой клиент Ollama ускорил пайплайн в 9 раз • Настройка Ollama (два параллельных запроса вместо одного) ускорила классификацию статей в 1,5 раза

AI-обработка оригинала Habr AI; редакция Hamidun News
Разработчик собрал RAG-систему на локальных моделях и поднял точность поиска с 20% до 53%
Источник: Habr AI. Коллаж: Hamidun News.
Слушать статью
Паспорт материала и цифры
ПараметрДанные
ПервоисточникHabr AI ↗
Главный редактор
Ключевой факт #1Агент на LangGraph сам выбирает из трёх инструментов: поиск по тексту, SQL-агрегация метрик и поиск по упомянутым компаниям с тональностью
Ключевой факт #2Переход с LangChain на прямой клиент Ollama ускорил пайплайн в 9 раз
Ключевой факт #3Настройка Ollama (два параллельных запроса вместо одного) ускорила классификацию статей в 1,5 раза

Разработчик под ником tzar26 выложил на GitHub разбор своей системы для анализа новостей — полностью автономной, без единого вызова облачных API вроде OpenAI или Anthropic. Все модели крутятся локально через Ollama: Qwen 2.5 14B для рассуждений и классификации, BGE-M3 для векторных представлений текста. Причина такого подхода прямо названа в статье: устойчивость к санкциям и независимость от внешних провайдеров. Получившийся стек — Python, FastAPI, PostgreSQL с расширением pgvector, Celery для фоновых задач, LangGraph для агента и Langfuse для мониторинга — автор собрал в Docker-контейнерах и покрыл тестами через GitHub Actions.

Почему обычный векторный поиск провалился на омонимах

Система собрала больше 12 000 статей и для начала искала по ним только через косинусное расстояние между эмбеддингами BGE-M3. На простых запросах всё работало нормально, но сложные темы вроде «ключевая ставка ЦБ» выдавали мусор. Нагляднее всего это показал запрос «золото»: вместо новостей о ценах на металл поиск честно находил статьи про фигуриста, который взял золото на соревнованиях — классическая путаница из-за того, что у слова несколько значений, а вектор не различает контекст.

Решением стал гибридный поиск: к векторному сравнению добавили точный текстовый поиск BM25 на базе PostgreSQL (через встроенный tsvector), а результаты обоих методов объединили алгоритмом RRF (Reciprocal Rank Fusion) — он берёт позиции документа в каждой из выдач и считает общий ранг. BM25 хорошо ловит точные термины типа «ОПЕК» или «ставка», а векторный поиск — синонимы и смысловые связи. В сумме доля релевантных документов в первой пятёрке результатов поиска (метрика Recall@5) выросла с 0,2 до 0,53, то есть больше чем в два раза.

Как перестать сваливать всё в мусорную категорию

Отдельная проблема возникла при автоматической классификации статей по темам. В первой версии промпта модель отправляла 74% всех статей в категорию «other» — то есть почти три четверти базы оказались без полезной метки. Причина оказалась простой: в инструкции была фраза «если сомневаешься — выбирай other», и модель из страха ошибиться массово ею пользовалась.

Разработчик переписал промпт: добавил чёткие описания для каждой категории, включая розничную торговлю (retail), макроэкономику (macro) и инциденты (incident), и убрал саму формулировку про сомнения. После этого доля неклассифицированных статей упала до 0,03%. Чтобы убедиться, что цифры не врут, автор дополнительно вручную проверил случайную выборку размеченных статей — точность классификации составила 85%.

Агент, который считает, а не просто ищет

Обычный поиск по базе отвечает на вопросы вроде «что пишут про нефть», но для аналитики этого мало. Поэтому автор построил на LangGraph агента с тремя инструментами на выбор:

  • `search_articles` — обычный гибридный поиск по тексту
  • `get_metrics` — SQL-агрегация, например подсчёт количества статей про энергетику за последние три дня
  • `search_by_entity` — поиск по извлечённым именам компаний с учётом тональности текста (результат отдельных NER- и сентимент-моделей)

На вопрос «есть ли негативные новости про ЦБ» агент сам решает, какой инструмент вызвать — ему не нужно явно указывать, что требуется посчитать тональность, а не просто найти текст. Для отладки этой логики развернули self-hosted Langfuse — он показывает каждый шаг цепочки «планировщик → вызов инструмента → финальный ответ», сколько токенов ушло на каждый шаг и какие именно аргументы модель передала в функции.

Технические грабли на локальном железе

Запуск моделей локально принёс свои проблемы, нетипичные для облачных API:

  • Ollama выгружала модели из видеопамяти при переключении между ними, и один запрос мог занимать до 60 секунд — починили переменной `OLLAMA_MAX_LOADED_MODELS=2`
  • Библиотека LangChain внутри использует потоковую агрегацию ответов, которая давала девятикратное замедление — её выкинули из пайплайна в пользу прямого клиента `ollama.AsyncClient`
  • SQLAlchemy привязывает пул соединений к циклу событий (event loop), и когда Celery прерывал задачу, соединения оставались «зомби» — решилось вызовом `await engine.dispose()` после каждой задачи
  • Ollama по умолчанию обрабатывает запросы последовательно; классификация одной статьи занимала около 10 секунд, а увеличение параметра `OLLAMA_NUM_PARALLEL` с 1 до 2 ускорило процесс минимум в полтора раза

Проект сейчас в статусе публичного MVP и выложен в открытом доступе на GitHub. Следующий шаг, по словам автора, уже закрытый — привязка упоминаемых компаний к тикерам Мосбиржи (вроде SBER и GAZP) и построение прогнозных дашбордов по конкретным акциям. Для инженеров, которые рассматривают локальный стек вместо облачных API, главный практический вывод звучит так: экономия на API-вызовах реальна, но платить за неё придётся временем на тюнинг инфраструктуры — от настройки параллелизма Ollama до борьбы с управлением соединениями в асинхронном коде.

Сюжет

Вопросы и ответы по материалу

Что известно по теме: почему обычный векторный поиск провалился на омонимах?

Система собрала больше 12 000 статей и для начала искала по ним только через косинусное расстояние между эмбеддингами BGE-M3. На простых запросах всё работало нормально, но сложные темы вроде «ключевая ставка ЦБ» выдавали мусор. Нагляднее всего это показал запрос «золото»: вместо новостей о ценах на металл поиск честно находил статьи про фигуриста, который взял золото на соревнованиях — классическая путаница из-за того, что у слова несколько значений, а вектор не различает контекст. Решением стал гибридный поиск: к векторному сравнению добавили точный текстовый поиск BM25 н

Что известно по теме: как перестать сваливать всё в мусорную категорию?

Отдельная проблема возникла при автоматической классификации статей по темам. В первой версии промпта модель отправляла 74% всех статей в категорию «other» — то есть почти три четверти базы оказались без полезной метки. Причина оказалась простой: в инструкции была фраза «если сомневаешься — выбирай other», и модель из страха ошибиться массово ею пользовалась. Разработчик переписал промпт: добавил чёткие описания для каждой категории, включая розничную торговлю (retail), макроэкономику (macro) и инциденты (incident), и убрал саму формулировку про сомнения. После этого доля н

Что известно по теме: агент, который считает, а не просто ищет?

Обычный поиск по базе отвечает на вопросы вроде «что пишут про нефть», но для аналитики этого мало. Поэтому автор построил на LangGraph агента с тремя инструментами на выбор: `search_articles` — обычный гибридный поиск по тексту `get_metrics` — SQL-агрегация, например подсчёт количества статей про энергетику за последние три дня `search_by_entity` — поиск по извлечённым именам компаний с учётом тональности текста (результат отдельных NER- и сентимент-моделей) На вопрос «есть ли негативные новости про ЦБ» агент сам решает, какой инструмент вызвать — ему не нужно явно указыва

ЖХ
Жемал Хамидун — CPO Alpina Digital, продакт-менеджер AlpinaGPT. Делает AI-продукты с 2019 года. Hamidun News — pet project, который вырос в production-агрегатор с 6000+ читателей.
✦ Практика · Hamidun Academy

Соберите рабочего ИИ-агента или сервис своими руками без ручного кода

В Академии Жемала Хамидуна (academy.hamidun.com) открыт бесплатный 72-часовой тест-драйв: интерактивные скетч-уроки по Claude Code и Cursor, живой Tech Radar и готовые шаблоны агентов.

Открыть 72ч бесплатный доступ →Интенсив по Вайб-кодингу
Что вы думаете?
Загружаем комментарии…