rasa-pro 3.12.0.dev12__py3-none-any.whl → 3.12.0.dev13__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 (71) 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 +53 -79
  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_manager/warm_rasa_process.py +0 -1
  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/utils/llm.py +1 -1
  62. rasa/shared/utils/schemas/domain.yml +0 -1
  63. rasa/telemetry.py +43 -13
  64. rasa/utils/common.py +0 -1
  65. rasa/validator.py +189 -82
  66. rasa/version.py +1 -1
  67. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/METADATA +1 -1
  68. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/RECORD +71 -67
  69. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/NOTICE +0 -0
  70. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/WHEEL +0 -0
  71. {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0.dev13.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,17 @@
1
1
  version: "3.1"
2
2
  responses:
3
3
 
4
+ utter_ask_continue_previous_flow:
5
+ - text: "Confirm if you would like to continue with the initial topic: {{context.interrupted_flow_id}}?"
6
+ metadata:
7
+ rephrase: True
8
+ template: jinja
9
+ buttons:
10
+ - title: Continue with the previous topic.
11
+ payload: /SetSlots(continue_previous_flow=True)
12
+ - title: Switch to new topic.
13
+ payload: /SetSlots(continue_previous_flow=False)
14
+
4
15
  utter_ask_rephrase:
5
16
  - text: I’m sorry I am unable to understand you, could you please rephrase?
6
17
 
@@ -9,6 +20,20 @@ responses:
9
20
  metadata:
10
21
  rephrase: True
11
22
 
23
+ utter_block_digressions:
24
+ - text: "We can look into {{ context.interrupting_flow_id }} later. Let's focus on the current topic: {{ context.interrupted_flow_id }}."
25
+ metadata:
26
+ rephrase: True
27
+ template: jinja
28
+ - text: "Let's continue with the current topic: {{ context.interrupted_flow_id }}."
29
+ condition:
30
+ - type: slot
31
+ name: continue_previous_flow
32
+ value: True
33
+ metadata:
34
+ rephrase: True
35
+ template: jinja
36
+
12
37
  utter_boolean_slot_rejection:
13
38
  - text: "Sorry, the value you provided, `{{value}}`, is not valid. Please respond with a valid value."
14
39
  metadata:
@@ -35,8 +60,14 @@ responses:
35
60
  rephrase: True
36
61
  template: jinja
37
62
 
63
+ utter_continue_interruption:
64
+ - text: "Let's continue with the chosen topic instead: {{ context.interrupting_flow_id }}."
65
+ metadata:
66
+ rephrase: True
67
+ template: jinja
68
+
38
69
  utter_corrected_previous_input:
39
- - text: "Ok, I am updating {{ context.corrected_slots.keys()|join(', ') }} to {{ context.corrected_slots.values()|join(', ') }} respectively."
70
+ - text: "Ok, I am updating {{ context.corrected_slots.keys()|join(', ') }} to {{ context.new_slot_values | join(', ') }} respectively."
40
71
  metadata:
41
72
  rephrase: True
42
73
  template: jinja
@@ -119,7 +150,10 @@ slots:
119
150
  type: float
120
151
  initial_value: 0.0
121
152
  max_value: 1000000
122
-
153
+ continue_previous_flow:
154
+ type: bool
155
+ mappings:
156
+ - type: from_llm
123
157
 
124
158
  flows:
125
159
  pattern_cancel_flow:
@@ -163,6 +197,7 @@ flows:
163
197
  steps:
164
198
  - action: action_clarify_flows
165
199
  - action: utter_clarification_options_rasa
200
+ - action: action_listen
166
201
 
167
202
  pattern_code_change:
168
203
  description: Conversation repair flow for cleaning the stack after an assistant update
@@ -212,6 +247,31 @@ flows:
212
247
  next: END
213
248
  - else: END
214
249
 
250
+ pattern_handle_digressions:
251
+ description: Conversation repair flow for handling digressions
252
+ name: pattern handle digressions
253
+ steps:
254
+ - noop: true
255
+ id: branching
256
+ next:
257
+ - if: context.ask_confirm_digressions contains context.interrupting_flow_id
258
+ then: continue_previous_flow
259
+ - if: context.block_digressions contains context.interrupting_flow_id
260
+ then: block_digression
261
+ - else: continue_digression
262
+ - id: continue_previous_flow
263
+ collect: continue_previous_flow
264
+ next:
265
+ - if: slots.continue_previous_flow
266
+ then: block_digression
267
+ - else: continue_digression
268
+ - id: block_digression
269
+ action: action_block_digression
270
+ next: END
271
+ - id: continue_digression
272
+ action: action_continue_digression
273
+ next: END
274
+
215
275
  pattern_human_handoff:
216
276
  description: Conversation repair flow for switching users to a human agent if their request can't be handled
217
277
  name: pattern human handoff
@@ -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
 
@@ -46,7 +46,6 @@ def _create_warm_rasa_process() -> WarmRasaProcess:
46
46
  ]
47
47
 
48
48
  envs = os.environ.copy()
49
- envs["RASA_TELEMETRY_ENABLED"] = "false"
50
49
  envs[RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME] = "true"
51
50
 
52
51
  log_id = uuid.uuid4().hex
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"