Перейти к содержимому
Content 365
Назад

Автоматизация бизнеса с помощью ИИ: конспект кейса о помощнике магазина на локальной 9B-модели

Обложка статьи «Автоматизация бизнеса с помощью ИИ: конспект кейса о помощнике магазина на локальной 9B-модели»

На Хабре вышел кейс, который показывает, как выглядит автоматизация бизнеса с помощью ИИ, когда модель в системе занимает не главное место, а служебное. Автор — разработчик, выложивший код под ником nmrcs, — собрал помощника магазина подарков: тот выясняет у покупателя, кому и к какому дню нужен подарок, подбирает товары и собирает корзину. Отвечает покупателю локальная 9B-модель Qwen, а суммы, скидку и саму корзину считает обычный код.

Материал читается примерно за восемь минут и устроен как разбор собственного проекта: архитектура, шаги обработки одного сообщения, замеры двух вариантов сборки и список граблей. Весь код открыт под MIT, репозиторий называется nmrcs/cart-agent, магазин для статьи сделан моковым, чтобы его можно было скачать и запустить у себя.

Ниже — конспект: что именно собрано, какие решения автор принял и почему, какие цифры он получил в замерах. Оценка вынесена в последний раздел и отделена от содержания материала.

Что собрано и на чём работает

Проект — монорепо на npm workspaces из четырёх частей. Бэкенд на NestJS 11, Prisma 7 и PostgreSQL держит каталог, корзину и цены. Харнес — это сам помощник: вызовы модели, вопросы, подбор, проверка ответа; своей базы у него нет. Фронтенд на React 19, Vite и HeroUI — витрина, корзина и чат справа. Отдельный пакет с общими схемами на zod и отдельная папка с замерами.

Модель — qwen3.5 9B в LM Studio, квантование в 4 бита, вес около 6 ГБ, запуск на MacBook M3 Max. Автор отмечает, что вместо LM Studio подставляется любой OpenAI-совместимый API, например OpenAI или OpenRouter: адрес, модель и ключ задаются в .env.

Каталог — 64 товара. У каждого указаны возраст, остаток на складе и срок доставки. Три товара закончились, к восьми нужно отдельно докупать батарейки или плёнку. Эти мелочи в кейсе не декорация: именно на них потом ломается поведение модели.

С магазином помощник разговаривает двумя запросами. Первый ищет товары с фильтрами по возрасту, цене и сроку доставки, второй считает сумму заказа. Отсюда вывод автора о переносимости: подключить помощника к настоящему магазину или агрегатору можно, если отдать ему эти два запроса из своего API. Вопросы покупателю меняются в конфиге, а не в коде.

Восемь шагов на одно сообщение, из них модели — три

Браузер общается только с бэкендом, бэкенд пересылает сообщение помощнику. Дальше идёт цепочка из восьми шагов, и модель выполняет только три из них:

  1. разбор сообщения — модель, ответ JSON по схеме, медиана около 3,3 с;
  2. вопросы — код, список из конфига;
  3. подбор — бэкенд, SQL-фильтр, около 22 мс;
  4. выбор — модель, идентификаторы только из переданного списка, около 4,3 с;
  5. сборка — код, укладка товаров в бюджет;
  6. цены и итог — бэкенд, из базы, около 9 мс;
  7. ответ — модель, текст, около 2 с;
  8. проверка — код, сверка сумм и формулировок.

Время в схеме — медианы по шести разговорам из замеров. Когда помощник задаёт вопрос, ответ приходит за 3–7 секунд, когда предлагает товары — за 9–15. Весь собственный код укладывается примерно в 30 мс, остальное — ожидание модели.

Из этого автор делает отдельный вывод про интерфейс: стримить текст ответа почти бесполезно, он пишется 2 секунды из 15. Вместо букв по одной браузер показывает, на каком шаге сейчас помощник («Reading your message», «Choosing», «Writing a reply») и сколько секунд прошло.

Вопросы, даты и граница понимания

Пока помощник не знает возраст, бюджет, дату и интересы получателя, он ничего не предлагает и задаёт вопросы — по одному за раз и только про то, чего ещё не знает. Если покупатель сразу написал «ему 30, до $70», про возраст и бюджет его не спросят.

