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
@@ -48,7 +48,7 @@ class ChangeFlowCommand(Command):
48
48
 
49
49
  def to_dsl(self) -> str:
50
50
  """Converts the command to a DSL string."""
51
- return "change"
51
+ return "ChangeFlow()"
52
52
 
53
53
  @staticmethod
54
54
  def from_dsl(match: re.Match, **kwargs: Any) -> ChangeFlowCommand:
@@ -57,4 +57,4 @@ class ChangeFlowCommand(Command):
57
57
 
58
58
  @staticmethod
59
59
  def regex_pattern() -> str:
60
- return r"^change"
60
+ return r"ChangeFlow\(\)"
@@ -59,7 +59,7 @@ class ChitChatAnswerCommand(FreeFormAnswerCommand):
59
59
 
60
60
  def to_dsl(self) -> str:
61
61
  """Converts the command to a DSL string."""
62
- return "chat"
62
+ return "ChitChat()"
63
63
 
64
64
  @classmethod
65
65
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> ChitChatAnswerCommand:
@@ -68,4 +68,4 @@ class ChitChatAnswerCommand(FreeFormAnswerCommand):
68
68
 
69
69
  @staticmethod
70
70
  def regex_pattern() -> str:
71
- return r"^chat$"
71
+ return r"ChitChat\(\)"
@@ -89,7 +89,7 @@ class ClarifyCommand(Command):
89
89
 
90
90
  def to_dsl(self) -> str:
91
91
  """Converts the command to a DSL string."""
92
- return f"clarify {' '.join(self.options)}"
92
+ return f"Clarify({', '.join(self.options)})"
93
93
 
94
94
  @classmethod
95
95
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> Optional[ClarifyCommand]:
@@ -99,4 +99,4 @@ class ClarifyCommand(Command):
99
99
 
100
100
  @staticmethod
101
101
  def regex_pattern() -> str:
102
- return r"^clarify([\"\'a-zA-Z0-9_, ]*)$"
102
+ return r"Clarify\(([\"\'a-zA-Z0-9_, ]*)\)"
@@ -31,6 +31,7 @@ class CorrectedSlot:
31
31
 
32
32
  name: str
33
33
  value: Any
34
+ filled_by: Optional[str] = None
34
35
 
35
36
 
36
37
  @dataclass
@@ -54,7 +55,9 @@ class CorrectSlotsCommand(Command):
54
55
  try:
55
56
  return CorrectSlotsCommand(
56
57
  corrected_slots=[
57
- CorrectedSlot(s["name"], value=s["value"])
58
+ CorrectedSlot(
59
+ s["name"], value=s["value"], filled_by=s.get("filled_by", None)
60
+ )
58
61
  for s in data["corrected_slots"]
59
62
  ]
60
63
  )
@@ -135,7 +138,10 @@ class CorrectSlotsCommand(Command):
135
138
  proposed_slots = {}
136
139
  for corrected_slot in self.corrected_slots:
137
140
  if tracker.get_slot(corrected_slot.name) != corrected_slot.value:
138
- proposed_slots[corrected_slot.name] = corrected_slot.value
141
+ proposed_slots[corrected_slot.name] = {
142
+ "value": corrected_slot.value,
143
+ "filled_by": corrected_slot.filled_by,
144
+ }
139
145
  else:
140
146
  structlogger.debug(
141
147
  "command_executor.skip_correction.slot_already_set", command=self
@@ -240,6 +246,9 @@ class CorrectSlotsCommand(Command):
240
246
  corrected_slots=proposed_slots,
241
247
  reset_flow_id=earliest_collect.flow_id if earliest_collect else None,
242
248
  reset_step_id=earliest_collect.step.id if earliest_collect else None,
249
+ new_slot_values=[
250
+ value.get("value") for slot, value in proposed_slots.items()
251
+ ],
243
252
  )
244
253
 
