Alexander Bond • 01.11.2026 • 11 мин чтения graphragграфы знанийai агенты

Графы знаний (Knowledge Graphs) и GraphRAG в AI-агентных сетях: связывание сущностей B2B-клиентов, динамическая память и устранение контекстных коллизий в Enterprise

Плотные эмбеддинги схлопывают холдинг, субхолдинг и дочернее юрлицо в один плоский top-k — и агент уверенно смешивает скидочные сетки разных компаний. Разбираем, как граф знаний, типизированные триплеты и гибридный ретривал (Dense Vector + Graph Traversal) превращают контекст Enterprise-агента из мешка чанков в проверяемую топологию фактов.

Плотные эмбеддинги схлопывают холдинг, субхолдинг и дочернее юрлицо в один плоский top-k — и агент уверенно смешивает скидочные сетки разных компаний. Разбираем, как граф знаний, типизированные триплеты и гибридный ретривал (Dense Vector + Graph Traversal) превращают контекст Enterprise-агента из мешка чанков в проверяемую топологию фактов.

1. Введение: Ограничения Vector-only RAG в Enterprise B2B

Плотный эмбеддинг отвечает на вопрос «о чём этот фрагмент», но молчит о том, кто в иерархии кому подчиняется. В First Touch графовая память и GraphRAG развёрнуты в контуре автономных агентных сетей именно потому, что классический top-k по косинусу деградирует уже на третьем уровне корпоративной вложенности.

Рассмотрим типовую цепочку: холдинг → субхолдинг → дочернее предприятие → конкретное юрлицо → локальный ЛПР → история рамочных договоров. Каждый переход — отдельный тип отношения, который физически не закодирован в тексте чанка. Векторный индекс видит два почти идентичных по семантике фрагмента — «ООО „Ромашка-Логистика“ — скидка 12%» и «ООО „Ромашка-Трейд“ — скидка 5%» — и с косинусной близостью 0.97 выдаёт их одним плоским списком. У модели нет ни одного сигнала, что это разные юридические лица с разными скидочными сетками и разными бюджетами.

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

Корень проблемы архитектурный, а не промптовый. Плоский индекс не хранит ограничений непротиворечивости: в нём нет операции «исключить сущности, не связанные с целевым юрлицом». Увеличением top-k, ре-ранкером или гибридом BM25+dense это не лечится — лечится добавлением топологии.

2. Архитектура GraphRAG для автономных AI-агентов

Граф знаний — топологическая основа агентной памяти, а не надстройка над векторами.

Узлы (Nodes): Company, Person, Role, Product, Deal, Document. Ключевое требование — стабильный суррогатный идентификатор: ИНН/КПП для юрлиц, корпоративный email + домен для персон, GUID для сделок. Строковое имя узлом быть не может — «Ромашка» встречается в 14 контекстах.

Ребра (Edges): типизированные направленные отношения с обязательной семантикой: BELONGS_TO, DECIDES_FOR, PURCHASED, INTEGRATED_WITH, SIGNED_CONTRACT, REPORTS_TO. Пример триплета: (ООО «Ромашка-Трейд», BELONGS_TO, Холдинг «Ромашка»), (Иванов А.А., DECIDES_FOR, ООО «Ромашка-Трейд», {role: CFO, budget_limit: 50M RUB}).

Entity-Relation Extraction. Двухфазный парсинг входящих сообщений и CRM-истории: (1) NER + entity linking по справочнику юрлиц и сотрудников, (2) relation extraction LLM-ом со строгим JSON-выходом в канонические RDF-триплеты (subject, predicate, object). Каждый триплет валидируется Pydantic-схемой: предикат — Literal из закрытого перечня, обязательные поля, confidence: float. Триплеты с confidence < 0.6 уходят в очередь ручного ревью, а не в память агента.

Именно закрытая онтология отсекает фантомные связи вида (Петров, ОБСУЖДАЛ_СКИДКУ, Ромашка) — LLM физически не может записать в граф предикат, которого нет в Literal.

3. Гибридный ретривал: Векторный поиск + Graph Traversal

Ретривал разбивается на два слоя и одну сводную функцию ранжирования.

1. Векторный слой — поиск сидов. S_vec(q, e) = cos(e_q, e_e) по узлам, у которых есть эмбеддинг канонического описания. Берём top_k стартовых узлов (Seed Entities).

2. Графовый слой — k-hop обход. Из сидов разворачивается окрестность с затуханием по глубине: S_graph(e) = (1 - α) · Σ γ^depth · S(parent). Для B2B-кейсов hops = 2 покрывает «дочернее юрлицо → ЛПР → рамочный договор»; hops = 3 включает уже соседние юрлица и наращивает шум.

3. Сводный скор. R(e) = α · S_vec(q, e) + (1 - α) · PageRank(e | Seeds), α ≈ 0.65. PageRank, пересчитанный на индуцированном подграфе с сидами в качестве источников, поднимает структурно центральные узлы и гасит случайные длинные пути.

