rasa-pro 3.11.0rc2__py3-none-any.whl → 3.11.1__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 (65) hide show
  1. rasa/__main__.py +9 -3
  2. rasa/cli/studio/upload.py +0 -15
  3. rasa/cli/utils.py +1 -1
  4. rasa/core/channels/development_inspector.py +8 -2
  5. rasa/core/channels/voice_ready/audiocodes.py +3 -4
  6. rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
  7. rasa/core/channels/voice_stream/asr/asr_event.py +1 -1
  8. rasa/core/channels/voice_stream/asr/azure.py +16 -9
  9. rasa/core/channels/voice_stream/asr/deepgram.py +17 -14
  10. rasa/core/channels/voice_stream/tts/azure.py +3 -1
  11. rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
  12. rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
  13. rasa/core/channels/voice_stream/voice_channel.py +48 -18
  14. rasa/core/information_retrieval/qdrant.py +1 -0
  15. rasa/core/nlg/contextual_response_rephraser.py +2 -2
  16. rasa/core/persistor.py +93 -49
  17. rasa/core/policies/enterprise_search_policy.py +5 -5
  18. rasa/core/policies/flows/flow_executor.py +18 -8
  19. rasa/core/policies/intentless_policy.py +9 -5
  20. rasa/core/processor.py +7 -5
  21. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +2 -1
  22. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +9 -0
  23. rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
  24. rasa/e2e_test/assertions.py +133 -16
  25. rasa/e2e_test/assertions_schema.yml +23 -0
  26. rasa/e2e_test/e2e_test_runner.py +2 -2
  27. rasa/engine/loader.py +12 -0
  28. rasa/engine/validation.py +310 -86
  29. rasa/model_manager/config.py +8 -0
  30. rasa/model_manager/model_api.py +166 -61
  31. rasa/model_manager/runner_service.py +31 -26
  32. rasa/model_manager/trainer_service.py +14 -23
  33. rasa/model_manager/warm_rasa_process.py +187 -0
  34. rasa/model_service.py +3 -5
  35. rasa/model_training.py +3 -1
  36. rasa/shared/constants.py +27 -5
  37. rasa/shared/core/constants.py +1 -1
  38. rasa/shared/core/domain.py +8 -31
  39. rasa/shared/core/flows/yaml_flows_io.py +13 -4
  40. rasa/shared/importers/importer.py +19 -2
  41. rasa/shared/importers/rasa.py +5 -1
  42. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  43. rasa/shared/providers/_configs/litellm_router_client_config.py +29 -9
  44. rasa/shared/providers/_utils.py +79 -0
  45. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  46. rasa/shared/providers/embedding/litellm_router_embedding_client.py +1 -1
  47. rasa/shared/providers/llm/_base_litellm_client.py +26 -0
  48. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  49. rasa/shared/providers/llm/litellm_router_llm_client.py +56 -1
  50. rasa/shared/providers/llm/self_hosted_llm_client.py +4 -28
  51. rasa/shared/providers/router/_base_litellm_router_client.py +35 -1
  52. rasa/shared/utils/common.py +30 -3
  53. rasa/shared/utils/health_check/health_check.py +26 -24
  54. rasa/shared/utils/yaml.py +116 -31
  55. rasa/studio/data_handler.py +3 -1
  56. rasa/studio/upload.py +119 -57
  57. rasa/telemetry.py +3 -1
  58. rasa/tracing/config.py +1 -1
  59. rasa/validator.py +40 -4
  60. rasa/version.py +1 -1
  61. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/METADATA +2 -2
  62. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/RECORD +65 -63
  63. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/NOTICE +0 -0
  64. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/WHEEL +0 -0
  65. {rasa_pro-3.11.0rc2.dist-info → rasa_pro-3.11.1.dist-info}/entry_points.txt +0 -0
rasa/studio/upload.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import argparse
2
2
  import base64
3
+ import re
3
4
  import sys
