rasa-pro 3.10.15__py3-none-any.whl → 3.11.0__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 (238) hide show
  1. rasa/__main__.py +31 -15
  2. rasa/api.py +12 -2
  3. rasa/cli/arguments/default_arguments.py +24 -4
  4. rasa/cli/arguments/run.py +15 -0
  5. rasa/cli/arguments/shell.py +5 -1
  6. rasa/cli/arguments/train.py +17 -9
  7. rasa/cli/evaluate.py +7 -7
  8. rasa/cli/inspect.py +19 -7
  9. rasa/cli/interactive.py +1 -0
  10. rasa/cli/project_templates/calm/config.yml +5 -7
  11. rasa/cli/project_templates/calm/endpoints.yml +15 -2
  12. rasa/cli/project_templates/tutorial/config.yml +8 -5
  13. rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
  14. rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
  15. rasa/cli/project_templates/tutorial/domain.yml +14 -0
  16. rasa/cli/project_templates/tutorial/endpoints.yml +5 -0
  17. rasa/cli/run.py +7 -0
  18. rasa/cli/scaffold.py +4 -2
  19. rasa/cli/studio/upload.py +0 -15
  20. rasa/cli/train.py +14 -53
  21. rasa/cli/utils.py +14 -11
  22. rasa/cli/x.py +7 -7
  23. rasa/constants.py +3 -1
  24. rasa/core/actions/action.py +77 -33
  25. rasa/core/actions/action_hangup.py +29 -0
  26. rasa/core/actions/action_repeat_bot_messages.py +89 -0
  27. rasa/core/actions/e2e_stub_custom_action_executor.py +5 -1
  28. rasa/core/actions/http_custom_action_executor.py +4 -0
  29. rasa/core/agent.py +2 -2
  30. rasa/core/brokers/kafka.py +3 -1
  31. rasa/core/brokers/pika.py +3 -1
  32. rasa/core/channels/__init__.py +10 -6
  33. rasa/core/channels/channel.py +41 -4
  34. rasa/core/channels/development_inspector.py +150 -46
  35. rasa/core/channels/inspector/README.md +1 -1
  36. rasa/core/channels/inspector/dist/assets/{arc-b6e548fe.js → arc-bc141fb2.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-fa03ac9e.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-ee67392a.js → classDiagram-936ed81e-55366915.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-9b283fae.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-8b6fcc2a.js → createText-62fc7601-b0ec81d6.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-22e77f4f.js → edges-f2ad444c-6166330c.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-60ffc87f.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-9dd802e4.js → flowDb-1972c806-fca3bfe4.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-5fa1912f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
  46. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-622a1fd2.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-e285a63a.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-f237bdca.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-4b03d70e.js → index-2c4b9a3b-f55afcdf.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/index-e7cef9de.js +1317 -0
  51. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-72a0fa5f.js → infoDiagram-736b4530-124d4a14.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-82218c41.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{layout-78cff630.js → layout-b9885fb6.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{line-5038b469.js → line-7c59abb6.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{linear-c4fc4098.js → linear-4776f780.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-c33c8ea6.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-a8d03059.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-6a0e56b2.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-2dc7c7bd.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-2360fe39.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-41b9f9ad.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-0aad326f.js → stateDiagram-59f0c015-042b3137.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-9847d984.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-564d890e.js → styles-080da4f6-23ffa4fc.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-38957613.js → styles-3dcbcfbf-94f59763.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-f0fc6921.js → styles-9c745c82-78a6bebc.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-ef3c5a77.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-bf3e91c1.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-4d4026c0.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
  70. rasa/core/channels/inspector/dist/index.html +18 -15
  71. rasa/core/channels/inspector/index.html +17 -14
  72. rasa/core/channels/inspector/package.json +5 -1
  73. rasa/core/channels/inspector/src/App.tsx +118 -68
  74. rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
  75. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +11 -10
  76. rasa/core/channels/inspector/src/components/DialogueStack.tsx +10 -25
  77. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -3
  78. rasa/core/channels/inspector/src/helpers/audiostream.ts +165 -0
  79. rasa/core/channels/inspector/src/helpers/formatters.test.ts +10 -0
  80. rasa/core/channels/inspector/src/helpers/formatters.ts +107 -41
  81. rasa/core/channels/inspector/src/helpers/utils.ts +92 -7
  82. rasa/core/channels/inspector/src/types.ts +21 -1
  83. rasa/core/channels/inspector/yarn.lock +94 -1
  84. rasa/core/channels/rest.py +51 -46
  85. rasa/core/channels/socketio.py +28 -1
  86. rasa/core/channels/telegram.py +1 -1
  87. rasa/core/channels/twilio.py +1 -1
  88. rasa/core/channels/{audiocodes.py → voice_ready/audiocodes.py} +122 -69
  89. rasa/core/channels/{voice_aware → voice_ready}/jambonz.py +26 -8
  90. rasa/core/channels/{voice_aware → voice_ready}/jambonz_protocol.py +57 -5
  91. rasa/core/channels/{twilio_voice.py → voice_ready/twilio_voice.py} +64 -28
  92. rasa/core/channels/voice_ready/utils.py +37 -0
  93. rasa/core/channels/voice_stream/asr/__init__.py +0 -0
  94. rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
  95. rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
  96. rasa/core/channels/voice_stream/asr/azure.py +129 -0
  97. rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
  98. rasa/core/channels/voice_stream/audio_bytes.py +8 -0
  99. rasa/core/channels/voice_stream/browser_audio.py +107 -0
  100. rasa/core/channels/voice_stream/call_state.py +23 -0
  101. rasa/core/channels/voice_stream/tts/__init__.py +0 -0
  102. rasa/core/channels/voice_stream/tts/azure.py +106 -0
  103. rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
  104. rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
  105. rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
  106. rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
  107. rasa/core/channels/voice_stream/util.py +57 -0
  108. rasa/core/channels/voice_stream/voice_channel.py +427 -0
  109. rasa/core/information_retrieval/qdrant.py +1 -0
  110. rasa/core/nlg/contextual_response_rephraser.py +45 -17
  111. rasa/{nlu → core}/persistor.py +203 -68
  112. rasa/core/policies/enterprise_search_policy.py +119 -63
  113. rasa/core/policies/flows/flow_executor.py +15 -22
  114. rasa/core/policies/intentless_policy.py +83 -28
  115. rasa/core/processor.py +25 -0
  116. rasa/core/run.py +12 -2
  117. rasa/core/secrets_manager/constants.py +4 -0
  118. rasa/core/secrets_manager/factory.py +8 -0
  119. rasa/core/secrets_manager/vault.py +11 -1
  120. rasa/core/training/interactive.py +33 -34
  121. rasa/core/utils.py +47 -21
  122. rasa/dialogue_understanding/coexistence/llm_based_router.py +41 -14
  123. rasa/dialogue_understanding/commands/__init__.py +6 -0
  124. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
  125. rasa/dialogue_understanding/commands/session_end_command.py +61 -0
  126. rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
  127. rasa/dialogue_understanding/commands/utils.py +5 -0
  128. rasa/dialogue_understanding/generator/constants.py +2 -0
  129. rasa/dialogue_understanding/generator/flow_retrieval.py +47 -9
  130. rasa/dialogue_understanding/generator/llm_based_command_generator.py +38 -15
  131. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  132. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +35 -13
  133. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +3 -0
  134. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +60 -13
  135. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +53 -0
  136. rasa/dialogue_understanding/patterns/repeat.py +37 -0
  137. rasa/dialogue_understanding/patterns/user_silence.py +37 -0
  138. rasa/dialogue_understanding/processor/command_processor.py +21 -1
  139. rasa/e2e_test/aggregate_test_stats_calculator.py +1 -11
  140. rasa/e2e_test/assertions.py +136 -61
  141. rasa/e2e_test/assertions_schema.yml +23 -0
  142. rasa/e2e_test/e2e_test_case.py +85 -6
  143. rasa/e2e_test/e2e_test_runner.py +2 -3
  144. rasa/engine/graph.py +0 -1
  145. rasa/engine/loader.py +12 -0
  146. rasa/engine/recipes/config_files/default_config.yml +0 -3
  147. rasa/engine/recipes/default_recipe.py +0 -1
  148. rasa/engine/recipes/graph_recipe.py +0 -1
  149. rasa/engine/runner/dask.py +2 -2
  150. rasa/engine/storage/local_model_storage.py +12 -42
  151. rasa/engine/storage/storage.py +1 -5
  152. rasa/engine/validation.py +527 -74
  153. rasa/model_manager/__init__.py +0 -0
  154. rasa/model_manager/config.py +40 -0
  155. rasa/model_manager/model_api.py +559 -0
  156. rasa/model_manager/runner_service.py +286 -0
  157. rasa/model_manager/socket_bridge.py +146 -0
  158. rasa/model_manager/studio_jwt_auth.py +86 -0
  159. rasa/model_manager/trainer_service.py +325 -0
  160. rasa/model_manager/utils.py +87 -0
  161. rasa/model_manager/warm_rasa_process.py +187 -0
  162. rasa/model_service.py +112 -0
  163. rasa/model_training.py +42 -23
  164. rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
  165. rasa/server.py +4 -2
  166. rasa/shared/constants.py +60 -8
  167. rasa/shared/core/constants.py +13 -0
  168. rasa/shared/core/domain.py +107 -50
  169. rasa/shared/core/events.py +29 -0
  170. rasa/shared/core/flows/flow.py +5 -0
  171. rasa/shared/core/flows/flows_list.py +19 -6
  172. rasa/shared/core/flows/flows_yaml_schema.json +10 -0
  173. rasa/shared/core/flows/utils.py +39 -0
  174. rasa/shared/core/flows/validation.py +121 -0
  175. rasa/shared/core/flows/yaml_flows_io.py +15 -27
  176. rasa/shared/core/slots.py +5 -0
  177. rasa/shared/importers/importer.py +59 -41
  178. rasa/shared/importers/multi_project.py +23 -11
  179. rasa/shared/importers/rasa.py +12 -3
  180. rasa/shared/importers/remote_importer.py +196 -0
  181. rasa/shared/importers/utils.py +3 -1
  182. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  183. rasa/shared/nlu/training_data/training_data.py +18 -19
  184. rasa/shared/providers/_configs/litellm_router_client_config.py +220 -0
  185. rasa/shared/providers/_configs/model_group_config.py +167 -0
  186. rasa/shared/providers/_configs/openai_client_config.py +1 -1
  187. rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
  188. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
  189. rasa/shared/providers/_configs/utils.py +16 -0
  190. rasa/shared/providers/_utils.py +79 -0
  191. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +13 -29
  192. rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
  193. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  194. rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
  195. rasa/shared/providers/llm/_base_litellm_client.py +34 -22
  196. rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
  197. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  198. rasa/shared/providers/llm/litellm_router_llm_client.py +182 -0
  199. rasa/shared/providers/llm/rasa_llm_client.py +112 -0
  200. rasa/shared/providers/llm/self_hosted_llm_client.py +5 -29
  201. rasa/shared/providers/mappings.py +19 -0
  202. rasa/shared/providers/router/__init__.py +0 -0
  203. rasa/shared/providers/router/_base_litellm_router_client.py +183 -0
  204. rasa/shared/providers/router/router_client.py +73 -0
  205. rasa/shared/utils/common.py +40 -24
  206. rasa/shared/utils/health_check/__init__.py +0 -0
  207. rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
  208. rasa/shared/utils/health_check/health_check.py +258 -0
  209. rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
  210. rasa/shared/utils/io.py +27 -6
  211. rasa/shared/utils/llm.py +353 -43
  212. rasa/shared/utils/schemas/events.py +2 -0
  213. rasa/shared/utils/schemas/model_config.yml +0 -10
  214. rasa/shared/utils/yaml.py +181 -38
  215. rasa/studio/data_handler.py +3 -1
  216. rasa/studio/upload.py +160 -74
  217. rasa/telemetry.py +94 -17
  218. rasa/tracing/config.py +3 -1
  219. rasa/tracing/instrumentation/attribute_extractors.py +95 -18
  220. rasa/tracing/instrumentation/instrumentation.py +121 -0
  221. rasa/utils/common.py +5 -0
  222. rasa/utils/endpoints.py +27 -1
  223. rasa/utils/io.py +8 -16
  224. rasa/utils/log_utils.py +9 -2
  225. rasa/utils/sanic_error_handler.py +32 -0
  226. rasa/validator.py +110 -4
  227. rasa/version.py +1 -1
  228. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/METADATA +14 -12
  229. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/RECORD +234 -183
  230. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +0 -1
  231. rasa/core/channels/inspector/dist/assets/index-a5d3e69d.js +0 -1040
  232. rasa/core/channels/voice_aware/utils.py +0 -20
  233. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +0 -407
  234. /rasa/core/channels/{voice_aware → voice_ready}/__init__.py +0 -0
  235. /rasa/core/channels/{voice_native → voice_stream}/__init__.py +0 -0
  236. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/NOTICE +0 -0
  237. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/WHEEL +0 -0
  238. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.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,6 +26,7 @@ from rasa.shared.constants import (
31
26
  LATEST_TRAINING_DATA_FORMAT_VERSION,
32
27
  SCHEMA_EXTENSIONS_FILE,
33
28
  RESPONSES_SCHEMA_FILE,
29
+ SENSITIVE_DATA,
34
30
  )
35
31
  from rasa.shared.exceptions import (
36
32
  YamlException,
@@ -50,6 +46,11 @@ from rasa.shared.utils.io import (
50
46
  raise_warning,
51
47
  read_json_file,
52
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
53
54
 
54
55
  logger = logging.getLogger(__name__)
55
56
 
@@ -62,8 +63,17 @@ READ_YAML_FILE_CACHE_MAXSIZE = os.environ.get(
62
63
 
63
64
  @dataclass
64
65
  class PathWithError:
66
+ """Represents a validation error at a specific location in the YAML content.
67
+
68
+ Attributes:
69
+ message (str): A description of the validation error.
70
+ path (List[str]): Path to the node where the error occurred.
71
+ key (Optional[str]): The specific key associated with the error, if any.
72
+ """
73
+
65
74
  message: str
66
75
  path: List[str] = field(default_factory=list)
76
+ key: Optional[str] = None
67
77
 
68
78
 
69
79
  def fix_yaml_loader() -> None:
@@ -87,6 +97,12 @@ def replace_environment_variables() -> None:
87
97
  def env_var_constructor(loader: BaseConstructor, node: ScalarNode) -> str:
88
98
  """Process environment variables found in the YAML."""
89
99
  value = loader.construct_scalar(node)
100
+
101
+ # get key of current node
102
+ key_node = list(loader.constructed_objects)[-1]
103
+ if isinstance(key_node, ScalarNode) and key_node.value in SENSITIVE_DATA:
104
+ return value
105
+
90
106
  expanded_vars = os.path.expandvars(value)
91
107
  not_expanded = [
92
108
  w for w in expanded_vars.split() if w.startswith("$") and w in value
@@ -104,7 +120,6 @@ def replace_environment_variables() -> None:
104
120
 
105
121
 
106
122
  fix_yaml_loader()
107
- replace_environment_variables()
108
123
 
109
124
 
110
125
  class YamlValidationException(YamlException, ValueError):
@@ -138,21 +153,72 @@ class YamlValidationException(YamlException, ValueError):
138
153
  if self.validation_errors:
139
154
  unique_errors = {}
140
155
  for error in self.validation_errors:
141
- line_number = self._line_number_for_path(self.content, error.path)
156
+ line_number = self._line_number_for_path(
157
+ self.content, error.path, error.key
158
+ )
142
159
 
143
160
  if line_number and self.filename:
144
- error_representation = f" in {self.filename}:{line_number}:\n"
161
+ error_location = f" in {self.filename}:{line_number}:\n"
145
162
  elif line_number:
146
- error_representation = f" in Line {line_number}:\n"
163
+ error_location = f" in Line {line_number}:\n"
147
164
  else:
148
- error_representation = ""
165
+ error_location = ""
149
166
 
150
- error_representation += f" {error.message}"
151
- unique_errors[error.message] = error_representation
167
+ code_snippet = self._get_code_snippet(line_number)
168
+ error_message = f"{error_location}\n{code_snippet}{error.message}\n"
169
+ unique_errors[error.message] = error_message
152
170
  error_msg = "\n".join(unique_errors.values())
153
171
  msg += f":\n{error_msg}"
154
172
  return msg
155
173
 
174
+ def _get_code_snippet(
175
+ self,
176
+ error_line: Optional[int],
177
+ context_lines: int = 2,
178
+ ) -> str:
179
+ """Extract code snippet from the YAML lines around the error.
180
+
181
+ Args:
182
+ error_line: Line number where the error occurred (1-based).
183
+ context_lines: Number of context lines before and after the error line.
184
+ Default is 2, balancing context and readability. Adjust as needed.
185
+
186
+ Returns:
187
+ A string containing the code snippet with the error highlighted.
188
+ """
189
+ yaml_lines = self._get_serialized_yaml_lines()
190
+ if not yaml_lines or error_line is None:
191
+ return ""
192
+
193
+ start = max(error_line - context_lines - 1, 0)
194
+ end = min(error_line + context_lines, len(yaml_lines))
195
+ snippet_lines = yaml_lines[start:end]
196
+ snippet = ""
197
+ for idx, line_content in enumerate(snippet_lines, start=start + 1):
198
+ prefix = ">>> " if idx == error_line else " "
199
+ line_number_str = str(idx)
200
+ snippet += f"{prefix}{line_number_str} | {line_content}\n"
201
+ return snippet
202
+
203
+ def _get_serialized_yaml_lines(self) -> List[str]:
204
+ """Serialize the content back to YAML and return the lines."""
205
+ yaml_lines = []
206
+ try:
207
+ yaml = YAML()
208
+ yaml.default_flow_style = False
209
+ # Set width to 1000, so we don't break the lines of the original YAML file
210
+ yaml.width = 1000 # type: ignore[assignment]
211
+ yaml.indent(mapping=2, sequence=4, offset=2)
212
+ stream = io.StringIO()
213
+ yaml.dump(self.content, stream)
214
+ serialized_yaml = stream.getvalue()
215
+ yaml_lines = serialized_yaml.splitlines()
216
+ return yaml_lines
217
+ except Exception as exc:
218
+ logger.debug(f"Error serializing YAML content: {exc}")
219
+
220
+ return yaml_lines
221
+
156
222
  def _calculate_number_of_lines(
157
223
  self,
158
224
  current: Union[CommentedSeq, CommentedMap],
@@ -220,7 +286,9 @@ class YamlValidationException(YamlException, ValueError):
220
286
  # Return the calculated child offset and True indicating a line number was found
221
287
  return child_offset, True
222
288
 
223
- def _line_number_for_path(self, current: Any, path: List[str]) -> Optional[int]:
289
+ def _line_number_for_path(
290
+ self, current: Any, path: List[str], key: Optional[str] = None
291
+ ) -> Optional[int]:
224
292
  """Get line number for a yaml path in the current content.
225
293
 
226
294
  Implemented using recursion: algorithm goes down the path navigating to the
@@ -229,6 +297,7 @@ class YamlValidationException(YamlException, ValueError):
229
297
  Args:
230
298
  current: current content
231
299
  path: path to traverse within the content
300
+ key: the key associated with the error, if any
232
301
 
233
302
  Returns:
234
303
  the line number of the path in the content.
@@ -239,6 +308,10 @@ class YamlValidationException(YamlException, ValueError):
239
308
  this_line = current.lc.line + 1 if hasattr(current, "lc") else None
240
309
 
241
310
  if not path:
311
+ if key and hasattr(current, "lc"):
312
+ if hasattr(current.lc, "data") and key in current.lc.data:
313
+ key_line_no = current.lc.data[key][0] + 1
314
+ return key_line_no
242
315
  return this_line
243
316
 
244
317
  head, tail = path[0], path[1:]
@@ -248,7 +321,7 @@ class YamlValidationException(YamlException, ValueError):
248
321
 
249
322
  if head:
250
323
  if isinstance(current, dict) and head in current:
251
- line = self._line_number_for_path(current[head], tail)
324
+ line = self._line_number_for_path(current[head], tail, key)
252
325
  if line is None:
253
326
  line_offset, found_lc = self._calculate_number_of_lines(
254
327
  current, head
@@ -258,14 +331,17 @@ class YamlValidationException(YamlException, ValueError):
258
331
  return this_line + line_offset
259
332
  return line
260
333
  elif isinstance(current, list) and head.isdigit():
261
- return self._line_number_for_path(current[int(head)], tail) or this_line
334
+ return (
335
+ self._line_number_for_path(current[int(head)], tail, key)
336
+ or this_line
337
+ )
262
338
  else:
263
339
  return this_line
264
- return self._line_number_for_path(current, tail) or this_line
340
+ return self._line_number_for_path(current, tail, key) or this_line
265
341
 
266
342
 
267
343
  def read_schema_file(
268
- schema_file: str, package_name: str = PACKAGE_NAME
344
+ schema_file: str, package_name: str = PACKAGE_NAME, expand_env_vars: bool = True
269
345
  ) -> Union[List[Any], Dict[str, Any]]:
270
346
  """Read a schema file from the package.
271
347
 
@@ -273,12 +349,13 @@ def read_schema_file(
273
349
  schema_file: The schema file to read.
274
350
  package_name: the name of the package the schema is located in. defaults
275
351
  to `rasa`.
352
+ expand_env_vars: Whether to expand environment variables in the file.
276
353
 
277
354
  Returns:
278
355
  The schema as a dictionary.
279
356
  """
280
357
  schema_path = str(files(package_name).joinpath(schema_file))
281
- return read_yaml_file(schema_path)
358
+ return read_yaml_file(schema_path, expand_env_vars=expand_env_vars)
282
359
 
283
360
 
284
361
  def parse_raw_yaml(raw_yaml_content: str) -> Dict[str, Any]:
@@ -323,13 +400,26 @@ def validate_yaml_content_using_schema(
323
400
  try:
324
401
  core.validate(raise_exception=True)
325
402
  except SchemaError:
403
+ # PyKwalify propagates each validation error up the data hierarchy, resulting
404
+ # in multiple redundant errors for a single issue. To present a clear message
405
+ # about the root cause, we use only the first error.
406
+ error = core.errors[0]
407
+
408
+ # Increment numeric indices by 1 to convert from 0-based to 1-based indexing
409
+ error_message = re.sub(
410
+ r"(/)(\d+)", lambda m: f"/{int(m.group(2)) + 1}", str(error)
411
+ )
412
+
326
413
  raise YamlValidationException(
327
414
  "Please make sure the file is correct and all "
328
415
  "mandatory parameters are specified. Here are the errors "
329
416
  "found during validation",
330
417
  [
331
- PathWithError(message=str(e), path=e.path.split("/"))
332
- for e in core.errors
418
+ PathWithError(
419
+ message=error_message,
420
+ path=error.path.removeprefix("/").split("/"),
421
+ key=getattr(error, "key", None),
422
+ )
333
423
  ],
334
424
  content=yaml_content,
335
425
  )
@@ -339,6 +429,7 @@ def validate_raw_yaml_using_schema(
339
429
  raw_yaml_content: str,
340
430
  schema_content: Dict[str, Any],
341
431
  schema_extensions: Optional[List[str]] = None,
432
+ expand_env_vars: bool = True,
342
433
  ) -> None:
343
434
  """Validate raw yaml content using a schema.
344
435
 
@@ -348,6 +439,7 @@ def validate_raw_yaml_using_schema(
348
439
  raw_yaml_content: the raw YAML content to be validated (usually a string)
349
440
  schema_content: the schema for the yaml_file_content
350
441
  schema_extensions: pykwalify schema extension files
442
+ expand_env_vars: Whether to expand environment variables.
351
443
  """
352
444
  try:
353
445
  # we need "rt" since
@@ -355,7 +447,11 @@ def validate_raw_yaml_using_schema(
355
447
  # will include e.g. at which line an object was parsed. this is very
356
448
  # helpful when we validate files later on and want to point the user to the
357
449
  # right line
358
- yaml_data = read_yaml(raw_yaml_content, reader_type=["safe", "rt"])
450
+ yaml_data = read_yaml(
451
+ raw_yaml_content,
452
+ reader_type=["safe", "rt"],
453
+ expand_env_vars=expand_env_vars,
454
+ )
359
455
  except (YAMLError, DuplicateKeyError) as e:
360
456
  raise YamlSyntaxException(underlying_yaml_exception=e)
361
457
 
@@ -363,7 +459,10 @@ def validate_raw_yaml_using_schema(
363
459
 
364
460
 
365
461
  def validate_raw_yaml_using_schema_file(
366
- raw_yaml_content: str, schema_path: str, package_name: str = PACKAGE_NAME
462
+ raw_yaml_content: str,
463
+ schema_path: str,
464
+ package_name: str = PACKAGE_NAME,
465
+ expand_env_vars: bool = True,
367
466
  ) -> None:
368
467
  """Validate raw yaml content using a schema from file.
369
468
 
@@ -372,15 +471,21 @@ def validate_raw_yaml_using_schema_file(
372
471
  schema_path: the schema used for validation
373
472
  package_name: the name of the package the schema is located in. defaults
374
473
  to `rasa`.
474
+ expand_env_vars: Whether to expand environment variables in the file.
375
475
  """
376
- schema_content = read_schema_file(schema_path, package_name)
377
- validate_raw_yaml_using_schema(raw_yaml_content, schema_content)
476
+ schema_content = read_schema_file(
477
+ schema_path, package_name, expand_env_vars=expand_env_vars
478
+ )
479
+ validate_raw_yaml_using_schema(
480
+ raw_yaml_content, schema_content, expand_env_vars=expand_env_vars
481
+ )
378
482
 
379
483
 
380
484
  def validate_raw_yaml_content_using_schema_with_responses(
381
485
  raw_yaml_content: str,
382
486
  schema_content: Union[List[Any], Dict[str, Any]],
383
487
  package_name: str = PACKAGE_NAME,
488
+ expand_env_vars: bool = True,
384
489
  ) -> None:
385
490
  """Validate raw yaml content using a schema with responses sub-schema.
386
491
 
@@ -389,18 +494,26 @@ def validate_raw_yaml_content_using_schema_with_responses(
389
494
  schema_content: the content of the YAML schema
390
495
  package_name: the name of the package the schema is located in. defaults
391
496
  to `rasa`.
497
+ expand_env_vars: Whether to expand environment variables in the file.
392
498
  """
393
499
  # bot responses are part of the schema extension
394
500
  # it will be included if the schema explicitly references it with include: responses
395
- bot_responses_schema_content = read_schema_file(RESPONSES_SCHEMA_FILE, package_name)
501
+ bot_responses_schema_content = read_schema_file(
502
+ RESPONSES_SCHEMA_FILE, package_name, expand_env_vars=expand_env_vars
503
+ )
396
504
  schema_content = dict(schema_content, **bot_responses_schema_content)
397
505
  schema_extensions = [str(files(package_name).joinpath(SCHEMA_EXTENSIONS_FILE))]
398
506
 
399
- validate_raw_yaml_using_schema(raw_yaml_content, schema_content, schema_extensions)
507
+ validate_raw_yaml_using_schema(
508
+ raw_yaml_content, schema_content, schema_extensions, expand_env_vars
509
+ )
400
510
 
401
511
 
402
512
  def validate_raw_yaml_using_schema_file_with_responses(
403
- raw_yaml_content: str, schema_path: str, package_name: str = PACKAGE_NAME
513
+ raw_yaml_content: str,
514
+ schema_path: str,
515
+ package_name: str = PACKAGE_NAME,
516
+ expand_env_vars: bool = True,
404
517
  ) -> None:
405
518
  """Validate domain yaml content using a schema from file with responses sub-schema.
406
519
 
@@ -409,10 +522,11 @@ def validate_raw_yaml_using_schema_file_with_responses(
409
522
  schema_path: the schema of the yaml file
410
523
  package_name: the name of the package the schema is located in. defaults
411
524
  to `rasa`.
525
+ expand_env_vars: Whether to expand environment variables in the file.
412
526
  """
413
- schema_content = read_schema_file(schema_path, package_name)
527
+ schema_content = read_schema_file(schema_path, package_name, expand_env_vars)
414
528
  validate_raw_yaml_content_using_schema_with_responses(
415
- raw_yaml_content, schema_content, package_name
529
+ raw_yaml_content, schema_content, package_name, expand_env_vars
416
530
  )
417
531
 
418
532
 
@@ -432,11 +546,14 @@ def read_yaml(
432
546
  ruamel.yaml.parser.ParserError: If there was an error when parsing the YAML.
433
547
  """
434
548
  custom_constructor = kwargs.get("custom_constructor", None)
549
+ expand_env_vars = kwargs.get("expand_env_vars", True)
435
550
 
436
551
  # Create YAML parser with custom constructor
437
552
  yaml_parser, reset_constructors = create_yaml_parser(
438
553
  reader_type, custom_constructor
439
554
  )
555
+ if expand_env_vars:
556
+ replace_environment_variables()
440
557
  yaml_content = yaml_parser.load(content) or {}
441
558
 
442
559
  # Reset to default constructors
@@ -492,6 +609,10 @@ def create_yaml_parser(
492
609
  yaml.resolver.BaseResolver.DEFAULT_SEQUENCE_TAG,
493
610
  original_sequence_constructor,
494
611
  )
612
+ # replace env var constructor with one that does not expand env vars
613
+ yaml_parser.constructor.add_constructor(
614
+ "!env_var", lambda loader, node: loader.construct_scalar(node)
615
+ )
495
616
 
496
617
  def custom_date_constructor(loader: SafeLoader, node: ScalarNode) -> str:
497
618
  """Custom constructor for parsing dates in the format '%Y-%m-%d'.
@@ -526,7 +647,9 @@ def _is_ascii(text: str) -> bool:
526
647
 
527
648
  @lru_cache(maxsize=READ_YAML_FILE_CACHE_MAXSIZE)
528
649
  def read_yaml_file(
529
- filename: Union[str, Path], reader_type: Union[str, Tuple[str]] = "safe"
650
+ filename: Union[str, Path],
651
+ reader_type: Union[str, Tuple[str]] = "safe",
652
+ expand_env_vars: bool = True,
530
653
  ) -> Union[List[Any], Dict[str, Any]]:
531
654
  """Parses a yaml file.
532
655
 
@@ -535,6 +658,7 @@ def read_yaml_file(
535
658
  Args:
536
659
  filename: The path to the file which should be read.
537
660
  reader_type: Reader type to use. By default "safe" will be used.
661
+ expand_env_vars: Whether to expand environment variables in the file.
538
662
 
539
663
  Returns:
540
664
  Parsed content of the file.
@@ -543,7 +667,11 @@ def read_yaml_file(
543
667
  fixed_reader_type = (
544
668
  list(reader_type) if isinstance(reader_type, tuple) else reader_type
545
669
  )
546
- return read_yaml(read_file(filename, DEFAULT_ENCODING), fixed_reader_type)
670
+ return read_yaml(
671
+ read_file(filename, DEFAULT_ENCODING),
672
+ fixed_reader_type,
673
+ expand_env_vars=expand_env_vars,
674
+ )
547
675
  except (YAMLError, DuplicateKeyError) as e:
548
676
  raise YamlSyntaxException(filename, e)
549
677
 
@@ -566,11 +694,14 @@ def read_config_file(
566
694
  return read_validated_yaml(filename, CONFIG_SCHEMA_FILE, reader_type)
567
695
 
568
696
 
569
- def read_model_configuration(filename: Union[Path, str]) -> Dict[str, Any]:
697
+ def read_model_configuration(
698
+ filename: Union[Path, str], expand_env_vars: bool = True
699
+ ) -> Dict[str, Any]:
570
700
  """Parses a model configuration file.
571
701
 
572
702
  Args:
573
703
  filename: The path to the file which should be read.
704
+ expand_env_vars: Whether to expand environment variables in the file.
574
705
 
575
706
  Raises:
576
707
  YamlValidationException: In case the model configuration doesn't match the
@@ -579,7 +710,9 @@ def read_model_configuration(filename: Union[Path, str]) -> Dict[str, Any]:
579
710
  Returns:
580
711
  Parsed config file.
581
712
  """
582
- return read_validated_yaml(filename, MODEL_CONFIG_SCHEMA_FILE)
713
+ return read_validated_yaml(
714
+ filename, MODEL_CONFIG_SCHEMA_FILE, expand_env_vars=expand_env_vars
715
+ )
583
716
 
584
717
 
585
718
  def dump_obj_as_yaml_to_string(
@@ -677,6 +810,7 @@ def read_validated_yaml(
677
810
  filename: Union[str, Path],
678
811
  schema: str,
679
812
  reader_type: Union[str, List[str]] = "safe",
813
+ expand_env_vars: bool = True,
680
814
  ) -> Any:
681
815
  """Validates YAML file content and returns parsed content.
682
816
 
@@ -685,6 +819,7 @@ def read_validated_yaml(
685
819
  schema: The path to the schema file which should be used for validating the
686
820
  file content.
687
821
  reader_type: Reader type to use. By default, "safe" will be used.
822
+ expand_env_vars: Whether to expand environment variables in the file.
688
823
 
689
824
  Returns:
690
825
  The parsed file content.
@@ -695,8 +830,10 @@ def read_validated_yaml(
695
830
  """
696
831
  content = read_file(filename)
697
832
 
698
- validate_raw_yaml_using_schema_file(content, schema)
699
- return read_yaml(content, reader_type)
833
+ validate_raw_yaml_using_schema_file(
834
+ content, schema, expand_env_vars=expand_env_vars
835
+ )
836
+ return read_yaml(content, reader_type, expand_env_vars=expand_env_vars)
700
837
 
701
838
 
702
839
  def validate_training_data(json_data: Dict[str, Any], schema: Dict[str, Any]) -> None:
@@ -815,6 +952,7 @@ def validate_yaml_with_jsonschema(
815
952
  humanize_error: Callable[
816
953
  [jsonschema.ValidationError], str
817
954
  ] = default_error_humanizer,
955
+ expand_env_vars: bool = True,
818
956
  ) -> None:
819
957
  """Validate data format.
820
958
 
@@ -825,6 +963,7 @@ def validate_yaml_with_jsonschema(
825
963
  to `rasa`.
826
964
  humanize_error: a function to convert a jsonschema.ValidationError into a
827
965
  human-readable error message. Defaults to `default_error_humanizer`.
966
+ expand_env_vars: Whether to expand environment variables in the file.
828
967
 
829
968
  Raises:
830
969
  YamlSyntaxException: if the yaml file is not valid.
@@ -842,7 +981,11 @@ def validate_yaml_with_jsonschema(
842
981
  # will include e.g. at which line an object was parsed. this is very
843
982
  # helpful when we validate files later on and want to point the user to the
844
983
  # right line
845
- source_data = read_yaml(yaml_file_content, reader_type=["safe", "rt"])
984
+ source_data = read_yaml(
985
+ yaml_file_content,
986
+ reader_type=["safe", "rt"],
987
+ expand_env_vars=expand_env_vars,
988
+ )
846
989
  except (YAMLError, DuplicateKeyError) as e:
847
990
  raise YamlSyntaxException(underlying_yaml_exception=e)
848
991
 
@@ -360,7 +360,9 @@ def import_data_from_studio(
360
360
  studio_domain.persist(domain_file)
361
361
 
362
362
  data_from_studio = TrainingDataImporter.load_from_dict(
363
- domain_path=str(domain_file), training_data_paths=data_paths
363
+ domain_path=str(domain_file),
364
+ training_data_paths=data_paths,
365
+ expand_env_vars=False,
364
366
  )
365
367
 
366
368
  return data_from_studio, data_original