Глава 4 Построение последовательного, логического диалога с ботом
В четвертой главе нашего руководства мы сосредоточимся на создании сложных и последовательных диалогов с вашим Telegram-ботом. Вы узнаете, как строить логические диалоги, которые позволят вашему боту взаимодействовать с пользователями более эффективно и естественно.
Мы рассмотрим, как организовать диалоговую логику, чтобы бот мог поддерживать связные и осмысленные беседы. Вы изучите методы управления состоянием диалога и обработки различных этапов общения, что сделает взаимодействие более плавным и интуитивно понятным.
Эта глава предоставит вам необходимые инструменты для создания интеллектуальных диалогов, улучшая опыт общения с вашим ботом. Надеюсь, что полученные знания помогут вам реализовать свои идеи и сделать ваш проект более успешным.
4.2 Конспект по разработке логического диалога с ботом
4.2.1 Введение
Для того, что бы бот мог запрашивать от вас данные, и ждать ввод какой-либо информации вам потребуется фиксировать текущее состояние диалога. Лучший способ это делать, использовать какую нибудь встраиваемую базу данных, например SQLite.
Т.е. логика будет следующей. Мы вызываем метод бота, и бот последовательно запрашивает у нас какую-то информацию, при этом на каждом шаге он ждёт ввод этой информации, и может осуществлять её проверку.
Мы напишем максимально простого бота, сначала он будет спрашивать ваше имя, потом возраст, полученные данные будет сохранять в базу данных. При запросе возраста будет проверять, что бы введённые данные были числом, а не текстом.
Такой простой диалог будет иметь всего три состояния: 1. start - обычное состояние бота, в котором он не ждёт от вас никакой информации 2. wait_name - состояние, при котором бот ожидает ввод имени 3. wait_age - состояние, при котором бот ожидает ввод вашего возраста, количество полных лет.
4.2.2 Процесс построения бота
- Создаём конфиг бота, в котором будем хранить некоторые настройки. В нашем случае токен бота, и путь к файлу базы данных.
- Создаём переменную среды, в которой будет хранится путь к проекту с ботом.
- Создаём саму базу данных, и ряд функций для того, что бы бот мог взаимодействовать с ней.
- Пишем методы бота, т.е. функции которые он будет выполнять.
- Добавляем фильтры сообщений. С помощью которых бот будет обращаться к нужным методам, в зависимости от текущего состояния чата.
- Добавляем обработчики, которые свяжут команды и сообщения с нужными методами бота.
- Запускаем бота.
4.2.3 Структура проекта бота
Для удобства мы разобъём код нашего бота, и прочие связанные с ним файлы на следующую структуру.
- bot.R - основной код нашего бота
- db_bot_function.R - блок кода с функциями для работы с базой данных
- bot_methods.R - код методов бота
- message_filters.R - фильтры сообщений
- handlers.R - обработчики
- config.cfg - конфиг бота
- create_db_data.sql - SQL скрипт создания таблицы с данными чата в базе данных
- create_db_state.sql - SQL скрипт создания таблицы текущего состояния чата в базе данных
- bot.db - база данных бота
Весь проект бота можно посмотреть, или скачать из моего репозитория на GitHub.
4.2.4 Конфиг бота
В качестве конфига мы будем использовать обычный ini файл, следующего вида:
[bot_settings]
bot_token=ТОКЕН_ВАШЕГО_БОТА
[db_settings]
db_path=C:/ПУТЬ/К/ПАПКЕ/ПРОЕКТА/bot.db
В конфиг мы записываем токен бота, и путь к базе данных, т.е. к файлу bot.db, сам файл мы будем создавать на следующем шаге.
Для более сложных ботов можно создавать и более сложные конфиги, к тому же необязательно писать именно ini конфиг, можете использовать любой другой формат включая JSON.
4.2.5 Создаём переменную среды
На каждом ПК папка с проектом бота может располагаться в разных директориях, и на разных дисках, поэтому в коде путь к папке проекта будет задан через переменную среды TG_BOT_PATH
.
Создать переменную среды можно несколькими способами, наиболее простой - прописать её в файле .Renviron.
Создать, или редактировать данный файл можно с помощью команды file.edit(path.expand(file.path("~", ".Renviron")))
. Выполните её и добавьте в файл одну строку:
TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ
Далее сохраните файл .Renviron и перезапустите RStudio.
4.2.6 Создаём базу данных
Следующий шаг - создание базы данных. Нам понадобится 2 таблицы:
- chat_data - данные которые бот запросил у пользователя
- chat_state - текущее состояние всех чатов
Создать эти таблицы можно с помощью следующего SQL запроса:
CREATE TABLE chat_data (
chat_id BIGINT PRIMARY KEY
UNIQUE,
name TEXT,
age INTEGER
);
CREATE TABLE chat_state (
chat_id BIGINT PRIMARY KEY
UNIQUE,
state TEXT
);
Если вы скачали проект бота с GitHub, то для создания базы можете воспользоваться следующим кодом на языке R.
# Скрипт создания базы данных
library(DBI) # интерфейс для работы с СУБД
library(configr) # чтение конфига
library(readr) # чтение текстовых SQL файлов
library(RSQLite) # драйвер для подключения к SQLite
# директория проекта
setwd(Sys.getenv('TG_BOT_PATH'))
# чтение конфига
cfg <- read.config('config.cfg')
# подключение к SQLite
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# Создание таблиц в базе
dbExecute(con, statement = read_file('create_db_data.sql'))
dbExecute(con, statement = read_file('create_db_state.sql'))
4.2.7 Пишем функции для работы с базой данных
У нас уже готов файл конфигурации и создана база данных. Теперь необходимо написать функции для чтения и записи данных в эту базу.
Если вы скачали проект из GitHub, то функции вы можете найти в файле db_bot_function.R.
# ###########################################################
# Function for work bot with database
# получить текущее состояние чата
get_state <- function(chat_id) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
chat_state <- dbGetQuery(con, str_interp("SELECT state FROM chat_state WHERE chat_id == ${chat_id}"))$state
return(unlist(chat_state))
dbDisconnect(con)
}
# установить текущее состояние чата
set_state <- function(chat_id, state) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# upsert состояние чата
dbExecute(con,
str_interp("
INSERT INTO chat_state (chat_id, state)
VALUES(${chat_id}, '${state}')
ON CONFLICT(chat_id)
DO UPDATE SET state='${state}';
")
)
dbDisconnect(con)
}
# запись полученных данных в базу
set_chat_data <- function(chat_id, field, value) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# upsert состояние чата
dbExecute(con,
str_interp("
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
")
)
dbDisconnect(con)
}
# read chat data
get_chat_data <- function(chat_id, field) {
con <- dbConnect(SQLite(), cfg$db_settings$db_path)
# upsert состояние чата
data <- dbGetQuery(con,
str_interp("
SELECT ${field}
FROM chat_data
WHERE chat_id = ${chat_id};
")
)
dbDisconnect(con)
return(data[[field]])
}
Мы создали 4 простые функции:
* get_state()
- получить текущее состояние чата из БД
* set_state()
- записать текущее состояние чата в БД
* get_chat_data()
- получить данные отправленные пользователем
* set_chat_data()
- записать данные полученные от пользователя
Все функции достаточно простые, они либо читают данные из базы с помощью команды dbGetQuery()
, либо совершают UPSERT
операцию (изменение существующих данных или запись новых данных в БД), с помощью функции dbExecute()
.
Синтаксис UPSERT операции выглядит следующим образом:
INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}')
ON CONFLICT(chat_id)
DO UPDATE SET ${field}='${value}';
Т.е. в наших таблицах поле chat_id имеет ограничение по уникальности и является первичным ключом таблиц. Изначально мы пробуем добавить информацию в таблицу, и получаем ошибку если данные по текущему чату уже присутствуют, в таком случае мы просто обновляем информацию по данному чату.
Далее эти функции мы будем использовать в методах и фильтрах бота.
4.2.8 Методы бота
Следующим шагом в построении нашего бота будет создание методов. Если вы скачали проект с GitHub, то все методы находятся в файле bot_methods.R.
# ###########################################################
# bot methods
# start dialog
start <- function(bot, update) {
#
# Send query
bot$sendMessage(update$from_chat_id(),
text = "Введи своё имя")
# переключаем состояние диалога в режим ожидания ввода имени
set_state(chat_id = update$from_chat_id(), state = 'wait_name')
}
# get current chat state
state <- function(bot, update) {
chat_state <- get_state(update$from_chat_id())
# Send state
bot$sendMessage(update$from_chat_id(),
text = unlist(chat_state))
}
# reset dialog state
reset <- function(bot, update) {
set_state(chat_id = update$from_chat_id(), state = 'start')
}
# enter username
enter_name <- function(bot, update) {
uname <- update$message$text
# Send message with name
bot$sendMessage(update$from_chat_id(),
text = paste0(uname, ", приятно познакомится, я бот!"))
# Записываем имя в глобальную переменную
#username <<- uname
set_chat_data(update$from_chat_id(), 'name', uname)
# Справшиваем возраст
bot$sendMessage(update$from_chat_id(),
text = "Сколько тебе лет?")
# Меняем состояние на ожидание ввода имени
set_state(chat_id = update$from_chat_id(), state = 'wait_age')
}
# enter user age
enter_age <- function(bot, update) {
uage <- as.numeric(update$effective_message()$text)
# проверяем было введено число или нет
if ( is.na(uage) ) {
# если введено не число то переспрашиваем возраст
bot$sendMessage(update$from_chat_id(),
text = "Ты ввёл некорректные данные, введи число")
} else {
# если введено число сообщаем что возраст принят
bot$sendMessage(update$from_chat_id(),
text = "ОК, возраст принят")
# записываем глобальную переменную с возрастом
#userage <<- uage
set_chat_data(update$from_chat_id(), 'age', uage)
# сообщаем какие данные были собраны
username <- get_chat_data(update$from_chat_id(), 'name')
userage <- get_chat_data(update$from_chat_id(), 'age')
bot$sendMessage(update$from_chat_id(),
text = paste0("Тебя зовут ", username, " и тебе ", userage, " лет. Будем знакомы"))
# возвращаем диалог в исходное состояние
set_state(chat_id = update$from_chat_id(), state = 'start')
}
}
Мы создали 5 методов:
- start - Запуск диалога
- state - Получить текущее состояние чата
- reset - Сбросить текущее состояние чата
- enter_name - Бот запрашивает ваше имя
- enter_age - Бот запрашивает ваш возраст
Метод start
запрашивает ваше имя, и переводит состояние чата в wait_name, т.е. в режим ожидания ввода вашего имени.
Далее, вы отправляете имя и оно обрабатывается методом enter_name
, бот с вами здоровается, записывает полученное имя в базу, и переводит чат в состояние wait_age.
На этом этапе бот ждёт от вас ввода вашего возраста. Вы отправляете ваш возраст, бот проверяет сообщение, если вы вместо числа отправили какой-то текст он скажет: Ты ввёл некорректные данные, введи число
, и будет ждать от вас повторного ввода данных. В случае если вы отправили число, бот сообщит о том, что он принял ваш возраст, запишет полученные данные в базу, сообщит все полученные от вас данные и переведёт состояние чата в исходное положение, т.е. в start
.
Вызвав метод state
вы в любой момент можете запросить текущее состояние чата, а методом reset
перевести чат в исходное состояние.
4.2.9 Фильтры сообщений
В нашем случае это одна из наиболее важных частей в построении бота. Именно с помощью фильтров сообщений бот будет понимать какую информацию он от вас ждёт, и как её надо обрабатывать.
В проекте на GitHub фильтры прописаны в файле message_filters.R.
Код фильтров сообщений:
# ###########################################################
# message state filters
# фильтр сообщений в состоянии ожидания имени
MessageFilters$wait_name <- BaseFilter(function(message) {
get_state( message$chat_id ) == "wait_name"
}
)
# фильтр сообщений в состоянии ожидания возраста
MessageFilters$wait_age <- BaseFilter(function(message) {
get_state( message$chat_id ) == "wait_age"
}
)
В фильтрах мы используем написанную ранее функцию get_state()
, для того, что бы запрашивать текущее состояние чата. Данна функция требует всего 1 аргумент, id чата.
Далее фильтр wait_name обрабатывает сообщения когда чат находится в состоянии wait_name
, и соответственно фильтр wait_age обрабатывает сообщения когда чат находится в состоянии wait_age
.
4.2.10 Обработчики
Файл с обработчиками называется handlers.R, и имеет следующий код:
# ###########################################################
# handlers
# command handlers
start_h <- CommandHandler('start', start)
state_h <- CommandHandler('state', state)
reset_h <- CommandHandler('reset', reset)
# message handlers
## !MessageFilters$command - означает что команды данные обработчики не обрабатывают,
## только текстовые сообщения
wait_age_h <- MessageHandler(enter_age, MessageFilters$wait_age & !MessageFilters$command)
wait_name_h <- MessageHandler(enter_name, MessageFilters$wait_name & !MessageFilters$command)
Сначала мы создаём обработчики команд, которые позволят вам запускать методы для начала диалога, его сброса, и запроса текущего состояния.
Далее мы создаём 2 обработчика сообщений с использованием созданных на прошлом шаге фильтров, и добавляем к ним фильтр !MessageFilters$command
, для того, что бы мы в любом состоянии чата могли использовать команды.
4.2.11 Код запуска бота
Теперь у нас всё готово к запуску, основной код запуска бота находится в файле bot.R.
library(telegram.bot)
library(tidyverse)
library(RSQLite)
library(DBI)
library(configr)
# переходим в папку проекта
setwd(Sys.getenv('TG_BOT_PATH'))
# читаем конфиг
cfg <- read.config('config.cfg')
# создаём экземпляр бота
updater <- Updater(cfg$bot_settings$bot_token)
# Загрузка компонентов бота
source('db_bot_function.R') # функции для работы с БД
source('bot_methods.R') # методы бота
source('message_filters.R') # фильтры сообщений
source('handlers.R') # обработчики сообщений
# Добавляем обработчики в диспетчер
updater <- updater +
start_h +
wait_age_h +
wait_name_h +
state_h +
reset_h
# Запускаем бота
updater$start_polling()
В любой момент с помощью команды /state
мы можем запрашивать текущее состояние чата, а с помощью команды /reset
переводить чат в исходное состояние и начинать диалог заново.
4.3 Заключение
Поздравляю с завершением работы над логическими диалогами! Ваш бот теперь способен проводить сложные и логически последовательные беседы с пользователями. В следующей главе мы сосредоточимся на управлении правами пользователей, что позволит вам контролировать доступ и функции вашего бота в зависимости от ролей и уровней доступа.
4.4 Тесты и задания
4.4.1 Тесты
Для закрепления материла рекомендую вам пройти тест доступный по ссылке.
4.4.2 Задания
- Постройте бота который будет поддерживать игру угадай число. Т.е. по команде
/start
бот будет загадывать число от 1 до 50. Далее у вас будет 5 попыток угадать это число.
Вы по очереди в каждой из попыток вводите числа, если введённое число меньше чем то, которое загадал бот то бот пишет “моё число больше”, иначе бот пишет “моё число меньше”. Если вы ввели правильное число то бот пишет что вы выйграли, и переводит диалог в исходное состояние.
Если вы всё сделали правильно, бот будет выглядеть так:
Победа с 5 попытки:
Пройгрыш