rasa-pro 3.12.0.dev12__py3-none-any.whl → 3.12.0rc1__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 (153) hide show
  1. rasa/anonymization/anonymization_rule_executor.py +16 -10
  2. rasa/cli/data.py +16 -0
  3. rasa/cli/inspect.py +20 -1
  4. rasa/cli/project_templates/calm/config.yml +2 -2
  5. rasa/cli/project_templates/calm/endpoints.yml +2 -2
  6. rasa/cli/shell.py +3 -3
  7. rasa/cli/utils.py +12 -0
  8. rasa/core/actions/action.py +99 -193
  9. rasa/core/actions/action_handle_digressions.py +142 -0
  10. rasa/core/actions/action_run_slot_rejections.py +16 -4
  11. rasa/core/actions/forms.py +10 -5
  12. rasa/core/channels/__init__.py +4 -0
  13. rasa/core/channels/studio_chat.py +19 -0
  14. rasa/core/channels/telegram.py +42 -24
  15. rasa/core/channels/voice_ready/audiocodes.py +42 -23
  16. rasa/core/channels/voice_ready/utils.py +1 -1
  17. rasa/core/channels/voice_stream/asr/asr_engine.py +10 -4
  18. rasa/core/channels/voice_stream/asr/azure.py +14 -1
  19. rasa/core/channels/voice_stream/asr/deepgram.py +20 -4
  20. rasa/core/channels/voice_stream/audiocodes.py +264 -0
  21. rasa/core/channels/voice_stream/browser_audio.py +5 -1
  22. rasa/core/channels/voice_stream/call_state.py +10 -1
  23. rasa/core/channels/voice_stream/genesys.py +335 -0
  24. rasa/core/channels/voice_stream/tts/azure.py +11 -2
  25. rasa/core/channels/voice_stream/tts/cartesia.py +29 -10
  26. rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
  27. rasa/core/channels/voice_stream/voice_channel.py +25 -3
  28. rasa/core/constants.py +2 -0
  29. rasa/core/migrate.py +2 -2
  30. rasa/core/nlg/contextual_response_rephraser.py +18 -1
  31. rasa/core/nlg/generator.py +83 -15
  32. rasa/core/nlg/response.py +6 -3
  33. rasa/core/nlg/translate.py +55 -0
  34. rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +1 -1
  35. rasa/core/policies/flows/flow_executor.py +47 -46
  36. rasa/core/processor.py +72 -9
  37. rasa/core/run.py +4 -3
  38. rasa/dialogue_understanding/commands/can_not_handle_command.py +20 -2
  39. rasa/dialogue_understanding/commands/cancel_flow_command.py +80 -4
  40. rasa/dialogue_understanding/commands/change_flow_command.py +20 -2
  41. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +20 -2
  42. rasa/dialogue_understanding/commands/clarify_command.py +29 -3
  43. rasa/dialogue_understanding/commands/command.py +1 -16
  44. rasa/dialogue_understanding/commands/command_syntax_manager.py +55 -0
  45. rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
  46. rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
  47. rasa/dialogue_understanding/commands/human_handoff_command.py +20 -2
  48. rasa/dialogue_understanding/commands/knowledge_answer_command.py +20 -2
  49. rasa/dialogue_understanding/commands/prompt_command.py +94 -0
  50. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +20 -2
  51. rasa/dialogue_understanding/commands/set_slot_command.py +29 -15
  52. rasa/dialogue_understanding/commands/skip_question_command.py +20 -2
  53. rasa/dialogue_understanding/commands/start_flow_command.py +61 -2
  54. rasa/dialogue_understanding/commands/utils.py +98 -4
  55. rasa/dialogue_understanding/constants.py +1 -0
  56. rasa/dialogue_understanding/generator/__init__.py +2 -0
  57. rasa/dialogue_understanding/generator/command_generator.py +110 -73
  58. rasa/dialogue_understanding/generator/command_parser.py +16 -13
  59. rasa/dialogue_understanding/generator/constants.py +3 -0
  60. rasa/dialogue_understanding/generator/llm_based_command_generator.py +170 -5
  61. rasa/dialogue_understanding/generator/llm_command_generator.py +5 -3
  62. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +26 -4
  63. rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
  64. rasa/dialogue_understanding/generator/prompt_templates/__init__.py +0 -0
  65. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +60 -0
  66. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +77 -0
  67. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +68 -0
  68. rasa/dialogue_understanding/generator/{single_step/command_prompt_template.jinja2 → prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2} +1 -1
  69. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +460 -0
  70. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +12 -318
  71. rasa/dialogue_understanding/generator/utils.py +32 -1
  72. rasa/dialogue_understanding/patterns/collect_information.py +1 -1
  73. rasa/dialogue_understanding/patterns/correction.py +13 -1
  74. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +78 -2
  75. rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
  76. rasa/dialogue_understanding/patterns/validate_slot.py +65 -0
  77. rasa/dialogue_understanding/processor/command_processor.py +154 -28
  78. rasa/dialogue_understanding/utils.py +31 -0
  79. rasa/dialogue_understanding_test/README.md +50 -0
  80. rasa/dialogue_understanding_test/du_test_case.py +28 -8
  81. rasa/dialogue_understanding_test/du_test_result.py +13 -9
  82. rasa/dialogue_understanding_test/io.py +14 -0
  83. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
  84. rasa/e2e_test/utils/io.py +0 -37
  85. rasa/engine/graph.py +1 -0
  86. rasa/engine/language.py +140 -0
  87. rasa/engine/recipes/config_files/default_config.yml +4 -0
  88. rasa/engine/recipes/default_recipe.py +2 -0
  89. rasa/engine/recipes/graph_recipe.py +2 -0
  90. rasa/engine/storage/local_model_storage.py +1 -0
  91. rasa/engine/storage/storage.py +4 -1
  92. rasa/model_manager/runner_service.py +7 -4
  93. rasa/model_manager/socket_bridge.py +7 -6
  94. rasa/model_manager/warm_rasa_process.py +0 -1
  95. rasa/model_training.py +24 -27
  96. rasa/shared/constants.py +15 -13
  97. rasa/shared/core/constants.py +30 -3
  98. rasa/shared/core/domain.py +13 -20
  99. rasa/shared/core/events.py +13 -2
  100. rasa/shared/core/flows/constants.py +11 -0
  101. rasa/shared/core/flows/flow.py +100 -19
  102. rasa/shared/core/flows/flows_yaml_schema.json +69 -3
  103. rasa/shared/core/flows/steps/collect.py +19 -37
  104. rasa/shared/core/flows/utils.py +43 -4
  105. rasa/shared/core/flows/validation.py +1 -1
  106. rasa/shared/core/slot_mappings.py +350 -111
  107. rasa/shared/core/slots.py +154 -3
  108. rasa/shared/core/trackers.py +77 -2
  109. rasa/shared/importers/importer.py +50 -2
  110. rasa/shared/nlu/constants.py +1 -0
  111. rasa/shared/nlu/training_data/schemas/responses.yml +19 -12
  112. rasa/shared/providers/_configs/azure_entra_id_config.py +541 -0
  113. rasa/shared/providers/_configs/azure_openai_client_config.py +138 -3
  114. rasa/shared/providers/_configs/client_config.py +3 -1
  115. rasa/shared/providers/_configs/default_litellm_client_config.py +3 -1
  116. rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +3 -1
  117. rasa/shared/providers/_configs/litellm_router_client_config.py +3 -1
  118. rasa/shared/providers/_configs/model_group_config.py +4 -2
  119. rasa/shared/providers/_configs/oauth_config.py +33 -0
  120. rasa/shared/providers/_configs/openai_client_config.py +3 -1
  121. rasa/shared/providers/_configs/rasa_llm_client_config.py +3 -1
  122. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +3 -1
  123. rasa/shared/providers/constants.py +6 -0
  124. rasa/shared/providers/embedding/azure_openai_embedding_client.py +28 -3
  125. rasa/shared/providers/embedding/litellm_router_embedding_client.py +3 -1
  126. rasa/shared/providers/llm/_base_litellm_client.py +42 -17
  127. rasa/shared/providers/llm/azure_openai_llm_client.py +81 -25
  128. rasa/shared/providers/llm/default_litellm_llm_client.py +3 -1
  129. rasa/shared/providers/llm/litellm_router_llm_client.py +29 -8
  130. rasa/shared/providers/llm/llm_client.py +23 -7
  131. rasa/shared/providers/llm/openai_llm_client.py +9 -3
  132. rasa/shared/providers/llm/rasa_llm_client.py +11 -2
  133. rasa/shared/providers/llm/self_hosted_llm_client.py +30 -11
  134. rasa/shared/providers/router/_base_litellm_router_client.py +3 -1
  135. rasa/shared/providers/router/router_client.py +3 -1
  136. rasa/shared/utils/constants.py +3 -0
  137. rasa/shared/utils/llm.py +31 -8
  138. rasa/shared/utils/pykwalify_extensions.py +24 -0
  139. rasa/shared/utils/schemas/domain.yml +26 -1
  140. rasa/telemetry.py +45 -14
  141. rasa/tracing/config.py +2 -0
  142. rasa/tracing/constants.py +12 -0
  143. rasa/tracing/instrumentation/instrumentation.py +36 -0
  144. rasa/tracing/instrumentation/metrics.py +41 -0
  145. rasa/tracing/metric_instrument_provider.py +40 -0
  146. rasa/utils/common.py +0 -1
  147. rasa/validator.py +561 -89
  148. rasa/version.py +1 -1
  149. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/METADATA +2 -1
  150. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/RECORD +153 -134
  151. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/NOTICE +0 -0
  152. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/WHEEL +0 -0
  153. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/entry_points.txt +0 -0