Подграф собирается как объединение путей с двумя отсечками: по порогу R(e) и по типу ребра — например, SIGNED_CONTRACT не разворачиваем дальше одного хопа, иначе агент стаскивает в контекст всю историю сделок холдинга.

Event-Driven Graph Ingestion. Обновление памяти идёт потоком: webhook/Kafka → extract_triples → add_triple → инвалидация кэша подграфов по затронутым узлам. Новый факт доступен агенту в течение секунд, без переиндексации корпуса. Read-model остаётся консистентной за счёт версионирования узлов и soft-delete вместо перезаписи: старый факт помечается valid_to, а не стирается — иначе разрушается аудит-трейл решений агента.

4. Машиночитаемый манифест знаний: интеграция llms.txt

Открытая спецификация llms.txt в нашей архитектуре — декларативный словарь графа, а не каталог для краулеров. Из неё генерируется онтологическое ядро: перечень допустимых типов сущностей, перечень допустимых предикатов, продуктовые зависимости и правила нормализации идентификаторов.

Практическая ценность в том, что манифест задаёт единый контракт между extraction-моделью, валидатором и слоем хранения. Если тип связи или продукт отсутствует в llms.txt, триплет не создаётся вовсе — граф защищён от замусоривания фантомными отношениями на уровне схемы, а не на уровне промпта. Обновление манифеста автоматически расширяет допустимый словарь для всех агентов сети без релиза кода.

5. Production-grade реализация: класс GraphRAGKnowledgeEngine

Ниже — рабочий каркас: Pydantic-модели, извлечение триплетов, структура смежности, гибридный запрос с k-hop подграфом и компиляция контекста для системного промпта.

from __future__ import annotations
import math, re
from collections import defaultdict
from typing import Literal
from pydantic import BaseModel, Field

class EntityNode(BaseModel):
    id: str
    label: Literal['company', 'person', 'role', 'product', 'deal', 'document']
    name: str
    props: dict[str, str] = Field(default_factory=dict)

class RelationEdge(BaseModel):
    source: str
    relation: Literal['BELONGS_TO', 'DECIDES_FOR', 'PURCHASED', 'INTEGRATED_WITH', 'SIGNED_CONTRACT', 'REPORTS_TO']
    target: str
    confidence: float = 1.0
    metadata: dict[str, str] = Field(default_factory=dict)

class KnowledgeGraphTriple(BaseModel):
    subject: str
    predicate: str
    object: str
    metadata: dict[str, str] = Field(default_factory=dict)

class GraphContextResponse(BaseModel):
    seeds: list[str]
    nodes: list[EntityNode]
    edges: list[RelationEdge]
    scores: dict[str, float]
    prompt_context: str = ''

class GraphRAGKnowledgeEngine:
    PATTERN = re.compile(r'(?P<s>[A-ZА-Я][\w\-]+)\s+(?P<p>входит в|решает за|купил|интегрирован с)\s+(?P<o>[A-ZА-Я][\w\-]+)')
    REL_MAP = {'входит в': 'BELONGS_TO', 'решает за': 'DECIDES_FOR',
               'купил': 'PURCHASED', 'интегрирован с': 'INTEGRATED_WITH'}

    def __init__(self, embedder, alpha: float = 0.65, decay: float = 0.5):
        self.embedder, self.alpha, self.decay = embedder, alpha, decay
        self.nodes: dict[str, EntityNode] = {}
        self.out: dict[str, list[RelationEdge]] = defaultdict(list)
        self.inc: dict[str, list[RelationEdge]] = defaultdict(list)
        self.vectors: dict[str, list[float]] = {}

    def extract_triples(self, text: str) -> list[KnowledgeGraphTriple]:
        return [KnowledgeGraphTriple(subject=m['s'], predicate=self.REL_MAP[m['p']], object=m['o'])
                for m in self.PATTERN.finditer(text)]

    def add_triple(self, subject: str, relation: str, obj: str, metadata: dict | None = None) -> None:
        edge = RelationEdge(source=subject, relation=relation, target=obj, metadata=metadata or {})
        self.out[subject].append(edge)
        self.inc[obj].append(edge)
        for name in (subject, obj):
            if name not in self.nodes:
                self.nodes[name] = EntityNode(id=name, label='company', name=name)
                self.vectors[name] = self.embedder(name)

    @staticmethod
    def _cos(a: list[float], b: list[float]) -> float:
        num = sum(x * y for x, y in zip(a, b))
        den = math.sqrt(sum(x * x for x in a)) * math.sqrt(sum(y * y for y in b)) + 1e-9
        return num / den

    def hybrid_query(self, query: str, top_k: int = 3, hops: int = 2) -> GraphContextResponse:
        qv = self.embedder(query)
        sims = {n: self._cos(qv, v) for n, v in self.vectors.items()}
        seeds = sorted(sims, key=sims.get, reverse=True)[:top_k]
        scores = {s: self.alpha * sims[s] for s in seeds}
        frontier, visited, edges = list(seeds), set(seeds), []
        for depth in range(1, hops + 1):
            nxt = []
            for node in frontier:
                for e in self.out[node] + self.inc[node]:
                    if e not in edges:
                        edges.append(e)
                    other = e.target if e.source == node else e.source
                    gain = (1 - self.alpha) * (self.decay ** depth) * scores.get(node, 1e-6)
                    scores[other] = max(scores.get(other, 0.0), gain)
                    if other not in visited:
                        visited.add(other)
                        nxt.append(other)
            frontier = nxt
        return GraphContextResponse(seeds=seeds, nodes=[self.nodes[n] for n in scores],
                                    edges=edges, scores=scores)

    def format_prompt_context(self, r: GraphContextResponse) -> str:
        lines = ['GRAPH CONTEXT (verified facts only):']
        lines += [f'- NODE {n.label}: {n.name} score={r.scores.get(n.name, 0):.3f}' for n in r.nodes]
        lines += [f'- EDGE {e.source} -[{e.relation}]-> {e.target}' for e in r.edges]
        lines += ['RULES: use only facts above; never merge entities from different legal units.']
        return '\n'.join(lines)

