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.

Files changed (72) hide show
  1. rasa/cli/inspect.py +20 -1
  2. rasa/cli/shell.py +3 -3
  3. rasa/core/actions/action.py +20 -7
  4. rasa/core/actions/action_handle_digressions.py +142 -0
  5. rasa/core/actions/forms.py +10 -5
  6. rasa/core/channels/__init__.py +2 -0
  7. rasa/core/channels/voice_ready/audiocodes.py +42 -23
  8. rasa/core/channels/voice_stream/browser_audio.py +1 -0
  9. rasa/core/channels/voice_stream/call_state.py +7 -1
  10. rasa/core/channels/voice_stream/genesys.py +331 -0
  11. rasa/core/channels/voice_stream/tts/azure.py +2 -1
  12. rasa/core/channels/voice_stream/tts/cartesia.py +16 -3
  13. rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
  14. rasa/core/channels/voice_stream/voice_channel.py +2 -1
  15. rasa/core/migrate.py +2 -2
  16. rasa/core/policies/flows/flow_executor.py +36 -42
  17. rasa/core/run.py +4 -3
  18. rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
  19. rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
  20. rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
  21. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
  22. rasa/dialogue_understanding/commands/clarify_command.py +2 -2
  23. rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
  24. rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
  25. rasa/dialogue_understanding/commands/human_handoff_command.py +2 -2
  26. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  27. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
  28. rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
  29. rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
  30. rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
  31. rasa/dialogue_understanding/commands/utils.py +1 -1
  32. rasa/dialogue_understanding/constants.py +1 -0
  33. rasa/dialogue_understanding/generator/command_generator.py +110 -73
  34. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  35. rasa/dialogue_understanding/generator/llm_based_command_generator.py +161 -3
  36. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
  37. rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
  38. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +40 -40
  39. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
  40. rasa/dialogue_understanding/generator/utils.py +32 -1
  41. rasa/dialogue_understanding/patterns/correction.py +13 -1
  42. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
  43. rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
  44. rasa/dialogue_understanding/processor/command_processor.py +115 -28
  45. rasa/dialogue_understanding/utils.py +31 -0
  46. rasa/dialogue_understanding_test/README.md +50 -0
  47. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
  48. rasa/model_service.py +4 -0
  49. rasa/model_training.py +24 -27
  50. rasa/shared/core/constants.py +28 -3
  51. rasa/shared/core/domain.py +13 -20
  52. rasa/shared/core/events.py +13 -2
  53. rasa/shared/core/flows/flow.py +17 -0
  54. rasa/shared/core/flows/flows_yaml_schema.json +38 -0
  55. rasa/shared/core/flows/steps/collect.py +18 -1
  56. rasa/shared/core/flows/utils.py +16 -1
  57. rasa/shared/core/slot_mappings.py +144 -108
  58. rasa/shared/core/slots.py +23 -2
  59. rasa/shared/core/trackers.py +3 -1
  60. rasa/shared/nlu/constants.py +1 -0
  61. rasa/shared/providers/llm/_base_litellm_client.py +0 -40
  62. rasa/shared/utils/llm.py +1 -86
  63. rasa/shared/utils/schemas/domain.yml +0 -1
  64. rasa/telemetry.py +43 -13
  65. rasa/utils/common.py +0 -1
  66. rasa/validator.py +189 -82
  67. rasa/version.py +1 -1
  68. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/METADATA +1 -1
  69. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/RECORD +72 -68
  70. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/NOTICE +0 -0
  71. {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/WHEEL +0 -0
  72. {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(slot: Slot, command: SetSlotCommand) -> bool:
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 slot_mappings:
672
- slot_mappings = [{"type": SlotMappingType.FROM_LLM.value}]
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
- should_be_set_by_llm = (
680
- command.extractor == SetSlotExtractor.LLM.value
681
- and mapping_type == SlotMappingType.FROM_LLM
682
- )
683
- should_be_set_by_nlu = (
684
- command.extractor == SetSlotExtractor.NLU.value
685
- and mapping_type.is_predefined_type()
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
- if should_be_set_by_llm or should_be_set_by_nlu:
689
- # if the extractor matches the mapping type, we can continue
690
- # setting the slot
691
- break
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
- structlogger.debug(
694
- "command_processor.clean_up_slot_command.skip_command.extractor_"
695
- "does_not_match_slot_mapping",
696
- extractor=command.extractor,
697
- slot_name=slot.name,
698
- mapping_type=mapping_type.value,
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 MAPPING_TYPE, SlotMappingType
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.value not in [
332
- mapping[MAPPING_TYPE] for mapping in slot_definition.mappings
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
- with telemetry.track_model_training(
356
- file_importer, model_type=training_type.model_type
357
- ):
358
- await trainer.train(
359
- model_configuration,
360
- file_importer,
361
- full_model_path,
362
- force_retraining=force_full_training,
363
- is_finetuning=is_finetuning,
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
 
@@ -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
- MAPPING_TYPE = "type"
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
- CUSTOM = "custom"
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 (self == SlotMappingType.CUSTOM or self == SlotMappingType.FROM_LLM)
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"
@@ -46,10 +46,8 @@ from rasa.shared.constants import (
46
46
  )
47
47
  from rasa.shared.core.constants import (
48
48
  ACTION_SHOULD_SEND_DOMAIN,
49
- ACTIVE_LOOP,
49
+ KEY_MAPPING_TYPE,
50
50
  KNOWLEDGE_BASE_SLOT_NAMES,
51
- MAPPING_CONDITIONS,
52
- MAPPING_TYPE,
53
51
  SLOT_MAPPINGS,
54
52
  SlotMappingType,
55
53
  )
@@ -290,8 +288,6 @@ class Domain:
290
288
  responses = data.get(KEY_RESPONSES, {})
291
289
 
292
290
  domain_slots = data.get(KEY_SLOTS, {})
293
- if domain_slots:
294
- rasa.shared.core.slot_mappings.validate_slot_mappings(domain_slots)
295
291
  slots = cls.collect_slots(domain_slots)
296
292
  domain_actions = data.get(KEY_ACTIONS, [])
297
293
  actions = cls._collect_action_names(domain_actions)
@@ -596,7 +592,7 @@ class Domain:
596
592
  ),
597
593
  )
598
594
  slot_dict[slot_name][SLOT_MAPPINGS] = [
599
- {MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
595
+ {KEY_MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
600
596
  ]
601
597
 
602
598
  slot = slot_class(slot_name, **slot_dict[slot_name])
@@ -1569,21 +1565,18 @@ class Domain:
1569
1565
  matching_entities = []
1570
1566
 
1571
1567
  for mapping in slot.mappings:
1572
- mapping_conditions = mapping.get(MAPPING_CONDITIONS)
1573
- if mapping[MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY) or (
1568
+ mapping_conditions = mapping.conditions
1569
+ if mapping.type != SlotMappingType.FROM_ENTITY or (
1574
1570
  mapping_conditions
1575
- and mapping_conditions[0].get(ACTIVE_LOOP) is not None
1571
+ and mapping_conditions[0].active_loop is not None
1576
1572
  ):
1577
1573
  continue
1578
1574
 
1579
1575
  for entity in entities:
1580
1576
  if (
1581
- entity.get(ENTITY_ATTRIBUTE_TYPE)
1582
- == mapping.get(ENTITY_ATTRIBUTE_TYPE)
1583
- and entity.get(ENTITY_ATTRIBUTE_ROLE)
1584
- == mapping.get(ENTITY_ATTRIBUTE_ROLE)
1585
- and entity.get(ENTITY_ATTRIBUTE_GROUP)
1586
- == mapping.get(ENTITY_ATTRIBUTE_GROUP)
1577
+ entity.get(ENTITY_ATTRIBUTE_TYPE) == mapping.entity
1578
+ and entity.get(ENTITY_ATTRIBUTE_ROLE) == mapping.role
1579
+ and entity.get(ENTITY_ATTRIBUTE_GROUP) == mapping.group
1587
1580
  ):
1588
1581
  matching_entities.append(entity.get("value"))
1589
1582
 
@@ -2015,19 +2008,19 @@ class Domain:
2015
2008
  is the total number of mappings which have conditions attached.
2016
2009
  """
2017
2010
  total_mappings = 0
2018
- custom_mappings = 0
2011
+ controlled_mappings = 0
2019
2012
  conditional_mappings = 0
2020
2013
 
2021
2014
  for slot in self.slots:
2022
2015
  total_mappings += len(slot.mappings)
2023
2016
  for mapping in slot.mappings:
2024
- if mapping[MAPPING_TYPE] == str(SlotMappingType.CUSTOM):
2025
- custom_mappings += 1
2017
+ if mapping.type == SlotMappingType.CONTROLLED:
2018
+ controlled_mappings += 1
2026
2019
 
2027
- if MAPPING_CONDITIONS in mapping:
2020
+ if mapping.conditions:
2028
2021
  conditional_mappings += 1
2029
2022
 
2030
- return (total_mappings, custom_mappings, conditional_mappings)
2023
+ return total_mappings, controlled_mappings, conditional_mappings
2031
2024
 
2032
2025
  def does_custom_action_explicitly_need_domain(self, action_name: Text) -> bool:
2033
2026
  """Assert if action has explicitly stated that it needs domain.