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 modules.nlu import NLU, Slot, UserAct
from modules.nlu import NLU, Slot, Act
from modules.state_monitor import DialogStateMonitor
from modules.generator import ResponseGenerator
from modules.strategy import DialoguePolicy
from modules.config import Config
import colorama
from colorama import Fore, Style
@ -10,13 +11,15 @@ colorama.init(autoreset=True)
def main():
print(Fore.CYAN + "Starting chatbot. Please wait...")
base_path = Path(__file__).resolve().parent
config_path = base_path / 'config' / 'config.json'
config = Config.load_config(config_path)
nlu = NLU()
dst = DialogStateMonitor()
generator = ResponseGenerator(config)
dp = DialoguePolicy()
generator = ResponseGenerator()
print(Fore.CYAN + "Witaj w chatbocie! Rozpocznij rozmowę.")
print(Fore.YELLOW + "Wpisz 'quit' aby zakończyć program.\n")
@ -28,13 +31,12 @@ def main():
break
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__":

View File

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

View File

@ -8,11 +8,41 @@ class ResponseGenerator:
def __init__(self, config: Config):
with config.responses_path.open('r', encoding='utf-8') as file:
self.responses: Dict[str, list] = json.load(file)
self.intent_to_response_key = {
"ask_name": "name_response",
"unknown": "unknown"
def nlg(self, system_act):
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:
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ć?"]))
return responses.get(intent, {}).get(slot, "Nieznane zapytanie.")

View File

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

View File

@ -1,5 +1,5 @@
import copy
from modules.nlu import UserAct
from modules.nlu import Act
import json
@ -39,11 +39,10 @@ class DialogStateMonitor:
def find_first_empty_slot(self):
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
def update(self, act: UserAct) -> None:
print(act)
def update(self, act: Act) -> None:
if act.intent == 'inform':
self.update_act(act.intent)
slots_mapping = {
@ -55,7 +54,7 @@ class DialogStateMonitor:
'email': []
}
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
for slot_name, values in slots_mapping.items():
@ -67,5 +66,7 @@ class DialogStateMonitor:
self.update_slot_names(slots_names)
elif act.intent == 'bye':
self.update_act(act.intent)
elif act.intent == 'unknown':
self.update_act(act.intent)
self.check_order_complete()

View File

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