245
254
  def run_command_on_tracker(
@@ -0,0 +1,150 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from typing import Any, Dict, List
5
+
6
+ import structlog
7
+
8
+ from rasa.dialogue_understanding.commands.command import Command
9
+ from rasa.dialogue_understanding.patterns.cannot_handle import (
10
+ CannotHandlePatternFlowStackFrame,
11
+ )
12
+ from rasa.dialogue_understanding.patterns.handle_digressions import (
13
+ HandleDigressionsPatternFlowStackFrame,
14
+ )
15
+ from rasa.dialogue_understanding.stack.utils import (
16
+ top_flow_frame,
17
+ user_flows_on_the_stack,
18
+ )
19
+ from rasa.shared.core.events import Event
20
+ from rasa.shared.core.flows import FlowsList
21
+ from rasa.shared.core.flows.steps import CollectInformationFlowStep
22
+ from rasa.shared.core.flows.utils import ALL_LABEL
23
+ from rasa.shared.core.trackers import DialogueStateTracker
24
+ from rasa.shared.nlu.constants import HANDLE_DIGRESSIONS_COMMAND
25
+
26
+ structlogger = structlog.get_logger()
27
+
28
+
29
+ @dataclass
30
+ class HandleDigressionsCommand(Command):
31
+ """A command to handle digressions during an active flow."""
32
+
33
+ flow: str
34
+ """The interrupting flow."""
35
+
36
+ @classmethod
37
+ def command(cls) -> str:
38
+ """Returns the command type."""
39
+ return HANDLE_DIGRESSIONS_COMMAND
40
+
41
+ @classmethod
42
+ def from_dict(cls, data: Dict[str, Any]) -> HandleDigressionsCommand:
43
+ """Converts the dictionary to a command.
44
+
45
+ Returns:
46
+ The converted dictionary.
47
+ """
48
+ try:
49
+ return HandleDigressionsCommand(flow=data["flow"])
50
+ except KeyError as e:
51
+ raise ValueError(
52
+ f"Missing parameter '{e}' while parsing HandleDigressionsCommand."
53
+ ) from e
54
+
55
+ def run_command_on_tracker(
56
+ self,
57
+ tracker: DialogueStateTracker,
58
+ all_flows: FlowsList,
59
+ original_tracker: DialogueStateTracker,
60
+ ) -> List[Event]:
61
+ """Runs the command on the tracker.
62
+
63
+ Args:
64
+ tracker: The tracker to run the command on.
65
+ all_flows: All flows in the assistant.
66
+ original_tracker: The tracker before any command was executed.
67
+
68
+ Returns:
69
+ The events to apply to the tracker.
70
+ """
71
+ stack = tracker.stack
72
+ original_stack = original_tracker.stack
73
+
74
+ if self.flow in user_flows_on_the_stack(stack):
75
+ structlogger.debug(
76
+ "command_executor.skip_command.already_started_flow", command=self
77
+ )
78
+ return []
79
+ elif self.flow not in all_flows.flow_ids:
80
+ structlogger.debug(
81
+ "command_executor.push_cannot_handle.start_invalid_flow_id",
82
+ command=self,
83
+ )
84
+ stack.push(CannotHandlePatternFlowStackFrame())
85
+ return tracker.create_stack_updated_events(stack)
86
+
87
+ # this allows to include called user flows in the stack search
88
+ latest_user_frame = top_flow_frame(original_stack, ignore_call_frames=False)
89
+
90
+ if latest_user_frame is None:
91
+ structlogger.debug(
92
+ "command_executor.skip_command.no_top_flow", command=self
93
+ )
94
+ return []
95
+
96
+ original_top_flow = latest_user_frame.flow(all_flows)
97
+ current_step = original_top_flow.step_by_id(latest_user_frame.step_id)
98
+ if not isinstance(current_step, CollectInformationFlowStep):
99
+ structlogger.debug(
100
+ "command_executor.skip_command.not_at_a_collect_step", command=self
101
+ )
102
+ return []
103
+
104
+ ask_confirm_digressions = set(
105
+ current_step.ask_confirm_digressions
106
+ + original_top_flow.ask_confirm_digressions
107
+ )
108
+
109
+ block_digressions = set(
110
+ current_step.block_digressions + original_top_flow.block_digressions
111
+ )
112
+
113
+ if block_digressions:
114
+ if ALL_LABEL in block_digressions:
115
+ block_digressions.remove(ALL_LABEL)
116
+ block_digressions.add(self.flow)
117
+
118
+ if ask_confirm_digressions:
119
+ if ALL_LABEL in ask_confirm_digressions:
120
+ ask_confirm_digressions.remove(ALL_LABEL)
121
+ ask_confirm_digressions.add(self.flow)
122
+
123
+ structlogger.debug(
124
+ "command_executor.push_handle_digressions",
125
+ interrupting_flow_id=self.flow,
126
+ interrupted_flow_id=original_top_flow.id,
127
+ interrupted_step_id=current_step.id,
128
+ ask_confirm_digressions=ask_confirm_digressions,
129
+ block_digressions=block_digressions,
130
+ )
131
+ stack.push(
132
+ HandleDigressionsPatternFlowStackFrame(
133
+ interrupting_flow_id=self.flow,
134
+ interrupted_flow_id=original_top_flow.id,
135
+ interrupted_step_id=current_step.id,
136
+ ask_confirm_digressions=ask_confirm_digressions,
137
+ block_digressions=block_digressions,
138
+ )
139
+ )
140
+
141
+ return tracker.create_stack_updated_events(stack)
142
+
143
+ def __hash__(self) -> int:
144
+ return hash(self.flow)
145
+
146
+ def __eq__(self, other: object) -> bool:
147
+ if not isinstance(other, HandleDigressionsCommand):
148
+ return False
149
+
150
+ return other.flow == self.flow
@@ -66,7 +66,7 @@ class HumanHandoffCommand(Command):
66
66
 
67
67
  def to_dsl(self) -> str:
68
68
  """Converts the command to a DSL string."""
69
- return "hand over"
69
+ return "HumanHandoff()"
70
70
 
71
71
  @classmethod
72
72
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> HumanHandoffCommand:
@@ -75,4 +75,4 @@ class HumanHandoffCommand(Command):
75
75
 
76
76
  @staticmethod
77
77
  def regex_pattern() -> str:
78
- return r"^hand over$"
78
+ return r"HumanHandoff\(\)"
@@ -59,7 +59,7 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
59
59
 
60
60
  def to_dsl(self) -> str:
61
61
  """Converts the command to a DSL string."""
62
- return "search"
62
+ return "SearchAndReply()"
63
63
 
64
64
  @classmethod
65
65
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> KnowledgeAnswerCommand:
@@ -68,4 +68,4 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
68
68
 
69
69
  @staticmethod
70
70
  def regex_pattern() -> str:
71
- return r"^search$"
71
+ return r"SearchAndReply\(\)"
@@ -60,7 +60,7 @@ class RepeatBotMessagesCommand(Command):
60
60
 
61
61
  def to_dsl(self) -> str:
62
62
  """Converts the command to a DSL string."""
63
- return "repeat message"
63
+ return "RepeatLastBotMessages()"
64
64
 
65
65
  @classmethod
66
66
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> RepeatBotMessagesCommand:
@@ -69,4 +69,4 @@ class RepeatBotMessagesCommand(Command):
69
69
 
70
70
  @staticmethod
71
71
  def regex_pattern() -> str:
72
- return r"^repeat message$"
72
+ return r"RepeatLastBotMessages\(\)"
@@ -2,7 +2,6 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from dataclasses import dataclass
5
- from enum import Enum
6
5
  from typing import Any, Dict, List
7
6
 
8
7
  import structlog
@@ -19,6 +18,7 @@ from rasa.dialogue_understanding.stack.utils import (
19
18
  get_collect_steps_excluding_ask_before_filling_for_active_flow,
20
19
  )
21
20
  from rasa.shared.constants import ROUTE_TO_CALM_SLOT
21
+ from rasa.shared.core.constants import SetSlotExtractor
22
22
  from rasa.shared.core.events import Event, SlotSet
23
23
  from rasa.shared.core.flows import FlowsList
24
24
  from rasa.shared.core.trackers import DialogueStateTracker
@@ -27,17 +27,6 @@ from rasa.shared.nlu.constants import SET_SLOT_COMMAND
27
27
  structlogger = structlog.get_logger()
28
28
 
29
29
 
30
- class SetSlotExtractor(Enum):
31
- """The extractors that can set a slot."""
32
-
33
- LLM = "LLM"
34
- COMMAND_PAYLOAD_READER = "CommandPayloadReader"
35
- NLU = "NLU"
36
-
37
- def __str__(self) -> str:
38
- return self.value
39
-
40
-
41
30
  def get_flows_predicted_to_start_from_tracker(
42
31
  tracker: DialogueStateTracker,
43
32
  ) -> List[str]:
@@ -137,6 +126,7 @@ class SetSlotCommand(Command):
137
126
  in {
138
127
  SetSlotExtractor.LLM.value,
139
128
  SetSlotExtractor.COMMAND_PAYLOAD_READER.value,
129
+ SetSlotExtractor.NLU.value,
140
130
  }
141
131
  ):