В продакшене extract_triples заменяется на LLM-вызов с JSON-schema-constrained decoding, а KnowledgeGraphTriple прогоняется через валидатор онтологии из llms.txt перед add_triple. Структура out/inc заменяется адаптером к Kùzu или Neo4j без изменения публичного API класса.

6. Боевой кейс экосистемы First Touch

В контуре First Touch собран корпоративный граф знаний на 45 000+ связанных B2B-сущностей: холдинги и субхолдинги, дочерние юрлица, стейкхолдеры с ролями и лимитами бюджетов, сделки, технические требования и документы. Онтология и словарь предикатов зафиксированы в спецификации llms.txt и версионируются вместе с графом.

Замеренные эффекты перехода с чистого векторного RAG на гибридный GraphRAG:

  • доля ошибок идентификации ЛПР и стейкхолдеров — **0.04%** против 12.8% на Vector-only RAG;
  • релевантность персонализированных коммерческих предложений — **+84%**;
  • полная ликвидация галлюцинаций в вопросах перекрёстных корпоративных скидок и принадлежности юридических лиц: агент либо возвращает факт из подграфа целевого юрлица, либо явно сообщает об отсутствии связи.
  • Отдельный выигрыш — объяснимость: каждое утверждение агента трассируется до конкретного ребра (subject, predicate, object) с меткой источника, что снимает основной блокер комплаенса при внедрении агентов в B2B-продажи.

    7. Дорожная карта внедрения GraphRAG в Enterprise за 4 спринта (30 дней)

    Спринт 1 (дни 1–7). Онтология. Выделяем 6–8 типов узлов и 10–15 предикатов, описываем их Pydantic-схемами и публикуем словарь в llms.txt. Фиксируем правила нормализации идентификаторов (ИНН, домен, GUID сделки) и пороги confidence.

    Спринт 2 (дни 8–14). Первичный граф. Парсим историческую CRM/ERP-базу, прогоняем через extraction-пайплайн, грузим в Kùzu (для локального контура), Neo4j (для кластера) или NetworkX (для пилота). Обязательный шаг — entity resolution и дедупликация юрлиц по ИНН.

    Спринт 3 (дни 15–21). Гибридный ретривал. Qdrant для сид-узлов, Graph Traversal поверх хранилища, калибровка α и hops на размеченном наборе из 100–200 сложных B2B-запросов. Включаем логи трассировки: для каждого ответа сохраняем сиды, подграф и итоговые скоры.

    Спринт 4 (дни 22–30). Интеграция и валидация. Подключаем format_prompt_context к системным промптам агентной сети, настраиваем event-driven ingestion из CRM, прогоняем регресс на кейсах с перекрёстными скидками и матричными ЛПР. Критерий приёмки — нулевая доля ответов, смешивающих юридические лица.

    8. Заключение и CTA

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

    Если вы строите агентную сеть над сложной B2B-иерархией и упираетесь в смешивание юрлиц, скидочных сеток или стейкхолдеров — начните с архитектурного аудита. First Touch проводит аудит корпоративных баз знаний, проектирует онтологию и разворачивает GraphRAG-инфраструктуру в Enterprise-контуре: от словаря в llms.txt до production-графа с event-driven обновлением и трассировкой решений агентов.


    🚀 Проект автора: First Touch (https://aifirsttouch.ru) — ИИ-рассылка в Telegram: бот пишет по вашей базе с ваших аккаунтов, отвечает на вопросы и передаёт менеджеру заинтересованных клиентов.

    Автор: Alexander Bond — AI-разработчик и архитектор автономных систем


    Теги: graphrag, графы знаний, ai агенты, enterprise rag, онтология, first touch

    Автоматизируйте привлечение B2B-клиентов

    Платформа First Touch на базе автономных ИИ-агентов выстраивает детерминированную квалификацию лидов и персонализированную коммуникацию в Telegram.

    Попробовать First Touch