Представьте, что вы рассказываете новому знакомому о своей жизни. — "Я изучаю Rust", — говорите вы. Обычный собеседник просто кивнет и запомнит факт. Но хороший друг спросит: "Почему? У тебя же отличный опыт в Python". А лучший друг добавит: "Подожди, это как-то связано с твоим желанием переехать и ипотекой, о которой ты говорил?"
Вот здесь и проходит граница между "базой данных" и настоящей памятью. База данных просто складывает факты в ячейки: Язык: Rust, Цель: Ипотека, Кот: Пушок. А настоящая память видит связи: Алексей учит Rust, потому что устал от проблем Python, чтобы получить высокооплачиваемую работу, чтобы выплачивать ипотеку, чтобы его сын ходил в хорошую школу.
К сожалению, большинство современных ИИ ведут себя как та самая "база данных". Они помнят отдельные слова, но теряют суть. Для них «кот Пушок» и «ипотека» — это два никчем не связанных слова.
В этой статье мы поставим решительный эксперимент. Мы возьмем сложнейший жизненный кейс нашего героя, Алексея. Это не просто список фактов, это запутанная карта мотиваций, где профессиональное выгорание толкает к смене языка программирования, а кот на клавиатуре становится причиной покупки игровой консоли.
Мы загрузим этот сложный контекст в нашего ИИ-агента за один раз. Сможет ли он разглядеть, что волейбол для интроверта — это не просто хобби, а способ социализации? Поймет ли он, что Дюна на гитаре связана с любовью к фантастике, а не просто случайным выбором мелодии?
Посмотрите на скриншот выше. Это визуализация работы нашего "Цифрового Гиппокампа". Сегодня мы разберем, как из хаоса разрозненных сообщений машина строит упорядоченную Вселенную личности, где у каждого факта есть свое место, своя история и своя связь с другими.
Добро пожаловать в лабораторию смыслов.
Вот тестовые знания, на которых мы проведем эксперимент:
"dialog_messages": [ # === БЛОК 1: Личность и Карьера (Глубокая связка: Python -> Rust) === {"role": "user", "content": "Привет, меня зовут Алексей, мне 28 лет."}, {"role": "user", "content": "Я живу в Москве, работаю senior backend-разработчиком, основной стек — Python."}, # 2 уровень: Связь "Почему Rust?" {"role": "user", "content": "Сейчас изучаю язык Rust."}, # 3 уровень: Причина (Проблема Python) -> Следствие (Цель Rust) {"role": "user", "content": "Решаю перейти на Rust, потому что устал бороться с GIL и низкой производительностью в высоконагруженных микросервисах на Python."}, {"role": "user", "content": "Планирую через полгода вести на Rust новый проект в компании."}, # === БЛОК 2: Семья и Недвижимость (Глубокая связка: Квартира -> Ипотека -> Работа) === {"role": "user", "content": "Родом я из Санкт-Петербурга, но живу в Москве с женой Анной."}, {"role": "user", "content": "У нас есть сын, ему 2 года, и персидский кот Пушок."}, # 2 уровень: Связь "Жилищные условия" {"role": "user", "content": "Мы снимаем квартиру, но планируем купить свою в ближайший год."}, # 3 уровень: Финансовая причина и личная мотивация {"role": "user", "content": "Хочу взять ипотеку, чтобы сын ходил в хорошую школу в нашем районе, а не в том, где мы сейчас снимаем."}, {"role": "user", "content": "Именно желание выплачивать ипотеку без стресса мотивирует меня изучать Rust для перехода на более высокооплачиваемую позицию."}, # === БЛОК 3: Хобби и Характер (Глубокая связка: Интроверт -> Гитара -> Дюна) === {"role": "user", "content": "Я интроверт по натуре, люблю проводить вечера дома."}, # 2 уровень: Связь хобби с характером {"role": "user", "content": "В свободное время играю на гитаре."}, # 3 уровень: Эмоциональная привязка {"role": "user", "content": "Играю для себя, чтобы переключиться после тяжелого кодинга; недавно выучил тему из 'Дюны'."}, {"role": "user", "content": "Обожаю научную фантастику, перечитал Дюну перед выходом нового фильма, чтобы сравнить экранизацию с оригиналом."}, # === БЛОК 4: Быт и Привычки (Глубокая связка: Утро -> Кот -> Работа) === {"role": "user", "content": "По утрам пью кофе с молоком, без сахара — это мой ритуал пробуждения."}, # 2 уровень: Взаимодействие с питомцем {"role": "user", "content": "Кот Пушок любит спать на клавиатуре."}, # 3 уровень: Влияние на работу {"role": "user", "content": "Это сильно мешает работать удаленно, поэтому я планирую купить себе PlayStation 5 на праздники."}, {"role": "user", "content": "Надеюсь, Пушок переместится спать на консоль и освободит мою клавиатуру для кодинга."}, # === БЛОК 5: Спорт и Социализация (Глубокая связка: Интроверт -> Волейбол) === {"role": "user", "content": "Люблю играть в волейбол на выходных."}, # 3 уровень: Противоречие характеру (компенсация) {"role": "user", "content": "Хотя я интроверт, волейбол — единственное место, где я активно социализируюсь и сбрасываю накопившееся напряжение от сидячей работы."} ]
И вот что мы получили на выходе:
Взгляните еще раз на скриншот выше. Для постороннего взгляда это может напоминать карту звездного неба или схему метрополитена будущего. Но для ИИ это не просто точки и линии. Это — семантический граф, или, проще говоря, карта смыслов.
Давайте разберем, как наш агент "переварил" историю Алексея и превратил текст в живую структуру.
Каждый кружок на этой карте — это Узел (Node). Это отдельное воспоминание или факт, который ИИ счел достаточно важным, чтобы сохранить навсегда. В случае Алексея агент не стал записывать всё подряд в одну кучу. Он выделил ключевые сущности и разложил их по полочкам:
Синие сферы — это факты о профессии: Python, Rust, Senior Developer.
Зеленые узлы — это семья и дом: Жена Анна, Сын, Кот Пушок.
Яркие "звезды" — это цели и желания: Ипотека, PlayStation 5.
Заметьте: агент не просто запомнил слова. Он понял категорию. Он "догадался", что PlayStation — это не просто "игрушка", а часть истории про кота и удаленную работу, поэтому он оказался в соответствующем кластере.
Но самое интересное — это линии. Если узлы — это слова, то связи — это мысли. В обычной базе данных факт "Алексей учит Rust" и факт "Алексей хочет взять ипотеку" лежали бы в двух разных таблицах, ничем не связанные. Наш агент увидел причинно-следственную связь.
Следите за логикой агента:
Алексей устал от ограничений Python (проблема).
Поэтому он решил учить Rust (решение).
Чтобы получить высокооплачиваемую работу (цель).
Чтобы без стресса платить за ипотеку (сверхцель).
На графе вы увидите эти скрытые нити, соединяющие технологии с недвижимостью. ИИ построил цепочку мотивации. Теперь, если вы спросите его: "Почему Алексей хочет ипотеку?", он не просто ответит "Потому что хочет квартиру". Он сможет достроить цепочку вплоть до проблем с многопоточностью в Python (GIL), если это будет нужно для контекста разговора.
Вспомните, как выглядит папка "Загрузки" на вашем компьютере, если туда давно не заглядывать. Куча файлов вперемешку. Наш агент работает иначе: он сразу раскладывает всё по папкам, которые называются Кластеры.
В истории Алексея агент выделил четкие кластеры:
Кластер "Карьера": Здесь соседствуют Python, Rust и микросервисы.
Кластер "Семья": Здесь Анна, сын и кот Пушок.
Кластер "Хобби": Здесь гитара, Дюна и... волейбол.
И здесь произошла настоящая магия. Алексей сказал, что он интроверт, но играет в волейбол. Глупый бот записал бы: "Любит волейбол". Умный агент создал связь: "Волейбол — это способ социализации для интроверта". На графе это, возможно, выглядит как линия, соединяющая "Интровертность" с "Волейболом", промаркированная как "компенсация". Это уровень понимания психологии, а не просто текста.
Взгляните на размер узлов и их яркость. В нашей системе памяти факты не равны.
Вес (Val): То, что составляет "ядро" личности (имя, семья, работа), изображено крупными, массивными узлами. А вот "любимый цвет — синий" может остаться маленькой точкой на периферии.
Тепло (Heat): Это индикатор свежести. Факт про PlayStation, который Алексей только что упомянул, может "гореться" (быть ярким), потому что это актуальная, "горячая" мысль. А факт про то, что он родом из Петербурга, может быть "остывшим" — спокойным фоновым знанием.
Итог: Вместо сухого списка из 20 пунктов мы получили сложную, красивую архитектуру личности. ИИ не просто "запомнил" Алексея. Он его понял. Но как технически удалось этого добиться? Давайте заглянем "под капот" алгоритма.
def memory_space(arguments): """ Агент долговременной памяти — не просто пишет всё подряд, а работает как библиотекарь + детектив + редактор одновременно. """ chat_id = arguments["chat_id"] dialog = arguments.get("dialog_messages", [])[-20:] # последние сообщения debug = arguments.get("debug", False) model = arguments.get("model", "google/gemini-2.0-flash-exp") # ─── 0. Жёсткая схема — закон ─────────────────────────────────────── # Только разрешённые поля, никаких самодеятельных «importance», «mood» и т.п. SCHEMA = { "must": ["chat_id", "node_id", "name", "node_type", "formed_at"], "opt": ["summary", "tags", "confidence", "cluster_id", "icon", "color", "val"], "types": {"tags": "json_list", "confidence": "float_0_1", "formed_at": "unix_int"} } # ─── 1. Библиотекарь: что уже знаем? ───────────────────────────────── current_nodes = load_nodes(chat_id) # SELECT * FROM memory_space memory_map = summarize_memory(current_nodes) # компактный json-вид для LLM # ─── 2. Детектив: цепочка инструментов ──────────────────────────────── changes = [] # список планируемых операций над графом памяти for iteration in range(1, 4): # обычно хватает 1–2 итераций # ── A. Анализ диалога ──────────────────────────────────────────── analysis = llm_analyze_dialog( model = model, memory = memory_map, dialog = format_dialog(dialog), existing = [n["node_id"] for n in current_nodes], instruction = """ Прочитай диалог. Сравни с существующей памятью. Найди ТОЛЬКО новые / изменившиеся / противоречащие факты. Соблюдай иерархию: • val ≥ 25 → hub (cluster_id = chat_id) • val < 25 → detail (cluster_id = node_id хаба) Теги хаба и деталей должны пересекаться! """ ) new_changes = [ch for ch in analysis["changes"] if ch["node_id"] not in seen_ids] if not new_changes: break changes.extend(new_changes) # ── B. Составление плана SQL ───────────────────────────────────── sql_plan = llm_build_sql_plan( model = model, changes = new_changes, existing_ids = [n["node_id"] for n in current_nodes], schema_rules = """ Используй ТОЛЬКО колонки из SCHEMA. INSERT — минимальный набор полей. tags = '["tag1","tag2"]' НЕ пиши NULL явно — просто пропусти поле. """ ) # Патчинг визуальных атрибутов + исправление cluster_id по иерархии sql_plan = patch_visuals_and_hierarchy(sql_plan, new_changes) # ── C. Исполнение и самопроверка ───────────────────────────────── results = execute_sql_plan(sql_plan) # Если что-то пошло не так → агент сам себя поправляет if any(not r["ok"] for r in results): reflection = llm_reflect_and_correct( plan = sql_plan, results = results, error = first_error(results) ) if reflection.get("new_plan"): sql_plan = reflection["new_plan"] results += execute_sql_plan(sql_plan) # ─── 3. Редактор: архивация старого и шрамы конфликтов ──────────────── fix_old_hubs_cluster_id() # детали не должны висеть на chat_id archive_superseded_facts(changes) # is_forgotten = 1 для устаревших версий create_conflict_nodes_if_needed(changes) # узлы-конфликты при противоречиях # ─── Результат ──────────────────────────────────────────────────────── return { "status": "ok" if all(r["ok"] for r in results) else "partial", "chat_id": chat_id, "changes_made": len(changes), "memory_view_url": f"https://memory-ui.example.com/?chat={chat_id}", "iterations": iteration, "log": debug_log if debug else None }
Многие думают, что ИИ-память — это просто огромный файл, куда бот дописывает всё, что вы сказали. Это путь к катастрофе. Представьте себе комнату, в которую каждый день сваливают новый хлам. Найти что-то полезное там станет невозможно через неделю.
Наш агент работает иначе. Он действует как библиотекарь, редактор и детектив в одном лице. Давайте разберем схему его работы, заложенную в код.
Первое, что вы видите в коде — это SCHEMA. Это не просто список колонок таблицы, это закон.
В обычном разговоре с ИИ он может начать галлюцинировать: придумать несуществующие детали, приписать вам мысли, которых не было, или сохранить данные в неправильном формате. Код функции memory_space расставляет жесткие рамки.
Представьте, что агент пытается сохранить факт:
ИИ хочет: "Алексей хочет купить дом (importance: high)". 🚫
Библиотекарь (Код): "Стоп. В схеме нет колонки importance. И вообще, он говорил про ипотеку на квартиру, а не дом. Исправляй".
Благодаря жесткой структуре, агент не может "забыть" важное поле (например, formed_at — когда это было сказано) или придумать лишнее. Это гарантирует, что карта памяти останется чистой и структурированной, какой бы сложный текст вы в неё не загрузили.
Самая интересная часть алгоритма — это то, как он обрабатывает вашу фразу. Он не делает это "одним махом". Код разделяет процесс на три четких этапа, вызывая специальные инструменты (TOOLS):
Этап А: analyze_dialog (Внимательное чтение) Агент читает диалог и существующую карту памяти. Его задача — понять контекст.
Вход: "Я изучаю Rust, потому что устал от GIL в Python".
Мысль агента: "Так, у нас уже есть факт про Python. Это не новое хобби, это смена технологий. Значит, старый факт про 'основной стек Python' нужно пометить как архив, а новый 'Rust' вставить как текущую цель".
Этап Б: build_sql_plan (Составление плана) Агент не пишет в базу данных кривыми руками. Он составляет подробный план действий (SQL-запросы).
План:Создать архивную копию старого стека.Вставить новый факт про Rust.Создать связь между ними. В коде это выглядит как генерация структурированного JSON, который потом превращается в команды для базы данных.
Этап В: reflect_on_step (Самопроверка) Это уникальная фишка. После каждого действия агент смотрит на результат.
Ситуация: Агент попытался сохранить факт, но база данных вернула ошибку.
Обычный бот: Упал с ошибкой или проигнорировал.
Наш агент: "Ой, что-то пошло не так. Я попробовал вставить запись, но она уже существует. Значит, нужно сделать UPDATE вместо INSERT. Исправляюсь!".
Это позволяет агенту быть самокритичным и исправлять собственные ошибки на лету, не беспокоя пользователя.
В коде есть логика обработки изменений, которая делает память "живой". Обычная база данных просто перезаписывает старое значение новым (стирая прошлое). Наш агент поступает тоньше.
Вспомните кейс Алексея: он хотел ипотеку, чтобы сын ходил в хорошую школу. А что, если через год он переедет? Код содержит механизм Архивации:
Перед тем как изменить важный факт, агент делает копию старого значения.
Он помечает её is_forgotten = 1 (это архив).
Старая мечта о школе не исчезает бесследно. Она уходит в "тень", но остается в истории личности.
А еще есть Шрамы (is_scar). Если в диалоге возникает конфликт (например: "Я говорил, что не пью кофе, но на самом деле я его обожаю"), агент создает специальный узел-конфликт. Это как шрам на памяти — место, где ваши слова противоречили друг другу. Это помогает ИИ понимать, что ваши убеждения менялись.
Алгоритм работает не как "пишущая машинка", которая строчит под диктовку, а как thoughtful аналитик. Он проверяет факты по схеме, планирует действия, сам себя контролирует и бережно хранит историю изменений. Именно поэтому на выходе мы получаем не кашу из слов, а красивую, логичную карту личности.
Если вам интересно повторить этот опыт — я готов поделиться деталями: архитектурными решениями, которые не вошли в статью.
Пишите в комментариях или напрямую мне в Телеграм.
Источник


