- BrainTools - https://www.braintools.ru -
Привет! Меня зовут Александр, я из Сбера. Ниже будет сказ про то, как мы творчески посмотрели на задачу улучшения качества поиска. Если вас не пугают термины вроде эмбеддинги, реранкеры, RAG и GraphRAG, то добро пожаловать под кат.
В основе поиска на некоторых поверхностях экосистемы (например, в ИИ-помощнике ГигаЧат, Сбербанк Онлайн) лежит система, под капотом которой — несколько источников с векторным поиском внутри каждого источника, либо с комбинацией векторного и полнотекстового поиска. Устройство поиска — тема отдельной (и не одной) статьи; в текущей я коснусь только ограничений векторного поиска и того, как может помочь граф.
Идея векторного поиска следующая: исходный текст (например, статья из интернета) превращают в вектор в многомерном пространстве, похожие тексты оказываются «рядом», при запросе находим ближайших соседей в этом же многомерном пространстве — вот и готов контекст для LLM, можно предметно отвечать на запрос пользователя, как это делает ГигаЧат.
Но у векторного поиска есть принципиальные ограничения.
Один вектор — один смысл. Каждый документ сжимается в единственный вектор фиксированной размерности. Один текст может быть релевантен десяткам разных запросов по разным причинам, но вектор усредняет все смыслы до одной точки пространства. При корпусе от 500 тыс. документов эмбеддинги начинают «сливаться»: размерности не хватает, чтобы различить все нюансы, и поиск начинает ошибаться в граничных случаях.
Как помогает граф? Сущности и связи в графе не усредняются: каждый узел хранит точное текстовое описание, а рёбра явно кодируют тип отношения. Вместо «близости в пространстве» граф возвращает структурно точный контекст: не «похожий на запрос документ», а «конкретная сущность и её прямые связи».
Семантическое сходство ≠ релевантность. Векторный поиск возвращает то, что похоже по смыслу, а не то, что отвечает на вопрос. Для многошаговых и аналитических запросов («Почему X привело к Y?») наиболее близкий по косинусу чанк может не содержать ответа вовсе. Поиск при этом не сигнализирует о провале — он молча возвращает «лучшее из имеющегося» с высоким скором, даже если ответа в корпусе нет.
Как помогает граф? Гибридный режим (вектор + граф) запускает параллельно два поиска. Граф проверяет, существует ли в корпусе структурный путь между сущностями запроса — это работает как фильтр уверенности. Если ни вектор, ни граф не нашли релевантного пути, то система может явно вернуть «не знаю» вместо галлюцинации.
Нет памяти [1] о структуре и связях. Векторный поиск работает с независимыми чанками: он не знает, что сущность A в одном документе и сущность A в другом — это одно и то же, и что между ними существует цепочка отношений. Multi-hop вопросы («Кто руководил компанией, которую приобрела организация, финансирующая X?») векторный поиск не решает в принципе: ответ требует последовательного обхода трёх разных документов, а не близости к запросу.
Как помогает граф? Он хранит связи между сущностями явно и согласованно. Retrieval-агент проходит по графу, собирая ответ по частям из разных источников. Там, где векторный поиск делает один запрос, графовый поиск может сделать направленный обход — и находит ответ, который в одном документе никогда не существовал.
В нашем распоряжении много данных (про устройство нашего петабайтного хранилища я рассказывал в другой статье) и ресурсов (GPU, платформа cloud.ru [2]). В результате мы, почти как наши предшественники физики-экспериментаторы, выдвинули гипотезу: граф знаний как дополнительный источник контекста может улучшить качество ответов. Осталось дело за малым: построить качественный граф и «прикрутить» к поиску.
Для оценки качества используем подход llm-as-a-judge, при котором одна языковая модель выступает автоматическим оценщиком качества ответов другой модели по заданным критериям (точность, релевантность, полнота), заменяя дорогостоящую ручную разметку. В качестве основного бенчмарка взяли переведённый на русский язык SimpleQA — бенчмарк от OpenAI из 4326 коротких фактологических вопросов с однозначными верифицируемыми ответами.
Вдохновившись Google Knowledge Panel (это то, что отображается справа от поиска Google и под капотом которого находится Google Knowledge Graph) и различными докладами BigTech-компаний, в которых строят граф «всея», мы сначала пошли тем же путём: поискали релевантные нашим задачам источники и пришли к выбору проекта Yago в качестве основного графа, а также серии других источников для дополнения.
Yago — это один из крупнейших публичных графов знаний со 120 млн верифицированных фактов, построенный на основе Wikidata с жёсткой логической схемой типов schema.org [3]. Граф покрывает большинство общих знаний о мире: люди, места, организации, события. Мы взяли готовый датасет Yago 4.5, позже научились обновлять данные с помощью кода преобразования Wikidata в Yago, выложенного на GitHub.
Основная причина использования этого графа — в структуре хранения. Структура данных Wikidata — триплеты с квалификаторами, а не факты: при работе агента получается четыре шага логики до извлечения факта (для интереса [4] посмотрите, как устроена модель Statement-Qualifier внутри Wikidata). Для работы ИИ-агентов дополнительный вызов LLM критичен — это задержка и лишние токены.
Всё это сложили в Apache Jena Fuseki (графовую RDF-базу), сделали API поверх данных (кстати, с ходу написали на Go, потому что можем!), добавили ИИ-агента и вывели на одну из наших внутренних поверхностей. В результате получился аналог Google Knowledge Panel «на минималках»: можно зайти внутри контура компании, сделать запрос (например, «Кто такой Илон Маск») и получить карточку с фотографией и ключевыми характеристиками.

