rasa-pro 3.9.18__py3-none-any.whl → 3.10.16__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 (183) hide show
  1. README.md +0 -374
  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 +27 -23
  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 +11 -3
  10. rasa/cli/data.py +70 -8
  11. rasa/cli/e2e_test.py +104 -431
  12. rasa/cli/evaluate.py +1 -1
  13. rasa/cli/interactive.py +1 -0
  14. rasa/cli/llm_fine_tuning.py +398 -0
  15. rasa/cli/project_templates/calm/endpoints.yml +1 -1
  16. rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
  17. rasa/cli/run.py +15 -14
  18. rasa/cli/scaffold.py +10 -8
  19. rasa/cli/studio/studio.py +35 -5
  20. rasa/cli/train.py +56 -8
  21. rasa/cli/utils.py +22 -5
  22. rasa/cli/x.py +1 -1
  23. rasa/constants.py +7 -1
  24. rasa/core/actions/action.py +98 -49
  25. rasa/core/actions/action_run_slot_rejections.py +4 -1
  26. rasa/core/actions/custom_action_executor.py +9 -6
  27. rasa/core/actions/direct_custom_actions_executor.py +80 -0
  28. rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
  29. rasa/core/actions/grpc_custom_action_executor.py +2 -2
  30. rasa/core/actions/http_custom_action_executor.py +6 -5
  31. rasa/core/agent.py +21 -17
  32. rasa/core/channels/__init__.py +2 -0
  33. rasa/core/channels/audiocodes.py +1 -16
  34. rasa/core/channels/voice_aware/__init__.py +0 -0
  35. rasa/core/channels/voice_aware/jambonz.py +103 -0
  36. rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
  37. rasa/core/channels/voice_aware/utils.py +20 -0
  38. rasa/core/channels/voice_native/__init__.py +0 -0
  39. rasa/core/constants.py +6 -1
  40. rasa/core/information_retrieval/faiss.py +7 -4
  41. rasa/core/information_retrieval/information_retrieval.py +8 -0
  42. rasa/core/information_retrieval/milvus.py +9 -2
  43. rasa/core/information_retrieval/qdrant.py +1 -1
  44. rasa/core/nlg/contextual_response_rephraser.py +32 -10
  45. rasa/core/nlg/summarize.py +4 -3
  46. rasa/core/policies/enterprise_search_policy.py +113 -45
  47. rasa/core/policies/flows/flow_executor.py +122 -76
  48. rasa/core/policies/intentless_policy.py +83 -29
  49. rasa/core/processor.py +72 -54
  50. rasa/core/run.py +5 -4
  51. rasa/core/tracker_store.py +8 -4
  52. rasa/core/training/interactive.py +1 -1
  53. rasa/core/utils.py +56 -57
  54. rasa/dialogue_understanding/coexistence/llm_based_router.py +53 -13
  55. rasa/dialogue_understanding/commands/__init__.py +6 -0
  56. rasa/dialogue_understanding/commands/restart_command.py +58 -0
  57. rasa/dialogue_understanding/commands/session_start_command.py +59 -0
  58. rasa/dialogue_understanding/commands/utils.py +40 -0
  59. rasa/dialogue_understanding/generator/constants.py +10 -3
  60. rasa/dialogue_understanding/generator/flow_retrieval.py +21 -5
  61. rasa/dialogue_understanding/generator/llm_based_command_generator.py +13 -3
  62. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +134 -90
  63. rasa/dialogue_understanding/generator/nlu_command_adapter.py +47 -7
  64. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +127 -41
  65. rasa/dialogue_understanding/patterns/restart.py +37 -0
  66. rasa/dialogue_understanding/patterns/session_start.py +37 -0
  67. rasa/dialogue_understanding/processor/command_processor.py +16 -3
  68. rasa/dialogue_understanding/processor/command_processor_component.py +6 -2
  69. rasa/e2e_test/aggregate_test_stats_calculator.py +134 -0
  70. rasa/e2e_test/assertions.py +1223 -0
  71. rasa/e2e_test/assertions_schema.yml +106 -0
  72. rasa/e2e_test/constants.py +20 -0
  73. rasa/e2e_test/e2e_config.py +220 -0
  74. rasa/e2e_test/e2e_config_schema.yml +26 -0
  75. rasa/e2e_test/e2e_test_case.py +131 -8
  76. rasa/e2e_test/e2e_test_converter.py +363 -0
  77. rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
  78. rasa/e2e_test/e2e_test_coverage_report.py +364 -0
  79. rasa/e2e_test/e2e_test_result.py +26 -6
  80. rasa/e2e_test/e2e_test_runner.py +493 -71
  81. rasa/e2e_test/e2e_test_schema.yml +96 -0
  82. rasa/e2e_test/pykwalify_extensions.py +39 -0
  83. rasa/e2e_test/stub_custom_action.py +70 -0
  84. rasa/e2e_test/utils/__init__.py +0 -0
  85. rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
  86. rasa/e2e_test/utils/io.py +598 -0
  87. rasa/e2e_test/utils/validation.py +80 -0
  88. rasa/engine/graph.py +9 -3
  89. rasa/engine/recipes/default_components.py +0 -2
  90. rasa/engine/recipes/default_recipe.py +10 -2
  91. rasa/engine/storage/local_model_storage.py +40 -12
  92. rasa/engine/validation.py +78 -1
  93. rasa/env.py +9 -0
  94. rasa/graph_components/providers/story_graph_provider.py +59 -6
  95. rasa/llm_fine_tuning/__init__.py +0 -0
  96. rasa/llm_fine_tuning/annotation_module.py +241 -0
  97. rasa/llm_fine_tuning/conversations.py +144 -0
  98. rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
  99. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
  100. rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
  101. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
  102. rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
  103. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
  104. rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
  105. rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
  106. rasa/llm_fine_tuning/storage.py +174 -0
  107. rasa/llm_fine_tuning/train_test_split_module.py +441 -0
  108. rasa/model_training.py +56 -16
  109. rasa/nlu/persistor.py +157 -36
  110. rasa/server.py +45 -10
  111. rasa/shared/constants.py +76 -16
  112. rasa/shared/core/domain.py +27 -19
  113. rasa/shared/core/events.py +28 -2
  114. rasa/shared/core/flows/flow.py +208 -13
  115. rasa/shared/core/flows/flow_path.py +84 -0
  116. rasa/shared/core/flows/flows_list.py +33 -11
  117. rasa/shared/core/flows/flows_yaml_schema.json +269 -193
  118. rasa/shared/core/flows/validation.py +112 -25
  119. rasa/shared/core/flows/yaml_flows_io.py +149 -10
  120. rasa/shared/core/trackers.py +6 -0
  121. rasa/shared/core/training_data/structures.py +20 -0
  122. rasa/shared/core/training_data/visualization.html +2 -2
  123. rasa/shared/exceptions.py +4 -0
  124. rasa/shared/importers/importer.py +64 -16
  125. rasa/shared/nlu/constants.py +2 -0
  126. rasa/shared/providers/_configs/__init__.py +0 -0
  127. rasa/shared/providers/_configs/azure_openai_client_config.py +183 -0
  128. rasa/shared/providers/_configs/client_config.py +57 -0
  129. rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
  130. rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
  131. rasa/shared/providers/_configs/openai_client_config.py +175 -0
  132. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +176 -0
  133. rasa/shared/providers/_configs/utils.py +101 -0
  134. rasa/shared/providers/_ssl_verification_utils.py +124 -0
  135. rasa/shared/providers/embedding/__init__.py +0 -0
  136. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +259 -0
  137. rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
  138. rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
  139. rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
  140. rasa/shared/providers/embedding/embedding_client.py +90 -0
  141. rasa/shared/providers/embedding/embedding_response.py +41 -0
  142. rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
  143. rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
  144. rasa/shared/providers/llm/__init__.py +0 -0
  145. rasa/shared/providers/llm/_base_litellm_client.py +251 -0
  146. rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
  147. rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
  148. rasa/shared/providers/llm/llm_client.py +76 -0
  149. rasa/shared/providers/llm/llm_response.py +50 -0
  150. rasa/shared/providers/llm/openai_llm_client.py +155 -0
  151. rasa/shared/providers/llm/self_hosted_llm_client.py +293 -0
  152. rasa/shared/providers/mappings.py +75 -0
  153. rasa/shared/utils/cli.py +30 -0
  154. rasa/shared/utils/io.py +65 -2
  155. rasa/shared/utils/llm.py +246 -200
  156. rasa/shared/utils/yaml.py +121 -15
  157. rasa/studio/auth.py +6 -4
  158. rasa/studio/config.py +13 -4
  159. rasa/studio/constants.py +1 -0
  160. rasa/studio/data_handler.py +10 -3
  161. rasa/studio/download.py +19 -13
  162. rasa/studio/train.py +2 -3
  163. rasa/studio/upload.py +19 -11
  164. rasa/telemetry.py +113 -58
  165. rasa/tracing/instrumentation/attribute_extractors.py +32 -17
  166. rasa/utils/common.py +18 -19
  167. rasa/utils/endpoints.py +7 -4
  168. rasa/utils/json_utils.py +60 -0
  169. rasa/utils/licensing.py +9 -1
  170. rasa/utils/ml_utils.py +4 -2
  171. rasa/validator.py +213 -3
  172. rasa/version.py +1 -1
  173. rasa_pro-3.10.16.dist-info/METADATA +196 -0
  174. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/RECORD +179 -113
  175. rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
  176. rasa/shared/providers/openai/clients.py +0 -43
  177. rasa/shared/providers/openai/session_handler.py +0 -110
  178. rasa_pro-3.9.18.dist-info/METADATA +0 -563
  179. /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
  180. /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
  181. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/NOTICE +0 -0
  182. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/WHEEL +0 -0
  183. {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.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."""
@@ -804,6 +804,26 @@ class StoryGraph:
804
804
  """Returns text representation of object."""
805
805
  return f"{self.__class__.__name__}: {len(self.story_steps)} story steps"
806
806
 
807
+ def has_e2e_stories(self) -> bool:
808
+ """
809
+ Checks if there are end-to-end (E2E) stories present in the story steps.
810
+
811
+ An E2E story is determined by checking if any `UserUttered` event has
812
+ associated text within the story steps.
813
+
814
+ Returns:
815
+ bool: True if any E2E story (i.e., a `UserUttered` event with text)
816
+ is found, False otherwise.
817
+ """
818
+ if not self.story_steps:
819
+ return False
820
+ for story_step in self.story_steps:
821
+ for event in story_step.events:
822
+ if isinstance(event, UserUttered):
823
+ if event.text:
824
+ return True
825
+ return False
826
+
807
827
 
808
828
  def generate_id(prefix: Text = "", max_chars: Optional[int] = None) -> Text:
809
829
  """Generate a random UUID.
@@ -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."""