Продвинутый

Graphify: локальный граф знаний из кода и PDF

Алексей Кузнецов
Алексей Кузнецов
Системный администратор4 ноября 2026 г.14 мин чтения

Graphify превращает код, документацию, SQL-схемы и PDF в граф знаний. Локальный AST-парсинг без векторного хранилища для Claude Code, Cursor и Codex.

Graphify: локальный граф знаний из кода и PDF

Graphify превращает код, документацию, SQL-схемы и PDF в граф знаний. Локальный AST-парсинг без векторного хранилища для Claude Code, Cursor и Codex.

Что делает Graphify и чем он отличается от векторных баз

Что делает Graphify и чем он отличается от векторных баз

Graphify берёт исходный код, SQL-схемы, Markdown-документацию и PDF, парсит их локально и складывает в ориентированный граф знаний. Результат можно передать Claude Code, Cursor или Codex — ИИ-агенту будет видно не просто «похожий фрагмент», а точные связи: какая функция вызывает другую, где объявлена таблица, какой раздел PDF ссылается на какой класс.

Подробнее про концепцию Graphify — Graphify: превращаем любой код в граф знаний.

С практической точки зрения, это инструмент для тех, кто устал от того, что векторный поиск выдаёт «почти подходящий» код вместо точного вызова. Если вам нужно знать, кто использует функцию calculate_total, векторная база может вернуть десять функций со словом «total» в названии. Graphify вернёт один список вызовов — без приближений.

Локальный детерминированный AST-парсинг

AST — Abstract Syntax Tree, синтаксическое дерево. Graphify обходит код не регулярками, а через встроенный парсер языка: для Python — модуль ast, для JavaScript/TypeScript — свой парсер на базе Espree или аналогичного. Результат детерминированный: подали парсеру тот же файл дважды — получишь идентичный граф.

Вот как выглядит простая функция на Python:

python
def calculate_total(items):
    total = 0
    for item in items:
        total += item.price
    return total

После парсинга Graphify превращает её в узлы и рёбра графа. Узел FunctionDef связывается с узлами Name (переменная total), BinOp (операция сложения), Return. Если в том же файле есть функция format_price, и calculate_total её вызывает, между ними появляется ребро CALLS.

Пример вывода в JSON:

json
{
  "nodes": [
    {"id": "func:calculate_total", "type": "function", "file": "billing.py", "line": 1},
    {"id": "func:format_price", "type": "function", "file": "billing.py", "line": 15},
    {"id": "var:total", "type": "variable", "file": "billing.py", "line": 2}
  ],
  "edges": [
    {"from": "func:calculate_total", "to": "var:total", "relation": "defines"},
    {"from": "func:calculate_total", "to": "func:format_price", "relation": "calls"},
    {"from": "func:calculate_total", "to": "var:total", "relation": "reads"}
  ]
}

Запуск парсинга выглядит так:

bash
# Graphify использует встроенный ast модуль Python — сторонние парсеры не нужны
graphify parse ./src --language python --output graph.json

Для SQL-схем и PDF принцип тот же: парсер извлекает сущности (таблицы, колонки, разделы документа) и связи между ними. Для PDF Graphify берёт текстовый слой, если он есть, и строит связи на основе заголовков и ссылок.

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

Почему в проекте нет векторного хранилища

Векторные базы вроде Pinecone, Milvus или pgvector ищут по сходству эмбеддингов. Для неструктурированного текста это работает неплохо, но для кода подход имеет серьёзные недостатки.

Первая проблема — недетерминизм. Один и тот же запрос к векторной базе может вернуть разные наборы документов в зависимости от порядка индексации, версии модели эмбеддингов и случайности в приближённом поиске nearest neighbours. Для инженера это неприемлемо: вы хотите знать, кто именно вызывает функцию, а не получать «похожие» функции.

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

Третья — ресурсы. Индексация эмбеддингов требует GPU или хотя бы мощного CPU, хранение векторов занимает место, а обновление индекса при изменении кода — это отдельная задача. Graphify обходится без этого: парсер работает на любом ноутбуке, а граф хранится в обычном JSON или SQLite.

Вот как выглядит запрос к графу в Graphify:

bash
graphify query "кто вызывает calculate_total" --format graph

Результат — не список похожих документов, а конкретные узлы графа:

