Эфир 18.11 · Поток 318 ноября в 19:00 МСК — бесплатный практический эфир по ИИ-агентам · Живой Поток 3 стартует 23.11Эфир 18 ноября →Поток 3 (23.11)
РелизAPI / Продукт · В работуHabr AI→ оригинал

В боте для amoCRM два лимита диалога не работали с запуска: виноваты И вместо ИЛИ и битая ветка

В админке бота Octery для amoCRM были два лимита: максимум ответов и порог бессмыслицы. Ни один не работал с самого начала. Условие передачи менеджеру собрали через И вместо ИЛИ, а ветка передачи писала значения в поля с неверными именами, и следующие шаги читали старый ответ модели.

AI-обработка оригинала Habr AI; редакция Hamidun News
В боте для amoCRM два лимита диалога не работали с запуска: виноваты И вместо ИЛИ и битая ветка
Источник: Habr AI. Коллаж: Hamidun News.
Слушать статью
Паспорт материала и цифры
ПараметрДанные
ПервоисточникHabr AI ↗
Главный редактор
Логика жила в конфиге пайплайнани юнит-тестов, ни CI, ни читаемого диффа
Ключевой факт #2Решение вынесли в чистую функцию в API, лимиты она берёт из настроек сама
Ключевой факт #3Каждый из 16 сценариев спецификации стал тестом с говорящим названием

Представьте: в админке стоят цифры, настройка сохраняется, интерфейс выглядит как надо. А на деле она не делает ничего. Именно так 30 сентября выяснилось, что в боте Octery не работают сразу оба лимита на длину диалога, и, судя по всему, не работали с самого начала.

Octery отвечает лидам в чатах amoCRM и передаёт сделку менеджеру. Лимитов два. Max Messages задаёт, сколько ответов бот даст, прежде чем позвать человека. Hard Stop Threshold определяет, сколько бессмысленных сообщений подряд он стерпит: «ааа», «тест», «???» или вообще чужой бот. Каждое такое сообщение стоит вызова модели, так что отвечать на них бесконечно незачем.

Две ошибки, которые прикрывали друг друга

Решение о передаче принималось после ответа модели, в пайплайне обработки. Условие собиралось из трёх проверок, и между ними стояло И. По смыслу настроек лимиты независимы: передавать надо, если исчерпан счётчик ответов, или если набралась серия бессмыслицы. С И бот звал менеджера, только когда клиент одновременно выбрал лимит сообщений и несколько раз подряд писал ерунду. Такого не случалось.

Вторая ошибка сидела в ветке, которая должна была сработать при выполнении условия. Она переписывала ответ на «передаю менеджеру» и ставила намерение manager. Но значения попадали в поля, у которых вместо имён стояли выражения: значение вставили в поле имени, и оно там осталось. К тому же отправка ответа и логирование читали данные не из этой ветки, а напрямую из разбора ответа модели. Даже если бы кто-то поправил И на ИЛИ, клиент получил бы обычный ответ, а в логах не было бы следа.

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

Почему никто не заметил

Логика жила в конфигурации пайплайна, а не в коде. Юнит-тест на конфиг не напишешь, в CI не прогонишь, а на ревью правка выглядит как «поправил условие», без диффа, который можно прочитать. Доводить живой диалог до лимита руками долго, поэтому при обычных проверках до него не доходили. На вопрос «работают ли лимиты» не было ни положительного, ни отрицательного ответа.

Что сделали

Решение переехало в API отдельной чистой функцией. Пайплайн передаёт ей ответ модели и два счётчика из состояния диалога, а лимиты функция берёт из настроек компании сама, так что вызывающий не подсунет другие. На выходе одно итоговое решение: текст, намерение, причина передачи. Все следующие шаги читают только его.

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

Тесты из спецификации

Поведение сначала описали сценариями, потом каждый стал тестом, всего 16. Названия говорят сами за себя: «hands off on the Nth non-meaningful message in a row, counting the current one», «a meaningful message breaks the non-meaningful streak». Тест «old AND case» оставили специально: единственный случай, где старое условие вообще срабатывало, теперь частный случай нового.

Риск выката и вывод

Главный риск не технический. Компании с маленькими лимитами впервые увидят, как бот замолкает и зовёт менеджера, и для них это будет выглядеть как новая функция, а не как исправление. Поэтому их предупреждают до включения, это отдельная строка плана выката.

Практический вывод для команд с no-code и low-code пайплайнами: если цифра видна клиенту в интерфейсе, у её поведения должен быть автоматический тест, а в названии теста должно читаться обещание клиенту. Бизнес-логику, от которой зависят деньги и передача клиента человеку, лучше выносить из конфигурации в код, где её можно прочитать в диффе и прогнать в CI.

Сюжет

Вопросы и ответы по материалу

Что известно по теме: две ошибки, которые прикрывали друг друга?

Решение о передаче принималось после ответа модели, в пайплайне обработки. Условие собиралось из трёх проверок, и между ними стояло И. По смыслу настроек лимиты независимы: передавать надо, если исчерпан счётчик ответов, или если набралась серия бессмыслицы. С И бот звал менеджера, только когда клиент одновременно выбрал лимит сообщений и несколько раз подряд писал ерунду. Такого не случалось. Вторая ошибка сидела в ветке, которая должна была сработать при выполнении условия.

Что известно по теме: почему никто не заметил?

Логика жила в конфигурации пайплайна, а не в коде. Юнит-тест на конфиг не напишешь, в CI не прогонишь, а на ревью правка выглядит как «поправил условие», без диффа, который можно прочитать. Доводить живой диалог до лимита руками долго, поэтому при обычных проверках до него не доходили. На вопрос «работают ли лимиты» не было ни положительного, ни отрицательного ответа.

Что известно по теме: что сделали?

Решение переехало в API отдельной чистой функцией. Пайплайн передаёт ей ответ модели и два счётчика из состояния диалога, а лимиты функция берёт из настроек компании сама, так что вызывающий не подсунет другие. На выходе одно итоговое решение: текст, намерение, причина передачи. Все следующие шаги читают только его. В формулах две ловушки на единицу. Счётчик ответов считается до текущего сообщения: бот даёт ровно max_messages ответов, потом передаёт.

ЖХ
Жемал Хамидун — CPO Alpina Digital, продакт-менеджер AlpinaGPT. Делает AI-продукты с 2019 года. Hamidun News — pet project, который вырос в production-агрегатор с 6000+ читателей.
✦ Единое AI-пространство · Nomad

Протестируйте новые LLM и мультимодальные модели в одном окне

Платформа Nomad AI (nomadnet.ai) даёт прямой доступ к Claude Opus 5.5, GPT-6, Gemini 3.1, DeepSeek и автономным агентам без VPN, зарубежных карт и зоопарка подписок.

Открыть Nomad AI →Автономный Nomad Agent
Что вы думаете?
Загружаем комментарии…