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ó::.*?(có|đượ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ó::.*?(có|đượ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ó::.*?(có|đượ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ó::.*?(có|đượ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=