func:calculate_total -> func:format_price (CALLS)
func:calculate_total -> func:validate_input (CALLS)
func:process_order -> func:calculate_total (CALLED_BY)

Если мы посмотрим на архитектуру ИИ-агентов вроде Claude Code, они строят контекст по файлам. Graphify даёт им не просто файл, а ребро графа: «эта функция вызывается отсюда, а отсюда импортируется». Это ближе к тому, как человек читает код, чем к семантическому поиску по векторам.

ИИ-агенты и ленивый подход к коду — Ponytail: как заставить ИИ-агента писать меньше кода.

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

Подключение навыка /graphify к Claude Code, Cursor и Codex

Graphify превращает исходный код, PDF и SQL-схемы в локальный граф знаний. Он обходит AST, извлекает связи между функциями, классами, таблицами и разделами документации, и сохраняет всё в одном файле — без векторного хранилища и без вероятностных угадываний. Для Claude Code, Cursor и Codex это значит, что агент может спросить «где используется эта функция?» и получить точный ответ с путями к файлам, а не правдоподобную догадку.

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

Установка и настройка навыка

Во-первых, ставим Graphify. Я предпочитаю ставить бинарь глобально, чтобы не тащить зависимости в каждый проект:

bash
npm install -g graphify@latest

Потом инициализируем граф в корне проекта:

bash
cd ~/projects/myapp
graphify init --include "*.ts,*.py,*.sql,*.pdf"

Команда обойдёт указанные расширения, построит AST для кода, извлечёт схему из SQL и текст из PDF, и запишет всё в локальный файл graphify.db. Векторного хранилища не будет — только граф и индекс.

Теперь подключаем навык к каждому инструменту.

Claude Code

Claude Code читает навыки из ~/.claude-code/skills.json. Прописываем путь к бинарнику и рабочую директорию проекта:

json
{
  "skills": {
    "graphify": {
      "command": "graphify",
      "args": ["serve", "--db", "/home/alex/projects/myapp/graphify.db"],
      "cwd": "/home/alex/projects/myapp"
    }
  }
}

Cursor

Cursor использует MCP-серверы. Добавляем запись в .cursor/mcp.json в корне проекта:

json
{
  "mcpServers": {
    "graphify": {
      "command": "graphify",
      "args": ["mcp", "--db", "./graphify.db"]
    }
  }
}

После перезапуска Cursor навык появится в списке доступных инструментов агента.

Codex

Для Codex CLI навык настраивается в ~/.codex/skills.yaml:

yaml
skills:
  graphify:
    binary: /usr/local/bin/graphify
    args:
      - serve
      - --db
      - /home/alex/projects/myapp/graphify.db
    env:
      GRAPHIFY_LOG_LEVEL: info

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

Пример запроса к кодовой базе

Когда навык подключён, агент видит инструмент /graphify и может запрашивать граф напрямую. Вот как это выглядит на практике.

Допустим, мы рефакторим модуль оплаты и хотим понять, какие функции затрагивают таблицу payments. Вместо того чтобы грепать по репозиторию, спрашиваем агента:

Используй /graphify. Найди все функции в коде, которые делают INSERT или UPDATE в таблицу payments, и покажи, где они вызываются.

Агент обойдёт AST, найдёт связи между SQL-запросами и функциями, и вернёт что-то вроде:

src/services/billing.ts:42 — processPayment() → INSERT INTO payments
src/services/billing.ts:78 — refundPayment() → UPDATE payments SET status = 'refunded'
src/routes/payments.ts:15 — вызывает processPayment из billing.ts
src/routes/payments.ts:33 — вызывает refundPayment из billing.ts

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

Если нужно проиндексировать новый PDF с документацией, кладу файл в папку docs/ и запускаю:

bash
graphify index --add ./docs/api-spec.pdf

После этого агент сможет отвечать на вопросы по содержимому PDF, ссылаясь на конкретные страницы и разделы.

По теме на этом сайте:

Что попадает в граф: код, документация, SQL, конфиги, PDF

Что попадает в граф: код, документация, SQL, конфиги, PDF

Graphify не использует векторное хранилище. Он строит ориентированный граф, где узлы — файлы, функции, таблицы, разделы документов, а рёбра — реальные зависимости, извлечённые из текста. Для Claude Code, Cursor и Codex это значит, что агент получает не «похожий по смыслу» фрагмент, а точную цепочку зависимостей. Если вы уже читали Graphify: превращаем любой код в граф знаний, там показывали общую идею; здесь разберём, что именно попадает в граф и как читать связи между узлами.

