working chatbot

This commit is contained in:
Anna Śmigiel 2024-06-11 18:10:51 +02:00
parent 253320ce1d
commit abdfe9d1b3
6 changed files with 75 additions and 41 deletions

View File

@ -1,7 +1,8 @@
from pathlib import Path from pathlib import Path
from modules.nlu import NLU, Slot, UserAct from modules.nlu import NLU, Slot, Act
from modules.state_monitor import DialogStateMonitor from modules.state_monitor import DialogStateMonitor
from modules.generator import ResponseGenerator from modules.generator import ResponseGenerator
from modules.strategy import DialoguePolicy
from modules.config import Config from modules.config import Config
import colorama import colorama
from colorama import Fore, Style from colorama import Fore, Style
@ -10,13 +11,15 @@ colorama.init(autoreset=True)
def main(): def main():
print(Fore.CYAN + "Starting chatbot. Please wait...")
base_path = Path(__file__).resolve().parent base_path = Path(__file__).resolve().parent
config_path = base_path / 'config' / 'config.json' config_path = base_path / 'config' / 'config.json'
config = Config.load_config(config_path) config = Config.load_config(config_path)
nlu = NLU() nlu = NLU()
dst = DialogStateMonitor() dst = DialogStateMonitor()
generator = ResponseGenerator(config) dp = DialoguePolicy()
generator = ResponseGenerator()
print(Fore.CYAN + "Witaj w chatbocie! Rozpocznij rozmowę.") print(Fore.CYAN + "Witaj w chatbocie! Rozpocznij rozmowę.")
print(Fore.YELLOW + "Wpisz 'quit' aby zakończyć program.\n") print(Fore.YELLOW + "Wpisz 'quit' aby zakończyć program.\n")
@ -28,13 +31,12 @@ def main():
break break
user_act = nlu.analyze(user_input) user_act = nlu.analyze(user_input)
# user_act = UserAct(intent='inform',
# slots=[Slot(name='item', value='laptop'), Slot(name='item', value='kot'),Slot(name='address', value='123 Main St')])
dst.update(user_act)
print(dst.state)
# response = generator.generate(intent)
# print(Fore.CYAN + "Bot: " + response) dst.update(user_act)
system_action = dp.next_action(dst)
response = generator.nlg(system_action)
print(Fore.CYAN + "Bot: " + response)
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -70,6 +70,8 @@ def conllu2flair_slot(sentences, label=None):
def predict_frame(model, sentence, label_type): def predict_frame(model, sentence, label_type):
if not sentence:
return 'unknown'
csentence = [{'form': word, 'slot': 'O'} for word in sentence] csentence = [{'form': word, 'slot': 'O'} for word in sentence]
fsentence = conllu2flair([csentence])[0] fsentence = conllu2flair([csentence])[0]
model.predict(fsentence) model.predict(fsentence)
@ -84,6 +86,8 @@ def predict_frame(model, sentence, label_type):
def predict_slot(model, sentence, label_type): def predict_slot(model, sentence, label_type):
if not sentence:
return {'form': '', 'slot': 'unknown'},
csentence = [{'form': word, 'slot': 'O'} for word in sentence] csentence = [{'form': word, 'slot': 'O'} for word in sentence]
fsentence = conllu2flair([csentence])[0] fsentence = conllu2flair([csentence])[0]
model.predict(fsentence) model.predict(fsentence)
@ -112,10 +116,9 @@ class Model:
trainset = list(parse_incr(f, fields=['id', 'form', 'frame', 'slot'], field_parsers=field_parsers)) trainset = list(parse_incr(f, fields=['id', 'form', 'frame', 'slot'], field_parsers=field_parsers))
with open(self.test_dataset, encoding='utf-8') as f: with open(self.test_dataset, encoding='utf-8') as f:
testset = list(parse_incr(f, fields=['id', 'form', 'frame', 'slot'], field_parsers=field_parsers)) testset = list(parse_incr(f, fields=['id', 'form', 'frame', 'slot'], field_parsers=field_parsers))
print('TRAINSET:', trainset)
corpus = Corpus(train=conllu2flair(trainset, label_type), test=conllu2flair(testset, label_type)) corpus = Corpus(train=conllu2flair(trainset, label_type), test=conllu2flair(testset, label_type))
label_dictionary = corpus.make_label_dictionary(label_type=label_type) label_dictionary = corpus.make_label_dictionary(label_type=label_type)
print('LABEL:' ,label_dictionary)
embedding_types = [ embedding_types = [
WordEmbeddings('pl'), WordEmbeddings('pl'),
FlairEmbeddings('pl-forward'), FlairEmbeddings('pl-forward'),

View File

@ -8,11 +8,41 @@ class ResponseGenerator:
def __init__(self, config: Config): def __init__(self, config: Config):
with config.responses_path.open('r', encoding='utf-8') as file: with config.responses_path.open('r', encoding='utf-8') as file:
self.responses: Dict[str, list] = json.load(file) self.responses: Dict[str, list] = json.load(file)
self.intent_to_response_key = {
"ask_name": "name_response", def nlg(self, system_act):
"unknown": "unknown" intent = system_act.intent
slot = system_act.slots[0].name if system_act.slots else None
responses = {
"inform": {
"item": "Nasz sklep oferuje szeroki wybór artykułów, takich jak\n artykuły spożywcze,\n ogrodowe\n oraz kosmetyki. Proszę podaj produkt.",
"address": "Nasz sklep nie ma fizycznego adresu. To sklep internetowy.",
"delivery_method": "Dostępne formy dostawy: INPOST, DPD, DHL.",
"payment_method": "Dostępne formy płatności: Karta, przy odbiorze",
"email": "Obsługa klienta: cs2137@gmail.com",
"card_nr": random.choice([
"Dzięki karcie rabatowej zbierasz punkty, które poźniej przekładają się na rabat.",
"Dzisiaj z kartą rabatową meble ogrodowe 200zł taniej!",
"Dzisiaj z kartą rabatową szampony 2 w cenie 1!"
])
},
"canthelp": {
"unknown": random.choice([
"Przepraszam, nie rozumiem polecenia...",
"Możesz powtórzyć?",
"Powiedz proszę jeszcze raz.."
])
},
"request": {
"card_nr": "Podaj proszę numer karty rabatowej",
"address": "Podaj proszę adres do wysyłki",
"item": "Jaki produkt chcesz kupić?",
"email": "Podaj proszę email.",
"delivery_method": "Jaką formą dostawy jesteś zainteresowany?",
"payment_method": "Jaką formą płatności jesteś zainteresowany?"
},
"bye": "Miłego dnia!",
"confirmation": "Zamówienie zostało złożone!"
} }
def generate(self, response_key: str) -> str: return responses.get(intent, {}).get(slot, "Nieznane zapytanie.")
response_key = self.intent_to_response_key.get(response_key, "unknown")
return random.choice(self.responses.get(response_key, ["Przepraszam, nie rozumiem. Możesz to powtórzyć?"]))

View File

@ -17,7 +17,7 @@ class Slot:
return f"Name: {self.name}, Value: {self.value}" return f"Name: {self.name}, Value: {self.value}"
class UserAct: class Act:
def __init__(self, intent: str, slots: list[Slot] = []): def __init__(self, intent: str, slots: list[Slot] = []):
self.slots = slots self.slots = slots
self.intent = intent self.intent = intent
@ -62,6 +62,4 @@ class NLU:
def analyze(self, text: str): def analyze(self, text: str):
intent = self.get_intent(text) intent = self.get_intent(text)
slots = self.get_slot(text) slots = self.get_slot(text)
print({'intent': intent, return Act(intent=intent, slots=slots)
'slots': slots})
return UserAct(intent=intent, slots=slots)

View File

@ -1,5 +1,5 @@
import copy import copy
from modules.nlu import UserAct from modules.nlu import Act
import json import json
@ -39,11 +39,10 @@ class DialogStateMonitor:
def find_first_empty_slot(self): def find_first_empty_slot(self):
for slot_name, slot_value in self.state['belief_state'].items(): for slot_name, slot_value in self.state['belief_state'].items():
if slot_name != 'order-completed' and self.is_value_empty(slot_value): if slot_name != 'order-completed' and slot_value in [None, '', [], {}]:
return slot_name return slot_name
def update(self, act: UserAct) -> None: def update(self, act: Act) -> None:
print(act)
if act.intent == 'inform': if act.intent == 'inform':
self.update_act(act.intent) self.update_act(act.intent)
slots_mapping = { slots_mapping = {
@ -55,7 +54,7 @@ class DialogStateMonitor:
'email': [] 'email': []
} }
for slot in act.slots: for slot in act.slots:
if slot.name in slots_mapping and self.is_value_empty(self.state, slot.name): if slot.name in slots_mapping and self.is_value_empty(self.state['belief_state'], slot.name):
slots_mapping[slot.name].append(slot.value) # To do: normalization slots_mapping[slot.name].append(slot.value) # To do: normalization
for slot_name, values in slots_mapping.items(): for slot_name, values in slots_mapping.items():
@ -67,5 +66,7 @@ class DialogStateMonitor:
self.update_slot_names(slots_names) self.update_slot_names(slots_names)
elif act.intent == 'bye': elif act.intent == 'bye':
self.update_act(act.intent) self.update_act(act.intent)
elif act.intent == 'unknown':
self.update_act(act.intent)
self.check_order_complete() self.check_order_complete()

View File

@ -1,24 +1,24 @@
from modules.nlu import UserAct, Slot from modules.nlu import Act, Slot
from numpy.random.mtrand import random import random
class DialoguePolicy: class DialoguePolicy:
def __init__(self, dst):
self.dialogue_state = dst.state
def next_action(self): def next_action(self, dst):
if not self.dialogue_state['belief_state']['order-complete']: if not dst.state['belief_state']['order-complete']:
user_intent = self.dialogue_state['act'] user_intent = dst.state['act']
if user_intent == "inform": if user_intent == "inform":
empty_slot = dst.find_first_empty_slot() empty_slot = dst.find_first_empty_slot()
return UserAct(intent="request", slots=[Slot(name=empty_slot, value='')]) return Act(intent="request", slots=[Slot(name=empty_slot, value='')])
if user_intent == "request": elif user_intent == "request":
if self.dialogue_state['slot_names']: if dst.state['slot_names']:
slot = random.choice(self.state['slot_names']) slot = random.choice(dst.state['slot_names'])
return UserAct(intent="inform", slots=[Slot(name=slot, value='')]) return Act(intent="inform", slots=[Slot(name=slot, value='')])
else: else:
return UserAct(intent="inform", slots=[Slot(name='unknown', value='')]) return Act(intent="canthelp", slots=[Slot(name='unknown', value='')])
if user_intent == "bye": elif user_intent == "bye":
return UserAct(intent="bye", slots=[Slot(name='', value='')]) return Act(intent="bye", slots=[Slot(name='', value='')])
else:
return Act(intent="canthelp", slots=[Slot(name='unknown', value='')])
else: else:
return UserAct(intent= "inform", slots=[Slot(name='confirmation', value='Zamówienie złożono poprawnie.')]) return Act(intent="inform", slots=[Slot(name='confirmation', value='Zamówienie złożono poprawnie.')])