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
@@ -1,18 +1,40 @@
1
- import logging
2
- from typing import TYPE_CHECKING, Any, Dict, List, Optional, Text, Tuple, cast
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ from enum import Enum
5
+ from typing import (
6
+ TYPE_CHECKING,
7
+ Any,
8
+ Dict,
9
+ List,
10
+ Optional,
11
+ Set,
12
+ Text,
13
+ Tuple,
14
+ Union,
15
+ cast,
16
+ )
17
+
18
+ import structlog
19
+ from pydantic import BaseModel, Field
3
20
 
4
21
  import rasa.shared.utils.io
5
22
  from rasa.shared.constants import DOCS_URL_NLU_BASED_SLOTS, IGNORED_INTENTS
6
23
  from rasa.shared.core.constants import (
7
- ACTIVE_FLOW,
24
+ ACTION_EXTRACT_SLOTS,
25
+ ACTION_VALIDATE_SLOT_MAPPINGS,
8
26
  ACTIVE_LOOP,
27
+ KEY_ACTION,
28
+ KEY_COEXISTENCE_SYSTEM,
29
+ KEY_MAPPING_TYPE,
30
+ KEY_RUN_ACTION_EVERY_TURN,
9
31
  MAPPING_CONDITIONS,
10
- MAPPING_TYPE,
11
32
  REQUESTED_SLOT,
12
- SLOT_MAPPINGS,
13
33
  SlotMappingType,
14
34
  )
35
+ from rasa.shared.core.events import BotUttered, Event, SlotSet
15
36
  from rasa.shared.core.slots import ListSlot, Slot
