From 2f9d464684c12a839c43f17ee56bfe27e164ae88 Mon Sep 17 00:00:00 2001 From: NeonKirill Date: Sun, 10 Mar 2024 17:25:37 +0100 Subject: [PATCH] Added pydantic-based model validation of incoming data --- neon_llm_core/utils/personas/models.py | 42 ++++++++++++++++++++++++ neon_llm_core/utils/personas/provider.py | 9 ++--- neon_llm_core/utils/personas/state.py | 29 ++++++++-------- requirements/requirements.txt | 3 +- 4 files changed, 63 insertions(+), 20 deletions(-) create mode 100644 neon_llm_core/utils/personas/models.py diff --git a/neon_llm_core/utils/personas/models.py b/neon_llm_core/utils/personas/models.py new file mode 100644 index 0000000..fa100ad --- /dev/null +++ b/neon_llm_core/utils/personas/models.py @@ -0,0 +1,42 @@ +# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System +# All trademark and other rights reserved by their respective owners +# Copyright 2008-2021 NeonGecko.com Inc. +# BSD-3 +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from this +# software without specific prior written permission. +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +from pydantic import BaseModel, computed_field + + +class PersonaModel(BaseModel): + name: str + user_id: str = None + description: str + enabled: bool = True + + @computed_field + @property + def id(self) -> str: + persona_id = self.name + if self.user_id: + persona_id += f"_{self.user_id}" + return persona_id diff --git a/neon_llm_core/utils/personas/provider.py b/neon_llm_core/utils/personas/provider.py index 657cd49..c562baf 100644 --- a/neon_llm_core/utils/personas/provider.py +++ b/neon_llm_core/utils/personas/provider.py @@ -31,6 +31,7 @@ from ovos_utils.log import LOG from neon_llm_core.utils.constants import LLM_VHOST +from neon_llm_core.utils.personas.models import PersonaModel from neon_llm_core.utils.personas.state import PersonaHandlersState @@ -38,6 +39,7 @@ class PersonasProvider: PERSONA_STATE_TTL = int(os.getenv("PERSONA_STATE_TTL", 15 * 60)) PERSONA_SYNC_INTERVAL = int(os.getenv("PERSONA_SYNC_INTERVAL", 5 * 60)) + GET_CONFIGURED_PERSONAS_QUEUE = "get_configured_personas" def __init__(self, service_name: str, ovos_config: dict): self.service_name = service_name @@ -74,14 +76,13 @@ def personas(self, data): self._persona_handlers_state.init_default_handlers() def _fetch_persona_config(self): - queue = "get_configured_personas" response = send_mq_request(vhost=LLM_VHOST, request_data={"service_name": self.service_name}, - target_queue=queue) + target_queue=PersonasProvider.GET_CONFIGURED_PERSONAS_QUEUE) self.personas = response.get('items', []) for persona in self.personas: - if persona: - self._persona_handlers_state.add_persona_handler(persona=persona) + persona = PersonaModel.parse_obj(obj=persona) + self._persona_handlers_state.add_persona_handler(persona=persona) def start_sync(self): self.persona_sync_thread.start() diff --git a/neon_llm_core/utils/personas/state.py b/neon_llm_core/utils/personas/state.py index c21b306..3f40d85 100644 --- a/neon_llm_core/utils/personas/state.py +++ b/neon_llm_core/utils/personas/state.py @@ -32,6 +32,7 @@ from ovos_utils import LOG from neon_llm_core.chatbot import LLMBot +from neon_llm_core.utils.personas.models import PersonaModel class PersonaHandlersState: @@ -47,29 +48,27 @@ def init_default_handlers(self): if self.ovos_config.get("llm_bots", {}).get(self.service_name): LOG.info(f"Chatbot(s) configured for: {self.service_name}") for persona in self.ovos_config['llm_bots'][self.service_name]: - self.add_persona_handler(persona=persona) + self.add_persona_handler(persona=PersonaModel.parse_obj(obj=persona)) - def add_persona_handler(self, persona: dict) -> Union[LLMBot, None]: - persona_name = persona['name'] - if persona_name in list(self._created_items): - if self._created_items[persona_name].persona != persona: - LOG.warning(f"Overriding already existing persona: '{persona_name}' with new data={persona}") - self._created_items[persona_name].stop() + def add_persona_handler(self, persona: PersonaModel) -> Union[LLMBot, None]: + if persona.id in list(self._created_items): + if self._created_items[persona.id].persona != persona: + LOG.warning(f"Overriding already existing persona: '{persona.id}' with new data={persona}") + self._created_items[persona.id].stop() # time to gracefully stop the submind time.sleep(0.5) else: LOG.warning('Persona config provided is identical to existing, skipping') - return self._created_items[persona_name] - if not persona.get('enabled', True): - LOG.warning(f"Persona disabled: {persona['name']}") + return self._created_items[persona.id] + if not persona.enabled: + LOG.warning(f"Persona disabled: '{persona.id}'") return # Get a configured username to use for LLM submind connections - if self.mq_config.get("users", {}).get("neon_llm_submind"): - self.ovos_config["MQ"]["users"][persona['name']] = self.mq_config['users']['neon_llm_submind'] - bot = LLMBot(llm_name=self.service_name, service_name=persona['name'], - persona=persona, config=self.ovos_config, + self.ovos_config["MQ"]["users"][persona.name] = self.mq_config['users'][f'neon_llm_{self.service_name}'] + bot = LLMBot(llm_name=self.service_name, service_name=persona.name, + persona=persona.model_dump(), config=self.ovos_config, vhost="/chatbots") bot.run() LOG.info(f"Started chatbot: {bot.service_name}") - self._created_items[persona_name] = bot + self._created_items[persona.id] = bot return bot diff --git a/requirements/requirements.txt b/requirements/requirements.txt index b9f3251..4662454 100644 --- a/requirements/requirements.txt +++ b/requirements/requirements.txt @@ -1,4 +1,5 @@ # networking neon-mq-connector>=0.7.1a4 ovos-utils~=0.0.32 -ovos-config~=0.0.10 \ No newline at end of file +ovos-config~=0.0.10 +pydantic==2.6.3 \ No newline at end of file