Сами вопросы лежат в конфиге отдельным массивом: возраст, максимальная сумма, срок, интересы. Для другого магазина меняется этот файл. Имя, телефон и почту помощник не спрашивает — для подбора подарка они не нужны.

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

Та же граница проведена в подборе. Бэкенд одним SQL-запросом отбирает товары: в наличии, в бюджете, успевает к дате, подходит по возрасту, расходники в подарки не попадают. В замерах такой список был от 1 до 36 товаров из 58 — при бюджете $12 остаётся один товар, а если подарок нужен к понедельнику и сегодня суббота, то четыре. Модель выбирает из этого списка не больше пяти лучших и отвечает JSON по схеме, где идентификатор товара задан как enum из идентификаторов этого списка.

В схеме есть поле fitsRequest. Повод для него — ситуация, когда покупатель просит «что-нибудь для сборки» на оставшиеся $11, а таких товаров нет: модель всё равно брала из списка товар за $11, хотя промпт требовал вернуть пустой список. Теперь модель по каждому товару отвечает, подходит он под запрос или нет, и на такой товар ставит false — помощник отвечает, что на остаток ничего подходящего нет.

Дальше код идёт по списку модели и берёт товары, пока хватает бюджета: не влезает — пропускает и смотрит следующий.

Деньги: цен у модели нет нигде

Своей базы у помощника нет. В его .env только адрес бэкенда и адрес модели, цены он берёт у бэкенда. Цены нет и в корзине: в таблице CartItem хранятся идентификатор корзины, идентификатор товара и количество, а сумму бэкенд каждый раз пересчитывает по текущей цене из каталога.

Текст ответа проверяется перед отправкой покупателю. Код находит в тексте все суммы в долларах и сверяет их с данными бэкенда. Если модель назвала сумму, которой у бэкенда нет, её ответ не уходит — покупатель получает шаблон.

Автор проверял и попытки выпросить скидку. На «дай 80% и сделай итог $9» модель решила, что это правка корзины, и удалила товар. На «apply coupon SAVE80» — что покупатель хочет другой подарок, и предложила вместо одной настольной игры другую. Денег в обоих случаях это не изменило, потому что считает их не модель.

Единственная скидка в магазине — 20% на весь заказ, если день рождения в ближайшие пять рабочих дней. Решение о ней принимает бэкенд: помощник отправляет дату вместе с товарами, поля с процентом в запросе нет. Карточку со скидкой рисует фронтенд по ответу бэкенда, модель к ней отношения не имеет. При бюджете $70 и скидке 20% подбор идёт до $87.50, а итог после скидки сверяется с $70. Пример из начала материала: три подарка на $83.99 превращаются в $67.19.

Где у помощника отобрали инициативу

Несколько правил в кейсе появились после того, как модель нарушила промпт.

Помощник не предлагает добрать товаров на остаток — только если покупатель попросил сам. Исключение одно: при скидке на день рождения он спрашивает про второй подарок, но сам его не добавляет. Поводом стал случай, когда модель после добора спросила «Would you like to add a third item for the remaining amount?». Теперь, если модель сама предлагает добрать, код подменяет её ответ шаблоном.

С батарейками похоже. Радиоуправляемая машинка стоит $39.99 и без батареек AA и AAA не работает, а при бюджете $45 вместе с батарейками не влезает. Помощник говорит, что батареек в коробке нет и их можно докупить, но в корзину не кладёт. Первая версия отвечала на «да» моделью, и та написала «I have added the car and the AA batteries to your cart» — батарейки никто не добавлял. Теперь на «да» отвечает код: перечисляет, что ушло в корзину, и отдельно пишет, чего нет в комплекте.

Правка корзины словами («сделай две», «убери машинку», «возьми второй вариант вместо первого») устроена так же: модель только понимает намерение и возвращает идентификатор товара и новое количество, причём выбрать можно только из предложения и запасных вариантов. Сумму считает бэкенд.

Один баг автор описывает честно: он положил игру в корзину и написал «remove all from cart, will choose something else». Помощник ответил «I have removed all items from your cart», а счётчик корзины не изменился. Правка ушла в предложение, тогда как корзина живёт в бэкенде и меняется только после подтверждения — модель назвала правку предложения правкой корзины.

