Реалізація Graph RAG (видобування з графа знань)
Graph RAG — архітектура, що розширює стандартний векторний RAG структурою графа знань. Замість пошуку тільки за семантично близьким чанкам, система може перетинати граф: від сущности через зв'язки знаходити пов'язані концепції, які не містять ключових слів запиту, але семантично релевантні. Microsoft Research в 2024 році опублікувала GraphRAG — найвпливовішу реалізацію цього підходу.
Коли потрібен Graph RAG
Стандартний RAG не справляється з:
- Запитаннями про відношення між сущностями ("Як пов'язані компанія X і контракт Y?")
- Глобальними суммаризуючими запитаннями ("Які основні теми в корпусі документів?")
- Multi-hop reasoning ("Хто є керівником відділу, відповідального за договір №123?")
- Трекингом змін у часі
Архітектура Microsoft GraphRAG
Документи
↓
LLM видобуває сущності та зв'язки
↓
Граф знань (NetworkX/Neo4j)
↓
Ієрархічне виявлення спільнот (Leiden algorithm)
↓
Суммарі спільнот → Community reports
↓
Два режими пошуку:
├── Local search: вектор + graph-traversal від точки
└── Global search: суммаризація community reports
Видобування сущностей та зв'язків через LLM
from openai import OpenAI
import json
client = OpenAI()
ENTITY_EXTRACTION_PROMPT = """Видобути сущності та зв'язки з наступного тексту.
Верни JSON:
{{
"entities": [
{{"id": "1", "name": "...", "type": "PERSON|ORG|CONTRACT|REGULATION|CONCEPT", "description": "..."}}
],
"relationships": [
{{"source": "id1", "target": "id2", "relation": "SIGNED|MANAGES|REFERS_TO|PART_OF", "description": "..."}}
]
}}
Текст:
{text}"""
def extract_graph_elements(text: str) -> dict:
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": ENTITY_EXTRACTION_PROMPT.format(text=text)}],
response_format={"type": "json_object"},
temperature=0,
)
return json.loads(response.choices[0].message.content)
Побудова графа знань з NetworkX
import networkx as nx
from typing import List
class KnowledgeGraph:
def __init__(self):
self.graph = nx.DiGraph()
self.entity_embeddings = {}
def add_elements(self, elements: dict, source_doc: str):
# Додаємо сущності
for entity in elements["entities"]:
self.graph.add_node(
entity["id"],
name=entity["name"],
type=entity["type"],
description=entity["description"],
source=source_doc,
)
# Додаємо зв'язки
for rel in elements["relationships"]:
self.graph.add_edge(
rel["source"],
rel["target"],
relation=rel["relation"],
description=rel["description"],
)
def get_subgraph(self, entity_id: str, depth: int = 2) -> nx.DiGraph:
"""Повертає подграф навколо сущності"""
nodes = {entity_id}
for _ in range(depth):
neighbors = set()
for node in nodes:
neighbors.update(self.graph.predecessors(node))
neighbors.update(self.graph.successors(node))
nodes.update(neighbors)
return self.graph.subgraph(nodes)
def serialize_subgraph(self, subgraph: nx.DiGraph) -> str:
"""Перетворює подграф на текст для контексту LLM"""
lines = []
for node in subgraph.nodes(data=True):
lines.append(f"Сущність: {node[1].get('name')} ({node[1].get('type')})")
lines.append(f" Опис: {node[1].get('description', '')}")
for edge in subgraph.edges(data=True):
source_name = subgraph.nodes[edge[0]].get("name", edge[0])
target_name = subgraph.nodes[edge[1]].get("name", edge[1])
lines.append(f"Зв'язок: {source_name} → {target_name} ({edge[2].get('relation')})")
lines.append(f" {edge[2].get('description', '')}")
return "\n".join(lines)
Local Search: GraphRAG запит
from langchain_openai import OpenAIEmbeddings
import numpy as np
class GraphRAGRetriever:
def __init__(self, knowledge_graph: KnowledgeGraph, vectorstore, embeddings):
self.kg = knowledge_graph
self.vectorstore = vectorstore
self.embeddings = embeddings
def local_search(self, query: str, top_k: int = 5) -> str:
"""
Local Search: комбінує векторний пошук
з graph-traversal від знайдених сущностей
"""
# 1. Векторний пошук чанків
vector_docs = self.vectorstore.similarity_search(query, k=top_k)
# 2. Видобування сущностей з знайдених чанків
mentioned_entities = self._extract_entities_from_docs(vector_docs, query)
# 3. Graph traversal: розширюємо контекст через пов'язані вузли
graph_contexts = []
for entity_id in mentioned_entities[:3]:
subgraph = self.kg.get_subgraph(entity_id, depth=2)
graph_context = self.kg.serialize_subgraph(subgraph)
graph_contexts.append(graph_context)
# 4. Об'єднуємо текстовий та граф-контекст
vector_context = "\n\n".join([d.page_content for d in vector_docs])
graph_context = "\n\n".join(graph_contexts)
return f"## Текстовий контекст\n{vector_context}\n\n## Контекст з графа знань\n{graph_context}"
Практичний кейс: аналіз корпоративної документації
Задача: асистент юридичного відділу для аналізу відношень між контрагентами, договорами та співробітниками (6500 договорів, 12 років історії).
Запитання, які не вирішував стандартний RAG:
- "Які постачальники брали участь в тендерах, де переможець пізніше був признаний банкротом?"
- "Які договори станут затронути зміною керівництва в компанії X?"
Граф: 45 000 сущностей, 180 000 зв'язків (Neo4j).
Результати:
- Multi-hop запитання (2+ переходи): вирішувалися у 12% стандартним RAG → 71% Graph RAG
- Глобальні суммаризуючі запитання: 34% → 82%
- Стандартні запитання (пошук факту): зопоставимо, незначний регрес (-3%)
- Час побудови графа: 4 дні (GPT-4o для видобування, $240)
Інструменти для Graph RAG
-
Microsoft GraphRAG library:
pip install graphrag— повна реалізація від Microsoft -
Neo4j + LangChain:
Neo4jGraph+GraphCypherQAChainдля Cypher-запитів -
LlamaIndex + Knowledge Graph:
KnowledgeGraphIndex - NetworkX: легковесний граф в Python без зовнішніх залежностей
Графіки
- Розроблення extraction pipeline (LLM → граф): 2–3 тижні
- Побудова графа з існуючих документів: 1–4 тижні
- Local/Global search реалізація: 2 тижні
- Тестування та оцінка: 1–2 тижні
- Всього: 6–11 тижнів







