Привет! Меня зовут Анастасия. Я старший продуктовый дизайнер внутренних сервисов в Ozon Tech. Чтобы быстро решать вопросы клиентов Ozon, мы используем чат-ботов и постоянно их улучшаем. Такие боты встроены в клиентский чат, чат с продавцами, чат Ozon Travel и другие.
В этой статье я расскажу о том, как мы с командой переработали конструктор, расширили круг его пользователей, с какими проблемами столкнулись и как решали их в условиях ограниченного времени.
![](https://habrastorage.org/getpro/habr/upload_files/63e/2e0/f65/63e2e0f65e5ea39cfb035f9214cad70e.jpg)
Подробнее о том, зачем нужен чат-бот в Ozon, мой коллега Дмитрий Васильев рассказал в своей статье «Любовь, люди и роботы: как создать чат-бота, за которого не стыдно».
Почему решили всё переделать
Ранее работать в конструкторе могли только специально обученные сотрудники — сценаристы. Они знали структуру JSON и умели правильно вызывать навыки, но их было всего 5-7 человек. Дело в том, что онбординг новых сотрудников был сложным и занимал много времени. Сначала требовалось найти человека с техническими навыками, а затем обучить его. Поэтому создание нового бота занимало не менее недели.
![Старый конструктор чат-ботов Старый конструктор чат-ботов](https://habrastorage.org/getpro/habr/upload_files/670/797/3b4/6707973b44363aa2ec68453e768b9b12.png)
Со временем ботов становилось всё больше, а поддерживать их было уже сложно и дорого. Поэтому мы решили разработать no-code-решение, которое позволяет:
создавать ботов почти без привлечения программистов;
масштабироваться на всю компанию;
запускать новых ботов в течение одного дня.
Про проектирование и разработку MVP
Мы разбили задачу на несколько этапов, чтобы приблизить запуск проекта. Первым шагом протестировали решение на одном боте. Вторым — добавили недостающую функциональность, интеграции с другими сервисами и перенесли оставшихся ботов.
При проектировании учитывали требования бизнес-заказчиков, опыт и пожелания пользователей старой платформы, а также лучшие решения популярных аналогичных сервисов. В результате мы собрали MVP на компонентах дизайн-системы OZI и спустя год работы запустили его под названием Bots Factory.
OZI — дизайн-система, на которой строится более 500 продуктов, сервисов, админпанелей и мобильных приложений. Подробнее о дизайн-системах в Ozon рассказал мой коллега Виктор Теплов в статье «Автостопом по дизайн-системе. Путеводитель с оглавлением».
Знакомство с конструктором
Давайте поближе познакомимся с конструктором и посмотрим, что он собой представляет. Вначале мы попадаем на главную страницу со списком всех ботов, а затем переходим к конкретному.
Каждый бот состоит из основного сценария и дополнительных. Их может быть очень много, что позволяет гибко настраивать взаимодействие с пользователями. Чтобы найти нужный сценарий, можно воспользоваться поиском по названию или содержимому, а также сортировкой по дате создания.
На странице есть полная информация о боте, включая его системное название, интеграцию, уникальный ID и историю версий, которая позволяет отслеживать изменения в сценариях. Кроме того, пользователи могут опубликовать бота, протестировать его, скопировать, изменить название, экспортировать или удалить.
![Главная страница бота Главная страница бота](https://habrastorage.org/getpro/habr/upload_files/6cd/a36/f89/6cda36f89eecdfefb65bdd5c7ddd88b0.gif)
Выбрав бота, мы выбираем сценарий и переходим к редактору. Редактор — это место, где можно собрать сценарий из блоков. А блоки — это шаги сценария, соединённые между собой связями.
Остановимся сначала на редакторе. Он построен на основе функциональности канваса. При его проектировании были позаимствованы некоторые функции из 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.
![Редактор. Массовое выделение блоков Редактор. Массовое выделение блоков](https://habrastorage.org/getpro/habr/upload_files/c6b/dc1/36c/c6bdc136c997726694c9e107790aee84.png)
Теперь давайте подробнее рассмотрим доступные типы блоков, каждый из которых выполняет свои функции:
«Старт»
«Контент»
«Условие»
«Действие»
«Подсценарий».
Блок «Старт» есть во всех сценариях и является точкой входа.
Другой тип — «Контент». С его помощью можно собрать сообщение, которое мы отправим пользователю. Оно может содержать текст, изображение, документ и кнопки.
![Страница сценария. Блок «Контент» и его настройки Страница сценария. Блок «Контент» и его настройки](https://habrastorage.org/getpro/habr/upload_files/421/232/5ae/4212325ae5f2b68f1b4b906ccbe69295.png)
Блок «Условия» определяет, какие переходы будут выполнены в зависимости от значений переменных, используемых в сценарии. Например, если мы хотим понять, обращается ли клиент по обычному заказу, Global или Fresh.
Блок «Действия» нужен, чтобы вызывать навыки и работать с переменными. С помощью навыков бот получает информацию о клиентах и их заказах, а также выполняет определённые действия, такие как отмена заказа или изменение даты доставки.
И последний тип имеет говорящее само за себя название — «Подсценарий». Он играет роль портала в другой сценарий. Например, если срабатывает выражение #message.ButtonID == 8
, то бот перейдёт в сценарий «Аннуляция Ozon Fresh», а если #message.ButtonID == 9
, то в сценарий — «Аннуляция постомат OzonBox».
![Страница сценария. Блоки «Действия», «Условия» и «Подсценарий» Страница сценария. Блоки «Действия», «Условия» и «Подсценарий»](https://habrastorage.org/getpro/habr/upload_files/127/d7b/e2a/127d7be2a40e9bd437332546b053bd57.png)
Чтобы лучше понять, как это всё работает, давайте посмотрим на простой пример из жизни: пользователь не смог оплатить заказ и заходит в чат поддержки. Ему автоматически отвечает бот. Затем пользователь выбирает заказ, который его интересует.
![Сообщение клиенту в чате Сообщение клиенту в чате](https://habrastorage.org/getpro/habr/upload_files/8d3/967/050/8d3967050363ebcf289c38feb4acb412.png)
Бот проходит через несколько сценариев и на этапе «Условия» выясняет, по какому заказу задан вопрос. Затем он получает данные по нему, проверяет статус, а также наличие оплаты из других сервисов. После этого бот останавливается на блоке «Контент» и отвечает клиенту.
![Сообщение клиенту в конструкторе и чате Сообщение клиенту в конструкторе и чате](https://habrastorage.org/getpro/habr/upload_files/5cd/a85/482/5cda854823b5bcdea874c2ee091ebbb1.png)
Теперь, когда вы понимаете, как работает наш конструктор, я хочу рассказать о проблеме, с которой мы столкнулись в блоке «Условия» после запуска первого этапа.
Конструктор выражений
Напомню, что после переноса одного бота нам нужно было перенести остальных. Для этого потребовалось добавить новые выражения. Выражения — это специальные конструкции, которые определяют поведение бота в ответ на определённые входящие сообщения или действия пользователя.
В первой версии мы добавили как простые A == B
, так и часть функциональных fn(A)
. А чтобы добавить другие (например, fn(A) == B
, A == fn(B)
, fn(A, B) == C
), нужно было пересобрать весь конструктор выражений.
Выражения являются частью условия. Условий может быть несколько, и в этом случае они проверяются последовательно, пока одно из них не сработает. А выражения между собой сравниваются с помощью функций «и» и «или».
![Функции «и»/«или» между условиями и выражениями Функции «и»/«или» между условиями и выражениями](https://habrastorage.org/getpro/habr/upload_files/826/ddc/9d3/826ddc9d3a19844678d6e9bcbb21609e.png)
Давайте рассмотрим процесс создания выражений в первой версии. Чтобы добавить простое выражение, сначала выбираем переменную, значение которой нужно проверить.
![Было. Простое выражение, пустое Было. Простое выражение, пустое](https://habrastorage.org/getpro/habr/upload_files/48d/aba/78a/48daba78ab85502e1962892e3fa0203e.png)
Затем выбираем оператор:
Равно ==
Не равно !=
Больше >
Меньше <
Больше или равно >=
Меньше или равно <=
Есть вхождение строки fn(A)
Нет вхождения строки !fn(A)
Теперь — тип данных второго аргумента и его значение. Доступны следующие типы: число, строка, кнопка или содержимое другой переменной.
Аргумент в выражении — это значение или переменная, которые передаются функции или операции для выполнения вычислений. В контексте математических и логических выражений аргументы могут быть числами, переменными или другими выражениями.
Например, в математической функции
( f(x) = x^2 )
аргументом является( x )
. Когда вы подставляете значение для( x )
, например( 3 )
, вы получаете( f(3) = 3^2 = 9 )
.
![Было. Простое выражение, заполненное Было. Простое выражение, заполненное](https://habrastorage.org/getpro/habr/upload_files/8f3/2f3/8ff/8f32f38ff15c46673a6a0b182c2777cb.png)
Во второй версии мы изменили поведение конструктора, чтобы более гибко им управлять. Теперь при создании выражения сначала указываем его тип: простое «(x)
» или функциональное «fn
». Если выбрано простое выражение, то заполняем переменную и завершаем правую часть выражения.
![Стало. Простое выражение Стало. Простое выражение ](https://habrastorage.org/getpro/habr/upload_files/3c1/036/035/3c10360357b32cd80260afb0596f408e.png)
Если выбрано функциональное, то сначала выбираем функцию. Затем система автоматически достроит левую часть выражения. Останется лишь заполнить её и завершить правую часть.
![Стало. Функциональное выражение, len и jsonpath Стало. Функциональное выражение, len и jsonpath](https://habrastorage.org/getpro/habr/upload_files/3cc/d1b/ba8/3ccd1bba8261d821b0cba8e295220574.png)
Если функция будет булевой, то конструктор построит всё выражение целиком, и останется лишь заполнить недостающую информацию.
![Стало. Функциональное выражение, is_prev_ticket_complete Стало. Функциональное выражение, is_prev_ticket_complete](https://habrastorage.org/getpro/habr/upload_files/43a/7c6/297/43a7c629768a8b9945b48cffac9b81ac.png)
В функциональных выражениях функция может быть расположена в левой или правой части. Однако мы решили не перегружать конструктор и пользователей лишней логикой, поэтому ограничились её использованием только слева. Также мы зафиксировали ширину всех полей, но при этом ширина для значений больше, так как они могут быть довольно длинными.
![Стало. Несколько выражений в условие Стало. Несколько выражений в условие](https://habrastorage.org/getpro/habr/upload_files/2e2/ca7/a1e/2e2ca7a1ed32bc4416892a2903e73264.png)
Работа с функциями find_regexp и find_substring оказалась наиболее сложной среди функциональных выражений. Для создания выражения с ними сначала нужно было выбрать переменную, а затем операцию — «есть вхождение строки» или «нет вхождения строки». После нужно было заполнить значение и продублировать эту конструкцию столько раз, сколько значений необходимо добавить (20–30 раз).
![Было. Функциональное выражение Было. Функциональное выражение](https://habrastorage.org/getpro/habr/upload_files/c3a/fc0/e8f/c3afc0e8f6809121263f5a2713268b5e.png)
Мы исправили это, и теперь необходимо просто выбрать нужную функцию, переменную и ввести все значения в одно поле.
![Стало. Функциональное выражение, find_regexp Стало. Функциональное выражение, find_regexp](https://habrastorage.org/getpro/habr/upload_files/81c/2c7/c99/81c2c7c99542c525a6d88374d4e7f8bb.png)
Статусная модель
Поскольку мы хотели быстрее запуститься, некоторые из первоначальных решений мы сочли не самыми оптимальными, особенно это касалось статусной модели.
Статусная модель была неудобной и содержала много «лишних» этапов, которые выполняли формальные функции и не приносили значительной пользы. Например, когда пользователь вносит изменения в сценарий и хочет их опубликовать, ему сначала нужно сохранить их, подготовить к сборке, выполнить сборку, протестировать и только после этого перейти к этапу публикации.
Кроме того, статусная модель не позволяла нескольким пользователям одновременно работать над одним и тем же сценарием. Это становилось проблемой в случаях, когда требовалось срочно внести изменения в сценарий, над которым уже работал другой пользователь. В таких ситуациях приходилось обращаться за помощью к разработчикам.
Мы решили не изобретать свой велосипед, а частично позаимствовать логику работы с ветками из Git. Теперь каждый бот считается мастер-веткой, а его сценарии всегда находятся в статусе «Опубликован». Чтобы внести изменения в логику бота, нужно отвести ветку. Ветки полностью клонируют мастер, а их количество может быть неограниченным.
![Страница ветки Страница ветки](https://habrastorage.org/getpro/habr/upload_files/13d/cd1/cc2/13dcd1cc21212b105d179acf4821f4ba.png)
На странице ветки можно добавить новый сценарий или изменить существующие. Изменённые сценарии будут отмечены и перемещены вверх списка в статусе «Черновик».
![Признак, что в сценарий были внесены изменения Признак, что в сценарий были внесены изменения](https://habrastorage.org/getpro/habr/upload_files/58f/bbb/9bf/58fbbb9bf48894cd232c119c9d17c494.png)
После внесения изменений мы можем перейти к этапу тестирования, а затем — к публикации. Если за это время никто не вносил изменения в те же блоки в мастере, что и мы, то слияние пройдёт без проблем. Кроме того, нужно обязательно добавить комментарий, чтобы другие пользователи могли понять, что именно было изменено.
![Страница тестирования и публикации Страница тестирования и публикации](https://habrastorage.org/getpro/habr/upload_files/f2f/f96/228/f2ff96228fce092aaa1b8c7ccfbdd6d6.gif)
Рассмотрим другую ситуацию. Пока мы работали в своей ветке, наши коллеги внесли изменения в мастер. В этом случае система уведомит нас об этом. Теперь нужно обновить свою ветку, подтянув изменения из мастера.
![Уведомление, что версия мастера изменилась Уведомление, что версия мастера изменилась](https://habrastorage.org/getpro/habr/upload_files/74f/968/63c/74f96863c8ef0d0a3a4b18ede90d9626.png)
Если при этом коллеги вносили изменения в те же блоки в мастере, что и мы, то возникнут конфликты. Механика их разрешения совпадает с Git. На самом деле конфликты бывают редко, поскольку пользователи обычно не работают одновременно над одними и теми же сценариями. Но если всё же это случится, то система предупредит нас на этапе публикации. Появится модальное окно, и после нажатия на кнопку «Разрешить» мы попадём на страницу разрешения конфликтов.
![Модальное окно «Разрешение конфликтов» Модальное окно «Разрешение конфликтов»](https://habrastorage.org/getpro/habr/upload_files/c54/41a/bc8/c5441abc814a86615101366623e7063b.png)
На этой странице мы можем посмотреть, в каких сценариях возникли проблемы. Если конфликты не требуют глубокого изучения, то можно разрешить их все сразу, выбрав либо свои изменения, либо из мастера. После этого можно продолжить процесс публикации.
![Страница со всеми конфликтами Страница со всеми конфликтами](https://habrastorage.org/getpro/habr/upload_files/b4e/6ad/888/b4e6ad8880ff5b485732bc6084c3f2c5.gif)
Если конфликты сложные, то можно перейти в нужные блоки и изучить их. На странице конфликта мы видим сплит-панель: в левой части содержимое блока из нашей ветки, а в правой — из мастера. Тут у нас также есть выбор, что применить: свои изменения или из мастера. Однако может оказаться так, что ни те, ни другие изменения применить «как есть» нельзя. В таком случае можем перейти в конструктор, кликнув по ссылке «Редактировать», и внести нужные изменения вручную.
![Страница с конфликтом в блоке. JSON и визуальное представление Страница с конфликтом в блоке. JSON и визуальное представление](https://habrastorage.org/getpro/habr/upload_files/723/2dd/34c/7232dd34c9a517e844d3a468bcb1caf7.png)
Нужно отметить, что в первой версии мы не будем подсвечивать различия на визуальном представлении. Однако это возможно в режиме JSON для пользователей, которые с ним знакомы.
Из стандартных функций Git мы также позаимствовали возможность откатывать изменения до любой версии.
Результаты
Bots Factory в Ozon существует уже чуть больше года, и за это время количество пользователей увеличилось с менее 10 до более 100. Теперь, помимо команды сценаристов, со своими ботами работают разные отделы и направления компании: операторы, юристы, HR и другие.
На данный момент у нас уже есть 3 бота (2 внутренних и 1 внешний), созданные бизнесом всего за несколько дней. Кроме того, скорость разработки сценариев значительно увеличилась: ранее на изменение простого консультационного сценария уходило около 2 часов, а теперь эту задачу можно выполнить менее чем за 5 минут.
У нас много планов по дальнейшему масштабированию. Например, мы планируем добавить возможность дообучения моделей распознавания без участия разработчиков и создавать собственных LLM-агентов, обученных на наших базах знаний.