Продвинутый

Graphify: knowledge graph для кода

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

Превращаем код, схемы и PDF в запросаемый knowledge graph. Локальный AST-парсинг без векторного хранилища для Claude Code, Cursor и других ИИ-агентов.

Graphify: knowledge graph для кода

Превращаем код, схемы и PDF в запросаемый knowledge graph. Локальный AST-парсинг без векторного хранилища для Claude Code, Cursor и других ИИ-агентов.

Зачем knowledge graph вместо обычного поиска по коду

Ограничения классического grep и LLM-чатов

Grep — это как искать ключи в комнате, где всё время меняется расклад. Классический поиск по подстроке не понимает контекста: он найдёт user_id везде, где это слово встречается, но не скажет, где оно используется как параметр функции, а где — как поле структуры.

bash
# Grep находит 47 совпадений, но неясно, какие из них — определения, а какие — вызовы
grep -rn "user_id" .

LLM-чаты хуже: они работают с тем, что им скормили в контекст. Обычно это 8–32 тысячи токенов. Для проекта в 100 тысяч строк кода это примерно 5% всего. ИИ видит отдельные файлы, но не связь между ними. Он не знает, что UserService в auth/ и UserController в api/ используют одну и ту же сущность, потому что не видит этой связи.

С практической точки зрения, это как пытаться собрать пазл, глядя только на отдельные кусочки.

Что такое knowledge graph и как он работает локально

Knowledge graph — это карта связей. Вместо того чтобы хранить код как плоский набор файлов, graph строит узлы (функции, классы, переменные, модули) и ребра (вызовы, наследование, зависимости, импорты).

Локальная реализация работает через AST-парсинг. Для Python это ast из стандартной библиотеки, для JavaScript — @swc/core, для Go — go/ast. Парсер разбирает каждый файл, извлекает сущности и их отношения, сохраняет в SQLite или embedded-БД вроде RocksDB.

python
# Пример: извлечение зависимостей из Python-файла
import ast

tree = ast.parse(open("service.py").read())
for node in ast.walk(tree):
    if isinstance(node, ast.ImportFrom):
        print(f"Импорт из {node.module}: {[n.name for n in node.names]}")

Полученный graph запросается не через векторный поиск, а через Cypher-подобные запросы или просто SQL с JOIN'ами:

sql
-- Кто вызывает функцию process_payment?
SELECT caller.name 
FROM calls c
JOIN functions caller ON c.caller_id = caller.id
JOIN functions target ON c.target_id = target.id
WHERE target.name = 'process_payment'

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

Связанная информация: Автоматизация и ИИ-агенты на сервере, Настройка CLAUDE.md для Claude Code по наблюдениям Андрея Карпатского.

Как устроен Graphify: AST-парсинг без векторного хранилища

Разбор архитектуры: deterministic подход

Графифай не использует вероятности. Ни в коем случае.

Когда я вижу «AI-анализ кода», мне сразу вспоминается та самая история с векторным хранилищем, которое «умное» перестраивало три раза в неделю и каждый раз выдало другой ответ на один и тот же запрос. Никакой предсказуемости — только «ну вроде работает». Graphify выбрал другой путь: deterministic подход. Каждый запуск даёт один и тот же результат. Это как настраивать сервер через конфиги, а не через пробование разных флагов в надежде, что что-то сработает.

Архитектура построена на трёх столпах:

  1. AST-парсинг на этапе импорта. Код разбирается на синтаксическое дерево с помощью специализированных парсеров для каждого языка. Никаких эмбеддингов, никаких LLM-средств для понимания структуры. Только чёткие правила.
  2. Построение графа по фиксированным правилам. Из узлов AST извлекаются сущности (функции, классы, таблицы, секции конфигов) и связи между ними. Правила жёсткие: если функция вызывает другую — связь есть. Если нет — связи нет. Без «может быть» и «скорее всего».
  3. Локальная индексация без векторного хранилища. Всё хранится в обычной базе, обычных таблицах. Запросы идут напрямую, без промежуточных слоёв абстракции.

Вот как это выглядит на практике. Допустим, у вас есть проект на Python:

python
# app/services/user_service.py
def get_user(user_id: int) -> User:
    return db.query(User).filter(User.id == user_id).first()

def create_user(name: str, email: str) -> User:
    user = User(name=name, email=email)
    db.add(user)
    db.commit()
    return user

Graphify построит граф, где get_user и create_user будут узлами типа function, а связь между ними появится только если одна вызывает другую. В данном случае связь будет с db.query и db.add — это методы объекта db, и они тоже станут узлами.

Конфигурация импорта выглядит предсказуемо:

yaml
# graphify.yaml
sources:
  - path: ./app
    type: python
    extensions: [.py]
  - path: ./migrations
    type: sql
    extensions: [.sql]
  - path: ./docs
    type: pdf
    extensions: [.pdf]

