Ночь — боту, а утро человеку: CRM-ветка и два Salesbot’а в amoCRM

от автора

Дисклеймер: в статье упоминается 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)

verdict — тот самый [VERDICT:ORDER:DATA|HOT] из третьей части

MESSENGER-ID (textarea)

chatId клиента

MESSENGER-TYPE (textarea)

источник — объект вебхука (inst)

Ответ ИИ (textarea)

анкета: Имя / Дата рождения / Контакт / Дизайн / Сфера

Тег сделки

task из [TASK:NEED_MANAGER_CALL]

Код обновления, сокращённо до сути:

{  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 и триггерится на события воронки.

Алгоритм из четырёх шагов:

  1. Сканирование маркеров. Salesbot проверяет сделку на системные теги (тот самый NEED_MANAGER_CALL из [TASK:]) и валидность поля «Вердикт ИИ».

  2. Оценка критичности. Поле «Вердикт ИИ» заполнено значит, ночь закончилась не просто перепиской: есть имя, контакт и суть запроса. Сделка признаётся приоритетной.

  3. Постановка задачи. На ответственного менеджера мгновенно вешается задача с типом «Связаться с клиентом (Лид от ИИ)» и жёстким дедлайном.

  4. Контроль внимания. Менеджер приходит на смену и видит не «какую-то обновлённую карточку», а активную задачу со счётчиком времени в разделе «Мои задачи».

Отдельно про «почему задача, а не уведомление» — это принципиальный выбор. Уведомление можно смахнуть и забыть; оно живёт секунды. Задача с дедлайном — это элемент учёта: она висит в списке, у неё есть срок, её невыполнение видно. Когда я говорю «исключить человеческий фактор», я не имею в виду, что менеджеры плохие. Я имею в виду, что «посмотрю карточки после кофе» — нормальное человеческое поведение, и система должна быть рассчитана на него, а не на идеального исполнителя.

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

1 - тег поставлен; 2 - заполнены поля Вердикт ИИ/ Ответ ИИ; 3 - заполнены поля  MESSENGER-ID/ MESSENGER-TYPE

1 — тег поставлен; 2 — заполнены поля Вердикт ИИ/ Ответ ИИ; 3 — заполнены поля  MESSENGER-ID/ MESSENGER-TYPE
Задача поставлена ровно в 10:00, менеджер открыл карточку в 10:17

Задача поставлена ровно в 10:00, менеджер открыл карточку в 10:17

Salesbot «Идентификатор»: слепая зона старой базы

Теперь обещанная разгадка из интро. Схема из второй части работает идеально, пока связка «чат = сделка» строится для новых клиентов: клик по рекламе = новая сделка от штатной интеграции = синхронизация = связка в Redis на 30 дней. Замкнутый цикл.

Но у любого бизнеса, внедряющего автоматизацию, за плечами уже есть база. Клиенты, заведённые менеджерами вручную, со сделками, историей, покупками. Что происходит, когда такой клиент вспоминает о вас в час ночи и пишет в Direct?

Разбираем по схеме: связки в Redis нет (её некто не создавал — сделку тогда создавал человек), поле MESSENGER-ID в карточке пустое. IF: Is New User? видит пустой ключ и делает единственно возможный логичный вывод: новый клиент. Дальше штатная интеграция создаёт вторую сделку на человека, который уже купил у вас браслет. Он замечает, что его «заново завели» — и это уже не техническая проблема, а репутационная.

Salesbot «Идентификатор» закрывает эту дыру на стороне amoCRM. Его настройки (реальные):

  • источник: Instagram-сообщения;

  • триггер: первое входящее сообщение от контакта;

  • режим: ежедневно с 20:00 до 00:00 и с 00:00 до 10:00 — ровно окно работы ИИ;

  • этапы воронки: все, кроме «Неразобранного» (там свои правила игры).

Алгоритм работы:

  1. Перехват. Старый контакт пишет в Direct в нерабочее время. Salesbot, в отличие от меня, всегда смотрит в базу: этот chatId у нас уже есть. Он инициализирует технический POST-запрос в n8n.

  2. Runtime-идентификация. n8n параллельно держит два потока: вебхук от amoCRM (через Salesbot) и входящий поток от Instagram Graph API. Они приходят почти одновременно.

  3. Генерация связей. n8n прописывает MESSENGER-ID в существующую карточку и создаёт в Redis ключ связки app:union:ig:{chatId} с обычным TTL 30 дней.

  4. Результат. Слепая зона закрывается на лету, сессия не прерывается: к моменту, когда ветка агента дойдёт до 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/