diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..491edde --- /dev/null +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index e69de29..8d24723 100644 --- a/README.md +++ b/README.md @@ -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 | +| | +| | +| | diff --git a/chatbot/config.json b/chatbot/config.json deleted file mode 100644 index 0567631..0000000 --- a/chatbot/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "data_path": "./data/intents.json" -} diff --git a/chatbot/config/config.json b/chatbot/config/config.json new file mode 100644 index 0000000..4fef2a5 --- /dev/null +++ b/chatbot/config/config.json @@ -0,0 +1,4 @@ +{ + "data_path": "./data/intents.json", + "responses_path": "./data/response.json" +} diff --git a/chatbot/data/intents.json b/chatbot/data/intents.json index a12f1d5..ffdeea1 100644 --- a/chatbot/data/intents.json +++ b/chatbot/data/intents.json @@ -1,12 +1,12 @@ { - "name_query": [ - "jak masz na imię", - "jak się nazywasz", - "twoje imię", - "jak mogę cię wołać", - "jak do ciebie mówić", - "jak cię zwać", - "jak cię nazywać", - "jak masz na imie" - ] + "name_query": [ + "jak masz na imię", + "jak się nazywasz", + "twoje imię", + "jak mogę cię wołać", + "jak do ciebie mówić", + "jak cię zwać", + "jak cię nazywać", + "jak masz na imie" + ] } \ No newline at end of file diff --git a/chatbot/main.py b/chatbot/main.py index 5e59c27..9579d09 100644 --- a/chatbot/main.py +++ b/chatbot/main.py @@ -1,26 +1,22 @@ -from modules.nlp import NaturalLanguageProcessor, Config -from modules.state_monitor import DialogueStateMonitor -from modules.strategy import DialogueStrategy -from modules.generator import NaturalLanguageGenerator +from pathlib import Path +from modules.nlp import NaturalLanguageProcessor +from modules.generator import ResponseGenerator +from modules.config import Config import colorama from colorama import Fore, Style colorama.init(autoreset=True) -def chatbot_response(input_text: str, nlp: NaturalLanguageProcessor) -> str: - dialogue_monitor = DialogueStateMonitor() - analysis = nlp.analyze(input_text) - dialogue_monitor.update_state(analysis['intent']) - response = DialogueStrategy.decide_response(dialogue_monitor.state) - final_response = NaturalLanguageGenerator.generate(response) +def main(): + base_path = Path(__file__).resolve().parent + config_path = base_path / 'config' / 'config.json' + config = Config.load_config(config_path) - return final_response - - -if __name__ == "__main__": - config = 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") while True: @@ -29,4 +25,10 @@ if __name__ == "__main__": print(Fore.RED + "Zamykanie chatbota...") 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() diff --git a/chatbot/modules/__init__.py b/chatbot/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chatbot/modules/config.py b/chatbot/modules/config.py new file mode 100644 index 0000000..7ba8ce2 --- /dev/null +++ b/chatbot/modules/config.py @@ -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) diff --git a/chatbot/modules/generator.py b/chatbot/modules/generator.py index f78f792..fe5af38 100644 --- a/chatbot/modules/generator.py +++ b/chatbot/modules/generator.py @@ -1,4 +1,13 @@ -class NaturalLanguageGenerator: - @staticmethod - def generate(response: str) -> str: - return response +import json +from typing import Dict +from .config import Config +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ć?"])) diff --git a/chatbot/modules/nlp.py b/chatbot/modules/nlp.py index 0276df2..7aad3e1 100644 --- a/chatbot/modules/nlp.py +++ b/chatbot/modules/nlp.py @@ -1,38 +1,20 @@ import json -import os -from typing import Any, Dict, Literal +from typing import Dict, List, TypedDict +from .config import Config -class Config: - def __init__(self) -> None: - 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 Intents(TypedDict): + name_query: List[str] class NaturalLanguageProcessor: - def __init__(self, config: Config) -> None: - self.config = config - data_path = self.config.get_data_path() - with open(data_path, 'r', encoding='utf-8') as file: - self.intents: Dict[str, Any] = json.load(file) + def __init__(self, config: Config): + with config.data_path.open('r', encoding='utf-8') as file: + self.intents: Intents = 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() - for phrase in self.intents.get('name_query', []): - if phrase in lower_text: - return {"intent": "ask_name"} - return {"intent": "unknown"} + for intent, phrases in self.intents.items(): + if any(phrase in lower_text for phrase in phrases): + return intent + return "unknown" diff --git a/chatbot/modules/strategy.py b/chatbot/modules/strategy.py index 385741c..c669c4c 100644 --- a/chatbot/modules/strategy.py +++ b/chatbot/modules/strategy.py @@ -1,7 +1,8 @@ +import random + + class DialogueStrategy: @staticmethod - def decide_response(state: dict) -> str: - if state['last_intent'] == 'ask_name': - return "Witaj, nazywam się Dia." - else: - return "Przepraszam, nie rozumiem. Możesz to powtórzyć?" + def decide_response(state: dict, responses) -> str: + intent_responses = responses.get(state['last_intent'], ["Przepraszam, nie rozumiem. Możesz to powtórzyć?"]) + return random.choice(intent_responses) diff --git a/evaluate.py b/evaluate.py index e53292c..4430177 100644 --- a/evaluate.py +++ b/evaluate.py @@ -2,7 +2,7 @@ import os import re import pandas as pd import numpy as np -from chatbot.modules.nlu import NLU +from modules.nlu import NLU rows = 0 hits = 0 diff --git a/modules/__init__.py b/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/chatbot/modules/nlu.py b/modules/nlu.py similarity index 96% rename from chatbot/modules/nlu.py rename to modules/nlu.py index 6e387ba..f75ae25 100644 --- a/chatbot/modules/nlu.py +++ b/modules/nlu.py @@ -1,10 +1,6 @@ -import copy -from copy import deepcopy -import json import os import jsgf - class NLU: def __init__(self): self.grammars = [ diff --git a/requirements.txt b/requirements.txt index e69de29..f876b9e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -0,0 +1,4 @@ +colorama~=0.4.6 +pydantic~=2.7.0 +pandas~=1.5.3 +numpy~=1.26.4 \ No newline at end of file diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..6789657 --- /dev/null +++ b/run.sh @@ -0,0 +1 @@ +docker run -it --rm -p 8888:8888 chatbot \ No newline at end of file