- BrainTools - https://www.braintools.ru -
Skeleton Indexing [1] (KDD 2025) + HippoRAG 2 [2] (ICML 2025) + VectorCypher [3] + Datalog Reasoning + 10 итераций оптимизации
Я реализовал Graph RAG систему, которая комбинирует 5 техник из свежих научных статей в единый пайплайн с декларативным reasoning-движком, полной провенансной трассировкой и типизированным API. Результат: 174/180 (96.7%) на билингвальном бенчмарке из 30 вопросов, оценённых в 6 режимах retrieval. Три режима достигли 100%. Ноль persistent failures.
GitHub: vpakspace/agentic-graph-rag [4]
Классический RAG — “разбей документ на чанки, сделай embeddings, найди похожие” — работает для простых фактоидных вопросов. Но он ломается на:
Вопросах о связях: “Как метод X связан с компонентом Y?” — ответ разбросан по разным чанкам
Multi-hop рассуждениях: “Что произойдёт, если изменить A, учитывая что A влияет на B, а B на C?”
Глобальных вопросах: “Перечисли все 7 архитектурных решений” — ответ в 7 разных местах документа
Кросс-языковых запросах: русский вопрос о концепциях из английского документа
Моя цель — система, которая справляется со всеми этими типами вопросов, а не только с простыми.
Проблема: извлечение сущностей из всех чанков — дорого (O(n) вызовов LLM).
Решение: строим KNN-граф по embeddings чанков → PageRank [5] → извлекаем сущности только из top-25% “скелетных” чанков. Периферийные чанки привязываем через keyword matching.
Chunks → KNN Graph → PageRank → Top-β Skeletal (full extraction)
→ Peripheral (keyword linking only)
Результат: 75% меньше вызовов LLM при сопоставимом качестве. Это не трюк — это математика [7]: PageRank выделяет чанки, которые наиболее “центральны” в семантическом пространстве документа.
Проблема: обычный GraphRAG теряет контекст полных пассажей. Обычный RAG теряет связи между сущностями.
Решение: два типа узлов в Neo4j [8]:
PhraseNode — сущность (имя, тип, PageRank score, embedding)
PassageNode — полный текст чанка (контент, embedding)
MENTIONED_IN — связывает сущности с пассажами
RELATED_TO — ко-вхождения между сущностями
Это даёт и навигацию по графу (через PhraseNode), и полный контекст (через PassageNode).
Гибридный retrieval в три фазы, вдохновлённый VectorCypherRetriever [3] из Neo4j GraphRAG:
Vector Index → находим ближайшие PhraseNode через cosine similarity
Cypher [9] Traversal → расширяем через RELATED_TO (до 3 хопов)
PassageNode Collection → собираем связанные пассажи → GraphContext
Ключевой инсайт: cosine re-ranking по реальным embeddings PassageNode из Neo4j бьёт RRF-фьюжн [10].
Три уровня маршрутизации с каскадным fallback:
|
Tier |
Метод |
Confidence |
Описание |
|---|---|---|---|
|
1 |
Mangle (Datalog [11]) |
0.7 |
65 билингвальных ключевых слов |
|
2 |
LLM (GPT-4o-mini) |
0.85 |
Классификация нейросетью |
|
3 |
Pattern (regex) |
0.5 |
Regex-паттерны как fallback |
Если качество retrieval ниже порога (relevance < 2.0 из 5), система эскалирует по цепочке инструментов:
vector_search → cypher_traverse → hybrid_search → comprehensive_search → full_document_read
Каждая попытка перефразирует запрос через LLM. Лучшие результаты отслеживаются по всем попыткам.
Полная реимплементация Google Mangle [12] (2,919 строк):
Lark [13]-based парсер с кастомной грамматикой
Semi-naive evaluation со стратифицированным отрицанием
35+ встроенных функций (арифметика, строки, списки, словари)
Temporal evaluation
Filter pushdown для внешних предикатов
Три файла правил:
routing.mg — маршрутизация запросов (65 ключевых слов)
access.mg — RBAC (role inheritance + permit/deny)
graph.mg — граф-инференс (reachable, common_neighbor, evidence)
% Транзитивное замыкание по графу
reachable(X, Y, 1) :- edge(X, R, Y).
reachable(X, Z, D) :- reachable(X, Y, D1), edge(Y, R, Z),
D = fn:plus(D1, 1), D < 5.
% Общие соседи двух сущностей
common_neighbor(A, B, N) :- edge(A, R1, N), edge(B, R2, N), A != B.
30 вопросов: 7 simple, 7 relation, 6 multi_hop, 6 global, 4 temporal
2 документа: Doc1 (русский, граф знаний) + Doc2 (английский, архитектура SCL)
6 режимов retrieval: vector, cypher, hybrid, agent_pattern, agent_llm, agent_mangle
180 оценок (30 × 6) через hybrid judge: embedding similarity + keyword overlap + LLM-as-judge [14]
v3: 38% ████░░░░░░░░░░░░░░░░ Baseline (вопросы на EN, документы на RU)
v4: 67% █████████░░░░░░░░░░░ +29pp — вопросы на RU (language match!)
v5: 73% ██████████░░░░░░░░░░ +6pp — comprehensive_search для global
v10: 65% █████████░░░░░░░░░░░ -8pp — добавили 15 новых вопросов
v11: 80% ████████████░░░░░░░░ +15pp — enumeration prompt
v12: 93% ██████████████████░░ +13pp — hybrid judge
v14: 96.7%███████████████████░ +3.7pp — semantic judge
|
Режим |
Результат |
|
|---|---|---|
|
Vector |
30/30 (100%) |
Чистый embedding search |
|
Hybrid |
30/30 (100%) |
Vector + Graph |
|
Agent (Mangle) |
30/30 (100%) |
Datalog правила |
|
Agent (LLM) |
29/30 (96%) |
GPT-4o-mini роутер |
|
Agent (Pattern) |
28/30 (93%) |
Regex паттерны |
|
Cypher |
27/30 (90%) |
Граф-траверсал |
|
Итого |
174/180 (96.7%) |
0 persistent failures |
Самое большое улучшение за всю историю проекта. Вопросы на английском о русском документе давали 38%. Переключение на русские вопросы — 67%. Embeddings хорошо справляются с кросс-языковым поиском, но LLM-генератор теряет контекст.
Ключевой инсайт v11: для глобальных вопросов ВСЕ нужные ключевые слова находились в top-30 чанков. Проблема была в том, что генератор не перечислял все пункты, а judge обрезал ответ до 500 символов.
Попытка сделать judge “умнее” через Chain-of-Thought [15] (“перечисли найденные ключевые слова → посчитай → выдай вердикт”) вызвала регрессию с 144/180 до 48/180. GPT-4o-mini буквально искал английские строки в русском тексте. Простой промпт “match CONCEPTS, not strings” работает в 3 раза лучше.
Hybrid search с Reciprocal Rank Fusion [10] давал худшие результаты, чем cosine re-ranking по реальным embeddings из Neo4j. RRF хорош для combining разных сигналов, но когда оба сигнала — embedding-based, прямое cosine similarity точнее.
Для вопросов с reference answer: cosine similarity между ответом системы и эталоном >= 0.65 → auto-PASS. Калибровка: правильный ответ ~0.677, неправильный (другой документ) ~0.570. Порог 0.65 идеально разделяет.
Русский вопрос о концепциях из английского документа (Doc2/SCL) ломает vector_search — он возвращает Doc1. Решение: детектируем кросс-языковой глобальный запрос → напрямую full_document_read вместо vector_search.
comprehensive_search (multi-query fan-out) генерирует N подзапросов → каждый через vector_search → RRF merge. Но если все подзапросы возвращают Doc1, то единственный full_document_read результат для Doc2 тонет в RRF-merge.
Ранний баг: каждая попытка перезаписывала предыдущие результаты. Если attempt 1 дал score 2.5, а attempt 2 — score 1.8, система возвращала 1.8. Фикс: трекаем best_results и best_score по всем попыткам.
Для глобальных вопросов (“перечисли все…”) обычный prompt генерирует текст, а не список. Специальный enumeration prompt: “Output a numbered list. Scan ALL chunks. Do not stop early.”
Обрезка ответа до 500 символов для judge убивала enumeration-ответы (7 пунктов ~ 1500 символов). Увеличение до 2000 — мгновенный +5pp.
Каждый запрос создаёт PipelineTrace:
{
"trace_id": "tr_abc123def456",
"router_step": {
"method": "mangle",
"decision": {"query_type": "simple", "suggested_tool": "vector_search"}
},
"tool_steps": [{
"tool_name": "vector_search",
"results_count": 10,
"relevance_score": 3.2,
"duration_ms": 150
}],
"escalation_steps": [],
"generator_step": {
"model": "gpt-4o-mini",
"confidence": 0.82
},
"total_duration_ms": 1800
}
API: FastAPI [16] REST (/api/v1/) + FastMCP [17] (SSE/MCP) — и для REST-клиентов, и для AI-агентов.
|
Метрика |
Значение |
|---|---|
|
Python LOC |
16,206 (118 файлов) |
|
Тесты |
586 (320 core + 108 PyMangle + 158 rag-core) |
|
Зависимости |
26 пакетов |
|
Итерации бенчмарка |
10 (v2 → v14) |
|
Файлы результатов |
15 JSON (~4.7 MB) |
|
Mangle-правила |
111 строк (3 файла) |
|
Классы |
36 (14 data models, 8 services, 6 config, 4 reasoning) |
Personalized PageRank [18] для query-focused графового обхода
Human evaluation в дополнение к LLM-as-judge
Streaming ответы в Streamlit [19] UI
Больше Mangle-правил — temporal reasoning, conflict resolution
|
Компонент |
Технология |
|---|---|
|
LLM |
|
|
Embeddings |
text-embedding-3-small [21] (1536 dim) |
|
Graph DB |
Neo4j 5.x [8] (Vector Index + Cypher) |
|
Reasoning |
PyMangle (Datalog [12], 2,919 LOC) |
|
Doc Parsing |
Docling [22] (PDF/DOCX/PPTX + GPU) |
|
Graph Algorithms |
NetworkX [23] (PageRank, KNN, PPR) |
|
API |
|
|
UI |
Streamlit [19] (7 tabs) |
|
Testing |
|
|
CI/CD |
GitHub Actions [26] |
Если вам интересны детали реализации или вы хотите обсудить Graph RAG — пишите в комментариях или открывайте issue на GitHub [4].
Автор: VladSpace
Источник [27]
Сайт-источник BrainTools: https://www.braintools.ru
Путь до страницы источника: https://www.braintools.ru/article/26125
URLs in this post:
[1] Skeleton Indexing: https://arxiv.org/abs/2502.09304
[2] HippoRAG 2: https://arxiv.org/abs/2502.14802
[3] VectorCypher: https://neo4j.com/docs/neo4j-graphrag-python/current/user_guide_rag.html
[4] vpakspace/agentic-graph-rag: https://github.com/vpakspace/agentic-graph-rag
[5] PageRank: https://en.wikipedia.org/wiki/PageRank
[6] Image: https://sourcecraft.dev/
[7] математика: http://www.braintools.ru/article/7620
[8] Neo4j: https://neo4j.com/
[9] Cypher: https://neo4j.com/docs/cypher-manual/current/introduction/
[10] RRF-фьюжн: https://plg.uwaterloo.ca/~gvcormac/cormacksigir09-rrf.pdf
[11] Datalog: https://en.wikipedia.org/wiki/Datalog
[12] Google Mangle: https://github.com/google/mangle
[13] Lark: https://github.com/lark-parser/lark
[14] LLM-as-judge: https://arxiv.org/abs/2306.05685
[15] Chain-of-Thought: https://arxiv.org/abs/2201.11903
[16] FastAPI: https://fastapi.tiangolo.com/
[17] FastMCP: https://github.com/jlowin/fastmcp
[18] Personalized PageRank: https://en.wikipedia.org/wiki/Personalized_PageRank
[19] Streamlit: https://streamlit.io/
[20] OpenAI GPT-4o / GPT-4o-mini: https://platform.openai.com/docs/models
[21] text-embedding-3-small: https://platform.openai.com/docs/guides/embeddings
[22] Docling: https://github.com/docling-project/docling
[23] NetworkX: https://networkx.org/
[24] pytest: https://docs.pytest.org/
[25] ruff: https://github.com/astral-sh/ruff
[26] GitHub Actions: https://github.com/features/actions
[27] Источник: https://habr.com/ru/articles/1003064/?utm_source=habrahabr&utm_medium=rss&utm_campaign=1003064
Нажмите здесь для печати.