4
5
  from typing import Dict, Iterable, List, Set, Text, Tuple, Union, Any
5
6
 
@@ -15,11 +16,18 @@ from rasa.shared.constants import (
15
16
  DEFAULT_DOMAIN_PATHS,
16
17
  DEFAULT_CONFIG_PATH,
17
18
  )
18
- from rasa.shared.core.flows.yaml_flows_io import YamlFlowsWriter
19
+ from rasa.shared.core.domain import Domain
20
+ from rasa.shared.core.flows.yaml_flows_io import YAMLFlowsReader, YamlFlowsWriter
19
21
  from rasa.shared.exceptions import RasaException
20
22
  from rasa.shared.importers.importer import TrainingDataImporter, FlowSyncImporter
21
- from rasa.shared.nlu.training_data.formats.rasa_yaml import RasaYAMLWriter
22
- from rasa.shared.utils.yaml import dump_obj_as_yaml_to_string, read_yaml_file
23
+ from rasa.shared.nlu.training_data.formats.rasa_yaml import (
24
+ RasaYAMLReader,
25
+ RasaYAMLWriter,
26
+ )
27
+ from rasa.shared.utils.yaml import (
28
+ dump_obj_as_yaml_to_string,
29
+ read_yaml_file,
30
+ )
23
31
  from rasa.studio import results_logger
24
32
  from rasa.studio.auth import KeycloakTokenReader
25
33
  from rasa.studio.config import StudioConfig
@@ -27,6 +35,27 @@ from rasa.studio.results_logger import StudioResult, with_studio_error_handler
27
35
 
28
36
  structlogger = structlog.get_logger()
29
37
 
38
+ CONFIG_KEYS = [
39
+ "recipe",
40
+ "language",
41
+ "pipeline",
42
+ "llm",
43
+ "policies",
44
+ "model_name",
45
+ "assistant_id",
46
+ ]
47
+
48
+ DOMAIN_KEYS = [
49
+ "version",
50
+ "actions",
51
+ "responses",
52
+ "slots",
53
+ "intents",
54
+ "entities",
55
+ "forms",
56
+ "session_config",
57
+ ]
58
+
30
59
 
