rasa-pro 3.11.0__py3-none-any.whl → 3.11.0a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +396 -17
- rasa/__main__.py +15 -31
- rasa/api.py +1 -5
- rasa/cli/arguments/default_arguments.py +2 -1
- rasa/cli/arguments/shell.py +1 -5
- rasa/cli/arguments/train.py +0 -14
- rasa/cli/e2e_test.py +1 -1
- rasa/cli/evaluate.py +8 -8
- rasa/cli/inspect.py +7 -15
- rasa/cli/interactive.py +0 -1
- rasa/cli/llm_fine_tuning.py +1 -1
- rasa/cli/project_templates/calm/config.yml +7 -5
- rasa/cli/project_templates/calm/endpoints.yml +2 -15
- rasa/cli/project_templates/tutorial/config.yml +5 -8
- rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
- rasa/cli/project_templates/tutorial/data/patterns.yml +0 -5
- rasa/cli/project_templates/tutorial/domain.yml +0 -14
- rasa/cli/project_templates/tutorial/endpoints.yml +0 -5
- rasa/cli/run.py +1 -1
- rasa/cli/scaffold.py +2 -4
- rasa/cli/studio/studio.py +8 -18
- rasa/cli/studio/upload.py +15 -0
- rasa/cli/train.py +0 -3
- rasa/cli/utils.py +1 -6
- rasa/cli/x.py +8 -8
- rasa/constants.py +1 -3
- rasa/core/actions/action.py +33 -75
- rasa/core/actions/e2e_stub_custom_action_executor.py +1 -5
- rasa/core/actions/http_custom_action_executor.py +0 -4
- rasa/core/channels/__init__.py +0 -2
- rasa/core/channels/channel.py +0 -20
- rasa/core/channels/development_inspector.py +3 -10
- rasa/core/channels/inspector/dist/assets/{arc-bc141fb2.js → arc-86942a71.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-be2db283.js → c4Diagram-d0fbc5ce-b0290676.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-55366915.js → classDiagram-936ed81e-f6405f6e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-bb529518.js → classDiagram-v2-c3cb15f1-ef61ac77.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-b0ec81d6.js → createText-62fc7601-f0411e58.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-6166330c.js → edges-f2ad444c-7dcc4f3b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-5ccc6a8e.js → erDiagram-9d236eb7-e0c092d7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-fca3bfe4.js → flowDb-1972c806-fba2e3ce.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4739080f.js → flowDiagram-7ea5b25a-7a70b71a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-24a5f41a.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-7c1b0e0f.js → flowchart-elk-definition-abe16c3d-00a59b68.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-772fd050.js → ganttDiagram-9b5ea136-293c91fa.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-8eae1dc9.js → gitGraphDiagram-99d0ae7c-07b2d68c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-f55afcdf.js → index-2c4b9a3b-bc959fbd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-e7cef9de.js → index-3a8a5a28.js} +143 -143
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-124d4a14.js → infoDiagram-736b4530-4a350f72.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-7c4fae44.js → journeyDiagram-df861f2b-af464fb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-b9885fb6.js → layout-0071f036.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-7c59abb6.js → line-2f73cc83.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-4776f780.js → linear-f014b4cc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2332c46c.js → mindmap-definition-beec6740-d2426fb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-8fb39303.js → pieDiagram-dbbf0591-776f01a2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3c7180a2.js → quadrantDiagram-4d7f4fd6-82e00b57.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-e910bcb8.js → requirementDiagram-6fc4c22a-ea13c6bb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-ead16c89.js → sankeyDiagram-8f13d901-1feca7e9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-29a02a19.js → sequenceDiagram-b655622a-070c61d2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-042b3137.js → stateDiagram-59f0c015-24f46263.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-2178c0f3.js → stateDiagram-v2-2b26beab-c9056051.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-23ffa4fc.js → styles-080da4f6-08abc34a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-94f59763.js → styles-3dcbcfbf-bc74c25a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-78a6bebc.js → styles-9c745c82-4e5d66de.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-eae2a6f6.js → svgDrawCommon-4835440b-849c4517.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-5c968d92.js → timeline-definition-5b62e21b-d0fb1598.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-fd3db0d5.js → xychartDiagram-2b33534f-04d115e2.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +1 -1
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +3 -6
- rasa/core/channels/socketio.py +2 -7
- rasa/core/channels/telegram.py +1 -1
- rasa/core/channels/twilio.py +1 -1
- rasa/core/channels/voice_ready/audiocodes.py +4 -15
- rasa/core/channels/voice_ready/jambonz.py +4 -15
- rasa/core/channels/voice_ready/twilio_voice.py +21 -6
- rasa/core/channels/voice_ready/utils.py +5 -6
- rasa/core/channels/voice_stream/asr/asr_engine.py +1 -19
- rasa/core/channels/voice_stream/asr/asr_event.py +0 -5
- rasa/core/channels/voice_stream/asr/deepgram.py +15 -28
- rasa/core/channels/voice_stream/audio_bytes.py +0 -1
- rasa/core/channels/voice_stream/tts/azure.py +3 -9
- rasa/core/channels/voice_stream/tts/cartesia.py +8 -12
- rasa/core/channels/voice_stream/tts/tts_engine.py +1 -11
- rasa/core/channels/voice_stream/twilio_media_streams.py +19 -28
- rasa/core/channels/voice_stream/util.py +4 -4
- rasa/core/channels/voice_stream/voice_channel.py +42 -222
- rasa/core/featurizers/single_state_featurizer.py +1 -22
- rasa/core/featurizers/tracker_featurizers.py +18 -115
- rasa/core/information_retrieval/qdrant.py +0 -1
- rasa/core/nlg/contextual_response_rephraser.py +25 -44
- rasa/core/persistor.py +34 -191
- rasa/core/policies/enterprise_search_policy.py +60 -119
- rasa/core/policies/flows/flow_executor.py +4 -7
- rasa/core/policies/intentless_policy.py +22 -82
- rasa/core/policies/ted_policy.py +33 -58
- rasa/core/policies/unexpected_intent_policy.py +7 -15
- rasa/core/processor.py +13 -89
- rasa/core/run.py +2 -2
- rasa/core/training/interactive.py +35 -34
- rasa/core/utils.py +22 -58
- rasa/dialogue_understanding/coexistence/llm_based_router.py +12 -39
- rasa/dialogue_understanding/commands/__init__.py +0 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
- rasa/dialogue_understanding/commands/utils.py +0 -5
- rasa/dialogue_understanding/generator/constants.py +0 -2
- rasa/dialogue_understanding/generator/flow_retrieval.py +4 -49
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +23 -37
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -57
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -19
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +0 -3
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +10 -90
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -53
- rasa/dialogue_understanding/processor/command_processor.py +1 -21
- rasa/e2e_test/assertions.py +16 -133
- rasa/e2e_test/assertions_schema.yml +0 -23
- rasa/e2e_test/e2e_test_case.py +6 -85
- rasa/e2e_test/e2e_test_runner.py +4 -6
- rasa/e2e_test/utils/io.py +1 -3
- rasa/engine/loader.py +0 -12
- rasa/engine/validation.py +11 -541
- rasa/keys +1 -0
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
- rasa/model_training.py +7 -29
- rasa/nlu/classifiers/diet_classifier.py +25 -38
- rasa/nlu/classifiers/logistic_regression_classifier.py +9 -22
- rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
- rasa/nlu/extractors/crf_entity_extractor.py +50 -93
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +16 -45
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
- rasa/nlu/tokenizers/whitespace_tokenizer.py +14 -3
- rasa/server.py +1 -3
- rasa/shared/constants.py +0 -61
- rasa/shared/core/constants.py +0 -9
- rasa/shared/core/domain.py +5 -8
- rasa/shared/core/flows/flow.py +0 -5
- rasa/shared/core/flows/flows_list.py +1 -5
- rasa/shared/core/flows/flows_yaml_schema.json +0 -10
- rasa/shared/core/flows/validation.py +0 -96
- rasa/shared/core/flows/yaml_flows_io.py +4 -13
- rasa/shared/core/slots.py +0 -5
- rasa/shared/importers/importer.py +2 -19
- rasa/shared/importers/rasa.py +1 -5
- rasa/shared/nlu/training_data/features.py +2 -120
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -18
- rasa/shared/providers/_configs/azure_openai_client_config.py +3 -5
- rasa/shared/providers/_configs/openai_client_config.py +1 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +0 -1
- rasa/shared/providers/_configs/utils.py +0 -16
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +29 -18
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +21 -54
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +0 -24
- rasa/shared/providers/llm/_base_litellm_client.py +31 -63
- rasa/shared/providers/llm/azure_openai_llm_client.py +29 -50
- rasa/shared/providers/llm/default_litellm_llm_client.py +0 -24
- rasa/shared/providers/llm/self_hosted_llm_client.py +29 -17
- rasa/shared/providers/mappings.py +0 -19
- rasa/shared/utils/common.py +2 -37
- rasa/shared/utils/io.py +6 -28
- rasa/shared/utils/llm.py +46 -353
- rasa/shared/utils/yaml.py +82 -181
- rasa/studio/auth.py +5 -3
- rasa/studio/config.py +4 -13
- rasa/studio/constants.py +0 -1
- rasa/studio/data_handler.py +4 -13
- rasa/studio/upload.py +80 -175
- rasa/telemetry.py +17 -94
- rasa/tracing/config.py +1 -3
- rasa/tracing/instrumentation/attribute_extractors.py +17 -94
- rasa/tracing/instrumentation/instrumentation.py +0 -121
- rasa/utils/common.py +0 -5
- rasa/utils/endpoints.py +1 -27
- rasa/utils/io.py +81 -7
- rasa/utils/log_utils.py +2 -9
- rasa/utils/tensorflow/model_data.py +193 -2
- rasa/validator.py +4 -110
- rasa/version.py +1 -1
- rasa_pro-3.11.0a1.dist-info/METADATA +576 -0
- {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/RECORD +182 -216
- rasa/core/actions/action_repeat_bot_messages.py +0 -89
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +0 -1
- rasa/core/channels/inspector/src/helpers/audiostream.ts +0 -165
- rasa/core/channels/voice_stream/asr/azure.py +0 -129
- rasa/core/channels/voice_stream/browser_audio.py +0 -107
- rasa/core/channels/voice_stream/call_state.py +0 -23
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +0 -60
- rasa/dialogue_understanding/commands/user_silence_command.py +0 -59
- rasa/dialogue_understanding/patterns/repeat.py +0 -37
- rasa/dialogue_understanding/patterns/user_silence.py +0 -37
- rasa/model_manager/__init__.py +0 -0
- rasa/model_manager/config.py +0 -40
- rasa/model_manager/model_api.py +0 -559
- rasa/model_manager/runner_service.py +0 -286
- rasa/model_manager/socket_bridge.py +0 -146
- rasa/model_manager/studio_jwt_auth.py +0 -86
- rasa/model_manager/trainer_service.py +0 -325
- rasa/model_manager/utils.py +0 -87
- rasa/model_manager/warm_rasa_process.py +0 -187
- rasa/model_service.py +0 -112
- rasa/shared/core/flows/utils.py +0 -39
- rasa/shared/providers/_configs/litellm_router_client_config.py +0 -220
- rasa/shared/providers/_configs/model_group_config.py +0 -167
- rasa/shared/providers/_configs/rasa_llm_client_config.py +0 -73
- rasa/shared/providers/_utils.py +0 -79
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +0 -135
- rasa/shared/providers/llm/litellm_router_llm_client.py +0 -182
- rasa/shared/providers/llm/rasa_llm_client.py +0 -112
- rasa/shared/providers/router/__init__.py +0 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +0 -183
- rasa/shared/providers/router/router_client.py +0 -73
- rasa/shared/utils/health_check/__init__.py +0 -0
- rasa/shared/utils/health_check/embeddings_health_check_mixin.py +0 -31
- rasa/shared/utils/health_check/health_check.py +0 -258
- rasa/shared/utils/health_check/llm_health_check_mixin.py +0 -31
- rasa/utils/sanic_error_handler.py +0 -32
- rasa/utils/tensorflow/feature_array.py +0 -366
- rasa_pro-3.11.0.dist-info/METADATA +0 -198
- {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -1,286 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import shutil
|
|
3
|
-
from typing import Dict, Optional
|
|
4
|
-
import aiohttp
|
|
5
|
-
import structlog
|
|
6
|
-
import subprocess
|
|
7
|
-
from pydantic import BaseModel, ConfigDict
|
|
8
|
-
from enum import Enum
|
|
9
|
-
|
|
10
|
-
from rasa.exceptions import ModelNotFound
|
|
11
|
-
from rasa.model_manager.utils import (
|
|
12
|
-
models_base_path,
|
|
13
|
-
subpath,
|
|
14
|
-
write_encoded_data_to_file,
|
|
15
|
-
)
|
|
16
|
-
from rasa.constants import MODEL_ARCHIVE_EXTENSION
|
|
17
|
-
|
|
18
|
-
from rasa.model_manager import config
|
|
19
|
-
from rasa.model_manager.utils import logs_path
|
|
20
|
-
from rasa.model_manager.warm_rasa_process import start_rasa_process
|
|
21
|
-
|
|
22
|
-
structlogger = structlog.get_logger()
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class BotSessionStatus(str, Enum):
|
|
26
|
-
"""Enum for the bot status."""
|
|
27
|
-
|
|
28
|
-
QUEUED = "queued"
|
|
29
|
-
RUNNING = "running"
|
|
30
|
-
STOPPED = "stopped"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
class BotSession(BaseModel):
|
|
34
|
-
"""Store information about a running bot."""
|
|
35
|
-
|
|
36
|
-
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
37
|
-
|
|
38
|
-
deployment_id: str
|
|
39
|
-
status: BotSessionStatus
|
|
40
|
-
process: subprocess.Popen
|
|
41
|
-
url: str
|
|
42
|
-
internal_url: str
|
|
43
|
-
port: int
|
|
44
|
-
log_id: str
|
|
45
|
-
returncode: Optional[int] = None
|
|
46
|
-
|
|
47
|
-
def is_alive(self) -> bool:
|
|
48
|
-
"""Check if the bot is alive."""
|
|
49
|
-
return self.process.poll() is None
|
|
50
|
-
|
|
51
|
-
def is_status_indicating_alive(self) -> bool:
|
|
52
|
-
"""Check if the status indicates that the bot is alive."""
|
|
53
|
-
return self.status in {BotSessionStatus.QUEUED, BotSessionStatus.RUNNING}
|
|
54
|
-
|
|
55
|
-
def has_died_recently(self) -> bool:
|
|
56
|
-
"""Check if the bot has died recently.
|
|
57
|
-
|
|
58
|
-
Process will indicate that the bot exited,
|
|
59
|
-
but status is not yet updated.
|
|
60
|
-
"""
|
|
61
|
-
return self.is_status_indicating_alive() and not self.is_alive()
|
|
62
|
-
|
|
63
|
-
async def completed_startup_recently(self) -> bool:
|
|
64
|
-
"""Check if the bot has completed startup recently."""
|
|
65
|
-
return self.status == BotSessionStatus.QUEUED and await is_bot_startup_finished(
|
|
66
|
-
self
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
def bot_path(deployment_id: str) -> str:
|
|
71
|
-
"""Return the path to the bot directory for a given deployment id."""
|
|
72
|
-
return os.path.abspath(
|
|
73
|
-
f"{config.SERVER_BASE_WORKING_DIRECTORY}/bots/{deployment_id}"
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
async def is_bot_startup_finished(bot: BotSession) -> bool:
|
|
78
|
-
"""Send a request to the bot to see if the bot is up and running."""
|
|
79
|
-
health_timeout = aiohttp.ClientTimeout(total=5, sock_connect=2, sock_read=3)
|
|
80
|
-
try:
|
|
81
|
-
async with aiohttp.ClientSession(timeout=health_timeout) as session:
|
|
82
|
-
# can't use /status as by default the bot API is not enabled, only
|
|
83
|
-
# the input channel
|
|
84
|
-
async with session.get(f"{bot.internal_url}/license") as resp:
|
|
85
|
-
return resp.status == 200
|
|
86
|
-
except aiohttp.client_exceptions.ClientConnectorError:
|
|
87
|
-
return False
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def set_bot_status_to_stopped(bot: BotSession) -> None:
|
|
91
|
-
"""Set a bots state to stopped."""
|
|
92
|
-
structlogger.info(
|
|
93
|
-
"model_runner.bot_stopped",
|
|
94
|
-
deployment_id=bot.deployment_id,
|
|
95
|
-
status=bot.process.returncode,
|
|
96
|
-
)
|
|
97
|
-
bot.status = BotSessionStatus.STOPPED
|
|
98
|
-
bot.returncode = bot.process.returncode
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
def set_bot_status_to_running(bot: BotSession) -> None:
|
|
102
|
-
"""Set a bots state to running."""
|
|
103
|
-
structlogger.info(
|
|
104
|
-
"model_runner.bot_running",
|
|
105
|
-
deployment_id=bot.deployment_id,
|
|
106
|
-
)
|
|
107
|
-
bot.status = BotSessionStatus.RUNNING
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
def get_open_port() -> int:
|
|
111
|
-
"""Get an open port on the system that is not in use yet."""
|
|
112
|
-
# from https://stackoverflow.com/questions/2838244/get-open-tcp-port-in-python/2838309#2838309
|
|
113
|
-
import socket
|
|
114
|
-
|
|
115
|
-
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
116
|
-
s.bind(("", 0))
|
|
117
|
-
s.listen(1)
|
|
118
|
-
port = s.getsockname()[1]
|
|
119
|
-
s.close()
|
|
120
|
-
return port
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
def write_encoded_config_data_to_files(
|
|
124
|
-
encoded_configs: Dict[str, bytes], base_path: str
|
|
125
|
-
) -> None:
|
|
126
|
-
"""Write the encoded config data to files."""
|
|
127
|
-
for key, value in encoded_configs.items():
|
|
128
|
-
write_encoded_data_to_file(value, subpath(base_path, f"{key}.yml"))
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
def prepare_bot_directory(
|
|
132
|
-
bot_base_path: str,
|
|
133
|
-
model_name: str,
|
|
134
|
-
encoded_configs: Dict[str, str],
|
|
135
|
-
) -> None:
|
|
136
|
-
"""Prepare the bot directory for a new bot session."""
|
|
137
|
-
if not os.path.exists(bot_base_path):
|
|
138
|
-
os.makedirs(bot_base_path, exist_ok=True)
|
|
139
|
-
else:
|
|
140
|
-
shutil.rmtree(bot_base_path, ignore_errors=True)
|
|
141
|
-
|
|
142
|
-
model_file_name = f"{model_name}.{MODEL_ARCHIVE_EXTENSION}"
|
|
143
|
-
model_path = subpath(models_base_path(), model_file_name)
|
|
144
|
-
|
|
145
|
-
if config.SERVER_MODEL_REMOTE_STORAGE and not os.path.exists(model_path):
|
|
146
|
-
fetch_remote_model_to_dir(
|
|
147
|
-
model_file_name,
|
|
148
|
-
models_base_path(),
|
|
149
|
-
config.SERVER_MODEL_REMOTE_STORAGE,
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
if not os.path.exists(model_path):
|
|
153
|
-
raise ModelNotFound(f"Model '{model_file_name}' not found in '{model_path}'.")
|
|
154
|
-
|
|
155
|
-
os.makedirs(subpath(bot_base_path, "models"), exist_ok=True)
|
|
156
|
-
shutil.copy(
|
|
157
|
-
src=model_path,
|
|
158
|
-
dst=subpath(bot_base_path, "models"),
|
|
159
|
-
)
|
|
160
|
-
|
|
161
|
-
write_encoded_config_data_to_files(encoded_configs, bot_base_path)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
def fetch_remote_model_to_dir(
|
|
165
|
-
model_name: str, target_path: str, storage_type: str
|
|
166
|
-
) -> str:
|
|
167
|
-
"""Fetch the model from remote storage.
|
|
168
|
-
|
|
169
|
-
Returns the path to the model diretory.
|
|
170
|
-
"""
|
|
171
|
-
from rasa.core.persistor import get_persistor
|
|
172
|
-
|
|
173
|
-
persistor = get_persistor(storage_type)
|
|
174
|
-
|
|
175
|
-
# we now there must be a persistor, because the config is set
|
|
176
|
-
# this is here to please the type checker for the call below
|
|
177
|
-
assert persistor is not None
|
|
178
|
-
|
|
179
|
-
try:
|
|
180
|
-
return persistor.retrieve(model_name=model_name, target_path=target_path)
|
|
181
|
-
except FileNotFoundError as e:
|
|
182
|
-
raise ModelNotFound() from e
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
def fetch_size_of_remote_model(model_name: str, storage_type: str) -> int:
|
|
186
|
-
"""Fetch the size of the model from remote storage."""
|
|
187
|
-
from rasa.core.persistor import get_persistor
|
|
188
|
-
|
|
189
|
-
persistor = get_persistor(storage_type)
|
|
190
|
-
|
|
191
|
-
# we now there must be a persistor, because the config is set
|
|
192
|
-
# this is here to please the type checker for the call below
|
|
193
|
-
assert persistor is not None
|
|
194
|
-
|
|
195
|
-
return persistor.size_of_persisted_model(model_name=model_name)
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
def start_bot_process(
|
|
199
|
-
deployment_id: str, bot_base_path: str, base_url_path: str
|
|
200
|
-
) -> BotSession:
|
|
201
|
-
port = get_open_port()
|
|
202
|
-
|
|
203
|
-
arguments = [
|
|
204
|
-
"run",
|
|
205
|
-
"--endpoints",
|
|
206
|
-
f"{bot_base_path}/endpoints.yml",
|
|
207
|
-
"--credentials",
|
|
208
|
-
f"{bot_base_path}/credentials.yml",
|
|
209
|
-
"--debug",
|
|
210
|
-
f"--port={port}",
|
|
211
|
-
"--model",
|
|
212
|
-
f"{bot_base_path}/models",
|
|
213
|
-
]
|
|
214
|
-
|
|
215
|
-
structlogger.debug(
|
|
216
|
-
"model_runner.bot.starting_command",
|
|
217
|
-
deployment_id=deployment_id,
|
|
218
|
-
arguments=" ".join(arguments),
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
warm_process = start_rasa_process(cwd=bot_base_path, arguments=arguments)
|
|
222
|
-
|
|
223
|
-
internal_bot_url = f"http://localhost:{port}"
|
|
224
|
-
|
|
225
|
-
structlogger.info(
|
|
226
|
-
"model_runner.bot.starting",
|
|
227
|
-
deployment_id=deployment_id,
|
|
228
|
-
log=logs_path(warm_process.log_id),
|
|
229
|
-
url=internal_bot_url,
|
|
230
|
-
port=port,
|
|
231
|
-
pid=warm_process.process.pid,
|
|
232
|
-
)
|
|
233
|
-
|
|
234
|
-
return BotSession(
|
|
235
|
-
deployment_id=deployment_id,
|
|
236
|
-
status=BotSessionStatus.QUEUED,
|
|
237
|
-
process=warm_process.process,
|
|
238
|
-
url=f"{base_url_path}?deployment_id={deployment_id}",
|
|
239
|
-
internal_url=internal_bot_url,
|
|
240
|
-
port=port,
|
|
241
|
-
log_id=warm_process.log_id,
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
def run_bot(
|
|
246
|
-
deployment_id: str,
|
|
247
|
-
model_name: str,
|
|
248
|
-
base_url_path: str,
|
|
249
|
-
encoded_configs: Dict[str, str],
|
|
250
|
-
) -> BotSession:
|
|
251
|
-
"""Deploy a bot based on a given training id."""
|
|
252
|
-
with structlog.contextvars.bound_contextvars(model_name=model_name):
|
|
253
|
-
bot_base_path = bot_path(deployment_id)
|
|
254
|
-
prepare_bot_directory(bot_base_path, model_name, encoded_configs)
|
|
255
|
-
|
|
256
|
-
return start_bot_process(deployment_id, bot_base_path, base_url_path)
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
async def update_bot_status(bot: BotSession) -> None:
|
|
260
|
-
"""Update the status of a bot based on the process return code."""
|
|
261
|
-
if bot.has_died_recently():
|
|
262
|
-
set_bot_status_to_stopped(bot)
|
|
263
|
-
elif await bot.completed_startup_recently():
|
|
264
|
-
set_bot_status_to_running(bot)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
def terminate_bot(bot: BotSession) -> None:
|
|
268
|
-
"""Terminate the bot process."""
|
|
269
|
-
if not bot.is_status_indicating_alive():
|
|
270
|
-
# if the bot is not running, we don't need to terminate it
|
|
271
|
-
return
|
|
272
|
-
|
|
273
|
-
try:
|
|
274
|
-
bot.process.terminate()
|
|
275
|
-
structlogger.info(
|
|
276
|
-
"model_runner.stop_bot.stopped",
|
|
277
|
-
deployment_id=bot.deployment_id,
|
|
278
|
-
status=bot.process.returncode,
|
|
279
|
-
)
|
|
280
|
-
bot.status = BotSessionStatus.STOPPED
|
|
281
|
-
bot.returncode = bot.process.returncode
|
|
282
|
-
except ProcessLookupError:
|
|
283
|
-
structlogger.debug(
|
|
284
|
-
"model_runner.stop_bot.process_not_found",
|
|
285
|
-
deployment_id=bot.deployment_id,
|
|
286
|
-
)
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
from typing import Any, Dict, Optional
|
|
2
|
-
|
|
3
|
-
from socketio import AsyncServer
|
|
4
|
-
import structlog
|
|
5
|
-
from socketio.asyncio_client import AsyncClient
|
|
6
|
-
|
|
7
|
-
from rasa.model_manager.runner_service import BotSession
|
|
8
|
-
from rasa.model_manager.studio_jwt_auth import (
|
|
9
|
-
UserToServiceAuthenticationError,
|
|
10
|
-
authenticate_user_to_service,
|
|
11
|
-
)
|
|
12
|
-
|
|
13
|
-
structlogger = structlog.get_logger()
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
# A simple in-memory store for active chat connections to studio frontend
|
|
17
|
-
socket_proxy_clients = {}
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
async def socketio_websocket_traffic_wrapper(
|
|
21
|
-
sio: AsyncServer,
|
|
22
|
-
running_bots: Dict[str, BotSession],
|
|
23
|
-
sid: str,
|
|
24
|
-
auth: Optional[Dict],
|
|
25
|
-
) -> bool:
|
|
26
|
-
"""Wrapper for bridging the user chat websocket and the bot server."""
|
|
27
|
-
auth_token = auth.get("token") if auth else None
|
|
28
|
-
|
|
29
|
-
if auth_token is None:
|
|
30
|
-
structlogger.error("model_runner.user_no_token", sid=sid)
|
|
31
|
-
return False
|
|
32
|
-
|
|
33
|
-
try:
|
|
34
|
-
authenticate_user_to_service(auth_token)
|
|
35
|
-
structlogger.debug("model_runner.user_authenticated_successfully", sid=sid)
|
|
36
|
-
except UserToServiceAuthenticationError as error:
|
|
37
|
-
structlogger.error(
|
|
38
|
-
"model_runner.user_authentication_failed", sid=sid, error=str(error)
|
|
39
|
-
)
|
|
40
|
-
return False
|
|
41
|
-
|
|
42
|
-
deployment_id = auth.get("deployment_id") if auth else None
|
|
43
|
-
|
|
44
|
-
if deployment_id is None:
|
|
45
|
-
structlogger.error("model_runner.bot_no_deployment_id", sid=sid)
|
|
46
|
-
return False
|
|
47
|
-
|
|
48
|
-
bot = running_bots.get(deployment_id)
|
|
49
|
-
if bot is None:
|
|
50
|
-
structlogger.error("model_runner.bot_not_found", deployment_id=deployment_id)
|
|
51
|
-
return False
|
|
52
|
-
|
|
53
|
-
if not bot.is_alive():
|
|
54
|
-
structlogger.error("model_runner.bot_not_alive", deployment_id=deployment_id)
|
|
55
|
-
return False
|
|
56
|
-
|
|
57
|
-
client = await create_bridge_client(sio, bot.internal_url, sid, deployment_id)
|
|
58
|
-
|
|
59
|
-
if client.sid is not None:
|
|
60
|
-
structlogger.debug(
|
|
61
|
-
"model_runner.bot_connection_established", deployment_id=deployment_id
|
|
62
|
-
)
|
|
63
|
-
socket_proxy_clients[sid] = client
|
|
64
|
-
return True
|
|
65
|
-
else:
|
|
66
|
-
structlogger.error(
|
|
67
|
-
"model_runner.bot_connection_failed", deployment_id=deployment_id
|
|
68
|
-
)
|
|
69
|
-
return False
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def create_bridge_server(sio: AsyncServer, running_bots: Dict[str, BotSession]) -> None:
|
|
73
|
-
"""Create handlers for the socket server side.
|
|
74
|
-
|
|
75
|
-
Forwards messages coming from the user to the bot.
|
|
76
|
-
"""
|
|
77
|
-
|
|
78
|
-
@sio.on("connect")
|
|
79
|
-
async def socketio_websocket_traffic(
|
|
80
|
-
sid: str, environ: Dict, auth: Optional[Dict]
|
|
81
|
-
) -> bool:
|
|
82
|
-
"""Bridge websockets between user chat socket and bot server."""
|
|
83
|
-
return await socketio_websocket_traffic_wrapper(sio, running_bots, sid, auth)
|
|
84
|
-
|
|
85
|
-
@sio.on("disconnect")
|
|
86
|
-
async def disconnect(sid: str) -> None:
|
|
87
|
-
"""Disconnect the bot connection."""
|
|
88
|
-
structlogger.debug("model_runner.bot_disconnect", sid=sid)
|
|
89
|
-
if sid in socket_proxy_clients:
|
|
90
|
-
await socket_proxy_clients[sid].disconnect()
|
|
91
|
-
del socket_proxy_clients[sid]
|
|
92
|
-
|
|
93
|
-
@sio.on("*")
|
|
94
|
-
async def handle_message(event: str, sid: str, data: Dict[str, Any]) -> None:
|
|
95
|
-
""" "Bridge messages between user and bot.
|
|
96
|
-
|
|
97
|
-
Both incoming user messages to the bot_url and
|
|
98
|
-
bot responses sent back to the client need to
|
|
99
|
-
happen in parallel in an async way.
|
|
100
|
-
"""
|
|
101
|
-
client = socket_proxy_clients.get(sid)
|
|
102
|
-
if client is None:
|
|
103
|
-
structlogger.error("model_runner.bot_not_connected", sid=sid)
|
|
104
|
-
return
|
|
105
|
-
|
|
106
|
-
await client.emit(event, data)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
async def create_bridge_client(
|
|
110
|
-
sio: AsyncServer, url: str, sid: str, deployment_id: str
|
|
111
|
-
) -> AsyncClient:
|
|
112
|
-
"""Create a new socket bridge client.
|
|
113
|
-
|
|
114
|
-
Forwards messages comming from the bot to the user.
|
|
115
|
-
"""
|
|
116
|
-
client = AsyncClient()
|
|
117
|
-
|
|
118
|
-
await client.connect(url)
|
|
119
|
-
|
|
120
|
-
@client.event # type: ignore[misc]
|
|
121
|
-
async def session_confirm(data: Dict[str, Any]) -> None:
|
|
122
|
-
structlogger.debug(
|
|
123
|
-
"model_runner.bot_session_confirmed", deployment_id=deployment_id
|
|
124
|
-
)
|
|
125
|
-
await sio.emit("session_confirm", room=sid)
|
|
126
|
-
|
|
127
|
-
@client.event # type: ignore[misc]
|
|
128
|
-
async def bot_message(data: Dict[str, Any]) -> None:
|
|
129
|
-
structlogger.debug("model_runner.bot_message", deployment_id=deployment_id)
|
|
130
|
-
await sio.emit("bot_message", data, room=sid)
|
|
131
|
-
|
|
132
|
-
@client.event # type: ignore[misc]
|
|
133
|
-
async def disconnect() -> None:
|
|
134
|
-
structlogger.debug(
|
|
135
|
-
"model_runner.bot_connection_closed", deployment_id=deployment_id
|
|
136
|
-
)
|
|
137
|
-
await sio.emit("disconnect", room=sid)
|
|
138
|
-
|
|
139
|
-
@client.event # type: ignore[misc]
|
|
140
|
-
async def connect_error() -> None:
|
|
141
|
-
structlogger.error(
|
|
142
|
-
"model_runner.bot_connection_error", deployment_id=deployment_id
|
|
143
|
-
)
|
|
144
|
-
await sio.emit("disconnect", room=sid)
|
|
145
|
-
|
|
146
|
-
return client
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from functools import cache
|
|
3
|
-
from http import HTTPStatus
|
|
4
|
-
from typing import Any, Dict, Optional
|
|
5
|
-
|
|
6
|
-
import jwt
|
|
7
|
-
import requests
|
|
8
|
-
import structlog
|
|
9
|
-
from rasa.shared.exceptions import RasaException
|
|
10
|
-
|
|
11
|
-
structlogger = structlog.get_logger()
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
AUTH_URL = os.getenv("KEYCLOAK_URL", "http://localhost:8081/auth")
|
|
15
|
-
|
|
16
|
-
AUTH_REALM = os.getenv("KEYCLOAK_REALM", "rasa-studio")
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class UserToServiceAuthenticationError(RasaException):
|
|
20
|
-
"""Raised when the user authentication fails."""
|
|
21
|
-
|
|
22
|
-
def __init__(self, message: str) -> None:
|
|
23
|
-
self.message = message
|
|
24
|
-
|
|
25
|
-
def __str__(self) -> str:
|
|
26
|
-
return f"{self.__class__.__name__}: {self.message}"
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
@cache
|
|
30
|
-
def get_public_key_from_keycloak() -> Optional[str]:
|
|
31
|
-
"""Fetch the public key from the keycloak server."""
|
|
32
|
-
realm_url = f"{AUTH_URL}/realms/{AUTH_REALM}"
|
|
33
|
-
|
|
34
|
-
try:
|
|
35
|
-
response = requests.get(realm_url)
|
|
36
|
-
except requests.RequestException as error:
|
|
37
|
-
structlogger.error("model_api.auth.keycloak_request_failed", error=str(error))
|
|
38
|
-
return None
|
|
39
|
-
|
|
40
|
-
if response.status_code != HTTPStatus.OK:
|
|
41
|
-
structlogger.error(
|
|
42
|
-
"model_api.auth.keycloak_public_key_fetch_failed",
|
|
43
|
-
status_code=response.status_code,
|
|
44
|
-
response=response.text,
|
|
45
|
-
)
|
|
46
|
-
return None
|
|
47
|
-
|
|
48
|
-
public_key = response.json().get("public_key")
|
|
49
|
-
|
|
50
|
-
if public_key is None:
|
|
51
|
-
structlogger.error(
|
|
52
|
-
"model_runner.keycloak_public_key_not_found",
|
|
53
|
-
response=response.text,
|
|
54
|
-
)
|
|
55
|
-
return None
|
|
56
|
-
|
|
57
|
-
public_key = f"-----BEGIN PUBLIC KEY-----\n{public_key}\n-----END PUBLIC KEY-----"
|
|
58
|
-
return public_key
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def authenticate_user_to_service(token: str) -> Dict[str, Any]:
|
|
62
|
-
"""Authenticate the user to the model service."""
|
|
63
|
-
if not token:
|
|
64
|
-
structlogger.debug("model_api.auth.no_token_provided")
|
|
65
|
-
raise UserToServiceAuthenticationError("No token provided.")
|
|
66
|
-
|
|
67
|
-
public_key = get_public_key_from_keycloak()
|
|
68
|
-
|
|
69
|
-
if public_key is None:
|
|
70
|
-
raise UserToServiceAuthenticationError(
|
|
71
|
-
"Failed to fetch public key from keycloak."
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
try:
|
|
75
|
-
return jwt.decode(
|
|
76
|
-
token,
|
|
77
|
-
public_key,
|
|
78
|
-
algorithms=["RS256", "HS256", "HS512", "ES256"],
|
|
79
|
-
audience="account",
|
|
80
|
-
)
|
|
81
|
-
except jwt.InvalidKeyError as error:
|
|
82
|
-
structlogger.info("model_api.auth.invalid_jwt_key", error=str(error))
|
|
83
|
-
raise UserToServiceAuthenticationError("Invalid JWT key.")
|
|
84
|
-
except jwt.InvalidTokenError as error:
|
|
85
|
-
structlogger.info("model_api.auth.invalid_jwt_token", error=str(error))
|
|
86
|
-
raise UserToServiceAuthenticationError("Invalid JWT token.") from error
|