1. Главная
  2. Блог
  3. Как мы дали ИИ-агенту память: pgvector и retrieval вместо fine-tuning

Как мы дали ИИ-агенту память: pgvector и retrieval вместо fine-tuning

Как мы дали ИИ-агенту память: pgvector и retrieval вместо fine-tuning

У нас был обычный ИИ-агент секретаря: разбирал клиентов, ставил задачи менеджерам, фиксировал результаты касаний. Внутри — LLM-движок, сверху — наш офисный API. И всё вроде бы работало — ровно одну итерацию.

А дальше начиналось знакомое. В понедельник он предлагал ветклинике зайти через прайс. Во вторник — снова через прайс. В среду — опять через прайс, хотя менеджер ещё в понедельник записал в CRM, что клиника про цены слышать не хочет и её волнует поток пациентов. Агент про это, конечно, не помнил: каждый цикл он начинал с чистого листа.

Ещё он стабильно дублировал задачи, не помнил прошлых возражений и генерил один и тот же сценарий одному и тому же клиенту неделю подряд. Промпт распухал от ручных правок — «а вот тут ещё учти, что…», — а весь опыт между касаниями просто испарялся. Так себе автономный ИИ-агент.

Логичный путь — fine-tuning. Мы пошли другим.

Почему не fine-tuning и не самая мощная модель

Первая идея была наивная: возьмём модель посильнее, и она сама вырулит. Подняли на пробу Claude Opus через прокси — и уткнулись в инфраструктуру. Вызовы через CLI-subprocess давали 75–150 секунд на запрос, а Cloudflare режет соединение на сотой секунде. Половина запросов отваливалась по таймауту, и это было не поправить косметически: сам механизм вызова добавлял слишком много накладных.

Fine-tuning отвалился по другим причинам:

  • датасет внутренних касаний — десятки строк в неделю, не тысячи;
  • любая правка регламента работы секретаря потребовала бы переобучения;
  • стоимость и время на каждый цикл несопоставимы с тем, как быстро меняется бизнес-логика.

Плюс была неприятная мысль, которую в чате техкоманды озвучил один из ребят: даже если мы дообучим модель на своих кейсах, объяснить менеджеру, почему агент вдруг предложил именно этот сценарий, будет невозможно. Fine-tuning размазывает знания по весам, и потом не покажешь пальцем, откуда конкретно взялся ответ.

Решение оказалось банальным. Оставили LLM-движок (стабильные 10–30 секунд на ответ, GigaChat Pro в проде плюс запасной вариант) и подвесили сбоку retrieval-слой, который подмешивает опыт прошлых касаний прямо в системный промпт. Гибрид собрался за неделю и не требовал переучивать модель.

Архитектура: secretary-ai как отдельный микросервис

Память вынесли в отдельный сервис, а не в монолит офисного API. Так его проще масштабировать, перезапускать и в перспективе подключать к другим агентам — у нас в компании это не единственный ИИ-агент, и общая платформа ИИ-агентов постепенно вырисовывается сама собой.

Стек скучный и дешёвый — специально:

  • FastAPI на VPS;
  • PostgreSQL с расширением pgvector;
  • sentence-transformers, модель paraphrase-multilingual-MiniLM-L12-v2 (384 измерения, мультиязычная, работает на CPU);
  • HNSW-индекс по векторным колонкам.

Embedder крутится локально, на той же VPS. Это принципиальный момент: никаких внешних API за эмбеддинги, никаких счётчиков токенов, никакой латентности на каждом запросе. Модель весит около 500 МБ, инференс одного предложения — десятки миллисекунд. Плюс данные не улетают наружу, что для нас важно и по 152-ФЗ, и по договорам с корпоративными заказчиками.

В pgvector лежат три типа сущностей:

  • lessons — короткие выводы, которые секретарь делает после касания («для ветклиник холодный заход через прайс не работает, нужен кейс с похожей клиникой»);
  • outcomes — фактические результаты касаний с привязкой к клиенту и каналу;
  • dossiers — векторизованные досье клиентов на основе карточек CRM.

Схема таблицы для lessons примерно такая:

create table lessons (
  id           bigserial primary key,
  client_type  text not null,
  channel      text,
  summary      text not null,
  embedding    vector(384) not null,
  created_at   timestamptz default now()
);

create index lessons_embedding_idx
  on lessons using hnsw (embedding vector_cosine_ops);

Ничего экзотического. Именно в этом и смысл.

Как работает retrieval перед вызовом LLM

Поток такой. Офисный API готовит контекст для очередного цикла секретаря по конкретному клиенту. Перед формированием итогового промпта он стучится в secretary-ai и просит «похожий опыт».

Внутри secretary-ai:

  1. Текущий контекст клиента эмбеддится той же моделью.
  2. По cosine similarity достаём top-5 lessons и top-3 outcomes.
  3. Параллельно подтягиваем dossier клиента, если он есть.
  4. Форматируем всё это в RAG-блок и возвращаем строкой.

