rasa-pro 3.9.18__py3-none-any.whl → 3.10.3__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 (189) hide show
  1. README.md +26 -57
  2. rasa/__init__.py +1 -2
  3. rasa/__main__.py +5 -0
  4. rasa/anonymization/anonymization_rule_executor.py +2 -2
  5. rasa/api.py +26 -22
  6. rasa/cli/arguments/data.py +27 -2
  7. rasa/cli/arguments/default_arguments.py +25 -3
  8. rasa/cli/arguments/run.py +9 -9
  9. rasa/cli/arguments/train.py +2 -0
  10. rasa/cli/data.py +70 -8
  11. rasa/cli/e2e_test.py +108 -433
  12. rasa/cli/interactive.py +1 -0
  13. rasa/cli/llm_fine_tuning.py +395 -0
  14. rasa/cli/project_templates/calm/endpoints.yml +1 -1
  15. rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
  16. rasa/cli/run.py +14 -13
  17. rasa/cli/scaffold.py +10 -8
  18. rasa/cli/train.py +8 -7
  19. rasa/cli/utils.py +15 -0
  20. rasa/constants.py +7 -1
  21. rasa/core/actions/action.py +98 -49
  22. rasa/core/actions/action_run_slot_rejections.py +4 -1
  23. rasa/core/actions/custom_action_executor.py +9 -6
  24. rasa/core/actions/direct_custom_actions_executor.py +80 -0
  25. rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
  26. rasa/core/actions/grpc_custom_action_executor.py +2 -2
  27. rasa/core/actions/http_custom_action_executor.py +6 -5
  28. rasa/core/agent.py +21 -17
  29. rasa/core/channels/__init__.py +2 -0
  30. rasa/core/channels/audiocodes.py +1 -16
  31. rasa/core/channels/inspector/dist/index.html +0 -2
  32. rasa/core/channels/inspector/index.html +0 -2
  33. rasa/core/channels/voice_aware/__init__.py +0 -0
  34. rasa/core/channels/voice_aware/jambonz.py +103 -0
  35. rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
  36. rasa/core/channels/voice_aware/utils.py +20 -0
  37. rasa/core/channels/voice_native/__init__.py +0 -0
  38. rasa/core/constants.py +6 -1
  39. rasa/core/featurizers/single_state_featurizer.py +1 -22
  40. rasa/core/featurizers/tracker_featurizers.py +18 -115
  41. rasa/core/information_retrieval/faiss.py +7 -4
  42. rasa/core/information_retrieval/information_retrieval.py +8 -0
  43. rasa/core/information_retrieval/milvus.py +9 -2
  44. rasa/core/information_retrieval/qdrant.py +1 -1
  45. rasa/core/nlg/contextual_response_rephraser.py +32 -10
  46. rasa/core/nlg/summarize.py +4 -3
  47. rasa/core/policies/enterprise_search_policy.py +100 -44
  48. rasa/core/policies/flows/flow_executor.py +130 -94
  49. rasa/core/policies/intentless_policy.py +52 -28
  50. rasa/core/policies/ted_policy.py +33 -58
  51. rasa/core/policies/unexpected_intent_policy.py +7 -15
  52. rasa/core/processor.py +20 -53
  53. rasa/core/run.py +5 -4
  54. rasa/core/tracker_store.py +8 -4
  55. rasa/core/utils.py +45 -56
  56. rasa/dialogue_understanding/coexistence/llm_based_router.py +45 -12
  57. rasa/dialogue_understanding/commands/__init__.py +4 -0
  58. rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
  59. rasa/dialogue_understanding/commands/session_start_command.py +59 -0
  60. rasa/dialogue_understanding/commands/set_slot_command.py +1 -5
  61. rasa/dialogue_understanding/commands/utils.py +38 -0
  62. rasa/dialogue_understanding/generator/constants.py +10 -3
  63. rasa/dialogue_understanding/generator/flow_retrieval.py +14 -5
  64. rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -2
  65. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +106 -87
  66. rasa/dialogue_understanding/generator/nlu_command_adapter.py +28 -6
  67. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +90 -37
  68. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +15 -15
  69. rasa/dialogue_understanding/patterns/session_start.py +37 -0
  70. rasa/dialogue_understanding/processor/command_processor.py +13 -14
  71. rasa/e2e_test/aggregate_test_stats_calculator.py +124 -0
  72. rasa/e2e_test/assertions.py +1181 -0
  73. rasa/e2e_test/assertions_schema.yml +106 -0
  74. rasa/e2e_test/constants.py +20 -0
  75. rasa/e2e_test/e2e_config.py +220 -0
  76. rasa/e2e_test/e2e_config_schema.yml +26 -0
  77. rasa/e2e_test/e2e_test_case.py +131 -8
  78. rasa/e2e_test/e2e_test_converter.py +363 -0
  79. rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
  80. rasa/e2e_test/e2e_test_coverage_report.py +364 -0
  81. rasa/e2e_test/e2e_test_result.py +26 -6
  82. rasa/e2e_test/e2e_test_runner.py +491 -72
  83. rasa/e2e_test/e2e_test_schema.yml +96 -0
  84. rasa/e2e_test/pykwalify_extensions.py +39 -0
  85. rasa/e2e_test/stub_custom_action.py +70 -0
  86. rasa/e2e_test/utils/__init__.py +0 -0
  87. rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
  88. rasa/e2e_test/utils/io.py +596 -0
  89. rasa/e2e_test/utils/validation.py +80 -0
  90. rasa/engine/recipes/default_components.py +0 -2
  91. rasa/engine/storage/local_model_storage.py +0 -1
  92. rasa/env.py +9 -0
  93. rasa/llm_fine_tuning/__init__.py +0 -0
  94. rasa/llm_fine_tuning/annotation_module.py +241 -0
  95. rasa/llm_fine_tuning/conversations.py +144 -0
  96. rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
  97. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
  98. rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
  99. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
  100. rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
  101. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
  102. rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
  103. rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
  104. rasa/llm_fine_tuning/storage.py +174 -0
  105. rasa/llm_fine_tuning/train_test_split_module.py +441 -0
  106. rasa/model_training.py +48 -16
  107. rasa/nlu/classifiers/diet_classifier.py +25 -38
  108. rasa/nlu/classifiers/logistic_regression_classifier.py +9 -44
  109. rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
  110. rasa/nlu/extractors/crf_entity_extractor.py +50 -93
  111. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -78
  112. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
  113. rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
  114. rasa/nlu/persistor.py +129 -32
  115. rasa/server.py +45 -10
  116. rasa/shared/constants.py +63 -15
  117. rasa/shared/core/domain.py +15 -12
  118. rasa/shared/core/events.py +28 -2
  119. rasa/shared/core/flows/flow.py +208 -13
  120. rasa/shared/core/flows/flow_path.py +84 -0
  121. rasa/shared/core/flows/flows_list.py +28 -10
  122. rasa/shared/core/flows/flows_yaml_schema.json +269 -193
  123. rasa/shared/core/flows/validation.py +112 -25
  124. rasa/shared/core/flows/yaml_flows_io.py +149 -10
  125. rasa/shared/core/trackers.py +6 -0
  126. rasa/shared/core/training_data/visualization.html +2 -2
  127. rasa/shared/exceptions.py +4 -0
  128. rasa/shared/importers/importer.py +60 -11
  129. rasa/shared/importers/remote_importer.py +196 -0
  130. rasa/shared/nlu/constants.py +2 -0
  131. rasa/shared/nlu/training_data/features.py +2 -120
  132. rasa/shared/providers/_configs/__init__.py +0 -0
  133. rasa/shared/providers/_configs/azure_openai_client_config.py +181 -0
  134. rasa/shared/providers/_configs/client_config.py +57 -0
  135. rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
  136. rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
  137. rasa/shared/providers/_configs/openai_client_config.py +175 -0
  138. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +171 -0
  139. rasa/shared/providers/_configs/utils.py +101 -0
  140. rasa/shared/providers/_ssl_verification_utils.py +124 -0
  141. rasa/shared/providers/embedding/__init__.py +0 -0
  142. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +254 -0
  143. rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
  144. rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
  145. rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
  146. rasa/shared/providers/embedding/embedding_client.py +90 -0
  147. rasa/shared/providers/embedding/embedding_response.py +41 -0
  148. rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
  149. rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
  150. rasa/shared/providers/llm/__init__.py +0 -0
  151. rasa/shared/providers/llm/_base_litellm_client.py +227 -0
  152. rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
  153. rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
  154. rasa/shared/providers/llm/llm_client.py +76 -0
  155. rasa/shared/providers/llm/llm_response.py +50 -0
  156. rasa/shared/providers/llm/openai_llm_client.py +155 -0
  157. rasa/shared/providers/llm/self_hosted_llm_client.py +169 -0
  158. rasa/shared/providers/mappings.py +75 -0
  159. rasa/shared/utils/cli.py +30 -0
  160. rasa/shared/utils/io.py +65 -3
  161. rasa/shared/utils/llm.py +223 -200
  162. rasa/shared/utils/yaml.py +122 -7
  163. rasa/studio/download.py +19 -13
  164. rasa/studio/train.py +2 -3
  165. rasa/studio/upload.py +2 -3
  166. rasa/telemetry.py +113 -58
  167. rasa/tracing/config.py +2 -3
  168. rasa/tracing/instrumentation/attribute_extractors.py +29 -17
  169. rasa/tracing/instrumentation/instrumentation.py +4 -47
  170. rasa/utils/common.py +18 -19
  171. rasa/utils/endpoints.py +7 -4
  172. rasa/utils/io.py +66 -0
  173. rasa/utils/json_utils.py +60 -0
  174. rasa/utils/licensing.py +9 -1
  175. rasa/utils/ml_utils.py +4 -2
  176. rasa/utils/tensorflow/model_data.py +193 -2
  177. rasa/validator.py +195 -1
  178. rasa/version.py +1 -1
  179. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/METADATA +47 -72
  180. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/RECORD +185 -121
  181. rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
  182. rasa/shared/providers/openai/clients.py +0 -43
  183. rasa/shared/providers/openai/session_handler.py +0 -110
  184. rasa/utils/tensorflow/feature_array.py +0 -366
  185. /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
  186. /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
  187. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/NOTICE +0 -0
  188. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/WHEEL +0 -0
  189. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.3.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,18 @@
