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
rasa/validator.py CHANGED
@@ -16,9 +16,12 @@ import rasa.shared.utils.cli
16
16
  import rasa.shared.utils.io
17
17
  from rasa.core.channels import UserMessage
18
18
  from rasa.dialogue_understanding.stack.frames import PatternFlowStackFrame
19
+ from rasa.engine.language import Language
19
20
  from rasa.shared.constants import (
20
21
  ASSISTANT_ID_DEFAULT_VALUE,
21
22
  ASSISTANT_ID_KEY,
23
+ CONFIG_ADDITIONAL_LANGUAGES_KEY,
24
+ CONFIG_LANGUAGE_KEY,
22
25
  CONFIG_MANDATORY_KEYS,
23
26
  CONFIG_PIPELINE_KEY,
24
27
  DOCS_URL_ACTIONS,
@@ -27,6 +30,7 @@ from rasa.shared.constants import (
27
30
  DOCS_URL_FORMS,
28
31
  DOCS_URL_RESPONSES,
29
32
  REQUIRED_SLOTS_KEY,
33
+ RESPONSE_CONDITION,
30
34
  UTTER_PREFIX,
31
35
  )
32
36
  from rasa.shared.core import constants
@@ -35,9 +39,8 @@ from rasa.shared.core.command_payload_reader import (
35
39
  CommandPayloadReader,
36
40
  )
37
41
  from rasa.shared.core.constants import (
38
- ACTIVE_LOOP,
39
- MAPPING_CONDITIONS,
40
- MAPPING_TYPE,
42
+ KEY_ALLOW_NLU_CORRECTION,
43
+ SLOTS,
41
44
  SlotMappingType,
42
45
  )
43
46
  from rasa.shared.core.domain import (
@@ -45,24 +48,28 @@ from rasa.shared.core.domain import (
45
48
  Domain,
46
49
  )
47
50
  from rasa.shared.core.events import ActionExecuted, ActiveLoop, UserUttered
48
- from rasa.shared.core.flows import FlowsList
51
+ from rasa.shared.core.flows import Flow, FlowsList
52
+ from rasa.shared.core.flows.constants import KEY_NAME, KEY_TRANSLATION
49
53
  from rasa.shared.core.flows.flow_step_links import IfFlowStepLink
50
54
  from rasa.shared.core.flows.steps.action import ActionFlowStep
51
55
  from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
52
56
  from rasa.shared.core.flows.steps.link import LinkFlowStep
53
57
  from rasa.shared.core.flows.steps.set_slots import SetSlotsFlowStep
54
58
  from rasa.shared.core.flows.utils import (
59
+ ALL_LABEL,
55
60
  get_duplicate_slot_persistence_config_error_message,
56
61
  get_invalid_slot_persistence_config_error_message,
57
62
  warn_deprecated_collect_step_config,
58
63
  )
59
64
  from rasa.shared.core.generator import TrainingDataGenerator
65
+ from rasa.shared.core.slot_mappings import CoexistenceSystemType
60
66
  from rasa.shared.core.slots import BooleanSlot, CategoricalSlot, ListSlot, Slot
61
67
  from rasa.shared.core.training_data.story_reader.yaml_story_reader import (
62
68
  YAMLStoryReader,
63
69
  )
64
70
  from rasa.shared.core.training_data.structures import StoryGraph
65
71
  from rasa.shared.data import create_regex_pattern_reader
72
+ from rasa.shared.exceptions import RasaException
66
73
  from rasa.shared.importers.importer import TrainingDataImporter
67
74
  from rasa.shared.nlu.constants import COMMANDS
68
75
  from rasa.shared.nlu.training_data.message import Message
@@ -518,9 +525,9 @@ class Validator:
518
525
 
519
526
  for slot in self.domain.slots:
520
527
  for mapping in slot.mappings:
521
- for condition in mapping.get(MAPPING_CONDITIONS, []):
522
- condition_active_loop = condition.get(ACTIVE_LOOP)
523
- mapping_type = SlotMappingType(mapping.get(MAPPING_TYPE))
528
+ for condition in mapping.conditions:
529
+ condition_active_loop = condition.active_loop
530
+ mapping_type = mapping.type
524
531
  if (
525
532
  condition_active_loop
526
533
  and condition_active_loop not in self.domain.form_names
@@ -908,6 +915,16 @@ class Validator:
908
915
  f"'{object_id}': {exception}"
909
916
  ),
910
917
  )
918
+ elif object_id.startswith("utter_"):
919
+ structlogger.error(
920
+ "validator.validate_conditional_response_variation_predicates.error",
921
+ utter=object_id,
922
+ exception=exception,
923
+ event_info=(
924
+ f"Could not initialize the predicate found under response "
925
+ f"variation '{object_id}': {exception}"
926
+ ),
927
+ )
911
928
  else:
912
929
  structlogger.error(
913
930
  "validator.verify_predicates.flow_guard_predicate.error",
@@ -939,7 +956,7 @@ class Validator:
939
956
  def _extract_slot_name_and_slot_value(
940
957
  self,
941
958
  predicate_syntax_tree: Any,
942
- ) -> tuple:
959
+ ) -> Tuple[Optional[List[str]], Optional[Any]]:
943
960
  """Extract the slot name and slot value from the predicate syntax tree.
944
961
 
945
962
  Args:
@@ -1044,15 +1061,15 @@ class Validator:
1044
1061
  False, if validation failed, previous value of all_good, otherwise
1045
1062
  """
1046
1063
  predicate_syntax_tree = self._extract_predicate_syntax_tree(predicate)
1047
- slot_name, slot_value = self._extract_slot_name_and_slot_value(
1064
+ slot_namespace, slot_value = self._extract_slot_name_and_slot_value(
1048
1065
  predicate_syntax_tree
1049
1066
  )
1050
1067
 
1051
- if slot_name is None:
1068
+ if slot_namespace is None:
1052
1069
  return all_good
1053
1070
 
1054
- if slot_name[0] == "slots":
1055
- slot_name = slot_name[1]
1071
+ if slot_namespace[0] == "slots":
1072
+ slot_name = slot_namespace[1]
1056
1073
  # slots.{{context.variable}} gets evaluated to `slots.None`,
1057
1074
  # these predicates can only be validated during runtime
1058
1075
  if slot_name == "None":
@@ -1264,6 +1281,7 @@ class Validator:
1264
1281
  self.verify_unique_flows(),
1265
1282
  self.verify_predicates(),
1266
1283
  self.verify_slot_persistence_configuration(),
1284
+ self.verify_digression_configuration(),
1267
1285
  ]
1268
1286
 
1269
1287
  all_good = all(flow_validation_conditions)
@@ -1272,6 +1290,165 @@ class Validator:
1272
1290
 
1273
1291
  return all_good
1274
1292
 
1293
+ def _get_response_translation_warnings(self) -> list:
1294
+ """Collect warnings for responses missing translations.
1295
+
1296
+ Returns:
1297
+ List of warnings for responses missing translations.
1298
+ """
1299
+ additional_languages = self.config.get(CONFIG_ADDITIONAL_LANGUAGES_KEY) or []
1300
+ response_warnings = []
1301
+
1302
+ for response_name, responses in self.domain.responses.items():
1303
+ provided_languages = set()
1304
+ # For each response variation, we check if the additional
1305
+ # languages are available in at least on variation
1306
+ for response in responses:
1307
+ translation = response.get(KEY_TRANSLATION) or {}
1308
+ for language_code in additional_languages:
1309
+ if translation.get(language_code):
1310
+ provided_languages.add(language_code)
1311
+
1312
+ missing_languages = [
1313
+ lang for lang in additional_languages if lang not in provided_languages
1314
+ ]
1315
+ if missing_languages:
1316
+ language_code_str = ", ".join(missing_languages)
1317
+ response_warnings.append(
1318
+ {
1319
+ "event": (
1320
+ "validator.verify_translations.missing_response_translation"
1321
+ ),
1322
+ "response": response_name,
1323
+ "missing_languages": missing_languages,
1324
+ "event_info": (
1325
+ f"The response '{response_name}' is "
1326
+ f"missing a translation for the following "
1327
+ f"languages: {language_code_str}."
1328
+ ),
1329
+ }
1330
+ )
1331
+ return response_warnings
1332
+
1333
+ def _get_flow_translation_warnings(self) -> list:
1334
+ """Collect warnings for flows missing translations.
1335
+
1336
+ Returns:
1337
+ List of warnings for flows missing translations.
1338
+ """
1339
+ additional_languages = self.config.get(CONFIG_ADDITIONAL_LANGUAGES_KEY) or []
1340
+
1341
+ flow_warnings = []
1342
+ for flow in self.flows.underlying_flows:
1343
+ required_field_translation = [KEY_NAME]
1344
+ missing_languages = []
1345
+ for language_code in additional_languages:
1346
+ translation = flow.translation.get(language_code)
1347
+ # If translation for the language code doesn't exist,
1348
+ # or the required fields are not set properly,
1349
+ # we add the language code to the list.
1350
+ if not translation or not all(
1351
+ getattr(translation, field, None)
1352
+ for field in required_field_translation
1353
+ ):
1354
+ missing_languages.append(language_code)
1355
+
1356
+ if missing_languages:
1357
+ language_code_str = ", ".join(missing_languages)
1358
+ flow_warnings.append(
1359
+ {
1360
+ "event": (
1361
+ "validator.verify_translations.missing_flow_translation"
1362
+ ),
1363
+ "flow": flow.id,
1364
+ "missing_languages": missing_languages,
1365
+ "event_info": (
1366
+ f"The flow '{flow.id}' is missing the translation for "
1367
+ f"the following languages: {language_code_str}."
1368
+ ),
1369
+ }
1370
+ )
1371
+ return flow_warnings
1372
+
1373
+ def verify_config_language(self) -> bool:
1374
+ """Verify that config languages are properly set up.
1375
+
1376
+ Returns:
1377
+ `True` if all languages are properly set up, `False` otherwise.
1378
+
1379
+ Raises:
1380
+ RasaException: If the default language is listed as an
1381
+ additional language or if the language code is invalid.
1382
+ """
1383
+ language = self.config.get(CONFIG_LANGUAGE_KEY)
1384
+ additional_languages = self.config.get(CONFIG_ADDITIONAL_LANGUAGES_KEY, [])
1385
+
1386
+ # Check if the default language is in the additional languages.
1387
+ if language in additional_languages:
1388
+ raise RasaException(
1389
+ f"The default language '{language}' is listed as an additional "
1390
+ f"language in the configuration file. Please remove it from "
1391
+ f"the list of additional languages."
1392
+ )
1393
+
1394
+ # Verify the language codes by initializing the Language class.
1395
+ for language_code in [language] + additional_languages:
1396
+ Language.from_language_code(language_code=language_code)
1397
+
1398
+ return True
1399
+
1400
+ def verify_translations(self, summary_mode: bool = False) -> bool:
1401
+ """Checks for inconsistencies in translations.
1402
+
1403
+ Args:
1404
+ summary_mode: If True, logs a single aggregated warning per category;
1405
+ otherwise, logs each warning individually.
1406
+
1407
+ Returns:
1408
+ `True` if no inconsistencies were found, `False` otherwise.
1409
+
1410
+ Raises:
1411
+ Warning: Single warning per response or flow missing translations
1412
+ if `summary_mode` is `True`, otherwise one warning per missing translation.
1413
+ """
1414
+ all_good = self.verify_config_language()
1415
+
1416
+ additional_languages = self.config.get(CONFIG_ADDITIONAL_LANGUAGES_KEY, [])
1417
+ if not additional_languages:
1418
+ return all_good
1419
+
1420
+ response_warnings = self._get_response_translation_warnings()
1421
+ flow_warnings = self._get_flow_translation_warnings()
1422
+
1423
+ if summary_mode:
1424
+ if response_warnings:
1425
+ count = len(response_warnings)
1426
+ structlogger.warn(
1427
+ "validator.verify_translations.missing_response_translation_summary",
1428
+ count=count,
1429
+ event_info=(
1430
+ f"{count} response{' is' if count == 1 else 's are'} "
1431
+ f"missing translations for some languages. "
1432
+ "Run 'rasa data validate language' for details."
1433
+ ),
1434
+ )
1435
+ if flow_warnings:
1436
+ count = len(flow_warnings)
1437
+ structlogger.warn(
1438
+ "validator.verify_translations.missing_flow_translation_summary",
1439
+ count=count,
1440
+ event_info=(
1441
+ f"{count} flow{' is' if count == 1 else 's are'} "
1442
+ f"missing translations for some languages. "
1443
+ "Run 'rasa data validate language' for details."
1444
+ ),
1445
+ )
1446
+ else:
1447
+ for warning in response_warnings + flow_warnings:
1448
+ structlogger.warn(**warning)
1449
+
1450
+ return all_good
1451
+
1275
1452
  def validate_button_payloads(self) -> bool:
1276
1453
  """Check if the response button payloads are valid."""
1277
1454
  all_good = True
@@ -1392,36 +1569,26 @@ class Validator:
1392
1569
 
1393
1570
  for slot in self.domain._user_slots:
1394
1571
  nlu_mappings = any(
1395
- [
1396
- SlotMappingType(
1397
- mapping.get("type", SlotMappingType.FROM_LLM.value)
1398
- ).is_predefined_type()
1399
- for mapping in slot.mappings
1400
- ]
1572
+ [mapping.type.is_predefined_type() for mapping in slot.mappings]
1401
1573
  )
1402
1574
  llm_mappings = any(
1403
- [
1404
- SlotMappingType(mapping.get("type", SlotMappingType.FROM_LLM.value))
1405
- == SlotMappingType.FROM_LLM
1406
- for mapping in slot.mappings
1407
- ]
1575
+ [mapping.type == SlotMappingType.FROM_LLM for mapping in slot.mappings]
1408
1576
  )
1409
- custom_mappings = any(
1577
+ controlled_mappings = any(
1410
1578
  [
1411
- SlotMappingType(mapping.get("type", SlotMappingType.FROM_LLM.value))
1412
- == SlotMappingType.CUSTOM
1579
+ mapping.type == SlotMappingType.CONTROLLED
1413
1580
  for mapping in slot.mappings
1414
1581
  ]
1415
1582
  )
1416
1583
 
1417
- all_good = self._slot_contains_all_mappings_types(
1418
- llm_mappings, nlu_mappings, custom_mappings, slot.name, all_good
1419
- )
1584
+ all_good = self._allow_nlu_correction_is_valid(slot, nlu_mappings, all_good)
1420
1585
 
1421
1586
  all_good = self._custom_action_name_is_defined_in_the_domain(
1422
- custom_mappings, slot, all_good
1587
+ controlled_mappings, slot, all_good
1423
1588
  )
1424
1589
 
1590
+ all_good = self._validate_controlled_mappings(slot, all_good)
1591
+
1425
1592
  all_good = self._config_contains_nlu_command_adapter(
1426
1593
  nlu_mappings, slot.name, all_good
1427
1594
  )
@@ -1433,23 +1600,45 @@ class Validator:
1433
1600
  return all_good
1434
1601
 
1435
1602
  @staticmethod
1436
- def _slot_contains_all_mappings_types(
1437
- llm_mappings: bool,
1438
- nlu_mappings: bool,
1439
- custom_mappings: bool,
1440
- slot_name: str,
1441
- all_good: bool,
1603
+ def _allow_nlu_correction_is_valid(
1604
+ slot: Slot, nlu_mappings: bool, all_good: bool
1442
1605
  ) -> bool:
1443
- if llm_mappings and (nlu_mappings or custom_mappings):
1606
+ """Verify that `allow_nlu_correction` property is used correctly in a `from_llm` mappings only.""" # noqa: E501
1607
+ if not slot.mappings:
1608
+ return all_good
1609
+
1610
+ invalid_usage = False
1611
+
1612
+ for mapping in slot.mappings:
1613
+ allow_nlu_correction = mapping.allow_nlu_correction
1614
+ if allow_nlu_correction and mapping.type != SlotMappingType.FROM_LLM:
1615
+ invalid_usage = True
1616
+
1617
+ if allow_nlu_correction and not nlu_mappings:
1618
+ structlogger.error(
1619
+ "validator.validate_slot_mappings_in_CALM.nlu_mappings_not_present",
1620
+ slot_name=slot.name,
1621
+ event_info=(
1622
+ f"The slot '{slot.name}' does not have any "
1623
+ f"NLU-based slot mappings. "
1624
+ f"The property `allow_nlu_correction` is only "
1625
+ f"applicable when the slot "
1626
+ f"contains both NLU-based and LLM-based slot mappings."
1627
+ ),
1628
+ )
1629
+ all_good = False
1630
+
1631
+ if invalid_usage:
1444
1632
  structlogger.error(
1445
- "validator.validate_slot_mappings_in_CALM.llm_and_nlu_mappings",
1446
- slot_name=slot_name,
1633
+ "validator.validate_slot_mappings_in_CALM.allow_nlu_correction",
1634
+ slot_name=slot.name,
1447
1635
  event_info=(
1448
- f"The slot '{slot_name}' has both LLM and "
1449
- f"NLU or custom slot mappings. "
1450
- f"Please make sure that the slot has only one type of mapping."
1636
+ f"The slot '{slot.name}' has at least 1 slot mapping with "
1637
+ f"'{KEY_ALLOW_NLU_CORRECTION}' set to 'true', but "
1638
+ f"the slot mapping type is not 'from_llm'. "
1639
+ f"Please set the slot mapping type to 'from_llm' "
1640
+ f"to allow the LLM to correct this slot."
1451
1641
  ),
1452
- docs_link=DOCS_URL_DOMAIN + "#calm-slot-mappings",
1453
1642
  )
1454
1643
  all_good = False
1455
1644
 
@@ -1457,55 +1646,32 @@ class Validator:
1457
1646
 
1458
1647
  def _custom_action_name_is_defined_in_the_domain(
1459
1648
  self,
1460
- custom_mappings: bool,
1649
+ controlled_mappings: bool,
1461
1650
  slot: Slot,
1462
1651
  all_good: bool,
1463
1652
  ) -> bool:
1464
- if not custom_mappings:
1465
- return all_good
1466
-
1467
- if not self.flows:
1653
+ if not controlled_mappings:
1468
1654
  return all_good
1469
1655
 
1470
- is_custom_action_defined = any(
1471
- [
1472
- mapping.get("action") is not None
1473
- and mapping.get("action") in self.domain.action_names_or_texts
1474
- for mapping in slot.mappings
1475
- ]
1476
- )
1477
-
1478
- if is_custom_action_defined:
1479
- return all_good
1480
-
1481
- slot_collected_by_flows = any(
1482
- [
1483
- step.collect == slot.name
1484
- for flow in self.flows.underlying_flows
1485
- for step in flow.steps
1486
- if isinstance(step, CollectInformationFlowStep)
1487
- ]
1488
- )
1489
-
1490
- if not slot_collected_by_flows:
1491
- # if the slot is not collected by any flow,
1492
- # it could be a DM1 custom slot
1493
- return all_good
1494
-
1495
- custom_action_ask_name = f"action_ask_{slot.name}"
1496
- if custom_action_ask_name not in self.domain.action_names_or_texts:
1497
- structlogger.error(
1498
- "validator.validate_slot_mappings_in_CALM.custom_action_not_in_domain",
1499
- slot_name=slot.name,
1500
- event_info=(
1501
- f"The slot '{slot.name}' has a custom slot mapping, but "
1502
- f"neither the action '{custom_action_ask_name}' nor "
1503
- f"another custom action are defined in the domain file. "
1504
- f"Please add one of the actions to your domain file."
1505
- ),
1506
- docs_link=DOCS_URL_DOMAIN + "#custom-slot-mappings",
1507
- )
1508
- all_good = False
1656
+ for mapping in slot.mappings:
1657
+ if (
1658
+ mapping.run_action_every_turn is not None
1659
+ and mapping.run_action_every_turn
1660
+ not in self.domain.action_names_or_texts
1661
+ ):
1662
+ structlogger.error(
1663
+ "validator.validate_slot_mappings_in_CALM.custom_action_not_in_domain",
1664
+ slot_name=slot.name,
1665
+ action_name=mapping.run_action_every_turn,
1666
+ event_info=(
1667
+ f"The slot '{slot.name}' has a custom action "
1668
+ f"'{mapping.run_action_every_turn}' "
1669
+ f"defined in its slot mappings, "
1670
+ f"but the action is not listed in the domain actions. "
1671
+ f"Please add the action to your domain file."
1672
+ ),
1673
+ )
1674
+ all_good = False
1509
1675
 
1510
1676
  return all_good
1511
1677
 
@@ -1590,7 +1756,7 @@ class Validator:
1590
1756
  collect_step = step.collect
1591
1757
  flow_slots.add(collect_step)
1592
1758
  if not step.reset_after_flow_ends:
1593
- warn_deprecated_collect_step_config(flow_id, collect_step)
1759
+ warn_deprecated_collect_step_config()
1594
1760
 
1595
1761
  if has_flow_level_persistence:
1596
1762
  structlogger.error(
@@ -1651,10 +1817,316 @@ class Validator:
1651
1817
  self.verify_predicates(),
1652
1818
  ]
1653
1819
  )
1820
+ valid_translations = self.verify_translations(summary_mode=True)
1654
1821
  valid_calm_slot_mappings = self.validate_CALM_slot_mappings()
1655
1822
 
1656
1823
  all_good = (
1657
- valid_responses and valid_nlu and valid_flows and valid_calm_slot_mappings
1824
+ valid_responses
1825
+ and valid_nlu
1826
+ and valid_flows
1827
+ and valid_translations
1828
+ and valid_calm_slot_mappings
1658
1829
  )
1659
1830
 
1660
1831
  return all_good
1832
+
1833
+ def verify_digression_configuration(self) -> bool:
1834
+ """Validates the digression configuration in flows."""
1835
+ all_good = True
1836
+
1837
+ for flow in self.flows.underlying_flows:
1838
+ all_good = self._validate_ask_confirm_digressions(flow, all_good)
1839
+ all_good = self._validate_block_digressions(flow, all_good)
1840
+
1841
+ return all_good
1842
+
1843
+ def _validate_ask_confirm_digressions(self, flow: Flow, all_good: bool) -> bool:
1844
+ """Validates the ask_confirm_digressions configuration in a flow."""
1845
+ for flow_id in flow.ask_confirm_digressions:
1846
+ if flow_id == ALL_LABEL:
1847
+ continue
1848
+ if flow_id not in self.flows.flow_ids:
1849
+ structlogger.error(
1850
+ "validator.verify_digression_configuration.ask_confirm_digressions",
1851
+ flow=flow.id,
1852
+ event_info=(
1853
+ f"The flow '{flow_id}' is listed in the "
1854
+ f"`ask_confirm_digressions` configuration of flow "
1855
+ f"'{flow.id}', but it is not found in the "
1856
+ f"flows file. Please make sure that the flow id is correct."
1857
+ ),
1858
+ )
1859
+ all_good = False
1860
+
1861
+ if flow_id in flow.block_digressions:
1862
+ structlogger.error(
1863
+ "validator.verify_digression_configuration.overlap_digressions",
1864
+ flow=flow.id,
1865
+ event_info=(
1866
+ f"The flow '{flow_id}' is listed in both the "
1867
+ f"`ask_confirm_digressions` and `block_digressions` "
1868
+ f"configuration of flow '{flow.id}'. "
1869
+ f"Please make sure that the flow id is not listed in both "
1870
+ f"configurations."
1871
+ ),
1872
+ )
1873
+ all_good = False
1874
+
1875
+ for step in flow.get_collect_steps():
1876
+ for flow_id in step.ask_confirm_digressions:
1877
+ if flow_id == ALL_LABEL:
1878
+ continue
1879
+
1880
+ if flow_id not in self.flows.flow_ids:
1881
+ structlogger.error(
1882
+ "validator.verify_digression_configuration.ask_confirm_digressions",
1883
+ flow=flow.id,
1884
+ step_id=step.id,
1885
+ event_info=(
1886
+ f"The flow '{flow_id}' is listed in the "
1887
+ f"`ask_confirm_digressions` configuration of step "
1888
+ f"'{step.id}' in flow '{flow.id}', but it is "
1889
+ f"not found in the flows file. "
1890
+ f"Please make sure that the flow id is correct."
1891
+ ),
1892
+ )
1893
+ all_good = False
1894
+
1895
+ if flow_id in step.block_digressions:
1896
+ structlogger.error(
1897
+ "validator.verify_digression_configuration.overlap_digressions",
1898
+ flow=flow.id,
1899
+ step_id=step.id,
1900
+ event_info=(
1901
+ f"The flow '{flow_id}' is listed in both the "
1902
+ f"`ask_confirm_digressions` and `block_digressions` "
1903
+ f"configuration of step '{step.id}' in flow '{flow.id}'. "
1904
+ f"Please make sure that the flow id is not listed in both "
1905
+ f"configurations."
1906
+ ),
1907
+ )
1908
+ all_good = False
1909
+
1910
+ return all_good
1911
+
1912
+ def _validate_block_digressions(self, flow: Flow, all_good: bool) -> bool:
1913
+ """Validates the block_digressions configuration in a flow."""
1914
+ for flow_id in flow.block_digressions:
1915
+ if flow_id == ALL_LABEL:
1916
+ continue
1917
+
1918
+ if flow_id not in self.flows.flow_ids:
1919
+ structlogger.error(
1920
+ "validator.verify_digression_configuration.block_digressions",
1921
+ flow=flow.id,
1922
+ event_info=(
1923
+ f"The flow '{flow_id}' is listed in the `block_digressions` "
1924
+ f"configuration of flow '{flow.id}', but it is not found "
1925
+ f"in the flows file. Please make sure that the flow id "
1926
+ f"is correct."
1927
+ ),
1928
+ )
1929
+ all_good = False
1930
+
1931
+ for step in flow.get_collect_steps():
1932
+ for flow_id in step.block_digressions:
1933
+ if flow_id == ALL_LABEL:
1934
+ continue
1935
+
1936
+ if flow_id not in self.flows.flow_ids:
1937
+ structlogger.error(
1938
+ "validator.verify_digression_configuration.block_digressions",
1939
+ flow=flow.id,
1940
+ step_id=step.id,
1941
+ event_info=(
1942
+ f"The flow '{flow_id}' is listed in the "
1943
+ f"`block_digressions` configuration of step "
1944
+ f"'{step.id}' in flow '{flow.id}', but it is "
1945
+ f"not found in the flows file. "
1946
+ f"Please make sure that the flow id is correct."
1947
+ ),
1948
+ )
1949
+ all_good = False
1950
+
1951
+ return all_good
1952
+
1953
+ def verify_slot_validation(self) -> bool:
1954
+ """Validates the slot validation configuration in the domain file."""
1955
+ all_good = True
1956
+
1957
+ for slot in self.domain._user_slots:
1958
+ if slot.requires_validation():
1959
+ refill_utter = slot.validation.refill_utter # type: ignore[union-attr]
1960
+ if refill_utter and refill_utter not in self.domain.responses:
1961
+ self._log_slot_validation_error(
1962
+ slot.name, "refill utterance", refill_utter
1963
+ )
1964
+ all_good = False
1965
+ rejections = slot.validation.rejections # type: ignore[union-attr]
1966
+ for rejection in rejections:
1967
+ if rejection.utter not in self.domain.responses:
1968
+ self._log_slot_validation_error(
1969
+ slot.name, "rejection utterance", rejection.utter
1970
+ )
1971
+ all_good = False
1972
+
1973
+ return all_good
1974
+
1975
+ def _log_slot_validation_error(self, slot_name: str, key: str, value: str) -> None:
1976
+ structlogger.error(
1977
+ "validator.verify_slot_validation.response_not_in_domain",
1978
+ slot=slot_name,
1979
+ event_info=(
1980
+ f"The slot '{slot_name}' requires validation, "
1981
+ f"but the {key} '{value}' "
1982
+ f"is not listed in the domain responses. "
1983
+ f"Please add it to your domain file."
1984
+ ),
1985
+ )
1986
+
1987
+ @staticmethod
1988
+ def _validate_controlled_mappings(slot: Slot, all_good: bool) -> bool:
1989
+ for mapping in slot.mappings:
1990
+ if (
1991
+ mapping.run_action_every_turn is not None
1992
+ and mapping.type != SlotMappingType.CONTROLLED
1993
+ ):
1994
+ structlogger.error(
1995
+ "validator.validate_slot_mappings_in_CALM.run_action_every_turn_invalid",
1996
+ slot_name=slot.name,
1997
+ event_info=(
1998
+ f"The slot '{slot.name}' has a custom action "
1999
+ f"'{mapping.run_action_every_turn}' "
2000
+ f"defined in its slot mapping, "
2001
+ f"but the slot mapping type is not 'controlled'. "
2002
+ ),
2003
+ )
2004
+ all_good = False
2005
+
2006
+ if (
2007
+ mapping.coexistence_system is not None
2008
+ and mapping.type != SlotMappingType.CONTROLLED
2009
+ ):
2010
+ structlogger.error(
2011
+ "validator.validate_slot_mappings_in_CALM.coexistence_system_invalid",
2012
+ slot_name=slot.name,
2013
+ event_info=(
2014
+ f"The slot '{slot.name}' has a coexistence system "
2015
+ f"'{mapping.coexistence_system.value}' "
2016
+ f"defined in its slot mapping, "
2017
+ f"but the slot mapping type is not 'controlled'. "
2018
+ ),
2019
+ )
2020
+ all_good = False
2021
+
2022
+ if (
2023
+ mapping.coexistence_system is not None
2024
+ and mapping.coexistence_system != CoexistenceSystemType.SHARED
2025
+ and slot.shared_for_coexistence
2026
+ ):
2027
+ structlogger.error(
2028
+ "validator.validate_slot_mappings_in_CALM.shared_for_coexistence_invalid",
2029
+ slot_name=slot.name,
2030
+ event_info=(
2031
+ f"The slot '{slot.name}' has the `shared_for_coexistence` "
2032
+ f"property set to `True`, but the slot mapping `controlled` "
2033
+ f"type defines the `coexistence_system` property with a "
2034
+ f"value different to the expected `SHARED` value. "
2035
+ ),
2036
+ )
2037
+ all_good = False
2038
+
2039
+ multiple_controlled_mappings = {
2040
+ mapping.coexistence_system.value
2041
+ for mapping in slot.mappings
2042
+ if mapping.type == SlotMappingType.CONTROLLED
2043
+ and mapping.coexistence_system is not None
2044
+ }
2045
+ contains_inconsistent_coexistence_system = len(multiple_controlled_mappings) > 1
2046
+
2047
+ if contains_inconsistent_coexistence_system:
2048
+ structlogger.error(
2049
+ "validator.validate_slot_mappings_in_CALM.inconsistent_multiple_mappings",
2050
+ slot_name=slot.name,
2051
+ event_info=(
2052
+ f"The slot '{slot.name}' has multiple `controlled` mappings "
2053
+ f"with different coexistence systems defined: "
2054
+ f"'{sorted(list(multiple_controlled_mappings))}'. "
2055
+ f"Please only define one coexistence system for the slot. "
2056
+ ),
2057
+ )
2058
+ all_good = False
2059
+
2060
+ return all_good
2061
+
2062
+ def validate_conditional_response_variation_predicates(self) -> bool:
2063
+ """Validate the conditional response variation predicates."""
2064
+ context = {"slots": {slot.name: None for slot in self.domain.slots}}
2065
+ all_good = True
2066
+
2067
+ for utter_name, variations in self.domain.responses.items():
2068
+ for variation in variations:
2069
+ condition = variation.get(RESPONSE_CONDITION)
2070
+ if not isinstance(condition, str):
2071
+ continue
2072
+
2073
+ predicate, all_good = self._construct_predicate(
2074
+ condition,
2075
+ utter_name,
2076
+ context,
2077
+ is_step=False,
2078
+ all_good=all_good,
2079
+ )
2080
+ if not predicate:
2081
+ continue
2082
+
2083
+ if not predicate.is_valid():
2084
+ structlogger.error(
2085
+ "validator.validate_conditional_response_variation_predicates.invalid_condition",
2086
+ utter=utter_name,
2087
+ event_info=(
2088
+ f"Detected invalid condition '{condition}' "
2089
+ f"for response variation '{utter_name}'. "
2090
+ f"Please make sure that all conditions are valid."
2091
+ ),
2092
+ )
2093
+ all_good = False
2094
+ continue
2095
+
2096
+ predicate_syntax_tree = self._extract_predicate_syntax_tree(predicate)
2097
+ slot_namespace, _ = self._extract_slot_name_and_slot_value(
2098
+ predicate_syntax_tree
2099
+ )
2100
+
2101
+ if slot_namespace is not None and slot_namespace[0] != SLOTS:
2102
+ structlogger.error(
2103
+ "validator.validate_conditional_response_variation_predicates.invalid_namespace",
2104
+ utter=utter_name,
2105
+ event_info=(
2106
+ f"Detected invalid namespace '{slot_namespace[0]}' in "
2107
+ f"condition '{condition}' for response variation "
2108
+ f"'{utter_name}'. Please make sure that you're "
2109
+ f"using a valid namespace. "
2110
+ f"The current supported option is: 'slots'."
2111
+ ),
2112
+ )
2113
+ all_good = False
2114
+ continue
2115
+
2116
+ if (
2117
+ slot_namespace is not None
2118
+ and slot_namespace[1] not in self.domain.slots
2119
+ ):
2120
+ structlogger.error(
2121
+ "validator.validate_conditional_response_variation_predicates.invalid_slot",
2122
+ utter=utter_name,
2123
+ event_info=(
2124
+ f"Detected invalid slot '{slot_namespace[1]}' in "
2125
+ f"condition '{condition}' for response variation "
2126
+ f"'{utter_name}'. Please make sure that all slots "
2127
+ f"are specified in the domain file."
2128
+ ),
2129
+ )
2130
+ all_good = False
2131
+
2132
+ return all_good