В системном промпте есть плейсхолдер {{relevant_experience}}, куда этот блок и подставляется. Модель видит не абстрактные инструкции, а конкретные прошлые ситуации с похожей геометрией: «вот что мы пробовали, вот что сработало, вот где обожглись».

Latency всей retrieval-операции — меньше 500 мс на HNSW-индексе. На фоне 10–30 секунд ответа LLM это вообще не заметно.

Первую версию, кстати, мы собрали без HNSW — на честном последовательном скане. На тестовом объёме это работало, а как только реальные касания пошли валом, запросы начали заметно проседать. Индекс добавили за полдня, и график latency вернулся к плинтусу.

Замыкание цикла: write-back после каждого касания

Retrieval без write-back — это просто красивая база знаний, которая со временем стухнет. Мы замкнули цикл.

После каждого результата касания офисный API дёргает secretary-ai и пушит туда:

  • новый lesson — короткий вывод, который сам движок LLM генерирует в отдельном служебном вызове;
  • новый outcome — фактический результат с метаданными (канал, дата, статус);
  • обновлённый dossier, если карточка клиента поменялась.

Каждая запись эмбеддится при вставке и сразу попадает в HNSW-индекс. Уже следующее касание этого или похожего клиента увидит свежий опыт.

Это и есть тот самый цикл обучения, ради которого обычно тащат fine-tuning: агент сам накапливает контекст, который сам же через retrieval и использует. По сути, из «умного скрипта» получаются автономные ИИ-агенты, которые постепенно становятся лучше без ручного вмешательства инженера.

Отдельная история — что писать в lesson. Первую неделю мы дали LLM полную свободу, и он честно писал банальности вроде «важно быть вежливым с клиентом». Пришлось прикрутить отдельный служебный промпт, который явно требует: короткий вывод, что именно сработало или не сработало, привязка к типу бизнеса и каналу, без общих слов. После этого база начала наполняться содержательно.

Что получили на практике

Числа приводить не буду — статистика на таком объёме была бы кокетством. Скажу качественно.

За первую пару недель:

  • база lessons из пустой превратилась в осмысленный конспект по нашим типам клиентов;
  • retrieval-слой держит ответ уверенно ниже полусекунды;
  • стоимость всей этой инфраструктуры — копейки на VPS, эмбеддер бесплатный;
  • ни одной миграции LLM, ни одной строчки fine-tuning-кода.

По ощущениям менеджеров, которые с этим агентом работают ежедневно:

  • перестал предлагать одно и то же одному и тому же клиенту;
  • начал узнавать паттерны по типам бизнеса — «холодный звонок ветклинике начинай с конкретики, а не с прайса»;
  • почти перестал плодить дубли задач, потому что видит свои же прошлые outcomes;
  • лучше держит тон, накопленный в dossier — раньше он у одного и того же клиента гулял от «дорогого партнёра» до «привет!».

Не всё было гладко. Первую неделю секретарь регулярно вытягивал не тот опыт: например, к производственной компании подтягивал уроки из работы с медициной, потому что в тексте карточки мелькали слова про «пациентов» и «поток». Пришлось добавить фильтрацию по client_type до векторного поиска, а не только по similarity. Стало заметно точнее.

Кому такой подход подходит

Часто спрашивают: «А нам такое надо? Может, проще купить готового ИИ-агента у вендора?» Ответ зависит от того, что у вас за задача.

Retrieval поверх собственных данных выигрывает, когда:

  • агент работает в цикле, где между итерациями накапливается контекст;
  • бизнес-логика меняется быстрее, чем имеет смысл переобучать модель;
  • объём данных — сотни-тысячи записей, а не десятки миллионов;
  • нужна объяснимость: какой именно прошлый опыт повлиял на конкретный ответ (для B2B это обычно критично).

Если это про вас — pgvector-подход вытягивает почти всё. Если задача звучит как «дай нам чат-бота, который отвечает на типовые вопросы» — это ближе к ИИ-ассистенту, и там часто хватает более простых архитектур. Разница между ИИ-агентом и ИИ-ассистентом как раз здесь: агент действует в цикле и накапливает опыт, ассистент отвечает на разовые вопросы. Путать их — верный способ переплатить за архитектуру или, наоборот, недобрать функциональности.

Что мы вынесли как архитектурное правило

Первая реакция на «модель тупит» — попробовать модель посильнее. Иногда это правильно. Чаще — нет.

В нашем случае LLM не тупила. Ей просто не хватало контекста: она не знала, что мы уже пробовали с этим клиентом и чем закончилось. Как только мы дали ей доступ к собственному прошлому через pgvector, проблема исчезла без смены движка.

Retrieval — это дешёвый способ дать LLM память. Часто он закрывает большую часть задач, ради которых люди затевают fine-tuning или миграцию на топовую модель. Начинайте с него — и только если упрётесь в потолок, идите дальше.

Похожие статьи

Хотите так же?

Обсудим, как это внедрить у вас

Опишите задачу — подберём ИИ-решение под ваш процесс и бюджет: что сделать агентом, какие нужны данные и интеграции, с чего начать пилот.