rasa-pro 3.12.0.dev9__py3-none-any.whl → 3.12.0.dev11__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/cli/inspect.py +20 -1
- rasa/cli/shell.py +3 -3
- rasa/core/actions/action.py +20 -7
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_stream/browser_audio.py +1 -0
- rasa/core/channels/voice_stream/call_state.py +7 -1
- rasa/core/channels/voice_stream/genesys.py +331 -0
- rasa/core/channels/voice_stream/tts/azure.py +2 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +16 -3
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +2 -1
- rasa/core/migrate.py +2 -2
- rasa/core/policies/flows/flow_executor.py +36 -42
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/clarify_command.py +2 -2
- 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 +2 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
- rasa/dialogue_understanding/commands/utils.py +1 -1
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +161 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +40 -40
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/processor/command_processor.py +115 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/model_service.py +4 -0
- rasa/model_training.py +24 -27
- rasa/shared/core/constants.py +28 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/flow.py +17 -0
- rasa/shared/core/flows/flows_yaml_schema.json +38 -0
- rasa/shared/core/flows/steps/collect.py +18 -1
- rasa/shared/core/flows/utils.py +16 -1
- rasa/shared/core/slot_mappings.py +144 -108
- rasa/shared/core/slots.py +23 -2
- rasa/shared/core/trackers.py +3 -1
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/providers/llm/_base_litellm_client.py +0 -40
- rasa/shared/utils/llm.py +1 -86
- rasa/shared/utils/schemas/domain.yml +0 -1
- rasa/telemetry.py +43 -13
- rasa/utils/common.py +0 -1
- rasa/validator.py +189 -82
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/RECORD +72 -68
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, Set
|
|
5
|
+
|
|
6
|
+
from rasa.dialogue_understanding.stack.frames import PatternFlowStackFrame
|
|
7
|
+
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
8
|
+
from rasa.shared.core.constants import (
|
|
9
|
+
KEY_ASK_CONFIRM_DIGRESSIONS,
|
|
10
|
+
KEY_BLOCK_DIGRESSIONS,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
FLOW_PATTERN_HANDLE_DIGRESSIONS = (
|
|
14
|
+
RASA_DEFAULT_FLOW_PATTERN_PREFIX + "handle_digressions"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class HandleDigressionsPatternFlowStackFrame(PatternFlowStackFrame):
|
|
20
|
+
"""A pattern flow stack frame that gets added if an interruption is completed."""
|
|
21
|
+
|
|
22
|
+
flow_id: str = FLOW_PATTERN_HANDLE_DIGRESSIONS
|
|
23
|
+
"""The ID of the flow."""
|
|
24
|
+
interrupting_flow_id: str = ""
|
|
25
|
+
"""The ID of the flow that interrupted the active flow."""
|
|
26
|
+
interrupted_flow_id: str = ""
|
|
27
|
+
"""The name of the active flow that was interrupted."""
|
|
28
|
+
interrupted_step_id: str = ""
|
|
29
|
+
"""The ID of the step that was interrupted."""
|
|
30
|
+
ask_confirm_digressions: Set[str] = field(default_factory=set)
|
|
31
|
+
"""The set of interrupting flow names to confirm."""
|
|
32
|
+
block_digressions: Set[str] = field(default_factory=set)
|
|
33
|
+
"""The set of interrupting flow names to block."""
|
|
34
|
+
|
|
35
|
+
@classmethod
|
|
36
|
+
def type(cls) -> str:
|
|
37
|
+
"""Returns the type of the frame."""
|
|
38
|
+
return FLOW_PATTERN_HANDLE_DIGRESSIONS
|
|
39
|
+
|
|
40
|
+
@staticmethod
|
|
41
|
+
def from_dict(data: Dict[str, Any]) -> HandleDigressionsPatternFlowStackFrame:
|
|
42
|
+
"""Creates a `DialogueStackFrame` from a dictionary.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
data: The dictionary to create the `DialogueStackFrame` from.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
The created `DialogueStackFrame`.
|
|
49
|
+
"""
|
|
50
|
+
return HandleDigressionsPatternFlowStackFrame(
|
|
51
|
+
frame_id=data["frame_id"],
|
|
52
|
+
step_id=data["step_id"],
|
|
53
|
+
interrupted_step_id=data["interrupted_step_id"],
|
|
54
|
+
interrupted_flow_id=data["interrupted_flow_id"],
|
|
55
|
+
interrupting_flow_id=data["interrupting_flow_id"],
|
|
56
|
+
ask_confirm_digressions=set(data.get(KEY_ASK_CONFIRM_DIGRESSIONS, [])),
|
|
57
|
+
# This attribute must be converted to a set to enable usage
|
|
58
|
+
# of subset `contains` pypred operator in the default pattern
|
|
59
|
+
# conditional branching
|
|
60
|
+
block_digressions=set(data.get(KEY_BLOCK_DIGRESSIONS, [])),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other: Any) -> bool:
|
|
64
|
+
if not isinstance(other, HandleDigressionsPatternFlowStackFrame):
|
|
65
|
+
return False
|
|
66
|
+
return (
|
|
67
|
+
self.flow_id == other.flow_id
|
|
68
|
+
and self.interrupted_step_id == other.interrupted_step_id
|
|
69
|
+
and self.interrupted_flow_id == other.interrupted_flow_id
|
|
70
|
+
and self.interrupting_flow_id == other.interrupting_flow_id
|
|
71
|
+
and self.ask_confirm_digressions == other.ask_confirm_digressions
|
|
72
|
+
and self.block_digressions == other.block_digressions
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
76
|
+
"""Returns the frame as a dictionary."""
|
|
77
|
+
data = super().as_dict()
|
|
78
|
+
# converting back to list to avoid serialization issues
|
|
79
|
+
data[KEY_ASK_CONFIRM_DIGRESSIONS] = list(self.ask_confirm_digressions)
|
|
80
|
+
data[KEY_BLOCK_DIGRESSIONS] = list(self.block_digressions)
|
|
81
|
+
return data
|
|
@@ -18,6 +18,9 @@ from rasa.dialogue_understanding.commands import (
|
|
|
18
18
|
from rasa.dialogue_understanding.commands.handle_code_change_command import (
|
|
19
19
|
HandleCodeChangeCommand,
|
|
20
20
|
)
|
|
21
|
+
from rasa.dialogue_understanding.commands.handle_digressions_command import (
|
|
22
|
+
HandleDigressionsCommand,
|
|
23
|
+
)
|
|
21
24
|
from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
|
|
22
25
|
from rasa.dialogue_understanding.patterns.chitchat import FLOW_PATTERN_CHITCHAT
|
|
23
26
|
from rasa.dialogue_understanding.patterns.collect_information import (
|
|
@@ -47,6 +50,7 @@ from rasa.shared.core.constants import (
|
|
|
47
50
|
from rasa.shared.core.events import Event, SlotSet
|
|
48
51
|
from rasa.shared.core.flows import FlowsList
|
|
49
52
|
from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
|
|
53
|
+
from rasa.shared.core.slot_mappings import SlotMapping
|
|
50
54
|
from rasa.shared.core.slots import Slot
|
|
51
55
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
52
56
|
from rasa.shared.core.training_data.structures import StoryGraph
|
|
@@ -395,6 +399,28 @@ def clean_up_commands(
|
|
|
395
399
|
command=command,
|
|
396
400
|
)
|
|
397
401
|
|
|
402
|
+
elif isinstance(command, StartFlowCommand) and active_flow is not None:
|
|
403
|
+
# push handle digressions command if we are at a collect step of
|
|
404
|
+
# a flow and a new flow is started
|
|
405
|
+
collect_info = get_current_collect_step(tracker.stack, all_flows)
|
|
406
|
+
current_flow = all_flows.flow_by_id(active_flow)
|
|
407
|
+
current_flow_condition = current_flow and (
|
|
408
|
+
current_flow.ask_confirm_digressions or current_flow.block_digressions
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
if collect_info and (
|
|
412
|
+
collect_info.ask_confirm_digressions
|
|
413
|
+
or collect_info.block_digressions
|
|
414
|
+
or current_flow_condition
|
|
415
|
+
):
|
|
416
|
+
clean_commands.append(HandleDigressionsCommand(flow=command.flow))
|
|
417
|
+
structlogger.debug(
|
|
418
|
+
"command_processor.clean_up_commands.push_handle_digressions",
|
|
419
|
+
command=command,
|
|
420
|
+
)
|
|
421
|
+
else:
|
|
422
|
+
clean_commands.append(command)
|
|
423
|
+
|
|
398
424
|
# handle chitchat command differently from other free-form answer commands
|
|
399
425
|
elif isinstance(command, ChitChatAnswerCommand):
|
|
400
426
|
clean_commands = clean_up_chitchat_command(
|
|
@@ -527,13 +553,48 @@ def clean_up_slot_command(
|
|
|
527
553
|
)
|
|
528
554
|
return resulting_commands
|
|
529
555
|
|
|
530
|
-
if not should_slot_be_set(slot, command):
|
|
556
|
+
if not should_slot_be_set(slot, command, resulting_commands):
|
|
557
|
+
structlogger.debug(
|
|
558
|
+
"command_processor.clean_up_slot_command.skip_command.extractor_"
|
|
559
|
+
"does_not_match_slot_mapping",
|
|
560
|
+
extractor=command.extractor,
|
|
561
|
+
slot_name=slot.name,
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
# prevent adding a cannot handle command in case commands_so_far already
|
|
565
|
+
# contains a valid prior set slot command for the same slot whose current
|
|
566
|
+
# slot command was rejected by should_slot_be_set
|
|
567
|
+
slot_command_exists_already = any(
|
|
568
|
+
isinstance(command, SetSlotCommand) and command.name == slot.name
|
|
569
|
+
for command in resulting_commands
|
|
570
|
+
)
|
|
571
|
+
|
|
531
572
|
cannot_handle = CannotHandleCommand(reason=CANNOT_HANDLE_REASON)
|
|
532
|
-
if cannot_handle not in resulting_commands:
|
|
573
|
+
if not slot_command_exists_already and cannot_handle not in resulting_commands:
|
|
533
574
|
resulting_commands.append(cannot_handle)
|
|
534
575
|
|
|
535
576
|
return resulting_commands
|
|
536
577
|
|
|
578
|
+
if (
|
|
579
|
+
slot.filled_by == SetSlotExtractor.NLU.value
|
|
580
|
+
and command.extractor == SetSlotExtractor.LLM.value
|
|
581
|
+
):
|
|
582
|
+
allow_nlu_correction = any(
|
|
583
|
+
[
|
|
584
|
+
mapping.allow_nlu_correction is True
|
|
585
|
+
for mapping in slot.mappings
|
|
586
|
+
if mapping.type == SlotMappingType.FROM_LLM
|
|
587
|
+
]
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
if not allow_nlu_correction:
|
|
591
|
+
structlogger.debug(
|
|
592
|
+
"command_processor.clean_up_slot_command"
|
|
593
|
+
".skip_command.disallow_llm_correction_of_nlu_set_value",
|
|
594
|
+
command=command,
|
|
595
|
+
)
|
|
596
|
+
return resulting_commands
|
|
597
|
+
|
|
537
598
|
if command.name in slots_so_far and command.name != ROUTE_TO_CALM_SLOT:
|
|
538
599
|
current_collect_info = get_current_collect_step(stack, all_flows)
|
|
539
600
|
|
|
@@ -574,7 +635,7 @@ def clean_up_slot_command(
|
|
|
574
635
|
)
|
|
575
636
|
|
|
576
637
|
# Group all corrections into one command
|
|
577
|
-
corrected_slot = CorrectedSlot(command.name, command.value)
|
|
638
|
+
corrected_slot = CorrectedSlot(command.name, command.value, command.extractor)
|
|
578
639
|
for c in resulting_commands:
|
|
579
640
|
if isinstance(c, CorrectSlotsCommand):
|
|
580
641
|
c.corrected_slots.append(corrected_slot)
|
|
@@ -658,7 +719,9 @@ def clean_up_chitchat_command(
|
|
|
658
719
|
return resulting_commands
|
|
659
720
|
|
|
660
721
|
|
|
661
|
-
def should_slot_be_set(
|
|
722
|
+
def should_slot_be_set(
|
|
723
|
+
slot: Slot, command: SetSlotCommand, commands_so_far: Optional[List[Command]] = None
|
|
724
|
+
) -> bool:
|
|
662
725
|
"""Check if a slot should be set by a command."""
|
|
663
726
|
if command.extractor == SetSlotExtractor.COMMAND_PAYLOAD_READER.value:
|
|
664
727
|
# if the command is issued by the command payload reader, it means the slot
|
|
@@ -666,37 +729,61 @@ def should_slot_be_set(slot: Slot, command: SetSlotCommand) -> bool:
|
|
|
666
729
|
# we can always set it
|
|
667
730
|
return True
|
|
668
731
|
|
|
732
|
+
if commands_so_far is None:
|
|
733
|
+
commands_so_far = []
|
|
734
|
+
|
|
735
|
+
set_slot_commands_so_far = [
|
|
736
|
+
command
|
|
737
|
+
for command in commands_so_far
|
|
738
|
+
if isinstance(command, SetSlotCommand) and command.name == slot.name
|
|
739
|
+
]
|
|
740
|
+
|
|
669
741
|
slot_mappings = slot.mappings
|
|
670
742
|
|
|
671
|
-
if not
|
|
672
|
-
slot_mappings = [
|
|
743
|
+
if not slot.mappings:
|
|
744
|
+
slot_mappings = [SlotMapping(type=SlotMappingType.FROM_LLM)]
|
|
673
745
|
|
|
674
|
-
for mapping in slot_mappings
|
|
675
|
-
mapping_type = SlotMappingType(
|
|
676
|
-
mapping.get("type", SlotMappingType.FROM_LLM.value)
|
|
677
|
-
)
|
|
746
|
+
mapping_types = [mapping.type for mapping in slot_mappings]
|
|
678
747
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
748
|
+
slot_has_nlu_mapping = any(
|
|
749
|
+
[mapping_type.is_predefined_type() for mapping_type in mapping_types]
|
|
750
|
+
)
|
|
751
|
+
slot_has_llm_mapping = any(
|
|
752
|
+
[mapping_type == SlotMappingType.FROM_LLM for mapping_type in mapping_types]
|
|
753
|
+
)
|
|
754
|
+
slot_has_controlled_mapping = any(
|
|
755
|
+
[mapping_type == SlotMappingType.CONTROLLED for mapping_type in mapping_types]
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
if set_slot_commands_so_far and command.extractor == SetSlotExtractor.LLM.value:
|
|
759
|
+
# covers the following scenarios:
|
|
760
|
+
# scenario 1: NLU mapping extracts a value for slot_a → If LLM extracts a value for slot_a, it is discarded. # noqa: E501
|
|
761
|
+
# scenario 2: NLU mapping is unable to extract a value for slot_a → If LLM extracts a value for slot_a, it is accepted. # noqa: E501
|
|
762
|
+
command_has_nlu_extractor = any(
|
|
763
|
+
[
|
|
764
|
+
command.extractor == SetSlotExtractor.NLU.value
|
|
765
|
+
for command in set_slot_commands_so_far
|
|
766
|
+
]
|
|
686
767
|
)
|
|
768
|
+
return not command_has_nlu_extractor and slot_has_llm_mapping
|
|
687
769
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
770
|
+
if (
|
|
771
|
+
slot_has_nlu_mapping
|
|
772
|
+
and command.extractor == SetSlotExtractor.LLM.value
|
|
773
|
+
and not slot_has_llm_mapping
|
|
774
|
+
):
|
|
775
|
+
return False
|
|
692
776
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
777
|
+
if (
|
|
778
|
+
slot_has_llm_mapping
|
|
779
|
+
and command.extractor == SetSlotExtractor.NLU.value
|
|
780
|
+
and not slot_has_nlu_mapping
|
|
781
|
+
):
|
|
782
|
+
return False
|
|
783
|
+
|
|
784
|
+
if slot_has_controlled_mapping and not (
|
|
785
|
+
slot_has_nlu_mapping or slot_has_llm_mapping
|
|
786
|
+
):
|
|
700
787
|
return False
|
|
701
788
|
|
|
702
789
|
return True
|
|
@@ -5,7 +5,10 @@ from rasa.dialogue_understanding.commands import Command
|
|
|
5
5
|
from rasa.dialogue_understanding.constants import (
|
|
6
6
|
RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME,
|
|
7
7
|
)
|
|
8
|
+
from rasa.shared.constants import ROUTE_TO_CALM_SLOT
|
|
9
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
8
10
|
from rasa.shared.nlu.constants import (
|
|
11
|
+
COMMANDS,
|
|
9
12
|
KEY_COMPONENT_NAME,
|
|
10
13
|
KEY_LLM_RESPONSE_METADATA,
|
|
11
14
|
KEY_PROMPT_NAME,
|
|
@@ -13,6 +16,7 @@ from rasa.shared.nlu.constants import (
|
|
|
13
16
|
KEY_USER_PROMPT,
|
|
14
17
|
PREDICTED_COMMANDS,
|
|
15
18
|
PROMPTS,
|
|
19
|
+
SET_SLOT_COMMAND,
|
|
16
20
|
)
|
|
17
21
|
from rasa.shared.nlu.training_data.message import Message
|
|
18
22
|
from rasa.shared.providers.llm.llm_response import LLMResponse
|
|
@@ -131,3 +135,30 @@ def add_prompt_to_message_parse_data(
|
|
|
131
135
|
|
|
132
136
|
# Update the message with the new prompts list.
|
|
133
137
|
message.set(PROMPTS, prompts, add_to_output=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _handle_via_nlu_in_coexistence(
|
|
141
|
+
tracker: Optional[DialogueStateTracker], message: Message
|
|
142
|
+
) -> bool:
|
|
143
|
+
"""Check if the message should be handled by the NLU subsystem in coexistence mode.""" # noqa: E501
|
|
144
|
+
if not tracker:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
if not tracker.has_coexistence_routing_slot:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
value = tracker.get_slot(ROUTE_TO_CALM_SLOT)
|
|
151
|
+
if value is not None:
|
|
152
|
+
return not value
|
|
153
|
+
|
|
154
|
+
# routing slot has been reset so we need to check
|
|
155
|
+
# the command issued by the Router component
|
|
156
|
+
if message.get(COMMANDS):
|
|
157
|
+
for command in message.get(COMMANDS):
|
|
158
|
+
if (
|
|
159
|
+
command.get("command") == SET_SLOT_COMMAND
|
|
160
|
+
and command.get("name") == ROUTE_TO_CALM_SLOT
|
|
161
|
+
):
|
|
162
|
+
return not command.get("value")
|
|
163
|
+
|
|
164
|
+
return False
|
|
@@ -377,3 +377,53 @@ Test cases in **to_review** may require manual intervention because the E2E test
|
|
|
377
377
|
Review these cases to ensure that the converted test cases are correct and the list of commands and
|
|
378
378
|
bot responses is complete.
|
|
379
379
|
|
|
380
|
+
|
|
381
|
+
## Converting DUT test from one DSL to a another DSL
|
|
382
|
+
|
|
383
|
+
If you need to transform your commands from one DSL format to another
|
|
384
|
+
(for instance, updating `StartFlow(flow_name)` to `start flow_name` or `SetSlot(slot_name, slot_value)` to `set slot_name slot_value`),
|
|
385
|
+
you can use a standalone Python script:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
python convert_dut_dsl.py --dut-tests-dir <path> --output-dir <path> --dsl-mappings <path>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The script has the following required parameters:
|
|
392
|
+
|
|
393
|
+
- `--dut-tests-dir <path>`: The directory (relative or absolute) containing your
|
|
394
|
+
existing Dialogue Understanding Tests (DUT). The script will look for `.yaml` or
|
|
395
|
+
`.yml` files within this folder (and subfolders).
|
|
396
|
+
- `--output-dir <path>`: The directory where transformed files will be saved. The folder
|
|
397
|
+
structure from your `dut-tests-dir` is preserved.
|
|
398
|
+
- `--dsl-mappings <path>`: The YAML file defining your DSL mapping rules.
|
|
399
|
+
|
|
400
|
+
The YAML file containing the mappings must adhere to the following format:
|
|
401
|
+
```yaml
|
|
402
|
+
mappings:
|
|
403
|
+
|
|
404
|
+
- from_dsl_regex: "^StartFlow\\(([^)]*)\\)$"
|
|
405
|
+
to_dsl_pattern: "start {1}"
|
|
406
|
+
|
|
407
|
+
- from_dsl_regex: "^SetSlot\\(([^,]+),\\s*(.*)\\)$"
|
|
408
|
+
to_dsl_pattern: "set {1} {2}"
|
|
409
|
+
|
|
410
|
+
- from_dsl_regex: "Clarify\(([\"\'a-zA-Z0-9_, ]*)\)"
|
|
411
|
+
to_dsl_pattern: "clarify {1}"
|
|
412
|
+
input_separators:
|
|
413
|
+
- ","
|
|
414
|
+
- " "
|
|
415
|
+
output_separator: " "
|
|
416
|
+
|
|
417
|
+
# ... add more mappings here
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- `from_dsl_regex`: A regular expression (string) used to match the old DSL command.
|
|
422
|
+
Must include any necessary anchors (like ^ and $) and capturing groups ( ... ) for
|
|
423
|
+
dynamic parts.
|
|
424
|
+
- `to_dsl_pattern`: A string that contains placeholders like `{1}`, `{2}`, etc. Each
|
|
425
|
+
placeholder corresponds to a capturing group in from_dsl_regex, in order of
|
|
426
|
+
appearance.
|
|
427
|
+
- `input_separators`: Optional list of separators of the captured groups that can be replaced
|
|
428
|
+
with the `output_separator`
|
|
429
|
+
- `output_separator`: Output separator to replace separators from the list of `input_separators` in the captured group.
|
|
@@ -20,7 +20,7 @@ from rasa.dialogue_understanding_test.test_case_simulation.exception import (
|
|
|
20
20
|
)
|
|
21
21
|
from rasa.dialogue_understanding_test.utils import filter_metadata
|
|
22
22
|
from rasa.e2e_test.e2e_test_case import Fixture, Metadata
|
|
23
|
-
from rasa.shared.core.constants import
|
|
23
|
+
from rasa.shared.core.constants import SlotMappingType
|
|
24
24
|
from rasa.shared.core.events import BotUttered, SlotSet, UserUttered
|
|
25
25
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
26
26
|
from rasa.shared.nlu.constants import COMMANDS, ENTITIES, INTENT
|
|
@@ -328,8 +328,8 @@ class TestCaseTrackerSimulator:
|
|
|
328
328
|
command.extractor = SetSlotExtractor.COMMAND_PAYLOAD_READER.value
|
|
329
329
|
# Use the SetSlotExtractor.NLU extractor if the slot mapping type is
|
|
330
330
|
# not FROM_LLM.
|
|
331
|
-
elif SlotMappingType.FROM_LLM
|
|
332
|
-
mapping
|
|
331
|
+
elif SlotMappingType.FROM_LLM not in [
|
|
332
|
+
mapping.type for mapping in slot_definition.mappings
|
|
333
333
|
]:
|
|
334
334
|
command.extractor = SetSlotExtractor.NLU.value
|
|
335
335
|
|
rasa/model_service.py
CHANGED
|
@@ -61,6 +61,7 @@ def main() -> None:
|
|
|
61
61
|
The API server can receive requests to train models, run bots, and manage
|
|
62
62
|
the lifecycle of models and bots.
|
|
63
63
|
"""
|
|
64
|
+
import rasa.telemetry
|
|
64
65
|
import rasa.utils.licensing
|
|
65
66
|
|
|
66
67
|
log_level = logging.DEBUG
|
|
@@ -74,6 +75,9 @@ def main() -> None:
|
|
|
74
75
|
|
|
75
76
|
rasa.utils.licensing.validate_license_from_env()
|
|
76
77
|
|
|
78
|
+
rasa.telemetry.initialize_telemetry()
|
|
79
|
+
rasa.telemetry.initialize_error_reporting()
|
|
80
|
+
|
|
77
81
|
try:
|
|
78
82
|
model_api.prepare_working_directories()
|
|
79
83
|
except Exception as e:
|
rasa/model_training.py
CHANGED
|
@@ -352,34 +352,31 @@ async def _train_graph(
|
|
|
352
352
|
model_name = determine_model_name(fixed_model_name, training_type)
|
|
353
353
|
full_model_path = Path(output_path, model_name)
|
|
354
354
|
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
355
|
+
await trainer.train(
|
|
356
|
+
model_configuration,
|
|
357
|
+
file_importer,
|
|
358
|
+
full_model_path,
|
|
359
|
+
force_retraining=force_full_training,
|
|
360
|
+
is_finetuning=is_finetuning,
|
|
361
|
+
)
|
|
362
|
+
if remote_storage:
|
|
363
|
+
push_model_to_remote_storage(full_model_path, remote_storage)
|
|
364
|
+
if not keep_local_model_copy:
|
|
365
|
+
full_model_path.unlink()
|
|
366
|
+
structlogger.info(
|
|
367
|
+
"model_training.train.finished_training",
|
|
368
|
+
event_info=(
|
|
369
|
+
f"Your Rasa model {model_name} is trained "
|
|
370
|
+
f"and saved at remote storage provider '{remote_storage}'."
|
|
371
|
+
),
|
|
372
|
+
)
|
|
373
|
+
else:
|
|
374
|
+
structlogger.info(
|
|
375
|
+
"model_training.train.finished_training",
|
|
376
|
+
event_info=(
|
|
377
|
+
f"Your Rasa model is trained and saved at '{full_model_path}'."
|
|
378
|
+
),
|
|
364
379
|
)
|
|
365
|
-
if remote_storage:
|
|
366
|
-
push_model_to_remote_storage(full_model_path, remote_storage)
|
|
367
|
-
if not keep_local_model_copy:
|
|
368
|
-
full_model_path.unlink()
|
|
369
|
-
structlogger.info(
|
|
370
|
-
"model_training.train.finished_training",
|
|
371
|
-
event_info=(
|
|
372
|
-
f"Your Rasa model {model_name} is trained "
|
|
373
|
-
f"and saved at remote storage provider '{remote_storage}'."
|
|
374
|
-
),
|
|
375
|
-
)
|
|
376
|
-
else:
|
|
377
|
-
structlogger.info(
|
|
378
|
-
"model_training.train.finished_training",
|
|
379
|
-
event_info=(
|
|
380
|
-
f"Your Rasa model is trained and saved at '{full_model_path}'."
|
|
381
|
-
),
|
|
382
|
-
)
|
|
383
380
|
|
|
384
381
|
return TrainingResult(str(full_model_path), 0)
|
|
385
382
|
|
rasa/shared/core/constants.py
CHANGED
|
@@ -51,6 +51,8 @@ ACTION_TRIGGER_CHITCHAT = "action_trigger_chitchat"
|
|
|
51
51
|
ACTION_RESET_ROUTING = "action_reset_routing"
|
|
52
52
|
ACTION_HANGUP = "action_hangup"
|
|
53
53
|
ACTION_REPEAT_BOT_MESSAGES = "action_repeat_bot_messages"
|
|
54
|
+
ACTION_BLOCK_DIGRESSION = "action_block_digression"
|
|
55
|
+
ACTION_CONTINUE_DIGRESSION = "action_continue_digression"
|
|
54
56
|
|
|
55
57
|
ACTION_METADATA_EXECUTION_SUCCESS = "execution_success"
|
|
56
58
|
ACTION_METADATA_EXECUTION_ERROR_MESSAGE = "execution_error_message"
|
|
@@ -81,6 +83,8 @@ DEFAULT_ACTION_NAMES = [
|
|
|
81
83
|
ACTION_RESET_ROUTING,
|
|
82
84
|
ACTION_HANGUP,
|
|
83
85
|
ACTION_REPEAT_BOT_MESSAGES,
|
|
86
|
+
ACTION_BLOCK_DIGRESSION,
|
|
87
|
+
ACTION_CONTINUE_DIGRESSION,
|
|
84
88
|
]
|
|
85
89
|
|
|
86
90
|
ACTION_SHOULD_SEND_DOMAIN = "send_domain"
|
|
@@ -137,7 +141,10 @@ DEFAULT_SLOT_NAMES = {
|
|
|
137
141
|
|
|
138
142
|
SLOT_MAPPINGS = "mappings"
|
|
139
143
|
MAPPING_CONDITIONS = "conditions"
|
|
140
|
-
|
|
144
|
+
KEY_MAPPING_TYPE = "type"
|
|
145
|
+
KEY_ALLOW_NLU_CORRECTION = "allow_nlu_correction"
|
|
146
|
+
KEY_ACTION = "action"
|
|
147
|
+
KEY_RUN_ACTION_EVERY_TURN = "run_action_every_turn"
|
|
141
148
|
|
|
142
149
|
|
|
143
150
|
class SlotMappingType(Enum):
|
|
@@ -148,7 +155,7 @@ class SlotMappingType(Enum):
|
|
|
148
155
|
FROM_TRIGGER_INTENT = "from_trigger_intent"
|
|
149
156
|
FROM_TEXT = "from_text"
|
|
150
157
|
FROM_LLM = "from_llm"
|
|
151
|
-
|
|
158
|
+
CONTROLLED = "controlled"
|
|
152
159
|
|
|
153
160
|
def __str__(self) -> str:
|
|
154
161
|
"""Returns the string representation that should be used in config files."""
|
|
@@ -156,7 +163,21 @@ class SlotMappingType(Enum):
|
|
|
156
163
|
|
|
157
164
|
def is_predefined_type(self) -> bool:
|
|
158
165
|
"""Returns True if the mapping type is NLU-predefined."""
|
|
159
|
-
return not (
|
|
166
|
+
return not (
|
|
167
|
+
self == SlotMappingType.CONTROLLED or self == SlotMappingType.FROM_LLM
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
class SetSlotExtractor(Enum):
|
|
172
|
+
"""The extractors that can set a slot."""
|
|
173
|
+
|
|
174
|
+
LLM = "LLM"
|
|
175
|
+
COMMAND_PAYLOAD_READER = "CommandPayloadReader"
|
|
176
|
+
NLU = "NLU"
|
|
177
|
+
CUSTOM = "CUSTOM"
|
|
178
|
+
|
|
179
|
+
def __str__(self) -> str:
|
|
180
|
+
return self.value
|
|
160
181
|
|
|
161
182
|
|
|
162
183
|
# the keys for `State` (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
|
|
@@ -181,3 +202,7 @@ POLICY_NAME_RULE = "RulePolicy"
|
|
|
181
202
|
CLASSIFIER_NAME_FALLBACK = "FallbackClassifier"
|
|
182
203
|
|
|
183
204
|
POLICIES_THAT_EXTRACT_ENTITIES = {"TEDPolicy"}
|
|
205
|
+
|
|
206
|
+
# digression constants
|
|
207
|
+
KEY_ASK_CONFIRM_DIGRESSIONS = "ask_confirm_digressions"
|
|
208
|
+
KEY_BLOCK_DIGRESSIONS = "block_digressions"
|
rasa/shared/core/domain.py
CHANGED
|
@@ -46,10 +46,8 @@ from rasa.shared.constants import (
|
|
|
46
46
|
)
|
|
47
47
|
from rasa.shared.core.constants import (
|
|
48
48
|
ACTION_SHOULD_SEND_DOMAIN,
|
|
49
|
-
|
|
49
|
+
KEY_MAPPING_TYPE,
|
|
50
50
|
KNOWLEDGE_BASE_SLOT_NAMES,
|
|
51
|
-
MAPPING_CONDITIONS,
|
|
52
|
-
MAPPING_TYPE,
|
|
53
51
|
SLOT_MAPPINGS,
|
|
54
52
|
SlotMappingType,
|
|
55
53
|
)
|
|
@@ -290,8 +288,6 @@ class Domain:
|
|
|
290
288
|
responses = data.get(KEY_RESPONSES, {})
|
|
291
289
|
|
|
292
290
|
domain_slots = data.get(KEY_SLOTS, {})
|
|
293
|
-
if domain_slots:
|
|
294
|
-
rasa.shared.core.slot_mappings.validate_slot_mappings(domain_slots)
|
|
295
291
|
slots = cls.collect_slots(domain_slots)
|
|
296
292
|
domain_actions = data.get(KEY_ACTIONS, [])
|
|
297
293
|
actions = cls._collect_action_names(domain_actions)
|
|
@@ -596,7 +592,7 @@ class Domain:
|
|
|
596
592
|
),
|
|
597
593
|
)
|
|
598
594
|
slot_dict[slot_name][SLOT_MAPPINGS] = [
|
|
599
|
-
{
|
|
595
|
+
{KEY_MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
|
|
600
596
|
]
|
|
601
597
|
|
|
602
598
|
slot = slot_class(slot_name, **slot_dict[slot_name])
|
|
@@ -1569,21 +1565,18 @@ class Domain:
|
|
|
1569
1565
|
matching_entities = []
|
|
1570
1566
|
|
|
1571
1567
|
for mapping in slot.mappings:
|
|
1572
|
-
mapping_conditions = mapping.
|
|
1573
|
-
if mapping
|
|
1568
|
+
mapping_conditions = mapping.conditions
|
|
1569
|
+
if mapping.type != SlotMappingType.FROM_ENTITY or (
|
|
1574
1570
|
mapping_conditions
|
|
1575
|
-
and mapping_conditions[0].
|
|
1571
|
+
and mapping_conditions[0].active_loop is not None
|
|
1576
1572
|
):
|
|
1577
1573
|
continue
|
|
1578
1574
|
|
|
1579
1575
|
for entity in entities:
|
|
1580
1576
|
if (
|
|
1581
|
-
entity.get(ENTITY_ATTRIBUTE_TYPE)
|
|
1582
|
-
|
|
1583
|
-
and entity.get(
|
|
1584
|
-
== mapping.get(ENTITY_ATTRIBUTE_ROLE)
|
|
1585
|
-
and entity.get(ENTITY_ATTRIBUTE_GROUP)
|
|
1586
|
-
== mapping.get(ENTITY_ATTRIBUTE_GROUP)
|
|
1577
|
+
entity.get(ENTITY_ATTRIBUTE_TYPE) == mapping.entity
|
|
1578
|
+
and entity.get(ENTITY_ATTRIBUTE_ROLE) == mapping.role
|
|
1579
|
+
and entity.get(ENTITY_ATTRIBUTE_GROUP) == mapping.group
|
|
1587
1580
|
):
|
|
1588
1581
|
matching_entities.append(entity.get("value"))
|
|
1589
1582
|
|
|
@@ -2015,19 +2008,19 @@ class Domain:
|
|
|
2015
2008
|
is the total number of mappings which have conditions attached.
|
|
2016
2009
|
"""
|
|
2017
2010
|
total_mappings = 0
|
|
2018
|
-
|
|
2011
|
+
controlled_mappings = 0
|
|
2019
2012
|
conditional_mappings = 0
|
|
2020
2013
|
|
|
2021
2014
|
for slot in self.slots:
|
|
2022
2015
|
total_mappings += len(slot.mappings)
|
|
2023
2016
|
for mapping in slot.mappings:
|
|
2024
|
-
if mapping
|
|
2025
|
-
|
|
2017
|
+
if mapping.type == SlotMappingType.CONTROLLED:
|
|
2018
|
+
controlled_mappings += 1
|
|
2026
2019
|
|
|
2027
|
-
if
|
|
2020
|
+
if mapping.conditions:
|
|
2028
2021
|
conditional_mappings += 1
|
|
2029
2022
|
|
|
2030
|
-
return
|
|
2023
|
+
return total_mappings, controlled_mappings, conditional_mappings
|
|
2031
2024
|
|
|
2032
2025
|
def does_custom_action_explicitly_need_domain(self, action_name: Text) -> bool:
|
|
2033
2026
|
"""Assert if action has explicitly stated that it needs domain.
|