@@ -46,10 +46,8 @@ from rasa.shared.constants import (
46
46
  )
47
47
  from rasa.shared.core.constants import (
48
48
  ACTION_SHOULD_SEND_DOMAIN,
49
- ACTIVE_LOOP,
49
+ KEY_MAPPING_TYPE,
50
50
  KNOWLEDGE_BASE_SLOT_NAMES,
51
- MAPPING_CONDITIONS,
52
- MAPPING_TYPE,
53
51
  SLOT_MAPPINGS,
54
52
  SlotMappingType,
55
53
  )
@@ -290,8 +288,6 @@ class Domain:
290
288
  responses = data.get(KEY_RESPONSES, {})
291
289
 
292
290
  domain_slots = data.get(KEY_SLOTS, {})
293
- if domain_slots:
294
- rasa.shared.core.slot_mappings.validate_slot_mappings(domain_slots)
295
291
  slots = cls.collect_slots(domain_slots)
296
292
  domain_actions = data.get(KEY_ACTIONS, [])
297
293
  actions = cls._collect_action_names(domain_actions)
@@ -596,7 +592,7 @@ class Domain:
596
592
  ),
597
593
  )
598
594
  slot_dict[slot_name][SLOT_MAPPINGS] = [
599
- {MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
595
+ {KEY_MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
600
596
  ]
601
597
 
602
598
  slot = slot_class(slot_name, **slot_dict[slot_name])
@@ -1569,21 +1565,18 @@ class Domain:
1569
1565
  matching_entities = []
1570
1566
 
1571
1567
  for mapping in slot.mappings:
1572
- mapping_conditions = mapping.get(MAPPING_CONDITIONS)
1573
- if mapping[MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY) or (
1568
+ mapping_conditions = mapping.conditions
1569
+ if mapping.type != SlotMappingType.FROM_ENTITY or (
1574
1570
  mapping_conditions
1575
- and mapping_conditions[0].get(ACTIVE_LOOP) is not None
1571
+ and mapping_conditions[0].active_loop is not None
1576
1572
  ):
1577
1573
  continue
1578
1574
 
1579
1575
  for entity in entities:
1580
1576
  if (
1581
- entity.get(ENTITY_ATTRIBUTE_TYPE)
1582
- == mapping.get(ENTITY_ATTRIBUTE_TYPE)
1583
- and entity.get(ENTITY_ATTRIBUTE_ROLE)
1584
- == mapping.get(ENTITY_ATTRIBUTE_ROLE)
1585
- and entity.get(ENTITY_ATTRIBUTE_GROUP)
1586
- == mapping.get(ENTITY_ATTRIBUTE_GROUP)
1577
+ entity.get(ENTITY_ATTRIBUTE_TYPE) == mapping.entity
1578
+ and entity.get(ENTITY_ATTRIBUTE_ROLE) == mapping.role
1579
+ and entity.get(ENTITY_ATTRIBUTE_GROUP) == mapping.group
1587
1580
  ):
1588
1581
  matching_entities.append(entity.get("value"))
1589
1582
 
@@ -2015,19 +2008,19 @@ class Domain:
2015
2008
  is the total number of mappings which have conditions attached.
2016
2009
  """
2017
2010
  total_mappings = 0
2018
- custom_mappings = 0
2011
+ controlled_mappings = 0
2019
2012
  conditional_mappings = 0
2020
2013
 
2021
2014
  for slot in self.slots:
2022
2015
  total_mappings += len(slot.mappings)
2023
2016
  for mapping in slot.mappings:
2024
- if mapping[MAPPING_TYPE] == str(SlotMappingType.CUSTOM):
2025
- custom_mappings += 1
2017
+ if mapping.type == SlotMappingType.CONTROLLED:
2018
+ controlled_mappings += 1
2026
2019
 
2027
- if MAPPING_CONDITIONS in mapping:
2020
+ if mapping.conditions:
2028
2021
  conditional_mappings += 1
2029
2022
 
2030
- return (total_mappings, custom_mappings, conditional_mappings)
2023
+ return total_mappings, controlled_mappings, conditional_mappings
2031
2024
 
2032
2025
  def does_custom_action_explicitly_need_domain(self, action_name: Text) -> bool:
2033
2026
  """Assert if action has explicitly stated that it needs domain.
@@ -1032,6 +1032,7 @@ class SlotSet(Event):
1032
1032
  value: Optional[Any] = None,
1033
1033
  timestamp: Optional[float] = None,
1034
1034
  metadata: Optional[Dict[Text, Any]] = None,
1035
+ filled_by: Optional[str] = None,
1035
1036
  ) -> None:
1036
1037
  """Creates event to set slot.
1037
1038
 
@@ -1043,6 +1044,7 @@ class SlotSet(Event):
1043
1044
  """
1044
1045
  self.key = key
1045
1046
  self.value = value
1047
+ self._filled_by = filled_by
1046
1048
  super().__init__(timestamp, metadata)
1047
1049
 
1048
1050
  def __repr__(self) -> Text:
@@ -1060,6 +1062,14 @@ class SlotSet(Event):
1060
1062
 
1061
1063
  return (self.key, self.value) == (other.key, other.value)
1062
1064
 
1065
+ @property
1066
+ def filled_by(self) -> Optional[str]:
1067
+ return self._filled_by
1068
+
1069
+ @filled_by.setter
1070
+ def filled_by(self, value: str) -> None:
1071
+ self._filled_by = value
1072
+
1063
1073
  def as_story_string(self) -> Text:
1064
1074
  """Returns text representation of event."""
1065
1075
  props = json.dumps({self.key: self.value}, ensure_ascii=False)
@@ -1081,7 +1091,7 @@ class SlotSet(Event):
1081
1091
  def as_dict(self) -> Dict[Text, Any]:
1082
1092
  """Returns serialized event."""
1083
1093
  d = super().as_dict()
1084
- d.update({"name": self.key, "value": self.value})
1094
+ d.update({"name": self.key, "value": self.value, "filled_by": self.filled_by})
1085
1095
  return d
1086
1096
 
1087
1097
  @classmethod
@@ -1092,13 +1102,14 @@ class SlotSet(Event):
1092
1102
  parameters.get("value"),
1093
1103
  parameters.get("timestamp"),
1094
1104
  parameters.get("metadata"),
1105
+ filled_by=parameters.get("filled_by"),
1095
1106
  )
1096
1107
  except KeyError as e:
1097
1108
  raise ValueError(f"Failed to parse set slot event. {e}")
1098
1109
 
1099
1110
  def apply_to(self, tracker: "DialogueStateTracker") -> None:
1100
1111
  """Applies event to current conversation state."""
1101
- tracker._set_slot(self.key, self.value)
1112
+ tracker._set_slot(self.key, self.value, self.filled_by)
1102
1113
 
1103
1114
 
1104
1115
  class Restarted(AlwaysEqualEventMixin):
@@ -0,0 +1,11 @@
1
+ KEY_ID = "id"
2
+ KEY_STEPS = "steps"
3
+ KEY_NAME = "name"
4
+ KEY_DESCRIPTION = "description"
5
+ KEY_IF = "if"
6
+ KEY_ALWAYS_INCLUDE_IN_PROMPT = "always_include_in_prompt"
7
+ KEY_NLU_TRIGGER = "nlu_trigger"
8
+ KEY_FILE_PATH = "file_path"
9
+ KEY_PERSISTED_SLOTS = "persisted_slots"
10
+ KEY_RUN_PATTERN_COMPLETED = "run_pattern_completed"
11
+ KEY_TRANSLATION = "translation"
@@ -7,10 +7,29 @@ from pathlib import Path
7
7
  from typing import Any, Dict, List, Optional, Set, Text, Union
8
8
 
9
9
  import structlog
10
+ from pydantic import BaseModel
10
11
  from pypred import Predicate
11
12
 
12
13
  import rasa.shared.utils.io
14
+ from rasa.engine.language import Language
13
15
  from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
16
+ from rasa.shared.core.constants import (
17
+ KEY_ASK_CONFIRM_DIGRESSIONS,
18
+ KEY_BLOCK_DIGRESSIONS,
19
+ )
20
+ from rasa.shared.core.flows.constants import (
21
+ KEY_ALWAYS_INCLUDE_IN_PROMPT,
22
+ KEY_DESCRIPTION,
23
+ KEY_FILE_PATH,
24
+ KEY_ID,
25
+ KEY_IF,
26
+ KEY_NAME,
27
+ KEY_NLU_TRIGGER,
28
+ KEY_PERSISTED_SLOTS,
29
+ KEY_RUN_PATTERN_COMPLETED,
30
+ KEY_STEPS,
31
+ KEY_TRANSLATION,
32
+ )
14
33
  from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
15
34
  from rasa.shared.core.flows.flow_step import FlowStep
16
35
  from rasa.shared.core.flows.flow_step_links import (
@@ -33,11 +52,22 @@ from rasa.shared.core.flows.steps.constants import (
33
52
  START_STEP,
34
53
  )
35
54
  from rasa.shared.core.flows.steps.continuation import ContinueFlowStep
55
+ from rasa.shared.core.flows.utils import extract_digression_prop
36
56
  from rasa.shared.core.slots import Slot
37
57
 
38
58
  structlogger = structlog.get_logger()
39
59
 
40
60
 
61
+ class FlowLanguageTranslation(BaseModel):
62
+ """Represents the translation of the flow properties in a specific language."""
63
+
64
+ name: str
65
+ """The human-readable name of the flow."""
66
+
67
+ class Config:
68
+ extra = "ignore"
69
+
70
+
41
71
  @dataclass
42
72
  class Flow:
43
73
  """Represents the configuration of a flow."""
@@ -48,6 +78,8 @@ class Flow:
48
78
  """The human-readable name of the flow."""
49
79
  description: Optional[Text] = None
50
80
  """The description of the flow."""
81
+ translation: Dict[Text, FlowLanguageTranslation] = field(default_factory=dict)
82
+ """The translation of the flow properties in different languages."""
51
83
  guard_condition: Optional[Text] = None
52
84
  """The condition that needs to be fulfilled for the flow to be startable."""
53
85
  step_sequence: FlowStepSequence = field(default_factory=FlowStepSequence.empty)
@@ -62,6 +94,12 @@ class Flow:
62
94
  """The path to the file where the flow is stored."""
63
95
  persisted_slots: List[str] = field(default_factory=list)
64
96
  """The list of slots that should be persisted after the flow ends."""
97
+ ask_confirm_digressions: List[str] = field(default_factory=list)
98
+ """The flow ids for which the assistant should ask for confirmation."""
99
+ block_digressions: List[str] = field(default_factory=list)
100
+ """The flow ids that the assistant should block from digressing to."""
101
+ run_pattern_completed: bool = True
102
+ """Whether the pattern_completed flow should be run after the flow ends."""
65
103
 
66
104
  @staticmethod
67
105
  def from_json(
@@ -79,6 +117,8 @@ class Flow:
79
117
  Returns:
80
118
  A Flow object.
81
119
  """
120
+ from rasa.shared.core.flows.utils import extract_translations
121
+
82
122
  step_sequence = FlowStepSequence.from_json(flow_id, data.get("steps"))
83
123
  nlu_triggers = NLUTriggers.from_json(data.get("nlu_trigger"))
84
124
 
@@ -87,17 +127,25 @@ class Flow:
87
127
 
88
128
  return Flow(
89
129
  id=flow_id,
90
- custom_name=data.get("name"),
91
- description=data.get("description"),
92
- always_include_in_prompt=data.get("always_include_in_prompt"),
93
- # str or bool are permitted in the flow schema but internally we want a str
94
- guard_condition=str(data["if"]) if "if" in data else None,
130
+ custom_name=data.get(KEY_NAME),
131
+ description=data.get(KEY_DESCRIPTION),
132
+ always_include_in_prompt=data.get(KEY_ALWAYS_INCLUDE_IN_PROMPT),
133
+ # str or bool are permitted in the flow schema, but internally we want a str
134
+ guard_condition=str(data[KEY_IF]) if KEY_IF in data else None,
95
135
  step_sequence=Flow.resolve_default_ids(step_sequence),
96
136
  nlu_triggers=nlu_triggers,
97
137
  # If we are reading the flows in after training the file_path is part of
98
138
  # data. When the model is trained, take the provided file_path.
99
- file_path=data.get("file_path") if "file_path" in data else file_path,
100
- persisted_slots=data.get("persisted_slots", []),
139
+ file_path=data.get(KEY_FILE_PATH) if KEY_FILE_PATH in data else file_path,
140
+ persisted_slots=data.get(KEY_PERSISTED_SLOTS, []),
141
+ ask_confirm_digressions=extract_digression_prop(
142
+ KEY_ASK_CONFIRM_DIGRESSIONS, data
143
+ ),
144
+ block_digressions=extract_digression_prop(KEY_BLOCK_DIGRESSIONS, data),
145
+ run_pattern_completed=data.get(KEY_RUN_PATTERN_COMPLETED, True),
146
+ translation=extract_translations(
147
+ translation_data=data.get(KEY_TRANSLATION, {})
148
+ ),
101
149
  )
102
150
 
103
151
  def get_full_name(self) -> str:
@@ -155,29 +203,62 @@ class Flow:
155
203
  The Flow object as serialized data.
156
204
  """
157
205
  data: Dict[Text, Any] = {
158
- "id": self.id,
159
- "steps": self.step_sequence.as_json(),
206
+ KEY_ID: self.id,
207
+ KEY_STEPS: self.step_sequence.as_json(),
160
208
  }
161
209
  if self.custom_name is not None:
162
- data["name"] = self.custom_name
210
+ data[KEY_NAME] = self.custom_name
163
211
  if self.description is not None:
164
- data["description"] = self.description
212
+ data[KEY_DESCRIPTION] = self.description
165
213
  if self.guard_condition is not None:
166
- data["if"] = self.guard_condition
214
+ data[KEY_IF] = self.guard_condition
167
215
  if self.always_include_in_prompt is not None:
168
- data["always_include_in_prompt"] = self.always_include_in_prompt
216
+ data[KEY_ALWAYS_INCLUDE_IN_PROMPT] = self.always_include_in_prompt
169
217
  if self.nlu_triggers:
170
- data["nlu_trigger"] = self.nlu_triggers.as_json()
218
+ data[KEY_NLU_TRIGGER] = self.nlu_triggers.as_json()
171
219
  if self.file_path:
172
- data["file_path"] = self.file_path
220
+ data[KEY_FILE_PATH] = self.file_path
173
221
  if self.persisted_slots:
174
- data["persisted_slots"] = self.persisted_slots
222
+ data[KEY_PERSISTED_SLOTS] = self.persisted_slots
223
+ if self.ask_confirm_digressions:
224
+ data[KEY_ASK_CONFIRM_DIGRESSIONS] = self.ask_confirm_digressions
225
+ if self.block_digressions:
226
+ data[KEY_BLOCK_DIGRESSIONS] = self.block_digressions
227
+ if self.run_pattern_completed is not None:
228
+ data["run_pattern_completed"] = self.run_pattern_completed
229
+ if self.translation:
230
+ data[KEY_TRANSLATION] = {
231
+ language_code: translation.dict()
232
+ for language_code, translation in self.translation.items()
233
+ }
175
234
 
176
235
  return data
177
236
 
178
- def readable_name(self) -> str:
179
- """Returns the name of the flow or its id if no name is set."""
180
- return self.name or self.id
237
+ def localized_name(self, language: Optional[Language] = None) -> Optional[Text]:
238
+ """Returns the language specific flow name or None.
239
+
240
+ Args:
241
+ language: Preferred language code.
242
+
243
+ Returns:
244
+ Flow name in the specified language or None.
245
+ """
246
+ language_code = language.code if language else None
247
+ translation = self.translation.get(language_code)
248
+ return translation.name if translation else None
249
+
250
+ def readable_name(self, language: Optional[Language] = None) -> str:
251
+ """
252
+ Returns the flow's name in the specified language if available; otherwise
253
+ falls back to the flow's name, and finally the flow's ID.
254
+
255
+ Args:
256
+ language: Preferred language code.
257
+
258
+ Returns:
259
+ string: the localized name, the default name, or the flow's ID.
260
+ """
261
+ return self.localized_name(language) or self.name or self.id
181
262
 
182
263
  def step_by_id(self, step_id: Optional[Text]) -> Optional[FlowStep]:
183
264
  """Returns the step with the given id."""
@@ -217,6 +217,12 @@
217
217
  "reset_after_flow_ends": {
218
218
  "type": "boolean"
219
219
  },
220
+ "ask_confirm_digressions": {
221
+ "$ref": "#/$defs/ask_confirm_digressions"
222
+ },
223
+ "block_digressions": {
224
+ "$ref": "#/$defs/block_digressions"
225
+ },
220
226
  "utter": {
221
227
  "type": "string"
222
228
  },
@@ -247,6 +253,32 @@
247
253
  }
248
254
  }
249
255
  },
