---
title: "Граф знаний в RAG: зачем он нужен и почему в нём нет чанков"
date: 2026-10-25T12:00:00+03:00
type: post
lang: ru
domains: ["ai","architecture"]
tags: ["rag","knowledge-graph","memgraph","agents"]
series: rag-in-production
series_weight: 4
summary: "Типизированная онтология и детерминированный каталог сделали граф знаний меньше и чище: связи по схеме — с 7.5% до 100%, графовая стадия — с таймаута в 5 с до десятков миллисекунд. Почему в графе нет чанков."
llm_summary: "Четвёртая статья цикла «RAG в проде». Граф знаний корпоративного RAG-ассистента (Memgraph рядом с PostgreSQL) перевели со схемы «всё, что нашла LLM» на типизированную онтологию: 64 типа в 19 семействах, проверка связей по семействам, идентичность через корень семейства, детерминированный каталог мастер-данных без LLM, гейт и дедупликация. Соответствие связей онтологии выросло с 7.5% до 100%, граф сократился с 7.4 до 4.7 млн вершин, ложные сущности (62% узлов) исчезли. Замена формы запроса IN $list на UNWIND сняла ~5 с с каждого ответа. В графе нет узлов-чанков: текст, векторы и права живут в PostgreSQL, связь — свойство chunk_id у упоминания. Вклад графа в топ-10 пока не виден; он расширяет пул кандидатов (+2.1 п.п. R@50) и служит картой для агентов."
---


Смена модели данных графа знаний — семейства типов, детерминированный каталог мастер-данных, проверка связей по схеме — подняла соответствие связей онтологии с 7.5% до 100% и сократила граф с 7.4 до 4.7 млн вершин. Задержка графовой стадии упала с таймаута в 5 с до десятков миллисекунд. Чего мы пока не можем показать, так это заметного вклада графа в топ-10 выдачи: его ценность сейчас — в глубине пула кандидатов, строках таблиц и контексте для модели и агентов.

> [!BIZ]
>
> - **Поиск перестал ждать граф:** графовая стадия раньше добавляла около 5 с к каждому ответу и возвращала пустоту, теперь она укладывается в 51 мс на проде.
> - **Граф стал меньше и чище:** 4.7 млн вершин вместо 7.4 млн, ложных сущностей больше нет (было 62% узлов), все проверенные связи соответствуют схеме.
> - **Реестр мастер-данных обрабатывается без LLM:** около 67 тысяч карточек в час на один поток вместо ~410 вики-документов, которым нужна модель.
> - **Честная оговорка:** в первой десятке выдачи пользы графа пока не видно. Он помогает глубже — в пуле кандидатов и в многошаговой работе агентов, а это ещё предстоит измерить.

> [!NOTE] О цикле «RAG в проде»
> Четыре статьи о том, как за две-три недели октября 2026 года мы перестроили поиск корпоративного RAG-ассистента. «Мы» — это автор и AI-агенты, которым он делегировал часть исследований и правок. Компания и системы обезличены, цифры настоящие.
>
> 1. [Гибридный поиск: как «ноги» усиливают друг друга](/posts/hybrid-search-rag)
> 2. [Индексы под гибридный поиск в PostgreSQL](/posts/postgres-hybrid-indexes)
> 3. [Структурный чанкинг и контекстные эмбеддинги](/posts/structural-chunking)
> 4. **Граф знаний в RAG** ← вы здесь