Работа агента разбита на пять этапов:
Выделение сущностей. LLM извлекает из запроса пользователя именованные сущности: людей, организации, топонимы, термины.
API Router. Сущность подставляется в заготовленный SPARQL- или Cypher-шаблон в зависимости от целевой базы. Подход защищает от ошибок кодогенерации, но ограничивает заранее прописанными сценариями.
Выполнение запроса. Графовая база обходит граф и возвращает релевантные узлы и связи.
Ранжирование. Результаты сортируются по рангу узла, весу рёбер и покрытию сущностей из запроса.
Суммаризация. LLM формирует финальный ответ на основе ранжированного контекста.
Выбор графовой базы данных — отдельное интеллектуальное упражнение. Нужно найти баланс между производительностью, масштабируемостью и спецификой решаемой задачи. А задача стала ещё интереснее тем, что у нас вырисовывались два сценария использования:
Сценарий 1 — RAG (Retrieval-Augmented Generation): относительно малое время отклика и возможности графового поиска, важна целостность данных.
Сценарий 2 — хранилище данных: большой объём данных по аналогии с Google Knowledge Graph, например для карточек сущностей (персон, локаций, произведений). Справедливости ради, в большинстве таких случаев графовые возможности не нужны, поэтому, теоретически, графовую базу здесь можно заменить на MongoDB или YT.
Исходя из этих сценариев, сформировался следующий шорт-лист:
Сценарий 1 — RAG:
FalkorDB — MPP in-memory графовая база, активно развивает поддержку GraphRAG.
Memgraph — in-memory single-node графовая база, оптимальна для быстрого чтения.
Сценарий 2 — хранилище:
NebulaGraph — open-source база, применяется в Tencent, поддерживает шардирование и горизонтальное масштабирование.
JanusGraph — фактически надстройка над Cassandra. Также поддерживает масштабирование.
Вначале взяли Apache Jena Fuseki — далеко не самую популярную базу, но для быстрого старта и загрузки RDF-дампа графа Yago подошла идеально. Правда, ограничения её использования проявились практически сразу: когда мы начали собирать аналитику по хранимым данным и для этого кидать запросы count(*), база уходила на десятки минут простоя.
Как только мы загрузили граф в базу и подключили агент, замерили качество на бенчмарке — сначала чистого графа, затем в связке с поиском, чтобы понять, есть ли прирост.
Методика тестирования, согласованная с командой поиска, выглядела так:
Поиск получает ответы от поисковых источников.
Мы добавляем к ним ответы от агента графа знаний.
Все ответы поступают на реранкер, где каждый источник получает скор релевантности.
Ответы ранжируются, собираются в единый список, из которого отбираются топ-5 по наибольшему скору релевантности запросу.
Эти пять финальных кандидатов передаются модели как контекст для формирования ответа.
Первичный замер показал: прироста на бенчмарках граф не даёт. Чтобы улучшить качество, мы набросали несколько гипотез, собрали тестовый стенд с быстрым подключением различных бенчмарков и версий агентов, и провели серию экспериментов.
Вот часть реализованных идей:
Расширение покрытия графа дополнительными источниками. Добавили IMDB — постарались сделать максимально просто, через интеграцию на уровне API, а не базы данных: другими словами, мы параллельно отправляем запрос в разные базы. Агрегировать источники на уровне данных намеренно не стали, чтобы не решать задачу сопоставления сущностей из разных источников.
Ранжирование извлечённых сущностей по релевантности. Простейший способ: по количеству связей у узла. Столкнулись с интересным случаем: на запрос, который содержал слово «Москва», база первой находила не город, а гостиницу «Москва» в Баку.
Различные лимиты после ранжирования (сколько отсекаем после запроса в базу). Про поиск баланса между полнотой данных и размером контекста.
Умный поиск на глубину 3. Обход графа на каждом уровне с контролем ветвления (берём не всех соседей, а только релевантных запросу). Кстати, это дало самый сильный прирост качества.
Поиск в ширину на один или два уровня. Возврат найденного узла вместе с ближайшими соседями на один и два хопа.
Использование подготовленных Cypher-шаблонов. Предзаданные паттерны запросов для типовых сценариев.
Векторный поиск по эмбеддингам узлов и свойств графа (напомню, что начали мы с полнотекстового поиска).
Встроенные графовые алгоритмы. Использование нативных графовых функций Memgraph. Например, если в запросе есть две сущности, то возвращаем маршрут между ними.
Прирост качества на ruSimpleQA составил +3 п. п. на агенте в изоляции — для нас это оказалось неожиданно мало. Рост совместного качества графа и поиска не вышел за пределы статистической погрешности. Предварительный вывод: граф на основе Yago не дал статистически значимого улучшения по сравнению с текущим векторным поиском.
Анализируя ошибки [5], мы пришли к нескольким выводам.
Векторный поиск не завёлся нормально. Для генерирования эмбеддингов нам приходилось использовать довольно скудное описание, собранное из нескольких полей графа, — это не всегда давало нужную точность. Полнотекстовый поиск тоже не всегда помогал найти нужную сущность. Стало очевидно, что нужно гибридное решение.
Готовый граф ≠ ваш тематика. Yago универсален, но «размыт»: тематическая специфика теряется в океане общих знаний. По большому счёту, нас, возможно, выручила бы интеграция данных на уровне графа, чтобы в нём было больше связей, причём из разных источников.
Агент накапливает ошибки. Каждый шаг трансформации в цепочке накапливает погрешность:
исходный запрос → выделение сущности → получение результатов из баз → агрегирование ответов из нескольких источников → суммаризация → ответ → оценка llm-as-a-judge
Во втором подходе мы решили сменить парадигму. Во-первых, вместо построения универсального графа знаний попробовать построить граф прямо из наших документов (напомню, что рядом с нами — огромное хранилище с большим количеством данных). Во-вторых, найти «коробочное» решение с гибридным поиском и возможностью построения графа из текста. К слову, после отработки узкоспециализированного решения мы планируем собрать единое решение на основе первого и второго подходов, чтобы выжать максимум пользы из наших трудов.
Microsoft GraphRAG — очевидный лидер по технологии построения графов из текстов — мы намеренно не рассматривали: индустрия пошла дальше, и мы выбрали LightRAG.
LightRAG — это RAG-фреймворк на основе графов знаний, ключевым принципом которого является двухуровневая система поиска (dual-level retrieval), объединяющая детальный локальный и широкий глобальный поиск.
В сравнении с графовым поиском у векторного поиска есть три системные проблемы:
чанки слишком маленькие и теряют контекст;
чанки разрозненны — достаются из совершенно разных документов;
LLM пытается связать разрозненные куски, но не всегда справляется.
LightRAG решает основные проблемы векторного поиска, выделяя сущности и связи, чтобы сохранить контекст между множеством чанков, и выполняя дедупликацию — граф не разрастается на одинаковой информации из разных источников. На сущности, связи и чанки создаются эмбеддинги.
|
Режим |
Принцип |
|
local |
Только локальные связи и детали графа |
|
global |
Глобальный обзор через широкие тематические связи |
|
hybrid |
Комбинация local + global (рекомендуется по умолчанию) |
|
naive |
Простой векторный поиск без графа |
|
mix |
Гибридный + наивный с переранжированием |
|
bypass |
Прямой запрос к LLM без RAG |
Из запроса выделяются ключевые слова двух уровней:
high-level — широкие концепции или темы;
low-level — конкретные сущности или термины.
Выполняется векторный поиск по обоим уровням:
high-level ищет по вектору связей;
low-level ищет по вектору нод, затем делает один хоп по ближайшим связям.
Извлекаются свойства (properties) найденных нод и связей.
Выполняется сортировка по rank (сумма степеней нод) и weight (сила связи, определяется LLM на этапе построения графа).
Достаются чанки, из которых были выделены найденные ноды и связи.
LLM суммаризирует чанки и формирует ответ.
Перед поиском LightRAG за четыре этапа строит граф знаний из документов:
Извлечение сущностей и связей. LLM выделяет из текста узлы (люди, организации, концепции) и рёбра (связи между ними) с описаниями.
Профилирование (KV-структуры). Каждая сущность и связь описывается как пара ключ–значение с деталями.
Векторизация. Значения (V) из KV-пар преобразуются в эмбеддинги, обеспечивая более точный поиск, чем стандартный chunk-based RAG.
Хранение. Граф и векторы сохраняются совместно, что позволяет инкрементально добавлять новые данные без переиндексации всей базы.
Описание (description) формируется у каждой сущности и связи с помощью LLM на основе чанков, из которых они были выделены. Главная сложность заключается в качестве входных данных: очистке, размере чанка и величине пересечения между ними. Мусор на входе с высокой вероятностью даст мусор на выходе.
Главное отличие от Microsoft GraphRAG — экономичность и скорость: LightRAG примерно в 30–40 раз дешевле на этапе индексации данных при сопоставимом качестве ответов. Вместо сложного обхода всего графа LightRAG редуцирует граф до нужного минимума (релевантного подграфа), что снижает нагрузку.
Мы взяли корпус документов (предварительно проанализировали, на какие именно вопросы продовый поиск не отвечает, и подобрали релевантные документы), проиндексировали их через LightRAG, подключили агент к LightRAG и прогнали на тестовом стенде на бенчмарках.
Результат: решение на LightRAG дало верные ответы на 74% вопросов из тех нескольких сотен вопросов, на которые не ответил поиск — и это дало +12 п. п. к точности ответов на полном бенчмарке из 4326 вопросов.
Кроме этого нам визуально понравилось то, что строит LightRAG (отдельно построили ещё несколько узкоспециализированных графов в качестве демонстрации).
После работы с несколькими экземплярами LightRAG мы прочувствовали, насколько это неудобно — четыре пода на каждый экземпляр: графовая база, векторная база, опционально MongoDB для хранения метаданных документов по обработке, сервер REST API сервер c пользовательским интерфейсом. Начали оборачивать всё в Helm-чарт — и нашли интересный проект ApeRAG: по сути, LightRAG на DevOps-стероидах, с возможностью использовать уже развёрнутые базы. Ну блеск!
Планируем запустить LightRAG на большем количестве документов нашего хранилища и проверить гипотезу улучшения качества поиска уже на production-трафике. Если результаты подтвердятся, то расскажем в следующей статье.
Здесь перед командой во весь рост встаёт крайне интересный инженерный и продуктовый вызов: ускорение построения графа. На текущем сетапе с H100 используем следующие модели:
|
Роль |
Модель |
|
LLM (построение графа) |
Qwen/Qwen3-30B-A3B-Instruct-2507-FP8 |
|
Эмбеддер |
BAAI/bge-m3 |
|
Реранкер |
BAAI/bge-reranker-v2-m3 |
Скорость индексации составила до 200 документов в час (в зависимости от размера документа) — нас это не устраивает, и оптимизация стала следующим приоритетом.
Консультируйтесь с экспертами в индустрии — не оставайтесь с проблемой один на один. Для решения задачи мы собрали сильную инженерную команду, но сразу ощутили нехватку глубоких знаний по графам и GraphRAG. В результате нашли эксперта, который помог двигаться быстрее. Этот урок стоил бы нам минимум несколько лишних недель, реши мы разбираться в одиночку.
Не гонитесь за красивыми числами на дашбордах. Большой готовый граф сразу показывает впечатляющие метрики, но это не всегда конвертируется в прирост на реальных бенчмарках. Важно с самого начала договориться об оценочной методике, максимально приближенной к промышленному сценарию использования.
Качество данных важнее архитектуры. Граф — это усилитель: хорошие данные на входе дают хорошие связи на выходе, мусорные — дают мусорный граф. Инвестиции в очистку и подготовку корпуса окупаются значительно быстрее, чем эксперименты с архитектурой поверх грязных нерелевантных данных.
Автор: anepochatykh
Источник [6]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/29606
URLs in this post:
[1] памяти: http://www.braintools.ru/article/4140
[2] cloud.ru: http://cloud.ru
[3] schema.org: http://schema.org
[4] интереса: http://www.braintools.ru/article/4220
[5] ошибки: http://www.braintools.ru/article/4192
[6] Источник: https://habr.com/ru/companies/sberbank/articles/1029580/?utm_campaign=1029580&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.