Písanie telegramového robota v R (časť 4): Budovanie konzistentného, ​​logického dialógu s robotom

Ak ste už čítali predchádzajúce tri články z tejto série, potom už viete, ako písať plnohodnotné telegramové roboty pomocou klávesnice.

V tomto článku sa naučíme, ako napísať robota, ktorý bude udržiavať konzistentný dialóg. Tie. Robot vám položí otázky a počká, kým zadáte nejaké informácie. V závislosti od údajov, ktoré zadáte, robot vykoná niektoré akcie.

Aj v tomto článku sa dozvieme, ako používať databázu pod kapotou bota, v našom príklade to bude SQLite, ale môžete použiť akýkoľvek iný DBMS. Podrobnejšie som o interakcii s databázami v jazyku R písal v v tomto článku.

Písanie telegramového robota v R (časť 4): Budovanie konzistentného, ​​logického dialógu s robotom

Všetky články zo série „Písanie telegramového robota v R“

  1. Vytvárame robota a používame ho na odosielanie správ v telegrame
  2. Pridajte do robota podporu príkazov a filtre správ
  3. Ako pridať podporu klávesnice do robota
  4. Budovanie konzistentného, ​​logického dialógu s robotom

Obsah

Ak máte záujem o analýzu údajov, mohla by vás zaujímať moja telegram и youtube kanálov. Väčšina obsahu je venovaná jazyku R.

  1. Úvod
  2. Proces stavby botov
  3. Štruktúra projektu robota
  4. Konfigurácia robota
  5. Vytvorte premennú prostredia
  6. Vytvorenie databázy
  7. Zápis funkcií na prácu s databázou
  8. Bot metódy
  9. Filtre správ
  10. Psovodi
  11. Spúšťací kód robota
  12. Záver

Úvod

Aby si od vás robot vyžiadal údaje a počkal, kým zadáte akékoľvek informácie, budete musieť zaznamenať aktuálny stav dialógu. Najlepší spôsob, ako to urobiť, je použiť nejaký druh vstavanej databázy, ako je SQLite.

Tie. Logika bude nasledovná. Zavoláme metódu bot a bot od nás postupne požaduje nejaké informácie a v každom kroku čaká na zadanie týchto informácií a môže ich skontrolovať.

Napíšeme čo najjednoduchšieho bota, najprv sa vás opýta na meno, potom na vek a prijaté údaje uloží do databázy. Pri otázke na vek skontroluje, či zadaný údaj je číslo a nie text.

Takýto jednoduchý dialóg bude mať iba tri stavy:

  1. štart je normálny stav robota, v ktorom od vás neočakáva žiadne informácie
  2. wait_name – stav, v ktorom robot čaká na zadanie mena
  3. wait_age je stav, v ktorom robot čaká na zadanie vášho veku, počet celých rokov.

Proces stavby botov

Počas článku si krok za krokom postavíme robota, celý proces možno schematicky znázorniť takto:
Písanie telegramového robota v R (časť 4): Budovanie konzistentného, ​​logického dialógu s robotom

  1. Vytvoríme konfiguráciu bota, do ktorej uložíme niektoré nastavenia. V našom prípade token bota a cesta k súboru databázy.
  2. Vytvoríme premennú prostredia, v ktorej bude uložená cesta k projektu s botom.
  3. Vytvárame samotnú databázu a množstvo funkcií, aby s ňou robot mohol interagovať.
  4. Píšeme bot metódy, t.j. funkcie, ktoré bude vykonávať.
  5. Pridanie filtrov správ. Pomocou ktorých bude robot pristupovať k potrebným metódam v závislosti od aktuálneho stavu chatu.
  6. Pridávame obslužné programy, ktoré spoja príkazy a správy s potrebnými metódami botov.
  7. Spustíme robota.

Štruktúra projektu robota