1
1
  from __future__ import annotations
2
2
 
3
- from collections import defaultdict
4
3
  import re
5
- from typing import Optional, Set, Text, List
6
4
  import typing
7
- from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
5
+ from collections import defaultdict
6
+ from typing import Optional, Set, Text, List
8
7
 
8
+ from rasa.shared.constants import (
9
+ RASA_DEFAULT_FLOW_PATTERN_PREFIX,
10
+ RASA_PATTERN_HUMAN_HANDOFF,
11
+ )
12
+ from rasa.shared.constants import (
13
+ RASA_PATTERN_INTERNAL_ERROR,
14
+ )
15
+ from rasa.shared.core.flows.flow import Flow
9
16
  from rasa.shared.core.flows.flow_step import (
10
17
  FlowStep,
11
18
  )
@@ -15,17 +22,15 @@ from rasa.shared.core.flows.flow_step_links import (
15
22
  ElseFlowStepLink,
16
23
  )
17
24
  from rasa.shared.core.flows.flow_step_sequence import FlowStepSequence
18
- from rasa.shared.core.flows.steps.constants import CONTINUE_STEP_PREFIX, DEFAULT_STEPS
19
25
  from rasa.shared.core.flows.steps.call import CallFlowStep
20
- from rasa.shared.core.flows.steps.link import LinkFlowStep
21
26
  from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
