rasa-pro 3.12.0.dev11__py3-none-any.whl → 3.12.0.dev12__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 +1 -20
  2. rasa/cli/shell.py +3 -3
  3. rasa/core/actions/action.py +7 -20
  4. rasa/core/actions/forms.py +5 -10
  5. rasa/core/channels/__init__.py +0 -2
  6. rasa/core/channels/voice_ready/audiocodes.py +23 -42
  7. rasa/core/channels/voice_stream/browser_audio.py +0 -1
  8. rasa/core/channels/voice_stream/call_state.py +1 -7
  9. rasa/core/channels/voice_stream/tts/azure.py +1 -2
  10. rasa/core/channels/voice_stream/tts/cartesia.py +3 -16
  11. rasa/core/channels/voice_stream/twilio_media_streams.py +1 -2
  12. rasa/core/channels/voice_stream/voice_channel.py +1 -2
  13. rasa/core/migrate.py +2 -2
  14. rasa/core/policies/flows/flow_executor.py +42 -36
  15. rasa/core/run.py +3 -4
  16. rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
  17. rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -62
  18. rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
  19. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
  20. rasa/dialogue_understanding/commands/clarify_command.py +2 -2
  21. rasa/dialogue_understanding/commands/correct_slots_command.py +2 -11
  22. rasa/dialogue_understanding/commands/human_handoff_command.py +2 -2
  23. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  24. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
  25. rasa/dialogue_understanding/commands/set_slot_command.py +15 -7
  26. rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
  27. rasa/dialogue_understanding/commands/start_flow_command.py +2 -43
  28. rasa/dialogue_understanding/commands/utils.py +1 -1
  29. rasa/dialogue_understanding/constants.py +0 -1
  30. rasa/dialogue_understanding/generator/command_generator.py +73 -110
  31. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  32. rasa/dialogue_understanding/generator/llm_based_command_generator.py +3 -161
  33. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +2 -10
  34. rasa/dialogue_understanding/generator/nlu_command_adapter.py +3 -44
  35. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +79 -53
  36. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +19 -11
  37. rasa/dialogue_understanding/generator/utils.py +1 -32
  38. rasa/dialogue_understanding/patterns/correction.py +1 -13
  39. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +2 -62
  40. rasa/dialogue_understanding/processor/command_processor.py +28 -115
  41. rasa/dialogue_understanding/utils.py +0 -31
  42. rasa/dialogue_understanding_test/README.md +0 -50
  43. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
  44. rasa/model_service.py +0 -4
  45. rasa/model_training.py +27 -24
  46. rasa/shared/core/constants.py +3 -28
  47. rasa/shared/core/domain.py +20 -13
  48. rasa/shared/core/events.py +2 -13
  49. rasa/shared/core/flows/flow.py +0 -17
  50. rasa/shared/core/flows/flows_yaml_schema.json +0 -38
  51. rasa/shared/core/flows/steps/collect.py +1 -18
  52. rasa/shared/core/flows/utils.py +1 -16
  53. rasa/shared/core/slot_mappings.py +108 -144
  54. rasa/shared/core/slots.py +2 -23
  55. rasa/shared/core/trackers.py +1 -3
  56. rasa/shared/nlu/constants.py +0 -1
  57. rasa/shared/utils/llm.py +1 -1
  58. rasa/shared/utils/schemas/domain.yml +1 -0
  59. rasa/telemetry.py +13 -43
  60. rasa/utils/common.py +1 -0
  61. rasa/validator.py +82 -189
  62. rasa/version.py +1 -1
  63. {rasa_pro-3.12.0.dev11.dist-info → rasa_pro-3.12.0.dev12.dist-info}/METADATA +1 -1
  64. {rasa_pro-3.12.0.dev11.dist-info → rasa_pro-3.12.0.dev12.dist-info}/RECORD +67 -71
  65. rasa/core/actions/action_handle_digressions.py +0 -142
  66. rasa/core/channels/voice_stream/genesys.py +0 -331
  67. rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -150
  68. rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
  69. {rasa_pro-3.12.0.dev11.dist-info → rasa_pro-3.12.0.dev12.dist-info}/NOTICE +0 -0
  70. {rasa_pro-3.12.0.dev11.dist-info → rasa_pro-3.12.0.dev12.dist-info}/WHEEL +0 -0
  71. {rasa_pro-3.12.0.dev11.dist-info → rasa_pro-3.12.0.dev12.dist-info}/entry_points.txt +0 -0
@@ -1,6 +1,5 @@
1
1
  from __future__ import annotations
