MS GraphRAG, Ollama и эксперимент с киберпанком

MS GraphRAG, Ollama и эксперимент с киберпанком

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

Почему Ollama?

Выделить отдельные вычислительные ресурсы под RAG в реальном проекте — непростая задача. Мне нужно было понять, насколько слабое «железо» может потянуть такие системы. В итоге выяснилось: даже 4-битные модели кое-как справляются.

Зачем тут киберпанк?

Я большой поклонник жанра и люблю тестировать RAG и LLM на знакомых текстах. Для экспериментов с Microsoft GraphRAG выбрал рассказ Уильяма Гибсона «Johnny Mnemonic» — сначала на английском. Проектная директория с результатами доступна на GitHub.

Граф знаний: первые впечатления

Граф построен с помощью Gephi и плагина Лейдена на основе сущностей и связей, извлечённых GraphRAG. Все данные — в файле graph.graphml в папке output. Подробная инструкция по визуализации прилагается. Результат впечатляет: такой граф вручную аналитик мог бы строить несколько дней.

Заметно, что сущности объединяются только при точном совпадении имён. Например, Molly Millions и Molly, Lo Tek и Lo Teks, Ralfi Face и Ralfi — не склеились. Однако алгоритм Лейдена в Gephi выделил сообщества, а GraphRAG — свои тематические кластеры, которые можно изучить в файле community_reports.parquet. Вот три примера высокоранговых сообществ (описания сокращены):

  • Сообщество: Yakuza, Sons of the Neon Chrysanthemum, Ono-Sendai
    Описание: В центре — Якудза, влиятельная криминальная организация. Конфликт с Sons of the Neon Chrysanthemum, промышленный шпионаж против Ono-Sendai и другие связи.
  • Сообщество: Johnny, Ralfi Face и связанные сущности
    Описание: Ядро — Джонни, центральный персонаж, втянутый в конфликты с Ральфи, Якудзой и Молли Миллионы.
  • Сообщество: Molly Millions, Ralfi, Drome
    Описание: Молли Миллионы — ключевая фигура в Дроме, где пересекаются преступные и бизнес-операции. Связи с Ральфи, Льюисом, Magnetic Dog Sisters и другими.

На мой взгляд, результат выглядит содержательно и логично.

Поиск по графу: глобальный и локальный

Ответы можно получать на русском, если подредактировать промпты в файле local_search_system_prompt.txt и global_search_reduce_system_prompt.txt и использовать LLM с поддержкой русского. Однако формулировать запросы лучше на английском — граф и описания элементов всё ещё на английском, и это повышает точность.

Глобальный поиск работает по принципу MapReduce: анализирует описания сообществ. Хорошо подходит для вопросов, требующих понимания всей картины. Например, на запрос о киберпанковских элементах в рассказе система выдала структурированный ответ с 8 пунктами:

  1. Интеграция технологий и биологии: кибернетические импланты, нейропертурбаторы — технологии проникают в тело. [Data: Reports (5, 7, 12, 2, +more)]
  2. Дистопические городские ландшафты: Найттаун, Дром — символы социального распада. [Data: Reports (1, 8, 2, +more)]
  3. Корпоративная конкуренция: шпионаж, нелегальные сделки — борьба за технологии и власть. [Data: Reports (0, 12, 2, +more)]
  4. Технологическое неравенство: LO TEK используют устаревшие фильтры, в то время как элита — киберимпланты. [Data: Reports (9, 2, +more)]
  5. Киберорганические существа: Джонс — кибердельфин, Молли — боевой киборг. [Data: Reports (11, 7, 2, +more)]
  6. Идентичность и анонимность: Джонни использует алиби, что отражает размытие личности в цифровом мире. [Data: Reports (1, 12, 2, +more)]
  7. Насилие и власть: жестокие столкновения, контроль через технологии. [Data: Reports (5, 7, 2, +more)]
  8. Сюрреалистические пространства: FUNLAND и торговые центры — смесь разрухи и функциональности. [Data: Reports (8, 11, 2, +more)]

Цифры в квадратных скобках — это human_readable_id из parquet-файлов. Их можно открыть, например, с помощью pandas.

Локальный поиск комбинирует данные из графа с фрагментами исходного текста. Подходит для вопросов о конкретных связях. Например, на запрос о персонаже Jones система выдала детальный ответ:

Jones — кибернетический дельфин с сенсорами и доступом к пресному резервуару в Funland. Сотрудничает с Кальмой, читающей следы. Играет ключевую роль в извлечении данных с чипа Джонни. [Data: Entities (28, 33); Relationships (44, 51, +more)]

Его основные связи:

  • С Molly Millions: совместная операция по извлечению данных. [Data: Entities (33); Relationships (44, 51, +more)]
  • С Johnny: использование сенсоров для доступа к информации. [Data: Entities (33); Relationships (44, 51, +more)]
  • С Squid: устройство на основе военных технологий НАВАЛЬНОЙ силы. [Data: Entities (19); Relationships (25, +more)]
  • С Yakuza: косвенная связь — Якудза опасаются, что Squid извлечёт их данные из мозга Джонни. [Data: Relationships (25); Entities (19)]

В реальной системе потребуется агент, определяющий, какой тип поиска использовать — глобальный или локальный.

В GraphRAG есть ещё два режима: BASIC и DRIFT. Базовый — обычный векторный поиск по чанкам. Полезно иметь для сравнения. DRIFT Search напоминает Query expansion, но работает очень медленно — учтите при экспериментах.