> [!TERM]- Термины за минуту
>
> - **Граф знаний** — сеть сущностей (систем, процессов, стандартов, организаций) и связей между ними, извлечённая из документов [[5]](#литература).
> - **Сущность** — один объект реального мира, одна на весь корпус. **Упоминание** — факт, что сущность встречается в конкретном документе и чанке.
> - **Онтология** — схема графа: какие бывают **типы** сущностей, как они сгруппированы в **семейства** и какие связи между какими типами допустимы.
> - **Тройка «тип — связь — тип»** — например, «Система — использует — Справочник». Тройка допустима, если её разрешает онтология.
> - **Суперузел** — вершина с огромным числом связей (у нас до 34 тысяч). Тормозит обходы и обычно означает ошибку моделирования.
> - **Мастер-данные, каталог** — эталонные справочники организации (договоры, коды). Каталог — сущности, построенные из них без LLM.
> - **Grounding** — привязка упоминания из текста к сущности каталога.
> - **Дедупликация** — слияние копий одной сущности, созданных разными документами или под разными типами.
> - **Сид** — стартовая сущность для обхода графа; у нас — сущности из найденных чанков.
> - **Обход в ширину** — поиск соседей шаг за шагом: сначала прямые связи, потом связи связей.
> - **MCP-инструмент** — функция, которую агент вызывает по протоколу Model Context Protocol (например, «найти соседей сущности»).
> - **Personalized PageRank** — оценка важности вершин относительно заданных стартовых; используется для многошагового поиска (HippoRAG [[2]](#литература)).
> - **п.п.** — процентные пункты. **95%-й бутстреп-интервал [a; b]** — если не включает ноль, эффект статистически значим.

## Исходные условия {audience="biz eng"}

Аудит схемы в начале октября показал, что граф большой, но в значительной части не про то. Сущности извлекались LLM из каждого документа, включая 600 тысяч карточек реестра, и писались без проверки по схеме.

- **7.4 млн вершин и 8.6 млн рёбер,** суперузлы степенью до 34 тысяч; при наполнении графовая БД сидела на 100% CPU.
- **62% вершин-«сущностей» сущностями не были.** Это либо узлы, дублирующие сами документы (632 тысячи), либо значения атрибутов вроде кодов и номеров (640 тысяч).
- **Связи не соответствовали онтологии:** только 7.5% троек «тип — связь — тип» и ~22% пар типов были допустимы по схеме (выборка 200 тысяч рёбер).
- **Одна сущность жила в нескольких копиях разных типов:** 5 293 таких группы, 10 734 узла; в крайнем случае одно имя было 9 сущностями шести типов.
- **Графовая стадия поиска таймаутила в 100% запросов** (150 из 150) и добавляла ~5.08 с к каждому ответу, возвращая пустой результат; расширение для агентов отвечало за 3.9 с (p50).
- **Вклад в качество был спорным.** На эталонном наборе (разметка LLM-судьёй [[6]](#литература)) отключение графа даже чуть поднимало общий nDCG@10 (0.635 → 0.646) и многошаговые запросы (0.595 → 0.637), но роняло строки таблиц (0.813 → 0.711).

## Зачем граф в RAG {audience="biz arch"}

Векторный и лексический поиск находят текст, похожий на запрос. Граф знаний находит текст, связанный с уже найденным через общую сущность: систему, процесс, стандарт, карточку реестра. В корпоративной базе знаний ответ часто размазан по таким связям: регламент ссылается на систему, описание системы — на справочник, а справочник живёт в реестре.

У графа три роли:

1. **Расширение пула кандидатов.** От топа гибридного поиска граф идёт через сущности к документам, которые не похожи на запрос ни по словам, ни по смыслу. Этих кандидатов оценивает тот же реранкер, что и остальные (подробно — в [первой статье](/posts/hybrid-search-rag)).
2. **Графовый контекст для модели.** Вместе с топ-10 чанков модель получает сущности из них и связи между ними. Это структура, которую модели иначе пришлось бы восстанавливать из текста [[1]](#литература) [[4]](#литература).
3. **Навигация для агентов.** Агент через инструменты расширения и соседей идёт от найденной сущности к связанным и строит исследование по шагам, а не одним поисковым запросом.

Сейчас в графе около 650 тысяч документов, 750 тысяч сущностей (из них ~590 тысяч — сущности каталога мастер-данных), 2.2 млн упоминаний сущностей в документах, 185 тысяч смысловых связей между сущностями и 64 типа сущностей в онтологии. Хранится он в Memgraph рядом с PostgreSQL, в котором лежат тексты и векторы.

## Смена модели данных: от «всё, что нашла LLM» к типизированной онтологии {audience="arch ml"}

Главное решение — разделить, что извлекает LLM и что можно знать точно. Смена схемы шла пятью фазами, каждая со своим контрактом и приёмкой.

1. **Документ — не сущность.** Документы получили собственную метку и связь «упоминание в документе», а значения атрибутов (коды, номера) ушли из вершин в свойства сущностей. Это убрало 62% ложных сущностей.
2. **Связи проверяются по семействам типов.** Онтология описывает 64 типа в 19 семействах и 16 типов связей. Ребро пишется, только если типы его концов допустимы для этой связи; проверка идёт по семейству, а не по каждому типу.
3. **Идентичность через семейство.** Типы наследуются одиночно, и над ними стоят семь «семейств идентичности» (организация, подразделение, персона, проект, ссылка на документ и др.). Идентификатор сущности считается от корня семейства, а не от конкретного типа, так что модель, спутавшая подтип, больше не рождает дубль:

    ```latex
    \mathrm{entity\_id} = \mathrm{sha1}(\mathrm{identity\_root} \,|\, \mathrm{name} \,|\, \mathrm{scope})
    ```

4. **Каталог мастер-данных без LLM.** Для карточек реестра сущность строится детерминированно из самой карточки, а её ключи (коды, номера) становятся отдельными узлами-ключами. Упоминания из вики привязываются к каталогу (grounding) с учётом прав доступа: нет уверенности — нет связи.
5. **Гейт и дедупликация.** LLM или NER-модель предлагает сущность, отдельная небольшая модель-судья решает: принять, перетипизировать или слить с существующей. Спорные слияния не выполняются, а помечаются ребром «возможно то же» для разбора.

```mermaid {caption="Модель данных графа после смены схемы: текст остаётся в PostgreSQL, граф хранит только ссылку на чанк"}
flowchart TB
  type["Тип сущности<br/>64 типа, 19 семейств"] -- реализация --> ent["Сущность<br/>каталог или извлечённая"]
  key["Ключ каталога<br/>код, номер, идентификатор"] -- ключ --> ent
  ent -- "смысловая связь<br/>16 типов, по схеме" --> ent
  ent -- упомянута в --> men["Упоминание<br/>свойство chunk_id"]
  men -- в документе --> doc["Документ<br/>версия, источник"]
  men -. chunk_id .-> chunk["Чанк<br/>не узел, только ссылка"]
  chunk -. текст по id .-> pg[("PostgreSQL<br/>текст, векторы, права")]
  classDef key stroke-width:3px
  class ent key
```

Сущность одна на весь корпус, упоминание — одно на документ и чанк. Поиск идёт от чанка к упоминанию, от него к сущности, дальше по смысловым связям и обратно через упоминания к другим документам.

### Что это дало {audience="biz ml"}

Замеры — на пробном перенаполнении (6 870 документов) и на проде после перехода.

| Показатель | До | После | Где измерено |
| --- | ---: | ---: | --- |
| Тройки, допустимые по схеме | 7.5% | 100% (n = 1 014) | проба |
| Пары типов, допустимые по схеме | ~22% | 88.5% | проба |
| Ложные сущности (документы, атрибуты) | 62% узлов | 0 | проба |
| Вызовов LLM на карточку реестра | ≥ 1 | 0 | прод |
| Вершин / рёбер в графе | 7.4 млн / 8.6 млн | 4.7 млн / 6.4 млн | прод |
| Смысловых связей между сущностями | 1.23 млн | 176 тыс. | прод |
| Максимальная степень сущности | до 34 тыс. | 4 152; у 96.8% — не больше 10 | прод |
| Привязка упоминаний к каталогу, точность | — | ~95% (39 из 41) | проба |
{caption="Граф до и после смены схемы. «Проба» — пробное перенаполнение 6 870 документов, «прод» — прод после перехода. «После» — на момент замера сразу после перехода; сейчас смысловых связей около 185 тыс."}

Каталог сделал реестр дешёвым: ~67 тысяч карточек в час на один поток против ~410 вики-документов, которым нужен LLM.

> [!WARNING] Чего таблица не показывает
>
> - **Grounding точный, но редкий.** Точность высокая, но покрытие малое: в 1 638 из 1 670 случаев кандидата в каталоге не нашлось.
> - **Раздробленность осталась:** 41 тысяча групп «имя + тип» на 137 тысяч узлов.
> - **Выборки маленькие.** «100%» допустимых троек — это n = 1 014, точность grounding — 39 из 41, ошибочных слияний 0 из 5. Для последней цифры выборка слишком мала для выводов.

Гейт пришлось ослабить. Первая версия с порогом 0.75 выбрасывала 44% сущностей при точности принятых ~0.94 — ценой потери примерно трети полноты. Вторая версия только перетипизирует (0.14–0.27% сущностей) и замедляет наполнение на ~8%. Дедупликация распределяет новые упоминания так: 97.9% — точное совпадение, 0.4% — слияние, 0.3% — на разбор, 1.2% — новая сущность.

## Почему в графе нет чанков {audience="arch eng"}

Граф у нас «без чанков»: в нём нет ни узлов-чанков, ни их текста. Связь с текстом — одно свойство chunk_id у упоминания сущности в документе. Текст, векторы, права доступа и признак «живой/удалённый» живут в PostgreSQL.

Ранняя схема содержала узлы чанков и рёбра «упоминание → чанк». От них отказались до перф-программы, и прямого замера «с чанками / без» у нас нет. Есть три аргумента и косвенные цифры.

1. **Размер и стоимость неиндексированных обходов.** Уже сейчас упоминания дают 2.16 из 4.67 млн вершин. Чанки (сейчас ~930 тысяч живых) с рёбрами от каждого упоминания добавили бы ещё полтора–два миллиона объектов. Каждый запрос, промахнувшийся мимо индекса, сканирует все вершины — у нас это стоило 1.2–1.7 с на запрос и полный таймаут графовой стадии.
2. **Чанки — самая изменчивая часть данных.** Каждая новая версия документа и каждая перенарезка меняет их идентификаторы. Даже с chunk_id как свойством после перечанкинга корпуса в графе осталось 59 461 ссылка на удалённые чанки, а один документ до чистки версий нёс в графе 410 chunk_id при 37 актуальных. Узлы-чанки превратили бы каждую такую перестройку в массовую перезапись графа.
3. **Один источник правды для доступа и удаления.** Текст отдаётся только из PostgreSQL, где применяются фильтры прав и признак живости. Граф не может «вернуть» удалённый или закрытый документ, даже если в нём осталась устаревшая ссылка.

Обратная сторона тоже измерена. Мы долго держали вторую копию связей «сущность — чанк» в PostgreSQL: 11.7 млн строк, из которых живых 2.74 млн, 9.9 ГБ. Фоновая пересвязка этой копии занимала 93% времени базы (подробнее — во [второй статье](/posts/postgres-hybrid-indexes)). После отказа от неё единственным источником связи стало свойство в графе.

> [!ARCH]
> Правило, которое мы вынесли: связь хранится один раз, текст — один раз, и не в одном и том же месте. Граф знает структуру и ссылается на текст; реляционная БД знает текст, права и живость. Любая вторая копия связи быстро становится главной статьёй расходов на синхронизацию.

## Как граф влияет на выдачу сейчас {audience="ml biz"}

На топ-10 — почти никак, на глубину пула — значимо. На новой схеме отключение графа даёт nDCG@10 +0.1 п.п. [−0.9; +1.1] и key-doc R@10 −0.1 п.п. [−1.4; +1.2], но снижает полноту пула из 50 кандидатов на 2.1 п.п. [−3.1; −1.2]. На старой схеме без графа проседали строки таблиц (−2.9 п.п. nDCG после дооценки) и короткие запросы.

Почему так. Граф приводит кандидатов, которые не похожи на запрос, и чаще всего реранкер ставит их ниже прямых попаданий. Это осознанный выбор. Когда графовые попадания сливались с остальными через RRF, сотни графовых чанков вытесняли векторные, и средний скор реранкера в выдаче падал до 0.06. Сейчас графовых кандидатов не больше 30, и решает реранкер (средний скор вернулся к 0.92; цифра из журнала разработки, не из эталонного прогона).

### Как работает стадия {audience="eng"}

- сиды — сущности, упомянутые в чанках топа после RRF;
- обход в ширину до двух шагов, до 25 соседей на шаг, с пропуском уже посещённых вершин;
- документы ранжируются по числу разных сущностей, через которые они достижимы;
- чанки документов берутся из PostgreSQL, до 5 на документ и до 200 всего;
- стадия идёт параллельно с первым проходом реранкера, с жёстким таймаутом и отказом без ошибки.

### Задержка: форма запроса важнее индекса {audience="eng"}

Индексы в графовой БД были, но запросы вида `MATCH (n) WHERE n.id IN $list` планировщик исполнял полным сканом 4.7 млн вершин. Форма `UNWIND $ids AS x MATCH (n:Label {id: x})` идёт по индексу.

| Запрос | До, мс | После, мс |
| --- | ---: | ---: |
| Графовая стадия поиска целиком | > 6 000 | 51 на проде |
| Сущности по 30 чанкам | 1 690 | 6 |
| Обход в ширину | 1 224 | 33–56 |
| Расширение сущностей для агента | 1 127 | 21 |
| Подграф для агента | 1 125 | 7 |
{caption="Задержка графовых запросов до и после смены формы запроса. «> 6 000» — время запроса без ограничения; в проде стадию обрывал таймаут 5 с, отсюда ~5.08 с к каждому ответу"}

> [!ENG] Проверьте у себя
> Наличие индекса не гарантирует, что планировщик им воспользуется. Сравните EXPLAIN для двух форм:
>
> ```cypher
> // полный скан всех вершин
> MATCH (n) WHERE n.id IN $list RETURN n;
>
> // поиск по индексу для каждого id
> UNWIND $ids AS x
> MATCH (n:Label {id: x})
> RETURN n;
> ```
>
> Важны обе детали: метка `:Label`, по которой построен индекс, и разворачивание списка через `UNWIND`.

Отдельно дорогим оказался LLM-фильтр графового контекста: 3.9 с на запрос, из них сам вызов модели — 0.35 с, остальное — эмбеддинг каждого имени сущности. Теперь, когда клиенту нужны только метрики графа, фильтр пропускается.

### Связь сущностей с чанками {audience="ml eng"}

Качество сидов зависит от того, к каким чанкам привязаны упоминания: чанк без сущностей не может стать сидом. После перехода на структурный чанкинг линкер учитывает путь раздела и термины чанка, и доля живых чанков с сущностями выросла с 70.8% до 73.3%. Дальше потолок — извлечение: LLM отдаёт 10–35 сущностей на документ независимо от его длины (см. [третью статью цикла](/posts/structural-chunking)).

## Глубокое исследование агентами {audience="arch biz"}

Для агентов граф — не часть поиска, а карта местности. Один поисковый запрос возвращает десять чанков; с графом агент видит, какие сущности в них упомянуты, с чем они связаны и в каких ещё документах встречаются. Это база для многошаговых вопросов вида «какие регламенты затронет замена системы X».

```mermaid {caption="Цепочка MCP-инструментов агента: граф отдаёт только структуру, текст агент получает только через expand"}
flowchart TB
  q["query<br/>чанки + сущности<br/>в каждом попадании"] --> lk["kg_entity_lookup<br/>сущность по имени и типу"]
  lk --> nb["kg_neighbors / kg_subgraph<br/>соседи на 1–3 шага,<br/>без текста"]
  nb --> rd["Связанные документы<br/>ссылки из упоминаний<br/>сущностей"]
  rd --> ex["expand(doc)<br/>текст чанков из PostgreSQL,<br/>с правами"]
  ex --> ans["Ответ агента<br/>или следующий шаг<br/>исследования"]
  ans -. новый запрос .-> q
  classDef key stroke-width:3px
  class nb key
```

Инструменты получили то же ускорение, что и поиск. До исправления формы запросов расширение документа с графом занимало ~7 с (p50 на 20 сценариях пользователя), и графовая часть была непустой в 13 из 20 случаев. Сейчас каждый из графовых запросов инструментов занимает единицы–десятки миллисекунд (см. таблицу выше), и агент может делать много шагов без заметной цены.

### Чего мы не знаем {audience="ml biz"}

Измерения того, насколько граф улучшает ответы агента, у нас нет: эталонный набор оценивает один поисковый запрос, а не исследование из нескольких шагов. Косвенные наблюдения скорее отрезвляют:

- многошаговые запросы через сущности долго были самым слабым классом (nDCG@10 0.34 против 0.53–0.64 у остальных), в основном из-за слишком строгого порога релевантности;
- их рост с 0.39 до 0.59 дали правки лексики и слияния, а не граф;
- граф теряет сиды, когда лексическая нога ничего не нашла: сущности берутся только из уже найденных чанков.

> [!ML]
> Следующий шаг — набор задач для агента с оценкой итогового ответа (а не выдачи) и сравнением «с графовыми инструментами / без». Для многошагового ранжирования в самом поиске мы рассматриваем Personalized PageRank по сущностям запроса, как в HippoRAG [[2]](#литература) [[3]](#литература), но пока не строили.

## Выводы {audience="biz arch eng ml"}

- **Схема важнее объёма.** Граф из всего, что нашла LLM, был больше и хуже: 62% ложных сущностей и 7.5% связей, допустимых по схеме. Типизация по семействам, проверка связей и детерминированный каталог сделали его меньше и чище.
- **Всё, что можно знать точно, не надо извлекать LLM.** Карточки мастер-данных превращаются в сущности детерминированно, в сто с лишним раз быстрее и без ошибок типизации.
- **Граф хранит структуру, а не текст.** Чанки и права доступа остаются в реляционной БД; в графе только ссылки на них. Это держит граф маленьким и переживает перенарезку корпуса без перестройки.
- **В графовой БД форма запроса решает всё.** Одна замена `IN $list` на `UNWIND` сняла 5 с с каждого ответа.
- **Польза графа пока не в топ-10.** Он расширяет пул кандидатов и даёт структуру модели и агентам. Чтобы превратить это в метрики выдачи, нужны три вещи: извлечение сущностей из всего длинного документа, сиды из самого запроса (а не только из найденных чанков) и оценка на уровне ответа агента.

Открытый вопрос для нас: после перехода на новую схему не было чистого сравнения «граф включён / выключен» по классам запросов на финальном конвейере. Это первое, что мы сделаем, прежде чем вкладываться в граф дальше.

## Литература {audience="ml" .references}

1. Edge D. et al. [From Local to Global: A Graph RAG Approach to Query-Focused Summarization](https://arxiv.org/abs/2404.16130). arXiv:2404.16130, 2024.
2. Jiménez Gutiérrez B. et al. [HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models](https://arxiv.org/abs/2405.14831). arXiv:2405.14831, 2024 — Personalized PageRank по сущностям запроса для многошагового поиска.
3. Jiménez Gutiérrez B. et al. [From RAG to Memory: Non-Parametric Continual Learning for Large Language Models](https://arxiv.org/abs/2502.14802). arXiv:2502.14802, 2025.
4. Guo Z. et al. [LightRAG: Simple and Fast Retrieval-Augmented Generation](https://arxiv.org/abs/2410.05779). arXiv:2410.05779, 2024 — двухуровневый поиск по сущностям и связям.
5. Pan S. et al. [Unifying Large Language Models and Knowledge Graphs: A Roadmap](https://arxiv.org/abs/2306.08302). arXiv:2306.08302, 2023.
6. Upadhyay S. et al. [UMBRELA: UMbrela is the (Open-Source Reproduction of the) Bing RELevance Assessor](https://arxiv.org/abs/2406.06519). arXiv:2406.06519, 2024 — LLM-судья для эталонного набора.

Идентификаторы arXiv сверены с API arXiv 25 октября 2026.

