Недавно мы рассказывали, как собрали AI-менеджера маркетплейсов на правилах : фраза проходит через регулярки, превращается в операцию из закрытого списка, операция идёт в API площадки. Модель там только помогала угадать операцию. Всё, что меняет кабинет, — только кнопкой владельца.
Первые владельцы с ним поговорили. Ниже — что мы увидели в этих разговорах, почему переделали менеджера почти с нуля и почему новая версия получилась свободнее, но при этом вся в ограничителях.
Что показали первые разговоры
Люди пишут не по списку. Мы ждали «покажи остатки на ВБ», а получили «почему просели продажи», «что делать с худи» и «сравни с прошлым месяцем, только без возвратов». Правила такие фразы не узнавали, и менеджер отвечал общим ответом. Таких фраз было [32] из [120].
Цифры есть, понимания нет. Это оказалось важнее всего. Заказы, остатки, воронку и расходы на рекламу селлер и так видит в кабинете площадки. Ему нужно другое: почему мало продаж. Может, карточка не находится в поиске. Может, реклама тратит деньги на запросы, по которым никто не покупает. Может, цена выше, чем у соседей по выдаче. Менеджер 1.0 честно показывал таблицу, а вывод из неё оставлял человеку.
Каждая новая просьба — новая ветка кода. Регулярка, обработчик, строка в списке операций, проверка в смоук-тесте. Владельцы просили быстрее, чем мы успевали дописывать.
И пара честных багов. Их нашли читатели прошлой статьи, и мы их признаём:
-
если владелец называл площадку, которая не подключена, менеджер молча отвечал по первой подключённой;
-
кнопка с новой ценой собиралась из снимка кабинета, а перед записью текущая цена не перечитывалась.
Оба бага — следствие самого устройства: решения принимает код, а код знает только те случаи, которые мы предусмотрели.
Решение: пусть модель сама выбирает, что делать
Мы перевернули схему. Раньше код разбирал фразу и звал обработчик. Теперь модель видит разговор и список инструментов, сама решает, что вызвать и в каком порядке, смотрит результат и продолжает, пока не будет готова ответить. Примерно так работают агенты для программистов, и мы взяли ту же идею.
Инструментами стали все операции из первой версии. Заказы, остатки, цены, акции, воронка, отзывы — каждая операция из закрытого списка превратилась в инструмент сама, переписывать их не пришлось. Сверху добавили новые:
-
сравнение продаж по товарам: последние 7 дней против предыдущих 7;
-
карточка товара целиком;
-
журнал: что и когда меняли в кабинете;
-
план: задачу из нескольких шагов агент сначала расписывает, потом делает;
-
память: «запомни, что худи мы больше не возим»;
-
наблюдение и «проверь позже»: менеджер сам возвращается к задаче через время.
Теперь на «почему просели продажи» агент может сам посмотреть продажи по товарам, найти, что упало, открыть эту карточку, проверить остатки и рекламу и только потом ответить.
Страшно. Поэтому — ограничители
Дать модели доступ к кабинету, где лежат цены и рекламные бюджеты, — решение, после которого плохо спится. Поэтому вторую версию мы обложили ограничениями со всех сторон.
Ход ограничен по шагам и по времени. Не больше восьми шагов с инструментами, не больше 150 секунд на ответ, не больше шести вызовов за шаг. Время кончилось — агент отвечает тем, что успел узнать.
MAX_STEPS = 8BUDGET = 150 # секунд на весь ходLAST_CALL = 25 # осталось меньше — последний шаг без инструментовCALLS_PER_STEP = 6 # параллельных вызовов в одном шагеdef run(ctx, system, history, tools): msgs = history + [{"role": "user", "content": ctx.q}] start = time.monotonic() for step in range(MAX_STEPS + 1): left = BUDGET - (time.monotonic() - start) last = step == MAX_STEPS or left < LAST_CALL if last: msgs.append({"role": "user", "content": "Время на задачу кончилось. Ответьте тем, что уже узнали, " "и скажите, что осталось сделать."}) r = providers.call(system, msgs, [] if last else tools, left) if not r.calls: return r.text # ответил текстом — ход окончен for c in r.calls[:CALLS_PER_STEP]: msgs.append(tool_result(c, run_tool(ctx, c)))
Упавший инструмент ход не роняет: модель получает ошибку словами и решает, что делать дальше.
Инструменты — узкие и с инструкцией. Каждый инструмент мы снабдили описанием, как им пользоваться: «до 15 результатов», «лимит общий, пачкой не зовите», «один-два вызова широкими словами, потом один-два финалиста». Данные перед передачей модели подрезали: таблица — до 200 строк, название — до 60 знаков, ячейка — до 120.
Запись — только то, что владелец уже видел. Кнопок под ответами больше нет: агент спрашивает словами «Делаю?», владелец отвечает «да». Но «да» — это не разрешение на что угодно. Выполнить можно только конкретное предложение из прошлого ответа, и проверяет это код, а не модель:
def _confirm(ctx, a): """Выполнить предложение из ПРОШЛОГО ответа: владелец его видел и согласился.""" if ctx.who == "поручение": # ход по расписанию: соглашаться некому return Out("Это ход по расписанию — подготовьте и спросите владельца.") if _NO.search(ctx.q.lower()): return Out("Владелец не согласился — не выполняю.") msg, act = find_offer(ctx.earlier, a.get("act")) if act is None: return Out("Такого предложения в прошлых ответах нет. Подготовьте и спросите.") if now() - msg.created_at > FRESH: # час return Out("Предложение старше часа — цифры могли измениться. Подготовьте заново.") ok, say = staff_work.do(ctx.db, ctx.tenant, msg, act["id"]) return Out(("Выполнено: " if ok else "Не вышло: ") + say)
Модель не может придумать правку и тут же её выполнить. Не может выполнить её ночью по таймеру. Не может применить цену, которую владелец видел вчера: через час предложение протухает, и агент обязан собрать его заново на свежих цифрах.
Скиллы: знания вместо сценариев
Свобода выбора инструментов не делает модель селлером. Она знает, что такое маркетплейс, но не знает, как опытный менеджер ищет нишу или почему карточка проваливается в выдаче. Поэтому мы дали агенту скиллы — библиотеку знаний должности.
На полку менеджера легли:
-
метод «от ниши до готовой карточки» по шагам;
-
статьи практиков о SEO карточек и разборе конкурентов;
-
руководство по аналитике MPStats и два десятка страниц его базы знаний.
Оглавление полки лежит в промпте, нужные статьи агент открывает сам под задачу. Чтобы конспекты не съедали контекст, их заранее сжимает более умная модель.
Инструментов при этом стало много, и каждый шаг агента тяжелел. Мы разложили их по группам и прятали редкие: агент находит их поиском, а не получает все сразу. Шаг соседнего сотрудника, маркетолога, похудел с 35 до 14,5 КБ.
И ещё одна мелочь, которая потом оказалась очень полезной. Если владелец просит то, для чего инструмента нет, агент не выкручивается. Он зовёт отдельный инструмент «нет инструмента», пишет, чего не хватило, и честно отвечает «пока не умею». Все такие просьбы по всем проектам собираются в одном списке в админке. По нему мы решаем, что добавлять первым.
Как тестируем
Как и в первый раз, менеджер работает в двух местах: на демо-магазине без сети и у владельцев с живыми кабинетами.
Добавился эталон: восемь типовых задач селлера и модель-судья, которая ставит ответу оценку от 1 до 5. Он видит данные, которые получил агент, и может проверить не только тон, но и цифры.
Смотрим на три вещи:
-
насколько глубоко агент копает, прежде чем ответить;
-
сколько раз он упирается в «пока не умею» и в чём именно;
-
где наши ограничители спасают, а где мешают.
Мы дали агенту свободу и тут же обложили её правилами. С виду всё под контролем: шаги посчитаны, данные отмерены, каждая запись — с согласия владельца. Менеджер стал отвечать увереннее и подробнее. Вопрос в том, на чём держится эта уверенность. Когда закончим разбирать тесты, поделимся результатами.
ссылка на оригинал статьи https://habr.com/ru/articles/1100298/