Попробовал DRIFT на вопросе про Jones. За 40 минут получил результат, похожий на локальный поиск. Возможно, вопрос был слишком простым, или нужна модель побольше.

Возникает вопрос: что делать с такими ответами в продакшене? Варианты: передавать эксперту или пропускать через ещё один LLM-запрос с обработкой human_readable_id и учётом истории диалога.

Какие модели работают?

Тестируемые модели:

  • Mistral 7B: не поддерживает глобальный поиск из-за проблем с JSON-выводом. Map-запросы падают.
  • Gemma3 4B и 12B: результаты похожи, но формулировки корявые. Граф проще, а Jones — человек, а не дельфин.
  • Qwen3 14B: результат устроил полностью. Все примеры в статье — на этой модели.

В качестве эмбеддинг-модели использовал user-bge-m3 от deepvk. Модель отлично работает на русском и справляется с английским.

Как воспроизвести эксперимент?

  1. Склонируйте репозиторий с проектной папкой. Переименуйте env.example в .env.
  2. Создайте и активируйте Python-окружение (например, через conda).
  3. Установите GraphRAG.
  4. Проверьте версию LiteLLM: должна быть не выше 1.82.6. Версии 1.82.7 и 1.82.8 — скомпрометированы.
  5. Установите пакеты для embedding_proxy (об этом ниже). Установите torch с CUDA или без — на CPU тоже будет работать.
  6. В отдельной консоли запустите прокси для эмбеддингов.
  7. В settings.yaml проверьте пути к LLM и эмбеддингу в секциях completion_models и embedding_models. Укажите api_base.
  8. В отдельной консоли загрузите LLM в Ollama.
  9. Остановите Ollama, установите переменные окружения:
    OLLAMA_HOST=0.0.0.0:11434 (если на другой машине)
    OLLAMA_CONTEXT_LENGTH=13000 (по умолчанию — 4K, маловато для промптов)
  10. Запустите Ollama: ollama serve.

Чтобы использовать свои тексты или перестроить индексы:

  • Удалите мой текст и поместите свои файлы в папку input.
  • Удалите папки cache, logs, output.
  • Запустите Ollama и embedding_proxy.
  • Выполните: graphrag index.

На машине с 16 ГБ GPU (не самой новой) обработка 38k слов («Johnny Mnemonic») занимает чуть больше часа.

Проблемы и костыли

GraphRAG использует LiteLLM как прокси. Ollama поддерживается, но иногда приходит запрос с некорректной ссылкой api/generation/api/show — возникает ошибка 404. К счастью, это не ломает пайплайн.

Запросы к LLM обрабатываются через очередь. Если ожидание превысит 10 минут — клиент отвалится. Параметр request_timeout в новых версиях GraphRAG не работает. Проблема в LiteLLM: у него таймаут 600 секунд по умолчанию для OpenAI-совместимых вызовов.

Самая серьёзная проблема — с эмбеддингами. Модели вроде bge-m3 в Ollama иногда выдают ошибку сериализации из-за Inf и NaN в JSON. Проблема известна с января 2026 года, но в версии Ollama 0.18.2 всё ещё актуальна. Поэтому я использую отдельный эмбеддинг-прокси: взял код из стороннего проекта, заменил вызов Ollama на langchain HF embedding. Криво, но зато можно использовать любые эмбеддинги с HuggingFace. По умолчанию — user-bge-m3. Чтобы сменить модель, используйте ключ model в CLI.

Если настраиваете эмбеддинг через Ollama — запускайте его отдельно, лучше на другой машине. Эмбеддинг используется в конце пайплайна, и если он упадёт — обидно после часа ожидания.

Важные настройки

Файл settings.yaml создаётся командой graphrag init. Его нужно отредактировать:

  • В секциях моделей укажите провайдера, модель и api_base с URL.
  • В vector_store задайте размер вектора. По умолчанию — 3072. Для bge-m3 нужно 1024, иначе пайплайн упадёт.
  • В snapshots установите graphml: true, если хотите визуализировать граф.

Плюсы и минусы GraphRAG

Если не считать, что под капотом всё равно есть векторный поиск, графовая система субъективно даёт более релевантные ответы, чем QA-система на чистом векторе. Но это не замена векторному поиску — скорее специализированное решение для сложных семантических текстов, где не нужна мгновенная реакция.

Плюсы:

  • Фреймворк сам выделяет сущности, связи, сообщества и генерирует описания.
  • Есть API и тестовое приложение на Streamlit — можно быстро собрать MVP.
  • Генератор потенциальных вопросов позволяет создавать бенчмарки и кешировать ответы (хотя я не рекомендую кешировать в RAG).

Минусы:

  • Очень высокая ресурсоёмкость. На обработку одного небольшого документа уходит столько времени, сколько хватило бы на векторную базу из тысяч текстов.
  • Примитивный чанкинг — по токенам или предложениям. Замена на семантический сплиттер или markdown-анализатор улучшила бы качество.
  • Хочется заменить векторное хранилище.

Генерацию ответов не тестировал — ноутбук с примерами опубликован с ошибками (например, «файл не найден»). Возможно, попробую позже. Также планирую переделать промпты и построить граф на русском. Если получится — выйдет апдейт.

Злоупотребление GraphRAG может вызвать графовую зависимость. Шутка. Берегите себя!

Читать оригинал