Skip to content

Commit d3bc687

Browse files
committed
Introduced Klat personas management with persistent synchronisation + fallback on the initial implementation
1 parent effa61e commit d3bc687

File tree

6 files changed

+212
-3
lines changed

6 files changed

+212
-3
lines changed

neon_llm_core/rmq.py

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,10 @@
3131
from neon_mq_connector.utils.rabbit_utils import create_mq_callback
3232
from ovos_utils.log import LOG
3333

34-
from neon_llm_core.config import load_config
34+
from neon_llm_core.utils.config import load_config
3535
from neon_llm_core.llm import NeonLLM
36+
from neon_llm_core.utils.constants import LLM_VHOST
37+
from neon_llm_core.utils.personas.provider import PersonasProvider
3638

3739

3840
class NeonLLMMQConnector(MQConnector, ABC):
@@ -45,11 +47,13 @@ def __init__(self):
4547
self.ovos_config = load_config()
4648
mq_config = self.ovos_config.get("MQ", dict())
4749
super().__init__(config=mq_config, service_name=self.service_name)
48-
self.vhost = "/llm"
50+
self.vhost = LLM_VHOST
4951

5052
self.register_consumers()
5153
self._model = None
5254
self._bots = list()
55+
self._personas_provider = PersonasProvider(service_name=self.name,
56+
ovos_config=self.ovos_config)
5357