2
2
 
3
- import copy
4
3
  import re
5
4
  from dataclasses import dataclass
6
5
  from typing import Any, Dict, List
@@ -9,11 +8,8 @@ import structlog
9
8
 
10
9
  from rasa.dialogue_understanding.commands.command import Command
11
10
  from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
12
- from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
13
11
  from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
14
- from rasa.dialogue_understanding.stack.frames import (
15
- UserFlowStackFrame,
16
- )
12
+ from rasa.dialogue_understanding.stack.frames import UserFlowStackFrame
17
13
  from rasa.dialogue_understanding.stack.frames.flow_stack_frame import FlowStackFrameType
18
14
  from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
19
15
  from rasa.shared.core.events import Event, FlowCancelled
@@ -93,8 +89,7 @@ class CancelFlowCommand(Command):
93
89
  original_stack = original_tracker.stack
94
90
 
95
91
  applied_events: List[Event] = []
96
- # capture the top frame before we push new frames onto the stack
97
- initial_top_frame = stack.top()
92
+
98
93
  user_frame = top_user_flow_frame(original_stack)
99
94
  current_flow = user_frame.flow(all_flows) if user_frame else None
100
95
 
@@ -119,21 +114,6 @@ class CancelFlowCommand(Command):
119
114
  if user_frame:
120
115
  applied_events.append(FlowCancelled(user_frame.flow_id, user_frame.step_id))
121
116
 
122
- if initial_top_frame and isinstance(
123
- initial_top_frame, ClarifyPatternFlowStackFrame
124
- ):
125
- structlogger.debug(
126
- "command_executor.cancel_flow.cancel_clarification_options",
127
- clarification_options=initial_top_frame.clarification_options,
128
- )
129
- applied_events += cancel_all_pending_clarification_options(
130
- initial_top_frame,
131
- original_stack,
132
- canceled_frames,
133
- all_flows,
134
- stack,
135
- )
136
-
137
117
  return applied_events + tracker.create_stack_updated_events(stack)
138
118
 
139
119
  def __hash__(self) -> int:
@@ -144,7 +124,7 @@ class CancelFlowCommand(Command):
144
124
 
145
125
  def to_dsl(self) -> str:
146
126
  """Converts the command to a DSL string."""
147
- return "CancelFlow()"
127
+ return "cancel flow"
148
128
 
149
129
  @classmethod
150
130
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> CancelFlowCommand:
@@ -153,42 +133,4 @@ class CancelFlowCommand(Command):
153
133
 
154
134
  @staticmethod
155
135
  def regex_pattern() -> str:
