Poprawki do systemu, złożenie wszystkich modułów w całość

This commit is contained in:
s495727 2024-06-13 15:25:52 +02:00
parent 41f41cc692
commit 10fc617cad
9 changed files with 598 additions and 500 deletions

View File

@ -1,39 +1,40 @@
{ {
"size": ["M", "L", "XL"],
"dough": [ "dough": [
"thick" "thick"
], ],
"drink": { "drink": {
"woda": {
"price": 5
},
"pepsi": { "pepsi": {
"price": 10 "price": 7
}, },
"cola": { "cola": {
"price": 10 "price": 8
},
"water": {
"price": 5
} }
}, },
"food": [ "food": [
"pizza" "pizza"
], ],
"meat": [ "meat": [
"chicken", "kurczak",
"ham", "szynka",
"tuna" "tuna"
], ],
"sauce": [ "sauce": [
"garlic", "garlic",
"1000w" "1000w"
], ],
"ingredient": { "ingredients": {
"chicken": {}, "kurczak": {},
"tuna": {}, "tuna": {},
"pineapple": {}, "ananas": {},
"onion": {}, "cebula": {},
"cheese": {}, "ser": {},
"tomato": {}, "pomidor": {},
"ham": {}, "szynka": {},
"pepper": {} "papryka": {}
}, },
"menu": [ "menu": [
"capri", "capri",

View File

@ -13,7 +13,8 @@ language_generation = NaturalLanguageGeneration(templates) # NLG
def frame_to_dict(frame): def frame_to_dict(frame):
return { return {
"act": frame.act, "act": frame.act,
"slots": [{"name": slot.name, "value": slot.value} for slot in frame.slots] "slots": [{"name": slot.name, "value": slot.value} for slot in frame.slots],
"act_understood": frame.act_understood,
} }
@ -23,8 +24,8 @@ print("CTRL+C aby zakończyć program.")
while True: while True:
monitor.reset() monitor.reset()
# print(f"\n==== Rozpoczynasz rozmowę nr {dial_num} ====\n") print(f"\n==== Rozpoczynasz rozmowę nr {dial_num} ====\n")
user_input = input("Witamy w naszej pizzerii. W czym mogę pomóc?\n") user_input = input("Witamy w naszej pizza-przez-internet. W czym mogę pomóc?\n")
while True: while True:
# NLU # NLU
@ -36,15 +37,20 @@ while True:
# DP # DP
system_action = dialog_policy.predict(monitor) system_action = dialog_policy.predict(monitor)
system_action = frame_to_dict(system_action) # Ensure system_action is a dictionary system_action_dict = frame_to_dict(system_action) # Ensure system_action is a dictionary
print("System action: ", system_action) print("System action: ", system_action_dict)
# NLG # NLG
response = language_generation.generate(frame, system_action) response = language_generation.generate(frame, system_action_dict)
print(response) print(response)
if system_action.act == "bye_and_thanks" or system_action.act == "bye":
print(monitor.print_order())
break
if frame.act == "bye": if frame.act == "bye":
print(monitor.print_order())
break break
user_input = input(">\n") user_input = input(">\n")
# dial_num += 1 dial_num += 1

View File

@ -1,10 +1,11 @@
from .slot import Slot from .slot import Slot
class Frame: class Frame:
def __init__(self, source: str, act: str, slots: list[Slot] = []): def __init__(self, source: str, act: str, slots: list[Slot] = [], act_understood = None):
self.source = source self.source = source
self.slots = slots self.slots = slots
self.act = act self.act = act
self.act_understood = act_understood
def __str__(self): def __str__(self):
msg = f"Act: {self.act}, Slots: [" msg = f"Act: {self.act}, Slots: ["

View File

@ -3,26 +3,37 @@ from model.frame import Frame
from model.slot import Slot from model.slot import Slot
class DialogPolicy: class DialogPolicy:
def _predict(self, dsm):
def predict(self, dsm):
last_frame = dsm.state['history'][-1] last_frame = dsm.state['history'][-1]
act_processed = dsm.state['was_system_act_processed']
if(dsm.state['was_previous_order_invalid'] == False): if(dsm.state['was_previous_order_invalid'] == False):
if("inform" in last_frame.act): if last_frame.act == "inform/order-complete":
act = last_frame.act
elif ("inform" in last_frame.act):
act = last_frame.act.split('/')[0] act = last_frame.act.split('/')[0]
else: else:
act = last_frame.act act = last_frame.act
match(act): match(act):
case "bye":
return Frame(source="system", act = "bye")
case "inform/order-complete":
return Frame(source="system", act = "bye_and_thanks")
case "inform" | "affirm" | "negate": case "inform" | "affirm" | "negate":
current_active_status = dsm.get_current_active_stage() current_active_stage = dsm.get_current_active_stage()
match(current_active_status): if current_active_stage == None:
return Frame(source="system", act = "bye_and_thanks")
match(current_active_stage['name']):
case "collect_food": case "collect_food":
return Frame(source="system", act = "request/food") # current_active_stage['confirmed'] = True
return Frame(source="system", act = "request/food", slots = [Slot("menu", dsm.state['constants']['menu'])], act_understood=act_processed)
case "collect_drinks": case "collect_drinks":
return Frame(source="system", act = "request/drinks") return Frame(source="system", act = "request/drinks", slots = [Slot("drink", dsm.state['constants']['drink'])], act_understood=act_processed)
case "collect_address": case "collect_address":
return Frame(source="system", act = "request/address") return Frame(source="system", act = "request/address", act_understood=act_processed)
if(current_active_status == None): case "collect_payment_method":
return Frame(source="system", act = "end") return Frame(source="system", act = "request/payment-method", act_understood=act_processed)
case "collect_phone":
return Frame(source="system", act = "request/phone", act_understood=act_processed)
case "request/menu": case "request/menu":
return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])]) return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])])
case "request/price": case "request/price":
@ -41,7 +52,12 @@ class DialogPolicy:
return Frame(source="system", act = "inform", slots = [Slot("drink", dsm.state['constants']['drink'])]) return Frame(source="system", act = "inform", slots = [Slot("drink", dsm.state['constants']['drink'])])
case "welcomemsg": case "welcomemsg":
return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])]) return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])])
case "bye": case "repeat":
return Frame(source="system", act = "bye") return Frame(source="system", act = "repeat")
return Frame(source="system", act = "repeat") return Frame(source="system", act = "repeat")
def predict(self, dsm):
frame = self._predict(dsm)
dsm.state["system_history"].append(frame)
return frame