142
132
  # Get the other predicted flows from the most recent message on the tracker.
@@ -154,7 +144,9 @@ class SetSlotCommand(Command):
154
144
  return []
155
145
 
156
146
  structlogger.debug("command_executor.set_slot", command=self)
157
- return [SlotSet(self.name, slot.coerce_value(self.value))]
147
+ return [
148
+ SlotSet(self.name, slot.coerce_value(self.value), filled_by=self.extractor)
149
+ ]
158
150
 
159
151
  def __hash__(self) -> int:
160
152
  return hash(self.value) + hash(self.name)
@@ -170,7 +162,7 @@ class SetSlotCommand(Command):
170
162
 
171
163
  def to_dsl(self) -> str:
172
164
  """Converts the command to a DSL string."""
173
- return f"set {self.name} {self.value}"
165
+ return f"SetSlot({self.name}, {self.value})"
174
166
 
175
167
  @classmethod
176
168
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> SetSlotCommand:
@@ -181,4 +173,4 @@ class SetSlotCommand(Command):
181
173
 
182
174
  @staticmethod
183
175
  def regex_pattern() -> str:
184
- return r"""^set ['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]? ['"]?(.+?)['"]?$"""
176
+ return r"""SetSlot\(['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]?, ?['"]?(.*)['"]?\)"""
@@ -75,7 +75,7 @@ class SkipQuestionCommand(Command):
75
75
 