156
- return r"CancelFlow\(\)"
157
-
158
-
159
- def cancel_all_pending_clarification_options(
160
- initial_top_frame: ClarifyPatternFlowStackFrame,
161
- original_stack: DialogueStack,
162
- canceled_frames: List[str],
163
- all_flows: FlowsList,
164
- stack: DialogueStack,
165
- ) -> List[FlowCancelled]:
166
- """Cancel all pending clarification options.
167
-
168
- This is a special case when the assistant asks the user to clarify
169
- which pending digression flow to start after the completion of an active flow.
170
- If the user chooses to cancel all options, this function takes care of
171
- updating the stack by removing all pending flow stack frames
172
- listed as clarification options.
173
- """
174
- clarification_names = set(initial_top_frame.names)
175
- to_be_canceled_frames = []
176
- applied_events = []
177
- for frame in reversed(original_stack.frames):
178
- if frame.frame_id in canceled_frames:
179
- continue
180
-
181
- to_be_canceled_frames.append(frame.frame_id)
182
- if isinstance(frame, UserFlowStackFrame):
183
- readable_flow_name = frame.flow(all_flows).readable_name()
184
- if readable_flow_name in clarification_names:
185
- stack.push(
186
- CancelPatternFlowStackFrame(
187
- canceled_name=readable_flow_name,
188
- canceled_frames=copy.deepcopy(to_be_canceled_frames),
189
- )
190
- )
191
- applied_events.append(FlowCancelled(frame.flow_id, frame.step_id))
192
- to_be_canceled_frames.clear()
193
-
194
- return applied_events
136
+ return r"^cancel flow$"
@@ -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 "ChangeFlow()"
51
+ return "change"
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"ChangeFlow\(\)"
60
+ return r"^change"
@@ -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 "ChitChat()"
62
+ return "offtopic reply"
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"ChitChat\(\)"
71
+ return r"^offtopic reply$"
@@ -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"disambiguate flows {' '.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"^disambiguate flows([\"\'a-zA-Z0-9_, ]*)$"
@@ -31,7 +31,6 @@ class CorrectedSlot:
31
31
 
32
32
  name: str
33
33
  value: Any
34
- filled_by: Optional[str] = None
35
34
 
36
35
 
37
36
  @dataclass
@@ -55,9 +54,7 @@ class CorrectSlotsCommand(Command):
55
54
  try:
56
55
  return CorrectSlotsCommand(
57
56
  corrected_slots=[
58
- CorrectedSlot(
59
- s["name"], value=s["value"], filled_by=s.get("filled_by", None)
60
- )
57
+ CorrectedSlot(s["name"], value=s["value"])
61
58
  for s in data["corrected_slots"]
62
59
  ]
63
60
  )
@@ -138,10 +135,7 @@ class CorrectSlotsCommand(Command):
138
135
  proposed_slots = {}
139
136
  for corrected_slot in self.corrected_slots:
140
137
  if tracker.get_slot(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
- }
138
+ proposed_slots[corrected_slot.name] = corrected_slot.value
145
139
  else:
146
140
  structlogger.debug(
147
141
  "command_executor.skip_correction.slot_already_set", command=self
@@ -246,9 +240,6 @@ class CorrectSlotsCommand(Command):
246
240
  corrected_slots=proposed_slots,
247
241
  reset_flow_id=earliest_collect.flow_id if earliest_collect else None,
248
242
  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
- ],
252
243
  )
253
244
 
254
245
  def run_command_on_tracker(
@@ -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 "HumanHandoff()"
69
+ return "hand over"
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"HumanHandoff\(\)"
78
+ return r"^hand over$"
@@ -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 "SearchAndReply()"
62
+ return "provide info"
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"SearchAndReply\(\)"
71
+ return r"^provide info$"
@@ -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 "RepeatLastBotMessages()"
63
+ return "repeat message"
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"RepeatLastBotMessages\(\)"
72
+ return r"^repeat message$"
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
  from dataclasses import dataclass
5
+ from enum import Enum
5
6
  from typing import Any, Dict, List
6
7
 
7
8
  import structlog
@@ -18,7 +19,6 @@ from rasa.dialogue_understanding.stack.utils import (
18
19
  get_collect_steps_excluding_ask_before_filling_for_active_flow,
19
20
  )
20
21
  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,6 +27,17 @@ 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
+
30
41
  def get_flows_predicted_to_start_from_tracker(
31
42
  tracker: DialogueStateTracker,
32
43
  ) -> List[str]:
@@ -126,7 +137,6 @@ class SetSlotCommand(Command):
126
137
  in {
127
138
  SetSlotExtractor.LLM.value,
128
139
  SetSlotExtractor.COMMAND_PAYLOAD_READER.value,
129
- SetSlotExtractor.NLU.value,
130
140
  }
131
141
  ):
132
142
  # Get the other predicted flows from the most recent message on the tracker.
@@ -144,9 +154,7 @@ class SetSlotCommand(Command):
144
154
  return []
145
155
 
146
156
  structlogger.debug("command_executor.set_slot", command=self)
147
- return [
148
- SlotSet(self.name, slot.coerce_value(self.value), filled_by=self.extractor)
149
- ]
157
+ return [SlotSet(self.name, slot.coerce_value(self.value))]
150
158
 
151
159
  def __hash__(self) -> int:
152
160
  return hash(self.value) + hash(self.name)
@@ -162,7 +170,7 @@ class SetSlotCommand(Command):
162
170
 
163
171
  def to_dsl(self) -> str:
164
172
  """Converts the command to a DSL string."""
165
- return f"SetSlot({self.name}, {self.value})"
173
+ return f"set slot {self.name} {self.value}"
166
174
 
167
175
  @classmethod
168
176
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> SetSlotCommand:
@@ -173,4 +181,4 @@ class SetSlotCommand(Command):
173
181
 
174
182
  @staticmethod
175
183
  def regex_pattern() -> str:
176
- return r"""SetSlot\(['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]?, ?['"]?(.*)['"]?\)"""
184
+ return r"""^set slot ['"]?([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 "SkipQuestion()"
78
+ return "skip"
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"SkipQuestion\(\)"
87
+ return r"^skip$"
@@ -7,11 +7,6 @@ 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
15
10
  from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
16
11
  FlowStackFrameType,
17
12
  UserFlowStackFrame,
@@ -73,10 +68,6 @@ class StartFlowCommand(Command):
73
68
  applied_events: List[Event] = []
74
69
 
75
70
  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
-
80
71
  structlogger.debug(
81
72
  "command_executor.skip_command.already_started_flow", command=self
82
73
  )
@@ -119,7 +110,7 @@ class StartFlowCommand(Command):
119
110
 
120
111
  def to_dsl(self) -> str:
121
112
  """Converts the command to a DSL string."""
122
- return f"StartFlow({self.flow})"
113
+ return f"start flow {self.flow}"
123
114
 
124
115
  @classmethod
125
116
  def from_dsl(cls, match: re.Match, **kwargs: Any) -> Optional[StartFlowCommand]:
@@ -128,36 +119,4 @@ class StartFlowCommand(Command):
128
119
 
129
120
  @staticmethod
130
121
  def regex_pattern() -> str:
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 []
122
+ return r"^start flow ['\"]?([a-zA-Z0-9_-]+)['\"]?$"
@@ -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,2 +1 @@
1
1
  RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME = "RASA_RECORD_COMMANDS_AND_PROMPTS"
2
- KEY_MINIMIZE_NUM_CALLS = "minimize_num_calls"
@@ -1,24 +1,23 @@
1
1
  from dataclasses import dataclass
2
- from typing import Any, Dict, List, Optional, Set, Text, Tuple
2
+ from typing import Any, Dict, List, Optional, Text
3
3
 
4
4
  import structlog
5
5
 
6
6
  from rasa.dialogue_understanding.commands import (
7
7
  Command,
8
- CorrectSlotsCommand,
9
8
  ErrorCommand,
10
9
  SetSlotCommand,
11
10
  StartFlowCommand,
12
11
  )
13
- from rasa.dialogue_understanding.utils import (
14
- _handle_via_nlu_in_coexistence,
15
- )
12
+ from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
16
13
  from rasa.shared.constants import (
17
14
  RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_EMPTY,
18
15
  RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_TOO_LONG,
19
16
  )
17
+ from rasa.shared.core.constants import SlotMappingType
20
18
  from rasa.shared.core.domain import Domain
21
19
  from rasa.shared.core.flows import FlowsList
20
+ from rasa.shared.core.slot_mappings import SlotFillingManager
22
21
  from rasa.shared.core.trackers import DialogueStateTracker
23
22
  from rasa.shared.nlu.constants import (
24
23
  COMMANDS,
@@ -93,9 +92,9 @@ class CommandGenerator:
93
92
  )
94
93
 
95
94
  for message in messages:
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.
95
+ if message.get(COMMANDS):
96
+ # do not overwrite commands if they are already present
97
+ # i.e. another command generator already predicted commands
99
98
  continue
100
99
 
101
100
  commands = await self._evaluate_and_predict(
@@ -107,6 +106,9 @@ class CommandGenerator:
107
106
  commands = self._check_commands_against_startable_flows(
108
107
  commands, startable_flows
109
108
  )
109
+ commands = self._check_commands_against_slot_mappings(
110
+ commands, tracker, domain
111
+ )
110
112
  commands_dicts = [command.as_dict() for command in commands]
111
113
  message.set(COMMANDS, commands_dicts, add_to_output=True)
112
114
 
@@ -200,94 +202,6 @@ class CommandGenerator:
200
202
  """
201
203
  raise NotImplementedError()
202
204
 
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
-
291
205
  def _check_commands_against_startable_flows(
292
206
  self, commands: List[Command], startable_flows: FlowsList
293
207
  ) -> List[Command]:
@@ -364,21 +278,70 @@ class CommandGenerator:
364
278
  return len(message.get(TEXT, "").strip()) == 0
365
279
 
366
280
  @staticmethod
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, [])
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
371
306
  ]
372
307
 
308
+ slot_filling_manager = SlotFillingManager(domain, tracker)
309
+ slots_to_be_removed = []
373
310
 
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)
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
+ ]
383
346
 
384
- return slot_names
347
+ return filtered_commands
@@ -125,7 +125,7 @@ def _parse_standard_commands(
125
125
  commands: List[Command] = []
126
126
  for command_clz in standard_commands:
127
127
  pattern = _get_compiled_pattern(command_clz.regex_pattern())
128
- if match := pattern.search(action):
128
+ if match := pattern.search(action.strip()):
129
129
  parsed_command = command_clz.from_dsl(match, **kwargs)
130
130
  if _additional_parsing_fn := _get_additional_parsing_logic(command_clz):
131
131
  parsed_command = _additional_parsing_fn(parsed_command, flows, **kwargs)