rasa-pro 3.11.0a4.dev3__py3-none-any.whl → 3.11.0rc2__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 (184) hide show
  1. rasa/__main__.py +22 -12
  2. rasa/api.py +1 -1
  3. rasa/cli/arguments/default_arguments.py +1 -2
  4. rasa/cli/arguments/shell.py +5 -1
  5. rasa/cli/e2e_test.py +1 -1
  6. rasa/cli/evaluate.py +8 -8
  7. rasa/cli/inspect.py +6 -4
  8. rasa/cli/llm_fine_tuning.py +1 -1
  9. rasa/cli/project_templates/calm/config.yml +5 -7
  10. rasa/cli/project_templates/calm/endpoints.yml +8 -0
  11. rasa/cli/project_templates/tutorial/config.yml +8 -5
  12. rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
  13. rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
  14. rasa/cli/project_templates/tutorial/domain.yml +14 -0
  15. rasa/cli/project_templates/tutorial/endpoints.yml +7 -7
  16. rasa/cli/run.py +1 -1
  17. rasa/cli/scaffold.py +4 -2
  18. rasa/cli/studio/studio.py +18 -8
  19. rasa/cli/utils.py +5 -0
  20. rasa/cli/x.py +8 -8
  21. rasa/constants.py +1 -1
  22. rasa/core/actions/action_repeat_bot_messages.py +17 -0
  23. rasa/core/channels/channel.py +20 -0
  24. rasa/core/channels/inspector/dist/assets/{arc-6852c607.js → arc-bc141fb2.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-acc952b2.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-848a7597.js → classDiagram-936ed81e-55366915.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-a73d3e68.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-e5ee049d.js → createText-62fc7601-b0ec81d6.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-771e517e.js → edges-f2ad444c-6166330c.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-aa347178.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-651fc57d.js → flowDb-1972c806-fca3bfe4.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-ca67804f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
  34. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-2dbc568d.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-25a65bd8.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-fdc7378d.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-6f1fd606.js → index-2c4b9a3b-f55afcdf.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{index-efdd30c1.js → index-e7cef9de.js} +68 -68
  39. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-cb1a041a.js → infoDiagram-736b4530-124d4a14.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-14609879.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{layout-2490f52b.js → layout-b9885fb6.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{line-40186f1f.js → line-7c59abb6.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{linear-08814e93.js → linear-4776f780.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-1a534584.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-72397b61.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3bb0b6a3.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-57334f61.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-111e1297.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-10bcfe62.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-acaf7513.js → stateDiagram-59f0c015-042b3137.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-3ec2a235.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-62730289.js → styles-080da4f6-23ffa4fc.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-5284ee76.js → styles-3dcbcfbf-94f59763.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-642435e3.js → styles-9c745c82-78a6bebc.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-b250a350.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-c2b147ed.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-f92cfea9.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
  58. rasa/core/channels/inspector/dist/index.html +1 -1
  59. rasa/core/channels/inspector/src/App.tsx +1 -1
  60. rasa/core/channels/inspector/src/helpers/audiostream.ts +77 -16
  61. rasa/core/channels/socketio.py +2 -1
  62. rasa/core/channels/telegram.py +1 -1
  63. rasa/core/channels/twilio.py +1 -1
  64. rasa/core/channels/voice_ready/audiocodes.py +12 -0
  65. rasa/core/channels/voice_ready/jambonz.py +15 -4
  66. rasa/core/channels/voice_ready/twilio_voice.py +6 -21
  67. rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
  68. rasa/core/channels/voice_stream/asr/azure.py +122 -0
  69. rasa/core/channels/voice_stream/asr/deepgram.py +16 -6
  70. rasa/core/channels/voice_stream/audio_bytes.py +1 -0
  71. rasa/core/channels/voice_stream/browser_audio.py +31 -8
  72. rasa/core/channels/voice_stream/call_state.py +23 -0
  73. rasa/core/channels/voice_stream/tts/azure.py +6 -2
  74. rasa/core/channels/voice_stream/tts/cartesia.py +10 -6
  75. rasa/core/channels/voice_stream/tts/tts_engine.py +1 -0
  76. rasa/core/channels/voice_stream/twilio_media_streams.py +27 -18
  77. rasa/core/channels/voice_stream/util.py +4 -4
  78. rasa/core/channels/voice_stream/voice_channel.py +189 -39
  79. rasa/core/featurizers/single_state_featurizer.py +22 -1
  80. rasa/core/featurizers/tracker_featurizers.py +115 -18
  81. rasa/core/nlg/contextual_response_rephraser.py +32 -30
  82. rasa/core/persistor.py +86 -39
  83. rasa/core/policies/enterprise_search_policy.py +119 -60
  84. rasa/core/policies/flows/flow_executor.py +7 -4
  85. rasa/core/policies/intentless_policy.py +78 -22
  86. rasa/core/policies/ted_policy.py +58 -33
  87. rasa/core/policies/unexpected_intent_policy.py +15 -7
  88. rasa/core/processor.py +25 -0
  89. rasa/core/training/interactive.py +34 -35
  90. rasa/core/utils.py +8 -3
  91. rasa/dialogue_understanding/coexistence/llm_based_router.py +39 -12
  92. rasa/dialogue_understanding/commands/change_flow_command.py +6 -0
  93. rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
  94. rasa/dialogue_understanding/commands/utils.py +5 -0
  95. rasa/dialogue_understanding/generator/constants.py +2 -0
  96. rasa/dialogue_understanding/generator/flow_retrieval.py +49 -4
  97. rasa/dialogue_understanding/generator/llm_based_command_generator.py +37 -23
  98. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +57 -10
  99. rasa/dialogue_understanding/generator/nlu_command_adapter.py +19 -1
  100. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +71 -11
  101. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +39 -0
  102. rasa/dialogue_understanding/patterns/user_silence.py +37 -0
  103. rasa/dialogue_understanding/processor/command_processor.py +21 -1
  104. rasa/e2e_test/e2e_test_case.py +85 -6
  105. rasa/e2e_test/e2e_test_runner.py +4 -2
  106. rasa/e2e_test/utils/io.py +1 -1
  107. rasa/engine/validation.py +316 -10
  108. rasa/model_manager/config.py +15 -3
  109. rasa/model_manager/model_api.py +15 -7
  110. rasa/model_manager/runner_service.py +8 -6
  111. rasa/model_manager/socket_bridge.py +6 -3
  112. rasa/model_manager/trainer_service.py +7 -5
  113. rasa/model_manager/utils.py +28 -7
  114. rasa/model_service.py +9 -2
  115. rasa/model_training.py +2 -0
  116. rasa/nlu/classifiers/diet_classifier.py +38 -25
  117. rasa/nlu/classifiers/logistic_regression_classifier.py +22 -9
  118. rasa/nlu/classifiers/sklearn_intent_classifier.py +37 -16
  119. rasa/nlu/extractors/crf_entity_extractor.py +93 -50
  120. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -16
  121. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +52 -17
  122. rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +5 -3
  123. rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
  124. rasa/server.py +3 -1
  125. rasa/shared/constants.py +36 -3
  126. rasa/shared/core/constants.py +7 -0
  127. rasa/shared/core/domain.py +26 -0
  128. rasa/shared/core/flows/flow.py +5 -0
  129. rasa/shared/core/flows/flows_list.py +5 -1
  130. rasa/shared/core/flows/flows_yaml_schema.json +10 -0
  131. rasa/shared/core/flows/utils.py +39 -0
  132. rasa/shared/core/flows/validation.py +96 -0
  133. rasa/shared/core/slots.py +5 -0
  134. rasa/shared/nlu/training_data/features.py +120 -2
  135. rasa/shared/providers/_configs/azure_openai_client_config.py +5 -3
  136. rasa/shared/providers/_configs/litellm_router_client_config.py +200 -0
  137. rasa/shared/providers/_configs/model_group_config.py +167 -0
  138. rasa/shared/providers/_configs/openai_client_config.py +1 -1
  139. rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
  140. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
  141. rasa/shared/providers/_configs/utils.py +16 -0
  142. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +18 -29
  143. rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
  144. rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
  145. rasa/shared/providers/llm/_base_litellm_client.py +37 -31
  146. rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
  147. rasa/shared/providers/llm/litellm_router_llm_client.py +127 -0
  148. rasa/shared/providers/llm/rasa_llm_client.py +112 -0
  149. rasa/shared/providers/llm/self_hosted_llm_client.py +1 -1
  150. rasa/shared/providers/mappings.py +19 -0
  151. rasa/shared/providers/router/__init__.py +0 -0
  152. rasa/shared/providers/router/_base_litellm_router_client.py +149 -0
  153. rasa/shared/providers/router/router_client.py +73 -0
  154. rasa/shared/utils/common.py +8 -0
  155. rasa/shared/utils/health_check/__init__.py +0 -0
  156. rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
  157. rasa/shared/utils/health_check/health_check.py +256 -0
  158. rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
  159. rasa/shared/utils/io.py +28 -6
  160. rasa/shared/utils/llm.py +353 -46
  161. rasa/shared/utils/yaml.py +111 -73
  162. rasa/studio/auth.py +3 -5
  163. rasa/studio/config.py +13 -4
  164. rasa/studio/constants.py +1 -0
  165. rasa/studio/data_handler.py +10 -3
  166. rasa/studio/upload.py +81 -26
  167. rasa/telemetry.py +92 -17
  168. rasa/tracing/config.py +2 -0
  169. rasa/tracing/instrumentation/attribute_extractors.py +94 -17
  170. rasa/tracing/instrumentation/instrumentation.py +121 -0
  171. rasa/utils/common.py +5 -0
  172. rasa/utils/io.py +7 -81
  173. rasa/utils/log_utils.py +9 -2
  174. rasa/utils/sanic_error_handler.py +32 -0
  175. rasa/utils/tensorflow/feature_array.py +366 -0
  176. rasa/utils/tensorflow/model_data.py +2 -193
  177. rasa/validator.py +70 -0
  178. rasa/version.py +1 -1
  179. {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc2.dist-info}/METADATA +11 -10
  180. {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc2.dist-info}/RECORD +183 -163
  181. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-587d82d8.js +0 -1
  182. {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc2.dist-info}/NOTICE +0 -0
  183. {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc2.dist-info}/WHEEL +0 -0
  184. {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc2.dist-info}/entry_points.txt +0 -0
rasa/shared/utils/yaml.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import datetime
2
+ import io
2
3
  import logging
3
4
  import os
4
5
  import re
@@ -8,19 +9,13 @@ from dataclasses import field
8
9
  from functools import lru_cache
9
10
  from io import StringIO
10
11
  from pathlib import Path
11
- from typing import Dict, List, Optional, Any, Callable, Tuple, Union
12
+ from typing import Any, List, Optional, Tuple, Dict, Callable, Union
12
13
 
13
14
  import jsonschema
14
15
  from importlib_resources import files
15
16
  from packaging import version
16
17
  from pykwalify.core import Core
17
18
  from pykwalify.errors import SchemaError
18
- from ruamel import yaml as yaml
19
- from ruamel.yaml import RoundTripRepresenter, YAMLError
20
- from ruamel.yaml.constructor import DuplicateKeyError, BaseConstructor, ScalarNode
21
- from ruamel.yaml.comments import CommentedSeq, CommentedMap
22
- from ruamel.yaml.loader import SafeLoader
23
-
24
19
  from rasa.shared.constants import (
25
20
  ASSERTIONS_SCHEMA_EXTENSIONS_FILE,
26
21
  ASSERTIONS_SCHEMA_FILE,
@@ -31,8 +26,7 @@ from rasa.shared.constants import (
31
26
  LATEST_TRAINING_DATA_FORMAT_VERSION,
32
27
  SCHEMA_EXTENSIONS_FILE,
33
28
  RESPONSES_SCHEMA_FILE,
34
- ORIGINAL_VALUE,
35
- RESOLVED_VALUE,
29
+ API_KEY,
36
30
  )
37
31
  from rasa.shared.exceptions import (
38
32
  YamlException,
@@ -52,6 +46,11 @@ from rasa.shared.utils.io import (
52
46
  raise_warning,
53
47
  read_json_file,
54
48
  )
49
+ from ruamel import yaml as yaml
50
+ from ruamel.yaml import YAML, RoundTripRepresenter, YAMLError
51
+ from ruamel.yaml.comments import CommentedSeq, CommentedMap
52
+ from ruamel.yaml.constructor import DuplicateKeyError, BaseConstructor, ScalarNode
53
+ from ruamel.yaml.loader import SafeLoader
55
54
 
56
55
  logger = logging.getLogger(__name__)
57
56
 
@@ -60,12 +59,22 @@ YAML_VERSION = (1, 2)
60
59
  READ_YAML_FILE_CACHE_MAXSIZE = os.environ.get(
61
60
  READ_YAML_FILE_CACHE_MAXSIZE_ENV_VAR, DEFAULT_READ_YAML_FILE_CACHE_MAXSIZE
62
61
  )
62
+ SENSITIVE_DATA = [API_KEY]
63
63
 
64
64
 
65
65
  @dataclass
66
66
  class PathWithError:
67
+ """Represents a validation error at a specific location in the YAML content.
68
+
69
+ Attributes:
70
+ message (str): A description of the validation error.
71
+ path (List[str]): Path to the node where the error occurred.
72
+ key (Optional[str]): The specific key associated with the error, if any.
73
+ """
74
+
67
75
  message: str
68
76
  path: List[str] = field(default_factory=list)
77
+ key: Optional[str] = None
69
78
 
70
79
 
71
80
  def fix_yaml_loader() -> None:
@@ -86,11 +95,15 @@ def replace_environment_variables() -> None:
86
95
  env_var_pattern = re.compile(r"^(.*)\$\{(.*)\}(.*)$")
87
96
  yaml.Resolver.add_implicit_resolver("!env_var", env_var_pattern, None)
88
97
 
89
- def env_var_constructor(
90
- loader: BaseConstructor, node: ScalarNode
91
- ) -> Union[dict, str]:
98
+ def env_var_constructor(loader: BaseConstructor, node: ScalarNode) -> str:
92
99
  """Process environment variables found in the YAML."""
93
100
  value = loader.construct_scalar(node)
101
+
102
+ # get key of current node
103
+ key_node = list(loader.constructed_objects)[-1]
104
+ if isinstance(key_node, ScalarNode) and key_node.value in SENSITIVE_DATA:
105
+ return value
106
+
94
107
  expanded_vars = os.path.expandvars(value)
95
108
  not_expanded = [
96
109
  w for w in expanded_vars.split() if w.startswith("$") and w in value
@@ -102,11 +115,6 @@ def replace_environment_variables() -> None:
102
115
  f"Please make sure to also set these "
103
116
  f"environment variables: '{not_expanded}'."
104
117
  )
105
- if expanded_vars:
106
- # if the environment variable is referenced using the ${} syntax
107
- # then we return a dictionary with the original value and the resolved,
108
- # value. So that the graph components can use the original value.
109
- return {ORIGINAL_VALUE: value, RESOLVED_VALUE: expanded_vars}
110
118
  return expanded_vars
111
119
 
112
120
  yaml.SafeConstructor.add_constructor("!env_var", env_var_constructor)
@@ -147,21 +155,72 @@ class YamlValidationException(YamlException, ValueError):
147
155
  if self.validation_errors:
148
156
  unique_errors = {}
149
157
  for error in self.validation_errors:
150
- line_number = self._line_number_for_path(self.content, error.path)
158
+ line_number = self._line_number_for_path(
159
+ self.content, error.path, error.key
160
+ )
151
161
 
152
162
  if line_number and self.filename:
153
- error_representation = f" in {self.filename}:{line_number}:\n"
163
+ error_location = f" in {self.filename}:{line_number}:\n"
154
164
  elif line_number:
155
- error_representation = f" in Line {line_number}:\n"
165
+ error_location = f" in Line {line_number}:\n"
156
166
  else:
157
- error_representation = ""
167
+ error_location = ""
158
168
 
159
- error_representation += f" {error.message}"
160
- unique_errors[error.message] = error_representation
169
+ code_snippet = self._get_code_snippet(line_number)
170
+ error_message = f"{error_location}\n{code_snippet}{error.message}\n"
171
+ unique_errors[error.message] = error_message
161
172
  error_msg = "\n".join(unique_errors.values())
162
173
  msg += f":\n{error_msg}"
163
174
  return msg
164
175
 
176
+ def _get_code_snippet(
177
+ self,
178
+ error_line: Optional[int],
179
+ context_lines: int = 2,
180
+ ) -> str:
181
+ """Extract code snippet from the YAML lines around the error.
182
+
183
+ Args:
184
+ error_line: Line number where the error occurred (1-based).
185
+ context_lines: Number of context lines before and after the error line.
186
+ Default is 2, balancing context and readability. Adjust as needed.
187
+
188
+ Returns:
189
+ A string containing the code snippet with the error highlighted.
190
+ """
191
+ yaml_lines = self._get_serialized_yaml_lines()
192
+ if not yaml_lines or error_line is None:
193
+ return ""
194
+
195
+ start = max(error_line - context_lines - 1, 0)
196
+ end = min(error_line + context_lines, len(yaml_lines))
197
+ snippet_lines = yaml_lines[start:end]
198
+ snippet = ""
199
+ for idx, line_content in enumerate(snippet_lines, start=start + 1):
200
+ prefix = ">>> " if idx == error_line else " "
201
+ line_number_str = str(idx)
202
+ snippet += f"{prefix}{line_number_str} | {line_content}\n"
203
+ return snippet
204
+
205
+ def _get_serialized_yaml_lines(self) -> List[str]:
206
+ """Serialize the content back to YAML and return the lines."""
207
+ yaml_lines = []
208
+ try:
209
+ yaml = YAML()
210
+ yaml.default_flow_style = False
211
+ # Set width to 1000, so we don't break the lines of the original YAML file
212
+ yaml.width = 1000 # type: ignore[assignment]
213
+ yaml.indent(mapping=2, sequence=4, offset=2)
214
+ stream = io.StringIO()
215
+ yaml.dump(self.content, stream)
216
+ serialized_yaml = stream.getvalue()
217
+ yaml_lines = serialized_yaml.splitlines()
218
+ return yaml_lines
219
+ except Exception as exc:
220
+ logger.debug(f"Error serializing YAML content: {exc}")
221
+
222
+ return yaml_lines
223
+
165
224
  def _calculate_number_of_lines(
166
225
  self,
167
226
  current: Union[CommentedSeq, CommentedMap],
@@ -229,7 +288,9 @@ class YamlValidationException(YamlException, ValueError):
229
288
  # Return the calculated child offset and True indicating a line number was found
230
289
  return child_offset, True
231
290
 
232
- def _line_number_for_path(self, current: Any, path: List[str]) -> Optional[int]:
291
+ def _line_number_for_path(
292
+ self, current: Any, path: List[str], key: Optional[str] = None
293
+ ) -> Optional[int]:
233
294
  """Get line number for a yaml path in the current content.
234
295
 
235
296
  Implemented using recursion: algorithm goes down the path navigating to the
@@ -238,6 +299,7 @@ class YamlValidationException(YamlException, ValueError):
238
299
  Args:
239
300
  current: current content
240
301
  path: path to traverse within the content
302
+ key: the key associated with the error, if any
241
303
 
242
304
  Returns:
243
305
  the line number of the path in the content.
@@ -248,6 +310,10 @@ class YamlValidationException(YamlException, ValueError):
248
310
  this_line = current.lc.line + 1 if hasattr(current, "lc") else None
249
311
 
250
312
  if not path:
313
+ if key and hasattr(current, "lc"):
314
+ if hasattr(current.lc, "data") and key in current.lc.data:
315
+ key_line_no = current.lc.data[key][0] + 1
316
+ return key_line_no
251
317
  return this_line
252
318
 
253
319
  head, tail = path[0], path[1:]
@@ -257,7 +323,7 @@ class YamlValidationException(YamlException, ValueError):
257
323
 
258
324
  if head:
259
325
  if isinstance(current, dict) and head in current:
260
- line = self._line_number_for_path(current[head], tail)
326
+ line = self._line_number_for_path(current[head], tail, key)
261
327
  if line is None:
262
328
  line_offset, found_lc = self._calculate_number_of_lines(
263
329
  current, head
@@ -267,10 +333,13 @@ class YamlValidationException(YamlException, ValueError):
267
333
  return this_line + line_offset
268
334
  return line
269
335
  elif isinstance(current, list) and head.isdigit():
270
- return self._line_number_for_path(current[int(head)], tail) or this_line
336
+ return (
337
+ self._line_number_for_path(current[int(head)], tail, key)
338
+ or this_line
339
+ )
271
340
  else:
272
341
  return this_line
273
- return self._line_number_for_path(current, tail) or this_line
342
+ return self._line_number_for_path(current, tail, key) or this_line
274
343
 
275
344
 
276
345
  def read_schema_file(
@@ -332,13 +401,26 @@ def validate_yaml_content_using_schema(
332
401
  try:
333
402
  core.validate(raise_exception=True)
334
403
  except SchemaError:
404
+ # PyKwalify propagates each validation error up the data hierarchy, resulting
405
+ # in multiple redundant errors for a single issue. To present a clear message
406
+ # about the root cause, we use only the first error.
407
+ error = core.errors[0]
408
+
409
+ # Increment numeric indices by 1 to convert from 0-based to 1-based indexing
410
+ error_message = re.sub(
411
+ r"(/)(\d+)", lambda m: f"/{int(m.group(2)) + 1}", str(error)
412
+ )
413
+
335
414
  raise YamlValidationException(
336
415
  "Please make sure the file is correct and all "
337
416
  "mandatory parameters are specified. Here are the errors "
338
417
  "found during validation",
339
418
  [
340
- PathWithError(message=str(e), path=e.path.split("/"))
341
- for e in core.errors
419
+ PathWithError(
420
+ message=error_message,
421
+ path=error.path.removeprefix("/").split("/"),
422
+ key=getattr(error, "key", None),
423
+ )
342
424
  ],
343
425
  content=yaml_content,
344
426
  )
@@ -425,47 +507,6 @@ def validate_raw_yaml_using_schema_file_with_responses(
425
507
  )
426
508
 
427
509
 
428
- def process_content(content: str) -> str:
429
- """
430
- Process the content to handle both Windows paths and emojis.
431
- Windows paths are processed by escaping backslashes but emojis are left untouched.
432
-
433
- Args:
434
- content: yaml content to be processed
435
- """
436
- # Detect common Windows path patterns: e.g., C:\ or \\
437
- UNESCAPED_WINDOWS_PATH_PATTERN = re.compile(
438
- r"(?<!\w)[a-zA-Z]:(\\[a-zA-Z0-9_ -]+)*(\\)?(?!\\n)"
439
- )
440
- ESCAPED_WINDOWS_PATH_PATTERN = re.compile(
441
- r"(?<!\w)[a-zA-Z]:(\\\\[a-zA-Z0-9_ -]+)+\\\\?(?!\\n)"
442
- )
443
-
444
- # Function to escape backslashes in Windows paths but leave other content as is
445
- def escape_windows_paths(match: re.Match) -> str:
446
- path = str(match.group(0))
447
- return path.replace("\\", "\\\\") # Escape backslashes only in Windows paths
448
-
449
- def unescape_windows_paths(match: re.Match) -> str:
450
- path = str(match.group(0))
451
- return path.replace("\\\\", "\\")
452
-
453
- # First, process Windows paths by escaping backslashes
454
- content = re.sub(UNESCAPED_WINDOWS_PATH_PATTERN, escape_windows_paths, content)
455
-
456
- # Ensure proper handling of emojis by decoding Unicode sequences
457
- content = (
458
- content.encode("utf-8")
459
- .decode("raw_unicode_escape")
460
- .encode("utf-16", "surrogatepass")
461
- .decode("utf-16")
462
- )
463
-
464
- content = re.sub(ESCAPED_WINDOWS_PATH_PATTERN, unescape_windows_paths, content)
465
-
466
- return content
467
-
468
-
469
510
  def read_yaml(
470
511
  content: str,
471
512
  reader_type: Union[str, List[str]] = "safe",
@@ -481,9 +522,6 @@ def read_yaml(
481
522
  Raises:
482
523
  ruamel.yaml.parser.ParserError: If there was an error when parsing the YAML.
483
524
  """
484
- if _is_ascii(content):
485
- content = process_content(content)
486
-
487
525
  custom_constructor = kwargs.get("custom_constructor", None)
488
526
 
489
527
  # Create YAML parser with custom constructor
rasa/studio/auth.py CHANGED
@@ -23,12 +23,10 @@ from rasa.studio.results_logger import with_studio_error_handler, StudioResult
23
23
  class StudioAuth:
24
24
  """Handles the authentication with the Rasa Studio authentication server."""
25
25
 
26
- def __init__(
27
- self,
28
- studio_config: StudioConfig,
29
- verify: bool = True,
30
- ) -> None:
26
+ def __init__(self, studio_config: StudioConfig) -> None:
31
27
  self.config = studio_config
28
+ verify = not studio_config.disable_verify
29
+
32
30
  self.keycloak_openid = KeycloakOpenID(
33
31
  server_url=studio_config.authentication_server_url,
34
32
  client_id=studio_config.client_id,
rasa/studio/config.py CHANGED
@@ -2,13 +2,14 @@ from __future__ import annotations
2
2
 
3
3
  import os
4
4
  from dataclasses import dataclass
5
- from typing import Dict, Optional, Text
5
+ from typing import Any, Dict, Optional, Text
6
6
 
7
7
  from rasa.utils.common import read_global_config_value, write_global_config_value
8
8
 
9
9
  from rasa.studio.constants import (
10
10
  RASA_STUDIO_AUTH_SERVER_URL_ENV,
11
11
  RASA_STUDIO_CLI_CLIENT_ID_KEY_ENV,
12
+ RASA_STUDIO_CLI_DISABLE_VERIFY_KEY_ENV,
12
13
  RASA_STUDIO_CLI_REALM_NAME_KEY_ENV,
13
14
  RASA_STUDIO_CLI_STUDIO_URL_ENV,
14
15
  STUDIO_CONFIG_KEY,
@@ -19,6 +20,7 @@ STUDIO_URL_KEY = "studio_url"
19
20
  CLIENT_ID_KEY = "client_id"
20
21
  REALM_NAME_KEY = "realm_name"
21
22
  CLIENT_SECRET_KEY = "client_secret"
23
+ DISABLE_VERIFY = "disable_verify"
22
24
 
23
25
 
24
26
  @dataclass
@@ -27,13 +29,15 @@ class StudioConfig:
27
29
  studio_url: Optional[Text]
28
30
  client_id: Optional[Text]
29
31
  realm_name: Optional[Text]
32
+ disable_verify: bool = False
30
33
 
31
- def to_dict(self) -> Dict[Text, Optional[Text]]:
34
+ def to_dict(self) -> Dict[Text, Optional[Any]]:
32
35
  return {
33
36
  AUTH_SERVER_URL_KEY: self.authentication_server_url,
34
37
  STUDIO_URL_KEY: self.studio_url,
35
38
  CLIENT_ID_KEY: self.client_id,
36
39
  REALM_NAME_KEY: self.realm_name,
40
+ DISABLE_VERIFY: self.disable_verify,
37
41
  }
38
42
 
39
43
  @classmethod
@@ -43,6 +47,7 @@ class StudioConfig:
43
47
  studio_url=data[STUDIO_URL_KEY],
44
48
  client_id=data[CLIENT_ID_KEY],
45
49
  realm_name=data[REALM_NAME_KEY],
50
+ disable_verify=data.get(DISABLE_VERIFY, False),
46
51
  )
47
52
 
48
53
  def write_config(self) -> None:
@@ -73,7 +78,7 @@ class StudioConfig:
73
78
  config = read_global_config_value(STUDIO_CONFIG_KEY, unavailable_ok=True)
74
79
 
75
80
  if config is None:
76
- return StudioConfig(None, None, None, None)
81
+ return StudioConfig(None, None, None, None, False)
77
82
 
78
83
  if not isinstance(config, dict):
79
84
  raise ValueError(
@@ -83,7 +88,7 @@ class StudioConfig:
83
88
  )
84
89
 
85
90
  for key in config:
86
- if not isinstance(config[key], str):
91
+ if not isinstance(config[key], str) and key != DISABLE_VERIFY:
87
92
  raise ValueError(
88
93
  "Invalid config file format. "
89
94
  f"Key '{key}' is not a text value."
@@ -102,6 +107,9 @@ class StudioConfig:
102
107
  studio_url=StudioConfig._read_env_value(RASA_STUDIO_CLI_STUDIO_URL_ENV),
103
108
  client_id=StudioConfig._read_env_value(RASA_STUDIO_CLI_CLIENT_ID_KEY_ENV),
104
109
  realm_name=StudioConfig._read_env_value(RASA_STUDIO_CLI_REALM_NAME_KEY_ENV),
110
+ disable_verify=bool(
111
+ os.getenv(RASA_STUDIO_CLI_DISABLE_VERIFY_KEY_ENV, False)
112
+ ),
105
113
  )
106
114
 
107
115
  @staticmethod
@@ -124,4 +132,5 @@ class StudioConfig:
124
132
  studio_url=self.studio_url or other.studio_url,
125
133
  client_id=self.client_id or other.client_id,
126
134
  realm_name=self.realm_name or other.realm_name,
135
+ disable_verify=self.disable_verify or other.disable_verify,
127
136
  )
rasa/studio/constants.py CHANGED
@@ -10,6 +10,7 @@ RASA_STUDIO_AUTH_SERVER_URL_ENV = "RASA_STUDIO_AUTH_SERVER_URL"
10
10
  RASA_STUDIO_CLI_STUDIO_URL_ENV = "RASA_STUDIO_CLI_STUDIO_URL"
11
11
  RASA_STUDIO_CLI_REALM_NAME_KEY_ENV = "RASA_STUDIO_CLI_REALM_NAME_KEY"
12
12
  RASA_STUDIO_CLI_CLIENT_ID_KEY_ENV = "RASA_STUDIO_CLI_CLIENT_ID_KEY"
13
+ RASA_STUDIO_CLI_DISABLE_VERIFY_KEY_ENV = "RASA_STUDIO_CLI_DISABLE_VERIFY_KEY"
13
14
 
14
15
  STUDIO_NLU_FILENAME = "studio_nlu.yml"
15
16
  STUDIO_DOMAIN_FILENAME = "studio_domain.yml"
@@ -76,7 +76,9 @@ class StudioDataHandler:
76
76
 
77
77
  return request
78
78
 
79
- def _make_request(self, GQL_req: Dict[Any, Any]) -> Dict[Any, Any]:
79
+ def _make_request(
80
+ self, GQL_req: Dict[Any, Any], verify: bool = True
81
+ ) -> Dict[Any, Any]:
80
82
  token = KeycloakTokenReader().get_token()
81
83
  if token.is_expired():
82
84
  token = self.refresh_token(token)
@@ -93,6 +95,7 @@ class StudioDataHandler:
93
95
  "Authorization": f"{token.token_type} {token.access_token}",
94
96
  "Content-Type": "application/json",
95
97
  },
98
+ verify=verify,
96
99
  )
97
100
 
98
101
  if res.status_code != 200:
@@ -128,7 +131,9 @@ class StudioDataHandler:
128
131
  The data from Rasa Studio.
129
132
  """
130
133
  GQL_req = self._build_request()
131
- response = self._make_request(GQL_req)
134
+ verify = not self.studio_config.disable_verify
135
+
136
+ response = self._make_request(GQL_req, verify=verify)
132
137
  self._extract_data(response)
133
138
 
134
139
  def request_data(
@@ -145,7 +150,9 @@ class StudioDataHandler:
145
150
  The data from Rasa Studio.
146
151
  """
147
152
  GQL_req = self._build_request(intent_names, entity_names)
148
- response = self._make_request(GQL_req)
153
+ verify = not self.studio_config.disable_verify
154
+
155
+ response = self._make_request(GQL_req, verify=verify)
149
156
  self._extract_data(response)
150
157
 
151
158
  def get_config(self) -> Optional[str]:
rasa/studio/upload.py CHANGED
@@ -56,29 +56,37 @@ def _get_selected_entities_and_intents(
56
56
 
57
57
  def handle_upload(args: argparse.Namespace) -> None:
58
58
  """Uploads primitives to rasa studio."""
59
- endpoint = StudioConfig.read_config().studio_url
59
+ studio_config = StudioConfig.read_config()
60
+ endpoint = studio_config.studio_url
61
+ verify = not studio_config.disable_verify
62
+
60
63
  if not endpoint:
61
64
  rasa.shared.utils.cli.print_error_and_exit(
62
65
  "No GraphQL endpoint found in config. Please run `rasa studio config`."
63
66
  )
64
- else:
65
- structlogger.info(
66
- "rasa.studio.upload.loading_data", event_info="Loading data..."
67
- )
67
+ return
68
68
 
69
- args.domain = rasa.cli.utils.get_validated_path(
70
- args.domain, "domain", DEFAULT_DOMAIN_PATHS
69
+ if not is_auth_working(endpoint, verify):
70
+ rasa.shared.utils.cli.print_error_and_exit(
71
+ "Authentication is invalid or expired. Please run `rasa studio login`."
71
72
  )
73
+ return
72
74
 
73
- args.config = rasa.cli.utils.get_validated_path(
74
- args.config, "config", DEFAULT_CONFIG_PATH
75
- )
75
+ structlogger.info("rasa.studio.upload.loading_data", event_info="Loading data...")
76
76
 
77
- # check safely if args.calm is set and not fail if not
78
- if hasattr(args, "calm") and args.calm:
79
- upload_calm_assistant(args, endpoint)
80
- else:
81
- upload_nlu_assistant(args, endpoint)
77
+ args.domain = rasa.cli.utils.get_validated_path(
78
+ args.domain, "domain", DEFAULT_DOMAIN_PATHS
79
+ )
80
+
81
+ args.config = rasa.cli.utils.get_validated_path(
82
+ args.config, "config", DEFAULT_CONFIG_PATH
83
+ )
84
+
85
+ # check safely if args.calm is set and not fail if not
86
+ if hasattr(args, "calm") and args.calm:
87
+ upload_calm_assistant(args, endpoint, verify=verify)
88
+ else:
89
+ upload_nlu_assistant(args, endpoint, verify=verify)
82
90
 
83
91
 
84
92
  config_keys = [
@@ -121,12 +129,18 @@ def _get_assistant_name(config: Dict[Text, Any]) -> str:
121
129
  ),
122
130
  )
123
131
 
124
- structlogger.info(f"Uploading assistant with the name '{assistant_name}'.")
132
+ structlogger.info(
133
+ "rasa.studio.upload.name_selected",
134
+ event_info=f"Uploading assistant with the name '{assistant_name}'.",
135
+ assistant_name=assistant_name,
136
+ )
125
137
  return assistant_name
126
138
 
127
139
 
128
140
  @with_studio_error_handler
129
- def upload_calm_assistant(args: argparse.Namespace, endpoint: str) -> StudioResult:
141
+ def upload_calm_assistant(
142
+ args: argparse.Namespace, endpoint: str, verify: bool = True
143
+ ) -> StudioResult:
130
144
  """Uploads the CALM assistant data to Rasa Studio.
131
145
 
132
146
  Args:
@@ -215,12 +229,16 @@ def upload_calm_assistant(args: argparse.Namespace, endpoint: str) -> StudioResu
215
229
  nlu_yaml=nlu_examples_yaml,
216
230
  )
217
231
 
218
- structlogger.info("Uploading to Rasa Studio...")
219
- return make_request(endpoint, graphql_req)
232
+ structlogger.info(
233
+ "rasa.studio.upload.calm", event_info="Uploading to Rasa Studio..."
234
+ )
235
+ return make_request(endpoint, graphql_req, verify)
220
236
 
221
237
 
222
238
  @with_studio_error_handler
223
- def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResult:
239
+ def upload_nlu_assistant(
240
+ args: argparse.Namespace, endpoint: str, verify: bool = True
241
+ ) -> StudioResult:
224
242
  """Uploads the classic (dm1) assistant data to Rasa Studio.
225
243
 
226
244
  Args:
@@ -230,10 +248,14 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
230
248
  - intents: The intents to upload
231
249
  - entities: The entities to upload
232
250
  endpoint: The studio endpoint
251
+ verify: Whether to verify SSL
233
252
  Returns:
234
253
  None
235
254
  """
236
- structlogger.info("Found DM1 assistant data, parsing...")
255
+ structlogger.info(
256
+ "rasa.studio.upload.nlu_data_read",
257
+ event_info="Found DM1 assistant data, parsing...",
258
+ )
237
259
  importer = TrainingDataImporter.load_from_dict(
238
260
  domain_path=args.domain, training_data_paths=args.data, config_path=args.config
239
261
  )
@@ -250,7 +272,9 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
250
272
 
251
273
  assistant_name = _get_assistant_name(config)
252
274
 
253
- structlogger.info("Validating data...")
275
+ structlogger.info(
276
+ "rasa.studio.upload.nlu_data_validate", event_info="Validating data..."
277
+ )
254
278
  _check_for_missing_primitives(
255
279
  intents, entities, intents_from_files, entities_from_files
256
280
  )
@@ -267,16 +291,41 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
267
291
 
268
292
  graphql_req = build_request(assistant_name, nlu_examples_yaml, domain_yaml)
269
293
 
270
- structlogger.info("Uploading to Rasa Studio...")
271
- return make_request(endpoint, graphql_req)
294
+ structlogger.info(
295
+ "rasa.studio.upload.nlu", event_info="Uploading to Rasa Studio..."
296
+ )
297
+ return make_request(endpoint, graphql_req, verify)
298
+
299
+
300
+ def is_auth_working(endpoint: str, verify: bool = True) -> bool:
301
+ """Send a test request to Studio to check if auth is working."""
302
+ result = make_request(
303
+ endpoint,
304
+ {
305
+ "operationName": "LicenseDetails",
306
+ "query": (
307
+ "query LicenseDetails {\n"
308
+ " licenseDetails {\n"
309
+ " valid\n"
310
+ " scopes\n"
311
+ " __typename\n"
312
+ " }\n"
313
+ "}"
314
+ ),
315
+ "variables": {},
316
+ },
317
+ verify,
318
+ )
319
+ return result.was_successful
272
320
 
273
321
 
274
- def make_request(endpoint: str, graphql_req: Dict) -> StudioResult:
322
+ def make_request(endpoint: str, graphql_req: Dict, verify: bool = True) -> StudioResult:
275
323
  """Makes a request to the studio endpoint to upload data.
276
324
 
277
325
  Args:
278
326
  endpoint: The studio endpoint
279
327
  graphql_req: The graphql request
328
+ verify: Whether to verify SSL
280
329
  """
281
330
  token = KeycloakTokenReader().get_token()
282
331
  res = requests.post(
@@ -286,6 +335,7 @@ def make_request(endpoint: str, graphql_req: Dict) -> StudioResult:
286
335
  "Authorization": f"{token.token_type} {token.access_token}",
287
336
  "Content-Type": "application/json",
288
337
  },
338
+ verify=verify,
289
339
  )
290
340
 
291
341
  if results_logger.response_has_errors(res.json()):
@@ -301,7 +351,12 @@ def _add_missing_entities(
301
351
  for entity in entities_from_intents:
302
352
  if entity not in entities:
303
353
  structlogger.warning(
304
- f"Adding entity '{entity}' to upload since it is used in an intent."
354
+ "rasa.studio.upload.adding_missing_entity",
355
+ event_info=(
356
+ f"Adding entity '{entity}' to upload "
357
+ "since it is used in an intent."
358
+ ),
359
+ entity=entity,
305
360
  )
306
361
  all_entities.append(entity)
307
362
  return all_entities