76
76
  def to_dsl(self) -> str:
77
77
  """Converts the command to a DSL string."""
78
- return "skip"
78
+ return "SkipQuestion()"
79
79
 
80
80
  @classmethod
81
81
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> SkipQuestionCommand:
@@ -84,4 +84,4 @@ class SkipQuestionCommand(Command):
84
84
 
85
85
  @staticmethod
86
86
  def regex_pattern() -> str:
87
- return r"^skip$"
87
+ return r"SkipQuestion\(\)"
@@ -7,6 +7,11 @@ from typing import Any, Dict, List, Optional
7
7
  import structlog
8
8
 
9
9
  from rasa.dialogue_understanding.commands.command import Command
10
+ from rasa.dialogue_understanding.patterns.clarify import FLOW_PATTERN_CLARIFICATION
11
+ from rasa.dialogue_understanding.patterns.continue_interrupted import (
12
+ ContinueInterruptedPatternFlowStackFrame,
13
+ )
14
+ from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
10
15
  from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
11
16
  FlowStackFrameType,
12
17
  UserFlowStackFrame,
@@ -68,6 +73,10 @@ class StartFlowCommand(Command):
68
73
  applied_events: List[Event] = []
69
74
 
70
75
  if self.flow in user_flows_on_the_stack(stack):