graph:
  output: ./graph.db
  format: sqlite

Каждый source обрабатывается независимо. Если вы добавите новый файл — граф обновится только для него. Остальное останется нетронутым. Это важно: вы не перестраиваете всё с нуля, вы инкрементально обновляете знания.

Поддерживаемые форматы: код, SQL, конфиги, PDF

Graphify не требует, чтобы всё было в одном языке. На практике проекты состоят из разного — и это нормально.

Код. Поддерживаются Python, JavaScript/TypeScript, Go, Rust, Java, C#. Каждый язык имеет свой парсер, но логика извлечения сущностей схожа. Для Python — это функции, классы, декораторы, вызовы. Для TypeScript — интерфейсы, типы, импорты, JSX-компоненты.

Пример для TypeScript:

typescript
// src/api/users.ts
import { prisma } from '../db/client';

export interface User {
  id: number;
  name: string;
  email: string;
}

export async function getUsers(): Promise<User[]> {
  return prisma.user.findMany();
}

export async function createUser(data: { name: string; email: string }): Promise<User> {
  return prisma.user.create({ data });
}

Из этого файла Graphify извлечёт:

  • Интерфейс User с полями id, name, email
  • Функции getUsers и createUser с их сигнатурами
  • Импорт prisma и вызовы prisma.user.findMany() и prisma.user.create()

SQL. DDL и DML разбираются отдельно. CREATE TABLE, ALTER TABLE, INSERT, UPDATE, DELETE — всё попадает в граф. Это особенно полезно, когда схема БД разрознена по миграциям.

sql
-- migrations/001_create_users.sql
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    email VARCHAR(255) UNIQUE NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- migrations/002_add_index.sql
CREATE INDEX idx_users_email ON users(email);

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

Конфиги. YAML, JSON, TOML, INI, HCL (Terraform). Graphify разбирает структуру конфигураций и связывает их с соответствующими сущностями в коде.

yaml
# config/database.yaml
database:
  host: localhost
  port: 5432
  name: myapp
  pool_size: 10

Этот конфиг становится узлом config в графе, и если ваш Python-код читает database.host, появится связь между конфигом и функцией, которая его использует.

PDF. Да, PDF. Используется OCR-движок Tesseract под капотом для извлечения текста, а затем применяются те же правила разбора. Это позволяет подключать архитектурные схемы, документацию и даже старые RFC, чтобы связать их с кодом.

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

Связанное чтение: Автоматизация и ИИ-агенты на сервере, Настройка CLAUDE.md для Claude Code по наблюдениям Андрея Карпатского.

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

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

С практической точки зрения, Graphify работает не как отдельное приложение, а как расширение контекста для LLM. Чтобы ИИ-агент (Claude Code, Cursor или Gemini CLI) мог использовать Graphify, ему нужно дать инструмент — так называемый «навык» (skill).

Если вы используете Claude Code, то оптимальный путь — добавить описание навыка в файл CLAUDE.md. Это обеспечит предсказуемость: агент будет знать, что при анализе сложной архитектуры ему стоит обратиться к графу знаний, не пытаясь угадать структуру проекта через ls -R.

Отредактируйте файл CLAUDE.md в корне проекта, добавив в него следующую секцию:

markdown
# Knowledge Graph Tooling
- Tool: `/graphify`
- Purpose: Query the local AST-based knowledge graph of the code.
## Практическое применение: от рефакторинга до аудита безопасности

### Трассировка зависимостей и поиск уязвимостей

Граф знаний раскрывает то, что обычные сканеры пропускают: не просто «библиотека X уязвима», а «вот как X попадает в ваш бинарь, вот где её код реально вызывается».

Возьмём типичный случай. В проекте на Python есть зависимость `requests==2.19.0` — сканер выдаёт красный флажок. Но на деле эта версия используется только в тестовом скрипте `tests/legacy/test_old_api.py`, который никогда не запускается в продакшене. Graphify строит путь: `requirements.txt → requests → test_old_api.py → function old_auth() → call requests.get()` и понимает, что уязвимость не критична.

