Дисклеймер: в статье упоминается Meta — организация, признанная экстремистской и запрещённая на территории РФ.
Ключи, ID и номера в примерах частично изменены; схема, код и логика — из рабочего проекта.
В прошлой статье (https://habr.com/ru/articles/1076198/) я разобрал конвейер между агентом и клиентом: теги, регулярки, абзацы по 180 символов. Осталось два больших пояснения — что происходит со сделкой в amoCRM, когда диалог дошёл до неё, и стоп-кран, которым человек перехватывает диалог у бота. Поэтому сегодня показываю два Salesbot’а, которые замыкают контур: «Раздатчик» (диспетчеризация задач менеджерам) и «Идентификатор» (оцифровка старой базы).
Хочу начать с вопроса, который оставался за кадром всех предыдущих статей. Нода IF: Is New User? в начале ветки проверяет Redis-ключ связки: пустой — значит клиент новый, запускаем синхронизацию со сделкой. А если клиент старый — заведён в CRM руками за год до внедрения ИИ? Если к примеру как в данном проекте, из-за внутренних решений касательно бизнес логики у заказчика сделки хранятся до последнего момента, наверное пока их не наступит больше чем за 30000 штук (P.S. про это я позже расскажу чуть позже — точнее про то КАК это ломает работу воркфлоу)? У него связки тоже нет. Формально он выглядит как новый. Обычная система в этот момент создаёт вторую сделку на первого же старого клиента, который написал ночью. Дубль. Добро пожаловать в слепую зону.
CRM-ветка: что происходит после Is Dialog Finished?
Напомню, где мы остановились. Нода Is Dialog Finished? (три условия через OR) решила: диалог больше не болтовня, несём данные в CRM. Дальше топология такая:
Is Dialog Finished? -> Redis: Get Union Link -> IF: Has Union Link? TRUE -> Amo: Update Existing Lead (Union) -> Redis: Save Union Link FALSE -> Prepare IG Response -> Send direct message -> Clear Cache
FALSE-ветка скучная и правильная: ответили клиенту, почистили агрегат, разошлись. А TRUE-ветка — это поход в amoCRM. Перед ним, естественно, ещё одна проверка: связку не просто читаем, а валидируем:
// из Redis достаётся ключ вида {"leadId":"123456"}IF: JSON.parse($json.amoLeadId).leadId не пустое
И только потом — обновление сделки. Вот его реальный маппинг (ID кастомных полей amoCRM оставил как есть — они вам ничего не говорят, зато честно видно, что поле не одно):
|
Поле amoCRM |
Что пишем из n8n |
|---|---|
|
Вердикт ИИ (text) |
|
|
MESSENGER-ID (textarea) |
|
|
MESSENGER-TYPE (textarea) |
источник — объект вебхука ( |
|
Ответ ИИ (textarea) |
анкета: Имя / Дата рождения / Контакт / Дизайн / Сфера |
|
Тег сделки |
|
Код обновления, сокращённо до сути:
{ id: JSON.parse($json.amoLeadId).leadId, responsible_user_id: 13533162, // жёстко прописанный менеджер custom_fields_values: [ { id: 2452289, value: verdict }, // Вердикт ИИ { id: 2461565, value: chatId }, // MESSENGER-ID { id: 2458673, value: 'Имя: ...\nДата рождения: ...\nКонтакт: ...\nДизайн: ...\nСфера: ...' } ], _embedded: { tags: [{ id: task }] }}
Три решения здесь не случайны.
Почему «Ответ ИИ» — отдельное поле, а не «пусть менеджер откроет Direct». Менеджер утром открывает карточку и видит анкету целиком: имя, дата рождения, контакт, сфера, дизайн. Ему не нужно листать мессенджер и собирать данные по сообщениям. Это и есть разница между «данные где-то в системе» и «данные в карточке».
Почему responsible_user_id прописан жёстко. Помните засаду из второй части — штатная интеграция при обновлении перетягивала «Ответственного» на своего технического пользователя? Вот его лечение в коде: каждый апдейт возвращает сделку нужному менеджеру. Не однажды — каждый.
Почему после апдейта снова Save Union Link. TTL связки продлевается при каждом касании: клиент, который пишет каждую неделю, никогда не выпадет из контекста.
Salesbot «Раздатчик»: почему задача, а не уведомление
Бот отработал ночь, данные в карточках. Казалось бы — победа. Но представьте утро менеджера: в воронке пятнадцать сделок, обновлённых за ночь, и среди них три горячих лида с телефонами. Без диспетчера он открывает карточки по очереди, в порядке, в котором они отображаются — а не в порядке приоритета. Горячий лид с телефоном ждёт рядом с «посмотрел каталог».
Это и есть слепая зона между «данные зафиксированы» и «данные отработаны». Закрывает её Salesbot «Раздатчик» — встроенный механизм amoCRM, который живёт уже не в n8n, а внутри CRM и триггерится на события воронки.
Алгоритм из четырёх шагов:
-
Сканирование маркеров. Salesbot проверяет сделку на системные теги (тот самый
NEED_MANAGER_CALLиз[TASK:]) и валидность поля «Вердикт ИИ». -
Оценка критичности. Поле «Вердикт ИИ» заполнено значит, ночь закончилась не просто перепиской: есть имя, контакт и суть запроса. Сделка признаётся приоритетной.
-
Постановка задачи. На ответственного менеджера мгновенно вешается задача с типом «Связаться с клиентом (Лид от ИИ)» и жёстким дедлайном.
-
Контроль внимания. Менеджер приходит на смену и видит не «какую-то обновлённую карточку», а активную задачу со счётчиком времени в разделе «Мои задачи».
Отдельно про «почему задача, а не уведомление» — это принципиальный выбор. Уведомление можно смахнуть и забыть; оно живёт секунды. Задача с дедлайном — это элемент учёта: она висит в списке, у неё есть срок, её невыполнение видно. Когда я говорю «исключить человеческий фактор», я не имею в виду, что менеджеры плохие. Я имею в виду, что «посмотрю карточки после кофе» — нормальное человеческое поведение, и система должна быть рассчитана на него, а не на идеального исполнителя.
Практический эффект по моей статистике: реакция на горячий лид в первые минуты смены, а не «когда дошли руки». Цифры по времени реакции и доле задач, закрытых в дедлайн — скрины ниже, после того как соберу их из amoCRM.
Salesbot «Идентификатор»: слепая зона старой базы
Теперь обещанная разгадка из интро. Схема из второй части работает идеально, пока связка «чат = сделка» строится для новых клиентов: клик по рекламе = новая сделка от штатной интеграции = синхронизация = связка в Redis на 30 дней. Замкнутый цикл.
Но у любого бизнеса, внедряющего автоматизацию, за плечами уже есть база. Клиенты, заведённые менеджерами вручную, со сделками, историей, покупками. Что происходит, когда такой клиент вспоминает о вас в час ночи и пишет в Direct?
Разбираем по схеме: связки в Redis нет (её некто не создавал — сделку тогда создавал человек), поле MESSENGER-ID в карточке пустое. IF: Is New User? видит пустой ключ и делает единственно возможный логичный вывод: новый клиент. Дальше штатная интеграция создаёт вторую сделку на человека, который уже купил у вас браслет. Он замечает, что его «заново завели» — и это уже не техническая проблема, а репутационная.
Salesbot «Идентификатор» закрывает эту дыру на стороне amoCRM. Его настройки (реальные):
-
источник: Instagram-сообщения;
-
триггер: первое входящее сообщение от контакта;
-
режим: ежедневно с 20:00 до 00:00 и с 00:00 до 10:00 — ровно окно работы ИИ;
-
этапы воронки: все, кроме «Неразобранного» (там свои правила игры).
Алгоритм работы:
-
Перехват. Старый контакт пишет в Direct в нерабочее время. Salesbot, в отличие от меня, всегда смотрит в базу: этот chatId у нас уже есть. Он инициализирует технический POST-запрос в n8n.
-
Runtime-идентификация. n8n параллельно держит два потока: вебхук от amoCRM (через Salesbot) и входящий поток от Instagram Graph API. Они приходят почти одновременно.
-
Генерация связей. n8n прописывает
MESSENGER-IDв существующую карточку и создаёт в Redis ключ связкиapp:union:ig:{chatId}с обычным TTL 30 дней. -
Результат. Слепая зона закрывается на лету, сессия не прерывается: к моменту, когда ветка агента дойдёт до
Is New User?, связка уже существует, и старый клиент опознаётся как старый. ИИ видит контекст прошлой покупки, а не спрашивает «чем могу помочь?» у человека, который у вас уже покупал.
Почему параллельные вебхуки не конфликтуют: запись идемпотентна. Ключ детерминирован chatId, значение — leadId той самой единственной карточки. Кто бы ни пришёл первым — Salesbot-вебхук или IG-поток — в Redis ляжет одно и то же значение под одним и тем же ключом. Это дешёвый, но важный принцип: если два источника пишут одно и то же, race condition не страшен.
Самое приятное в этом модуле — он оцифровывает базу сам. Каждый старый клиент, написавший ночью, навсегда получает MESSENGER-ID в карточке и связку в Redis. Через пару месяцев работы вся активная база оказывается «подключённой» к ИИ-контуру без единого ручного действия.


Стоп-кран: как человек перехватывает диалог
Последний элемент, который отличает боевую систему от демо: что происходит, когда менеджер хочет вмешаться в диалог, который ведёт бот.
Требование формулируется просто: любое событие «человек вступил в диалог» должно глушить бота — иначе клиент получит два ответа подряд от разных сущностей. В моей схеме это решено на двух уровнях.
Проверка перед отправкой. В ветке отправки стоит предохранитель IF: Go or STOP?, который смотрит на связку со сделкой до того, как воркфлоу пойдёт дальше. Отбойная ветка (Clear Cache STOP) подчищает агрегат сообщений, чтобы накопленный буфер не протёк в следующую сессию. Гарантия банальная, но критичная: не отправляем то, что отправлять уже не нужно.
Управление по состоянию сделки. Идея, которую я вынашиваю в следующую итерацию из обсуждений с заказчиком — явный флаг: скрытое поле-чекбокс «Остановить ИИ» в карточке. Перед отправкой ответа n8n проверяет галочку; менеджер, взяв диалог, ставит её — бот молчит. Вариант ещё проще, который уже работает в amoCRM из коробки: смена этапа сделки на «взят в работу» как сигнал. Здесь честно скажу: текущая версия закрывает основной сценарий, чекбокс — следующий шаг, и я сознательно не выпускаю его, пока не проверю все ветки.
Важно, что стоп-кран не ломает сессию: Redis-контекст и связка остаются на месте. Когда менеджер закончит и клиент снова напишет ночью — бот продолжит с того места, где был контекст, а не с «Здравствуйте! Чем можем помочь?».
Что в итоге в цифрах
Методология прежняя: amoCRM + Google Sheets, не контролируемый эксперимент. Что могу сказать честно:
-
сделок с дублями после внедрения «Идентификатора»: ;
-
задача «Связаться с клиентом (Лид от ИИ)» ставится на каждую сделку с непустым «Вердиктом ИИ» — здесь механика даёт 100% покрытие по построению;
-
среднее время реакции менеджера на задачу: ;
-
снижение ручной работы команды — около 40% (оценка по списку задач до/после, как и во второй части).
Что дальше
На бумаге всё выглядит красиво: вот мы элегантно закрыли проблему вебхуков, вот дисциплинированные Salesbot’ы ставят задачи, вот стоп-кран для человека. Но у каждой такой схемы есть обратная сторона, которую не рисуют на архитектурных картинках: собственная архитектура ломает всё сама (сложный промпт — это тоже риск), есть ограничения платформ, и есть баги, которые всплывают не в день запуска, а через недели — когда трафик идёт как снежный ком. С нами случилось именно так.
В следующей части — разбор самого странного бага серии: рассуждающая модель тратила токены, отчитывалась finish_reason: "stop", а клиенту уезжала пустая строка. Reasoning Lock: почему LLM сама решает молчать, почему это не ловится автотестами — и четвёртый контур предохранителей, на этот раз от самой модели. Контур комментариев и статистика не пропали — переезжают на шаг позже: баг важнее.
Всем добра!)
ссылка на оригинал статьи https://habr.com/ru/articles/1076972/