76
+ top_frame = stack.top()
77
+ if top_frame is not None and top_frame.type() == FLOW_PATTERN_CLARIFICATION:
78
+ return self.change_flow_frame_position_in_the_stack(stack, tracker)
79
+
71
80
  structlogger.debug(
72
81
  "command_executor.skip_command.already_started_flow", command=self
73
82
  )
@@ -110,7 +119,7 @@ class StartFlowCommand(Command):
110
119
 
111
120
  def to_dsl(self) -> str:
112
121
  """Converts the command to a DSL string."""
113
- return f"start {self.flow}"
122
+ return f"StartFlow({self.flow})"
114
123
 
115
124
  @classmethod
116
125
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> Optional[StartFlowCommand]:
@@ -119,4 +128,36 @@ class StartFlowCommand(Command):
119
128
 
120
129
  @staticmethod
121
130
  def regex_pattern() -> str:
122
- return r"^start ['\"]?([a-zA-Z0-9_-]+)['\"]?$"
131
+ return r"StartFlow\(['\"]?([a-zA-Z0-9_-]+)['\"]?\)"
132
+
133
+ def change_flow_frame_position_in_the_stack(
134
+ self, stack: DialogueStack, tracker: DialogueStateTracker
135
+ ) -> List[Event]:
136
+ """Changes the position of the flow frame in the stack.
137
+
138
+ This is a special case when pattern clarification is the active flow and
139
+ the same flow is selected to start. In this case, the existing flow frame
140
+ should be moved up in the stack.
141
+ """
142
+ frames = stack.frames[:]
143
+
144
+ for idx, frame in enumerate(frames):
145
+ if isinstance(frame, UserFlowStackFrame) and frame.flow_id == self.flow:
146
+ structlogger.debug(
147
+ "command_executor.change_flow_position_during_clarification",
148
+ command=self,
149
+ index=idx,
150
+ )
151
+ # pop the continue interrupted flow frame if it exists
152
+ next_frame = frames[idx + 1] if idx + 1 < len(frames) else None
153
+ if (
154
+ isinstance(next_frame, ContinueInterruptedPatternFlowStackFrame)
155
+ and next_frame.previous_flow_name == self.flow
156
+ ):
157
+ stack.frames.pop(idx + 1)
158
+ # move up the existing flow from the stack
159
+ stack.frames.pop(idx)
160
+ stack.push(frame)
161
+ return tracker.create_stack_updated_events(stack)
162
+
163
+ return []
@@ -27,7 +27,7 @@ def extract_cleaned_options(options_str: str) -> List[str]:
27
27
  """Extract and clean options from a string."""
28
28
  return sorted(
29
29
  opt.strip().strip('"').strip("'")
30
- for opt in options_str.split(" ")
30
+ for opt in options_str.split(",")
31
31
  if opt.strip()
32
32
  )
33
33
 
@@ -1 +1,2 @@
1
1
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME = "RASA_RECORD_COMMANDS_AND_PROMPTS"
2
+ KEY_MINIMIZE_NUM_CALLS = "minimize_num_calls"
@@ -1,23 +1,24 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Dict, List, Optional, Text
2
+ from typing import Any, Dict, List, Optional, Set, Text, Tuple
3
3
 
4
4
  import structlog
5
5
 
6
6
  from rasa.dialogue_understanding.commands import (
7
7
  Command,
8
+ CorrectSlotsCommand,
8
9
  ErrorCommand,
9
10
  SetSlotCommand,
10
11
  StartFlowCommand,
11
12
  )
12
- from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
13
+ from rasa.dialogue_understanding.utils import (
14
+ _handle_via_nlu_in_coexistence,
15
+ )
13
16
  from rasa.shared.constants import (
14
17
  RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_EMPTY,
15
18
  RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_TOO_LONG,
16
19
  )