```python
# Пример: Graphify выявляет, что уязвимая функция вызывается только в тестах
# Анализ пути зависимости:
# main.py → utils/auth.py → requests.get()  ← продакшен
# tests/legacy/test_old_api.py → requests.get()  ← тесты, не в проде

Для поиска уязвимостей по типу «использование eval() с пользовательским вводом» удобно задать запрос в GraphQL:

graphql
{
  vulnerabilities(pattern: "eval\\(|exec\\(|subprocess\\.call\\(") {
    file
    line
    function
    callers(depth: 3) {
      file
      function
      sourceCode
    }
  }
}

Команда для локального запуска анализа:

bash
graphify scan --lang python --vuln-patterns "eval,exec,subprocess.call" --output report.json .

Алексей: «Три года назад аудиторы провели pentest нашего сервиса и нашли, что pickle.loads() вызывается на входе из API. Мы бы проверили это за пять минут, если бы тогда был Graphify. Теперь каждый PR проходит через graphify audit --fail-on-critical в CI.»

Интеграция с существующими CI-CD и локальными серверами

Graphify не требует облачных сервисов — парсинг происходит локально, а результаты кладутся в SQLite или JSON. Это значит, что его можно встроить в любой pipeline, даже если ваш CI работает в изолированной сети.

Пример конфигурации для GitHub Actions:

yaml
# .github/workflows/security-audit.yml
name: Security Audit
on: [push, pull_request]

jobs:
  graphify-audit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Install Graphify
        run: |
          curl -sSL https://graphify.ai/install.sh | sh
          graphify version
      - name: Build knowledge graph
        run: |
          graphify build --lang python --output .graphify/graph.db .
      - name: Run vulnerability scan
        run: |
          graphify audit --graph .graphify/graph.db --rules owasp-top-10 --format sarif --output results.sarif
      - name: Upload SARIF results
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif

Для локального сервера на базе Proxmox или домашней ноды с Kubernetes удобно запускать Graphify как sidecar-контейнер или systemd-сервис. Алексей запускает его на ноде Винни-Пух (его первой домашней машине) через systemd:

ini
# /etc/systemd/system/graphify.service
[Unit]
Description=Graphify Knowledge Graph Builder
After=network.target

[Service]
Type=simple
User=graphify
WorkingDirectory=/opt/projects
ExecStart=/usr/local/bin/graphify watch --lang python --graph /var/lib/graphify/graph.db --interval 300 .
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target

Активируем и проверяем статус:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now graphify
sudo systemctl status graphify

С практической точки зрения, если вы уже используете CLAUDE.md для Claude Code, Graphify можно подключить как внешнее хранилище знаний. Claude Code будет запрашивать граф при анализе кода, а не полагаться только на встроенный контекст.

Алексей: «Мы пробовали несколько подходов: сначала просто запускали Graphify вручную перед ревью, потом вынесли в CI, а сейчас он работает постоянно на одной из нод кластера. Каждые пять минут граф обновляется, и любой ИИ-агент в локальной сети может к нему подключиться по HTTP API. Это как Ponytail: ленивый подход к AI-агентам — только вместо лени мы получаем предсказуемость.»

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

Как Graphify отличается от обычного grep по коду

Grep ищет подстроки, а Graphify понимает структуру. Если вы ищете user_id в grep, получите 47 совпадений без контекста — где это определение, где вызов, где поле структуры. Graphify строит knowledge graph: каждая функция, класс и переменная становятся узлами, а связи между ними — ребрами. Это значит, что вы можете спросить не «где встречается user_id», а «какие функции используют UserService из auth/», и получите точный ответ с контекстом зависимостей.

Нужен ли мне векторный поиск для работы с Graphify

Нет, векторный поиск не используется. Graphify работает исключительно на основе AST-парсинга и построения знаний через графовые структуры. Это особенность локальной реализации: вы извлекаете сущности и их отношения прямо из синтаксического дерева кода, сохраняете в SQLite или RocksDB, а затем запрашиваете через Cypher-подобные запросы. Векторные модели здесь избыточны — вы не ищете близкие по смыслу фрагменты, вы запрашиваете конкретные связи между конкретными сущностями.

Можно ли использовать Graphify с существующими ИИ-агентами

Да, Graphify специально разработан для интеграции с Claude Code, Cursor и другими ИИ-агентами. Поскольку graph хранится в локальной базе данных и доступен через стандартные запросы, ИИ-агент может получать контекст не через загрузку файлов в контекст, а через целенаправленные запросы к графу знаний. Это решает основную проблему LLM-чатов: вместо 5% кода в контексте вы получаете доступ к 100% структуре проекта по запросу.

Что делать, если мой язык программирования не поддерживается

Graphify использует языковые парсеры для извлечения AST, и поддержка зависит от доступности парсера для вашего языка. Для Python используется стандартный ast, для JavaScript — @swc/core, для Go — go/ast. Если ваш язык не поддерживается из коробки, вы можете добавить парсер, реализовав интерфейс извлечения сущностей и связей. Архитектура построена так, что добавление нового языка не требует переработки всей системы.

Как Graphify масштабируется на большие проекты

Графовая база данных масштабируется линейно с ростом кодовой базы, но ключевое преимущество — вы не загружаете весь код в контекст. Для проекта в 100 тысяч строк вы строите graph один раз, а затем запрашиваете только нужные связи. Это как хранить карту дома, а не сам дом целиком — вы всегда знаете, где находится нужная комната, не обходя все остальные.

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

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