View File

@ -1,11 +1,19 @@
from src.model.frame import Frame from model.frame import Frame
import copy import copy
import json import json
def normalize(value): def normalize(value):
value = value.lower() value = value.lower()
# TODO: pomyslec nad odmianą slów if value[-1] in [",", "!", "?" ,"." ,")" ,"(",")"]:
value = value[:-1]
if value[-2:] == "ie":
value = value[:-2] + "a"
if value[-1] in ["ę", "ą", "y"]:
value = value[:-1] + "a"
if value in ["pizz", "pizzy", "pizze", "picce"]:
value = "pizza"
return ' '.join(value.split()) return ' '.join(value.split())
@ -13,7 +21,8 @@ class DialogStateMonitor:
def __init__(self, initial_state_file: str = 'attributes.json'): def __init__(self, initial_state_file: str = 'attributes.json'):
with open(initial_state_file) as file: with open(initial_state_file) as file:
constants = json.load(file) constants = json.load(file)
self.__initial_state = dict(belief_state={ self.__initial_state = dict(
belief_state={
'order': [], 'order': [],
'address': {}, 'address': {},
'order-complete': False, 'order-complete': False,
@ -25,26 +34,59 @@ class DialogStateMonitor:
}, },
total_cost=0, total_cost=0,
stages=[ stages=[
{'completed': False, 'name': 'collect_food'}, {'completed': False, 'name': 'collect_food', "confirmed": False},
{'completed': False, 'name': 'collect_drinks'}, {'completed': False, 'name': 'collect_drinks', "confirmed": False},
{'completed': False, 'name': 'collect_address'}, {'completed': False, 'name': 'collect_address', "confirmed": False},
{'completed': False, 'name': 'collect_phone', "confirmed": False},
], ],
was_previous_order_invalid=False, was_previous_order_invalid=False,
was_system_act_processed=False,
constants=constants, constants=constants,
history=[]) history=[],
system_history=[]
)
self.state = copy.deepcopy(self.__initial_state) self.state = copy.deepcopy(self.__initial_state)
def get_current_active_stage(self) -> str | None: def get_current_active_stage(self) -> str | None:
for stage in self.state['stages']: for stage in self.state['stages']:
if stage['completed'] is False: if stage['completed'] is False:
return stage['name'] print("Current stage: ", stage['name'])
return stage
self.state['belief_state']['order-complete'] = True
def mark_current_stage_completed(self) -> None: def mark_current_stage_completed(self) -> None:
for stage in self.state['stages']: for stage in self.state['stages']:
if stage['completed'] is False: if stage['completed'] is False: # and stage['confirmed']:
print("Stage completed: ", stage['name'])
stage['completed'] = True stage['completed'] = True
return return
def complete_stage_if_valid(self, stage_name):
for stage in self.state['stages']:
if stage['name'] != stage_name:
continue
if stage['name'] == "collect_food":
for order in self.state['belief_state']['order']:
if order.get("pizza"):
stage['completed'] = True
return
elif stage["name"] == "collect_drinks":
for order in self.state['belief_state']['order']:
if order.get("drink"):
stage['completed'] = True
return
elif stage["name"] == "collect_address":
if not len(self.state['belief_state']['address']):
return
stage['completed'] = True
return
elif stage["name"] == "collect_phone":
if self.state['belief_state']["phone"].get("phone"):
stage['completed'] = True
return
pass
def item_exists(self, type: str, name: str) -> bool: def item_exists(self, type: str, name: str) -> bool:
return normalize(name) in self.state['constants'][type] return normalize(name) in self.state['constants'][type]
@ -54,7 +96,29 @@ class DialogStateMonitor:
def get_total_cost(self) -> int: def get_total_cost(self) -> int:
return self.state['total_cost'] return self.state['total_cost']
def slot_augmentation(self, slot, value):
drink_normalize = ["woda", "pepsi", "cola", "coca cola", "cole", "coca"]
if slot.name in ["food", "pizza", "ingredient"]:
if value in drink_normalize:
slot.name = 'drink'
return slot
def slot_valid(self, slot, act):
if act == "inform/order":
if slot.name in ["address", "payment-method", 'delivery', 'phone', 'time', 'name']:
return False
return True
def value_valid(self, slot_name, value):
if slot_name == "food":
if value != "pizza":
return False
return True
def update(self, frame: Frame) -> None: def update(self, frame: Frame) -> None:
self.state['was_system_act_processed'] = False
belief_state_copy = copy.deepcopy(self.state["belief_state"])
self.state['history'].append(frame) self.state['history'].append(frame)
if frame.source != 'user': if frame.source != 'user':
return return
@ -62,27 +126,40 @@ class DialogStateMonitor:
new_order = dict() new_order = dict()
for slot in frame.slots: for slot in frame.slots:
value = normalize(slot.value) value = normalize(slot.value)
if (slot.name == 'pizza' and self.get_current_active_stage() == 'collect_food') or (slot.name == 'drink' and self.get_current_active_stage() == 'collect_drinks'): slot = self.slot_augmentation(slot, value)
if not self.slot_valid(slot, frame.act):
continue
if not self.value_valid(slot.name, value):
continue
stage_name = self.get_current_active_stage()['name']
is_collect_food = (slot.name == 'pizza' and stage_name == 'collect_food')
is_collect_drinks = (slot.name == 'drink' and stage_name == 'collect_drinks')
if is_collect_food or is_collect_drinks:
if self.item_exists(slot.name, value) is False: if self.item_exists(slot.name, value) is False:
self.state['was_previous_order_invalid'] = True self.state['was_previous_order_invalid'] = True
return return
self.state['was_previous_order_invalid'] = False self.state['was_previous_order_invalid'] = False
self.state['total_cost'] += self.state['constants'][slot.name][value]['price'] self.state['total_cost'] += self.state['constants'][slot.name][value]['price']
self.mark_current_stage_completed()
new_order[slot.name] = value new_order[slot.name] = value
if len(new_order) > 0:
self.state['belief_state']['order'].append(new_order) self.state['belief_state']['order'].append(new_order)
elif frame.act == 'inform/address' and self.get_current_active_stage() == 'collect_address': self.complete_stage_if_valid('collect_food')
self.complete_stage_if_valid('collect_drinks')
elif frame.act == 'inform/address':
for slot in frame.slots: for slot in frame.slots:
self.state['belief_state']['address'][slot.name] = normalize(slot.value) self.state['belief_state']['address'][slot.name] = normalize(slot.value)
self.mark_current_stage_completed() self.complete_stage_if_valid('collect_address')
elif frame.act == 'inform/phone': elif frame.act == 'inform/phone':
for slot in frame.slots: for slot in frame.slots:
self.state['belief_state']['phone'][slot.name] = normalize(slot.value) self.state['belief_state']['phone'][slot.name] = normalize(slot.value)
self.complete_stage_if_valid('collect_phone')
elif frame.act == 'inform/order-complete': elif frame.act == 'inform/order-complete':
self.state['belief_state']['order-complete'] = True self.state['belief_state']['order-complete'] = True
elif frame.act == 'inform/delivery': elif frame.act == 'inform/delivery':
for slot in frame.slots: for slot in frame.slots:
self.state['belief_state']['delivery'][slot.name] = normalize(slot.value) self.state['belief_state']['delivery'][slot.name] = normalize(slot.value)
self.complete_stage_if_valid('collect_address')
elif frame.act == 'inform/payment': elif frame.act == 'inform/payment':
for slot in frame.slots: for slot in frame.slots:
self.state['belief_state']['payment'][slot.name] = normalize(slot.value) self.state['belief_state']['payment'][slot.name] = normalize(slot.value)
@ -92,6 +169,16 @@ class DialogStateMonitor:
elif frame.act == 'inform/name': elif frame.act == 'inform/name':
for slot in frame.slots: for slot in frame.slots:
self.state['belief_state']['name'][slot.name] = normalize(slot.value) self.state['belief_state']['name'][slot.name] = normalize(slot.value)
elif frame.act == 'negate':
if "request" in self.state["system_history"][-1].act:
self.mark_current_stage_completed()
self.state['was_system_act_processed'] = True
if self.state["belief_state"] != belief_state_copy and frame.act not in ["repeat", 'affirm', 'negate']:
self.state['was_system_act_processed'] = True
def reset(self) -> None: def reset(self) -> None:
self.state = copy.deepcopy(self.__initial_state) self.state = copy.deepcopy(self.__initial_state)
def print_order(self) -> dict:
return json.dumps(self.state['belief_state'])

