Перейти к основному содержимому

ADR 0099 — Neo4j как граф знаний и шаблоны prompt, драйвер в api

  • Статус: Принят
  • Дата: 2026-08-19
  • Контекст: фундамент графа знаний (RN-413/414/415), решение владельца продукта привнести Neo4j в продукт

Контекст​

Два реальных пробела продукта мотивировали эту поставку. Первый: ни один агент не потребляет RAG, который уже существует (pgvector, гибридный поиск вектор+лексика, HybridSearchUseCase), — его использует лишь web-вкладка "Chat RAG". Второй: каждый prompt агента — inline heredoc Elixir — идентичности (Engine.Harness.Agents), kickoffs PO/Arquiteto/Dev Lead/UX/Infra, prompt суммаризации ContextManager, — ничто из этого не живёт вне кода, версионированно или переиспользуемо.

Владелец продукта попросил Neo4j по мотивам репозитория ErickWendel/neo4j-ai-experiments, который использует граф как память ИИ-агентов и хранит prompts в файлах отдельно от логики приложения (каталог prompts/, с именованными шаблонами для преобразования NL→Cypher, контекста и форматирования ответа). Этот проект был конкретным вдохновением для принятого здесь шаблона — стоит выразить явную благодарность: без него форма "prompt как версионируемый файл, а не строка, запертая в коде" не имела бы столь прямого прецедента для следования.

Решение​

Neo4j входит как граф знаний — ПРОИЗВОДНАЯ память event log, никогда не источник истины — с двумя обязанностями:

  1. Версионируемые шаблоны prompt (PromptTemplate/PromptVersion), постепенно заменяющие inline heredocs (сама миграция GenServers — будущая волна; эта поставка лишь строит фундамент).
  2. Реляционная память: взаимодействия пользователя, гипотезы Psicólogo со свидетельствами (EVIDENCIA → Evento{sessionId,seq}), профили компетентности Anamnese, handoffs между агентами.

pgvector ПО-ПРЕЖНЕМУ является векторным индексом чанков — граф не хранит никаких embedding. Хранение одного вектора в двух базах разошлось бы при первой переиндексации, затронувшей лишь одну из них; решением было сохранить ОДИН векторный источник, а граф — как слой ОТНОШЕНИЙ поверх.

Драйвер: neo4j-driver (официальный пакет) в apps/api, а не в engine. api уже владеет ВСЕМ хранением продукта (Postgres, pgvector, permissions.json) и существующим RAG; engine уже потребляет всё через внутренний HTTP с service token, никогда не открывал прямого подключения к базе домена. Повторение этого шаблона — engine вызывает внутренний маршрут, api говорит с базой — сохраняет ОДНУ границу учётных данных/пула вместо двух.

Минимальная схема с ограничениями уникальности (в Neo4j Community нет составного NODE KEY, лишь IS UNIQUE по одиночному свойству): PromptTemplate.name, Usuario.id, Projeto.id, Agente.slug, Interacao.sessionId. Hipotese/Handoff используют MERGE по собственному естественному ключу (id гипотезы; (sessionId, seq) handoff) без формального ограничения — идемпотентность обеспечивает MERGE, а не дополнительное структурное ограничение.

Деградация, а не crash​

GraphStore.onModuleInit НИКОГДА не бросает исключение. Три переменные окружения (NEO4J_URI/NEO4J_USER/NEO4J_PASSWORD) обязательны в продакшене (ранний сбой при загрузке, в том же духе, что GIT_OAUTH_STATE_SECRET из ADR 0059) и необязательны вне его — никому не нужно поднимать локальный Neo4j только чтобы прогнать suite api. При отсутствии или недоступности драйвер остаётся null, и каждая операция бросает GraphUnavailableError, преобразуемую в 503 (никогда не сырой 500) глобальным фильтром GraphErrorFilter, или в деградированный ответ в use cases, у которых есть fallback (поиск RAG без обогащения графом, например).

Последствия​

  • Граф восстанавливается replay event log/outbox (см. Волну 2, RN-416), — никогда не второй источник истины, который нужно синхронизировать ручной дисциплиной.
  • Миграция inline kickoffs GenServers на потребление шаблонов графа — ПОСЛЕДУЮЩАЯ волна, объявленная вне этой поставки, — этот ADR лишь устанавливает, что механизм существует и его безопасно вводить постепенно.
  • Принятое и новое для продукта последствие: это первая инфраструктурная зависимость, не являющаяся ни Postgres, ни Ollama. docker-compose.yml получает ещё один сервис для тех, кто запускает pnpm dev локально, с ограниченной кучей (эталонная машина разработчика имеет 15 GB, уже разделённых между Postgres, Ollama, api, engine и web).