22
- from rasa.shared.core.flows.flow import Flow
27
+ from rasa.shared.core.flows.steps.constants import CONTINUE_STEP_PREFIX, DEFAULT_STEPS
28
+ from rasa.shared.core.flows.steps.link import LinkFlowStep
23
29
  from rasa.shared.exceptions import RasaException
24
30
 
25
31
  if typing.TYPE_CHECKING:
26
32
  from rasa.shared.core.flows.flows_list import FlowsList
27
33
 
28
-
29
34
  FLOW_ID_REGEX = r"""^[a-zA-Z0-9_][a-zA-Z0-9_-]*?$"""
30
35
 
31
36
 
@@ -132,36 +137,75 @@ class NoNextAllowedForLinkException(RasaException):
132
137
  class ReferenceToPatternException(RasaException):
133
138
  """Raised when a flow step is referencing a pattern, which is not allowed."""
134
139
 
135
- def __init__(self, referenced_pattern: str, flow_id: str, step_id: str) -> None:
140
+ def __init__(
141
+ self, referenced_pattern: str, flow_id: str, step_id: str, call_step: bool
142
+ ) -> None:
136
143
  """Initializes the exception."""
137
144
  self.step_id = step_id
138
145
  self.flow_id = flow_id
139
146
  self.referenced_pattern = referenced_pattern
147
+ self.call_step = call_step
140
148
 
141
149
  def __str__(self) -> str:
142
150
  """Return a string representation of the exception."""
