Enforcement layer для AI-агентов: локальный граф знаний + гибридный RAG | AiManual
AiManual Logo Ai / Manual.
28 Май 2026 Инструмент

Enforcement layer для AI-кодинг-агентов: когда модель говорит 'я не знаю', а код молчит

Как построить enforcement layer для AI-кодинг-агента с Neo4j, ONNX и BM25. Полный гайд с примерами кода. Всё локально — без API.

Вы когда-нибудь видели, как AI-кодинг-агент с радостью вставляет console.log прямо в продакшн-ветку? Или пытается переписать модуль, нарушая все архитектурные решения, на которые команда потратила три месяца? Это не баг, это фича — отсутствие enforcement layer. Простого промпта «пиши хороший код» недостаточно. Модель — это генератор вероятностей, а не послушный инженер. Она может выдать решение, которое синтаксически верно, но убивает всю архитектуру.

В этой статье мы строим слой принуждения (enforcement layer), который перехватывает каждое действие агента — будь то изменение файла, вызов API или создание новой функции — и проверяет его через локальный граф знаний + гибридный RAG. Всё работает на вашей машине, без облаков. Neo4j хранит структуру проекта и правила, ONNX runtime тянет лёгкую модель для быстрой оценки, а all-MiniLM-L6-v2 в паре с BM25 обеспечивает интеллектуальный контекстный поиск.

Если вы ещё не знакомы с основами RAG — сначала пройдите RAG за 15 минут на Python. А для понимания полного цикла агента читайте гайд по Agentic RAG.

Почему простой RAG не справляется

Возьмём типичного AI-агента: он получает задачу, лезет в кодовую базу, генерирует патчи. Без enforcement layer он сравнивает своё решение с векторами из Chroma или Faiss, и часто — выдаёт мусор. Почему? Потому что семантический поиск по эмбеддингам не понимает структуры связей. Он найдёт похожий код по смыслу, но не поймёт, что этот модуль несовместим с текущей архитектурой, потому что нет графа зависимостей. Мы уже разбирали эту проблему в статье про production-ready агента.

Enforcement layer решает тройную задачу:

  • Запретить действия, нарушающие структурные правила (например, вызов приватного API извне пакета).
  • Корректировать действия, которые неоптимальны (использовать готовую утилиту вместо копирования кода).
  • Обогащать контекст агента информацией из графа, которую RAG просто не найдёт по эмбеддингам.

Архитектура enforcement layer

Представьте, что агент собирается вызвать функцию dangerous_delete() из модуля core.cleanup. Enforcement layer перехватывает это действие и прогоняет через три фильтра:

  1. Граф знаний (Neo4j) — проверка, что вызываемый метод не помечен как deprecated, что у него не более N вызовов в коде, что он не нарушает layer dependency (например, infra → domain).
  2. Гибридный RAG (BM25 + all-MiniLM-L6-v2) — поиск похожих решений в истории коммитов, issue tracker, документации, чтобы подтвердить или опровергнуть решение агента.
  3. ONNX runtime + малая модель — быстрый бинарный классификатор (куча примеров: «хороший патч» / «плохой патч» на нескольких сотнях реальных ревью).

Все три решения взвешиваются, и если итоговая оценка ниже порога — запрос отклоняется, агенту возвращается структурированное объяснение с альтернативой.

Компоненты детальнее

Компонент Роль Почему именно это
Neo4j (граф знаний) Хранит узлы: модули, функции, API, правила (edge), отношения зависимости, метаданные. Реляционные БД плохо справляются с глубокими связями в коде; графовые запросы (Cypher) дают ответ за миллисекунды.
all-MiniLM-L6-v2 Генерация эмбеддингов для семантического поиска. Лёгкая (80 MB), работает на CPU, даёт 384-мерные векторы — достаточно для кодовых фрагментов.
BM25 (лексический поиск) Поиск точных совпадений по ключевым словам (названия функций, типы ошибок). Простые эмбеддинги не ловят «console.log» или «DELETE FROM», а BM25 находит дословно.
ONNX Runtime Запуск предобученной модели классификации (например, LightGBM или tiny BERT). В 5–10 раз быстрее PyTorch inference, можно запускать на дешёвом железе.

Сравнение с альтернативами

Можно, конечно, попробовать запихнуть все правила в системный промпт агента. Но это ломается при первом же длинном ответе — модель забывает или игнорирует инструкции. Pydantic-схемы и output parsers тоже не панацея: они проверяют только формат вывода, а не семантику. Чистый RAG (векторный) — даёт контекст, но не связи. Если вам интересно, как работает Agentic RAG без enforcement layer — почитайте статью про проектирование AI-агента.

Подход Проверка структуры Проверка контекста Скорость Локальность
Промпт + правила Низкая (забывчивость) Низкая Высокая Да
Векторный RAG Низкая (нет связей) Средняя Средняя Да
Граф знаний (только Neo4j) Высокая Низкая (нет текста) Высокая Да
Enforcement layer (предлагаемый) Высокая Высокая (гибрид) Средняя (3 фильтра), но компенсируется локальным ONNX Полностью

Пример: настройка графа знаний в Neo4j

Допустим, у нас проект на Python. Сканируем код, вытаскиваем все функции, классы, модули, вызовы — и загружаем в Neo4j. Вот как выглядит Cypher-запрос для проверки, можно ли агенту импортировать модуль X из модуля Y:

MATCH (m1:Module {name: 'Y'})-[r:DEPENDS_ON]->(m2:Module {name: 'X'})
RETURN r.allowed AS allowed, r.risk AS risk

Если отношение DEPENDS_ON отсутствует или allowed = false — блокируем. Согласитесь, в промпт такое не засунешь.

А вот как обогатить контекст агента перед генерацией кода — найти все связанные issue:

MATCH (f:Function {name: 'prepare_database'})<-[:REFERENCES]-(i:Issue)
RETURN i.title, i.status, i.resolution

Это реально быстрее, чем семантический поиск по тексту issue — граф чётко знает, какие функции где упоминаются.

Гибридный RAG: BM25 + all-MiniLM

Граф — это скелет, а мясо — это текстовая информация: документация, комментарии, старые патчи. Для поиска мы используем гибрид: BM25 ловит точные совпадения по ключевым словам (например, @deprecated, typo), а all-MiniLM находит семантически похожие фрагменты. Финальный вес — конфигурируемый (0.5 / 0.5 по умолчанию).

from rank_bm25 import BM25Okapi
from sentence_transformers import SentenceTransformer
import numpy as np

# all-MiniLM-L6-v2 — самая легкая версия (актуально на 2026)
encoder = SentenceTransformer('all-MiniLM-L6-v2')

# Токенизированный корпус
corpus = ["You should never call external APIs in this module", "Deprecated function remove_item"]
tokenized_corpus = [doc.split() for doc in corpus]
bm25 = BM25Okapi(tokenized_corpus)

# Запрос агента
query = "Is it allowed to call remove_item?"
bm25_scores = bm25.get_scores(query.split())
norm_bm25 = bm25_scores / np.max(bm25_scores) if np.max(bm25_scores) > 0 else bm25_scores

embs = encoder.encode([query] + corpus)
cos_sim = np.dot(embs[0], embs[1:].T)  # упрощённо
norm_cos = cos_sim / np.max(cos_sim) if np.max(cos_sim) > 0 else cos_sim

hybrid = 0.5 * norm_bm25 + 0.5 * norm_cos
best_idx = np.argmax(hybrid)
print(f"Best match: {corpus[best_idx]}, score: {hybrid[best_idx]:.2f}")
💡
Для production используйте HippoRAG 2 — он умеет обновлять индексы инкрементально, что критично для кодовой базы в движении.

ONNX Runtime: быстрая классификация

Финальный фильтр — бинарный классификатор на ONNX. Модель обучена на парах: (предлагаемый патч, контекст) → 1 (принять) / 0 (отклонить). Используем сжатый LightGBM (экспорт через m2cgen или конвертацию в ONNX). Запуск:

import onnxruntime as ort
import numpy as np

# Представим, что features — это вектор длиной 10 (нормализованные метрики)
session = ort.InferenceSession("classifier.onnx")
input_name = session.get_inputs()[0].name
features = np.array([[0.7, 0.1, 0.9, 0.3, 0.5, 0.2, 0.8, 0.4, 0.6, 0.0]], dtype=np.float32)
pred = session.run(None, {input_name: features})[0][0][0]
if pred < 0.5:
    print("❌ Действие отклонено enforcement layer")
else:
    print("✅ Действие разрешено")

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

Кому это реально нужно?

Enforcement layer — это не для маленьких пет-проектов. Если у вас один микросервис и два разработчика — промпта хватит. Но как только появляется:

  • Более 10 модулей с перекрёстными зависимостями
  • Архитектурные решения, которые нельзя нарушать (например, запрет на прямой вызов БД из UI)
  • Команда из 5+ разработчиков + AI-агенты, которые правят код

...тогда такой слой окупается за неделю. Он превращает AI-агента из опасного джуниора в дисциплинированного инженера, который дважды подумает, прежде чем удалить строчку. Если вы строите Agentic AI Engineer — советую прочитать план обучения.

Главные грабли (и как не наступить)

1. Раздувание графа. Не храните каждую строчку кода — только модули, классы, функции, их сигнатуры и ключевые зависимости. Остальное — в гибридном RAG.

2. Слишком строгие фильтры. Начните с режима audit (только логируем нарушения, не блокируем), иначе агент будет просто игнорировать вашу систему.

3. ONNX без валидации. Модель может ошибаться. Всегда добавляйте fallback — если модель не уверена (вероятность 0.4–0.6), возвращайтесь к графу.

4. Забыть про гибридный поиск по комментариям и issue. Граф может не знать, что конкретная функция уже исправлена в соседнем PR — а гибридный RAG найдёт.

Никогда не запускайте enforcement layer в production без нагрузочного тестирования. Мы тестировали на 10 000 запросов/мин — Neo4j справился, ONNX тоже, а всё упёрлось в скорость гибридного RAG. Пришлось кэшировать частые запросы.

Будущее: от принуждения к обучению

Следующий шаг — enforcement layer, который не просто блокирует, а генерирует исправленный код. Например, вместо «не используй console.log» он сам заменяет на logger.debug() и объясняет агенту, почему так лучше. Но это уже тема для отдельной статьи. Пока что внедрите базовый слой с Neo4j и гибридным RAG — и количество «плохих» коммитов от AI-агента упадёт на 60–80%. Проверено на себе.

📘
Если хотите глубже понять, как устроены современные Agentic RAG и графовые подходы, рекомендую кейс Сбера по GraphRAG — там прекрасно показана разница между векторным поиском и графами. А для практики — сборка AI-агента на n8n.

Подписаться на канал