View File

@ -91,10 +91,13 @@ class NaturalLanguageUnderstanding():
def predict(self, text: str): def predict(self, text: str):
if not self.use_mocks: if not self.use_mocks:
try:
act = self.__predict_intention(text) act = self.__predict_intention(text)
slots = self.__predict_slot(text) slots = self.__predict_slot(text)
frame = Frame(source = 'user', act = act, slots = slots) frame = Frame(source = 'user', act = act, slots = slots)
return frame return frame
except:
return Frame(source="user", act = "repeat", slots=[])
else: else:
frames = [ frames = [
Frame(source="user", act = "inform/order", slots=[Slot(name="pizza", value="barcelona")]), Frame(source="user", act = "inform/order", slots=[Slot(name="pizza", value="barcelona")]),

View File

@ -27,6 +27,10 @@ class NaturalLanguageGeneration:
slot_dict[slot['name']] = slot['value'] slot_dict[slot['name']] = slot['value']
response = template.format(**slot_dict) response = template.format(**slot_dict)
if system_action['act_understood'] == True:
response = f"Zrozumiałem. {response}"
elif system_action['act_understood'] == False:
response = f"Nie zrozumiałem. {response}"
return response return response
def parse_frame(frame): def parse_frame(frame):
@ -37,14 +41,3 @@ def parse_frame(frame):
slots = [{"name": slot.name, "value": slot.value} for slot in frame.slots] slots = [{"name": slot.name, "value": slot.value} for slot in frame.slots]
return act, slots return act, slots
class Slot:
def __init__(self, name, value):
self.name = name
self.value = value
class Frame:
def __init__(self, act, slots):
self.act = act
self.slots = slots

View File

@ -32,39 +32,32 @@ def generate_ingredients_response(slots):
ingredient_list = [] ingredient_list = []
for ingredient in ingredients: for ingredient in ingredients:
ingredient_list.extend(ingredient.keys()) ingredient_list.extend(ingredient.keys())
response = f"Składniki to: {', '.join(ingredient_list)}." response = f"Dostępne składniki to: {', '.join(ingredient_list)}."
return response return response
return "Nie podano składników." return "Nie podano składników."
def generate_drinks_response(slots): def generate_drinks_response(slots):
drinks = [slot['value'] for slot in slots if slot['name'] == 'drink'] drinks = [slot['value'] for slot in slots if slot['name'] == 'drink']
print(slots)
print(drinks)
if drinks: if drinks:
drink_details = [] drink_details = []
for drink in drinks: for drink in drinks:
for name, details in drink.items(): for name, details in drink.items():
price = details.get('price', 'unknown') # price = details.get('price', 'unknown')
drink_details.append(f"{name} w cenie {price}") drink_details.append(f"{name}") # w cenie {price} zł")
if len(drink_details) > 1: if len(drink_details) > 1:
response = f"Dostępne napoje to: {', '.join(drink_details[:-1])} oraz {drink_details[-1]}." response = f"Czy chcesz coś do picia? Dostępne napoje to: {', '.join(drink_details[:-1])} oraz {drink_details[-1]}."
else: else:
response = f"Dostępne napoje to: {drink_details[0]}." response = f"Czy chcesz coś do picia? Dostępne napoje to: {drink_details[0]}."
return response return response
return "Nie podano napojów." return "Nie podano napojów."
def generate_size_response(slots): def generate_size_response(slots):
sizes = [slot['value'] for slot in slots if slot['name'] == 'size'] sizes = [slot['value'] for slot in slots if slot['name'] == 'size'][0]
if sizes: if len(sizes) != 0:
size_details = [] return F"Dostępne rozmiary to: {', '.join(sizes)}"
for size in sizes:
for name, details in size.items():
rozmiar = details.get('rozmiar', 'unknown')
size_details.append(f"{name} o średnicy {rozmiar} cm")
if len(size_details) > 1:
response = f"Dostępne rozmiary to: {', '.join(size_details[:-1])} oraz {size_details[-1]}."
else:
response = f"Dostępne rozmiary to: {size_details[0]}."
return response
return "Nie podano rozmiarów." return "Nie podano rozmiarów."
def generate_sauce_response(slots): def generate_sauce_response(slots):
@ -96,6 +89,8 @@ def select_template(act, slots):
return generate_size_response(slots) return generate_size_response(slots)
elif "price" in slot_names: elif "price" in slot_names:
return random.choice(templates["inform/price"]) return random.choice(templates["inform/price"])
elif "food" in slot_names:
return random.choice(templates["inform/menu"])
if act == "inform": if act == "inform":
if "menu" in slot_names: if "menu" in slot_names:
@ -160,11 +155,21 @@ def select_template(act, slots):
elif act == "request/drinks": elif act == "request/drinks":
return random.choice(templates["request/drinks"]) return random.choice(templates["request/drinks"])
elif act == "request/food": elif act == "request/food":
return random.choice(templates["request/food"]) return random.choice(templates["inform/menu"])
elif act == "inform/name": elif act == "inform/name":
return random.choice(templates["inform/name"]) return random.choice(templates["inform/name"])
elif act == "bye": elif act == "bye":
return random.choice(templates["bye"]) return random.choice(templates["bye"]) # TODO force end?
elif act == "bye_and_thanks":
return random.choice(templates["bye_and_thanks"])
elif act == "repeat":
return random.choice(templates["repeat"])
elif act == "request/address":
return random.choice(templates["request/address"])
elif act == "request/payment-method":
return random.choice(templates["request/payment-method"])
elif act == "request/phone":
return random.choice(templates["request/phone"])
return None return None

View File

@ -8,10 +8,6 @@ templates = {
], ],
"inform/menu": [ "inform/menu": [
"Oferujemy następujące pizze: {menu}.", "Oferujemy następujące pizze: {menu}.",
"Nasze pizze to: {menu}.",
"W naszym menu znajdziesz pizze: {menu}.",
"Dostępne pizze to: {menu}.",
"Proszę, oto lista dostępnych pizz: {menu}."
], ],
"inform/name": [ "inform/name": [
"Twoje imię to {name}.", "Twoje imię to {name}.",
@ -86,10 +82,6 @@ templates = {
"Koszt dostawy: {delivery-price} zł." "Koszt dostawy: {delivery-price} zł."
], ],
"request/menu": [ "request/menu": [
"Oto nasze menu: {menu}.",
"Nasze menu obejmuje: {menu}.",
"Proszę, oto lista dostępnych dań: {menu}.",
"Dostępne dania to: {menu}.",
"W naszym menu znajdziesz: {menu}." "W naszym menu znajdziesz: {menu}."
], ],
"inform/time": [ "inform/time": [
@ -99,26 +91,11 @@ templates = {
"Godzina: {time}.", "Godzina: {time}.",
"Obecny czas: {time}." "Obecny czas: {time}."
], ],
"request/drinks": [
"Jakie napoje chciałbyś zamówić?",
"Proszę wybrać napoje do zamówienia.",
"Jakie napoje dołączamy do zamówienia?",
"Co chciałbyś pić?",
"Proszę podać napoje do zamówienia."
],
"welcomemsg": [ "welcomemsg": [
"Witaj w naszej wspaniałej pizzerii. W czym mogę pomóc?", "Witaj w naszej wspaniałej pizzerii. W czym mogę pomóc?",
"Halo, halo, tu najlepsza pizza w mieście. Masz głoda?", "Halo, halo, tu najlepsza pizza w mieście. Masz głoda?",
"Dzieńdoberek, gdyby wszyscy jedli nasze pizze, na świecie nie byłoby wojen. Jaką pizzę sobie dziś gruchniesz?", "Dzieńdoberek, gdyby wszyscy jedli nasze pizze, na świecie nie byłoby wojen. Jaką pizzę sobie dziś gruchniesz?",
], ],
"request/menu": [
"W naszym menu znajdują się pizze, spaghetti, gnocci oraz aranchini. Polecam potrawkę śląską po grecku.",
"Smażymy, gotujemy, prażymy, ale najlepiej nam wychodzi pizza. Na co masz ochotę?",
],
"request/drink": [
"Oferujemy napoje zimne, ciepłe i letnie. Cola, fanta, woda mineralna, kawa, herbata lub frappe.",
"Może z alkoholem? Mamy świeżo warzone piwo",
],
"request/price": [ "request/price": [
"Cena za {food} wynosi {price} zł.", "Cena za {food} wynosi {price} zł.",
"Koszt {food} to {price} zł.", "Koszt {food} to {price} zł.",
@ -172,9 +149,18 @@ templates = {
"Niestety, nie mamy {ingredient/neg}." "Niestety, nie mamy {ingredient/neg}."
], ],
"default/template": [ "default/template": [
"Przepraszamy, ale nie rozumiemy Twojego zapytania.", "Nie zrozumiałem, spróbuj inaczej sformułować zdanie."
"Proszę spróbować ponownie później.", ],
"Nie rozpoznajemy Twojej prośby, spróbuj ponownie.", "repeat": [
"Strasznie szybko to napisałeś, nie zrozumiałem...." "Nie zrozumiałem, spróbuj inaczej sformułować zdanie.",
],
"request/address": [
"Jaki adres dostawy?"
],
"bye_and_thanks": [
"Dziękujemy za zamówienie. Do zobaczenia!",
],
"request/phone": [
"Podaj proszę numer telefonu do kontaktu dla kuriera."
] ]
} }