143
- return (
144
- f"Step '{self.step_id}' in flow '{self.flow_id}' is referencing a pattern "
145
- f"'{self.referenced_pattern}', which is not allowed. "
146
- f"Patterns can not be used as a target for a link or call step."
151
+ message = (
152
+ f"Step '{self.step_id}' in flow '{self.flow_id}' is referencing a "
153
+ f"pattern '{self.referenced_pattern}', which is not allowed. "
154
+ )
155
+ if self.call_step:
156
+ return message + "Patterns can not be used as a target for a call step."
157
+ else:
158
+ return message + (
159
+ "All patterns, except for 'pattern_human_handoff', can "
160
+ "not be used as a target for a link step."
161
+ )
162
+
163
+
164
+ class PatternReferencedPatternException(RasaException):
165
+ """Raised when a pattern is referencing a pattern, which is not allowed."""
166
+
167
+ def __init__(self, flow_id: str, step_id: str, call_step: bool) -> None:
168
+ """Initializes the exception."""
169
+ self.step_id = step_id
170
+ self.flow_id = flow_id
171
+ self.call_step = call_step
172
+
173
+ def __str__(self) -> str:
174
+ """Return a string representation of the exception."""
175
+ message = (
176
+ f"Step '{self.step_id}' in pattern '{self.flow_id}' is referencing a "
177
+ f"pattern which is not allowed. "
147
178
  )
179
+ if self.call_step:
180
+ return message + "Patterns can not use call steps to other patterns."
181
+ else:
182
+ return message + (
183
+ "Patterns can not use link steps to other patterns. "
184
+ "Exception: patterns can link to 'pattern_human_handoff'."
185
+ )
148
186
 
149
187
 
150
188
  class PatternReferencedFlowException(RasaException):
151
189
  """Raised when a pattern is referencing a flow, which is not allowed."""
152
190
 
153
- def __init__(self, flow_id: str, step_id: str) -> None:
191
+ def __init__(self, flow_id: str, step_id: str, call_step: bool) -> None:
154
192
  """Initializes the exception."""
155
193
  self.step_id = step_id
156
194
  self.flow_id = flow_id
195
+ self.call_step = call_step
157
196
 
158
197
  def __str__(self) -> str:
159
198
  """Return a string representation of the exception."""
160
- return (
161
- f"Step '{self.step_id}' in flow '{self.flow_id}' is referencing a flow "
199
+ message = (
200
+ f"Step '{self.step_id}' in pattern '{self.flow_id}' is referencing a flow "
162
201
  f"which is not allowed. "
163
- f"Patterns can not use link or call steps."
164
202
  )
203
+ if self.call_step:
204
+ return message + "Patterns can not use call steps."
205
+ else:
206
+ return message + (
207
+ "'pattern_internal_error' can not use link steps to user flows."
208
+ )
165
209
 
166
210
 
167
211
  class NoLinkAllowedInCalledFlowException(RasaException):
@@ -459,35 +503,76 @@ def validate_linked_flows_exists(flows: "FlowsList") -> None:
459
503
  if not isinstance(step, LinkFlowStep):
460
504
  continue
461
505
 
462
- if flows.flow_by_id(step.link) is None:
506
+ # It might be that the flows do not contain the default rasa patterns, but
507
+ # only the user flows. Manually check for `pattern_human_handoff` as this
508
+ # pattern can be linked to and it is part of the default patterns of rasa.
509
+ if (
510
+ flows.flow_by_id(step.link) is None
511
+ and step.link != RASA_PATTERN_HUMAN_HANDOFF
512
+ ):
463
513
  raise UnresolvedFlowException(step.link, flow.id, step.id)
464
514
 
465
515
 
466
516
  def validate_patterns_are_not_called_or_linked(flows: "FlowsList") -> None:
467
- """Validates that patterns are never called or linked."""
517
+ """Validates that patterns are never called or linked.
518
+
519
+ Exception: pattern_human_handoff can be linked.
520
+ """
468
521
  for flow in flows.underlying_flows:
469
522
  for step in flow.steps:
470
- if isinstance(step, LinkFlowStep) and step.link.startswith(
471
- RASA_DEFAULT_FLOW_PATTERN_PREFIX
523
+ if (
524
+ isinstance(step, LinkFlowStep)
525
+ and step.link.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX)
526
+ and step.link != RASA_PATTERN_HUMAN_HANDOFF
472
527
  ):
473
- raise ReferenceToPatternException(step.link, flow.id, step.id)
528
+ raise ReferenceToPatternException(
529
+ step.link, flow.id, step.id, call_step=False
530
+ )
474
531
 
475
532
  if isinstance(step, CallFlowStep) and step.call.startswith(
476
533
  RASA_DEFAULT_FLOW_PATTERN_PREFIX
477
534
  ):
478
- raise ReferenceToPatternException(step.call, flow.id, step.id)
535
+ raise ReferenceToPatternException(
536
+ step.call, flow.id, step.id, call_step=True
537
+ )
479
538
 
480
539
 
481
540
  def validate_patterns_are_not_calling_or_linking_other_flows(
482
541
  flows: "FlowsList",
483
542
  ) -> None:
484
- """Validates that patterns do not contain call or link steps."""
543
+ """Validates that patterns do not contain call or link steps.
544
+
545
+ Link steps to user flows are allowed for all patterns but 'pattern_internal_error'.
546
+ Link steps to other patterns, except for 'pattern_human_handoff', are forbidden.
547
+ """
485
548
  for flow in flows.underlying_flows:
486
549
  if not flow.is_rasa_default_flow:
487
550
  continue
488
551
  for step in flow.steps:
489
- if isinstance(step, (LinkFlowStep, CallFlowStep)):
490
- raise PatternReferencedFlowException(flow.id, step.id)
552
+ if isinstance(step, LinkFlowStep):
553
+ if step.link == RASA_PATTERN_HUMAN_HANDOFF:
554
+ # links to 'pattern_human_handoff' are allowed
555
+ continue
556
+ if step.link.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX):
557
+ # all other patterns are allowed to link to user flows, but not
558
+ # to other patterns
559
+ raise PatternReferencedPatternException(
560
+ flow.id, step.id, call_step=False
561
+ )
562
+ if flow.id == RASA_PATTERN_INTERNAL_ERROR:
563
+ # 'pattern_internal_error' is not allowed to link at all
564
+ raise PatternReferencedFlowException(
565
+ flow.id, step.id, call_step=False
566
+ )
567
+ if isinstance(step, CallFlowStep):
568
+ if step.call.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX):
569
+ raise PatternReferencedPatternException(
570
+ flow.id, step.id, call_step=True
571
+ )
572
+ else:
573
+ raise PatternReferencedFlowException(
574
+ flow.id, step.id, call_step=True
575
+ )
491
576
 
