rasa-pro 3.13.0.dev3__py3-none-any.whl → 3.13.0.dev5__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 (132) hide show
  1. rasa/__main__.py +3 -1
  2. rasa/cli/inspect.py +8 -4
  3. rasa/cli/project_templates/default/config.yml +5 -32
  4. rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_cancels_during_a_correction.yml +1 -1
  5. rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_changes_mind_on_a_whim.yml +1 -1
  6. rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_handle.yml +1 -1
  7. rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_name.yml +1 -1
  8. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_adds_contact_to_their_list.yml +1 -1
  9. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_lists_contacts.yml +1 -1
  10. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact.yml +1 -1
  11. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact_from_list.yml +1 -1
  12. rasa/cli/project_templates/default/endpoints.yml +18 -2
  13. rasa/cli/scaffold.py +3 -4
  14. rasa/cli/studio/download.py +1 -1
  15. rasa/cli/studio/upload.py +0 -6
  16. rasa/core/channels/channel.py +68 -5
  17. rasa/core/channels/inspector/dist/assets/{arc-c7691751.js → arc-9f75cc3b.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-ab99dff7.js → blockDiagram-38ab4fdb-7f34db23.js} +1 -1
  19. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-08c35a6b.js → c4Diagram-3d4e48cf-948bab2c.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/channel-dfa68278.js +1 -0
  21. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-9e9c71c9.js → classDiagram-70f12bd4-53b0dd0e.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-15e7e2bf.js → classDiagram-v2-f2320105-fdf789e7.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/clone-edb7f119.js +1 -0
  24. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-9c105cb1.js → createText-2e5e7dd3-87c4ece5.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-77e89e48.js → edges-e0da2a9e-5a8b0749.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-7a011646.js → erDiagram-9861fffd-66da90e2.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-b6f105ac.js → flowDb-956e92f1-10044f05.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ce4f18c2.js → flowDiagram-66a62f08-f338f66a.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-65e7c670.js +1 -0
  30. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-cb5f6da4.js → flowchart-elk-definition-4a651766-b13140aa.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-e4d19e28.js → ganttDiagram-c361ad54-f2b4a55a.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-727b1c33.js → gitGraphDiagram-72cf32ee-dedc298d.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{graph-6e2ab9a7.js → graph-4ede11ff.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{index-3862675e-84ec700f.js → index-3862675e-65549d37.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{index-098a1a24.js → index-3a23e736.js} +142 -129
  36. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-78dda442.js → infoDiagram-f8f76790-65439671.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-f1cc6dd1.js → journeyDiagram-49397b02-56d03d98.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{layout-d98dcd0c.js → layout-dd48f7f4.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{line-838e3d82.js → line-1569ad2c.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{linear-eae72406.js → linear-48bf4935.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-c96fd84b.js → mindmap-definition-fc14e90a-688504c1.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-c936d4e2.js → pieDiagram-8a3498a8-78b6d7e6.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b338eb8f.js → quadrantDiagram-120e2f19-048b84b3.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-c6b6c0d5.js → requirementDiagram-deff3bca-dd67f107.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-b9372e19.js → sankeyDiagram-04a897e0-8128436e.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-479e0a3f.js → sequenceDiagram-704730f1-1a0d1461.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-fd26eebc.js → stateDiagram-587899a1-46d388ed.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-3233e0ae.js → stateDiagram-v2-d93cdb3a-ea42951a.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-1fdd392b.js → styles-6aaf32cf-7427ed0c.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-6d7bfa1b.js → styles-9a916d00-ff5e5a16.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-f86aab11.js → styles-c10674c1-7b3680cf.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-e3e49d7a.js → svgDrawCommon-08f97a94-f860f2ad.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-6fe08b4d.js → timeline-definition-85554ec2-2eebf0c8.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-c2e06fd6.js → xychartDiagram-e933f94c-5d7f4e96.js} +1 -1
  55. rasa/core/channels/inspector/dist/index.html +1 -1
  56. rasa/core/channels/inspector/src/App.tsx +3 -2
  57. rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
  58. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
  59. rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
  60. rasa/core/channels/inspector/src/types.ts +1 -1
  61. rasa/core/channels/voice_ready/audiocodes.py +41 -15
  62. rasa/core/channels/voice_ready/twilio_voice.py +48 -1
  63. rasa/core/channels/voice_stream/tts/azure.py +11 -2
  64. rasa/core/channels/voice_stream/twilio_media_streams.py +101 -26
  65. rasa/core/channels/voice_stream/voice_channel.py +28 -2
  66. rasa/core/concurrent_lock_store.py +24 -10
  67. rasa/core/lock_store.py +151 -60
  68. rasa/dialogue_understanding_test/du_test_case.py +16 -8
  69. rasa/plugin.py +0 -3
  70. rasa/shared/constants.py +1 -0
  71. rasa/shared/core/domain.py +165 -11
  72. rasa/shared/core/flows/flow.py +155 -131
  73. rasa/shared/core/flows/flow_step.py +19 -3
  74. rasa/shared/core/flows/flow_step_links.py +15 -0
  75. rasa/shared/core/flows/flow_step_sequence.py +6 -0
  76. rasa/shared/core/flows/nlu_trigger.py +13 -0
  77. rasa/shared/core/flows/steps/action.py +7 -4
  78. rasa/shared/core/flows/steps/call.py +11 -4
  79. rasa/shared/core/flows/steps/collect.py +27 -6
  80. rasa/shared/core/flows/steps/internal.py +6 -1
  81. rasa/shared/core/flows/steps/link.py +7 -4
  82. rasa/shared/core/flows/steps/no_operation.py +7 -4
  83. rasa/shared/core/flows/steps/set_slots.py +8 -4
  84. rasa/shared/core/flows/yaml_flows_io.py +106 -5
  85. rasa/shared/importers/importer.py +8 -0
  86. rasa/shared/providers/_utils.py +83 -0
  87. rasa/shared/providers/llm/_base_litellm_client.py +6 -3
  88. rasa/shared/providers/llm/azure_openai_llm_client.py +6 -68
  89. rasa/shared/providers/router/_base_litellm_router_client.py +53 -1
  90. rasa/shared/utils/common.py +42 -0
  91. rasa/studio/download/domains.py +49 -0
  92. rasa/studio/download/download.py +439 -0
  93. rasa/studio/download/flows.py +359 -0
  94. rasa/studio/results_logger.py +6 -1
  95. rasa/studio/upload.py +69 -5
  96. rasa/utils/common.py +36 -0
  97. rasa/utils/endpoints.py +22 -1
  98. rasa/utils/licensing.py +1 -1
  99. rasa/validator.py +1 -2
  100. rasa/version.py +1 -1
  101. {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/METADATA +8 -8
  102. {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/RECORD +119 -125
  103. rasa/cli/project_templates/calm/config.yml +0 -10
  104. rasa/cli/project_templates/calm/credentials.yml +0 -33
  105. rasa/cli/project_templates/calm/endpoints.yml +0 -58
  106. rasa/cli/project_templates/default/actions/actions.py +0 -27
  107. rasa/cli/project_templates/default/data/nlu.yml +0 -91
  108. rasa/cli/project_templates/default/data/rules.yml +0 -13
  109. rasa/cli/project_templates/default/data/stories.yml +0 -30
  110. rasa/cli/project_templates/default/domain.yml +0 -34
  111. rasa/cli/project_templates/default/tests/test_stories.yml +0 -91
  112. rasa/core/channels/inspector/dist/assets/channel-11268142.js +0 -1
  113. rasa/core/channels/inspector/dist/assets/clone-ff7f2ce7.js +0 -1
  114. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-cba7ae20.js +0 -1
  115. rasa/studio/download.py +0 -489
  116. /rasa/cli/project_templates/{calm → default}/actions/action_template.py +0 -0
  117. /rasa/cli/project_templates/{calm → default}/actions/add_contact.py +0 -0
  118. /rasa/cli/project_templates/{calm → default}/actions/db.py +0 -0
  119. /rasa/cli/project_templates/{calm → default}/actions/list_contacts.py +0 -0
  120. /rasa/cli/project_templates/{calm → default}/actions/remove_contact.py +0 -0
  121. /rasa/cli/project_templates/{calm → default}/data/flows/add_contact.yml +0 -0
  122. /rasa/cli/project_templates/{calm → default}/data/flows/list_contacts.yml +0 -0
  123. /rasa/cli/project_templates/{calm → default}/data/flows/remove_contact.yml +0 -0
  124. /rasa/cli/project_templates/{calm → default}/db/contacts.json +0 -0
  125. /rasa/cli/project_templates/{calm → default}/domain/add_contact.yml +0 -0
  126. /rasa/cli/project_templates/{calm → default}/domain/list_contacts.yml +0 -0
  127. /rasa/cli/project_templates/{calm → default}/domain/remove_contact.yml +0 -0
  128. /rasa/cli/project_templates/{calm → default}/domain/shared.yml +0 -0
  129. /rasa/{cli/project_templates/calm/actions → studio/download}/__init__.py +0 -0
  130. {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/NOTICE +0 -0
  131. {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/WHEEL +0 -0
  132. {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/entry_points.txt +0 -0
@@ -38,6 +38,7 @@ from rasa.shared.core.flows.flow_step_sequence import FlowStepSequence
38
38
  from rasa.shared.core.flows.nlu_trigger import NLUTriggers
39
39
  from rasa.shared.core.flows.steps import (
40
40
  ActionFlowStep,
41
+ CallFlowStep,
41
42
  CollectInformationFlowStep,
42
43
  EndFlowStep,
43
44
  StartFlowStep,
@@ -52,6 +53,8 @@ from rasa.shared.core.slots import Slot
52
53
 
53
54
  structlogger = structlog.get_logger()
54
55
 
56
+ DEFAULT_RUN_PATTERN_COMPLETED = True
57
+
55
58
 
56
59
  class FlowLanguageTranslation(BaseModel):
57
60
  """Represents the translation of the flow properties in a specific language."""
@@ -60,8 +63,15 @@ class FlowLanguageTranslation(BaseModel):
60
63
  """The human-readable name of the flow."""
61
64
 
62
65
  class Config:
66
+ """Config for the FlowLanguageTranslation class."""
67
+
63
68
  extra = "ignore"
64
69
 
70
+ def __eq__(self, other: object) -> bool:
71
+ if isinstance(other, FlowLanguageTranslation):
72
+ return self.name == other.name
73
+ return False
74
+
65
75
 
66
76
  @dataclass
67
77
  class Flow:
@@ -89,8 +99,25 @@ class Flow:
89
99
  """The path to the file where the flow is stored."""
90
100
  persisted_slots: List[str] = field(default_factory=list)
91
101
  """The list of slots that should be persisted after the flow ends."""
92
- run_pattern_completed: bool = True
102
+ run_pattern_completed: bool = DEFAULT_RUN_PATTERN_COMPLETED
93
103
  """Whether the pattern_completed flow should be run after the flow ends."""
104
+ metadata: Dict[Text, Any] = field(default_factory=dict)
105
+
106
+ def __eq__(self, other: object) -> bool:
107
+ if isinstance(other, Flow):
108
+ return (
109
+ self.id == other.id
110
+ and self.custom_name == other.custom_name
111
+ and self.description == other.description
112
+ and self.translation == other.translation
113
+ and self.guard_condition == other.guard_condition
114
+ and self.step_sequence == other.step_sequence
115
+ and self.nlu_triggers == other.nlu_triggers
116
+ and self.always_include_in_prompt == other.always_include_in_prompt
117
+ and self.persisted_slots == other.persisted_slots
118
+ and self.run_pattern_completed == other.run_pattern_completed
119
+ )
120
+ return False
94
121
 
95
122
  @staticmethod
96
123
  def from_json(
@@ -129,7 +156,9 @@ class Flow:
129
156
  # data. When the model is trained, take the provided file_path.
130
157
  file_path=data.get(KEY_FILE_PATH) if KEY_FILE_PATH in data else file_path,
131
158
  persisted_slots=data.get(KEY_PERSISTED_SLOTS, []),
132
- run_pattern_completed=data.get(KEY_RUN_PATTERN_COMPLETED, True),
159
+ run_pattern_completed=data.get(
160
+ KEY_RUN_PATTERN_COMPLETED, DEFAULT_RUN_PATTERN_COMPLETED
161
+ ),
133
162
  translation=extract_translations(
134
163
  translation_data=data.get(KEY_TRANSLATION, {})
135
164
  ),
@@ -208,7 +237,7 @@ class Flow:
208
237
  if self.persisted_slots:
209
238
  data[KEY_PERSISTED_SLOTS] = self.persisted_slots
210
239
  if self.run_pattern_completed is not None:
211
- data["run_pattern_completed"] = self.run_pattern_completed
240
+ data[KEY_RUN_PATTERN_COMPLETED] = self.run_pattern_completed
212
241
  if self.translation:
213
242
  data[KEY_TRANSLATION] = {
214
243
  language_code: translation.dict()
@@ -231,9 +260,9 @@ class Flow:
231
260
  return translation.name if translation else None
232
261
 
233
262
  def readable_name(self, language: Optional[Language] = None) -> str:
234
- """
235
- Returns the flow's name in the specified language if available; otherwise
236
- falls back to the flow's name, and finally the flow's ID.
263
+ """Returns the flow's name in the specified language if available.
264
+
265
+ Otherwise falls back to the flow's name, and finally the flow's ID.
237
266
 
238
267
  Args:
239
268
  language: Preferred language code.
@@ -466,161 +495,156 @@ class Flow:
466
495
  and a set of visited step IDs to prevent revisiting steps.
467
496
  It calls `go_over_steps` to recursively explore and fill the paths list.
468
497
  """
469
- flow_paths_list = FlowPathsList(self.id, paths=[])
470
- steps: List[FlowStep] = self.steps
498
+ all_paths = FlowPathsList(self.id, paths=[])
499
+ start_step: FlowStep = self.first_step_in_flow()
471
500
  current_path: FlowPath = FlowPath(flow=self.id, nodes=[])
472
- step_ids_visited: Set[str] = set()
501
+ visited_step_ids: Set[str] = set()
473
502
 
474
- self._go_over_steps(steps, current_path, flow_paths_list, step_ids_visited)
475
-
476
- if not flow_paths_list.is_path_part_of_list(current_path):
477
- flow_paths_list.paths.append(copy.deepcopy(current_path))
503
+ self._go_over_steps(start_step, current_path, all_paths, visited_step_ids)
478
504
 
479
505
  structlogger.debug(
480
506
  "shared.core.flows.flow.extract_all_paths",
481
507
  comment="Extraction complete",
482
- number_of_paths=len(flow_paths_list.paths),
508
+ number_of_paths=len(all_paths.paths),
483
509
  flow_name=self.name,
484
510
  )
485
- return flow_paths_list
511
+ return all_paths
486
512
 
487
513
  def _go_over_steps(
488
514
  self,
489
- steps_to_go: Union[str, List[FlowStep]],
515
+ current_step: FlowStep,
490
516
  current_path: FlowPath,
491
- completed_paths: FlowPathsList,
492
- step_ids_visited: Set[str],
517
+ all_paths: FlowPathsList,
518
+ visited_step_ids: Set[str],
493
519
  ) -> None:
494
520
  """Processes the flow steps recursively.
495
521
 
496
- Either following direct step IDs or handling conditions, and adds complete
497
- paths to the collected_paths.
498
-
499
522
  Args:
500
- steps_to_go: Either a direct step ID or a list of steps to process.
523
+ current_step: The current step being processed.
501
524
  current_path: The current path being constructed.
502
- completed_paths: The list where completed paths are added.
503
- step_ids_visited: A set of step IDs that have been visited to avoid cycles.
525
+ all_paths: The list where completed paths are added.
526
+ visited_step_ids: A set of steps that have been visited to avoid cycles.
504
527
 
505
528
  Returns:
506
- None: This function modifies collected_paths in place by appending new paths
529
+ None: This function modifies all_paths in place by appending new paths
507
530
  as they are found.
508
531
  """
509
- # Case 1: If the steps_to_go is a custom_id string
510
- # This happens when a "next" of, for example, a IfFlowStepLink is targeting
511
- # a specific step by id
512
- if isinstance(steps_to_go, str):
513
- for i, step in enumerate(self.steps):
514
- # We don't need to check for 'id' as a link can only happen to a
515
- # custom id.
516
- if step.custom_id == steps_to_go:
517
- self._go_over_steps(
518
- self.steps[i:], current_path, completed_paths, step_ids_visited
519
- )
520
-
521
- # Case 2: If steps_to_go is a list of steps
522
- else:
523
- for i, step in enumerate(steps_to_go):
524
- # 1. Check if the step is relevant for testable_paths extraction.
525
- # We only create new path nodes for ActionFlowStep and
526
- # CollectInformationFlowStep because these are externally visible
527
- # changes in the assistant's behaviour (trackable in the e2e tests).
528
- # For other flow steps, we only follow their links.
529
- # We decided to ignore calls to other flows in our coverage analysis.
530
- if not isinstance(step, (CollectInformationFlowStep, ActionFlowStep)):
531
- self._handle_links(
532
- step.next.links,
533
- current_path,
534
- completed_paths,
535
- step_ids_visited,
536
- )
537
- continue
538
-
539
- # 2. Check if already visited this custom step id
540
- # in order to keep track of loops
541
- if step.custom_id is not None and step.custom_id in step_ids_visited:
542
- if not completed_paths.is_path_part_of_list(current_path):
543
- completed_paths.paths.append(copy.deepcopy(current_path))
544
- return # Stop traversing this path if we've revisited a step
545
- elif step.custom_id is not None:
546
- step_ids_visited.add(step.custom_id)
547
-
548
- # 3. Append step info to the path
549
- current_path.nodes.append(
550
- PathNode(
551
- flow=current_path.flow,
552
- step_id=step.id,
553
- lines=step.metadata["line_numbers"],
554
- )
532
+ # Check if the step is relevant for testable_paths extraction.
533
+ # We only create new path nodes for ActionFlowStep, CallFlowStep and
534
+ # CollectInformationFlowStep because these are externally visible
535
+ # changes in the assistant's behaviour (trackable in the e2e tests).
536
+ # For other flow steps, we only follow their links.
537
+ # We decided to ignore calls to other flows in our coverage analysis.
538
+ should_add_node = isinstance(
539
+ current_step, (CollectInformationFlowStep, ActionFlowStep, CallFlowStep)
540
+ )
541
+ if should_add_node:
542
+ # Add current step to the current path that is being constructed.
543
+ current_path.nodes.append(
544
+ PathNode(
545
+ flow=current_path.flow,
546
+ step_id=current_step.id,
547
+ lines=current_step.metadata["line_numbers"],
555
548
  )
549
+ )
556
550
 
557
- # 4. Check if 'END' branch
558
- if (
559
- len(step.next.links) == 1
560
- and isinstance(step.next.links[0], StaticFlowStepLink)
561
- and step.next.links[0].target == END_STEP
562
- ):
563
- if not completed_paths.is_path_part_of_list(current_path):
564
- completed_paths.paths.append(copy.deepcopy(current_path))
565
- return
566
- else:
567
- self._handle_links(
568
- step.next.links,
569
- current_path,
570
- completed_paths,
571
- step_ids_visited,
572
- )
551
+ if current_step.id in visited_step_ids or self.is_end_of_path(current_step):
552
+ # Found a cycle, or reached an end step, do not proceed further.
553
+ all_paths.paths.append(copy.deepcopy(current_path))
554
+ # Remove the last node from the path if it was added.
555
+ if should_add_node:
556
+ current_path.nodes.pop()
557
+ return
558
+
559
+ # Mark current step as visited in this path.
560
+ visited_step_ids.add(current_step.id)
561
+
562
+ # Iterate over all links of the current step.
563
+ for link in current_step.next.links:
564
+ self._handle_link(
565
+ current_path,
566
+ all_paths,
567
+ visited_step_ids,
568
+ link,
569
+ )
570
+
571
+ # Backtrack the current step and remove it from the path.
572
+ visited_step_ids.remove(current_step.id)
573
573
 
574
- def _handle_links(
574
+ # Remove the last node from the path if it was added.
575
+ if should_add_node:
576
+ current_path.nodes.pop()
577
+
578
+ def _handle_link(
575
579
  self,
576
- links: List[FlowStepLink],
577
- path: FlowPath,
578
- collected_paths: FlowPathsList,
579
- step_ids_visited: set,
580
+ current_path: FlowPath,
581
+ all_paths: FlowPathsList,
582
+ visited_step_ids: Set[str],
583
+ link: FlowStepLink,
580
584
  ) -> None:
581
- """Processes the next step in a flow.
582
-
583
- Potentially recursively calling itself to handle conditional paths and
584
- branching.
585
+ """Handles the next step in a flow.
585
586
 
586
587
  Args:
587
- links: Links listed in the "next" attribute.
588
- path: The current path taken in the flow.
589
- collected_paths: A list of paths collected so far.
590
- step_ids_visited: A set of step IDs that have already been visited
591
- to avoid loops.
588
+ current_path: The current path being constructed.
589
+ all_paths: The list where completed paths are added.
590
+ visited_step_ids: A set of steps that have been visited to avoid cycles.
591
+ link: The link to be followed.
592
592
 
593
593
  Returns:
594
- None: Modifies collected_paths in place by appending new paths
595
- as they are completed.
594
+ None: This function modifies all_paths in place by appending new paths
595
+ as they are found.
596
596
  """
597
- steps = self.steps
598
-
599
- for link in links:
600
- # Direct step id reference
601
- if isinstance(link, StaticFlowStepLink):
602
- # Find this id in the flow steps and restart from there
603
- for i, step in enumerate(steps):
604
- if step.id == link.target_step_id:
605
- self._go_over_steps(
606
- steps[i:],
607
- copy.deepcopy(path),
608
- collected_paths,
609
- copy.deepcopy(step_ids_visited),
610
- )
611
-
612
- # If conditions
613
- elif isinstance(link, (IfFlowStepLink, ElseFlowStepLink)):
614
- # Handling conditional paths
615
- target_steps: Union[str, List[FlowStep]]
616
- if isinstance(link.target_reference, FlowStepSequence):
617
- target_steps = link.target_reference.child_steps
618
- else:
619
- target_steps = link.target_reference
620
-
597
+ # StaticFlowStepLink is a direct link to the next step.
598
+ if isinstance(link, StaticFlowStepLink):
599
+ # Find the step by its id and continue the path.
600
+ if step := self._get_step_by_step_id(link.target_step_id):
621
601
  self._go_over_steps(
622
- target_steps,
623
- copy.deepcopy(path),
624
- collected_paths,
625
- copy.deepcopy(step_ids_visited),
602
+ step,
603
+ current_path,
604
+ all_paths,
605
+ visited_step_ids,
626
606
  )
607
+ return
608
+ # IfFlowStepLink and ElseFlowStepLink are conditional links.
609
+ elif isinstance(link, (IfFlowStepLink, ElseFlowStepLink)):
610
+ if isinstance(link.target_reference, FlowStepSequence):
611
+ # If the target is a FlowStepSequence, we need to go over all
612
+ # child steps of the sequence.
613
+ for child_step in link.target_reference.child_steps:
614
+ self._go_over_steps(
615
+ child_step,
616
+ current_path,
617
+ all_paths,
618
+ visited_step_ids,
619
+ )
620
+ return
621
+ else:
622
+ # Find the step by its id and continue the path.
623
+ if step := self._get_step_by_step_id(link.target_reference):
624
+ self._go_over_steps(
625
+ step,
626
+ current_path,
627
+ all_paths,
628
+ visited_step_ids,
629
+ )
630
+ return
631
+
632
+ def is_end_of_path(self, step: FlowStep) -> bool:
633
+ """Check if there is no path available from the current step."""
634
+ if (
635
+ len(step.next.links) == 1
636
+ and isinstance(step.next.links[0], StaticFlowStepLink)
637
+ and step.next.links[0].target == END_STEP
638
+ ):
639
+ return True
640
+ return False
641
+
642
+ def _get_step_by_step_id(
643
+ self,
644
+ step_id: Optional[str],
645
+ ) -> Optional[FlowStep]:
646
+ """Get a step by its id from a list of steps."""
647
+ for step in self.steps:
648
+ if step.id == step_id:
649
+ return step
650
+ return None
@@ -100,7 +100,9 @@ class FlowStep:
100
100
  """Most steps allow linking to the next step. But some don't."""
101
101
  return True
102
102
 
103
- def as_json(self) -> Dict[Text, Any]:
103
+ def as_json(
104
+ self, step_properties: Optional[Dict[Text, Any]] = None
105
+ ) -> Dict[Text, Any]:
104
106
  """Serialize the FlowStep object.
105
107
 
106
108
  Returns:
@@ -108,10 +110,13 @@ class FlowStep:
108
110
  """
109
111
  data: Dict[Text, Any] = {"id": self.id}
110
112
 
111
- if dumped_next := self.next.as_json():
112
- data["next"] = dumped_next
113
+ if step_properties:
114
+ data.update(step_properties)
115
+
113
116
  if self.description:
114
117
  data["description"] = self.description
118
+ if dumped_next := self.next.as_json():
119
+ data["next"] = dumped_next
115
120
  if self.metadata:
116
121
  data["metadata"] = self.metadata
117
122
  return data
@@ -143,6 +148,17 @@ class FlowStep:
143
148
  """Return all the utterances used in this step."""
144
149
  return set()
145
150
 
151
+ def __eq__(self, other: object) -> bool:
152
+ if not isinstance(other, FlowStep):
153
+ return False
154
+
155
+ return (
156
+ self.idx == other.idx
157
+ and self.description == other.description
158
+ and self.next == other.next
159
+ and self.flow_id == other.flow_id
160
+ )
161
+
146
162
 
147
163
  @dataclass
148
164
  class FlowStepWithFlowReference:
@@ -74,6 +74,11 @@ class FlowStepLinks:
74
74
  depth = max(depth, link.depth_in_tree())
75
75
  return depth
76
76
 
77
+ def __eq__(self, other: object) -> bool:
78
+ if isinstance(other, FlowStepLinks):
79
+ return self.links == other.links
80
+ return False
81
+
77
82
 
78
83
  class FlowStepLink:
79
84
  """A flow step link that links two steps in a single flow."""
@@ -193,6 +198,11 @@ class BranchingFlowStepLink(FlowStepLink):
193
198
  return depth + 1
194
199
  return 1
195
200
 
201
+ def __eq__(self, other: object) -> bool:
202
+ if isinstance(other, BranchingFlowStepLink):
203
+ return self.target_reference == other.target_reference
204
+ return False
205
+
196
206
 
197
207
  @dataclass
198
208
  class IfFlowStepLink(BranchingFlowStepLink):
@@ -324,3 +334,8 @@ class StaticFlowStepLink(FlowStepLink):
324
334
  def depth_in_tree(self) -> int:
325
335
  """Returns the depth in the tree."""
326
336
  return 0
337
+
338
+ def __eq__(self, other: object) -> bool:
339
+ if isinstance(other, StaticFlowStepLink):
340
+ return self.target_step_id == other.target_step_id
341
+ return False
@@ -70,3 +70,9 @@ class FlowStepSequence:
70
70
  def empty(cls) -> FlowStepSequence:
71
71
  """Create an empty FlowStepSequence object."""
72
72
  return cls(child_steps=[])
73
+
74
+ def __eq__(self, other: object) -> bool:
75
+ return (
76
+ isinstance(other, FlowStepSequence)
77
+ and self.child_steps == other.child_steps
78
+ )
@@ -28,6 +28,14 @@ class NLUTrigger:
28
28
  """
29
29
  return self.intent == intent and confidence >= self.confidence_threshold
30
30
 
31
+ def __eq__(self, other: object) -> bool:
32
+ if isinstance(other, NLUTrigger):
33
+ return (
34
+ self.intent == other.intent
35
+ and self.confidence_threshold == other.confidence_threshold
36
+ )
37
+ return False
38
+
31
39
 
32
40
  @dataclass
33
41
  class NLUTriggers:
@@ -115,3 +123,8 @@ class NLUTriggers:
115
123
  trigger_condition.is_triggered(intent, confidence)
116
124
  for trigger_condition in self.trigger_conditions
117
125
  )
126
+
127
+ def __eq__(self, other: object) -> bool:
128
+ if isinstance(other, NLUTriggers):
129
+ return self.trigger_conditions == other.trigger_conditions
130
+ return False
@@ -31,15 +31,13 @@ class ActionFlowStep(FlowStep):
31
31
  **base.__dict__,
32
32
  )
33
33
 
34
- def as_json(self) -> Dict[Text, Any]:
34
+ def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
35
35
  """Serialize the ActionFlowStep
36
36
 
37
37
  Returns:
38
38
  The ActionFlowStep object as serialized data.
39
39
  """
40
- data = super().as_json()
41
- data["action"] = self.action
42
- return data
40
+ return super().as_json(step_properties={"action": self.action})
43
41
 
44
42
  @property
45
43
  def default_id_postfix(self) -> str:
@@ -55,3 +53,8 @@ class ActionFlowStep(FlowStep):
55
53
  def custom_action(self) -> Optional[str]:
56
54
  """Return all the custom actions used in this step."""
57
55
  return self.action if not self.action.startswith(UTTER_PREFIX) else None
56
+
57
+ def __eq__(self, other: object) -> bool:
58
+ if isinstance(other, type(self)):
59
+ return self.action == other.action and super().__eq__(other)
60
+ return False
@@ -34,15 +34,13 @@ class CallFlowStep(FlowStep):
34
34
  **base.__dict__,
35
35
  )
36
36
 
37
- def as_json(self) -> Dict[Text, Any]:
37
+ def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
38
38
  """Returns the flow step as a dictionary.
39
39
 
40
40
  Returns:
41
41
  The flow step as a dictionary.
42
42
  """
43
- dump = super().as_json()
44
- dump["call"] = self.call
45
- return dump
43
+ return super().as_json(step_properties={"call": self.call})
46
44
 
47
45
  def steps_in_tree(
48
46
  self, should_resolve_calls: bool = True
@@ -62,3 +60,12 @@ class CallFlowStep(FlowStep):
62
60
  def default_id_postfix(self) -> str:
63
61
  """Returns the default id postfix of the flow step."""
64
62
  return f"call_{self.call}"
63
+
64
+ def __eq__(self, other: object) -> bool:
65
+ if isinstance(other, type(self)):
66
+ return (
67
+ self.call == other.call
68
+ and self.called_flow_reference == other.called_flow_reference
69
+ and super().__eq__(other)
70
+ )
71
+ return False
@@ -7,6 +7,10 @@ from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
7
7
  from rasa.shared.core.flows.flow_step import FlowStep
8
8
  from rasa.shared.core.slots import SlotRejection
9
9
 
10
+ DEFAULT_ASK_BEFORE_FILLING = False
11
+ DEFAULT_RESET_AFTER_FLOW_ENDS = True
12
+ DEFAULT_FORCE_SLOT_FILLING = False
13
+
10
14
 
11
15
  @dataclass
12
16
  class CollectInformationFlowStep(FlowStep):
@@ -20,9 +24,9 @@ class CollectInformationFlowStep(FlowStep):
20
24
  """The action that the assistant uses to ask for the slot."""
21
25
  rejections: List[SlotRejection]
22
26
  """how the slot value is validated using predicate evaluation."""
23
- ask_before_filling: bool = False
27
+ ask_before_filling: bool = DEFAULT_ASK_BEFORE_FILLING
24
28
  """Whether to always ask the question even if the slot is already filled."""
25
- reset_after_flow_ends: bool = True
29
+ reset_after_flow_ends: bool = DEFAULT_RESET_AFTER_FLOW_ENDS
26
30
  """Whether to reset the slot value at the end of the flow."""
27
31
  force_slot_filling: bool = False
28
32
  """Whether to keep only the SetSlot command for the collected slot."""
@@ -43,7 +47,7 @@ class CollectInformationFlowStep(FlowStep):
43
47
  base = super().from_json(flow_id, data)
44
48
  return CollectInformationFlowStep(
45
49
  collect=data["collect"],
46
- utter=data.get("utter", f"{UTTER_ASK_PREFIX}{data['collect']}"),
50
+ utter=data.get("utter", cls._default_utter(data["collect"])),
47
51
  # as of now it is not possible to define a different name for the
48
52
  # action, always use the default name 'action_ask_<slot_name>'
49
53
  collect_action=f"{ACTION_ASK_PREFIX}{data['collect']}",
@@ -57,7 +61,11 @@ class CollectInformationFlowStep(FlowStep):
57
61
  **base.__dict__,
58
62
  )
59
63
 
60
- def as_json(self) -> Dict[str, Any]:
64
+ @staticmethod
65
+ def _default_utter(collect: str) -> str:
66
+ return f"{UTTER_ASK_PREFIX}{collect}"
67
+
68
+ def as_json(self) -> Dict[str, Any]: # type: ignore[override]
61
69
  """Serialize the CollectInformationFlowStep object.
62
70
 
63
71
  Returns:
@@ -70,8 +78,7 @@ class CollectInformationFlowStep(FlowStep):
70
78
  data["reset_after_flow_ends"] = self.reset_after_flow_ends
71
79
  data["rejections"] = [rejection.as_dict() for rejection in self.rejections]
72
80
  data["force_slot_filling"] = self.force_slot_filling
73
-
74
- return data
81
+ return super().as_json(step_properties=data)
75
82
 
76
83
  @property
77
84
  def default_id_postfix(self) -> str:
@@ -82,3 +89,17 @@ class CollectInformationFlowStep(FlowStep):
82
89
  def utterances(self) -> Set[str]:
83
90
  """Return all the utterances used in this step."""
84
91
  return {self.utter} | {r.utter for r in self.rejections}
92
+
93
+ def __eq__(self, other: object) -> bool:
94
+ if isinstance(other, type(self)):
95
+ return (
96
+ self.collect == other.collect
97
+ and self.utter == other.utter
98
+ and self.collect_action == other.collect_action
99
+ and self.rejections == other.rejections
100
+ and self.ask_before_filling == other.ask_before_filling
101
+ and self.reset_after_flow_ends == other.reset_after_flow_ends
102
+ and self.force_slot_filling == other.force_slot_filling
103
+ and super().__eq__(other)
104
+ )
105
+ return False
@@ -28,7 +28,7 @@ class InternalFlowStep(FlowStep):
28
28
  "or de-serialized."
29
29
  )
30
30
 
31
- def as_json(self) -> Dict[Text, Any]:
31
+ def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
32
32
  """Serialize the InternalFlowStep object
33
33
 
34
34
  Returns:
@@ -43,3 +43,8 @@ class InternalFlowStep(FlowStep):
43
43
  def default_id_postfix(self) -> str:
44
44
  """Returns the default id postfix of the flow step."""
45
45
  raise ValueError("Internal flow steps do not need a default id")
46
+
47
+ def __eq__(self, other: object) -> bool:
48
+ if isinstance(other, type(self)):
49
+ return super().__eq__(other)
50
+ return False
@@ -37,17 +37,20 @@ class LinkFlowStep(FlowStep):
37
37
  **base.__dict__,
38
38
  )
39
39
 
40
- def as_json(self) -> Dict[Text, Any]:
40
+ def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
41
41
  """Serialize the LinkFlowStep object
42
42
 
43
43
  Returns:
44
44
  the LinkFlowStep object as serialized data.
45
45
  """
46
- data = super().as_json()
47
- data["link"] = self.link
48
- return data
46
+ return super().as_json(step_properties={"link": self.link})
49
47
 
50
48
  @property
51
49
  def default_id_postfix(self) -> str:
52
50
  """Returns the default id postfix of the flow step."""
53
51
  return f"link_{self.link}"
52
+
53
+ def __eq__(self, other: object) -> bool:
54
+ if isinstance(other, type(self)):
55
+ return self.link == other.link and super().__eq__(other)
56
+ return False