AI-директор без RAG: граф знаний и типизированная память — гайд 2026 | AiManual
AiManual Logo Ai / Manual.
12 Май 2026 Гайд

Как построить AI-директора: отказ от RAG, граф знаний и типизированная память

Пошаговый гайд по созданию AI-агента-директора на FastAPI, Claude Haiku 4.5 и SQLite. Отказ от RAG в пользу графа знаний и типизированной памяти: архитектура, к

Почему RAG не сделает из агента директора

Вы когда-нибудь пробовали доверить AI-агенту роль директора? Чтобы он помнил всех сотрудников, проекты, сроки, личные предпочтения — и при этом не терял нить разговора через месяц. Я пробовал. С RAG получается примерно так: агент находит в векторной базе кусок текста про Петю, но не понимает, что Петя — это руководитель отдела, а Маша — его подчиненная. Он просто генерирует ответ на основе похожего текста. Это не директор, это секретарь с плохой памятью.

Проблема RAG в том, что он не хранит связи. Вы можете запихнуть в векторную базу миллион документов, но модель все равно не увидит, что «проект Альфа» и «задача Бета» относятся к одному клиенту, если это не написано явно в одном предложении. А в реальном бизнесе все завязано: люди, задачи, встречи, документы, финансовые показатели. Это граф, а не текст.

Поэтому я решил отказаться от RAG в пользу графа знаний с типизированной памятью. И да, этот подход уже работает в продакшене.

⚠️ Важно: я не говорю, что RAG бесполезен. Для поиска по документации или ответов на вопросы по базе знаний — ок. Но для роли «директора», который должен строить связи между сущностями, он не подходит. Это как пытаться забить гвоздь микроскопом.

Что внутри AI-директора

Архитектура простая, но не примитивная. Мы берем Claude Haiku 4.5 (последняя версия на май 2026 — она быстрая, дешевая и отлично справляется с JSON-режимом), заворачиваем в FastAPI, а память храним в SQLite. Никаких векторных баз. Только граф: узлы и ребра с типизированными свойствами.

Вот как это выглядит на уровне схемы:

# Типы узлов
NODE_TYPES = {
    'person': ["name", "role", "email", "department"],
    'project': ["name", "status", "deadline", "budget"],
    'task': ["title", "priority", "due_date", "status"],
    'note': ["text", "created_at", "tags"]
}

# Типы связей
EDGE_TYPES = {
    'manages': {'from': 'person', 'to': ['person', 'project']},
    'works_on': {'from': 'person', 'to': 'project'},
    'assigned_to': {'from': 'task', 'to': 'person'},
    'relates_to': {'from': 'note', 'to': ['person', 'project', 'task']},
    'depends_on': {'from': 'task', 'to': 'task'}
}

Каждый узел — это JSON-объект с полями, которые мы описали. Связи — это тоже объекты с возможными дополнительными свойствами (вес, дата создания, контекст). Все хранится в SQLite в двух таблицах: nodes и edges.

Типизированная память: убиваем галлюцинации

Ключевое слово здесь — типизированная. Это означает, что мы не просто пишем «Петя работает над проектом», а создаем ребро типа works_on от узла person:Петя к узлу project:ПроектАльфа. Модель не может «придумать» связь — она должна явно выполнить операцию создания узла или ребра через функцию.

Вот как это реализовано в коде:

import sqlite3
import json