492
577
 
493
578
  def validate_step_ids_are_unique(flows: "FlowsList") -> None:
@@ -517,8 +602,10 @@ def validate_flow_id(flow: Flow) -> None:
517
602
  """Validates if the flow id comply with a specified regex.
518
603
  Flow IDs can start with an alphanumeric character or an underscore.
519
604
  Followed by zero or more alphanumeric characters, hyphens, or underscores.
605
+
520
606
  Args:
521
607
  flow: The flow to validate.
608
+
522
609
  Raises:
523
610
  FlowIdNamingException: If the flow id does not comply with the regex.
524
611
  """
@@ -1,17 +1,18 @@
1
1
  import textwrap
2
2
  from pathlib import Path
3
- from typing import Any, Dict, List, Text, Union
3
+ from typing import Any, Dict, List, Text, Union, Optional
4
4
 
5
5
  import jsonschema
6
+ import ruamel.yaml.nodes as yaml_nodes
7
+ from ruamel import yaml as yaml
6
8
 
7
9
  import rasa.shared
8
10
  import rasa.shared.data
9
- from rasa.shared.importers.importer import FlowSyncImporter
10
11
  import rasa.shared.utils.io
11
- from rasa.shared.exceptions import RasaException, YamlException
12
-
13
12
  from rasa.shared.core.flows.flow import Flow
14
13
  from rasa.shared.core.flows.flows_list import FlowsList
