在 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在內的任何其他格式。

建立環境變數

在每台電腦上,包含機器人專案的資料夾可以位於不同的目錄和不同的磁碟機上,因此在程式碼中將透過環境變數設定專案資料夾的路徑 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 將聊天恢復到原始狀態並再次開始對話。

結論

在本文中,我們了解如何在機器人內部使用資料庫,以及如何透過記錄聊天狀態來建立順序邏輯對話。

在本例中,我們查看了最原始的範例,以便您更容易理解建立此類機器人的想法;在實踐中,您可以建立更複雜的對話。

在本系列的下一篇文章中,我們將學習如何限制機器人使用者使用其各種方法的權利。

來源: www.habr.com

添加評論