Привет! Меня зовут Анастасия. Я старший продуктовый дизайнер внутренних сервисов в Ozon Tech. Чтобы быстро решать вопросы клиентов Ozon, мы используем чат-ботов и постоянно их улучшаем. Такие боты встроены в клиентский чат, чат с продавцами, чат Ozon Travel и другие.
В этой статье я расскажу о том, как мы с командой переработали конструктор, расширили круг его пользователей, с какими проблемами столкнулись и как решали их в условиях ограниченного времени.
Подробнее о том, зачем нужен чат-бот в Ozon, мой коллега Дмитрий Васильев рассказал в своей статье «Любовь, люди и роботы: как создать чат-бота, за которого не стыдно».
Почему решили всё переделать
Ранее работать в конструкторе могли только специально обученные сотрудники — сценаристы. Они знали структуру JSON и умели правильно вызывать навыки, но их было всего 5-7 человек. Дело в том, что онбординг новых сотрудников был сложным и занимал много времени. Сначала требовалось найти человека с техническими навыками, а затем обучить его. Поэтому создание нового бота занимало не менее недели.
Со временем ботов становилось всё больше, а поддерживать их было уже сложно и дорого. Поэтому мы решили разработать no-code-решение, которое позволяет:
создавать ботов почти без привлечения программистов;
масштабироваться на всю компанию;
запускать новых ботов в течение одного дня.
Про проектирование и разработку MVP
Мы разбили задачу на несколько этапов, чтобы приблизить запуск проекта. Первым шагом протестировали решение на одном боте. Вторым — добавили недостающую функциональность, интеграции с другими сервисами и перенесли оставшихся ботов.
При проектировании учитывали требования бизнес-заказчиков, опыт и пожелания пользователей старой платформы, а также лучшие решения популярных аналогичных сервисов. В результате мы собрали MVP на компонентах дизайн-системы OZI и спустя год работы запустили его под названием Bots Factory.
OZI — дизайн-система, на которой строится более 500 продуктов, сервисов, админпанелей и мобильных приложений. Подробнее о дизайн-системах в Ozon рассказал мой коллега Виктор Теплов в статье «Автостопом по дизайн-системе. Путеводитель с оглавлением».
Знакомство с конструктором
Давайте поближе познакомимся с конструктором и посмотрим, что он собой представляет. Вначале мы попадаем на главную страницу со списком всех ботов, а затем переходим к конкретному.
Каждый бот состоит из основного сценария и дополнительных. Их может быть очень много, что позволяет гибко настраивать взаимодействие с пользователями. Чтобы найти нужный сценарий, можно воспользоваться поиском по названию или содержимому, а также сортировкой по дате создания.
На странице есть полная информация о боте, включая его системное название, интеграцию, уникальный ID и историю версий, которая позволяет отслеживать изменения в сценариях. Кроме того, пользователи могут опубликовать бота, протестировать его, скопировать, изменить название, экспортировать или удалить.
Выбрав бота, мы выбираем сценарий и переходим к редактору. Редактор — это место, где можно собрать сценарий из блоков. А блоки — это шаги сценария, соединённые между собой связями.
Остановимся сначала на редакторе. Он построен на основе функциональности канваса. При его проектировании были позаимствованы некоторые функции из Miro и Figma, что позволило сократить время онбординга новых пользователей. Вот некоторые из них:
Выделение. Блоки можно выделять, щёлкая по ним мышью. Для выделения нескольких блоков удерживаем клавишу Shift (⇧ на Mac) во время щелчка. Чтобы выделить все блоки на канвасе, используем комбинацию клавиш Ctrl (Cmd на Mac) + A.
Перемещение. Чтобы переместить выделенный блок, удерживаем левую кнопку мыши и перетаскиваем его в нужное место. Для перемещения нескольких блоков сначала выделяем их, а затем перетаскиваем все вместе.
Копирование и вставка. Копировать блоки можно с помощью комбинации Ctrl (Cmd на Mac) + C, а вставлять — с помощью Ctrl (Cmd на Mac) + V.
Удаление. Для удаления выделенных блоков используем клавишу Delete (Backspace на Mac).
Отмена и повтор действий. Последнее действие можно отменить с помощью Ctrl (Cmd на Mac) + Z, а повторить — с помощью Ctrl (Cmd на Mac) + Shift + Z.
Теперь давайте подробнее рассмотрим доступные типы блоков, каждый из которых выполняет свои функции:
«Старт»
«Контент»
«Условие»
«Действие»
«Подсценарий».
Блок «Старт» есть во всех сценариях и является точкой входа.
Другой тип — «Контент». С его помощью можно собрать сообщение, которое мы отправим пользователю. Оно может содержать текст, изображение, документ и кнопки.
Блок «Условия» определяет, какие переходы будут выполнены в зависимости от значений переменных, используемых в сценарии. Например, если мы хотим понять, обращается ли клиент по обычному заказу, Global или Fresh.
Блок «Действия» нужен, чтобы вызывать навыки и работать с переменными. С помощью навыков бот получает информацию о клиентах и их заказах, а также выполняет определённые действия, такие как отмена заказа или изменение даты доставки.
И последний тип имеет говорящее само за себя название — «Подсценарий». Он играет роль портала в другой сценарий. Например, если срабатывает выражение #message.ButtonID == 8
, то бот перейдёт в сценарий «Аннуляция Ozon Fresh», а если #message.ButtonID == 9
, то в сценарий — «Аннуляция постомат OzonBox».
Чтобы лучше понять, как это всё работает, давайте посмотрим на простой пример из жизни: пользователь не смог оплатить заказ и заходит в чат поддержки. Ему автоматически отвечает бот. Затем пользователь выбирает заказ, который его интересует.
Бот проходит через несколько сценариев и на этапе «Условия» выясняет, по какому заказу задан вопрос. Затем он получает данные по нему, проверяет статус, а также наличие оплаты из других сервисов. После этого бот останавливается на блоке «Контент» и отвечает клиенту.
Теперь, когда вы понимаете, как работает наш конструктор, я хочу рассказать о проблеме, с которой мы столкнулись в блоке «Условия» после запуска первого этапа.
Конструктор выражений
Напомню, что после переноса одного бота нам нужно было перенести остальных. Для этого потребовалось добавить новые выражения. Выражения — это специальные конструкции, которые определяют поведение бота в ответ на определённые входящие сообщения или действия пользователя.
В первой версии мы добавили как простые A == B
, так и часть функциональных fn(A)
. А чтобы добавить другие (например, fn(A) == B
, A == fn(B)
, fn(A, B) == C
), нужно было пересобрать весь конструктор выражений.
Выражения являются частью условия. Условий может быть несколько, и в этом случае они проверяются последовательно, пока одно из них не сработает. А выражения между собой сравниваются с помощью функций «и» и «или».
Давайте рассмотрим процесс создания выражений в первой версии. Чтобы добавить простое выражение, сначала выбираем переменную, значение которой нужно проверить.
Затем выбираем оператор:
Равно ==
Не равно !=
Больше >
Меньше <
Больше или равно >=
Меньше или равно <=
Есть вхождение строки fn(A)
Нет вхождения строки !fn(A)
Теперь — тип данных второго аргумента и его значение. Доступны следующие типы: число, строка, кнопка или содержимое другой переменной.
Аргумент в выражении — это значение или переменная, которые передаются функции или операции для выполнения вычислений. В контексте математических и логических выражений аргументы могут быть числами, переменными или другими выражениями.
Например, в математической функции
( f(x) = x^2 )
аргументом является( x )
. Когда вы подставляете значение для( x )
, например( 3 )
, вы получаете( f(3) = 3^2 = 9 )
.
Во второй версии мы изменили поведение конструктора, чтобы более гибко им управлять. Теперь при создании выражения сначала указываем его тип: простое «(x)
» или функциональное «fn
». Если выбрано простое выражение, то заполняем переменную и завершаем правую часть выражения.
Если выбрано функциональное, то сначала выбираем функцию. Затем система автоматически достроит левую часть выражения. Останется лишь заполнить её и завершить правую часть.
Если функция будет булевой, то конструктор построит всё выражение целиком, и останется лишь заполнить недостающую информацию.
В функциональных выражениях функция может быть расположена в левой или правой части. Однако мы решили не перегружать конструктор и пользователей лишней логикой, поэтому ограничились её использованием только слева. Также мы зафиксировали ширину всех полей, но при этом ширина для значений больше, так как они могут быть довольно длинными.
Работа с функциями find_regexp и find_substring оказалась наиболее сложной среди функциональных выражений. Для создания выражения с ними сначала нужно было выбрать переменную, а затем операцию — «есть вхождение строки» или «нет вхождения строки». После нужно было заполнить значение и продублировать эту конструкцию столько раз, сколько значений необходимо добавить (20–30 раз).
Мы исправили это, и теперь необходимо просто выбрать нужную функцию, переменную и ввести все значения в одно поле.
Статусная модель
Поскольку мы хотели быстрее запуститься, некоторые из первоначальных решений мы сочли не самыми оптимальными, особенно это касалось статусной модели.
Статусная модель была неудобной и содержала много «лишних» этапов, которые выполняли формальные функции и не приносили значительной пользы. Например, когда пользователь вносит изменения в сценарий и хочет их опубликовать, ему сначала нужно сохранить их, подготовить к сборке, выполнить сборку, протестировать и только после этого перейти к этапу публикации.
Кроме того, статусная модель не позволяла нескольким пользователям одновременно работать над одним и тем же сценарием. Это становилось проблемой в случаях, когда требовалось срочно внести изменения в сценарий, над которым уже работал другой пользователь. В таких ситуациях приходилось обращаться за помощью к разработчикам.
Мы решили не изобретать свой велосипед, а частично позаимствовать логику работы с ветками из Git. Теперь каждый бот считается мастер-веткой, а его сценарии всегда находятся в статусе «Опубликован». Чтобы внести изменения в логику бота, нужно отвести ветку. Ветки полностью клонируют мастер, а их количество может быть неограниченным.
На странице ветки можно добавить новый сценарий или изменить существующие. Изменённые сценарии будут отмечены и перемещены вверх списка в статусе «Черновик».
После внесения изменений мы можем перейти к этапу тестирования, а затем — к публикации. Если за это время никто не вносил изменения в те же блоки в мастере, что и мы, то слияние пройдёт без проблем. Кроме того, нужно обязательно добавить комментарий, чтобы другие пользователи могли понять, что именно было изменено.
Рассмотрим другую ситуацию. Пока мы работали в своей ветке, наши коллеги внесли изменения в мастер. В этом случае система уведомит нас об этом. Теперь нужно обновить свою ветку, подтянув изменения из мастера.
Если при этом коллеги вносили изменения в те же блоки в мастере, что и мы, то возникнут конфликты. Механика их разрешения совпадает с Git. На самом деле конфликты бывают редко, поскольку пользователи обычно не работают одновременно над одними и теми же сценариями. Но если всё же это случится, то система предупредит нас на этапе публикации. Появится модальное окно, и после нажатия на кнопку «Разрешить» мы попадём на страницу разрешения конфликтов.
На этой странице мы можем посмотреть, в каких сценариях возникли проблемы. Если конфликты не требуют глубокого изучения, то можно разрешить их все сразу, выбрав либо свои изменения, либо из мастера. После этого можно продолжить процесс публикации.
Если конфликты сложные, то можно перейти в нужные блоки и изучить их. На странице конфликта мы видим сплит-панель: в левой части содержимое блока из нашей ветки, а в правой — из мастера. Тут у нас также есть выбор, что применить: свои изменения или из мастера. Однако может оказаться так, что ни те, ни другие изменения применить «как есть» нельзя. В таком случае можем перейти в конструктор, кликнув по ссылке «Редактировать», и внести нужные изменения вручную.
Нужно отметить, что в первой версии мы не будем подсвечивать различия на визуальном представлении. Однако это возможно в режиме JSON для пользователей, которые с ним знакомы.
Из стандартных функций Git мы также позаимствовали возможность откатывать изменения до любой версии.
Результаты
Bots Factory в Ozon существует уже чуть больше года, и за это время количество пользователей увеличилось с менее 10 до более 100. Теперь, помимо команды сценаристов, со своими ботами работают разные отделы и направления компании: операторы, юристы, HR и другие.
На данный момент у нас уже есть 3 бота (2 внутренних и 1 внешний), созданные бизнесом всего за несколько дней. Кроме того, скорость разработки сценариев значительно увеличилась: ранее на изменение простого консультационного сценария уходило около 2 часов, а теперь эту задачу можно выполнить менее чем за 5 минут.
У нас много планов по дальнейшему масштабированию. Например, мы планируем добавить возможность дообучения моделей распознавания без участия разработчиков и создавать собственных LLM-агентов, обученных на наших базах знаний.