5458
if self.ovos_config.get("llm_bots", {}).get(self.name):
5559
from neon_llm_core.chatbot import LLMBot
@@ -242,3 +246,15 @@ def compose_opinion_prompt(respondent_nick: str, question: str,
242246
@param answer: respondent's response to the question
243247
"""
244248
pass
249+
250+
def run(self, run_consumers: bool = True, run_sync: bool = True,
251+
run_observer: bool = True, **kwargs):
252+
super().run(run_consumers=run_consumers,
253+
run_sync=run_sync,
254+
run_observer=run_observer,
255+
**kwargs)
256+
self._personas_provider.start_sync()
257+
258+
def stop(self):
259+
super().stop()
260+
self._personas_provider.stop_sync()

neon_llm_core/utils/__init__.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
2+
# All trademark and other rights reserved by their respective owners
3+
# Copyright 2008-2021 NeonGecko.com Inc.
4+
# BSD-3
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# 1. Redistributions of source code must retain the above copyright notice,
8+
# this list of conditions and the following disclaimer.
9+
# 2. Redistributions in binary form must reproduce the above copyright notice,
10+
# this list of conditions and the following disclaimer in the documentation
11+
# and/or other materials provided with the distribution.
12+
# 3. Neither the name of the copyright holder nor the names of its
13+
# contributors may be used to endorse or promote products derived from this
14+
# software without specific prior written permission.
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
22+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

neon_llm_core/config.py renamed to neon_llm_core/utils/config.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from ovos_utils.log import LOG
3232
from ovos_config.config import Configuration
3333

34+
from neon_llm_core.utils.constants import LLM_VHOST
35+
3436

3537
def load_config() -> dict:
3638
"""
@@ -56,4 +58,4 @@ class LLMMQConfig:
5658
ask_response_queue: str
5759
ask_appraiser_queue: str
5860
ask_discusser_queue: str
59-
vhost: str = '/llm'
61+
vhost: str = LLM_VHOST

neon_llm_core/utils/constants.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
2+
# All trademark and other rights reserved by their respective owners
3+
# Copyright 2008-2021 NeonGecko.com Inc.
4+
# BSD-3
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# 1. Redistributions of source code must retain the above copyright notice,
8+
# this list of conditions and the following disclaimer.
9+
# 2. Redistributions in binary form must reproduce the above copyright notice,
10+
# this list of conditions and the following disclaimer in the documentation
11+
# and/or other materials provided with the distribution.
12+
# 3. Neither the name of the copyright holder nor the names of its
13+
# contributors may be used to endorse or promote products derived from this
14+
# software without specific prior written permission.
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
22+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
27+
LLM_VHOST = '/llm'
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# NEON AI (TM) SOFTWARE, Software Development Kit & Application Development System
2+
# All trademark and other rights reserved by their respective owners
3+
# Copyright 2008-2021 NeonGecko.com Inc.
4+
# BSD-3
5+
# Redistribution and use in source and binary forms, with or without
6+
# modification, are permitted provided that the following conditions are met:
7+
# 1. Redistributions of source code must retain the above copyright notice,
8+
# this list of conditions and the following disclaimer.
9+
# 2. Redistributions in binary form must reproduce the above copyright notice,
10+
# this list of conditions and the following disclaimer in the documentation
11+
# and/or other materials provided with the distribution.
12+
# 3. Neither the name of the copyright holder nor the names of its
13+
# contributors may be used to endorse or promote products derived from this
14+
# software without specific prior written permission.
15+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17+
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18+
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19+
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20+
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21+
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
22+
# OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23+
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24+
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25+
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26+
import os
27+
from time import time
28+
29+
from neon_mq_connector.utils import RepeatingTimer
30+
from neon_mq_connector.utils.client_utils import send_mq_request
31+
from ovos_utils.log import LOG
32+
33+
from neon_llm_core.utils.constants import LLM_VHOST
34+
from neon_llm_core.utils.personas.state import PersonaHandlersState
35+
36+
37+
class PersonasProvider:
38+
39+
PERSONA_STATE_TTL = int(os.getenv("PERSONA_STATE_TTL", 15 * 60))
40+
PERSONA_SYNC_INTERVAL = int(os.getenv("PERSONA_SYNC_INTERVAL", 5 * 60))
41+
42+
def __init__(self, service_name: str, ovos_config: dict):
43+
self.service_name = service_name
44+
self._persona_handlers_state = PersonaHandlersState(service_name=service_name,
45+
ovos_config=ovos_config)
46+
self._personas = [] # list of personas available for given service
47+
self._persona_last_sync = 0
48+
self._persona_sync_thread = None
49+
50+
@property
51+
def persona_sync_thread(self):
52+
"""Creates new synchronization thread which fetches Klat personas"""
53+
if not (isinstance(self._persona_sync_thread, RepeatingTimer) and
54+
self._persona_sync_thread.is_alive()):
55+
self._persona_sync_thread = RepeatingTimer(self.PERSONA_SYNC_INTERVAL,
56+
self._fetch_persona_config)
57+
self._persona_sync_thread.daemon = True
58+
return self._persona_sync_thread
59+
60+
@property
61+
def personas(self):
62+
return self._personas
63+
64+
@personas.setter
65+
def personas(self, data):
66+
now = int(time())
67+
LOG.debug(f'Setting personas={data}')
68+
if data and isinstance(data, list):
69+
self._personas = data
70+
self._persona_last_sync = now
71+
elif now - self._persona_last_sync > self.PERSONA_STATE_TTL:
72+
LOG.warning(f'Persona state TTL expired, resetting personas config')
73+
self._personas = []
74+
self._persona_handlers_state.init_default_handlers()
75+
76+
def _fetch_persona_config(self):
77+
response = send_mq_request(vhost=LLM_VHOST,
78+
request_data={"service_name": self.service_name},
79+
target_queue="get_configured_personas")
80+
self.personas = response.get('items', [])
81+
for persona in self.personas:
82+
if persona:
83+
self._persona_handlers_state.add_persona_handler(persona=persona)
84+
85+
def start_sync(self):
86+
self.persona_sync_thread.start()
87+
88+
def stop_sync(self):
89+
if self._persona_sync_thread:
90+
self._persona_sync_thread.cancel()
91+
self._persona_sync_thread = None

neon_llm_core/utils/personas/state.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import time
2+
from typing import Dict, Union
3+
4+
from ovos_utils import LOG
5+
6+
from neon_llm_core.chatbot import LLMBot
7+
8+
9+
class PersonaHandlersState:
10+
11+
def __init__(self, service_name: str, ovos_config: dict):
12+
self._created_items: Dict[str, LLMBot] = {}
13+
self.service_name = service_name
14+
self.ovos_config = ovos_config
15+
self.mq_config = ovos_config.get('MQ', {})
16+
self.init_default_handlers()
17+
18+
def init_default_handlers(self):
19+
if self.ovos_config.get("llm_bots", {}).get(self.service_name):
20+
from neon_llm_core.chatbot import LLMBot
21+
LOG.info(f"Chatbot(s) configured for: {self.service_name}")
22+
for persona in self.ovos_config['llm_bots'][self.service_name]:
23+
self.add_persona_handler(persona=persona)
24+
25+
def add_persona_handler(self, persona: dict) -> Union[LLMBot, None]:
26+
persona_name = persona['name']
27+
if persona_name in list(self._created_items):
28+
if self._created_items[persona_name].persona != persona:
29+
LOG.warning(f"Overriding already existing persona: '{persona_name}' with new data={persona}")
30+
self._created_items[persona_name].stop()
31+
# time to gracefully stop the submind
32+
time.sleep(0.5)
33+
else:
34+
LOG.warning('Persona config provided is identical to existing, skipping')
35+
return self._created_items[persona_name]
36+
if not persona.get('enabled', True):
37+
LOG.warning(f"Persona disabled: {persona['name']}")
38+
return
39+
# Get a configured username to use for LLM submind connections
40+
if self.mq_config.get("users", {}).get("neon_llm_submind"):
41+
self.ovos_config["MQ"]["users"][persona['name']] = self.mq_config['users']['neon_llm_submind']
42+
bot = LLMBot(llm_name=self.service_name, service_name=persona['name'],
43+
persona=persona, config=self.ovos_config,
44+
vhost="/chatbots")
45+
bot.run()
46+
LOG.info(f"Started chatbot: {bot.service_name}")
47+
self._created_items[persona_name] = bot
48+
return bot

0 commit comments

Comments
 (0)