20/07/2019, 10:16

Xây dựng Chat Bot đơn giản theo kịch bản có sẵn (Phần 2)

Trong bài viết trước, mình đã chia sẻ với mọi người về ý tưởng xây dựng một Chatbot theo kịch bản có sẵn. Trong phần này, mình sẽ cùng mọi người từng bước để xây dựng 1 con Chatbot demo. Kịch bản demo Kịch bản gồm : Câu kích hoạt: câu hỏi chủ đề. Bot hỏi (nói). Người dùng trả lời (yêu ...

Trong bài viết trước, mình đã chia sẻ với mọi người về ý tưởng xây dựng một Chatbot theo kịch bản có sẵn. Trong phần này, mình sẽ cùng mọi người từng bước để xây dựng 1 con Chatbot demo.

Kịch bản demo

Kịch bản gồm :

  • Câu kích hoạt: câu hỏi chủ đề.
  • Bot hỏi (nói).
  • Người dùng trả lời (yêu cầu).
  • Bot Xử lý lấy kết quả.
0::start::.*?hợp đồng.*?điện::1
1::speak::Để phục vụ bạn chi tiết hơn, bạn vui lòng cung cấp thêm thông tin cá nhân.::36
2::choose::không::không|không.*?được|.{1,6}không.{1,6}::6::::.*?(|được|đồng ý|nhất trí).*?::3
3::speak::Mời bạn đọc số điện thoại::4
4::input::<NUMBER>::16
5::speak::Cảm ơn bạn::6
6::speak::Bạn cần chúng tôi tư vấn gì về hợp đồng mua bán điện? Thay đổi thông tin, Gia hạn, Chấm dứt, Tạm ngừng sử dụng điện, hay Cấp lại?::7
7::choose_req::thay đổi thông tin::.*?(thay|đổi|thay đổi).*?::24::gia hạn::.*?gia hạn.*?::24::chấm dứt::.*?chấm dứt.*?::24::tạm ngưng sử dụng điện::.*?tạm (ngừng|ngưng|dừng).*?::24::cấp lại::.*?cấp lại.*?::24
8::solve::<SHOW_MEET_TIME>::::35::::35
9::solve::<SAVE_DATE>::::33::::33
10::solve::<SAVE_TIME>::::8::::8
11::speak::Về việc Tạm ngừng sử dụng điện .. blah .. blah::13
12::speak::Về việc cấp lại hợp đồng điện .. blah .. blah::13
13::speak::Bạn cần mình tư vấn điều gì nữa không?::14
14::choose::không::không|không.*?được|.{1,6}không.{1,6}::15::::.*?(|được|đồng ý|nhất trí).*?::6
15::speak::Cảm ơn bạn, chúc bạn một ngày tốt lành!::-1
16::solve::<SAVE_NUMBER_PHONE>::<EXIST>::18::<NEW>::19
17::speak::ha ha::15
18::speak::Thông tin của bạn: <ATT>::5
19::speak::Bạn có sẵn sàng cung cấp địa chỉ/ khu vực bạn sinh sống không?::20
20::choose::không::không|không.*?được|.{1,6}không.{1,6}::6::::.*?(|được|đồng ý|nhất trí).*?::21
21::speak::Mời bạn đọc địa chỉ::22
22::input::<ADDRESS>::23
23::solve::<SAVE_ADDRESS>::<OK>::5
24::speak::Bạn muốn chúng tôi tư vấn thủ tục nào của vấn đề <ATT>?::25
25::speak::Kênh tiếp nhận, Hồ sơ, Chi phí hay Thời gian giải quyết?::26
26::choose_req::kênh tiếp nhận::.*?kênh tiếp nhận.*?::38::hồ sơ::.*?hồ sơ.*?::38::chi phí::.*?chi phí.*?::38::thời gian::.*?thời gian.*?::38
27::solve::<SHOW_REQUEST>::<EXIST>::28::<NEW>::28
28::speak::<ATT>::29
29::speak::Bạn có muốn đặt lịch hẹn không?::30
30::choose::không::không|không.*?được|.{1,6}không.{1,6}::13::::.*?(|được|đồng ý|nhất trí).*?::31
31::speak::Mời bạn chọn ngày::32
32::input::<DATE>::9
33::speak::Mời bạn chọn giờ::34
34::input::<TIME>::10
35::speak::Bạn đặt lịch hẹn vào <ATT>::13
36::speak::Trong trường hợp không cần thiết, bạn có thể lựa chọn Không để nhận được thông tin tổng quan.::37
37::speak::Bạn có sẵn sàng cung cấp số điện thoại không?::2
38::solve::<CHECK_INFO>::<EXITS>::27::<NEW>::27

Bot

Một Bot sẽ quản lý user, script, trả ra response cho người dùng khi script xử lý thông tin xong.

import sys
from script import Script
from flask import jsonify
from static import *
import time
from user import User

class Bot:
    def __init__(self):
        self.stop = False
        self.user = User()
        self.script = Script(self)
        self.list_speaks = []
        self.born_time = time.time()

    def reset_time(self):
        self.born_time = time.time()

    def check_over_time(self):
        if time.time() - self.born_time > SESSION_LIVE_TIME:
            return True
        return False

    def solved(self, prob, ok):
        pass

    def speak(self, sentence):
        return sentence
        # return jsonify({"answer": sentence})

    def listen(self):
        sentence = sys.stdin.readline()
        sentence = sentence[0:len(sentence) - 1]
        return sentence
        # return jsonify({"answer": sentence})

    def next_sentence(self, input=None):
        script = self.script
        if self.stop:
            return jsonify({"answer": "STOP"})
        node = script.get_node()
        if node is None:
            self.script.reset()
            return self.next_sentence()
        if not node.type == "speak":
            self.list_speaks = []
        if node.type == "speak":
            ans = self.speak(node.get_speak_sentence())
            script.next_node()
            self.list_speaks.append(ans)
            if script.get_node() is None or not script.get_node().type == "speak":
                return self.list_speaks
            return self.next_sentence()
        elif node.type == "start":
            if input is None:
                input = ""
            sentence = input
            if not node.check_question(sentence):
                ans = self.speak(START_CONVERSATION_SENTENCE)
                return ans
            else:
                script.next_node()
        elif node.type == "input":
            if input is None:
                return None
            input = input
            if node.check_input(input):
                script.next_node(input)
            else:
                return self.speak(ERROR_INPUT)
        elif node.type == "solve":
            answer = node.solve()
            self.solved(node.func, answer)
            script.next_node(answer)
        else:
            if input is None:
                return None
            input = node.select_a_choose(input.lower())
            # print(node.type, bot.user_request)
            if input is None:
                return self.speak(ERROR_CHOOSE)
            else:
                input = input
                if node.type == "choose_req":
                    self.user.set_request(self.user.request + (input + "::"))
                script.next_node(input)
        return self.next_sentence()

Script

Quản lý list_node -> kịch bản người dùng, chuyển trạng thái từ node này sang node khác.

from static import *
from node import MainQuestion, SpeakSentence, InputSentence, SolveCase, ChooseSentence

def get_type(raw):
    tmp = raw.split("::")
    return tmp[1]

class Script:
    def __init__(self, chat_bot):
        file_script = open(SCRIPT_FILE, "r")
        self.list_node = []
        self.chat_bot = chat_bot
        for line in file_script:
            raw_node = line[0:len(line) - 1]
            # print(raw_node)
            type = get_type(raw_node)
            if type == "start":
                node = MainQuestion(raw_node, chat_bot=chat_bot)
            elif type == "speak":
                node = SpeakSentence(raw_node, chat_bot=chat_bot)
            elif type == "input":
                node = InputSentence(raw_node, chat_bot=chat_bot)
            elif type == "solve":
                node = SolveCase(raw_node, chat_bot=chat_bot)
            else:
                node = ChooseSentence(raw_node, chat_bot=chat_bot, type=type)
            self.list_node.append(node)
        self.id_now = 0

    def get_start_node(self):
        if len(self.list_node) == 0:
            return None
        return self.list_node[0]

    def get_node(self):
        if self.id_now == -1:
            return None
        return self.list_node[self.id_now]

    def next_node(self, attribute=None):
        self.id_now = self.list_node[self.id_now].get_next()
        if self.get_node() is not None:
            self.get_node().set_attribute(attribute)

    def reset(self):
        self.__init__(self)

Node

Abstract Node : là một node trong script, set_next, get_next -> chuyển trạng thái trong script.

import checker
import services

class Node:
    def __init__(self, raw, chat_bot, type=None, id=-1):
        self.type = type
        self.id = id
        self.next = None
        self.raw = raw
        self.chat_bot = chat_bot
        if type == "input":
            self.raw = self.raw.lower()
        self.id_next = -1
        self.attribute = None

    def set_next(self, id_next):
        self.id_next = id_next

    def set_attribute(self, att=None):
        self.attribute = att

    def get_next(self):
        return self.id_next
  • Node dạng Speak
class SpeakSentence(Node):
    def __init__(self, raw, chat_bot):
        super().__init__(raw=raw,  chat_bot=chat_bot, type="speak")
        tmp = raw.split("::")
        self.set_next(int(tmp[len(tmp) - 1]))

    def get_speak_sentence(self):
        ans = self.raw.split("::")[2]
        if self.attribute is not None:
            ans = ans.replace("<ATT>", self.attribute)
        return ans

  • Node dạng Chọn yêu cầu
class ChooseSentence(Node):
    def __init__(self, raw, chat_bot, type="choose"):
        super().__init__(raw=raw, chat_bot=chat_bot,type=type)
        self.list_chooses = []
        tmp = raw.split("::")[2:len(raw)]
        # print(raw)
        for i in range(0, len(tmp), 3):
            self.list_chooses.append((tmp[i], tmp[i+1], int(tmp[i + 2])))

    def select_a_choose(self, input):
        for choose in self.list_chooses:
            if checker.check_choose(input, choose[1]):
                self.set_next(choose[2])
                return choose[0]
        return None

  • Node dạng nhập input
class InputSentence(Node):
    def __init__(self, raw, chat_bot):
        super().__init__(raw=            
0