rasa-pro 3.10.6__py3-none-any.whl → 3.10.7.dev1__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 (61) hide show
  1. rasa/api.py +8 -2
  2. rasa/cli/arguments/default_arguments.py +23 -2
  3. rasa/cli/arguments/run.py +2 -0
  4. rasa/cli/inspect.py +5 -2
  5. rasa/cli/run.py +7 -0
  6. rasa/cli/train.py +9 -4
  7. rasa/cli/utils.py +3 -3
  8. rasa/core/agent.py +2 -2
  9. rasa/core/brokers/kafka.py +3 -1
  10. rasa/core/brokers/pika.py +3 -1
  11. rasa/core/channels/voice_aware/utils.py +6 -5
  12. rasa/core/nlg/contextual_response_rephraser.py +11 -2
  13. rasa/{nlu → core}/persistor.py +1 -1
  14. rasa/core/policies/enterprise_search_policy.py +11 -2
  15. rasa/core/policies/intentless_policy.py +9 -2
  16. rasa/core/run.py +2 -1
  17. rasa/core/secrets_manager/constants.py +4 -0
  18. rasa/core/secrets_manager/factory.py +8 -0
  19. rasa/core/secrets_manager/vault.py +11 -1
  20. rasa/core/utils.py +30 -19
  21. rasa/dialogue_understanding/coexistence/llm_based_router.py +9 -2
  22. rasa/dialogue_understanding/generator/llm_based_command_generator.py +11 -2
  23. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  24. rasa/e2e_test/e2e_test_runner.py +1 -1
  25. rasa/engine/graph.py +0 -1
  26. rasa/engine/recipes/config_files/default_config.yml +0 -3
  27. rasa/engine/recipes/default_recipe.py +0 -1
  28. rasa/engine/recipes/graph_recipe.py +0 -1
  29. rasa/engine/storage/local_model_storage.py +0 -1
  30. rasa/engine/storage/storage.py +1 -5
  31. rasa/model_manager/__init__.py +0 -0
  32. rasa/model_manager/config.py +7 -0
  33. rasa/model_manager/model_api.py +424 -0
  34. rasa/model_manager/runner_service.py +185 -0
  35. rasa/model_manager/socket_bridge.py +44 -0
  36. rasa/model_manager/trainer_service.py +240 -0
  37. rasa/model_manager/utils.py +27 -0
  38. rasa/model_service.py +43 -0
  39. rasa/model_training.py +11 -6
  40. rasa/server.py +1 -1
  41. rasa/shared/constants.py +2 -0
  42. rasa/shared/core/domain.py +101 -47
  43. rasa/shared/core/flows/flows_list.py +19 -6
  44. rasa/shared/core/flows/validation.py +25 -0
  45. rasa/shared/core/flows/yaml_flows_io.py +3 -24
  46. rasa/shared/importers/importer.py +32 -32
  47. rasa/shared/importers/multi_project.py +23 -11
  48. rasa/shared/importers/rasa.py +7 -2
  49. rasa/shared/importers/remote_importer.py +2 -2
  50. rasa/shared/importers/utils.py +3 -1
  51. rasa/shared/nlu/training_data/training_data.py +18 -19
  52. rasa/shared/utils/common.py +3 -22
  53. rasa/shared/utils/llm.py +28 -2
  54. rasa/shared/utils/schemas/model_config.yml +0 -10
  55. rasa/tracing/instrumentation/attribute_extractors.py +1 -1
  56. rasa/version.py +1 -1
  57. {rasa_pro-3.10.6.dist-info → rasa_pro-3.10.7.dev1.dist-info}/METADATA +2 -2
  58. {rasa_pro-3.10.6.dist-info → rasa_pro-3.10.7.dev1.dist-info}/RECORD +61 -53
  59. {rasa_pro-3.10.6.dist-info → rasa_pro-3.10.7.dev1.dist-info}/NOTICE +0 -0
  60. {rasa_pro-3.10.6.dist-info → rasa_pro-3.10.7.dev1.dist-info}/WHEEL +0 -0
  61. {rasa_pro-3.10.6.dist-info → rasa_pro-3.10.7.dev1.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,240 @@
1
+ import os
2
+ from typing import Any, Dict, Optional
3
+ import shutil
4
+ import base64
5
+ import structlog
6
+ import subprocess
7
+ from dataclasses import dataclass
8
+
9
+ from rasa.model_manager.config import RASA_PYTHON_PATH, SERVER_BASE_WORKING_DIRECTORY
10
+ from rasa.model_manager.utils import logs_path
11
+
12
+ structlogger = structlog.get_logger()
13
+
14
+
15
+ @dataclass
16
+ class TrainingSession:
17
+ """Store information about a training session."""
18
+
19
+ training_id: str
20
+ assistant_id: str
21
+ client_id: Optional[str]
22
+ progress: int
23
+ status: str
24
+ process: subprocess.Popen
25
+
26
+
27
+ def train_path(training_id: str) -> str:
28
+ """Return the path to the training directory for a given training id."""
29
+ return os.path.abspath(f"{SERVER_BASE_WORKING_DIRECTORY}/trainings/{training_id}")
30
+
31
+
32
+ def cache_for_assistant_path(assistant_id: str) -> str:
33
+ """Return the path to the cache directory for a given assistant id."""
34
+ return os.path.abspath(f"{SERVER_BASE_WORKING_DIRECTORY}/caches/{assistant_id}")
35
+
36
+
37
+ def write_encoded_data_to_file(encoded_data: bytes, file: str) -> None:
38
+ """Write base64 encoded data to a file."""
39
+ # create the directory if it does not exist of the parent directory
40
+ os.makedirs(os.path.dirname(file), exist_ok=True)
41
+
42
+ with open(file, "w") as f:
43
+ decoded = base64.b64decode(encoded_data)
44
+ text = decoded.decode("utf-8")
45
+ f.write(text)
46
+
47
+
48
+ def terminate_training(training: TrainingSession) -> None:
49
+ if training.status == "running":
50
+ structlogger.info(
51
+ "model_trainer.user_stopping_training", training_id=training.training_id
52
+ )
53
+ try:
54
+ training.process.terminate()
55
+ training.status = "stopped"
56
+ except ProcessLookupError:
57
+ structlogger.debug(
58
+ "model_trainer.training_process_not_found",
59
+ training_id=training.training_id,
60
+ )
61
+
62
+
63
+ def update_training_status(training: TrainingSession) -> None:
64
+ if training.status != "running":
65
+ # skip if the training is not running
66
+ return
67
+ if training.process.poll() is None:
68
+ # process is still running
69
+ return
70
+
71
+ complete_training(training)
72
+
73
+
74
+ def complete_training(training: TrainingSession) -> None:
75
+ """Complete a training session.
76
+
77
+ Transitions the status of a training process to "done" if the process has
78
+ finished successfully, and to "error" if the process has finished with an
79
+ error.
80
+ """
81
+ training.status = "done" if training.process.returncode == 0 else "error"
82
+ training.progress = 100
83
+
84
+ structlogger.info(
85
+ "model_trainer.training_finished",
86
+ training_id=training.training_id,
87
+ status=training.status,
88
+ )
89
+
90
+ # persist the assistant cache to speed up future training runs for this
91
+ # assistant
92
+ persist_rasa_cache(training.assistant_id, train_path(training.training_id))
93
+
94
+
95
+ def seed_training_directory_with_rasa_cache(
96
+ training_base_path: str, assistant_id: str
97
+ ) -> None:
98
+ """Populate the training directory with the cache of a previous training."""
99
+ # check if there is a cache for this assistant
100
+ cache_path = cache_for_assistant_path(assistant_id)
101
+
102
+ if os.path.exists(cache_path):
103
+ structlogger.debug(
104
+ "model_trainer.populating_training_dir_with_cache",
105
+ assistant_id=assistant_id,
106
+ training_base_path=training_base_path,
107
+ )
108
+ # copy the cache to the training directory
109
+ shutil.copytree(cache_path, f"{training_base_path}/.rasa")
110
+
111
+
112
+ def persist_rasa_cache(assistant_id: str, training_base_path: str) -> None:
113
+ """Persist the cache of a training session to speed up future trainings."""
114
+ # copy the cache from the training directory to the cache directory
115
+ # cache files are stored inside of `/.rasa/` of the training folder
116
+ structlogger.debug(
117
+ "model_trainer.persisting_assistant_cache", assistant_id=assistant_id
118
+ )
119
+ cache_path = cache_for_assistant_path(assistant_id)
120
+ # clean up the cache directory first
121
+ shutil.rmtree(cache_path, ignore_errors=True)
122
+ shutil.copytree(f"{training_base_path}/.rasa", cache_path)
123
+
124
+
125
+ def write_training_data_to_files(
126
+ encoded_training_data: Dict[str, Any], training_base_path: str
127
+ ) -> None:
128
+ """Write the training data to files in the training directory.
129
+
130
+ Incoming data format, all keys being optional:
131
+ ````
132
+ {
133
+ "domain": "base64 encoded domain.yml",
134
+ "credentials": "base64 encoded credentials.yml",
135
+ "endpoints": "base64 encoded endpoints.yml",
136
+ "flows": "base64 encoded flows.yml",
137
+ "config": "base64 encoded config.yml",
138
+ "stories": "base64 encoded stories.yml",
139
+ "rules": "base64 encoded rules.yml",
140
+ "nlu": "base64 encoded nlu.yml"
141
+ }
142
+ ```
143
+ """
144
+ data_to_be_written_to_files = {
145
+ "domain": "domain.yml",
146
+ "credentials": "credentials.yml",
147
+ "endpoints": "endpoints.yml",
148
+ "flows": "data/flows.yml",
149
+ "config": "config.yml",
150
+ "stories": "data/stories.yml",
151
+ "rules": "data/rules.yml",
152
+ "nlu": "data/nlu.yml",
153
+ }
154
+
155
+ for key, file in data_to_be_written_to_files.items():
156
+ write_encoded_data_to_file(
157
+ encoded_training_data.get(key, ""),
158
+ f"{training_base_path}/{file}",
159
+ )
160
+
161
+
162
+ def prepare_training_directory(
163
+ training_base_path: str, assistant_id: str, data: Dict[str, Any]
164
+ ) -> None:
165
+ """Prepare the training directory for a new training session."""
166
+ encoded_training_data = data.get("bot_config", {}).get("data", {})
167
+
168
+ # create a new working directory and store the training data from the
169
+ # request there. the training data in the request is base64 encoded
170
+ os.makedirs(training_base_path, exist_ok=True)
171
+
172
+ seed_training_directory_with_rasa_cache(training_base_path, assistant_id)
173
+ write_training_data_to_files(encoded_training_data, training_base_path)
174
+
175
+
176
+ def start_training_process(
177
+ training_id: str, assistant_id: str, client_id: str, training_base_path: str
178
+ ) -> TrainingSession:
179
+ log_path = logs_path(training_id)
180
+
181
+ # Start the training in a subprocess
182
+ # set the working directory to the training directory
183
+ # run the rasa train command as a subprocess, activating poetry before running
184
+ # pipe the stdout and stderr to the same file
185
+ process = subprocess.Popen(
186
+ [
187
+ RASA_PYTHON_PATH,
188
+ "-m",
189
+ "rasa.__main__",
190
+ "train",
191
+ "--debug",
192
+ "--out",
193
+ f"{training_base_path}/models",
194
+ "--data",
195
+ f"{training_base_path}/data",
196
+ "--config",
197
+ f"{training_base_path}/config.yml",
198
+ "--domain",
199
+ f"{training_base_path}/domain.yml",
200
+ "--endpoints",
201
+ f"{training_base_path}/endpoints.yml",
202
+ ],
203
+ cwd=training_base_path,
204
+ stdout=open(log_path, "w"),
205
+ stderr=subprocess.STDOUT,
206
+ env=os.environ.copy(),
207
+ )
208
+
209
+ structlogger.info(
210
+ "model_trainer.training_started",
211
+ training_id=training_id,
212
+ assistant_id=assistant_id,
213
+ client_id=client_id,
214
+ log=log_path,
215
+ pid=process.pid,
216
+ )
217
+
218
+ return TrainingSession(
219
+ training_id=training_id,
220
+ assistant_id=assistant_id,
221
+ client_id=client_id,
222
+ progress=0,
223
+ status="running",
224
+ process=process, # Store the process handle
225
+ )
226
+
227
+
228
+ def run_training(
229
+ training_id: str, assistant_id: str, client_id: str, data: Dict
230
+ ) -> TrainingSession:
231
+ """Run a training session."""
232
+ training_base_path = train_path(training_id)
233
+
234
+ prepare_training_directory(training_base_path, assistant_id, data)
235
+ return start_training_process(
236
+ training_id=training_id,
237
+ assistant_id=assistant_id,
238
+ client_id=client_id,
239
+ training_base_path=training_base_path,
240
+ )
@@ -0,0 +1,27 @@
1
+ import os
2
+
3
+ from rasa.model_manager.config import SERVER_BASE_WORKING_DIRECTORY
4
+
5
+
6
+ def logs_base_path() -> str:
7
+ """Return the path to the logs directory."""
8
+ return os.path.abspath(f"{SERVER_BASE_WORKING_DIRECTORY}/logs")
9
+
10
+
11
+ def models_base_path() -> str:
12
+ """Return the path to the models directory."""
13
+ return os.path.abspath(f"{SERVER_BASE_WORKING_DIRECTORY}/models")
14
+
15
+
16
+ def logs_path(action_id: str) -> str:
17
+ """Return the path to the log file for a given action id.
18
+
19
+ Args:
20
+ action_id: can either be a training_id or a deployment_id
21
+ """
22
+ return os.path.abspath(f"{logs_base_path()}/{action_id}.txt")
23
+
24
+
25
+ def models_path(training_id: str) -> str:
26
+ """Return the path to the models directory for a given training id."""
27
+ return os.path.abspath(f"{models_base_path()}/{training_id}")
rasa/model_service.py ADDED
@@ -0,0 +1,43 @@
1
+ import os
2
+ import logging
3
+
4
+ import structlog
5
+
6
+ from rasa.model_manager import model_api
7
+ from rasa.utils.common import configure_logging_and_warnings
8
+ import rasa.utils.licensing
9
+
10
+ structlogger = structlog.get_logger()
11
+
12
+ MODEL_SERVICE_PORT = 8000
13
+
14
+
15
+ def main() -> None:
16
+ """Start the Rasa Model Manager server.
17
+
18
+ The API server can receive requests to train models, run bots, and manage
19
+ the lifecycle of models and bots.
20
+ """
21
+ model_api.prepare_working_directories()
22
+
23
+ configure_logging_and_warnings(
24
+ log_level=logging.DEBUG,
25
+ logging_config_file=None,
26
+ warn_only_once=True,
27
+ filter_repeated_logs=True,
28
+ )
29
+
30
+ rasa.utils.licensing.validate_license_from_env()
31
+ # assert that an openai api key is set
32
+ assert (
33
+ "OPENAI_API_KEY" in os.environ
34
+ ), "Please set the OPENAI_API_KEY environment variable"
35
+
36
+ structlogger.debug("model_training.starting_server", port=MODEL_SERVICE_PORT)
37
+ structlogger.debug("model_running.starting_server", port=MODEL_SERVICE_PORT)
38
+
39
+ model_api.app.run(host="0.0.0.0", port=MODEL_SERVICE_PORT, legacy=True)
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
rasa/model_training.py CHANGED
@@ -15,6 +15,7 @@ import rasa.shared.utils.common
15
15
  import rasa.shared.utils.io
16
16
  import rasa.utils.common
17
17
  from rasa import telemetry
18
+ from rasa.core.persistor import StorageType
18
19
  from rasa.engine.caching import LocalTrainingCache
19
20
  from rasa.engine.recipes.recipe import Recipe
20
21
  from rasa.engine.runner.dask import DaskGraphRunner
@@ -22,7 +23,6 @@ from rasa.engine.storage.local_model_storage import LocalModelStorage
22
23
  from rasa.engine.storage.storage import ModelStorage
23
24
  from rasa.engine.training.components import FingerprintStatus
24
25
  from rasa.engine.training.graph_trainer import GraphTrainer
25
- from rasa.nlu.persistor import StorageType
26
26
  from rasa.shared.core.domain import Domain
27
27
  from rasa.shared.core.events import SlotSet
28
28
  from rasa.shared.core.training_data.structures import StoryGraph
@@ -156,6 +156,7 @@ async def train(
156
156
  model_to_finetune: Optional[Text] = None,
157
157
  finetuning_epoch_fraction: float = 1.0,
158
158
  remote_storage: Optional[StorageType] = None,
159
+ file_importer: Optional[TrainingDataImporter] = None,
159
160
  ) -> TrainingResult:
160
161
  """Trains a Rasa model (Core and NLU).
161
162
 
@@ -177,14 +178,18 @@ async def train(
177
178
  a directory in case the latest trained model should be used.
178
179
  finetuning_epoch_fraction: The fraction currently specified training epochs
179
180
  in the model configuration which should be used for finetuning.
180
- remote_storage: The remote storage which should be used to store the model.
181
+ remote_storage: Optional name of the remote storage to
182
+ use for storing the model.
183
+ file_importer: Instance of `TrainingDataImporter` to use for training.
184
+ If it is not provided, a new instance will be created.
181
185
 
182
186
  Returns:
183
187
  An instance of `TrainingResult`.
184
188
  """
185
- file_importer = TrainingDataImporter.load_from_config(
186
- config, domain, training_files, core_additional_arguments
187
- )
189
+ if not file_importer:
190
+ file_importer = TrainingDataImporter.load_from_config(
191
+ config, domain, training_files, core_additional_arguments
192
+ )
188
193
 
189
194
  stories = file_importer.get_stories()
190
195
  flows = file_importer.get_flows()
@@ -555,7 +560,7 @@ async def train_nlu(
555
560
 
556
561
  def push_model_to_remote_storage(model_path: Path, remote_storage: StorageType) -> None:
557
562
  """push model to remote storage"""
558
- from rasa.nlu.persistor import get_persistor
563
+ from rasa.core.persistor import get_persistor
559
564
 
560
565
  persistor = get_persistor(remote_storage)
561
566
 
rasa/server.py CHANGED
@@ -50,11 +50,11 @@ from rasa.core.channels.channel import (
50
50
  UserMessage,
51
51
  )
52
52
  from rasa.core.constants import DEFAULT_RESPONSE_TIMEOUT
53
+ from rasa.core.persistor import parse_remote_storage
53
54
  from rasa.core.test import test
54
55
  from rasa.core.utils import AvailableEndpoints
55
56
  from rasa.nlu.emulators.emulator import Emulator
56
57
  from rasa.nlu.emulators.no_emulator import NoEmulator
57
- from rasa.nlu.persistor import parse_remote_storage
58
58
  from rasa.nlu.test import CVEvaluationResult
59
59
  from rasa.shared.constants import (
60
60
  DEFAULT_MODELS_PATH,
rasa/shared/constants.py CHANGED
@@ -183,6 +183,8 @@ STREAM_CONFIG_KEY = "stream"
183
183
  N_REPHRASES_CONFIG_KEY = "n"
184
184
  USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY = "use_chat_completions_endpoint"
185
185
 
186
+ LLM_API_HEALTH_CHECK_ENV_VAR = "LLM_API_HEALTH_CHECK"
187
+
186
188
  AZURE_API_KEY_ENV_VAR = "AZURE_API_KEY"
187
189
  AZURE_AD_TOKEN_ENV_VAR = "AZURE_AD_TOKEN"
188
190
  AZURE_API_BASE_ENV_VAR = "AZURE_API_BASE"