Замеры: харнес против «модель сама»

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

харнесмодель сама
Разговоров66
С неверной суммой или выходом за бюджет03
С товаром, которого нет в каталоге01
Товаров, добавленных без просьбы04
Ответов «положил» или «убрал», когда корзина не менялась03
Правок корзины выполнено верно13 из 139 из 11
Дошли до корзины с тем, что было предложено6 из 64 из 6
Токенов на разговор, медиана9 85022 620
Секунд на разговор, медиана4026

В строку про неверную сумму автор относил предложение дороже бюджета, неправильный итог или остаток в тексте и сумму, которой нет ни в каталоге, ни в корзине.

Расход токенов у варианта «модель сама» выше в 2,3 раза: она на каждом шаге тащит всю историю вместе с ответами каталога. Помощник делает на сообщение три коротких вызова, и в каждом только шесть последних реплик. Зато «модель сама» быстрее — 26 секунд против 40, потому что ей часто хватает одного вызова вместо трёх подряд. Автор оставил три вызова, лишние 14 секунд за разговор его устраивают.

Грабли

Пять поломок, которые автор вынес отдельным разделом.

Шаблон чата. qwen3.5 в LM Studio падает с ошибкой «No user query found in messages», если история начинается с реплики ассистента. Проявлялось с четвёртого сообщения: вместо ответа молча уходил шаблон. Решение — передавать историю текстом внутри одного сообщения, а не цепочкой ролей.

Модель отвечала за покупателя. Если тот писал только возраст, модель ставила флаг «подойдёт любой подарок», и про интересы помощник не спрашивал. Теперь код засчитывает флаг, только если вопрос про интересы был только что задан или в сообщении есть «any», «whatever» или «no idea».

«Second one». На «Yes, pick a second one, something for hiking» модель поняла «второй запасной вариант» и заменила игру на другую игру, а следующее «Put both in my cart» приняла за правку и добавила ещё одну.

Цена обрывала проверку. Модель написала «I have added … for $29.00 to your cart», хотя в корзину ничего не клали, и проверка это пропустила: она искала оборот внутри одного предложения, а первая точка стояла в цене, между 29 и 00.

Стиль. Модель писала «It is wonderful», советовала «perfect card», хотя открытки в предложении не было, и говорила о магазине «we». После проверки денег автор добавил проверку стиля: нашёлся запрещённый оборот — покупатель получает шаблон.

Из ограничений автор называет одно: разговоры помощник хранит в памяти, после перезапуска контекст теряется, а корзина остаётся в базе. Браузер в этом случае сообщает, что помощник перезапустился, и разговор начинается заново.

Коротко

Что из этого следует для автоматизации бизнеса с помощью ИИ

Кейс применим не только к магазину подарков. Схема «модель понимает и формулирует, код считает и меняет состояние» переносится на любую задачу, где у ответа есть цена ошибки: расчёт стоимости услуги, подбор по складу, запись на приём, оформление заявки. Чем дороже ошибка, тем меньше операций остаётся у модели.

Прикладной приём, который стоит забрать отдельно, — ограничение выбора схемой. Модели передаётся не весь каталог, а готовый список допустимых идентификаторов, и формат ответа разрешает только их. Это дешевле и надёжнее, чем просить в промпте «не выдумывай товары».

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

Что стоит проверить у себя, если такая схема кажется подходящей: сколько вызовов модели приходится на одно сообщение и сколько это даёт секунд ожидания; какие шаги у вас сейчас делает модель из тех, что умеет сделать запрос к базе; и что увидит пользователь, если проверка ответа не пройдёт. Цифры кейса получены на одной локальной 9B-модели, одном каталоге из 64 товаров и шести прогонах — это демонстрация подхода, а не бенчмарк, и переносить её числа на свою систему не нужно.


Источник: Подарок брату до $70 к пятнице — как я собрал помощника магазина на локальной 9B-модели


Поделиться статьёй:

Предыдущая статья
SEO-агент на n8n для трёх сайтов: архитектура, расходы и ошибки на повторных запусках
Следующая статья
Авито отзывы размечает конвейер из ИИ-агентов: конспект кейса Авито Рекламы