feat: add chatbot config and exercise

This commit is contained in:
Mikołaj Gawron 2024-05-07 21:40:14 +02:00
parent f039d6462b
commit 5996ff712a
16 changed files with 147 additions and 73 deletions

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM python:3.12.3
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY chatbot/ chatbot/
WORKDIR /app/chatbot
CMD ["python", "main.py"]

View File

@ -0,0 +1,41 @@
# Systemy dialogowe - sklep internetowy
## Requirements
Docker version 20.\*+
## Installation
Make sure that you Docker is running now.
`docker build -t chatbot .`
There is always possibilit to run it without Docker.
List of the required library we can find inside:
`requirements.txt`
or by:
`pip install requirements.txt`
## Development and usage
We can run script
`sh run.sh`
or
`docker run -it --rm -p 8888:8888 chatbot`
On both cases we are mapping our src directory to workdir inside Docker to always run the newest code.
## Contributors
| Team member |
|----------------|
| Mikołaj Gawron |
| |
| |
| |

View File

@ -1,3 +0,0 @@
{
"data_path": "./data/intents.json"
}

View File

@ -0,0 +1,4 @@
{
"data_path": "./data/intents.json",
"responses_path": "./data/response.json"
}

View File

@ -1,12 +1,12 @@
{ {
"name_query": [ "name_query": [
"jak masz na imię", "jak masz na imię",
"jak się nazywasz", "jak się nazywasz",
"twoje imię", "twoje imię",
"jak mogę cię wołać", "jak mogę cię wołać",
"jak do ciebie mówić", "jak do ciebie mówić",
"jak cię zwać", "jak cię zwać",
"jak cię nazywać", "jak cię nazywać",
"jak masz na imie" "jak masz na imie"
] ]
} }

View File

@ -1,26 +1,22 @@
from modules.nlp import NaturalLanguageProcessor, Config from pathlib import Path
from modules.state_monitor import DialogueStateMonitor from modules.nlp import NaturalLanguageProcessor
from modules.strategy import DialogueStrategy from modules.generator import ResponseGenerator
from modules.generator import NaturalLanguageGenerator from modules.config import Config
import colorama import colorama
from colorama import Fore, Style from colorama import Fore, Style
colorama.init(autoreset=True) colorama.init(autoreset=True)
def chatbot_response(input_text: str, nlp: NaturalLanguageProcessor) -> str: def main():
dialogue_monitor = DialogueStateMonitor() base_path = Path(__file__).resolve().parent
analysis = nlp.analyze(input_text) config_path = base_path / 'config' / 'config.json'
dialogue_monitor.update_state(analysis['intent']) config = Config.load_config(config_path)
response = DialogueStrategy.decide_response(dialogue_monitor.state)
final_response = NaturalLanguageGenerator.generate(response)
return final_response
if __name__ == "__main__":
config = Config()
nlp = NaturalLanguageProcessor(config) nlp = NaturalLanguageProcessor(config)
generator = ResponseGenerator(config)
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")
while True: while True:
@ -29,4 +25,10 @@ if __name__ == "__main__":
print(Fore.RED + "Zamykanie chatbota...") print(Fore.RED + "Zamykanie chatbota...")
break break
print(Fore.CYAN + "Bot: " + chatbot_response(user_input, nlp)) intent = nlp.analyze(user_input)
response = generator.generate(intent)
print(Fore.CYAN + "Bot: " + response)
if __name__ == "__main__":
main()

View File

24
chatbot/modules/config.py Normal file
View File

@ -0,0 +1,24 @@
import json
from pathlib import Path
from pydantic import BaseModel, ValidationError
class Config(BaseModel):
data_path: Path
responses_path: Path
@classmethod
def load_config(cls, config_path: Path) -> 'Config':
try:
with config_path.open('r', encoding='utf-8') as config_file:
config_data = json.load(config_file)
return cls(**config_data)
except FileNotFoundError:
print("Config file not found.")
exit(1)
except json.JSONDecodeError:
print("Invalid JSON.")
exit(1)
except ValidationError as e:
print(f"Configuration validation error: {e}")
exit(1)

View File

@ -1,4 +1,13 @@
class NaturalLanguageGenerator: import json
@staticmethod from typing import Dict
def generate(response: str) -> str: from .config import Config
return response import random
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)
def generate(self, response_key: str) -> str:
return random.choice(self.responses.get(response_key, ["Przepraszam, nie rozumiem. Możesz to powtórzyć?"]))

View File

@ -1,38 +1,20 @@
import json import json
import os from typing import Dict, List, TypedDict
from typing import Any, Dict, Literal from .config import Config
class Config: class Intents(TypedDict):
def __init__(self) -> None: name_query: List[str]
try:
config_path = os.path.join(os.path.dirname(os.path.dirname(__file__)), 'config.json')
with open(config_path, 'r', encoding='utf-8') as config_file:
self.config_data: Dict[str, Any] = json.load(config_file)
except FileNotFoundError:
print("Config file not found.")
self.config_data = {}
except json.JSONDecodeError:
print("Invalid JSON.")
self.config_data = {}
def get_data_path(self) -> str:
data_path = self.config_data.get('data_path', '')
if not isinstance(data_path, str):
raise ValueError("Data path must be a string.")
return os.path.join(os.path.dirname(os.path.dirname(__file__)), data_path)
class NaturalLanguageProcessor: class NaturalLanguageProcessor:
def __init__(self, config: Config) -> None: def __init__(self, config: Config):
self.config = config with config.data_path.open('r', encoding='utf-8') as file:
data_path = self.config.get_data_path() self.intents: Intents = json.load(file)
with open(data_path, 'r', encoding='utf-8') as file:
self.intents: Dict[str, Any] = json.load(file)
def analyze(self, input_text: str) -> Dict[str, Literal['ask_name', 'unknown']]: def analyze(self, input_text: str) -> str:
lower_text = input_text.lower() lower_text = input_text.lower()
for phrase in self.intents.get('name_query', []): for intent, phrases in self.intents.items():
if phrase in lower_text: if any(phrase in lower_text for phrase in phrases):
return {"intent": "ask_name"} return intent
return {"intent": "unknown"} return "unknown"

View File

@ -1,7 +1,8 @@
import random
class DialogueStrategy: class DialogueStrategy:
@staticmethod @staticmethod
def decide_response(state: dict) -> str: def decide_response(state: dict, responses) -> str:
if state['last_intent'] == 'ask_name': intent_responses = responses.get(state['last_intent'], ["Przepraszam, nie rozumiem. Możesz to powtórzyć?"])
return "Witaj, nazywam się Dia." return random.choice(intent_responses)
else:
return "Przepraszam, nie rozumiem. Możesz to powtórzyć?"

View File

@ -2,7 +2,7 @@ import os
import re import re
import pandas as pd import pandas as pd
import numpy as np import numpy as np
from chatbot.modules.nlu import NLU from modules.nlu import NLU
rows = 0 rows = 0
hits = 0 hits = 0

0
modules/__init__.py Normal file
View File

View File

@ -1,10 +1,6 @@
import copy
from copy import deepcopy
import json
import os import os
import jsgf import jsgf
class NLU: class NLU:
def __init__(self): def __init__(self):
self.grammars = [ self.grammars = [

View File

@ -0,0 +1,4 @@
colorama~=0.4.6
pydantic~=2.7.0
pandas~=1.5.3
numpy~=1.26.4

1
run.sh Normal file
View File

@ -0,0 +1 @@
docker run -it --rm -p 8888:8888 chatbot