From bd57bca99cc7ae8ff931348748008b18bc9e0721 Mon Sep 17 00:00:00 2001 From: Daniel McKnight <34697904+NeonDaniel@users.noreply.github.com> Date: Fri, 8 Dec 2023 14:34:28 -0800 Subject: [PATCH] Add `bot_config` param with `Configuration` handling (#181) * Add support for chatbot-specific configuration Update module init to set default config envvars Add config documentation to README.md * Refactor init Update `ChatBot.log` to use bot_id for log name instead of class name * Minor logging refactor * Cleanup changes Update documentation --------- Co-authored-by: Daniel McKnight --- README.md | 42 +++++++++++++++++++++++++++-- chatbot_core/__init__.py | 10 +++++-- chatbot_core/chatbot_abc.py | 13 +++++++-- chatbot_core/utils/bot_utils.py | 4 +-- chatbot_core/utils/version_utils.py | 3 ++- chatbot_core/v1/__init__.py | 10 ++++--- chatbot_core/v2/__init__.py | 6 +++-- 7 files changed, 73 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index c0ce978..c4cb4bb 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,49 @@ To utilize this repository for creating your own chat bots, install this package You can install this package with the following command: -`pip install git+https://github.com/neongeckocom/chatbot-core` +`pip install neon-chatbot-core` *Note*: It is recommended to install this to a virtual environment to avoid conflicts with package versions and commandline entry points. Most IDE's (i.e. [PyCharm](https://www.jetbrains.com/pycharm/)) handle this for individual projects. +### Configuration + +#### Bot-specific configuration +Configuration for chatbots should be defined in `~/.config/neon/chatbots.yaml` +by default. Chatbots may be configured as: +```yaml +chatbots: + : {} +``` +> For Klat v1, `bot_id` is the `username` the bot connects as, for MQ connected +> bots, `bot_id` is the MQ `service_name`. + +Any bot-specific configuration will be accessible as `self.bot_config`. For Klat +v1 connections, `password` should be specified in the `chatbots` +config section. + +#### MQ Connection configuration +For v2 bots, MQ connections must also be configured. This should be completed in +the same `~/.config/neon/chatbots.yaml` file as the bot-specific config. + +```yaml +MQ: + server: mq.neonaiservices.com + port: 5672 + users: + : + user: neon_bot_submind + password: +``` + +#### SocketIO Connection configuration +For v1 bots, SIO connections may be configured in `~/.config/neon/chatbots.yaml`: +```yaml +socket_io: + server: 2222.us + port: 8888 +``` + ### Organizing your bots It is recommended to create a module for each of your bots. You should use subdirectories, each containing `__init__.py` that includes your `ChatBot` as well as any supporting configuration files, etc. You may also organize this as a @@ -47,7 +85,7 @@ my_bots └--my_bot.py ``` -### Klat.com Credentials +### Legacy Klat.com Credentials Bots should be able to login to [klat.com](https://klat.com); a YAML file containing credentials for each bot can be used to save usernames and passwords for each bot. Each bot module should have a key matching the module name, a `username`, and a `password`. diff --git a/chatbot_core/__init__.py b/chatbot_core/__init__.py index 5057561..f6ec93f 100644 --- a/chatbot_core/__init__.py +++ b/chatbot_core/__init__.py @@ -17,20 +17,22 @@ # US Patents 2008-2021: US7424516, US20140161250, US20140177813, US8638908, US8068604, US8553852, US10530923, US10530924 # China Patent: CN102017585 - Europe Patent: EU2156652 - Patents Pending -from ovos_utils.log import LOG, log_deprecation from os import environ +environ.setdefault("OVOS_CONFIG_BASE_FOLDER", "neon") +environ.setdefault("OVOS_CONFIG_FILENAME", "chatbots.yaml") + from neon_utils.decorators import module_property @module_property def _ChatBot(): - LOG.debug(f"Getting class for {environ.get('CHATBOT_VERSION')}") from chatbot_core.utils.version_utils import get_class return get_class() @module_property def _ConversationControls(): + from ovos_utils.log import log_deprecation log_deprecation("import from `chatbot_core.utils.enum` directly", "3.0.0") from chatbot_core.utils.enum import ConversationControls @@ -39,6 +41,7 @@ def _ConversationControls(): @module_property def _ConversationState(): + from ovos_utils.log import log_deprecation log_deprecation("import from `chatbot_core.utils.enum` directly", "3.0.0") from chatbot_core.utils.enum import ConversationState @@ -46,6 +49,7 @@ def _ConversationState(): def generate_random_response(*args, **kwargs): + from ovos_utils.log import log_deprecation log_deprecation("import from `chatbot_core.utils.bot_utils` directly", "3.0.0") from chatbot_core.utils.bot_utils import generate_random_response @@ -53,6 +57,7 @@ def generate_random_response(*args, **kwargs): def clean_up_bot(*args, **kwargs): + from ovos_utils.log import log_deprecation log_deprecation("import from `chatbot_core.utils.bot_utils` directly", "3.0.0") from chatbot_core.utils.bot_utils import clean_up_bot @@ -60,6 +65,7 @@ def clean_up_bot(*args, **kwargs): def grammar_check(*args, **kwargs): + from ovos_utils.log import log_deprecation log_deprecation("import from `chatbot_core.utils.bot_utils` directly", "3.0.0") from chatbot_core.utils.bot_utils import grammar_check diff --git a/chatbot_core/chatbot_abc.py b/chatbot_core/chatbot_abc.py index eda2a57..d2212c5 100644 --- a/chatbot_core/chatbot_abc.py +++ b/chatbot_core/chatbot_abc.py @@ -23,6 +23,7 @@ from abc import ABC, abstractmethod from queue import Queue from typing import Optional +from ovos_config.config import Configuration from neon_utils.log_utils import init_log from ovos_utils.log import LOG @@ -31,14 +32,22 @@ class ChatBotABC(ABC): """Abstract class gathering all the chatbot-related methods children should implement""" - def __init__(self): + def __init__(self, bot_id: str, config: dict = None): + """ + Common chatbot initialization + @param bot_id: ID of this chatbot, used to read configuration + @param config: Dict configuration for this chatbot + """ + self._bot_id = bot_id + self.bot_config = config or Configuration().get("chatbots", + {}).get(bot_id) or {} self.shout_queue = Queue(maxsize=256) self.__log = None @property def log(self): if not self.__log: - self.__log = init_log(log_name=self.__class__.__name__) + self.__log = init_log(log_name=self._bot_id) return self.__log @abstractmethod diff --git a/chatbot_core/utils/bot_utils.py b/chatbot_core/utils/bot_utils.py index b45f726..0800e66 100644 --- a/chatbot_core/utils/bot_utils.py +++ b/chatbot_core/utils/bot_utils.py @@ -273,7 +273,6 @@ def start_bots(domain: str = None, bot_dir: str = None, username: str = None, credentials = load_credentials_yml(cred_file) else: credentials = {} - processes = [] # Check for specified bot to start @@ -604,8 +603,7 @@ def run_mq_bot(chatbot_name: str, vhost: str = '/chatbots', @returns: Started ChatBotV2 instance """ from neon_utils.log_utils import init_log - init_log({"logs": {"level_overrides": {"error": ['pika'], - "warning": ["filelock"]}}}) + init_log(log_name=chatbot_name) os.environ['CHATBOT_VERSION'] = 'v2' run_kwargs = run_kwargs or dict() init_kwargs = init_kwargs or dict() diff --git a/chatbot_core/utils/version_utils.py b/chatbot_core/utils/version_utils.py index f2ec767..11c1128 100644 --- a/chatbot_core/utils/version_utils.py +++ b/chatbot_core/utils/version_utils.py @@ -21,6 +21,7 @@ from typing import Optional from chatbot_core.chatbot_abc import ChatBotABC +from ovos_utils.log import LOG def get_class() -> Optional[type(ChatBotABC)]: @@ -33,7 +34,7 @@ def get_class() -> Optional[type(ChatBotABC)]: from chatbot_core.v2 import ChatBot as ChatBot_v2 version = os.environ.get('CHATBOT_VERSION', 'v1').lower() - + LOG.debug(f"version={version}") chatbot_versions = { 'v1': ChatBot_v1, 'v2': ChatBot_v2 diff --git a/chatbot_core/v1/__init__.py b/chatbot_core/v1/__init__.py index 6930dc6..7a33f78 100644 --- a/chatbot_core/v1/__init__.py +++ b/chatbot_core/v1/__init__.py @@ -38,10 +38,14 @@ def __init__(self, *args, **kwargs): socket, domain, username, password, on_server, is_prompter = \ self.parse_init(*args, **kwargs) LOG.info(f"Starting {username}") - socket = socket or start_socket() + ChatBotABC.__init__(self, username) + if not socket: + from ovos_config.config import Configuration + sio_config = Configuration().get("socket_io", {}) + socket = start_socket(addr=sio_config.get("server"), + port=sio_config.get("port")) init_nick = "Prompter" if is_prompter else "" KlatApi.__init__(self, socket, domain, init_nick) - ChatBotABC.__init__(self) # self.log.debug("Connector started") self.on_server = on_server self.is_prompter = is_prompter @@ -52,7 +56,7 @@ def __init__(self, *args, **kwargs): self.selected_history = list() self.username = username - self.password = password + self.password = password or self.bot_config.get("password") self.facilitator_nicks = ["proctor", "scorekeeper", "stenographer"] self.response_probability = 75 # % probability for a bot to respond to an input in non-proctored conversation diff --git a/chatbot_core/v2/__init__.py b/chatbot_core/v2/__init__.py index ff2be6c..33567d5 100644 --- a/chatbot_core/v2/__init__.py +++ b/chatbot_core/v2/__init__.py @@ -34,8 +34,10 @@ class ChatBot(KlatAPIMQ, ChatBotABC): def __init__(self, *args, **kwargs): config, service_name, vhost, bot_type = self.parse_init(*args, **kwargs) - KlatAPIMQ.__init__(self, config, service_name, vhost) - ChatBotABC.__init__(self) + mq_config = config.get("MQ") or config + bot_config = config.get("chatbots", {}).get(service_name) + KlatAPIMQ.__init__(self, mq_config, service_name, vhost) + ChatBotABC.__init__(self, service_name, bot_config) self.bot_type = bot_type self.current_conversations = dict() self.on_server = True