Поддерживаемые источники и типы связей

Graphify берёт на вход пять типов источников. Каждый парсится своим способом, но результат попадает в одну модель графа.

Исходный код. AST-парсинг работает для Python, JavaScript, TypeScript, Go и ряда других языков. Извлекаются определения функций, классов, импорты и вызовы.

Пример Python-файла:

python
# src/services/auth.py
from db.models import User
from core.config import settings

def authenticate(username: str, password: str) -> User:
    user = User.get_by_username(username)
    if not user:
        raise AuthError("user not found")
    return user

Graphify создаст узлы для файла, функции authenticate и класса User, а также рёбра:

  • authenticate → User.get_by_username (тип calls)
  • authenticate → settings (тип reads)

Документация. Markdown и reST файлы делятся на разделы по заголовкам. Между разделами ставятся рёбра references, если в тексте есть ссылка на другой документ или раздел.

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

Пример:

sql
-- schema.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password_hash TEXT NOT NULL
);

Здесь узел users свяжется с узлом auth.py через ребро queries, если в коде есть обращение к таблице.

Конфиги. YAML, JSON, TOML, INI. Graphify извлекает ключи и значения, затем связывает конфигурационный узел с сервисом, который его читает.

Пример конфига Graphify:

yaml
# graphify.yaml
sources:
  - path: ./src
    language: python
    parser: ast
  - path: ./docs
    type: markdown
  - path: ./schema.sql
    type: sql
  - path: ./pdfs
    type: pdf
    extract: text

PDF. Извлекается текстовый слой. Если PDF содержит только сканы, без OCR ничего не выйдет — Graphify не делает оптическое распознавание сам. Из текста строятся узлы разделов и рёбра между ними.

Типы связей (рёбер) зависят от источника, но я придерживаюсь такого набора:

  • imports / includes — один модуль тянет другой;
  • calls — функция вызывает другую функцию;
  • defines — файл или блок объявляет сущность (таблицу, класс, константу);
  • queries — код обращается к таблице или представлению в БД;
  • reads / writes — чтение или запись конфигурации;
  • references — ссылка в документации на другой раздел или файл.

Во-первых, это даёт предсказуемость: если вы знаете тип ребра, вы знаете, что произойдёт при изменении узла. Во-вторых, нет «семантического сходства» на уровне векторов — только реальные связи из текста.

Как читать рёбра графа

Ребро в Graphify читается как утверждение: «источник делает что-то с целью». Формат в выводе обычно такой:

src/services/auth.py --[calls]--> db/models.py::User.get_by_username

Разбираем по частям:

  • src/services/auth.py — источник, файл или функция;
  • --[calls]--> — тип связи, направление от источника к цели;
  • db/models.py::User.get_by_username — цель с уточнением, какой именно объект в файле.

С практической точки зрения, рёбра нужны для ответа на вопрос «что сломается, если я поменяю это?». Допустим, вы решаете переименовать колонку email в таблице users. В графе найдёте узел users.email, пройдёте по входящим рёбрам типа queries и увидите список сервисов, которые обращаются к этой колонке. В моей практике такой обход сэкономил часы поиска по grep-у, когда в проекте было больше ста микросервисов.

Пример для Claude Code или Cursor: агент спрашивает, где используется константа MAX_RETRY_COUNT. Вместо того чтобы гадать по семантике, он идёт по рёбрам reads от узла конфига к узлам сервисов и выдаёт точный список файлов.

Если вы используете Graphify вместе с Ponytail, получается интересная связка: Ponytail ограничивает объём кода, который пишет агент, а Graphify показывает, какие существующие узлы затрагивает изменение. Это снижает количество дублирующего кода и случайных связей. Подробнее про Ponytail можно почитать в материалах Ponytail: как заставить ИИ-агента писать меньше кода и Ponytail: ленивый сеньор для вашего ИИ-агента.

Важный нюанс: граф отражает только то, что явно написано в коде и документах. Неявные зависимости — например, ожидание, что колонка заполнена не-null значением, хотя в схеме стоит NULL — в графе не отобразятся. Это не недостаток, а особенность статического анализа. Для динамических проверок нужны тесты и мониторинг, но это уже другая история.