17
- from rasa.shared.core.constants import SlotMappingType
18
20
  from rasa.shared.core.domain import Domain
19
21
  from rasa.shared.core.flows import FlowsList
20
- from rasa.shared.core.slot_mappings import SlotFillingManager
21
22
  from rasa.shared.core.trackers import DialogueStateTracker
22
23
  from rasa.shared.nlu.constants import (
23
24
  COMMANDS,
@@ -92,9 +93,9 @@ class CommandGenerator:
92
93
  )
93
94
 
94
95
  for message in messages:
95
- if message.get(COMMANDS):
96
- # do not overwrite commands if they are already present
97
- # i.e. another command generator already predicted commands
96
+ if _handle_via_nlu_in_coexistence(tracker, message):
97
+ # Skip running the CALM pipeline if the message should
98
+ # be handled by the NLU-based system in a coexistence mode.
98
99
  continue
99
100
 
100
101
  commands = await self._evaluate_and_predict(
@@ -106,9 +107,6 @@ class CommandGenerator:
106
107
  commands = self._check_commands_against_startable_flows(
107
108
  commands, startable_flows
108
109
  )
109
- commands = self._check_commands_against_slot_mappings(
110
- commands, tracker, domain
111
- )
112
110
  commands_dicts = [command.as_dict() for command in commands]
113
111
  message.set(COMMANDS, commands_dicts, add_to_output=True)
114
112
 
@@ -202,6 +200,94 @@ class CommandGenerator:
202
200
  """
203
201
  raise NotImplementedError()
204
202
 
203
+ def _check_commands_overlap(
204
+ self, prior_commands: List[Command], commands: List[Command]
205
+ ) -> List[Command]:
206
+ """Check if there is overlap between the prior commands and the current ones.
207
+
208
+ Args:
209
+ prior_commands: The prior commands.
210
+ commands: The commands to check.
211
+
212
+ Returns:
213
+ The final commands.
214
+ """
215
+ if not prior_commands:
216
+ return commands
217
+
218
+ prior_commands, commands = self._check_slot_command_overlap(
219
+ prior_commands, commands
220
+ )
221
+
222
+ prior_start_flow_names = {
223
+ command.flow
224
+ for command in prior_commands
225
+ if isinstance(command, StartFlowCommand)
226
+ }
227
+ current_start_flow_names = {
228
+ command.flow
229
+ for command in commands
230
+ if isinstance(command, StartFlowCommand)
231
+ }
232
+
233
+ return self._check_start_flow_command_overlap(
234
+ prior_commands,
235
+ commands,
236
+ prior_start_flow_names,
237
+ current_start_flow_names,
238
+ )
239
+
240
+ def _check_start_flow_command_overlap(
241
+ self,
242
+ prior_commands: List[Command],
243
+ commands: List[Command],
244
+ prior_start_flow_names: Set[str],
245
+ current_start_flow_names: Set[str],
246
+ ) -> List[Command]:
247
+ """Get the final commands.
248
+
249
+ Args:
250
+ prior_commands: The prior commands.
251
+ commands: The currently predicted commands to check.
252
+ prior_start_flow_names: The names of the flows from the prior commands.
253
+ current_start_flow_names: The names of the flows from the current commands.
254
+
255
+ Returns:
256
+ The final commands.
257
+ """
258
+ raise NotImplementedError()
259
+
260
+ def _check_slot_command_overlap(
261
+ self,
262
+ prior_commands: List[Command],
263
+ commands: List[Command],
264
+ ) -> Tuple[List[Command], List[Command]]:
265
+ """Check if the current commands overlap with the prior commands."""
266
+ prior_slot_names = gather_slot_names(prior_commands)
267
+ current_slot_names = gather_slot_names(commands)
268
+ overlapping_slot_names = prior_slot_names.intersection(current_slot_names)
269
+
270
+ structlogger.debug(
271
+ "command_generator.check_slot_command_overlap",
272
+ overlapping_slot_names=overlapping_slot_names,
273
+ )
274
+
275
+ if not overlapping_slot_names:
276
+ return prior_commands, commands
277
+
278
+ return self._filter_slot_commands(
279
+ prior_commands, commands, overlapping_slot_names
280
+ )
281
+
282
+ def _filter_slot_commands(
283
+ self,
284
+ prior_commands: List[Command],
285
+ commands: List[Command],
286
+ overlapping_slot_names: Set[str],
287
+ ) -> Tuple[List[Command], List[Command]]:
288
+ """Filter out the overlapping slot commands."""
289
+ raise NotImplementedError()
290
+
205
291
  def _check_commands_against_startable_flows(
206
292
  self, commands: List[Command], startable_flows: FlowsList
207
293
  ) -> List[Command]:
@@ -278,70 +364,21 @@ class CommandGenerator:
278
364
  return len(message.get(TEXT, "").strip()) == 0
279
365
 
280
366
  @staticmethod
281
- def _check_commands_against_slot_mappings(
282
- commands: List[Command],
283
- tracker: DialogueStateTracker,
284
- domain: Optional[Domain] = None,
285
- ) -> List[Command]:
286
- """Check if the LLM-issued slot commands are fillable.
287
-
288
- The LLM-issued slot commands are fillable if the slot
289
- mappings are satisfied.
290
- """
291
- if not domain:
292
- return commands
293
-
294
- llm_fillable_slot_names = [
295
- command.name
296
- for command in commands
297
- if isinstance(command, SetSlotCommand)
298
- and command.extractor == SetSlotExtractor.LLM.value
299
- ]
300
-
301
- if not llm_fillable_slot_names:
302
- return commands
303
-
304
- llm_fillable_slots = [
305
- slot for slot in domain.slots if slot.name in llm_fillable_slot_names
367
+ def _get_prior_commands(message: Message) -> List[Command]:
368
+ """Get the prior commands from the tracker."""
369
+ return [
370
+ Command.command_from_json(command) for command in message.get(COMMANDS, [])
306
371
  ]
307
372
 
308
- slot_filling_manager = SlotFillingManager(domain, tracker)
309
- slots_to_be_removed = []
310
373
 
311
- structlogger.debug(
312
- "command_processor.check_commands_against_slot_mappings.active_flow",
313
- active_flow=tracker.active_flow,
314
- )
315
-
316
- for slot in llm_fillable_slots:
317
- should_fill_slot = False
318
- for mapping in slot.mappings:
319
- mapping_type = SlotMappingType(mapping.get("type"))
320
-
321
- should_fill_slot = slot_filling_manager.should_fill_slot(
322
- slot.name, mapping_type, mapping
323
- )
324
-
325
- if should_fill_slot:
326
- break
327
-
328
- if not should_fill_slot:
329
- structlogger.debug(
330
- "command_processor.check_commands_against_slot_mappings.slot_not_fillable",
331
- slot_name=slot.name,
332
- )
333
- slots_to_be_removed.append(slot.name)
334
-
335
- if not slots_to_be_removed:
336
- return commands
337
-
338
- filtered_commands = [
339
- command
340
- for command in commands
341
- if not (
342
- isinstance(command, SetSlotCommand)
343
- and command.name in slots_to_be_removed
344
- )
345
- ]
374
+ def gather_slot_names(commands: List[Command]) -> Set[str]:
375
+ """Gather all slot names from the commands."""
376
+ slot_names = set()
377
+ for command in commands:
378
+ if isinstance(command, SetSlotCommand):
379
+ slot_names.add(command.name)
380
+ if isinstance(command, CorrectSlotsCommand):
381
+ for slot in command.corrected_slots:
382
+ slot_names.add(slot.name)
346
383
 
347
- return filtered_commands
384
+ return slot_names