Оглавление:
История
Память: факты, embeddings и забывание
Разные модели под разные задачи
Tool calling
Планировщик и proactive
Агенты и мультиагентский пайплайн
Vision, который знает контекст
Персональные данные и GigaChat
Что делать дальше
История
Изначально ничего общего с ассистентом не было. Мы с женой делали текстовую браузерную игру: выборы, немного тактики, бои. Она автор, я помогал адаптировать её тексты под формат игры и собирал бэкенд. Чтобы ей было удобно генерировать лор, я поднял отдельный репозиторий, прикрутил интеграцию с Claude и китайской GLM моделью, собрал админку игры на Symfony. Контента она наделала много, и он был нужен и мне: характеристики персонажей, описания боёв. Запросы вроде «покажи бой Анвара с Медведем» или «какая воля у Кид» решались через локальную LLM (Ollama).
Там же появилась первая связка, которая потом стала основой: Ollama на игровом ПК плюс Postgres с pgvector.
Эмбеддинги по лору игры — и простые запросы начинают работать быстро и бесплатно. Получился простенький RAG.

Вторая проблема выросла из иллюстраций. Для игры их нужно было много. Сначала генерация промптов для Stable Diffusion жила в админке, потом я вынес это в отдельное приложение — я уже писал про него (https://habr.com/ru/articles/1030628/). Но главное не это. Главное, что пока я всё это собирал, я постоянно и подолгу общался с ИИ. И ловил себя на том, что раз за разом пишу одно и то же: "у меня RTX 4070 Ti Super, 16 ГБ VRAM, мне надо вот это ...".
И в какой-то момент пришёл к логичному: "Если ИИ и так всё это знает обо мне, почему я должен повторять?" Так родилась идея ассистента, который помнит тебя между разговорами, умеет работать с документами и реально что-то делает, а не просто отвечает на вопросы.
Что я хотел получить:
память: ассистент сам вытаскивает факты из диалога и хранит их;
ассистенты: чаты с готовым промптом, по сути агенты;
база знаний: прикрепляешь ссылку или файл, и ИИ работает прямо в этом контексте;
и конкретные мои «хотелки»: "прочитай статью и перескажи коротко", "найди салонный фильтр для моей машины" (именно моей), "найди простую чёрную майку на маркетпйлесах" (без допроса про рост и размер), "напомни в 15:00 о ...", "какие у меня планы на завтра?".
Стек выбрал такой: бэкенд на Go, фронт на Vue 3 как PWA и Capacitor для сборки APK под Android, в базе PostgreSQL с расширением pgvector (потому что уже было), LLM через облачные провайдеры. Об этом далее.

Память: факты, embeddings и забывание
Память делится на две части, и это важно их не смешивать.
Первая часть — это векторный поиск по контексту. Когда приходит сообщение пользователя, я собираю промпт: системный шаблон, профиль, последние сообщения истории и релевантные куски из базы знаний через RAG. История и база знаний живут на pgvector. Я выбрал pgvector, а не отдельную векторную базу типа Qdrant или Weaviate, по простой причине (выше писал): у меня и так PostgreSQL, профиль и факты там же.
Вторая часть — это факты. Это не просто «история переписки», а структурированные утверждения про пользователя, которые ассистент сам вытаскивает из диалога. После каждого сообщения работает extraction: отдельный вызов модели читает пару последних сообщений и возвращает JSON с фактами. «У пользователя Toyota Camry 2019 года», «аллергия на пыльцу», «работает ночью».
Пример — как это выглядит под капотом. Представим, что пользователь написал:
У меня Toyota Camry 2019 года. В среду был на медосмотре, всё нормально, но врач сказал сдавать кровь каждые полгода. И напомни завтра в 15:00 забрать заказ из пункта выдачи.
Что extraction вытаскивает из этого сообщения:
{ “facts”: [
{“fact”: “У пользователя Toyota Camry 2019 года”, “category”: “personal”, “kind”: “fact”},
{“fact”: “Пользователь должен сдавать кровь каждые полгода”, “category”: “health”, “kind”: “fact”}
],
“tasks”: [ {“description”: “Забрать заказ из пункта выдачи”, “deadline”: “2026-06-23T12:00:00Z”}
],
“completed_tasks”: []
}
Обратите внимание на дедлайн: "завтра в 15:00" превратилось в 2026-06-23T12:00:00Z — конкретная дата и 12:00 UTC.
Факты потом подмешиваются в системный промпт при следующем разговоре. И тут выясняется, что просто копить их нельзя: их становится слишком много, они устаревают, начинают конфликтовать — один факт говорит одно, а другой, добавленный через неделю, почти противоположное. Память без управления быстро превращается в помойку, и качество ответов падает.
Поэтому поверх накопления пришлось строить ещё несколько механизмов. Сначала нормализация времени: "сегодня", "вчера", "ночью" в тексте факта заменяются на конкретную дату, иначе через неделю факт теряет смысл. Потом веса: у каждого факта есть вес. При сохранении нового факта система ищет похожий по смыслу (similarity выше 0.70) и, если находит, считает это повтором — вес старого факта растёт, а дубль не плодится. В обратную сторону вес падает сам: планы старше 30 дней и события старше 14 дней постепенно уменьшаются до нуля. Факт с весом 0 физически не удаляется, но в промпт больше не попадает. Не всё стареет одинаково: имя и семью важно держать вечно, а "что ел на обед неделю назад" можно смело забывать. Дальше — summary: старые факты периодически агрегируются и сжимаются, чтобы не тянуть в промпт всё подряд.
И всё равно весов и summary мало: иногда факт нужно выкинуть сознательно и сразу. Для этого есть забывание прямо из чата. Пишешь "забудь про носовое кровотечение" — модель вытаскивает суть ("носовое кровотечение"), и система удаляет все похожие факты.
Про нос — пример реальный. Я разбил нос и попросил чат "Фитнес-тренер" подстроить мне тренировку на сегодня. Проблема всплыла позже: чат запомнил про нос и стал постоянно его учитывать — уменьшать нагрузки "чтобы не было проблем с давлением" и раз за разом спрашивать: "Как сегодня твой нос?". Пришлось написать "забудь про носовое кровотечение" — и вопросы закончились.
Изоляция памяти (space)
С играми всплыла и вторая проблема — утечка контекста. Однажды фитнес-тренер посоветовал мне "не брать большие веса после вчерашнего удара топором по голове". Удар топором был в чате-игре D&D, а попал в советы по тренировкам. Поэтому факты разделили по пространствам (space): у некоторых чатов своя локальная память, и, к примеру, игровые факты в ней и остаются, не смешиваясь с основным профилем. Глобальное живёт отдельно от того, сколько HP у моего орка.
Кстати, про игры. Играть с ИИ — отдельный кайф: контекст держится, и сюжет меняется прямо в диалоге. Моя ситуация: играю орком в D&D, захожу в очередную комнату, на меня кидается маг с ножиком. Вместо того чтобы сразу бить, я заговорил с ним: "Мы прошли кучу комнат и завалили куда более страшных противников, ты будешь просто очередной жертвой; давай лучше выберемся, я познакомлю тебя с прекрасными девами и угощу вином, только умойся, а то от тебя плохо пахнет (я использовал другое слово)". Маг сел, загрустил и сказал, что с ним ещё никто так не говорил, и отдал ключ от следующей комнаты.

Разные модели под разные задачи
Первоначально у меня была одна модель на всё.
Дело в том, что задачи очень разные по требованиям. Чат должен быть умным и быстрым. Extraction — это вообще не чат: модели скармливают короткий промпт и просят вернуть строгий JSON, тут важна дешевизна и следование формату, а не глубина рассуждений. Vision — отдельный класс моделей. Пихать одну дорогую vision-модель на extraction — дороже.
Поэтому в архитектуре появилось разделение: chat-модель, extraction-модель и vision-модель, каждая со своим назначением. В конфиге провайдера это просто три поля, и у каждой свой модельный ID.
Для ориентира — цены у одного из агрегаторов (не буду писать какой) на момент написания:
Задача | Модель | Input / Output, ₽ за 1M токенов |
Чат (основная) | Gemini 2.5 Flash | 33 / 276 |
Чат (эконом) | Qwen 3.5 Flash | 4 / 44 |
Extraction | GPT-5 Nano | 4 / 31 |
Vision | Qwen3 VL Flash | 3 / 33 |
Embeddings | text‑embedding-3-small | 3 |
Extraction гоняется на дешёвой Nano, основное общение — на Gemini, и только когда в чате появляется фото, работает vision-модель. Себестоимость среднего пользователя при такой схеме получается вменяемой.
Важный момент про саму интеграцию. Я не стал привязываться к одному провайдеру. Все LLM-вызовы идут через единый OpenAI-совместимый клиент, а провайдеры лежат в таблице с приоритетами. Если основной падает или отдаёт 5xx, запрос уходит на следующий в цепочке. Это спасает и при сбоях, и при перегрузке.
Tool calling
Чтобы ассистент не просто говорил, а действовал, нужен tool calling. Я использую стандартный OpenAI-формат: описываю функции, модель сама решает их вызвать, tool_choice выставляется в auto, когда функции переданы.
Зачем это нужно. В чат-играх (D&D, детектив, выживалка на острове) ассистент поначалу запоминал состояние персонажа как факт: "здоровье 3/30 HP". Но HP в бою меняется каждый ход, и сохранённый факт мгновенно устаревал — в новом бою модель приписывала персонажу старое здоровье и путалась. Запоминать через extraction то, что постоянно меняется, нельзя.
Tool calling чинит это иначе: модель не хранит HP как факт, а вызывает функцию и получает актуальное состояние персонажа. Точное значение по запросу — без устаревшей памяти и без угадывания. В этом и суть tools: давать модели доступ к живым данным вместо того, чтобы она их тащила из фактов.
Планировщик и proactive
Следующая ступень после "отвечает, когда спросили" — ассистент, действует сам. Хорошо работает с чатом "Планировщик" (но не только там). Он не привязан к одному чату: собирает факты из всех разговоров и видит задачи пользователя, поэтому картина у него цельная, а не обрывок текущего диалога.
Работает в двух режимах.
Проактивный: утром планировщик сам пишет планы на день, а в течение дня напоминает о задаче, время которой подошло.
Реактивный: на "какие у меня планы на завтра?" отвечает по реальным задачам, а на "добавь задачу на завтра" или "для такого-то факта укажи время" — вносит правки. Задачи и факты можно не только читать, но и менять прямо из чата.
Агенты и мультиагентский пайплайн
Ассистенты в моём понимании — это чаты с готовым системным промптом. Завёл чат "травник", задал ему роль — и он придерживается её в ответах, опираясь на твою память и базу знаний.
Но интереснее, когда агенты работают не поодиночке, а в цепочке. Возьмём пример: приложением пользуется моя жена. Она выращивает травы и делает из них натуральную хендмейд-косметику. Допустим, нужно сделать контент про какое-то растение. Сначала используем чат "травник" — он вытаскивает суть и факты по теме. Его результат уходит копирайтеру, который превращает это в читаемый текст. Затем подключается SMM-стратег и адаптирует текст под конкретную площадку: заголовок, тон, формат. Плюс она обучила модель своему стилю письма (по примеру своих же статей). На выходе — не один ответ модели, а готовый материал, прошедший через трёх специалистов и сохранивший стиль автора.
Мультиагентский пайплайн можно применить и для других задач:
обучение и экзаменация;
генератор идей → критик → финализатор;
вопрос, прошедший через разных советчиков и критиков;
шопинг: ищем девайс — один агент собирает характеристики, другой подбирает примеры, третий сверяет с бюджетом.
Придумать можно ещё многое. Близкая аналогия — то, как агенты работают в разработке ПО: архитектор -> кодер -> ревьюер.
Только что пока я пишу статью прибежала радостная жена и говорит: смотри, я задала вопрос, а он мне написал что написал:


Тот же принцип работает и в поиске по маркетплейсам. Когда я говорю "найди салонный фильтр для моей машины", в запрос уходит не абстрактное "модель и год", а конкретно мой автомобиль из памяти. Профиль инжектируется в поиск, и результат получается под меня, а не усреднённый. Разница с обычным "найдите фильтр" колоссальная.
Vision, который знает контекст
Vision-модели работают куда интереснее, когда им не просто показываешь картинку, а даёшь контекст из памяти.
Если просто кинуть фотографию в модель, она опишет, что видит. А если в тот же промпт положить факты про пользователя и его окружение, модель начинает "узнавать". На фото семья — и ассистент не просто видит "несколько человек", а может соотнести их с тем, что знает: кто есть кто. Это уже не распознавание, а понимание.
Технически это мультимодальный контент в OpenAI-формате: текст плюс image_url. Никакой магии, просто правильная сборка промпта. Но эффект заметный.
Персональные данные и GigaChat
Технически всё описанное работает. Но на иностранной модели или на локальной Ollama: и чат, и vision, и embeddings, и tool calling. Проблемы начались, когда я решил выложить всё это в общий доступ.
Проблема в персональных данных. Многие российские агрегаторы юридически находятся в РФ, но физически у них нет своих GPU, и запросы уходят за рубеж даже к простым моделям. Какие точно я не могу сказать и они то не раскроют, как я понимаю. Для продукта, который помнит про пользователя всё, это означает, что все эти данные уезжают в другую страну. С учётом 152-ФЗ это не та ситуация, в которую хочется добровольно лезть.
Логичная мысль — перейти на отечественные модели, чья инфраструктура реально в РФ. Попробовал GigaChat, как самая известная и (наверное) умная. И тут начались проблемы.
GigaChat не OpenAI-совместимый. У него своя авторизация, свой формат промптов, свой формат vision, свои короткие embeddings. По уровню рассуждений он примерно соответствует GPT-4o-mini (как мне показалось). Техническую несовместимость ещё можно докрутить, но основных проблем оказалось несколько.
Extraction фактов не отработал так, как мне нужно. Summary, на котором держится агрегация и сжатие памяти, тоже не зашло. Tool calling на момент моего тестирования в моём сценарии не отработал адекватно. И цены: самая простая модель Lite стоит ~19 тыс. рублей за 300 млн токенов — GigaChat сильно дороже.

Самое наглядное получилось с vision. Я сфотографировал мак без цветка — в таком состоянии растение узнать непросто.


Qwen3 VL Flash определил, что это за растение. GigaChat увидел просто «какое-то растение». И это не придирка к частному примеру, а разница в пригодности под задачу.
Что делать дальше
Коротко: Проект готов технически. Поэтому прежде чем вкладывать ещё месяцы в допиливание GigaChat или в свою GPU-инфраструктуру, я хочу понять одно — нужна ли вообще кому-то такая штука.
Вот и сам вопрос. Как по-вашему, что мне делать с этим проектом (не нашел, как сделать опрос):
Добить и выложить в опенсорс, пусть живёт как есть. Бэкенд можно использовать как для работы через агрегаторы, так и с локальной Ollama.
Тянуть в «серую зону»: остаться на зарубежных моделях через агрегатор, оформить согласие на обработку и не париться. Как я понимаю куча ИИ-чатов в русторе так и живут.
Не нужно. Ниша занята, продукт не взлетит, оставить как опыт.
Свой вариант в комментариях.
Если кому-то интересно или есть мысли — напишите. Выкинуть жалко: сейчас используем только для себя, а лезть туда, где я не силён, — не буду.
