用 R 编写电报机器人(第 4 部分):与机器人建立一致、逻辑的对话

如果您已经阅读了前面的内容 三篇文章 通过本系列,您已经知道如何使用键盘编写成熟的电报机器人。

在本文中,我们将学习如何编写一个保持一致对话的机器人。 那些。 机器人会问您问题并等待您输入一些信息。 根据您输入的数据,机器人将执行一些操作。

另外,在本文中,我们将学习如何在机器人的后台使用数据库,在我们的示例中它将是 SQLite,但您可以使用任何其他 DBMS。 我更详细地写了有关用 R 语言与数据库交互的文章 本文.

用 R 编写电报机器人(第 4 部分):与机器人建立一致、逻辑的对话

“用 R 编写电报机器人”系列的所有文章

  1. 我们创建一个机器人并用它来发送电报消息
  2. 向机器人添加命令支持和消息过滤器
  3. 如何向机器人添加键盘支持
  4. 与机器人建立一致、合乎逻辑的对话

内容

如果您对数据分析感兴趣,您可能会对我的文章感兴趣 电报 и YouTube的 渠道。 大部分内容专门介绍 R 语言。

  1. 介绍
  2. 机器人构建流程
  3. 机器人项目结构
  4. 机器人配置
  5. 创建环境变量
  6. 创建数据库
  7. 编写函数来处理数据库
  8. 机器人方法
  9. 消息过滤器
  10. 处理程序
  11. 机器人启动代码
  12. 结论

介绍

为了让机器人向您请求数据并等待您输入任何信息,您需要记录对话的当前状态。 最好的方法是使用某种嵌入式数据库,例如 SQLite。

那些。 逻辑如下。 我们调用机器人方法,机器人依次向我们请求一些信息,并且在每一步中它都会等待输入这些信息并可以检查它。

我们将编写最简单的机器人,首先它会询问您的姓名,然后是您的年龄,并将收到的数据保存到数据库中。 当询问年龄时,它会检查输入的数据是否是数字而不是文本。

这样一个简单的对话只有三种状态:

  1. 开始是机器人的正常状态,此时它不需要您提供任何信息
  2. wait_name - 机器人等待输入名称的状态
  3. wait_age 是机器人等待输入您的年龄(完整年数)的状态。

机器人构建流程

在本文中,我们将逐步构建一个机器人;整个过程可以示意性地描述如下:
用 R 编写电报机器人(第 4 部分):与机器人建立一致、逻辑的对话

  1. 我们创建一个机器人配置,在其中存储一些设置。 在我们的例子中,是机器人令牌和数据库文件的路径。
  2. 我们创建一个环境变量,其中将存储机器人项目的路径。
  3. 我们创建数据库本身以及许多函数,以便机器人可以与其交互。
  4. 我们编写机器人方法,即它将执行的功能。
  5. 添加消息过滤器。 借助它,机器人将根据聊天的当前状态访问必要的方法。
  6. 我们添加处理程序,将命令和消息与必要的机器人方法连接起来。
  7. 让我们启动机器人。

机器人项目结构

为了方便起见,我们将机器人的代码和其他相关文件划分为以下结构。

  • 机器人R — 我们机器人的主要代码
  • db_bot_function.R — 具有处理数据库功能的代码块
  • bot_methods.R — 机器人方法的代码
  • 消息过滤器.R — 消息过滤器
  • 处理程序.R - 处理程序
  • 配置文件 - 机器人配置
  • 创建数据库数据.sql — 用于在数据库中创建包含聊天数据的表的 SQL 脚本
  • 创建_db_state.sql — 用于在数据库中创建当前聊天状态表的 SQL 脚本
  • 机器人数据库 - 机器人数据库

您可以查看整个机器人项目,或者 下载 从我的 GitHub 上的存储库.

机器人配置

我们将使用常用的作为配置 ini文件,如下形式:

[bot_settings]
bot_token=ТОКЕН_ВАШЕГО_БОТА

[db_settings]
db_path=C:/ПУТЬ/К/ПАПКЕ/ПРОЕКТА/bot.db

在配置中,我们写入机器人令牌和数据库路径,即到 bot.db 文件;我们将在下一步中创建该文件本身。

对于更复杂的机器人,您可以创建更复杂的配置,此外,没有必要编写ini配置,您可以使用包括JSON在内的任何其他格式。

创建环境变量

在每台 PC 上,包含 bot 项目的文件夹可以位于不同的目录和不同的驱动器上,因此在代码中将通过环境变量设置项目文件夹的路径 TG_BOT_PATH.

创建环境变量有多种方法,最简单的就是将其写入文件中 .Renviron.

您可以使用以下命令创建或编辑此文件 file.edit(path.expand(file.path("~", ".Renviron")))。 执行它并向文件添加一行:

TG_BOT_PATH=C:/ПУТЬ/К/ВАШЕМУ/ПРОЕКТУ

接下来保存文件 .Renviron 并重新启动 RStudio。

创建数据库

下一步是创建数据库。 我们需要两张表:

  • 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'))

编写函数来处理数据库

我们已经准备好配置文件并创建数据库。 现在您需要编写函数来读取和写入该数据库的数据。