14
+ from rasa.shared.exceptions import RasaException, YamlException
15
+ from rasa.shared.importers.importer import FlowSyncImporter
15
16
  from rasa.shared.utils.yaml import (
16
17
  validate_yaml_with_jsonschema,
17
18
  read_yaml,
@@ -27,11 +28,14 @@ class YAMLFlowsReader:
27
28
  """Class that reads flows information in YAML format."""
28
29
 
29
30
  @classmethod
30
- def read_from_file(cls, filename: Union[Text, Path]) -> FlowsList:
31
+ def read_from_file(
32
+ cls, filename: Union[Text, Path], add_line_numbers: bool = True
33
+ ) -> FlowsList:
31
34
  """Read flows from file.
32
35
 
33
36
  Args:
34
37
  filename: Path to the flows file.
38
+ add_line_numbers: Flag whether to add line numbers to yaml
35
39
 
36
40
  Returns:
37
41
  `Flow`s read from `filename`.
@@ -41,6 +45,8 @@ class YAMLFlowsReader:
41
45
  rasa.shared.utils.io.read_file(
42
46
  filename, rasa.shared.utils.io.DEFAULT_ENCODING
43
47
  ),
48
+ add_line_numbers=add_line_numbers,
49
+ file_path=filename,
44
50
  )
45
51
  except YamlException as e:
46
52
  e.filename = str(filename)
@@ -195,11 +201,19 @@ class YAMLFlowsReader:
195
201
  )
196
202
 
197
203
  @classmethod
198
- def read_from_string(cls, string: Text) -> FlowsList:
204
+ def read_from_string(
205
+ cls,
206
+ string: Text,
207
+ add_line_numbers: bool = True,
208
+ file_path: Optional[Union[str, Path]] = None,
209
+ ) -> FlowsList:
199
210
  """Read flows from a string.
200
211
 
201
212
  Args:
202
213
  string: Unprocessed YAML file content.
214
+ add_line_numbers: If true, a custom constructor is added to add line
215
+ numbers to each node.
216
+ file_path: File path of the flow.
203
217
 
204
218
  Returns:
205
219
  `Flow`s read from `string`.
@@ -207,10 +221,14 @@ class YAMLFlowsReader:
207
221
  validate_yaml_with_jsonschema(
208
222
  string, FLOWS_SCHEMA_FILE, humanize_error=cls.humanize_flow_error
209
223
  )
224
+ if add_line_numbers:
225
+ yaml_content = read_yaml(string, custom_constructor=line_number_constructor)
226
+ yaml_content = process_yaml_content(yaml_content)
210
227
 
211
- yaml_content = read_yaml(string)
228
+ else:
229
+ yaml_content = read_yaml(string)
212
230
 
213
- return FlowsList.from_json(yaml_content.get(KEY_FLOWS, {}))
231
+ return FlowsList.from_json(yaml_content.get(KEY_FLOWS, {}), file_path=file_path)
214
232
 
215
233
 
216
234
  class YamlFlowsWriter:
@@ -246,14 +264,18 @@ class YamlFlowsWriter:
246
264
 
247
265
  def flows_from_str(yaml_str: str) -> FlowsList:
248
266
  """Reads flows from a YAML string."""
249
- flows = YAMLFlowsReader.read_from_string(textwrap.dedent(yaml_str))
267
+ flows = YAMLFlowsReader.read_from_string(
268
+ textwrap.dedent(yaml_str), add_line_numbers=False
269
+ )
250
270
  flows.validate()
251
271
  return flows
252
272
 
253
273
 
254
274
  def flows_from_str_including_defaults(yaml_str: str) -> FlowsList:
255
275
  """Reads flows from a YAML string and combine them with default flows."""
256
- flows = YAMLFlowsReader.read_from_string(textwrap.dedent(yaml_str))
276
+ flows = YAMLFlowsReader.read_from_string(
277
+ textwrap.dedent(yaml_str), add_line_numbers=False
278
+ )
257
279
  all_flows = FlowSyncImporter.merge_with_default_flows(flows)
258
280
  all_flows.validate()
259
281
  return all_flows
@@ -276,3 +298,120 @@ def is_flows_file(file_path: Union[Text, Path]) -> bool:
276
298
  return rasa.shared.data.is_likely_yaml_file(file_path) and is_key_in_yaml(
277
299
  file_path, KEY_FLOWS
278
300
  )
301
+
302
+
303
+ def line_number_constructor(loader: yaml.Loader, node: yaml_nodes.Node) -> Any:
304
+ """A custom YAML constructor adding line numbers to nodes.
305
+
306
+ Args:
307
+ loader (yaml.Loader): The YAML loader.
308
+ node (yaml.nodes.Node): The YAML node.
309
+
310
+ Returns:
311
+ Any: The constructed Python object with added line numbers in metadata.
312
+ """
313
+ if isinstance(node, yaml_nodes.MappingNode):
314
+ mapping = loader.construct_mapping(node, deep=True)
315
+ if "metadata" not in mapping:
316
+ # We add the line information to the metadata of a flow step
317
+ # Lines are 0-based index; adding 1 to start from
318
+ # line 1 for human readability
319
+ mapping["metadata"] = {
320
+ "line_numbers": f"{node.start_mark.line + 1}-{node.end_mark.line}"
321
+ }
322
+ return mapping
323
+ elif isinstance(node, yaml_nodes.SequenceNode):
324
+ sequence = loader.construct_sequence(node, deep=True)
325
+ for item in node.value:
326
+ if isinstance(item, yaml_nodes.MappingNode):
327
+ start_line = item.start_mark.line + 1
328
+ end_line = item.end_mark.line
329
+ # Only add line numbers to dictionary items within the sequence
330
+ index = node.value.index(item)
331
+ if isinstance(sequence[index], dict):
332
+ if "metadata" not in sequence[index]:
333
+ sequence[index]["metadata"] = {}
334
+ if "line_numbers" not in sequence[index]["metadata"]:
335
+ sequence[index]["metadata"]["line_numbers"] = (
336
+ f"{start_line}-{end_line}"
337
+ )
338
+
339
+ return sequence
340
+ return loader.construct_object(node, deep=True)
341
+
342
+
343
+ def _remove_keys_recursively(
344
+ data: Union[Dict, List], keys_to_delete: List[str]
345
+ ) -> None:
346
+ """Recursively removes all specified keys in the given data.
347
+
348
+ Special handling for 'metadata'.
349
+
350
+ Args:
351
+ data: The data structure (dictionary or list) to clean.
352
+ keys_to_delete: A list of keys to remove from the dictionaries.
353
+ """
354
+ if isinstance(data, dict):
355
+ keys = list(data.keys())
356
+ for key in keys:
357
+ if key in keys_to_delete:
358
+ # Special case for 'metadata': only delete if it
359
+ # only contains 'line_numbers'
360
+ if key == "metadata" and isinstance(data[key], dict):
361
+ if len(data[key]) == 1 and "line_numbers" in data[key]:
362
+ del data[key]
363
+ else:
364
+ del data[key]
365
+ else:
366
+ _remove_keys_recursively(data[key], keys_to_delete)
367
+ elif isinstance(data, list):
368
+ for item in data:
369
+ _remove_keys_recursively(item, keys_to_delete)
370
+
371
+
372
+ def _process_keys_recursively(
373
+ data: Union[Dict, List], keys_to_check: List[str]
374
+ ) -> None:
375
+ """Recursively iterates over YAML content and applies remove_keys_recursively."""
376
+ if isinstance(data, dict):
377
+ keys = list(
378
+ data.keys()
379
+ ) # Make a list of keys to avoid changing the dictionary size during iteration
380
+ for key in keys:
381
+ if key in keys_to_check:
382
+ _remove_keys_recursively(data[key], ["metadata"])
383
+ else:
384
+ _process_keys_recursively(data[key], keys_to_check)
385
+ elif isinstance(data, list):
386
+ for item in data:
387
+ _process_keys_recursively(item, keys_to_check)
388
+
389
+
390
+ def process_yaml_content(yaml_content: Dict[str, Any]) -> Dict[str, Any]:
391
+ """Processes parsed YAML content to remove "metadata"."""
392
+ # Remove metadata on the top level
393
+ if "metadata" in yaml_content and (
394
+ len(yaml_content["metadata"]) == 1
395
+ and "line_numbers" in yaml_content["metadata"]
396
+ ):
397
+ del yaml_content["metadata"]
398
+
399
+ # We expect metadata only under "flows" key...
400
+ keys_to_delete_metadata = [key for key in yaml_content if key != "flows"]
401
+ for key in keys_to_delete_metadata:
402
+ _remove_keys_recursively(yaml_content[key], ["metadata"])
403
+
404
+ # ...but "metadata" is also not a key of "flows"
405
+ if "flows" in yaml_content and "metadata" in yaml_content["flows"]:
406
+ if (
407
+ len(yaml_content["flows"]["metadata"]) == 1
408
+ and "line_numbers" in yaml_content["flows"]["metadata"]
409
+ ):
410
+ del yaml_content["flows"]["metadata"]
411
+
412
+ # Under the "flows" key certain keys cannot have metadata
413
+ _process_keys_recursively(
414
+ yaml_content["flows"], ["nlu_trigger", "set_slots", "metadata"]
415
+ )
416
+
417
+ return yaml_content
@@ -449,6 +449,12 @@ class DialogueStateTracker:
449
449
  current_context = self.stack.current_context()
450
450
  return current_context.get("flow_id")
451
451
 
452
+ @property
453
+ def current_step_id(self) -> Optional[str]:
454
+ """Returns the id of the current step id."""
455
+ current_context = self.stack.current_context()
456
+ return current_context.get("step_id")
457
+
452
458
  @property
453
459
  def has_coexistence_routing_slot(self) -> bool:
454
460
  """Returns whether the coexistence routing slot is present."""
@@ -87,13 +87,13 @@
87
87
  // trigger a refresh by fetching an updated graph
88
88
  setInterval(function () {
89
89
  fetch(url).then(r => r.text()).then(dot => {
90
- document.getElementById('errormsg').innerHTML = '';
90
+ document.getElementById('errormsg').innerText = '';
91
91
  if (oldInputGraphValue === dot) return;
92
92
 
93
93
  oldInputGraphValue = dot;
94
94
  drawGraph(dot);
95
95
  }).catch(err => {
96
- document.getElementById('errormsg').innerHTML =
96
+ document.getElementById('errormsg').innerText=
97
97
  'Failed to update plot. (' + err.message + ')';
98
98
  });
99
99
  }, refreshInterval);
rasa/shared/exceptions.py CHANGED
@@ -161,3 +161,7 @@ class ProviderClientAPIException(RasaException):
161
161
  if self.info:
162
162
  s += f"\nInfo: \n{self.info}\n"
163
163
  return s
164
+
165
+
166
+ class ProviderClientValidationError(RasaException):
167
+ """Raised for errors that occur during validation of the API client."""
@@ -1,28 +1,28 @@
1
+ import logging
1
2
  from abc import ABC, abstractmethod
2
3
  from functools import reduce
3
- from typing import Text, Optional, List, Dict, Set, Any, Tuple, Type, Union, cast
4
- import logging
4
+ from typing import Any, Dict, List, Optional, Set, Text, Tuple, Type, Union, cast
5
5
 
6
6
  import importlib_resources
7
7
 
8
8
  import rasa.shared.constants
9
- from rasa.shared.core.flows import FlowsList
10
- import rasa.shared.utils.common
11
9
  import rasa.shared.core.constants
10
+ import rasa.shared.utils.common
12
11
  import rasa.shared.utils.io
13
12
  from rasa.shared.core.domain import (
14
- Domain,
13
+ IS_RETRIEVAL_INTENT_KEY,
14
+ KEY_ACTIONS,
15
15
  KEY_E2E_ACTIONS,
16
16
  KEY_INTENTS,
17
17
  KEY_RESPONSES,
18
- KEY_ACTIONS,
18
+ Domain,
19
19
  )
20
20
  from rasa.shared.core.events import ActionExecuted, UserUttered
21
+ from rasa.shared.core.flows import FlowsList
21
22
  from rasa.shared.core.training_data.structures import StoryGraph
23
+ from rasa.shared.nlu.constants import ACTION_NAME, ENTITIES
22
24
  from rasa.shared.nlu.training_data.message import Message
23
25
  from rasa.shared.nlu.training_data.training_data import TrainingData
24
- from rasa.shared.nlu.constants import ENTITIES, ACTION_NAME
25
- from rasa.shared.core.domain import IS_RETRIEVAL_INTENT_KEY
26
26
  from rasa.shared.utils.yaml import read_config_file
27
27
 
28
28
  logger = logging.getLogger(__name__)
@@ -165,7 +165,7 @@ class TrainingDataImporter(ABC):
165
165
  config_path: Optional[Text] = None,
166
166
  domain_path: Optional[Text] = None,
167
167
  training_data_paths: Optional[List[Text]] = None,
168
- args: Optional[Dict[Text, Any]] = {},
168
+ args: Optional[Dict[Text, Any]] = None,
169
169
  ) -> "TrainingDataImporter":