37
+ from rasa.shared.exceptions import RasaException
16
38
  from rasa.shared.nlu.constants import (
17
39
  ENTITIES,
18
40
  ENTITY_ATTRIBUTE_GROUP,
@@ -21,25 +43,108 @@ from rasa.shared.nlu.constants import (
21
43
  ENTITY_ATTRIBUTE_VALUE,
22
44
  INTENT,
23
45
  INTENT_NAME_KEY,
24
- NOT_INTENT,
25
46
  TEXT,
26
47
  )
27
48
 
28
49
  if TYPE_CHECKING:
50
+ from rasa.core.channels.channel import OutputChannel
51
+ from rasa.core.nlg import NaturalLanguageGenerator
29
52
  from rasa.shared.core.domain import Domain
30
53
  from rasa.shared.core.trackers import DialogueStateTracker
31
54
  from rasa.shared.nlu.training_data.message import Message
32
55
  from rasa.utils.endpoints import EndpointConfig
33
56
 
57
+ structlogger = structlog.get_logger()
58
+
59
+
60
+ class SlotMappingCondition(BaseModel):
61
+ """Defines a condition for a slot mapping."""
62
+
63
+ active_loop: Optional[str]
64
+ requested_slot: Optional[str] = None
65
+ active_flow: Optional[str] = None
66
+
67
+ @staticmethod
68
+ def from_dict(data: Dict[str, Any]) -> SlotMappingCondition:
69
+ # we allow None as a valid value for active_loop
70
+ # therefore we need to set a different default value
71
+ active_loop = data.pop(ACTIVE_LOOP, "")
34
72
 
35
- logger = logging.getLogger(__name__)
73
+ return SlotMappingCondition(active_loop=active_loop, **data)
36
74
 
75
+ def as_dict(self) -> Dict[str, Any]:
76
+ return self.model_dump(exclude_none=True)
37
77
 
38
- class SlotMapping:
78
+
79
+ class CoexistenceSystemType(Enum):
80
+ NLU = "NLU"
81
+ CALM = "CALM"
82
+ SHARED = "SHARED"
83
+
84
+
85
+ class SlotMapping(BaseModel):
39
86
  """Defines functionality for the available slot mappings."""
40
87
 
88
+ type: SlotMappingType
89
+ conditions: List[SlotMappingCondition] = Field(default_factory=list)
90
+ entity: Optional[str] = None
91
+ intent: Optional[Union[str, List[str]]] = None
92
+ role: Optional[str] = None
93
+ group: Optional[str] = None
94
+ not_intent: Optional[Union[str, List[str]]] = None
95
+ value: Optional[Any] = None
96
+ allow_nlu_correction: Optional[bool] = None
97
+ run_action_every_turn: Optional[str] = None
98
+ coexistence_system: Optional[CoexistenceSystemType] = None
99
+
100
+ @staticmethod
101
+ def from_dict(data: Dict[str, Any], slot_name: str) -> SlotMapping:
102
+ data_copy = copy.deepcopy(data)
103
+ mapping_type = SlotMapping.validate_mapping(data_copy, slot_name)
104
+ conditions = [
105
+ SlotMappingCondition.from_dict(condition)
106
+ for condition in data_copy.pop(MAPPING_CONDITIONS, [])
107
+ ]
108
+
109
+ deprecated_action = data_copy.pop(KEY_ACTION, None)
110
+ if deprecated_action:
111
+ rasa.shared.utils.io.raise_deprecation_warning(
112
+ f"The `{KEY_ACTION}` key in slot mappings is deprecated and "
113
+ f"will be removed in Rasa Pro 4.0.0. "
114
+ f"Please use the `{KEY_RUN_ACTION_EVERY_TURN}` key instead.",
115
+ )
116
+ data_copy[KEY_RUN_ACTION_EVERY_TURN] = deprecated_action
117
+
118
+ run_action_every_turn = data_copy.pop(KEY_RUN_ACTION_EVERY_TURN, None)
119
+
120
+ coexistence_system = data_copy.pop(KEY_COEXISTENCE_SYSTEM, None)
121
+ coexistence_system_type = (
122
+ CoexistenceSystemType(coexistence_system) if coexistence_system else None
123
+ )
124
+
125
+ return SlotMapping(
126
+ type=mapping_type,
127
+ conditions=conditions,
128
+ run_action_every_turn=run_action_every_turn,
129
+ coexistence_system=coexistence_system_type,
130
+ **data_copy,
131
+ )
132
+
133
+ def as_dict(self) -> Dict[str, Any]:
134
+ data = self.model_dump(mode="json", exclude_none=True)
135
+ data[KEY_MAPPING_TYPE] = self.type.value
136
+
137
+ if self.conditions:
138
+ data[MAPPING_CONDITIONS] = [
139
+ condition.as_dict() for condition in self.conditions
140
+ ]
141
+ else:
142
+ data.pop(MAPPING_CONDITIONS, None)
143
+
144
+ return data
145
+
41
146
  @staticmethod
42
- def validate(mapping: Dict[Text, Any], slot_name: Text) -> None:
147
+ def validate_mapping(mapping: Dict[str, Any], slot_name: str) -> SlotMappingType:
43
148
  """Validates a slot mapping.
44
149
 
45
150
  Args:
@@ -58,12 +163,22 @@ class SlotMapping:
58
163
  f"{DOCS_URL_NLU_BASED_SLOTS} for more information."
59
164
  )
60
165
 
166
+ mapping_raw = mapping.pop(KEY_MAPPING_TYPE, SlotMappingType.FROM_LLM.value)
167
+
168
+ if mapping_raw == "custom":
169
+ rasa.shared.utils.io.raise_deprecation_warning(
170
+ "The `custom` slot mapping type is deprecated and "
171
+ "will be removed in Rasa Pro 4.0.0. "
172
+ "Please use the `controlled` slot mapping type instead.",
173
+ )
174
+ mapping_raw = "controlled"
175
+
61
176
  try:
62
- mapping_type = SlotMappingType(mapping.get(MAPPING_TYPE))
177
+ mapping_type = SlotMappingType(mapping_raw)
63
178
  except ValueError:
64
179
  raise InvalidDomain(
65
180
  f"Your domain uses an invalid slot mapping of type "
66
- f"'{mapping.get(MAPPING_TYPE)}' for slot '{slot_name}'. Please see "
181
+ f"'{mapping_raw}' for slot '{slot_name}'. Please see "
67
182
  f"{DOCS_URL_NLU_BASED_SLOTS} for more information."
68
183
  )
69
184
 
@@ -72,7 +187,7 @@ class SlotMapping:
72
187
  SlotMappingType.FROM_INTENT: ["value"],
73
188
  SlotMappingType.FROM_TRIGGER_INTENT: ["value"],
74
189
  SlotMappingType.FROM_TEXT: [],
75
- SlotMappingType.CUSTOM: [],
190
+ SlotMappingType.CONTROLLED: [],
76
191
  SlotMappingType.FROM_LLM: [],
77
192
  }
78
193
 
@@ -86,19 +201,18 @@ class SlotMapping:
86
201
  f"{DOCS_URL_NLU_BASED_SLOTS} for more information."
87
202
  )
88
203
 
89
- @staticmethod
204
+ return mapping_type
205
+
90
206
  def _get_active_loop_ignored_intents(
91
- mapping: Dict[Text, Any], domain: "Domain", active_loop_name: Text
207
+ self, domain: "Domain", active_loop_name: Text
92
208
  ) -> List[Text]:
93
- from rasa.shared.core.constants import ACTIVE_LOOP
94
-
95
- mapping_conditions = mapping.get(MAPPING_CONDITIONS)
209
+ mapping_conditions = self.conditions
96
210
  active_loop_match = True
97
211
  ignored_intents = []
98
212
 
99
213
  if mapping_conditions:
100
214
  match_list = [
101
- condition.get(ACTIVE_LOOP) == active_loop_name
215
+ condition.active_loop == active_loop_name
102
216
  for condition in mapping_conditions
103
217
  ]
104
218
  active_loop_match = any(match_list)
@@ -111,24 +225,21 @@ class SlotMapping:
111
225
 
112
226
  return ignored_intents
113
227
 
114
- @staticmethod
115
228
  def intent_is_desired(
116
- mapping: Dict[Text, Any],
229
+ self,
117
230
  tracker: "DialogueStateTracker",
118
231
  domain: "Domain",
119
232
  message: Optional["Message"] = None,
120
233
  ) -> bool:
121
234
  """Checks whether user intent matches slot mapping intent specifications."""
122
- mapping_intents = SlotMapping.to_list(mapping.get(INTENT, []))
123
- mapping_not_intents = SlotMapping.to_list(mapping.get(NOT_INTENT, []))
235
+ mapping_intents = SlotMapping.to_list(self.intent)
236
+ mapping_not_intents = SlotMapping.to_list(self.not_intent)
124
237
 
125
238
  active_loop_name = tracker.active_loop_name
126
239
  if active_loop_name:
127
240
  mapping_not_intents = (
128
241
  mapping_not_intents
129
- + SlotMapping._get_active_loop_ignored_intents(
130
- mapping, domain, active_loop_name
131
- )
242
+ + self._get_active_loop_ignored_intents(domain, active_loop_name)
132
243
  )
133
244
 
134
245
  if message is not None:
@@ -155,16 +266,14 @@ class SlotMapping:
155
266
 
156
267
  return x
157
268
 
158
- @staticmethod
159
269
  def entity_is_desired(
160
- mapping: Dict[Text, Any],
270
+ self,
161
271
  tracker: "DialogueStateTracker",
162
272
  message: Optional["Message"] = None,
163
273
  ) -> List[str]:
164
274
  """Checks whether slot should be filled by an entity in the input or not.
165
275
 
166
276
  Args:
167
- mapping: Slot mapping.
168
277
  tracker: The tracker.
169
278
  message: The message being processed.
170
279
 
@@ -176,19 +285,16 @@ class SlotMapping:
176
285
  matching_values = [
177
286
  cast(Text, entity[ENTITY_ATTRIBUTE_VALUE])
178
287
  for entity in extracted_entities
179
- if entity.get(ENTITY_ATTRIBUTE_TYPE)
180
- == mapping.get(ENTITY_ATTRIBUTE_TYPE)
181
- and entity.get(ENTITY_ATTRIBUTE_GROUP)
182
- == mapping.get(ENTITY_ATTRIBUTE_GROUP)
183
- and entity.get(ENTITY_ATTRIBUTE_ROLE)
184
- == mapping.get(ENTITY_ATTRIBUTE_ROLE)
288
+ if entity.get(ENTITY_ATTRIBUTE_TYPE) == self.entity
289
+ and entity.get(ENTITY_ATTRIBUTE_GROUP) == self.group
290
+ and entity.get(ENTITY_ATTRIBUTE_ROLE) == self.role
185
291
  ]
186
292
  elif tracker.latest_message and tracker.latest_message.text is not None:
187
293
  matching_values = list(
188
294
  tracker.get_latest_entity_values(
189
- mapping.get(ENTITY_ATTRIBUTE_TYPE),
190
- mapping.get(ENTITY_ATTRIBUTE_ROLE),
191
- mapping.get(ENTITY_ATTRIBUTE_GROUP),
295
+ self.entity,
296
+ self.role,
297
+ self.group,
192
298
  )
193
299
  )
194
300
  else:
@@ -196,45 +302,38 @@ class SlotMapping:
196
302
 
197
303
  return matching_values
198
304
 
199
- @staticmethod
200
305
  def check_mapping_validity(
306
+ self,
201
307
  slot_name: Text,
202
- mapping_type: SlotMappingType,
203
- mapping: Dict[Text, Any],
204
308
  domain: "Domain",
205
309
  ) -> bool:
206
310
  """Checks the mapping for validity.
207
311
 
208
312
  Args:
209
313
  slot_name: The name of the slot to be validated.
210
- mapping_type: The type of the slot mapping.
211
- mapping: Slot mapping.
212
314
  domain: The domain to check against.
213
315
 
214
316
  Returns:
215
317
  True, if intent and entity specified in a mapping exist in domain.
216
318
  """
217
319
  if (
218
- mapping_type == SlotMappingType.FROM_ENTITY
219
- and mapping.get(ENTITY_ATTRIBUTE_TYPE) not in domain.entities
320
+ self.type == SlotMappingType.FROM_ENTITY
321
+ and self.entity not in domain.entities
220
322
  ):
221
323
  rasa.shared.utils.io.raise_warning(
222
324
  f"Slot '{slot_name}' uses a 'from_entity' mapping "
223
- f"for a non-existent entity '{mapping.get(ENTITY_ATTRIBUTE_TYPE)}'. "
325
+ f"for a non-existent entity '{self.entity}'. "
224
326
  f"Skipping slot extraction because of invalid mapping."
225
327
  )
226
328
  return False
227
329
 
228
- if (
229
- mapping_type == SlotMappingType.FROM_INTENT
230
- and mapping.get(INTENT) is not None
231
- ):
232
- intent_list = SlotMapping.to_list(mapping.get(INTENT))
330
+ if self.type == SlotMappingType.FROM_INTENT and self.intent is not None:
331
+ intent_list = SlotMapping.to_list(self.intent)
233
332
  for intent in intent_list:
234
333
  if intent and intent not in domain.intents:
235
334
  rasa.shared.utils.io.raise_warning(
236
335
  f"Slot '{slot_name}' uses a 'from_intent' mapping for "
237
- f"a non-existent intent '{mapping.get('intent')}'. "
336
+ f"a non-existent intent '{intent}'. "
238
337
  f"Skipping slot extraction because of invalid mapping."
239
338
  )
240
339
  return False
@@ -242,22 +341,6 @@ class SlotMapping:
242
341
  return True
243
342
 
244
343
 
245
- def validate_slot_mappings(domain_slots: Dict[Text, Any]) -> None:
246
- """Raises InvalidDomain exception if slot mappings are invalid."""
247
- rasa.shared.utils.io.raise_warning(
248
- f"Slot auto-fill has been removed in 3.0 and replaced with a "
249
- f"new explicit mechanism to set slots. "
250
- f"Please refer to {DOCS_URL_NLU_BASED_SLOTS} to learn more.",
251
- UserWarning,
252
- )
253
-
254
- for slot_name, properties in domain_slots.items():
255
- mappings = properties.get(SLOT_MAPPINGS, [])
256
-
257
- for slot_mapping in mappings:
258
- SlotMapping.validate(slot_mapping, slot_name)
259
-
260
-
261
344
  class SlotFillingManager:
262
345
  """Manages slot filling based on conversation context."""
263
346
 
@@ -272,45 +355,39 @@ class SlotFillingManager:
272
355
  self.tracker = tracker
273
356
  self.message = message
274
357
  self._action_endpoint = action_endpoint
358
+ self.executed_custom_actions: Set[str] = set()
275
359
 
276
360
  def is_slot_mapping_valid(
277
361
  self,
278
362
  slot_name: str,
279
- mapping_type: SlotMappingType,
280
- mapping: Dict[str, Any],
363
+ mapping: SlotMapping,
281
364
  ) -> bool:
282
365
  """Check if a slot mapping is valid."""
283
- return SlotMapping.check_mapping_validity(
366
+ return mapping.check_mapping_validity(
284
367
  slot_name=slot_name,
285
- mapping_type=mapping_type,
286
- mapping=mapping,
287
368
  domain=self.domain,
288
369
  )
289
370
 
290
- def is_intent_desired(self, mapping: Dict[str, Any]) -> bool:
371
+ def is_intent_desired(self, mapping: SlotMapping) -> bool:
291
372
  """Check if the intent matches the one indicated in the slot mapping."""
292
- return SlotMapping.intent_is_desired(
293
- mapping=mapping,
373
+ return mapping.intent_is_desired(
294
374
  tracker=self.tracker,
295
375
  domain=self.domain,
296
376
  message=self.message,
297
377
  )
298
378
 
299
- def _verify_mapping_conditions(
300
- self, mapping: Dict[Text, Any], slot_name: Text
301
- ) -> bool:
302
- if mapping.get(MAPPING_CONDITIONS) and mapping[MAPPING_TYPE] != str(
379
+ def _verify_mapping_conditions(self, mapping: SlotMapping, slot_name: Text) -> bool:
380
+ if mapping.conditions and mapping.type != str(
303
381
  SlotMappingType.FROM_TRIGGER_INTENT
304
382
  ):
305
- if not self._matches_mapping_conditions(mapping, slot_name):
306
- return False
383
+ return self._matches_mapping_conditions(mapping, slot_name)
307
384
 
308
385
  return True
309
386
 
310
387
  def _matches_mapping_conditions(
311
- self, mapping: Dict[Text, Any], slot_name: Text
388
+ self, mapping: SlotMapping, slot_name: Text
312
389
  ) -> bool:
313
- slot_mapping_conditions = mapping.get(MAPPING_CONDITIONS)
390
+ slot_mapping_conditions = mapping.conditions
314
391
 
315
392
  if not slot_mapping_conditions:
316
393
  return True
@@ -328,20 +405,20 @@ class SlotFillingManager:
328
405
  @staticmethod
329
406
  def _mapping_conditions_match_flow(
330
407
  active_flow: str,
331
- slot_mapping_conditions: List[Dict[str, str]],
408
+ slot_mapping_conditions: List[SlotMappingCondition],
332
409
  ) -> bool:
333
410
  active_flow_conditions = list(
334
- filter(lambda x: x.get(ACTIVE_FLOW) is not None, slot_mapping_conditions)
411
+ filter(lambda x: x.active_flow is not None, slot_mapping_conditions)
335
412
  )
336
413
  return any(
337
414
  [
338
- condition.get(ACTIVE_FLOW) == active_flow
415
+ condition.active_flow == active_flow
339
416
  for condition in active_flow_conditions
340
417
  ]
341
418
  )
342
419
 
343
420
  def _mapping_conditions_match_form(
344
- self, slot_name: str, slot_mapping_conditions: List[Dict[str, str]]
421
+ self, slot_name: str, slot_mapping_conditions: List[SlotMappingCondition]
345
422
  ) -> bool:
346
423
  if (
347
424
  self.tracker.is_active_loop_rejected
@@ -351,12 +428,10 @@ class SlotFillingManager:
351
428
 
352
429
  # check if found mapping conditions matches form
353
430
  for condition in slot_mapping_conditions:
354
- # we allow None as a valid value for active_loop
355
- # therefore we need to set a different default value
356
- active_loop = condition.get(ACTIVE_LOOP, "")
431
+ active_loop = condition.active_loop
357
432
 
358
433
  if active_loop and active_loop == self.tracker.active_loop_name:
359
- condition_requested_slot = condition.get(REQUESTED_SLOT)
434
+ condition_requested_slot = condition.requested_slot
360
435
  if not condition_requested_slot:
361
436
  return True
362
437
  if condition_requested_slot == self.tracker.get_slot(REQUESTED_SLOT):
@@ -370,11 +445,11 @@ class SlotFillingManager:
370
445
  def _fails_unique_entity_mapping_check(
371
446
  self,
372
447
  slot_name: Text,
373
- mapping: Dict[Text, Any],
448
+ mapping: SlotMapping,
374
449
  ) -> bool:
375
450
  from rasa.core.actions.forms import FormAction
376
451
 
377
- if mapping[MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY):
452
+ if mapping.type != SlotMappingType.FROM_ENTITY:
378
453
  return False
379
454
 
380
455
  form_name = self.tracker.active_loop_name
@@ -395,12 +470,9 @@ class SlotFillingManager:
395
470
 
396
471
  return True
397
472
 
398
- def _is_trigger_intent_mapping_condition_met(
399
- self, mapping: Dict[Text, Any]
400
- ) -> bool:
473
+ def _is_trigger_intent_mapping_condition_met(self, mapping: SlotMapping) -> bool:
401
474
  active_loops_in_mapping_conditions = [
402
- condition.get(ACTIVE_LOOP)
403
- for condition in mapping.get(MAPPING_CONDITIONS, [])
475
+ condition.active_loop for condition in mapping.conditions
404
476
  ]
405
477
 
406
478
  trigger_mapping_condition_met = True
@@ -421,7 +493,7 @@ class SlotFillingManager:
421
493
  def extract_slot_value_from_predefined_mapping(
422
494
  self,
423
495
  mapping_type: SlotMappingType,
424
- mapping: Dict[Text, Any],
496
+ mapping: SlotMapping,
425
497
  ) -> List[Any]:
426
498
  """Extracts slot value if slot has an applicable predefined mapping."""
427
499
  if (
@@ -454,9 +526,9 @@ class SlotFillingManager:
454
526
  value: List[Any] = []
455
527
 
456
528
  if should_fill_entity_slot:
457
- value = SlotMapping.entity_is_desired(mapping, self.tracker, self.message)
529
+ value = mapping.entity_is_desired(self.tracker, self.message)
458
530
  elif should_fill_intent_slot or should_fill_trigger_slot:
459
- value = [mapping.get("value")]
531
+ value = [mapping.value]
460
532
  elif should_fill_text_slot:
461
533
  value = [self.message.get(TEXT)] if self.message is not None else []
462
534
  if not value:
@@ -468,11 +540,9 @@ class SlotFillingManager:
468
540
 
469
541
  return value
470
542
 
471
- def should_fill_slot(
472
- self, slot_name: str, mapping_type: SlotMappingType, mapping: Dict[Text, Any]
473
- ) -> bool:
543
+ def should_fill_slot(self, slot_name: str, mapping: SlotMapping) -> bool:
474
544
  """Checks if a slot should be filled based on the conversation context."""
475
- if not self.is_slot_mapping_valid(slot_name, mapping_type, mapping):
545
+ if not self.is_slot_mapping_valid(slot_name, mapping):
476
546
  return False
477
547
 
478
548
  if not self.is_intent_desired(mapping):
@@ -486,6 +556,173 @@ class SlotFillingManager:
486
556
 
487
557
  return True
488
558
 
559
+ @staticmethod
560
+ def should_fill_slot_in_coexistence(
561
+ is_calm_system: bool,
562
+ slot: Slot,
563
+ calm_slot_names: Set[str],
564
+ ) -> bool:
565
+ """Check if a slot should be filled in a coexistence assistant."""
566
+ if slot.shared_for_coexistence:
567
+ return True
568
+
569
+ coexistence_systems = [
570
+ mapping.coexistence_system
571
+ for mapping in slot.mappings
572
+ if mapping.type == SlotMappingType.CONTROLLED
573
+ ]
574
+
575
+ if not is_calm_system and (
576
+ slot.name in calm_slot_names
577
+ or CoexistenceSystemType.CALM in coexistence_systems
578
+ ):
579
+ return False
580
+
581
+ if is_calm_system and (
582
+ slot.name not in calm_slot_names
583
+ or CoexistenceSystemType.NLU in coexistence_systems
584
+ ):
585
+ return False
586
+
587
+ return True
588
+
589
+ async def run_action_at_every_turn(
590
+ self,
591
+ slot: Slot,
592
+ output_channel: "OutputChannel",
593
+ nlg: "NaturalLanguageGenerator",
594
+ ) -> List[Event]:
595
+ """Runs a custom action at every turn to fill a slot.
596
+
597
+ This executes only if the slot has a controlled mapping type
598
+ with the `run_action_every_turn` key set.
599
+ """
600
+ slot_events: List[Event] = []
601
+ for mapping in slot.mappings:
602
+ should_fill_controlled_slot = mapping.type == SlotMappingType.CONTROLLED
603
+
604
+ if not should_fill_controlled_slot:
605
+ continue
606
+
607
+ custom_events = await self._execute_custom_action(
608
+ mapping,
609
+ output_channel,
610
+ nlg,
611
+ )
612
+ slot_events.extend(custom_events)
613
+
614
+ return slot_events
615
+
616
+ async def _execute_custom_action(
617
+ self,
618
+ mapping: "SlotMapping",
619
+ output_channel: "OutputChannel",
620
+ nlg: "NaturalLanguageGenerator",
621
+ ) -> List[Event]:
622
+ custom_action = mapping.run_action_every_turn
623
+
624
+ if not custom_action or custom_action in self.executed_custom_actions:
625
+ return []
626
+
627
+ slot_events = await self._run_custom_action(custom_action, output_channel, nlg)
628
+
629
+ self.executed_custom_actions.add(custom_action)
630
+
631
+ return slot_events
632
+
633
+ async def _run_custom_action(
634
+ self,
635
+ custom_action: str,
636
+ output_channel: "OutputChannel",
637
+ nlg: "NaturalLanguageGenerator",
638
+ recreate_tracker: bool = False,
639
+ ) -> List[Event]:
640
+ from rasa.core.actions.action import RemoteAction
641
+ from rasa.shared.core.trackers import DialogueStateTracker
642
+ from rasa.utils.endpoints import ClientResponseError
643
+
644
+ slot_events: List[Event] = []
645
+ remote_action = RemoteAction(custom_action, self._action_endpoint)
646
+ disallowed_types = set()
647
+
648
+ tracker = (
649
+ DialogueStateTracker.from_events(
650
+ self.tracker.sender_id,
651
+ self.tracker.events_after_latest_restart() + slot_events,
652
+ slots=self.domain.slots,
653
+ )
654
+ if recreate_tracker
655
+ else self.tracker
656
+ )
657
+
658
+ try:
659
+ custom_events = await remote_action.run(
660
+ output_channel, nlg, tracker, self.domain
661
+ )
662
+ for event in custom_events:
663
+ if isinstance(event, SlotSet):
664
+ slot_events.append(event)
665
+ elif isinstance(event, BotUttered):
666
+ slot_events.append(event)
667
+ else:
668
+ disallowed_types.add(event.type_name)
669
+ except (RasaException, ClientResponseError) as e:
670
+ structlogger.warning(
671
+ "slot_filling_manager.run_custom_action_failed",
672
+ failed_custom_action=custom_action,
673
+ event_info=f"Failed to execute custom action '{custom_action}' "
674
+ f"as a result of error '{e!s}'. The default action "
675
+ f"'{ACTION_EXTRACT_SLOTS}' failed to fill slots with custom "
676
+ f"mappings.",
677
+ )
678
+
679
+ for type_name in disallowed_types:
680
+ structlogger.info(
681
+ "slot_filling_manager.run_custom_action_disallowed_event",
682
+ custom_action_name=custom_action,
683
+ disallowed_evet_type=type_name,
684
+ event_info=f"Running custom action '{custom_action}' has resulted "
685
+ f"in an event of type '{type_name}'. This is "
686
+ f"disallowed and the tracker will not be "
687
+ f"updated with this event.",
688
+ )
689
+
690
+ return slot_events
691
+
692
+ async def execute_validation_action(
693
+ self,
694
+ extraction_events: List[Event],
695
+ output_channel: "OutputChannel",
696
+ nlg: "NaturalLanguageGenerator",
697
+ ) -> List[Event]:
698
+ slot_events: List[SlotSet] = [
699
+ event for event in extraction_events if isinstance(event, SlotSet)
700
+ ]
701
+
702
+ slot_candidates = "\n".join([e.key for e in slot_events])
703
+ structlogger.debug(
704
+ "slot_filling_manager.execute_validation_action",
705
+ slot_candidates=slot_candidates,
706
+ event_info=f"Validating extracted slots: {slot_candidates}",
707
+ )
708
+
709
+ if ACTION_VALIDATE_SLOT_MAPPINGS not in self.domain.user_actions:
710
+ return cast(List[Event], slot_events)
711
+
712
+ validate_events = await self._run_custom_action(
713
+ ACTION_VALIDATE_SLOT_MAPPINGS, output_channel, nlg, recreate_tracker=True
714
+ )
715
+ validated_slot_names = [
716
+ event.key for event in validate_events if isinstance(event, SlotSet)
717
+ ]
718
+
719
+ # If the custom action doesn't return a SlotSet event for an extracted slot
720
+ # candidate we assume that it was valid. The custom action has to return a
721
+ # SlotSet(slot_name, None) event to mark a Slot as invalid.
722
+ return validate_events + [
723
+ event for event in slot_events if event.key not in validated_slot_names
724
+ ]
725
+
489
726
 
490
727
  def extract_slot_value(
491
728
  slot: Slot, slot_filling_manager: SlotFillingManager
@@ -494,14 +731,12 @@ def extract_slot_value(
494
731
  is_extracted = False
495
732
 
496
733
  for mapping in slot.mappings:
497
- mapping_type = SlotMappingType(
498
- mapping.get(MAPPING_TYPE, SlotMappingType.FROM_LLM.value)
499
- )
734
+ mapping_type = mapping.type
500
735
 
501
- if mapping_type in [SlotMappingType.FROM_LLM, SlotMappingType.CUSTOM]:
736
+ if mapping_type in [SlotMappingType.FROM_LLM, SlotMappingType.CONTROLLED]:
502
737
  continue
503
738
 
504
- if not slot_filling_manager.should_fill_slot(slot.name, mapping_type, mapping):
739
+ if not slot_filling_manager.should_fill_slot(slot.name, mapping):
505
740
  continue
506
741
 
507
742
  value: List[Any] = (
@@ -518,7 +753,11 @@ def extract_slot_value(
518
753
  value is not None
519
754
  or slot_filling_manager.tracker.get_slot(slot.name) is not None
520
755
  ):
521
- logger.debug(f"Extracted value '{value}' for slot '{slot.name}'.")
756
+ structlogger.debug(
757
+ "slot_filling_manager.extract_slot_value",
758
+ slot_name=slot.name,
759
+ event_info=f"Extracted value '{value}' for slot '{slot.name}'.",
760
+ )
522
761
 
523
762
  is_extracted = True
524
763
  return value, is_extracted