The Jerusalem PostUS declares Lebanese diplomat persona non grata after post praising Hezbollah's 1983 bombingZDF heuteEntdecken Sie das ZDF-NachrichtenstudioThe South AfricanTeen rugby prodigy dies after battle with meningitisХабрЗатестил Kimi AI, Qwen, DeepSeek и Grok на работе с текстами. Делюсь результатамиRadio-CanadaPologne : une fusillade fait six morts à CracovieGlobal NewsUnifor says it believes Stellantis sold its Brampton plant20 MinutenArzt hat 20’000 Fr. Lohn: «Fürs Eigenheim reichts trotzdem nicht»DW EnglishSouth Africa's ANC facing worst-ever performance in local electionsVarietyRichard Lloyd, Television’s Fiery and Fluent Guitarist, Dies at 75Wirtualna Polska"Musimy być razem". Mocne słowa kard. Rysia po masakrze w KrakowieSouth China Morning PostClashes in Paris after new French protestsIl Fatto Quotidiano“L’ad di Nvidia Jensen Huang ha avuto una relazione con una donna reclutata come spia da Pechino”
The Daily Newsstand · Free, Always
Saturday, November 7, 2026

В настройках бота было два лимита на длину диалога. Ни один из них ни разу не сработал

Translate

В админке Octery у каждой компании есть две настройки, которые ограничивают разговор бота с клиентом. Max Messages: сколько ответов бот даёт, прежде чем позвать менеджера. Hard Stop Threshold: сколько бессмысленных сообщений подряд он терпит, прежде чем сделать то же самое. 30 сентября выяснилось, что не работает ни та, ни другая, и, похоже, не работала с самого начала.

Octery это бот, который отвечает лидам в чатах amoCRM и передаёт сделку менеджеру. Лимиты нужны ему по двум причинам. Если человек задал уже двадцатый вопрос, ему пора к живому специалисту, бот своё дело сделал. А если в чат пишут «ааа», «тест», «???» или там вообще другой бот, каждое такое сообщение стоит нам вызова модели, и отвечать на них бесконечно незачем.

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

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

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

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

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

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

Доводить тестовый диалог до лимита руками долго, и при обычных проверках до него не доходили. Так что на вопрос «работают ли лимиты» у нас не было ни одного ответа, ни положительного, ни отрицательного.

Что сделали

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

Формулы стоит записать явно, потому что в них две ловушки на единицу:

// счётчик ответов считается до текущего сообщения:
// бот даёт ровно max_messages ответов, потом передаёт
const byMessages = messageCounter >= maxMessages;

// серия бессмыслицы считается вместе с текущим сообщением,
// так написано в подсказке к настройке в интерфейсе
const byNonsense = !meaningful && nonMeaningfulStreak + 1 >= hardStopThreshold;

if (byMessages || byNonsense) { /* передать менеджеру */ }

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

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

Поведение сначала описали сценариями в спецификации, а потом каждый сценарий стал тестом. Их 16, вот часть названий как есть:

passes the agent answer through when no limit is reached
hands off once max_messages were already processed, even for a meaningful message
still answers with the agent on the last allowed message
hands off on the Nth non-meaningful message in a row, counting the current one
does not hand off on the first non-meaningful message
a meaningful message breaks the non-meaningful streak
hands off when both limits are reached (old AND case)
uses the default phrase when consult_phrase_template is empty

Тест с пометкой «old AND case» проверяет единственный случай, в котором старое условие вообще срабатывало (до сломанной ветки дело всё равно не доходило). Я оставил его специально: старое условие входит в новое как частный случай.

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

Сама ошибка банальная, И вместо ИЛИ, такое бывает у всех. Меня в этой истории больше задело другое: у настройки, которую видит клиент, не было ни одной автоматической проверки. Теперь правило простое: если цифра стоит в интерфейсе, у её поведения есть тест, и тест называется так, чтобы по названию было понятно, что обещано клиенту.

View the original on Хабр →

KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.