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": [
"thick"
],
"drink": {
"woda": {
"price": 5
},
"pepsi": {
"price": 10
"price": 7
},
"cola": {
"price": 10
},
"water": {
"price": 5
"price": 8
}
},
"food": [
"pizza"
],
"meat": [
"chicken",
"ham",
"kurczak",
"szynka",
"tuna"
],
"sauce": [
"garlic",
"1000w"
],
"ingredient": {
"chicken": {},
"ingredients": {
"kurczak": {},
"tuna": {},
"pineapple": {},
"onion": {},
"cheese": {},
"tomato": {},
"ham": {},
"pepper": {}
"ananas": {},
"cebula": {},
"ser": {},
"pomidor": {},
"szynka": {},
"papryka": {}
},
"menu": [
"capri",

View File

@ -13,7 +13,8 @@ language_generation = NaturalLanguageGeneration(templates) # NLG
def frame_to_dict(frame):
return {
"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:
monitor.reset()
# print(f"\n==== Rozpoczynasz rozmowę nr {dial_num} ====\n")
user_input = input("Witamy w naszej pizzerii. W czym mogę pomóc?\n")
print(f"\n==== Rozpoczynasz rozmowę nr {dial_num} ====\n")
user_input = input("Witamy w naszej pizza-przez-internet. W czym mogę pomóc?\n")
while True:
# NLU
@ -36,15 +37,20 @@ while True:
# DP
system_action = dialog_policy.predict(monitor)
system_action = frame_to_dict(system_action) # Ensure system_action is a dictionary
print("System action: ", system_action)
system_action_dict = frame_to_dict(system_action) # Ensure system_action is a dictionary
print("System action: ", system_action_dict)
# NLG
response = language_generation.generate(frame, system_action)
response = language_generation.generate(frame, system_action_dict)
print(response)
if system_action.act == "bye_and_thanks" or system_action.act == "bye":
print(monitor.print_order())
break
if frame.act == "bye":
print(monitor.print_order())
break
user_input = input(">\n")
# dial_num += 1
dial_num += 1

View File

@ -1,10 +1,11 @@
from .slot import Slot
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.slots = slots
self.act = act
self.act_understood = act_understood
def __str__(self):
msg = f"Act: {self.act}, Slots: ["

View File

@ -3,26 +3,37 @@ from model.frame import Frame
from model.slot import Slot
class DialogPolicy:
def predict(self, dsm):
def _predict(self, dsm):
last_frame = dsm.state['history'][-1]
act_processed = dsm.state['was_system_act_processed']
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]
else:
act = last_frame.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":
current_active_status = dsm.get_current_active_stage()
match(current_active_status):
current_active_stage = dsm.get_current_active_stage()
if current_active_stage == None:
return Frame(source="system", act = "bye_and_thanks")
match(current_active_stage['name']):
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":
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":
return Frame(source="system", act = "request/address")
if(current_active_status == None):
return Frame(source="system", act = "end")
return Frame(source="system", act = "request/address", act_understood=act_processed)
case "collect_payment_method":
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":
return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])])
case "request/price":
@ -41,7 +52,12 @@ class DialogPolicy:
return Frame(source="system", act = "inform", slots = [Slot("drink", dsm.state['constants']['drink'])])
case "welcomemsg":
return Frame(source="system", act = "inform", slots = [Slot("menu", dsm.state['constants']['menu'])])
case "bye":
return Frame(source="system", act = "bye")
case "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 json
def normalize(value):
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())
@ -13,7 +21,8 @@ class DialogStateMonitor:
def __init__(self, initial_state_file: str = 'attributes.json'):
with open(initial_state_file) as file:
constants = json.load(file)
self.__initial_state = dict(belief_state={
self.__initial_state = dict(
belief_state={
'order': [],
'address': {},
'order-complete': False,
@ -25,26 +34,59 @@ class DialogStateMonitor:
},
total_cost=0,
stages=[
{'completed': False, 'name': 'collect_food'},
{'completed': False, 'name': 'collect_drinks'},
{'completed': False, 'name': 'collect_address'},
{'completed': False, 'name': 'collect_food', "confirmed": False},
{'completed': False, 'name': 'collect_drinks', "confirmed": False},
{'completed': False, 'name': 'collect_address', "confirmed": False},
{'completed': False, 'name': 'collect_phone', "confirmed": False},
],
was_previous_order_invalid=False,
was_system_act_processed=False,
constants=constants,
history=[])
history=[],
system_history=[]
)
self.state = copy.deepcopy(self.__initial_state)
def get_current_active_stage(self) -> str | None:
for stage in self.state['stages']:
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:
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
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:
return normalize(name) in self.state['constants'][type]
@ -54,7 +96,29 @@ class DialogStateMonitor:
def get_total_cost(self) -> int:
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:
self.state['was_system_act_processed'] = False
belief_state_copy = copy.deepcopy(self.state["belief_state"])
self.state['history'].append(frame)
if frame.source != 'user':
return
@ -62,27 +126,40 @@ class DialogStateMonitor:
new_order = dict()
for slot in frame.slots:
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:
self.state['was_previous_order_invalid'] = True
return
self.state['was_previous_order_invalid'] = False
self.state['total_cost'] += self.state['constants'][slot.name][value]['price']
self.mark_current_stage_completed()
new_order[slot.name] = value
if len(new_order) > 0:
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:
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':
for slot in frame.slots:
self.state['belief_state']['phone'][slot.name] = normalize(slot.value)
self.complete_stage_if_valid('collect_phone')
elif frame.act == 'inform/order-complete':
self.state['belief_state']['order-complete'] = True
elif frame.act == 'inform/delivery':
for slot in frame.slots:
self.state['belief_state']['delivery'][slot.name] = normalize(slot.value)
self.complete_stage_if_valid('collect_address')
elif frame.act == 'inform/payment':
for slot in frame.slots:
self.state['belief_state']['payment'][slot.name] = normalize(slot.value)
@ -92,6 +169,16 @@ class DialogStateMonitor:
elif frame.act == 'inform/name':
for slot in frame.slots:
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:
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):
if not self.use_mocks:
try:
act = self.__predict_intention(text)
slots = self.__predict_slot(text)
frame = Frame(source = 'user', act = act, slots = slots)
return frame
except:
return Frame(source="user", act = "repeat", slots=[])
else:
frames = [
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']
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
def parse_frame(frame):
@ -37,14 +41,3 @@ def parse_frame(frame):
slots = [{"name": slot.name, "value": slot.value} for slot in frame.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 = []
for ingredient in ingredients:
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 "Nie podano składników."
def generate_drinks_response(slots):
drinks = [slot['value'] for slot in slots if slot['name'] == 'drink']
print(slots)
print(drinks)
if drinks:
drink_details = []
for drink in drinks:
for name, details in drink.items():
price = details.get('price', 'unknown')
drink_details.append(f"{name} w cenie {price}")
# price = details.get('price', 'unknown')
drink_details.append(f"{name}") # w cenie {price} zł")
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:
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 "Nie podano napojów."
def generate_size_response(slots):
sizes = [slot['value'] for slot in slots if slot['name'] == 'size']
if sizes:
size_details = []
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
sizes = [slot['value'] for slot in slots if slot['name'] == 'size'][0]
if len(sizes) != 0:
return F"Dostępne rozmiary to: {', '.join(sizes)}"
return "Nie podano rozmiarów."
def generate_sauce_response(slots):
@ -96,6 +89,8 @@ def select_template(act, slots):
return generate_size_response(slots)
elif "price" in slot_names:
return random.choice(templates["inform/price"])
elif "food" in slot_names:
return random.choice(templates["inform/menu"])
if act == "inform":
if "menu" in slot_names:
@ -160,11 +155,21 @@ def select_template(act, slots):
elif act == "request/drinks":
return random.choice(templates["request/drinks"])
elif act == "request/food":
return random.choice(templates["request/food"])
return random.choice(templates["inform/menu"])
elif act == "inform/name":
return random.choice(templates["inform/name"])
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

View File

@ -8,10 +8,6 @@ templates = {
],
"inform/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": [
"Twoje imię to {name}.",
@ -86,10 +82,6 @@ templates = {
"Koszt dostawy: {delivery-price} zł."
],
"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}."
],
"inform/time": [
@ -99,26 +91,11 @@ templates = {
"Godzina: {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": [
"Witaj w naszej wspaniałej pizzerii. W czym mogę pomóc?",
"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?",
],
"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": [
"Cena za {food} wynosi {price} zł.",
"Koszt {food} to {price} zł.",
@ -172,9 +149,18 @@ templates = {
"Niestety, nie mamy {ingredient/neg}."
],
"default/template": [
"Przepraszamy, ale nie rozumiemy Twojego zapytania.",
"Proszę spróbować ponownie później.",
"Nie rozpoznajemy Twojej prośby, spróbuj ponownie.",
"Strasznie szybko to napisałeś, nie zrozumiałem...."
"Nie zrozumiałem, spróbuj inaczej sformułować zdanie."
],
"repeat": [
"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."
]
}