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:
pythondef 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:
bashgraphify 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. Я предпочитаю ставить бинарь глобально, чтобы не тащить зависимости в каждый проект:
bashnpm install -g graphify@latest
Потом инициализируем граф в корне проекта:
bashcd ~/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:
yamlskills:
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/ и запускаю:
bashgraphify index --add ./docs/api-spec.pdf
После этого агент сможет отвечать на вопросы по содержимому PDF, ссылаясь на конкретные страницы и разделы.
По теме на этом сайте:
- Graphify: превращаем любой код в граф знаний
- Ponytail: как заставить ИИ-агента писать меньше кода
- DietrichGebert/ponytail: Ленивый подход к AI-агентам
- ECC: оптимизация производительности agent harness для ИИ
- Ponytail: ленивый сеньор для вашего ИИ-агента
Что попадает в граф: код, документация, 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 могут игнорироваться.
Посмотреть список поддерживаемых языков можно командой:
bashgraphify 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 для построения графа, поэтому закрытый код остаётся внутри вашей сети. Это особенно важно для команд, работающих с коммерческой тайной или персональными данными.