170
170
  """Loads a `TrainingDataImporter` instance from a dictionary."""
171
171
  from rasa.shared.importers.rasa import RasaFileImporter
@@ -194,16 +194,19 @@ class TrainingDataImporter(ABC):
194
194
  config_path: Text,
195
195
  domain_path: Optional[Text] = None,
196
196
  training_data_paths: Optional[List[Text]] = None,
197
- args: Optional[Dict[Text, Any]] = {},
197
+ args: Optional[Dict[Text, Any]] = None,
198
198
  ) -> Optional["TrainingDataImporter"]:
199
199
  from rasa.shared.importers.multi_project import MultiProjectImporter
200
200
  from rasa.shared.importers.rasa import RasaFileImporter
201
+ from rasa.shared.importers.remote_importer import RemoteTrainingDataImporter
201
202
 
202
203
  module_path = importer_config.pop("name", None)
203
204
  if module_path == RasaFileImporter.__name__:
204
205
  importer_class: Type[TrainingDataImporter] = RasaFileImporter
205
206
  elif module_path == MultiProjectImporter.__name__:
206
207
  importer_class = MultiProjectImporter
208
+ elif module_path == RemoteTrainingDataImporter.__name__:
209
+ importer_class = RemoteTrainingDataImporter
207
210
  else:
208
211
  try:
209
212
  importer_class = rasa.shared.utils.common.class_from_module_path(
@@ -216,7 +219,6 @@ class TrainingDataImporter(ABC):
216
219
  constructor_arguments = rasa.shared.utils.common.minimal_kwargs(
217
220
  {**importer_config, **(args or {})}, importer_class
218
221
  )
219
-
220
222
  return importer_class(
221
223
  config_path,
222
224
  domain_path,
@@ -232,6 +234,26 @@ class TrainingDataImporter(ABC):
232
234
  """Returns text representation of object."""
233
235
  return self.__class__.__name__
234
236
 
237
+ def get_user_flows(self) -> FlowsList:
238
+ """Retrieves the user-defined flows that should be used for training.
239
+
240
+ Implemented by FlowSyncImporter and E2EImporter only.
241
+
242
+ Returns:
243
+ `FlowsList` containing all loaded flows.
244
+ """
245
+ raise NotImplementedError
246
+
247
+ def get_user_domain(self) -> Domain:
248
+ """Retrieves the user-defined domain that should be used for training.
249
+
250
+ Implemented by FlowSyncImporter and E2EImporter only.
251
+
252
+ Returns:
253
+ `Domain`.
254
+ """
255
+ raise NotImplementedError
256
+
235
257
 
236
258
  class NluDataImporter(TrainingDataImporter):
237
259
  """Importer that skips any Core-related file reading."""
@@ -448,6 +470,10 @@ class FlowSyncImporter(PassThroughImporter):
448
470
 
449
471
  return self.merge_with_default_flows(flows)
450
472
 
473
+ @rasa.shared.utils.common.cached_method
474
+ def get_user_flows(self) -> FlowsList:
475
+ return self._importer.get_flows()
476
+
451
477
  @rasa.shared.utils.common.cached_method
452
478
  def get_domain(self) -> Domain:
453
479
  """Merge existing domain with properties of flows."""
@@ -476,6 +502,11 @@ class FlowSyncImporter(PassThroughImporter):
476
502
  )
477
503
  return domain
478
504
 
505
+ @rasa.shared.utils.common.cached_method
506
+ def get_user_domain(self) -> Domain:
507
+ """Retrieves only user defined domain."""
508
+ return self._importer.get_domain()
509
+
479
510
 
480
511
  class ResponsesSyncImporter(PassThroughImporter):
481
512
  """Importer that syncs `responses` between Domain and NLU training data.
@@ -602,6 +633,15 @@ class E2EImporter(PassThroughImporter):
602
633
  - adds potential end-to-end bot messages from stories as actions to the domain
603
634
  """
604
635
 
636
+ @rasa.shared.utils.common.cached_method
637
+ def get_user_flows(self) -> FlowsList:
638
+ if not isinstance(self._importer, FlowSyncImporter):
639
+ raise NotImplementedError(
640
+ "Accessing user flows is only supported with FlowSyncImporter."
641
+ )
642
+
643
+ return self._importer.get_user_flows()
644
+
605
645
  @rasa.shared.utils.common.cached_method
606
646
  def get_domain(self) -> Domain:
607
647
  """Retrieves model domain (see parent class for full docstring)."""
@@ -610,6 +650,15 @@ class E2EImporter(PassThroughImporter):
610
650
 
611
651
  return original.merge(e2e_domain)
612
652
 
653
+ @rasa.shared.utils.common.cached_method
654
+ def get_user_domain(self) -> Domain:
655
+ """Retrieves only user defined domain."""
656
+ if not isinstance(self._importer, FlowSyncImporter):
657
+ raise NotImplementedError(
658
+ "Accessing user domain is only supported with FlowSyncImporter."
659
+ )
660
+ return self._importer.get_user_domain()
661
+
613
662
  def _get_domain_with_e2e_actions(self) -> Domain:
614
663
  stories = self.get_stories()
615
664