3 недели назад я написал статью о том, как я применяю chatGPT для практики разговорного английского. В ней я описал в общих чертах о том, какое задание я отправляю в API openai.com и как прикрутил к этому всему телеграм‑бота на удаленном сервере. Несколько человек спросили, не хочу ли я сделать такого бота не приватным. Я немного подумал и решил, что хочу, поэтому следующие 2 недели я вечерами дописывал бота вместо практики английского языка. В этой статье я расскажу о некоторых интересных вещах, которые я узнал в процессе работы над ботом.
Я снова пройдусь по верхам, не вдаваясь в детали кода, который, к тому же, я пока не стал публиковать.
Новые фичи
Логично было бы начать масштабирование бота с добавления учета пользователей, но мне подумалось, что несерьезно отдавать в народ продукт, который имеет в запасе только одно задание для языковой модели. Я вспомнил, чему учили меня зеленая сова и лингвистический лев, и начал добавлять активности.
Свободное сообщение. Если отправить любое сообщение без выбора из меню, оно направится языковой модели. По сути, это как обычный браузерный доступ к chatGPT, но я установил ограничение как на длину входящего сообщения, так и на длину генерации, так что сгенерировать в моем боте рассказ, дипломную работу или еще что‑нибудь длинное не выйдет.
Conversation. Тот самый основной режим. По нажатию кнопки пользователю предлагается выбрать тему для разговора, а языковой модели отправляется задание: задай вопрос на эту тему, проверь ответ на предмет грамматических ошибок и затем задай следующий вопрос. Здесь раскрывается вся мощь языковой модели — я устраивал себе технические интервью по python, а модель не только исправляла мои речевые ошибки, но и указывала на неточности в самих ответах и дополняла их.
Grammar. Здесь модель генерирует рандомные предложения на русском в любом времени и проверяет перевод пользователя на корректность. По ходу использования я понял, что когда‑нибудь потом стоить разделить задания и добавить генерацию предложений хотя бы в 3 основных временах: настоящем, прошедшем и будущем. Потому что чаще всего модель предлагает present simple, что не такое уж сложное испытание. Ко всему прочему наблюдается низкая вариативность предложений: не обходится ни одной тренировки без «Я люблю гулять по парку осенью» и «Кошка спит на окне».
Впрочем, по ходу общения я успешно просил бота давать более сложные преложения.
Listening. Оно же аудирование. Можно было бы попросить бота генерировать теперь фразы на английском и озвучивать их, но предыдущее задание показало, что нет смысла так делать. Поэтому я решил взять англоязычные книги, нарезать их по предложениям, а после по запросу брать случайный отрывок из списка и озвучивать его с помощью какой‑нибудь text‑to‑speech модели. Удивительно, но openai умеет только speech‑to‑text (который уже прикручен к распознаванию голосовых сообщений в чате), так что пришлось регистрировать проект в гугл клауде и отправлять отрывки туда.
Какие выбрать книги? Я взял ровно те же англоязычные книги, что стоят у меня на полке.
С сегментацией на предложения пришлось немного повозиться: хотя пакет nltk из коробки неплохо сегментирует текст, все же во фразах остается много мусора. Для выделения диалогов, прямой речи и прочих экспрессий использовались разные символы ("“”`’‘) и менялись от книги к книге. Плюс 3 вида дефисов и тире. При этом нельзя просто выкинуть пунктуацию, иначе при озвучке фраза сольется во что‑то монотонное и неразборчивое.
А еще те, кто читал «Оно» знают, что там главный герой заикается. Волевым решением я убрал все предложения с заиканиями из всех текстов, т.к. это нельзя ни озвучить, ни проверить нормально. Билл Денбро стал еще более молчаливым. А так же я оставил только средней длины фразы. В общем, классическая NLP‑рутина.
При проверке ответа пользователя и из исходной фразы, и из ответа уже убираются все не буквенные символы, фразы приводятся к нижнему регистру и матчатся. Понимая, что я сам многих слов не знаю, в этом режиме малодушно добавил на клавитару бота кнопки skip — пропустить, и show — показать фразу.
Vocabulary. Классическая тренировка слово — перевод. И снова — можно генерировать слова языковой моделью, а можно взять их из англо‑русских и русско‑английских словарей. Я нашел хорошо структурированный словарь В. К. Мюллера редакции 2013 года на 86 тысяч слов. Столько слов не знал даже Шекспир. Поэтому я взял те же книги, положил в Counter лемматизированные слова из каждой книги, отфильтровал по частоте, чтобы избавиться от общеупотребимых простых слов и имен, и нашел их и их переводы в словаре Мюллера.
Если судить по себе, то в англо‑русском словаре в моем мозге больше слов, чем в русско‑английском. Я могу без особых проблем читать английский текст и все понимать, но перевести аналогичный русскоязычный текст на английский уже проблема. Странно, что не во всех тренажерах, которые я пробовал, есть именно русско‑английские словарные пары. Поэтому я сделал их в своем боте. Для этого я прошелся в двойном цикле по каждому отобранному слову в англо‑русском словаре и по каждому варианту перевода слова нашел перевод в русско‑английском словаре.
Пользователь так же выбирает книгу, выбирает язык словаря, из которого будут выбираться случайные слова, и пытается перевести их на другой язык. Проверка — наличие перевода в списке возможных переводов для этого слова.
Иногда возможные переводы довольно неожиданные. Возможно, лучшим решением было бы найти русскоязычные тексты этих книг и аналогично отобрать слова из них.
В этом режиме я добавил кнопку пропуска слова и кое‑какой полезный функционал: при угадывании слова, оно кладется по ключу пользователя во внешнюю БД с меткой mistake = 0, а при ошибке или пропуске — mistake = 1. А к выбору книг я добавил опцию «My vocab», при выборе которой можно повторить уже изученные слова, где с вероятностью 0.3 будет даваться случайное ранее угаданное слово, и с вероятностью, соответственно, 0.7 — не угаданное.
Из не вошедшего. Хотелось сделать еще задание на понимание текста — аудирование или текстовое. Я просил языковую модель сгенерировать небольшой рассказ и после сгенерировать вопросы по этому рассказу с возможными ответами да/нет. И модель успешно это делала, но вопросы полностью повторяли предложения рассказа, что лишало задание всякого смысла. Например, в рассказе было: «Однажды, маленькая девочка темной ночью пошла за яблоками». Вопрос модели: «Правда ли, что маленькая девочка однажды ночью пошла за яблоками?». И пока мне не удалось заставить модель задавать чуть менее подробные вопросы, хотя бы такие: «Правда ли, что девочка пошла за яблоками?» или «Правда ли, что девочка ушла днем?».
Разработка
На самом деле, несмотря на то, что разработка новых фичей, встраивание их, программирование поведения бота по нажатию на кнопки и так далее — это самое сложное и самое объемное в проделанной работе, я не вижу смысла погружаться глубоко в детали реализации. Тем более, всегда можно сделать лучше.
Кратко скажу лишь, что бот теперь хранит в ОП текущее состояние общения с каждым пользователем: сообщения для языковой модели (для сохранения контекста), текущие книгу, фразу, словарь, язык словаря, слово в соответствующих режимах и сам тип тренировки. Все это сбрасывается по кнопке /reset.
В долговременной памяти во внешней БД хранятся айдишники пользователей, их баланс и их персональные словари.
Монетизация
Это тот вопрос, с которым я больше всего не хотел связываться. Все же, для прозрачности я решился оформить самозанятость и прикрутить к боту юкассу (как будто единственное, что работает с самозанятыми).
По юкассе стоит поделиться парой советов: во‑первых, в документации у них есть все, нужно только внимательно читать; во‑вторых, подключение тестовых платежей отличается от реальных. На тестовые платежи не действуют ограничения по длине описания, которые передаются в чеке. О таких ограничениях пришлось читать в документации. В‑третьих, самозанятым не нужно включать галочку авто‑отправки чеков в налоговую. Насколько я понял, это работает только при наличии арендованной кассы. А в функцию инвойса обязывает добавить данные пользователя. Так что самозанятым проще самим формировать и отправлять чеки в налоговую. В‑четвертых, есть ограничение по лимитам карт, не все платежи проходят.
Ошибки
Возможно, главная моя ошибка — публикация этой статьи. Я добавил начисление тестового баланса каждому новому пользователю, чтобы попробовать бота. И если вдруг статья станет популярной, то как бы не набежало много пользователей и не съело мой бюджет в аккаунте openai.
Еще стоило сразу поработать с неймингом. Видимо название EnglishChatBot привлекает иностранных пользователей telegram, которым этот бот, в общем‑то, не нужен.
Недостатки
Я так и не настроил webhook, потому что на самописный сертификат телеграм ругается. Возможно, в будущем я разберусь с этой проблемой. А пока работоспособность бота зависит от серверов самого телеграма.
А еще работоспособность зависит от openai. Бывают периоды высокой нагрузки, когда невозможно в адекватные сроки дождаться ответа от языковой модели. Но здесь я уже ни на что не могу повлиять.
Каждая генерация — это недетерминированный процесс, и на один и тот же запрос может прилетать разный ответ. Иногда это приводит к замыканию бота: он не понимает задание, которе ранее успешно понимал, теряет в креативности, а иногда в процессе диалога вместо следующего вопроса или фразы присылает сообщение с благодарностью за беседу. И что в таком случае делать дальше? Я жму /reset.
Выводы
То, что я делал для себя, как способ поработать с телеграм‑ботами, chatGPT и даже получить пользу в виде практики языка, вылилось в небольшой стартап.
Стоит заметить, что нельзя полностью доверять языковой модели свое обучение. Если уровень владения английским ниже A2 (примерно), то не стоит пользоваться этим ботом. Бот может не замечать ошибок, и чем длиннее ответ, тем вероятнее он их пропустит. Нужно, как минимум, знать правила построения предложений и времена, чтобы критично подходить к своим же ответам. Я рассматриваю этого бота как способ снять барьер при попытке заговорить на английском, но сомневаюсь, что бот может заменить живое общение.
Надеюсь, что теперь бот будет полезен не только мне.