Pre pohodlie rozdelíme kód nášho robota a ďalšie súvisiace súbory do nasledujúcej štruktúry.

  • bot.R — hlavný kód nášho robota
  • db_bot_function.R — blok kódu s funkciami na prácu s databázou
  • bot_methods.R — kód metód robotov
  • správy_filtre.R — filtre správ
  • manipulanti.R - manipulanti
  • config.cfg - konfigurácia robota
  • create_db_data.sql — SQL skript na vytvorenie tabuľky s údajmi chatu v databáze
  • create_db_state.sql — SQL skript na vytvorenie tabuľky aktuálneho stavu chatu v databáze
  • bot.db - databáza botov

Môžete si prezrieť celý projekt bota, príp k stiahnutiu z môjho úložisko na GitHub.

Konfigurácia robota

Ako konfiguráciu použijeme zvyčajný ini súbor, nasledujúci formulár:

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

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

V konfigurácii napíšeme bot token a cestu k databáze, t.j. do súboru bot.db, samotný súbor vytvoríme v ďalšom kroku.

Pre zložitejšie roboty môžete vytvárať zložitejšie konfigurácie, okrem toho nie je potrebné písať ini konfiguráciu, môžete použiť akýkoľvek iný formát vrátane JSON.

Vytvorte premennú prostredia

Na každom PC môže byť priečinok s projektom robota umiestnený v rôznych adresároch a na rôznych diskoch, takže v kóde bude cesta k priečinku projektu nastavená cez premennú prostredia TG_BOT_PATH.

Existuje niekoľko spôsobov, ako vytvoriť premennú prostredia, najjednoduchšie je zapísať ju do súboru .Renviron.

Tento súbor môžete vytvoriť alebo upraviť pomocou príkazu file.edit(path.expand(file.path("~", ".Renviron"))). Spustite ho a pridajte jeden riadok do súboru:

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

Ďalej súbor uložte .Renviron a reštartujte RStudio.

Vytvorenie databázy

Ďalším krokom je vytvorenie databázy. Budeme potrebovať 2 tabuľky:

  • chat_data — údaje, ktoré si robot vyžiadal od používateľa
  • chat_state — aktuálny stav všetkých chatov

Tieto tabuľky môžete vytvoriť pomocou nasledujúceho dotazu 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
);

Ak ste si stiahli projekt robota z GitHub, potom na vytvorenie databázy môžete použiť nasledujúci kód v 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'))

Zápis funkcií na prácu s databázou

Už máme pripravený konfiguračný súbor a vytvorenú databázu. Teraz musíte napísať funkcie na čítanie a zápis údajov do tejto databázy.

Ak ste si stiahli projekt z GitHub, potom nájdete funkcie v súbore db_bot_function.R.

Funkčný kód pre prácu s databázou

# ###########################################################
# 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]])

}

Vytvorili sme 4 jednoduché funkcie:

  • get_state() — získajte aktuálny stav chatu z databázy
  • set_state() — zapísať aktuálny stav chatu do databázy
  • get_chat_data() — prijímať údaje odoslané používateľom
  • set_chat_data() — zaznamenávať údaje prijaté od používateľa

Všetky funkcie sú pomerne jednoduché, buď načítajú dáta z databázy pomocou príkazu dbGetQuery()alebo sa zaviazať UPSERT operácie (zmena existujúcich údajov alebo zápis nových údajov do databázy), pomocou funkcie dbExecute().

Syntax operácie UPSERT je nasledovná:

INSERT INTO chat_data (chat_id, ${field})
VALUES(${chat_id}, '${value}') 
ON CONFLICT(chat_id) 
DO UPDATE SET ${field}='${value}';

Tie. v našom poli tabuľky chat_id má obmedzenie jedinečnosti a je primárnym kľúčom tabuliek. Najprv sa pokúsime pridať informácie do tabuľky a ak už existujú údaje pre aktuálny rozhovor, zobrazí sa chyba. V takom prípade jednoducho aktualizujeme informácie pre tento rozhovor.