Ограничения Graphify и когда подход не подходит

Ограничения Graphify и когда подход не подходит

Graphify строит локальный граф знаний из исходного кода, документации, SQL-схем и PDF. Подход без векторного хранилища выглядит привлекательно: нет необходимости поднимать Qdrant или Milvus, нет проблем с эмбеддингами и переиндексацией при изменении кода. Но у этого подхода есть границы, и лучше знать их заранее, чем обнаружить, что индексация висит уже третий час.

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

Размер репозитория и производительность

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

Во-первых, нет волшебной кнопки «проиндексировать всё и забыть». Каждое изменение в коде требует перепарсинга хотя бы затронутых файлов. Если в команде десять разработчиков и пуш в основную ветку происходит каждые десять минут, локальный граф может устареть быстрее, чем пересоберётся.

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

yaml
# .graphify.yaml
include:
  - src/
  - lib/
  - packages/*/src
exclude:
  - node_modules/
  - target/
  - .git/
  - dist/
  - build/

Если репозиторий всё равно велик, разбейте его на более мелкие части или индексируйте отдельно каждый сервис. Для огромных монорепозиториев подход с локальным графом может стать узким местом — в таких случаях векторное хранилище с инкрементальной индексацией справится лучше. Если вы уже оптимизируете работу агентов, рекомендую материал про ECC и производительность agent harness.

Поддерживаемые языки и форматы файлов

Graphify полагается на парсеры AST для каждого языка. На момент написания поддерживаются основные языки: JavaScript, TypeScript, Python, Go, Rust, Java, C# и несколько других. Если ваш проект написан на менее распространённом языке — например, на Elixir, Haskell или старом Perl — файл может быть пропущен или распознан только как обычный текст без структурных связей.

С PDF ситуация ещё проще: Graphify извлекает текстовый слой. Если PDF содержит сканированные страницы без OCR, текст извлечь не получится. SQL-схемы парсятся как DDL, но не все диалекты поддерживаются одинаково хорошо — специфичные расширения PostgreSQL или Oracle могут игнорироваться.

Посмотреть список поддерживаемых языков можно командой:

bash
graphify list-languages

Пример вывода:

javascript
typescript
python
go
rust
java
csharp

Если в вашем стеке преобладает язык из этого списка, Graphify будет полезен. Если нет — подход не подходит, и стоит посмотреть в сторону инструментов с векторным хранилищем или ручного построения графа.

Предсказуемость конфигурации — это то, чем Graphify привлекает на небольших проектах, и то, чего не хватает на больших. Знайте границы инструмента, и он не подведёт.

Часто задаваемые вопросы

Нужно ли устанавливать векторную базу данных для работы Graphify?

Нет, векторное хранилище не требуется. Graphify строит ориентированный граф на основе локального AST-парсинга, поэтому все связи между функциями, классами и таблицами вычисляются детерминированно. С практической точки зрения, вы получаете точные вызовы вместо приближённых совпадений по ключевым словам.

Какие языки программирования и форматы файлов поддерживает Graphify?

На данный момент Graphify парсит Python через встроенный модуль ast, а JavaScript и TypeScript — через парсер на базе Espree. Помимо кода, инструмент обрабатывает SQL-схемы, Markdown-документацию и PDF. Если файл не входит в этот список, граф для него не соберётся, поэтому стоит заранее проверить стек проекта.

Как Graphify интегрируется с Claude Code, Cursor и Codex?

Результат работы Graphify экспортируется в формате, который ИИ-агент может прочитать как контекст. Claude Code, Cursor или Codex видят не отдельные фрагменты, а связи: кто вызывает функцию, где объявлена таблица, какой раздел PDF ссылается на класс. Это позволяет задавать точные вопросы об архитектуре, а не гадать по ключевым словам.

Нужно ли пересобирать граф после каждого изменения в коде?

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

Безопасно ли использовать Graphify с закрытым исходным кодом?

Да, весь парсинг выполняется локально на вашем оборудовании, и данные не отправляются на внешние серверы. Graphify не требует доступа к облачному API для построения графа, поэтому закрытый код остаётся внутри вашей сети. Это особенно важно для команд, работающих с коммерческой тайной или персональными данными.

Поделиться:TelegramX / TwitterVK

Читайте также