如果您从以下位置下载了该项目 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}';

那些。 在我们的表格字段中 聊天ID 具有唯一性约束,是表的主键。 最初,我们尝试向表中添加信息,如果当前聊天的数据已经存在,我们会收到错误消息,在这种情况下,我们只需更新此聊天的信息即可。

接下来,我们将在机器人的方法和过滤器中使用这些函数。

机器人方法

构建机器人的下一步是创建方法。 如果您从以下位置下载了该项目 GitHub上,那么所有方法都在文件中 bot_methods.R.

机器人方法代码

# ###########################################################
# bot methods

# start dialog
start <- function(bot, update) {

  # 

  # Send query
  bot$sendMessage(update$message$chat_id, 
                  text = "Введи своё имя")

  # переключаем состояние диалога в режим ожидания ввода имени
  set_state(chat_id = update$message$chat_id, state = 'wait_name')

}

# get current chat state
state <- function(bot, update) {

  chat_state <- get_state(update$message$chat_id)

  # Send state
  bot$sendMessage(update$message$chat_id, 
                  text = unlist(chat_state))

}

# reset dialog state
reset <- function(bot, update) {

  set_state(chat_id = update$message$chat_id, state = 'start')

}

# enter username
enter_name <- function(bot, update) {

  uname <- update$message$text

  # Send message with name
  bot$sendMessage(update$message$chat_id, 
                  text = paste0(uname, ", приятно познакомится, я бот!"))

  # Записываем имя в глобальную переменную
  #username <<- uname
  set_chat_data(update$message$chat_id, 'name', uname) 

  # Справшиваем возраст
  bot$sendMessage(update$message$chat_id, 
                  text = "Сколько тебе лет?")

  # Меняем состояние на ожидание ввода имени
  set_state(chat_id = update$message$chat_id, state = 'wait_age')

}

# enter user age
enter_age <- function(bot, update) {

  uage <- as.numeric(update$message$text)

  # проверяем было введено число или нет
  if ( is.na(uage) ) {

    # если введено не число то переспрашиваем возраст
    bot$sendMessage(update$message$chat_id, 
                    text = "Ты ввёл некорректные данные, введи число")

  } else {

    # если введено число сообщаем что возраст принят
    bot$sendMessage(update$message$chat_id, 
                    text = "ОК, возраст принят")

    # записываем глобальную переменную с возрастом
    #userage <<- uage
    set_chat_data(update$message$chat_id, 'age', uage) 

    # сообщаем какие данные были собраны
    username <- get_chat_data(update$message$chat_id, 'name')
    userage  <- get_chat_data(update$message$chat_id, 'age')

    bot$sendMessage(update$message$chat_id, 
                    text = paste0("Тебя зовут ", username, " и тебе ", userage, " лет. Будем знакомы"))

    # возвращаем диалог в исходное состояние
    set_state(chat_id = update$message$chat_id, state = 'start')
  }

}

我们创建了 5 个方法:

  • 开始——启动一个对话框
  • state——获取当前聊天状态
  • 重置 — 重置当前聊天状态
  • Enter_name — 机器人询问您的姓名
  • Enter_age — 机器人询问您的年龄

方法 start 询问您的姓名,并将聊天状态更改为 等待名称, IE。 等待输入您的姓名。

接下来,您发送名称并由该方法处理 enter_name,机器人向你打招呼,将收到的名字写入数据库,并将聊天切换到状态 等待时间.

在此阶段,机器人希望您输入年龄。 你发送你的年龄,机器人检查消息,如果你发送一些文本而不是数字,它会说: Ты ввёл некорректные данные, введи число,并将等待您重新输入数据。 如果您发送了一个号码,机器人将报告它已接受您的年龄,将收到的数据写入数据库,报告从您收到的所有数据并将聊天状态返回到原始位置,即V start.

通过调用方法 state 您可以随时请求当前的聊天状态,并使用 reset 将聊天恢复到原始状态。

消息过滤器

在我们的例子中,这是构建机器人最重要的部分之一。 在消息过滤器的帮助下,机器人将了解它期望从您那里获得哪些信息以及应如何处理这些信息。

在项目中 GitHub上 过滤器已注册在文件中 消息过滤器.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_age.

处理程序

带有处理程序的文件称为 处理程序.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,这样我们就可以在任何聊天状态下使用命令。

机器人启动代码

现在我们已经准备好启动了,启动机器人的主要代码在文件中 机器人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()

结果,我们得到了这个机器人:
用 R 编写电报机器人(第 4 部分):与机器人建立一致、逻辑的对话

随时使用命令 /state 我们可以查询当前的聊天状态,使用命令 /reset 将聊天恢复到原始状态并再次开始对话。

结论

在本文中,我们了解了如何在机器人内部使用数据库,以及如何通过记录聊天状态来构建顺序逻辑对话。

在本例中,我们查看了最原始的示例,以便您更容易理解构建此类机器人的想法;在实践中,您可以构建更复杂的对话。

在本系列的下一篇文章中,我们将学习如何限制机器人用户使用其各种方法的权利。

来源: habr.com

添加评论