Ďalej použijeme tieto funkcie v metódach a filtroch robotov.

Bot metódy

Ďalším krokom pri budovaní nášho robota je vytvorenie metód. Ak ste si stiahli projekt z GitHub, potom sú všetky metódy v súbore bot_methods.R.

Kód metódy robota

# ###########################################################
# 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')
  }

}

Vytvorili sme 5 metód:

  • štart — Spustenie dialógového okna
  • stav — Získajte aktuálny stav chatu
  • reset — Obnovenie aktuálneho stavu chatu
  • enter_name — Robot sa pýta na vaše meno
  • enter_age — Robot sa pýta na váš vek

metóda start požiada o vaše meno a zmení stav rozhovoru na wait_name, t.j. do pohotovostného režimu na zadanie vášho mena.

Ďalej odošlete meno a spracuje sa metódou enter_name, pozdraví vás bot, prijaté meno zapíše do databázy a prepne chat do stavu wait_age.

V tejto fáze robot očakáva, že zadáte svoj vek. Pošlete svoj vek, robot skontroluje správu, ak ste namiesto čísla poslali nejaký text, povie: Ты ввёл некорректные данные, введи числоa počká, kým znova zadáte svoje údaje. Ak ste poslali číslo, bot zahlási, že akceptoval váš vek, prijaté údaje zapíše do databázy, nahlási všetky od vás prijaté údaje a vráti stav chatu do pôvodnej polohy, t.j. V start.

Volaním metódy state môžete kedykoľvek požiadať o aktuálny stav chatu pomocou reset vrátiť chat do pôvodného stavu.

Filtre správ

V našom prípade je to jedna z najdôležitejších častí pri budovaní robota. Práve pomocou filtrov správ robot pochopí, aké informácie od vás očakáva a ako by mali byť spracované.

V projekte na GitHub filtre sú zaregistrované v súbore správy_filtre.R.

Kód filtra správ:

# ###########################################################
# 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"
}
)

Vo filtroch používame predtým napísanú funkciu get_state(), za účelom vyžiadania aktuálneho stavu chatu. Táto funkcia vyžaduje iba 1 argument, ID chatu.

Ďalší filter wait_name spracováva správy, keď je chat v stave wait_namea podľa toho aj filter wait_age spracováva správy, keď je chat v stave wait_age.

Psovodi

Súbor s obslužnými programami sa nazýva manipulanti.Ra má nasledujúci kód:

# ###########################################################
# 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)

Najprv vytvoríme obslužné nástroje príkazov, ktoré vám umožnia spustiť metódy na spustenie dialógu, jeho resetovanie a dotazovanie sa na aktuálny stav.

Ďalej vytvoríme 2 obslužné programy správ pomocou filtrov vytvorených v predchádzajúcom kroku a pridáme k nim filter !MessageFilters$command, aby sme mohli používať príkazy v akomkoľvek stave chatu.

Spúšťací kód robota

Teraz máme všetko pripravené na spustenie, hlavný kód na spustenie bota je v súbore 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()

V dôsledku toho sme dostali tohto robota:
Písanie telegramového robota v R (časť 4): Budovanie konzistentného, ​​logického dialógu s robotom

Kedykoľvek pomocou príkazu /state môžeme sa opýtať na aktuálny stav chatu a pomocou príkazu /reset vráťte chat do pôvodného stavu a znova začnite dialóg.

Záver

V tomto článku sme prišli na to, ako používať databázu vo vnútri robota a ako vytvárať postupné logické dialógy zaznamenávaním stavu chatu.

V tomto prípade sme sa pozreli na najprimitívnejší príklad, aby ste ľahšie pochopili myšlienku budovania takýchto robotov; v praxi môžete vytvoriť oveľa zložitejšie dialógy.

V ďalšom článku tejto série sa dozvieme, ako obmedziť práva používateľov botov na používanie rôznych jeho metód.

Zdroj: hab.com

Pridať komentár