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.
- rasa/anonymization/anonymization_rule_executor.py +16 -10
- rasa/cli/data.py +16 -0
- rasa/cli/inspect.py +20 -1
- rasa/cli/project_templates/calm/config.yml +2 -2
- rasa/cli/project_templates/calm/endpoints.yml +2 -2
- rasa/cli/shell.py +3 -3
- rasa/cli/utils.py +12 -0
- rasa/core/actions/action.py +99 -193
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/action_run_slot_rejections.py +16 -4
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +4 -0
- rasa/core/channels/studio_chat.py +19 -0
- rasa/core/channels/telegram.py +42 -24
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_ready/utils.py +1 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +10 -4
- rasa/core/channels/voice_stream/asr/azure.py +14 -1
- rasa/core/channels/voice_stream/asr/deepgram.py +20 -4
- rasa/core/channels/voice_stream/audiocodes.py +264 -0
- rasa/core/channels/voice_stream/browser_audio.py +5 -1
- rasa/core/channels/voice_stream/call_state.py +10 -1
- rasa/core/channels/voice_stream/genesys.py +335 -0
- rasa/core/channels/voice_stream/tts/azure.py +11 -2
- rasa/core/channels/voice_stream/tts/cartesia.py +29 -10
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +25 -3
- rasa/core/constants.py +2 -0
- rasa/core/migrate.py +2 -2
- rasa/core/nlg/contextual_response_rephraser.py +18 -1
- rasa/core/nlg/generator.py +83 -15
- rasa/core/nlg/response.py +6 -3
- rasa/core/nlg/translate.py +55 -0
- rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +1 -1
- rasa/core/policies/flows/flow_executor.py +47 -46
- rasa/core/processor.py +72 -9
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +20 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +80 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +20 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/clarify_command.py +29 -3
- rasa/dialogue_understanding/commands/command.py +1 -16
- rasa/dialogue_understanding/commands/command_syntax_manager.py +55 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
- rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +20 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/prompt_command.py +94 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +20 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +29 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +20 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +61 -2
- rasa/dialogue_understanding/commands/utils.py +98 -4
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/__init__.py +2 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +16 -13
- rasa/dialogue_understanding/generator/constants.py +3 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +170 -5
- rasa/dialogue_understanding/generator/llm_command_generator.py +5 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +26 -4
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/prompt_templates/__init__.py +0 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +60 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +77 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +68 -0
- rasa/dialogue_understanding/generator/{single_step/command_prompt_template.jinja2 → prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2} +1 -1
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +460 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +12 -318
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/collect_information.py +1 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +78 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/patterns/validate_slot.py +65 -0
- rasa/dialogue_understanding/processor/command_processor.py +154 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/du_test_case.py +28 -8
- rasa/dialogue_understanding_test/du_test_result.py +13 -9
- rasa/dialogue_understanding_test/io.py +14 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/e2e_test/utils/io.py +0 -37
- rasa/engine/graph.py +1 -0
- rasa/engine/language.py +140 -0
- rasa/engine/recipes/config_files/default_config.yml +4 -0
- rasa/engine/recipes/default_recipe.py +2 -0
- rasa/engine/recipes/graph_recipe.py +2 -0
- rasa/engine/storage/local_model_storage.py +1 -0
- rasa/engine/storage/storage.py +4 -1
- rasa/model_manager/runner_service.py +7 -4
- rasa/model_manager/socket_bridge.py +7 -6
- rasa/model_manager/warm_rasa_process.py +0 -1
- rasa/model_training.py +24 -27
- rasa/shared/constants.py +15 -13
- rasa/shared/core/constants.py +30 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/constants.py +11 -0
- rasa/shared/core/flows/flow.py +100 -19
- rasa/shared/core/flows/flows_yaml_schema.json +69 -3
- rasa/shared/core/flows/steps/collect.py +19 -37
- rasa/shared/core/flows/utils.py +43 -4
- rasa/shared/core/flows/validation.py +1 -1
- rasa/shared/core/slot_mappings.py +350 -111
- rasa/shared/core/slots.py +154 -3
- rasa/shared/core/trackers.py +77 -2
- rasa/shared/importers/importer.py +50 -2
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/nlu/training_data/schemas/responses.yml +19 -12
- rasa/shared/providers/_configs/azure_entra_id_config.py +541 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +138 -3
- rasa/shared/providers/_configs/client_config.py +3 -1
- rasa/shared/providers/_configs/default_litellm_client_config.py +3 -1
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +3 -1
- rasa/shared/providers/_configs/litellm_router_client_config.py +3 -1
- rasa/shared/providers/_configs/model_group_config.py +4 -2
- rasa/shared/providers/_configs/oauth_config.py +33 -0
- rasa/shared/providers/_configs/openai_client_config.py +3 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +3 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +3 -1
- rasa/shared/providers/constants.py +6 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +28 -3
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +3 -1
- rasa/shared/providers/llm/_base_litellm_client.py +42 -17
- rasa/shared/providers/llm/azure_openai_llm_client.py +81 -25
- rasa/shared/providers/llm/default_litellm_llm_client.py +3 -1
- rasa/shared/providers/llm/litellm_router_llm_client.py +29 -8
- rasa/shared/providers/llm/llm_client.py +23 -7
- rasa/shared/providers/llm/openai_llm_client.py +9 -3
- rasa/shared/providers/llm/rasa_llm_client.py +11 -2
- rasa/shared/providers/llm/self_hosted_llm_client.py +30 -11
- rasa/shared/providers/router/_base_litellm_router_client.py +3 -1
- rasa/shared/providers/router/router_client.py +3 -1
- rasa/shared/utils/constants.py +3 -0
- rasa/shared/utils/llm.py +31 -8
- rasa/shared/utils/pykwalify_extensions.py +24 -0
- rasa/shared/utils/schemas/domain.yml +26 -1
- rasa/telemetry.py +45 -14
- rasa/tracing/config.py +2 -0
- rasa/tracing/constants.py +12 -0
- rasa/tracing/instrumentation/instrumentation.py +36 -0
- rasa/tracing/instrumentation/metrics.py +41 -0
- rasa/tracing/metric_instrument_provider.py +40 -0
- rasa/utils/common.py +0 -1
- rasa/validator.py +561 -89
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/METADATA +2 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/RECORD +153 -134
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
39
|
-
|
|
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.
|
|
522
|
-
condition_active_loop = condition.
|
|
523
|
-
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
|
-
) ->
|
|
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
|
-
|
|
1064
|
+
slot_namespace, slot_value = self._extract_slot_name_and_slot_value(
|
|
1048
1065
|
predicate_syntax_tree
|
|
1049
1066
|
)
|
|
1050
1067
|
|
|
1051
|
-
if
|
|
1068
|
+
if slot_namespace is None:
|
|
1052
1069
|
return all_good
|
|
1053
1070
|
|
|
1054
|
-
if
|
|
1055
|
-
slot_name =
|
|
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
|
-
|
|
1577
|
+
controlled_mappings = any(
|
|
1410
1578
|
[
|
|
1411
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
|
1437
|
-
|
|
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
|
-
|
|
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.
|
|
1446
|
-
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 '{
|
|
1449
|
-
f"
|
|
1450
|
-
f"
|
|
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
|
-
|
|
1649
|
+
controlled_mappings: bool,
|
|
1461
1650
|
slot: Slot,
|
|
1462
1651
|
all_good: bool,
|
|
1463
1652
|
) -> bool:
|
|
1464
|
-
if not
|
|
1465
|
-
return all_good
|
|
1466
|
-
|
|
1467
|
-
if not self.flows:
|
|
1653
|
+
if not controlled_mappings:
|
|
1468
1654
|
return all_good
|
|
1469
1655
|
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
mapping.
|
|
1473
|
-
and mapping.
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
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(
|
|
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
|
|
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
|