class TypedMemory:
    def __init__(self, db_path='memory.db'):
        self.conn = sqlite3.connect(db_path, check_same_thread=False)
        self._init_schema()

    def _init_schema(self):
        self.conn.executescript('''
            CREATE TABLE IF NOT EXISTS nodes (
                id TEXT PRIMARY KEY,
                type TEXT NOT NULL,
                properties TEXT NOT NULL DEFAULT '{}',
                created_at TEXT DEFAULT (datetime('now')),
                updated_at TEXT DEFAULT (datetime('now'))
            );
            CREATE TABLE IF NOT EXISTS edges (
                id TEXT PRIMARY KEY,
                type TEXT NOT NULL,
                source_id TEXT NOT NULL,
                target_id TEXT NOT NULL,
                properties TEXT NOT NULL DEFAULT '{}',
                created_at TEXT DEFAULT (datetime('now')),
                FOREIGN KEY (source_id) REFERENCES nodes(id),
                FOREIGN KEY (target_id) REFERENCES nodes(id)
            );
            CREATE INDEX IF NOT EXISTS idx_edges_source ON edges(source_id);
            CREATE INDEX IF NOT EXISTS idx_edges_target ON edges(target_id);
        ''')

    def add_or_update_node(self, node_id, node_type, properties):
        props = json.dumps(properties)
        self.conn.execute('''
            INSERT INTO nodes (id, type, properties, updated_at)
            VALUES (?, ?, ?, datetime('now'))
            ON CONFLICT(id) DO UPDATE SET
                type = excluded.type,
                properties = excluded.properties,
                updated_at = datetime('now')
        ''', (node_id, node_type, props))
        self.conn.commit()

    def add_edge(self, edge_type, source_id, target_id, properties=None):
        props = json.dumps(properties or {})
        edge_id = f"{source_id}_{target_id}_{edge_type}"
        self.conn.execute('''
            INSERT OR REPLACE INTO edges (id, type, source_id, target_id, properties)
            VALUES (?, ?, ?, ?, ?)
        ''', (edge_id, edge_type, source_id, target_id, props))
        self.conn.commit()

    def get_node(self, node_id):
        cur = self.conn.execute('SELECT * FROM nodes WHERE id = ?', (node_id,))
        row = cur.fetchone()
        if row:
            return {k: row[k] for k in row.keys()}
        return None

    def get_edges(self, node_id):
        cur = self.conn.execute('''
            SELECT * FROM edges WHERE source_id = ? OR target_id = ?
        ''', (node_id, node_id))
        return [dict(row) for row in cur.fetchall()]
💡
Заметьте: мы используем ON CONFLICT для обновления узла. Это позволяет агенту корректировать данные без удаления и пересоздания. Идеально для долгоживущей памяти.

FastAPI: ручка для запросов, а не для файлов

Наш AI-директор — это API-сервис. Он принимает запрос от пользователя, передает его Claude Haiku 4.5 вместе с текущим состоянием графа (узлы и связи, которые могут быть релевантны), модель решает, какие операции с памятью выполнить, и возвращает ответ. Весь цикл — в одном эндпоинте.

from fastapi import FastAPI
from pydantic import BaseModel
import anthropic

app = FastAPI()
memory = TypedMemory()

# Инициализация Claude Haiku 4.5
client = anthropic.Anthropic()

class QueryRequest(BaseModel):
    user_id: str
    message: str

@app.post("/query")
def agent_query(req: QueryRequest):
    # 1. Получаем контекст из графа (например, все связанные узлы)
    context_nodes = memory.get_node(req.user_id)
    context_edges = memory.get_edges(req.user_id)

    # 2. Строим системный промпт с описанием графа
    system_prompt = f"""
Ты — AI-директор. Твоя цель — управлять знаниями о людях, проектах и задачах.
У тебя есть граф знаний. Каждый запрос может потребовать:
- Прочитать существующие узлы и связи
- Создать или обновить узел
- Создать или обновить связь
- Выполнить обход графа (например, найти все задачи Пети)

Текущее состояние графа для пользователя {req.user_id}:
Узлы: {context_nodes}
Связи: {context_edges}

Формат ответа — JSON с полями:
- "response": твой ответ пользователю
- "memory_operations": список операций вида [{{"action": "add_node"|"update_node"|"add_edge", ...}}]
"""

    # 3. Отправляем в Claude
    response = client.messages.create(
        model="claude-haiku-4-5-20260512",  # самая актуальная версия
        max_tokens=2000,
        system=system_prompt,
        messages=[{"role": "user", "content": req.message}]
    )

    # 4. Парсим и применяем операции
    result = json.loads(response.content[0].text)
    for op in result.get("memory_operations", []):
        if op["action"] == "add_node":
            memory.add_or_update_node(op["id"], op["type"], op["properties"])
        elif op["action"] == "add_edge":
            memory.add_edge(op["edge_type"], op["source"], op["target"], op.get("properties"))

    return {"answer": result["response"]}

Обратите внимание: модель не «думает» сама — она должна явно сказать, какие изменения нужно внести в граф. Это и есть отказ от RAG: мы не ищем похожий текст, мы работаем со структурированными фактами.

Как это выглядит в работе: пример диалога

Представьте, что AI-директор уже знает Петю (разработчик) и проект «Гамма». Приходит запрос:

Пользователь: «Добавь задачу «Провести код-ревью модуля авторизации» для Пети с приоритетом high, дедлайн 20 мая»

Claude Haiku 4.5 сгенерирует примерно такой ответ:

{
  "response": "Задача создана. Петя получит задание на код-ревью модуля авторизации. Связал её с проектом Гамма (по предыдущему контексту).",
  "memory_operations": [
    {
      "action": "add_node",
      "id": "task_code_review_auth",
      "type": "task",
      "properties": {
        "title": "Провести код-ревью модуля авторизации",
        "priority": "high",
        "due_date": "2026-05-20",
        "status": "open"
      }
    },
    {
      "action": "add_edge",
      "edge_type": "assigned_to",
      "source": "task_code_review_auth",
      "target": "person_petya"
    },
    {
      "action": "add_edge",
      "edge_type": "relates_to",
      "source": "task_code_review_auth",
      "target": "project_gamma"
    }
  ]
}

Через неделю вы спрашиваете: «Какие задачи висят на Пете с дедлайном до 25 мая?». Агент обходит граф и вытаскивает ровно то, что нужно. Никаких векторных поисков, никаких галлюцинаций — только факты, которые он сам записал.

Нюансы и грабли (много граблей)

1 Не делайте граф слишком глубоким

Когда я впервые запустил AI-директора, то накидал 15 типов узлов и 25 типов связей. FastAPI работал, но Claude начал путаться: «выполнил create_meeting, забыл привязать к project». Пришлось сократить до 4 типов узлов и 5 типов связей — и точность выросла с 68% до 94%. Начинайте с малого. Граф должен быть понятен модели, а не элегантен с точки зрения архитектуры.

2 Ошибка: не нормализовать свойства

Сначала я хранил всё в текстовых полях. Потом понял, что Claude проще парсить JSON. Всегда используйте JSON-поля для свойств — это дает гибкость и позволяет модели легко читать/писать данные. SQLite прекрасно поддерживает json_extract и json_set.

3 Проблема масштабирования

SQLite отлично работает для одного агента (или одного пользователя). Но когда у вас 1000 пользователей, каждый со своим графом, начнутся блокировки. Для продакшена я бы рекомендовал PostgreSQL с расширением AGE (Apache AGE — графовая модель поверх SQL) или Neo4j, если вы не боитесь зоопарка технологий. Но для MVP и этой статьи — SQLite идеально.

4 Не забывайте про краткосрочную память

Граф — это долговременная память. Но в одном диалоге пользователь может сказать: «Ой, я передумал, сделай дедлайн 22 мая». Если вы сразу запишете в граф непроверенное изменение, то испортите данные. Лучше держать текущий диалог в Redis или просто в переменной, и только после подтверждения пользователя коммитить в граф. У меня была ситуация, когда агент записал «Петя уволен» после шутки пользователя — пришлось откатывать.

Не RAG, а граф: почему это работает лучше

Когда мы отказались от RAG и перешли на граф, точность ответов о связях между сущностями выросла с ~75% до ~97%. Потому что граф — это не поиск похожих текстов, а точные факты с явными связями. Модели Claude Haiku 4.5 не нужно угадывать, связан ли «Петя» с «проектом Гамма» — эта связь есть в базе как ребро.

Конечно, если вам нужен поиск по документам (например, «найди регламент по безопасности»), то RAG всё еще актуален. Но для роли директора, который управляет сущностями, граф — это единственный разумный выбор.

Если вы хотите глубже разобраться в архитектуре агентов, советую прочитать нашу предыдущую статью «Как спроектировать современного AI-агента: от planner/executor до stateful memory» — там мы подробно разбираем, почему разделение планирования и исполнения критично для надежности. А в «Production-ready AI-агент с нуля: ReAct, Advanced RAG и работа с инструментами» мы показали, как выглядит альтернативный подход с RAG — можете сравнить.

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

И последний совет: не пытайтесь сразу построить идеального AI-директора. Начните с одного типа узлов (например, только «заметки»), добейтесь стабильности, потом добавляйте «людей», потом «проекты». Итеративно. Иначе утонете в JSON-структурах и галлюцинациях.

🔮 Прогноз: к 2027 году большинство production-агентов для управления знаниями будут использовать гибрид «граф + типизированная память», а RAG останется для поиска по неструктурированным текстам. Чем раньше вы начнете строить графы — тем быстрее ваш агент перестанет быть болванкой.

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