rasa-pro 3.12.0.dev9__py3-none-any.whl → 3.12.0.dev11__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- rasa/cli/inspect.py +20 -1
- rasa/cli/shell.py +3 -3
- rasa/core/actions/action.py +20 -7
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_stream/browser_audio.py +1 -0
- rasa/core/channels/voice_stream/call_state.py +7 -1
- rasa/core/channels/voice_stream/genesys.py +331 -0
- rasa/core/channels/voice_stream/tts/azure.py +2 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +16 -3
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +2 -1
- rasa/core/migrate.py +2 -2
- rasa/core/policies/flows/flow_executor.py +36 -42
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/clarify_command.py +2 -2
- rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
- rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +2 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
- rasa/dialogue_understanding/commands/utils.py +1 -1
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +161 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +40 -40
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/processor/command_processor.py +115 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/model_service.py +4 -0
- rasa/model_training.py +24 -27
- rasa/shared/core/constants.py +28 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/flow.py +17 -0
- rasa/shared/core/flows/flows_yaml_schema.json +38 -0
- rasa/shared/core/flows/steps/collect.py +18 -1
- rasa/shared/core/flows/utils.py +16 -1
- rasa/shared/core/slot_mappings.py +144 -108
- rasa/shared/core/slots.py +23 -2
- rasa/shared/core/trackers.py +3 -1
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/providers/llm/_base_litellm_client.py +0 -40
- rasa/shared/utils/llm.py +1 -86
- rasa/shared/utils/schemas/domain.yml +0 -1
- rasa/telemetry.py +43 -13
- rasa/utils/common.py +0 -1
- rasa/validator.py +189 -82
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/RECORD +72 -68
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev11.dist-info}/entry_points.txt +0 -0
|
@@ -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 "
|
|
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"
|
|
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 "
|
|
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"
|
|
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"
|
|
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"
|
|
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(
|
|
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] =
|
|
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 "
|
|
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"
|
|
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 "
|
|
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"
|
|
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 "
|
|
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"
|
|
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 [
|
|
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"
|
|
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"""
|
|
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 "
|
|
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"
|
|
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"
|
|
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"
|
|
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,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.
|
|
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
|
|
96
|
-
#
|
|
97
|
-
#
|
|
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
|
|
282
|
-
commands
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
for
|
|
319
|
-
|
|
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
|
-
|
|
384
|
+
return slot_names
|