31
60
  def _get_selected_entities_and_intents(
32
61
  args: argparse.Namespace,
@@ -54,6 +83,44 @@ def _get_selected_entities_and_intents(
54
83
  return list(entities), list(intents)
55
84
 
56
85
 
86
+ def run_validation(args: argparse.Namespace) -> None:
87
+ """Run the validation before uploading to Studio.
88
+
89
+ This is to avoid uploading invalid assistant data
90
+ that would raise errors during Rasa Pro training in Studio.
91
+
92
+ The validation checks that were selected to be run before uploading
93
+ maintain parity with the features that are supported in Studio.
94
+ """
95
+ from rasa.validator import Validator
96
+
97
+ training_data_importer = TrainingDataImporter.load_from_dict(
98
+ domain_path=args.domain,
99
+ training_data_paths=args.data,
100
+ config_path=args.config,
101
+ expand_env_vars=False,
102
+ )
103
+
104
+ structlogger.info(
105
+ "rasa.studio.upload.validating_data",
106
+ event_info="Validating domain and training data...",
107
+ )
108
+
109
+ validator = Validator.from_importer(training_data_importer)
110
+
111
+ if not validator.verify_studio_supported_validations():
112
+ structlogger.error(
113
+ "rasa.studio.upload.validate_files.project_validation_error",
114
+ event_info="Project validation completed with errors.",
115
+ )
116
+ sys.exit(1)
117
+
118
+ structlogger.info(
119
+ "rasa.studio.upload.validate_files.success",
120
+ event_info="Project validation completed successfully.",
121
+ )
122
+
123
+
57
124
  def handle_upload(args: argparse.Namespace) -> None:
58
125
  """Uploads primitives to rasa studio."""
59
126
  studio_config = StudioConfig.read_config()
@@ -82,6 +149,10 @@ def handle_upload(args: argparse.Namespace) -> None:
82
149
  args.config, "config", DEFAULT_CONFIG_PATH
83
150
  )
84
151
 
152
+ Domain.expand_env_vars = False
153
+ RasaYAMLReader.expand_env_vars = False
154
+ YAMLFlowsReader.expand_env_vars = False
155
+
85
156
  # check safely if args.calm is set and not fail if not
86
157
  if hasattr(args, "calm") and args.calm:
87
158
  upload_calm_assistant(args, endpoint, verify=verify)
@@ -89,17 +160,6 @@ def handle_upload(args: argparse.Namespace) -> None:
89
160
  upload_nlu_assistant(args, endpoint, verify=verify)
90
161
 
91
162
 
92
- config_keys = [
93
- "recipe",
94
- "language",
95
- "pipeline",
96
- "llm",
97
- "policies",
98
- "model_name",
99
- "assistant_id",
100
- ]
101
-
102
-
103
163
  def extract_values(data: Dict, keys: List[Text]) -> Dict:
104
164
  """Extracts values for given keys from a dictionary."""
105
165
  return {key: data.get(key) for key in keys if data.get(key)}
@@ -141,7 +201,7 @@ def _get_assistant_name(config: Dict[Text, Any]) -> str:
141
201
  def upload_calm_assistant(
142
202
  args: argparse.Namespace, endpoint: str, verify: bool = True
143
203
  ) -> StudioResult:
144
- """Uploads the CALM assistant data to Rasa Studio.
204
+ """Validates and uploads the CALM assistant data to Rasa Studio.
145
205
 
146
206
  Args:
147
207
  args: The command line arguments
@@ -154,6 +214,8 @@ def upload_calm_assistant(
154
214
  Returns:
155
215
  None
156
216
  """
217
+ run_validation(args)
218
+
157
219
  structlogger.info(
158
220
  "rasa.studio.upload.loading_data", event_info="Parsing CALM assistant data..."
159
221
  )
@@ -161,71 +223,48 @@ def upload_calm_assistant(
161
223
  importer = TrainingDataImporter.load_from_dict(
162
224
  domain_path=args.domain,
163
225
  config_path=args.config,
226
+ expand_env_vars=False,
164
227
  )
165
228
 
166
229
  # Prepare config and domain
167
230
  config = importer.get_config()
231
+ assistant_name = _get_assistant_name(config)
232
+ config_from_files = read_yaml_file(args.config, expand_env_vars=False)
168
233
  domain_from_files = importer.get_user_domain().as_dict()
169
- endpoints_from_files = read_yaml_file(args.endpoints)
170
- config_from_files = read_yaml_file(args.config)
171
234
 
172
235
  # Extract domain and config values
173
- domain_keys = [
174
- "version",
175
- "actions",
176
- "responses",
177
- "slots",
178
- "intents",
179
- "entities",
180
- "forms",
181
- "session_config",
182
- ]
183
-
184
- domain = extract_values(domain_from_files, domain_keys)
185
-
186
- assistant_name = _get_assistant_name(config)
187
-
188
- training_data_paths = args.data
189
-
190
- if isinstance(training_data_paths, list):
191
- training_data_paths.append(args.flows)
192
- elif isinstance(training_data_paths, str):
193
- if isinstance(args.flows, list):
194
- training_data_paths = [training_data_paths] + args.flows
195
- elif isinstance(args.flows, str):
196
- training_data_paths = [training_data_paths, args.flows]
197
- else:
198
- raise RasaException("Invalid flows path")
236
+ domain = extract_values(domain_from_files, DOMAIN_KEYS)
199
237
 
200
238
  # Prepare flows
201
239
  flow_importer = FlowSyncImporter.load_from_dict(
202
- training_data_paths=training_data_paths
240
+ training_data_paths=args.data, expand_env_vars=False
203
241
  )
204
-
205
242
  flows = list(flow_importer.get_user_flows())
206
243
 
207
244
  # We instantiate the TrainingDataImporter again on purpose to avoid
208
245
  # adding patterns to domain's actions. More info https://t.ly/W8uuc
209
246
  nlu_importer = TrainingDataImporter.load_from_dict(
210
- domain_path=args.domain, training_data_paths=args.data
247
+ training_data_paths=args.data, expand_env_vars=False
211
248
  )
212
249
  nlu_data = nlu_importer.get_nlu_data()
213
-
214
- intents_from_files = nlu_data.intents
215
-
216
250
  nlu_examples = nlu_data.filter_training_examples(
217
- lambda ex: ex.get("intent") in intents_from_files
251
+ lambda ex: ex.get("intent") in nlu_data.intents
218
252
  )
219
-
220
253
  nlu_examples_yaml = RasaYAMLWriter().dumps(nlu_examples)
221
254
 
255
+ # Prepare endpoints
256
+ endpoints_from_files = read_yaml_file(args.endpoints, expand_env_vars=False)
257
+ endpoints_str = dump_obj_as_yaml_to_string(
258
+ endpoints_from_files, transform=remove_quotes
259
+ )
260
+
222
261
  # Build GraphQL request
223
262
  graphql_req = build_import_request(
224
263
  assistant_name,
225
264
  flows_yaml=YamlFlowsWriter().dumps(flows),
226
265
  domain_yaml=dump_obj_as_yaml_to_string(domain),
227
266
  config_yaml=dump_obj_as_yaml_to_string(config_from_files),
228
- endpoints=dump_obj_as_yaml_to_string(endpoints_from_files),
267
+ endpoints=endpoints_str,
229
268
  nlu_yaml=nlu_examples_yaml,
230
269
  )
231
270
 
@@ -257,18 +296,22 @@ def upload_nlu_assistant(
257
296
  event_info="Found DM1 assistant data, parsing...",
258
297
  )
259
298
  importer = TrainingDataImporter.load_from_dict(
260
- domain_path=args.domain, training_data_paths=args.data, config_path=args.config
299
+ domain_path=args.domain,
300
+ training_data_paths=args.data,
301
+ config_path=args.config,
302
+ expand_env_vars=False,
261
303
  )
262
304
 
263
305
  intents_from_files = importer.get_nlu_data().intents
264
- entities_from_files = importer.get_domain().entities
265
306
 
307
+ domain_from_files = importer.get_domain()
308
+ entities_from_files = domain_from_files.entities
266
309
  entities, intents = _get_selected_entities_and_intents(
267
310
  args, intents_from_files, entities_from_files
268
311
  )
269
312
 
270
313
  config_from_files = importer.get_config()
271
- config = extract_values(config_from_files, config_keys)
314
+ config = extract_values(config_from_files, CONFIG_KEYS)
272
315
 
273
316
  assistant_name = _get_assistant_name(config)
274
317
 
@@ -286,7 +329,7 @@ def upload_nlu_assistant(
286
329
  all_entities = _add_missing_entities(nlu_examples.entities, entities)
287
330
  nlu_examples_yaml = RasaYAMLWriter().dumps(nlu_examples)
288
331
 
289
- domain = _filter_domain(all_entities, intents, importer.get_domain().as_dict())
332
+ domain = _filter_domain(all_entities, intents, domain_from_files.as_dict())
290
333
  domain_yaml = dump_obj_as_yaml_to_string(domain)
291
334
 
292
335
  graphql_req = build_request(assistant_name, nlu_examples_yaml, domain_yaml)
@@ -424,7 +467,9 @@ def build_request(
424
467
 
425
468
 
426
469
  def _filter_domain(
427
- entities: List[Union[str, Dict]], intents: List[str], domain_from_files: Dict
470
+ entities: List[Union[str, Dict]],
471
+ intents: List[str],
472
+ domain_from_files: Dict[str, Any],
428
473
  ) -> Dict:
429
474
  """Filters the domain to only include the selected entities and intents."""
430
475
  selected_entities = _remove_not_selected_entities(
@@ -485,3 +530,20 @@ def _remove_not_selected_entities(
485
530
  domain_entities.remove(entity)
486
531
 
487
532
  return domain_entities
533
+
534
+
535
+ def remove_quotes(node: Any) -> Any:
536
+ """Transform function to remove quotes from a node if it is a string.
537
+
538
+ This is to prevent wrapping unexpanded environment variables in quotes
539
+ when uploading endpoints to Rasa Studio.
540
+ """
541
+ if isinstance(node, str):
542
+ matches = re.findall(r"'\$\{([^}]+)\}'", node)
543
+ for match in matches:
544
+ node = node.replace(f"'${{{match}}}'", f"${{{match}}}")
545
+ return node
546
+ elif isinstance(node, dict):
547
+ return {k: remove_quotes(v) for k, v in node.items()}
548
+ else:
549
+ return node
rasa/telemetry.py CHANGED
@@ -35,9 +35,9 @@ from rasa.constants import (
35
35
  from rasa.shared.constants import (
36
36
  PROMPT_CONFIG_KEY,
37
37
  PROMPT_TEMPLATE_CONFIG_KEY,
38
- MODEL_GROUP_CONFIG_KEY,
39
38
  LLM_API_HEALTH_CHECK_ENV_VAR,
40
39
  LLM_API_HEALTH_CHECK_DEFAULT_VALUE,
40
+ MODEL_GROUP_CONFIG_KEY,
41
41
  )
42
42
  from rasa.engine.storage.local_model_storage import LocalModelStorage
43
43
  from rasa.shared.constants import DOCS_URL_TELEMETRY, UTTER_ASK_PREFIX
@@ -1133,6 +1133,7 @@ def _get_llm_command_generator_config(config: Dict[str, Any]) -> Optional[Dict]:
1133
1133
  def extract_llm_command_generator_llm_client_settings(component: Dict) -> Dict:
1134
1134
  """Extracts settings related to LLM command generator."""
1135
1135
  llm_config = component.get(LLM_CONFIG_KEY, {})
1136
+ # Config at this stage is not yet resolved, so read from `model_group`
1136
1137
  llm_model_group_id = llm_config.get(MODEL_GROUP_CONFIG_KEY)
1137
1138
  llm_model_name = llm_config.get(MODEL_CONFIG_KEY) or llm_config.get(
1138
1139
  MODEL_NAME_CONFIG_KEY
@@ -1174,6 +1175,7 @@ def _get_llm_command_generator_config(config: Dict[str, Any]) -> Optional[Dict]:
1174
1175
  if flow_retrieval_enabled
1175
1176
  else None
1176
1177
  )
1178
+ # Config at this stage is not yet resolved, so read from `model_group`
1177
1179
  flow_retrieval_embedding_model_group_id = embeddings_config.get(
1178
1180
  MODEL_GROUP_CONFIG_KEY
1179
1181
  )
rasa/tracing/config.py CHANGED
@@ -131,7 +131,7 @@ def get_tracer_provider(endpoints_file: Text) -> Optional[TracerProvider]:
131
131
 
132
132
  if not cfg:
133
133
  logger.info(
134
- f"No endpoint for tracing type available in {endpoints_file},"
134
+ f"No endpoint for tracing type available in {endpoints_file}, "
135
135
  f"tracing will not be configured."
136
136
  )
137
137
  return None
rasa/validator.py CHANGED
@@ -1,6 +1,7 @@
1
1
  import logging
2
2
  import re
3
3
  import string
4
+ import sys
4
5
  from collections import defaultdict
5
6
  from typing import Set, Text, Optional, Dict, Any, List, Tuple
6
7
 
@@ -304,7 +305,7 @@ class Validator:
304
305
  return any(cls.check_for_placeholder(i) for i in value)
305
306
  return False
306
307
 
307
- def check_for_no_empty_paranthesis_in_responses(self) -> bool:
308
+ def check_for_no_empty_parenthesis_in_responses(self) -> bool:
308
309
  """Checks if there are no empty parenthesis in utterances."""
309
310
  everything_is_alright = True
310
311
 
@@ -315,12 +316,12 @@ class Validator:
315
316
  for key in RESPONSE_KEYS_TO_INTERPOLATE
316
317
  ):
317
318
  structlogger.error(
318
- "validator.empty_paranthesis_in_utterances",
319
+ "validator.empty_parenthesis_in_utterances",
319
320
  response=response_text,
320
321
  event_info=(
321
322
  f"The response '{response_text}' in the domain file "
322
- f"contains empty parenthesis, which is not permitted."
323
- f" Please remove the empty parenthesis."
323
+ f"contains empty parenthesis, which is not permitted. "
324
+ f"Please remove the empty parenthesis."
324
325
  ),
325
326
  )
326
327
  everything_is_alright = False
@@ -1603,3 +1604,38 @@ class Validator:
1603
1604
  ),
1604
1605
  )
1605
1606
  return is_valid
1607
+
1608
+ def verify_studio_supported_validations(self) -> bool:
1609
+ """Validates the assistant project for Rasa Studio supported features.
1610
+
1611
+ Ensure to add new validations here if they are required for
1612
+ Rasa Studio Upload CLI.
1613
+ """
1614
+ if self.domain.is_empty():
1615
+ structlogger.error(
1616
+ "rasa.validator.verify_studio_supported_validations.empty_domain",
1617
+ event_info="Encountered empty domain during validation.",
1618
+ )
1619
+ sys.exit(1)
1620
+
1621
+ self.warn_if_config_mandatory_keys_are_not_set()
1622
+
1623
+ valid_responses = (
1624
+ self.check_for_no_empty_parenthesis_in_responses()
1625
+ and self.validate_button_payloads()
1626
+ )
1627
+ valid_nlu = self.verify_nlu()
1628
+ valid_flows = all(
1629
+ [
1630
+ self.verify_flows_steps_against_domain(),
1631
+ self.verify_unique_flows(),
1632
+ self.verify_predicates(),
1633
+ ]
1634
+ )
1635
+ valid_calm_slot_mappings = self.validate_CALM_slot_mappings()
1636
+
1637
+ all_good = (
1638
+ valid_responses and valid_nlu and valid_flows and valid_calm_slot_mappings
1639
+ )
1640
+
1641
+ return all_good
rasa/version.py CHANGED
@@ -1,3 +1,3 @@
1
1
  # this file will automatically be changed,
2
2
  # do not add anything but the version number here!
3
- __version__ = "3.11.0rc2"
3
+ __version__ = "3.11.1"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rasa-pro
3
- Version: 3.11.0rc2
3
+ Version: 3.11.1
4
4
  Summary: State-of-the-art open-core Conversational AI framework for Enterprises that natively leverages generative AI for effortless assistant development.
5
5
  Home-page: https://rasa.com
6
6
  Keywords: nlp,machine-learning,machine-learning-library,bot,bots,botkit,rasa conversational-agents,conversational-ai,chatbot,chatbot-framework,bot-framework
@@ -103,7 +103,7 @@ Requires-Dist: pyyaml (>=6.0)
103
103
  Requires-Dist: qdrant-client (>=1.9.0,<2.0.0)
104
104
  Requires-Dist: questionary (>=1.10.0,<2.1.0)
105
105
  Requires-Dist: randomname (>=0.2.1,<0.3.0)
106
- Requires-Dist: rasa-sdk (==3.11.0rc1)
106
+ Requires-Dist: rasa-sdk (==3.11.0)
107
107
  Requires-Dist: redis (>=4.6.0,<6.0)
108
108
  Requires-Dist: regex (>=2022.10.31,<2022.11)
109
109
  Requires-Dist: requests (>=2.31.0,<2.32.0)