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.

Files changed (220) hide show
  1. README.md +396 -17
  2. rasa/__main__.py +15 -31
  3. rasa/api.py +1 -5
  4. rasa/cli/arguments/default_arguments.py +2 -1
  5. rasa/cli/arguments/shell.py +1 -5
  6. rasa/cli/arguments/train.py +0 -14
  7. rasa/cli/e2e_test.py +1 -1
  8. rasa/cli/evaluate.py +8 -8
  9. rasa/cli/inspect.py +7 -15
  10. rasa/cli/interactive.py +0 -1
  11. rasa/cli/llm_fine_tuning.py +1 -1
  12. rasa/cli/project_templates/calm/config.yml +7 -5
  13. rasa/cli/project_templates/calm/endpoints.yml +2 -15
  14. rasa/cli/project_templates/tutorial/config.yml +5 -8
  15. rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
  16. rasa/cli/project_templates/tutorial/data/patterns.yml +0 -5
  17. rasa/cli/project_templates/tutorial/domain.yml +0 -14
  18. rasa/cli/project_templates/tutorial/endpoints.yml +0 -5
  19. rasa/cli/run.py +1 -1
  20. rasa/cli/scaffold.py +2 -4
  21. rasa/cli/studio/studio.py +8 -18
  22. rasa/cli/studio/upload.py +15 -0
  23. rasa/cli/train.py +0 -3
  24. rasa/cli/utils.py +1 -6
  25. rasa/cli/x.py +8 -8
  26. rasa/constants.py +1 -3
  27. rasa/core/actions/action.py +33 -75
  28. rasa/core/actions/e2e_stub_custom_action_executor.py +1 -5
  29. rasa/core/actions/http_custom_action_executor.py +0 -4
  30. rasa/core/channels/__init__.py +0 -2
  31. rasa/core/channels/channel.py +0 -20
  32. rasa/core/channels/development_inspector.py +3 -10
  33. rasa/core/channels/inspector/dist/assets/{arc-bc141fb2.js → arc-86942a71.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-be2db283.js → c4Diagram-d0fbc5ce-b0290676.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-55366915.js → classDiagram-936ed81e-f6405f6e.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-bb529518.js → classDiagram-v2-c3cb15f1-ef61ac77.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-b0ec81d6.js → createText-62fc7601-f0411e58.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-6166330c.js → edges-f2ad444c-7dcc4f3b.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-5ccc6a8e.js → erDiagram-9d236eb7-e0c092d7.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-fca3bfe4.js → flowDb-1972c806-fba2e3ce.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4739080f.js → flowDiagram-7ea5b25a-7a70b71a.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-24a5f41a.js +1 -0
  43. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-7c1b0e0f.js → flowchart-elk-definition-abe16c3d-00a59b68.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-772fd050.js → ganttDiagram-9b5ea136-293c91fa.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-8eae1dc9.js → gitGraphDiagram-99d0ae7c-07b2d68c.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-f55afcdf.js → index-2c4b9a3b-bc959fbd.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{index-e7cef9de.js → index-3a8a5a28.js} +143 -143
  48. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-124d4a14.js → infoDiagram-736b4530-4a350f72.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-7c4fae44.js → journeyDiagram-df861f2b-af464fb7.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{layout-b9885fb6.js → layout-0071f036.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{line-7c59abb6.js → line-2f73cc83.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{linear-4776f780.js → linear-f014b4cc.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2332c46c.js → mindmap-definition-beec6740-d2426fb6.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-8fb39303.js → pieDiagram-dbbf0591-776f01a2.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3c7180a2.js → quadrantDiagram-4d7f4fd6-82e00b57.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-e910bcb8.js → requirementDiagram-6fc4c22a-ea13c6bb.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-ead16c89.js → sankeyDiagram-8f13d901-1feca7e9.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-29a02a19.js → sequenceDiagram-b655622a-070c61d2.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-042b3137.js → stateDiagram-59f0c015-24f46263.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-2178c0f3.js → stateDiagram-v2-2b26beab-c9056051.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-23ffa4fc.js → styles-080da4f6-08abc34a.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-94f59763.js → styles-3dcbcfbf-bc74c25a.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-78a6bebc.js → styles-9c745c82-4e5d66de.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-eae2a6f6.js → svgDrawCommon-4835440b-849c4517.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-5c968d92.js → timeline-definition-5b62e21b-d0fb1598.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-fd3db0d5.js → xychartDiagram-2b33534f-04d115e2.js} +1 -1
  67. rasa/core/channels/inspector/dist/index.html +1 -1
  68. rasa/core/channels/inspector/src/App.tsx +1 -1
  69. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +3 -6
  70. rasa/core/channels/socketio.py +2 -7
  71. rasa/core/channels/telegram.py +1 -1
  72. rasa/core/channels/twilio.py +1 -1
  73. rasa/core/channels/voice_ready/audiocodes.py +4 -15
  74. rasa/core/channels/voice_ready/jambonz.py +4 -15
  75. rasa/core/channels/voice_ready/twilio_voice.py +21 -6
  76. rasa/core/channels/voice_ready/utils.py +5 -6
  77. rasa/core/channels/voice_stream/asr/asr_engine.py +1 -19
  78. rasa/core/channels/voice_stream/asr/asr_event.py +0 -5
  79. rasa/core/channels/voice_stream/asr/deepgram.py +15 -28
  80. rasa/core/channels/voice_stream/audio_bytes.py +0 -1
  81. rasa/core/channels/voice_stream/tts/azure.py +3 -9
  82. rasa/core/channels/voice_stream/tts/cartesia.py +8 -12
  83. rasa/core/channels/voice_stream/tts/tts_engine.py +1 -11
  84. rasa/core/channels/voice_stream/twilio_media_streams.py +19 -28
  85. rasa/core/channels/voice_stream/util.py +4 -4
  86. rasa/core/channels/voice_stream/voice_channel.py +42 -222
  87. rasa/core/featurizers/single_state_featurizer.py +1 -22
  88. rasa/core/featurizers/tracker_featurizers.py +18 -115
  89. rasa/core/information_retrieval/qdrant.py +0 -1
  90. rasa/core/nlg/contextual_response_rephraser.py +25 -44
  91. rasa/core/persistor.py +34 -191
  92. rasa/core/policies/enterprise_search_policy.py +60 -119
  93. rasa/core/policies/flows/flow_executor.py +4 -7
  94. rasa/core/policies/intentless_policy.py +22 -82
  95. rasa/core/policies/ted_policy.py +33 -58
  96. rasa/core/policies/unexpected_intent_policy.py +7 -15
  97. rasa/core/processor.py +13 -89
  98. rasa/core/run.py +2 -2
  99. rasa/core/training/interactive.py +35 -34
  100. rasa/core/utils.py +22 -58
  101. rasa/dialogue_understanding/coexistence/llm_based_router.py +12 -39
  102. rasa/dialogue_understanding/commands/__init__.py +0 -4
  103. rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
  104. rasa/dialogue_understanding/commands/utils.py +0 -5
  105. rasa/dialogue_understanding/generator/constants.py +0 -2
  106. rasa/dialogue_understanding/generator/flow_retrieval.py +4 -49
  107. rasa/dialogue_understanding/generator/llm_based_command_generator.py +23 -37
  108. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -57
  109. rasa/dialogue_understanding/generator/nlu_command_adapter.py +1 -19
  110. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +0 -3
  111. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +10 -90
  112. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -53
  113. rasa/dialogue_understanding/processor/command_processor.py +1 -21
  114. rasa/e2e_test/assertions.py +16 -133
  115. rasa/e2e_test/assertions_schema.yml +0 -23
  116. rasa/e2e_test/e2e_test_case.py +6 -85
  117. rasa/e2e_test/e2e_test_runner.py +4 -6
  118. rasa/e2e_test/utils/io.py +1 -3
  119. rasa/engine/loader.py +0 -12
  120. rasa/engine/validation.py +11 -541
  121. rasa/keys +1 -0
  122. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
  123. rasa/model_training.py +7 -29
  124. rasa/nlu/classifiers/diet_classifier.py +25 -38
  125. rasa/nlu/classifiers/logistic_regression_classifier.py +9 -22
  126. rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
  127. rasa/nlu/extractors/crf_entity_extractor.py +50 -93
  128. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +16 -45
  129. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
  130. rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
  131. rasa/nlu/tokenizers/whitespace_tokenizer.py +14 -3
  132. rasa/server.py +1 -3
  133. rasa/shared/constants.py +0 -61
  134. rasa/shared/core/constants.py +0 -9
  135. rasa/shared/core/domain.py +5 -8
  136. rasa/shared/core/flows/flow.py +0 -5
  137. rasa/shared/core/flows/flows_list.py +1 -5
  138. rasa/shared/core/flows/flows_yaml_schema.json +0 -10
  139. rasa/shared/core/flows/validation.py +0 -96
  140. rasa/shared/core/flows/yaml_flows_io.py +4 -13
  141. rasa/shared/core/slots.py +0 -5
  142. rasa/shared/importers/importer.py +2 -19
  143. rasa/shared/importers/rasa.py +1 -5
  144. rasa/shared/nlu/training_data/features.py +2 -120
  145. rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -18
  146. rasa/shared/providers/_configs/azure_openai_client_config.py +3 -5
  147. rasa/shared/providers/_configs/openai_client_config.py +1 -1
  148. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +0 -1
  149. rasa/shared/providers/_configs/utils.py +0 -16
  150. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +29 -18
  151. rasa/shared/providers/embedding/azure_openai_embedding_client.py +21 -54
  152. rasa/shared/providers/embedding/default_litellm_embedding_client.py +0 -24
  153. rasa/shared/providers/llm/_base_litellm_client.py +31 -63
  154. rasa/shared/providers/llm/azure_openai_llm_client.py +29 -50
  155. rasa/shared/providers/llm/default_litellm_llm_client.py +0 -24
  156. rasa/shared/providers/llm/self_hosted_llm_client.py +29 -17
  157. rasa/shared/providers/mappings.py +0 -19
  158. rasa/shared/utils/common.py +2 -37
  159. rasa/shared/utils/io.py +6 -28
  160. rasa/shared/utils/llm.py +46 -353
  161. rasa/shared/utils/yaml.py +82 -181
  162. rasa/studio/auth.py +5 -3
  163. rasa/studio/config.py +4 -13
  164. rasa/studio/constants.py +0 -1
  165. rasa/studio/data_handler.py +4 -13
  166. rasa/studio/upload.py +80 -175
  167. rasa/telemetry.py +17 -94
  168. rasa/tracing/config.py +1 -3
  169. rasa/tracing/instrumentation/attribute_extractors.py +17 -94
  170. rasa/tracing/instrumentation/instrumentation.py +0 -121
  171. rasa/utils/common.py +0 -5
  172. rasa/utils/endpoints.py +1 -27
  173. rasa/utils/io.py +81 -7
  174. rasa/utils/log_utils.py +2 -9
  175. rasa/utils/tensorflow/model_data.py +193 -2
  176. rasa/validator.py +4 -110
  177. rasa/version.py +1 -1
  178. rasa_pro-3.11.0a1.dist-info/METADATA +576 -0
  179. {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/RECORD +182 -216
  180. rasa/core/actions/action_repeat_bot_messages.py +0 -89
  181. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +0 -1
  182. rasa/core/channels/inspector/src/helpers/audiostream.ts +0 -165
  183. rasa/core/channels/voice_stream/asr/azure.py +0 -129
  184. rasa/core/channels/voice_stream/browser_audio.py +0 -107
  185. rasa/core/channels/voice_stream/call_state.py +0 -23
  186. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +0 -60
  187. rasa/dialogue_understanding/commands/user_silence_command.py +0 -59
  188. rasa/dialogue_understanding/patterns/repeat.py +0 -37
  189. rasa/dialogue_understanding/patterns/user_silence.py +0 -37
  190. rasa/model_manager/__init__.py +0 -0
  191. rasa/model_manager/config.py +0 -40
  192. rasa/model_manager/model_api.py +0 -559
  193. rasa/model_manager/runner_service.py +0 -286
  194. rasa/model_manager/socket_bridge.py +0 -146
  195. rasa/model_manager/studio_jwt_auth.py +0 -86
  196. rasa/model_manager/trainer_service.py +0 -325
  197. rasa/model_manager/utils.py +0 -87
  198. rasa/model_manager/warm_rasa_process.py +0 -187
  199. rasa/model_service.py +0 -112
  200. rasa/shared/core/flows/utils.py +0 -39
  201. rasa/shared/providers/_configs/litellm_router_client_config.py +0 -220
  202. rasa/shared/providers/_configs/model_group_config.py +0 -167
  203. rasa/shared/providers/_configs/rasa_llm_client_config.py +0 -73
  204. rasa/shared/providers/_utils.py +0 -79
  205. rasa/shared/providers/embedding/litellm_router_embedding_client.py +0 -135
  206. rasa/shared/providers/llm/litellm_router_llm_client.py +0 -182
  207. rasa/shared/providers/llm/rasa_llm_client.py +0 -112
  208. rasa/shared/providers/router/__init__.py +0 -0
  209. rasa/shared/providers/router/_base_litellm_router_client.py +0 -183
  210. rasa/shared/providers/router/router_client.py +0 -73
  211. rasa/shared/utils/health_check/__init__.py +0 -0
  212. rasa/shared/utils/health_check/embeddings_health_check_mixin.py +0 -31
  213. rasa/shared/utils/health_check/health_check.py +0 -258
  214. rasa/shared/utils/health_check/llm_health_check_mixin.py +0 -31
  215. rasa/utils/sanic_error_handler.py +0 -32
  216. rasa/utils/tensorflow/feature_array.py +0 -366
  217. rasa_pro-3.11.0.dist-info/METADATA +0 -198
  218. {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/NOTICE +0 -0
  219. {rasa_pro-3.11.0.dist-info → rasa_pro-3.11.0a1.dist-info}/WHEEL +0 -0
  220. {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