256
+ "ask_confirm_digressions": {
257
+ "oneOf": [
258
+ {
259
+ "type": "boolean"
260
+ },
261
+ {
262
+ "type": "array",
263
+ "items": {
264
+ "type": "string"
265
+ }
266
+ }
267
+ ]
268
+ },
269
+ "block_digressions": {
270
+ "oneOf": [
271
+ {
272
+ "type": "boolean"
273
+ },
274
+ {
275
+ "type": "array",
276
+ "items": {
277
+ "type": "string"
278
+ }
279
+ }
280
+ ]
281
+ },
250
282
  "flow": {
251
283
  "required": [
252
284
  "steps",
@@ -256,9 +288,37 @@
256
288
  "additionalProperties": false,
257
289
  "schema_name": "dictionary with flow properties",
258
290
  "properties": {
291
+ "name": {
292
+ "type": "string"
293
+ },
259
294
  "description": {
260
295
  "type": "string"
261
296
  },
297
+ "translation": {
298
+ "type": "object",
299
+ "schema_name": "flow translation mapping",
300
+ "properties": {
301
+ "metadata": {
302
+ "type": "object"
303
+ }
304
+ },
305
+ "patternProperties": {
306
+ "^(?!metadata$).+": {
307
+ "type": "object",
308
+ "additionalProperties": false,
309
+ "required": [
310
+ "name"
311
+ ],
312
+ "properties": {
313
+ "name": {
314
+ "type": "string"
315
+ },
316
+ "metadata": {}
317
+ }
318
+ }
319
+ },
320
+ "additionalProperties": false
321
+ },
262
322
  "if": {
263
323
  "type": [
264
324
  "string",
@@ -268,9 +328,6 @@
268
328
  "always_include_in_prompt": {
269
329
  "type": "boolean"
270
330
  },
271
- "name": {
272
- "type": "string"
273
- },
274
331
  "nlu_trigger": {
275
332
  "$ref": "#/$defs/nlu_trigger"
276
333
  },
@@ -282,6 +339,15 @@
282
339
  },
283
340
  "persisted_slots": {
284
341
  "$ref": "#/$defs/persisted_slots"
342
+ },
343
+ "ask_confirm_digressions": {
344
+ "$ref": "#/$defs/ask_confirm_digressions"
345
+ },
346
+ "block_digressions": {
347
+ "$ref": "#/$defs/block_digressions"
348
+ },
349
+ "run_pattern_completed": {
350
+ "type": "boolean"
285
351
  }
286
352
  }
287
353
  },
@@ -1,46 +1,16 @@
1
1
  from __future__ import annotations
2
2
 
3
- from dataclasses import dataclass
3
+ from dataclasses import dataclass, field
4
4
  from typing import Any, Dict, List, Set, Text
5
5
 
6
6
  from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
7
+ from rasa.shared.core.constants import (
8
+ KEY_ASK_CONFIRM_DIGRESSIONS,
9
+ KEY_BLOCK_DIGRESSIONS,
10
+ )
7
11
  from rasa.shared.core.flows.flow_step import FlowStep
8
-
9
-
10
- @dataclass
11
- class SlotRejection:
12
- """A pair of validation condition and an utterance for the case of failure."""
13
-
14
- if_: str
15
- """The condition that should be checked."""
16
- utter: str
17
- """The utterance that should be executed if the condition is met."""
18
-
19
- @staticmethod
20
- def from_dict(data: Dict[str, Any]) -> SlotRejection:
21
- """Create a SlotRejection object from serialized data.
22
-
23
- Args:
24
- data: data for a SlotRejection object in a serialized format
25
-
26
- Returns:
27
- A SlotRejection object
28
- """
29
- return SlotRejection(
30
- if_=data["if"],
31
- utter=data["utter"],
32
- )
33
-
34
- def as_dict(self) -> Dict[str, Any]:
35
- """Serialize the SlotRejection object.
36
-
37
- Returns:
38
- the SlotRejection object as serialized data
39
- """
40
- return {
41
- "if": self.if_,
42
- "utter": self.utter,
43
- }
12
+ from rasa.shared.core.flows.utils import extract_digression_prop
13
+ from rasa.shared.core.slots import SlotRejection
44
14
 
45
15
 
46
16
  @dataclass
@@ -59,6 +29,10 @@ class CollectInformationFlowStep(FlowStep):
59
29
  """Whether to always ask the question even if the slot is already filled."""
60
30
  reset_after_flow_ends: bool = True
61
31
  """Whether to reset the slot value at the end of the flow."""
32
+ ask_confirm_digressions: List[str] = field(default_factory=list)
33
+ """The flow id digressions for which the assistant should ask for confirmation."""
34
+ block_digressions: List[str] = field(default_factory=list)
35
+ """The flow id digressions that should be blocked during the flow step."""
62
36
 
63
37
  @classmethod
64
38
  def from_json(
@@ -86,6 +60,10 @@ class CollectInformationFlowStep(FlowStep):
86
60
  SlotRejection.from_dict(rejection)
87
61
  for rejection in data.get("rejections", [])
88
62
  ],
63
+ ask_confirm_digressions=extract_digression_prop(
64
+ KEY_ASK_CONFIRM_DIGRESSIONS, data
65
+ ),
66
+ block_digressions=extract_digression_prop(KEY_BLOCK_DIGRESSIONS, data),
89
67
  **base.__dict__,
90
68
  )
91
69
 
@@ -101,6 +79,10 @@ class CollectInformationFlowStep(FlowStep):
101
79
  data["ask_before_filling"] = self.ask_before_filling
102
80
  data["reset_after_flow_ends"] = self.reset_after_flow_ends
103
81
  data["rejections"] = [rejection.as_dict() for rejection in self.rejections]
82
+ data["ask_confirm_digressions"] = self.ask_confirm_digressions
83
+ data["block_digressions"] = (
84
+ self.block_digressions if self.block_digressions else False
85
+ )
104
86
 
105
87
  return data
106
88
 
@@ -1,16 +1,21 @@
1
- from typing import Set
1
+ from typing import TYPE_CHECKING, Any, Dict, List, Set, Text
2
2
 
3
3
  from rasa.shared.utils.io import raise_deprecation_warning
4
4
 
5
+ if TYPE_CHECKING:
6
+ from rasa.shared.core.flows.flow import FlowLanguageTranslation
7
+
8
+
5
9
  RESET_PROPERTY_NAME = "reset_after_flow_ends"
6
10
  PERSIST_PROPERTY_NAME = "persisted_slots"
11
+ ALL_LABEL = "ALL"
7
12
 
8
13
 
9
- def warn_deprecated_collect_step_config(flow_id: str, collect_step: str) -> None:
14
+ def warn_deprecated_collect_step_config() -> None:
10
15
  """Warns about deprecated reset_after_flow_ends usage in collect steps."""
11
16
  raise_deprecation_warning(
12
- f"Configuring '{RESET_PROPERTY_NAME}' in collect step '{collect_step}' is "
13
- f"deprecated and will be removed in Rasa Pro 4.0.0. In flow id '{flow_id}', "
17
+ f"Configuring '{RESET_PROPERTY_NAME}' in collect steps is "
18
+ f"deprecated and will be removed in Rasa Pro 4.0.0. In the parent flow, "
14
19
  f"please use the '{PERSIST_PROPERTY_NAME}' "
15
20
  "property at the flow level instead."
16
21
  )
@@ -38,3 +43,37 @@ def get_invalid_slot_persistence_config_error_message(
38
43
  f"are neither used in a collect step nor a set_slot step of the flow. "
39
44
  f"Please remove such slots from the '{PERSIST_PROPERTY_NAME}' property."
40
45
  )
46
+
47
+
48
+ def extract_digression_prop(prop: str, data: Dict[str, Any]) -> List[str]:
49
+ """Extracts the digression property from the data.
50
+
51
+ There can be two types of properties: ask_confirm_digressions and
52
+ block_digressions.
53
+ """
54
+ digression_property = data.get(prop, [])
55
+
56
+ if isinstance(digression_property, bool):
57
+ digression_property = [ALL_LABEL] if digression_property else []
58
+
59
+ return digression_property
60
+
61
+
62
+ def extract_translations(
63
+ translation_data: Dict[Text, Any],
64
+ ) -> Dict[Text, "FlowLanguageTranslation"]:
65
+ """Extracts translations from a dictionary.
66
+
67
+ Args:
68
+ translation_data: The dictionary containing the translations.
69
+
70
+ Returns:
71
+ A dictionary containing the extracted translations.
72
+ """
73
+ from rasa.shared.core.flows.flow import FlowLanguageTranslation
74
+
75
+ return {
76
+ language_code: FlowLanguageTranslation.parse_obj({**data})
77
+ for language_code, data in translation_data.items()
78
+ if language_code != "metadata"
79
+ }
@@ -723,7 +723,7 @@ def validate_slot_persistence_configuration(flow: Flow) -> None:
723
723
  flow_slots.add(step.collect)
724
724
  if not step.reset_after_flow_ends:
725
725
  collect_step = step.collect
726
- warn_deprecated_collect_step_config(flow_id, collect_step)
726
+ warn_deprecated_collect_step_config()
727
727
  if has_flow_level_persistence:
728
728
  raise DuplicateSlotPersistConfigException(flow_id, collect_step)
729
729