AACsearch
GraphRAG

Модель сущностей GraphRAG

Форма данных knowledge graph — GraphNode, GraphEdge, evidence-чанки и ограничения, обеспечивающие безопасный мультитенантный graph-запрос.

GraphRAG-enabled Knowledge space хранит направленный размеченный граф рядом с KnowledgeChunk. Эта страница — справочник по схеме, LLM-проходам, которые её наполняют, и ограничениям при прямых запросах.

Схема

Две таблицы в packages/database/prisma/schema.prisma:

GraphNode

model GraphNode {
  id               String         @id @default(cuid())
  knowledgeSpaceId String
  knowledgeSpace   KnowledgeSpace @relation(fields: [knowledgeSpaceId], references: [id], onDelete: Cascade)
  canonicalName    String
  nodeType         String
  metadata         Json           @default("{}")
  createdAt        DateTime       @default(now())
  updatedAt        DateTime       @updatedAt
  deletedAt        DateTime?

  @@unique([knowledgeSpaceId, canonicalName, nodeType])
  @@index([knowledgeSpaceId, nodeType])
  @@map("graph_node")
}
ПолеНазначение
canonicalNameНормализованная метка сущности («OpenAI», «Wallet API», «PrestaShop Module»). Решается LLM при ingest.
nodeTypeСемантический тип: "Product", "Person", "Concept", "API", "Organization", "Document", "Event".
metadataСвободный JSON. Сейчас: { aliases: string[], firstSeenChunkId: string }. Эволюционирует аддитивно.
(spaceId, canonicalName, nodeType)Уникальный ключ. «OpenAI/Organization» — одна нода; «OpenAI/Product» — отдельная.

GraphEdge

model GraphEdge {
  id               String         @id @default(cuid())
  knowledgeSpaceId String
  knowledgeSpace   KnowledgeSpace @relation(fields: [knowledgeSpaceId], references: [id], onDelete: Cascade)
  fromNodeId       String
  toNodeId         String
  relationType     String
  weight           Float          @default(1)
  evidenceChunkId  String?
  metadata         Json           @default("{}")
  createdAt        DateTime       @default(now())
  deletedAt        DateTime?

  @@index([knowledgeSpaceId, fromNodeId, relationType])
  @@index([knowledgeSpaceId, toNodeId, relationType])
  @@map("graph_edge")
}
ПолеНазначение
fromNodeId / toNodeIdНаправленная пара. Порядок важен: «OpenAI provides GPT-4» — from=OpenAI, to=GPT-4, rel=provides.
relationTypeГлагол: "provides", "owns", "depends_on", "deprecates", "replaces", "integrates_with".
weightConfidence × частота. Нормализуется в [0, 1] per ingest. Несколько упоминаний — суммируются.
evidenceChunkIdЧанк-источник связи. Используется graphragExplain для цитирования.
metadataСвободный JSON: { confidence: number, model: string, extractedAt: ISO }.

Глобальной уникальности на edges нет — несколько (from, to, rel) могут сосуществовать, у каждой свой evidence-чанк. Агрегации суммируют weight.

Каскадные удаления

Всё каскадит на KnowledgeSpace.id:

  • Удалить space → все nodes и edges.
  • Удалить документ → его чанки каскадят, но edges, указывавшие на них, сохраняют evidenceChunkId через onDelete: SetNull — связь живёт, просто без цитаты. Намеренно: удаление одного документа не должно убивать граф, выведенный из двадцати других.

Как наполняются nodes и edges

Драйвер — buildGraphFromChunks в packages/api/modules/knowledge/lib/graphrag.ts. Два LLM-прохода:

Проход 1 — entity resolution

resolveEntitiesFromChunks(chunks) в entity-resolution.ts отправляет каждый чанк в модель с промптом, требующим:

  • canonicalName — нормализованная форма.
  • type — один из допустимых nodeType.
  • aliases — surface forms в чанке, маппящиеся в эту сущность.

Дедуп — внутри чанка и через чанки по (canonicalName, type). Существующие ноды переиспользуются через upsertGraphNode. Aliases мерджатся в metadata.aliases.

Проход 2 — relation typing

В дело идут только чанки с ≥ 2 разными сущностями. extractRelationsFromChunks запрашивает у модели для каждого:

  • from, to — канонические имена, выданные на проходе 1.
  • relationType — глагол в lower snake_case из небольшого типизованного словаря.
  • confidence0..1.

Принятая связь → новый GraphEdge с evidenceChunkId. Edges ниже confidence-порога (сейчас 0.5) — отбрасываются.

Прямой запрос графа

Граф можно дёргать через Prisma-хелперы из @repo/database — для кастомных дашбордов или evaluations:

import { db, listGraphEdgesForNodes } from "@repo/database";

// Все edges из ноды:
const outgoing = await db.graphEdge.findMany({
  where: { knowledgeSpaceId: spaceId, fromNodeId: nodeId },
});

// Двунаправленный fan-out из набора anchor-нод:
const edges = await listGraphEdgesForNodes({
  knowledgeSpaceId: spaceId,
  nodeIds: anchorIds,
});

Для мультитенант-безопасности каждый запрос ДОЛЖЕН содержать knowledgeSpaceId (Инвариант 5 на Knowledge). Репозиторные хелперы это форсят; прямой db.graphEdge.findMany без него — баг.

Confidence и шум

Два повода видеть «странные» связи:

  1. Hallucinated relation. Модель выдумала глагол. confidence обычно низкий; фильтруйте metadata.confidence > 0.7, если рендерите edges пользователю напрямую.
  2. Реальное, но противоречивое. Два чанка не согласны. weight накапливается, edges живут — но graphragExplain иногда процитирует оба. Лечить надо контент, не граф.

Job сообществ (graphragCommunities) учитывает только edges с weight ≥ 0.5.

Эволюция схемы

Схема заморожена (Инвариант 9). Ожидается аддитивно через:

  • Новые relationType — без миграции; колонка String.
  • Новые ключи в metadata JSON — аддитивно, валидация при записи.
  • Новые nodeType — без миграции; считайте множество открытым.

Переименование/удаление существующих — Gate A (явный аппрув), плюс ingest-time migration plan; молча переписывать historical edges нельзя.

Инспекция в дашборде

Дашборд Knowledge → Graph (Beta) показывает список нод, счётчики edges по relation type и обнаруженные сообщества. Визуальный explorer — roadmap.

Канонический путь инспекции пока — Postgres напрямую:

SELECT relationType, count(*) AS edges, avg(weight) AS avg_weight
FROM graph_edge
WHERE knowledgeSpaceId = '…'
  AND deletedAt IS NULL
GROUP BY 1
ORDER BY 2 DESC;

Что нужно держать в голове

  • 2-hop горизонт. graphragExplain ходит максимум 2 hops по умолчанию. Дальше шум перевешивает сигнал.
  • Нет поиска по атрибутам edge. Запрос «edges с metadata.confidence > 0.9» через public API недоступен — только через Prisma.
  • Нет write API. Edges и nodes создаются только ingest-пайплайном. Прямого user-facing создания нет.
  • Один граф на space. Cross-space джойны не поддерживаются.

Связанные страницы

On this page