rasa-pro 3.12.6.dev1__py3-none-any.whl → 3.12.7.dev1__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/run.py +10 -6
- rasa/cli/utils.py +7 -0
- rasa/core/actions/action.py +0 -6
- rasa/core/channels/voice_ready/audiocodes.py +46 -17
- rasa/core/policies/flows/flow_executor.py +3 -38
- rasa/core/processor.py +19 -5
- rasa/core/utils.py +53 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -59
- rasa/dialogue_understanding/commands/start_flow_command.py +0 -41
- rasa/dialogue_understanding/generator/command_generator.py +67 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +2 -12
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +0 -61
- rasa/dialogue_understanding/processor/command_processor.py +7 -65
- rasa/dialogue_understanding/stack/utils.py +0 -38
- rasa/e2e_test/utils/validation.py +3 -3
- rasa/shared/core/constants.py +0 -8
- rasa/shared/core/flows/flow.py +0 -17
- rasa/shared/core/flows/flows_yaml_schema.json +3 -38
- rasa/shared/core/flows/steps/collect.py +5 -18
- rasa/shared/core/flows/utils.py +1 -16
- rasa/shared/core/slot_mappings.py +11 -5
- rasa/shared/nlu/constants.py +0 -1
- rasa/shared/utils/common.py +11 -1
- rasa/validator.py +1 -123
- rasa/version.py +1 -2
- {rasa_pro-3.12.6.dev1.dist-info → rasa_pro-3.12.7.dev1.dist-info}/METADATA +3 -1
- {rasa_pro-3.12.6.dev1.dist-info → rasa_pro-3.12.7.dev1.dist-info}/RECORD +30 -33
- rasa/core/actions/action_handle_digressions.py +0 -164
- rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -144
- rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
- {rasa_pro-3.12.6.dev1.dist-info → rasa_pro-3.12.7.dev1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.6.dev1.dist-info → rasa_pro-3.12.7.dev1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.6.dev1.dist-info → rasa_pro-3.12.7.dev1.dist-info}/entry_points.txt +0 -0
rasa/cli/run.py
CHANGED
|
@@ -64,12 +64,16 @@ def run_actions(args: argparse.Namespace) -> None:
|
|
|
64
64
|
|
|
65
65
|
def _validate_model_path(model_path: Text, parameter: Text, default: Text) -> Text:
|
|
66
66
|
if model_path is not None and not os.path.exists(model_path):
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
67
|
+
raise ModelNotFound(
|
|
68
|
+
f"The provided model path '{model_path}' could not be found. "
|
|
69
|
+
"Provide an existing model path."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if model_path is None:
|
|
73
|
+
logger.debug(
|
|
74
|
+
f"Parameter '{parameter}' not set. "
|
|
75
|
+
"Using default location '{default}' instead."
|
|
76
|
+
)
|
|
73
77
|
os.makedirs(default, exist_ok=True)
|
|
74
78
|
model_path = default
|
|
75
79
|
|
rasa/cli/utils.py
CHANGED
|
@@ -14,6 +14,7 @@ import structlog
|
|
|
14
14
|
import rasa.shared.utils.cli
|
|
15
15
|
import rasa.shared.utils.io
|
|
16
16
|
from rasa import telemetry
|
|
17
|
+
from rasa.exceptions import ModelNotFound
|
|
17
18
|
from rasa.shared.constants import (
|
|
18
19
|
ASSISTANT_ID_DEFAULT_VALUE,
|
|
19
20
|
ASSISTANT_ID_KEY,
|
|
@@ -77,6 +78,12 @@ def get_validated_path(
|
|
|
77
78
|
if current and os.path.exists(current):
|
|
78
79
|
return current
|
|
79
80
|
|
|
81
|
+
if parameter == "model":
|
|
82
|
+
raise ModelNotFound(
|
|
83
|
+
f"The provided model path '{current}' could not be found. "
|
|
84
|
+
"Provide an existing model path."
|
|
85
|
+
)
|
|
86
|
+
|
|
80
87
|
# try to find a valid option among the defaults
|
|
81
88
|
if isinstance(default, str) or isinstance(default, Path):
|
|
82
89
|
default_options = [str(default)]
|
rasa/core/actions/action.py
CHANGED
|
@@ -105,10 +105,6 @@ logger = logging.getLogger(__name__)
|
|
|
105
105
|
def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["Action"]:
|
|
106
106
|
"""List default actions."""
|
|
107
107
|
from rasa.core.actions.action_clean_stack import ActionCleanStack
|
|
108
|
-
from rasa.core.actions.action_handle_digressions import (
|
|
109
|
-
ActionBlockDigressions,
|
|
110
|
-
ActionContinueDigression,
|
|
111
|
-
)
|
|
112
108
|
from rasa.core.actions.action_hangup import ActionHangup
|
|
113
109
|
from rasa.core.actions.action_repeat_bot_messages import ActionRepeatBotMessages
|
|
114
110
|
from rasa.core.actions.action_run_slot_rejections import ActionRunSlotRejections
|
|
@@ -143,8 +139,6 @@ def default_actions(action_endpoint: Optional[EndpointConfig] = None) -> List["A
|
|
|
143
139
|
ActionResetRouting(),
|
|
144
140
|
ActionHangup(),
|
|
145
141
|
ActionRepeatBotMessages(),
|
|
146
|
-
ActionBlockDigressions(),
|
|
147
|
-
ActionContinueDigression(),
|
|
148
142
|
]
|
|
149
143
|
|
|
150
144
|
|
|
@@ -115,11 +115,21 @@ class Conversation:
|
|
|
115
115
|
async def handle_activities(
|
|
116
116
|
self,
|
|
117
117
|
message: Dict[Text, Any],
|
|
118
|
+
input_channel_name: str,
|
|
118
119
|
output_channel: OutputChannel,
|
|
119
120
|
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
120
121
|
) -> None:
|
|
121
122
|
"""Handle activities sent by Audiocodes."""
|
|
122
123
|
structlogger.debug("audiocodes.handle.activities")
|
|
124
|
+
if input_channel_name == "":
|
|
125
|
+
structlogger.warning(
|
|
126
|
+
"audiocodes.handle.activities.empty_input_channel_name",
|
|
127
|
+
event_info=(
|
|
128
|
+
"Audiocodes input channel name is empty "
|
|
129
|
+
f"for conversation {self.conversation_id}"
|
|
130
|
+
),
|
|
131
|
+
)
|
|
132
|
+
|
|
123
133
|
for activity in message["activities"]:
|
|
124
134
|
text = None
|
|
125
135
|
if activity[ACTIVITY_ID_KEY] in self.activity_ids:
|
|
@@ -143,6 +153,7 @@ class Conversation:
|
|
|
143
153
|
metadata = self.get_metadata(activity)
|
|
144
154
|
user_msg = UserMessage(
|
|
145
155
|
text=text,
|
|
156
|
+
input_channel=input_channel_name,
|
|
146
157
|
output_channel=output_channel,
|
|
147
158
|
sender_id=self.conversation_id,
|
|
148
159
|
metadata=metadata,
|
|
@@ -394,7 +405,12 @@ class AudiocodesInput(InputChannel):
|
|
|
394
405
|
# start a background task to handle activities
|
|
395
406
|
self._create_task(
|
|
396
407
|
conversation_id,
|
|
397
|
-
conversation.handle_activities(
|
|
408
|
+
conversation.handle_activities(
|
|
409
|
+
request.json,
|
|
410
|
+
input_channel_name=self.name(),
|
|
411
|
+
output_channel=ac_output,
|
|
412
|
+
on_new_message=on_new_message,
|
|
413
|
+
),
|
|
398
414
|
)
|
|
399
415
|
return response.json(response_json)
|
|
400
416
|
|
|
@@ -407,23 +423,9 @@ class AudiocodesInput(InputChannel):
|
|
|
407
423
|
Example of payload:
|
|
408
424
|
{"conversation": <conversation_id>, "reason": Optional[Text]}.
|
|
409
425
|
"""
|
|
410
|
-
self.
|
|
411
|
-
|
|
412
|
-
await on_new_message(
|
|
413
|
-
UserMessage(
|
|
414
|
-
text=f"{INTENT_MESSAGE_PREFIX}session_end",
|
|
415
|
-
output_channel=None,
|
|
416
|
-
sender_id=conversation_id,
|
|
417
|
-
metadata=reason,
|
|
418
|
-
)
|
|
419
|
-
)
|
|
420
|
-
del self.conversations[conversation_id]
|
|
421
|
-
structlogger.debug(
|
|
422
|
-
"audiocodes.disconnect",
|
|
423
|
-
conversation=conversation_id,
|
|
424
|
-
request=request.json,
|
|
426
|
+
return await self._handle_disconnect(
|
|
427
|
+
request, conversation_id, on_new_message
|
|
425
428
|
)
|
|
426
|
-
return response.json({})
|
|
427
429
|
|
|
428
430
|
@ac_webhook.route("/conversation/<conversation_id>/keepalive", methods=["POST"])
|
|
429
431
|
async def keepalive(request: Request, conversation_id: Text) -> HTTPResponse:
|
|
@@ -438,6 +440,32 @@ class AudiocodesInput(InputChannel):
|
|
|
438
440
|
|
|
439
441
|
return ac_webhook
|
|
440
442
|
|
|
443
|
+
async def _handle_disconnect(
|
|
444
|
+
self,
|
|
445
|
+
request: Request,
|
|
446
|
+
conversation_id: Text,
|
|
447
|
+
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
448
|
+
) -> HTTPResponse:
|
|
449
|
+
"""Triggered when the call is disconnected."""
|
|
450
|
+
self._get_conversation(request.token, conversation_id)
|
|
451
|
+
reason = {"reason": request.json.get("reason")}
|
|
452
|
+
await on_new_message(
|
|
453
|
+
UserMessage(
|
|
454
|
+
text=f"{INTENT_MESSAGE_PREFIX}session_end",
|
|
455
|
+
input_channel=self.name(),
|
|
456
|
+
output_channel=None,
|
|
457
|
+
sender_id=conversation_id,
|
|
458
|
+
metadata=reason,
|
|
459
|
+
)
|
|
460
|
+
)
|
|
461
|
+
del self.conversations[conversation_id]
|
|
462
|
+
structlogger.debug(
|
|
463
|
+
"audiocodes.disconnect",
|
|
464
|
+
conversation=conversation_id,
|
|
465
|
+
request=request.json,
|
|
466
|
+
)
|
|
467
|
+
return response.json({})
|
|
468
|
+
|
|
441
469
|
|
|
442
470
|
class AudiocodesOutput(OutputChannel):
|
|
443
471
|
@classmethod
|
|
@@ -445,6 +473,7 @@ class AudiocodesOutput(OutputChannel):
|
|
|
445
473
|
return CHANNEL_NAME
|
|
446
474
|
|
|
447
475
|
def __init__(self) -> None:
|
|
476
|
+
super().__init__()
|
|
448
477
|
self.messages: List[Dict] = []
|
|
449
478
|
|
|
450
479
|
async def add_message(self, message: Dict) -> None:
|
|
@@ -23,7 +23,6 @@ from rasa.core.policies.flows.flow_step_result import (
|
|
|
23
23
|
)
|
|
24
24
|
from rasa.dialogue_understanding.commands import CancelFlowCommand
|
|
25
25
|
from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
|
|
26
|
-
from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
|
|
27
26
|
from rasa.dialogue_understanding.patterns.collect_information import (
|
|
28
27
|
CollectInformationPatternFlowStackFrame,
|
|
29
28
|
)
|
|
@@ -51,7 +50,6 @@ from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
|
|
|
51
50
|
)
|
|
52
51
|
from rasa.dialogue_understanding.stack.utils import (
|
|
53
52
|
top_user_flow_frame,
|
|
54
|
-
user_flows_on_the_stack,
|
|
55
53
|
)
|
|
56
54
|
from rasa.shared.constants import RASA_PATTERN_HUMAN_HANDOFF
|
|
57
55
|
from rasa.shared.core.constants import (
|
|
@@ -280,33 +278,6 @@ def trigger_pattern_continue_interrupted(
|
|
|
280
278
|
return events
|
|
281
279
|
|
|
282
280
|
|
|
283
|
-
def trigger_pattern_clarification(
|
|
284
|
-
current_frame: DialogueStackFrame, stack: DialogueStack, flows: FlowsList
|
|
285
|
-
) -> None:
|
|
286
|
-
"""Trigger the pattern to clarify which topic to continue if needed."""
|
|
287
|
-
if not isinstance(current_frame, UserFlowStackFrame):
|
|
288
|
-
return None
|
|
289
|
-
|
|
290
|
-
if current_frame.frame_type in [
|
|
291
|
-
FlowStackFrameType.CALL,
|
|
292
|
-
FlowStackFrameType.INTERRUPT,
|
|
293
|
-
]:
|
|
294
|
-
# we want to return to the flow that called
|
|
295
|
-
# the current flow or the flow that was interrupted
|
|
296
|
-
# by the current flow
|
|
297
|
-
return None
|
|
298
|
-
|
|
299
|
-
pending_flows = [
|
|
300
|
-
flows.flow_by_id(frame.flow_id)
|
|
301
|
-
for frame in stack.frames
|
|
302
|
-
if isinstance(frame, UserFlowStackFrame)
|
|
303
|
-
and frame.flow_id != current_frame.flow_id
|
|
304
|
-
]
|
|
305
|
-
|
|
306
|
-
flow_names = [flow.readable_name() for flow in pending_flows if flow is not None]
|
|
307
|
-
stack.push(ClarifyPatternFlowStackFrame(names=flow_names))
|
|
308
|
-
|
|
309
|
-
|
|
310
281
|
def trigger_pattern_completed(
|
|
311
282
|
current_frame: DialogueStackFrame, stack: DialogueStack, flows: FlowsList
|
|
312
283
|
) -> None:
|
|
@@ -675,15 +646,9 @@ def _run_end_step(
|
|
|
675
646
|
structlogger.debug("flow.step.run.flow_end")
|
|
676
647
|
current_frame = stack.pop()
|
|
677
648
|
trigger_pattern_completed(current_frame, stack, flows)
|
|
678
|
-
resumed_events =
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
# we need to trigger the pattern clarify
|
|
682
|
-
trigger_pattern_clarification(current_frame, stack, flows)
|
|
683
|
-
else:
|
|
684
|
-
resumed_events = trigger_pattern_continue_interrupted(
|
|
685
|
-
current_frame, stack, flows, tracker
|
|
686
|
-
)
|
|
649
|
+
resumed_events = trigger_pattern_continue_interrupted(
|
|
650
|
+
current_frame, stack, flows, tracker
|
|
651
|
+
)
|
|
687
652
|
reset_events: List[Event] = reset_scoped_slots(current_frame, flow, tracker)
|
|
688
653
|
return ContinueFlowWithNextStep(
|
|
689
654
|
events=initial_events + reset_events + resumed_events, has_flow_ended=True
|
rasa/core/processor.py
CHANGED
|
@@ -76,6 +76,7 @@ from rasa.shared.core.constants import (
|
|
|
76
76
|
SLOT_SILENCE_TIMEOUT,
|
|
77
77
|
USER_INTENT_RESTART,
|
|
78
78
|
USER_INTENT_SILENCE_TIMEOUT,
|
|
79
|
+
SetSlotExtractor,
|
|
79
80
|
)
|
|
80
81
|
from rasa.shared.core.events import (
|
|
81
82
|
ActionExecuted,
|
|
@@ -766,13 +767,26 @@ class MessageProcessor:
|
|
|
766
767
|
if self.http_interpreter:
|
|
767
768
|
parse_data = await self.http_interpreter.parse(message)
|
|
768
769
|
else:
|
|
769
|
-
regex_reader = create_regex_pattern_reader(message, self.domain)
|
|
770
|
-
|
|
771
770
|
processed_message = Message({TEXT: message.text})
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
771
|
+
|
|
772
|
+
all_flows = await self.get_flows()
|
|
773
|
+
should_force_slot_command, slot_name = (
|
|
774
|
+
rasa.core.utils.should_force_slot_filling(tracker, all_flows)
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
if should_force_slot_command:
|
|
778
|
+
command = SetSlotCommand(
|
|
779
|
+
name=slot_name,
|
|
780
|
+
value=message.text,
|
|
781
|
+
extractor=SetSlotExtractor.COMMAND_PAYLOAD_READER.value,
|
|
775
782
|
)
|
|
783
|
+
processed_message.set(COMMANDS, [command.as_dict()], add_to_output=True)
|
|
784
|
+
else:
|
|
785
|
+
regex_reader = create_regex_pattern_reader(message, self.domain)
|
|
786
|
+
if regex_reader:
|
|
787
|
+
processed_message = regex_reader.unpack_regex_message(
|
|
788
|
+
message=processed_message, domain=self.domain
|
|
789
|
+
)
|
|
776
790
|
|
|
777
791
|
# Invalid use of slash syntax, sanitize the message before passing
|
|
778
792
|
# it to the graph
|
rasa/core/utils.py
CHANGED
|
@@ -19,6 +19,7 @@ from rasa.core.constants import (
|
|
|
19
19
|
)
|
|
20
20
|
from rasa.core.lock_store import InMemoryLockStore, LockStore, RedisLockStore
|
|
21
21
|
from rasa.shared.constants import DEFAULT_ENDPOINTS_PATH, TCP_PROTOCOL
|
|
22
|
+
from rasa.shared.core.constants import SlotMappingType
|
|
22
23
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
23
24
|
from rasa.utils.endpoints import (
|
|
24
25
|
EndpointConfig,
|
|
@@ -30,6 +31,7 @@ from rasa.utils.io import write_yaml
|
|
|
30
31
|
if TYPE_CHECKING:
|
|
31
32
|
from rasa.core.nlg import NaturalLanguageGenerator
|
|
32
33
|
from rasa.shared.core.domain import Domain
|
|
34
|
+
from rasa.shared.core.flows.flows_list import FlowsList
|
|
33
35
|
|
|
34
36
|
structlogger = structlog.get_logger()
|
|
35
37
|
|
|
@@ -364,3 +366,54 @@ def add_bot_utterance_metadata(
|
|
|
364
366
|
]
|
|
365
367
|
|
|
366
368
|
return message
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
def should_force_slot_filling(
|
|
372
|
+
tracker: Optional[DialogueStateTracker], flows: "FlowsList"
|
|
373
|
+
) -> Tuple[bool, Optional[str]]:
|
|
374
|
+
"""Check if the flow should force slot filling.
|
|
375
|
+
|
|
376
|
+
This is only valid when the flow is at a collect information step which
|
|
377
|
+
has set `force_slot_filling` to true and the slot has a valid `from_text` mapping.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
tracker: The dialogue state tracker.
|
|
381
|
+
flows: The list of flows.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
A tuple of a boolean indicating if the flow should force slot filling
|
|
385
|
+
and the name of the slot if applicable.
|
|
386
|
+
"""
|
|
387
|
+
from rasa.dialogue_understanding.processor.command_processor import (
|
|
388
|
+
get_current_collect_step,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
if tracker is None:
|
|
392
|
+
structlogger.error(
|
|
393
|
+
"slot.force_slot_filling.error",
|
|
394
|
+
event_info="Tracker is None. Cannot force slot filling.",
|
|
395
|
+
)
|
|
396
|
+
return False, None
|
|
397
|
+
|
|
398
|
+
stack = tracker.stack
|
|
399
|
+
step = get_current_collect_step(stack, flows)
|
|
400
|
+
if step is None or not step.force_slot_filling:
|
|
401
|
+
return False, None
|
|
402
|
+
|
|
403
|
+
slot_name = step.collect
|
|
404
|
+
slot = tracker.slots.get(slot_name)
|
|
405
|
+
|
|
406
|
+
if not slot:
|
|
407
|
+
structlogger.debug(
|
|
408
|
+
"slot.force_slot_filling.error",
|
|
409
|
+
event_info=f"Slot '{slot_name}' not found in tracker. "
|
|
410
|
+
f"Cannot force slot filling. "
|
|
411
|
+
f"Please check if the slot is defined in the domain.",
|
|
412
|
+
)
|
|
413
|
+
return False, None
|
|
414
|
+
|
|
415
|
+
for slot_mapping in slot.mappings:
|
|
416
|
+
if slot_mapping.type == SlotMappingType.FROM_TEXT:
|
|
417
|
+
return True, slot_name
|
|
418
|
+
|
|
419
|
+
return False, None
|
|
@@ -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
|
|
@@ -13,10 +12,11 @@ from rasa.dialogue_understanding.commands.command_syntax_manager import (
|
|
|
13
12
|
CommandSyntaxVersion,
|
|
14
13
|
)
|
|
15
14
|
from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
|
|
16
|
-
from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
|
|
17
15
|
from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
|
|
18
|
-
from rasa.dialogue_understanding.stack.frames import
|
|
19
|
-
|
|
16
|
+
from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
|
|
17
|
+
FlowStackFrameType,
|
|
18
|
+
UserFlowStackFrame,
|
|
19
|
+
)
|
|
20
20
|
from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
|
|
21
21
|
from rasa.shared.core.events import Event, FlowCancelled
|
|
22
22
|
from rasa.shared.core.flows import FlowsList
|
|
@@ -95,8 +95,6 @@ class CancelFlowCommand(Command):
|
|
|
95
95
|
original_stack = original_tracker.stack
|
|
96
96
|
|
|
97
97
|
applied_events: List[Event] = []
|
|
98
|
-
# capture the top frame before we push new frames onto the stack
|
|
99
|
-
initial_top_frame = stack.top()
|
|
100
98
|
user_frame = top_user_flow_frame(original_stack)
|
|
101
99
|
current_flow = user_frame.flow(all_flows) if user_frame else None
|
|
102
100
|
|
|
@@ -123,21 +121,6 @@ class CancelFlowCommand(Command):
|
|
|
123
121
|
if user_frame:
|
|
124
122
|
applied_events.append(FlowCancelled(user_frame.flow_id, user_frame.step_id))
|
|
125
123
|
|
|
126
|
-
if initial_top_frame and isinstance(
|
|
127
|
-
initial_top_frame, ClarifyPatternFlowStackFrame
|
|
128
|
-
):
|
|
129
|
-
structlogger.debug(
|
|
130
|
-
"command_executor.cancel_flow.cancel_clarification_options",
|
|
131
|
-
clarification_options=initial_top_frame.clarification_options,
|
|
132
|
-
)
|
|
133
|
-
applied_events += cancel_all_pending_clarification_options(
|
|
134
|
-
initial_top_frame,
|
|
135
|
-
original_stack,
|
|
136
|
-
canceled_frames,
|
|
137
|
-
all_flows,
|
|
138
|
-
stack,
|
|
139
|
-
)
|
|
140
|
-
|
|
141
124
|
return applied_events + tracker.create_stack_updated_events(stack)
|
|
142
125
|
|
|
143
126
|
def __hash__(self) -> int:
|
|
@@ -172,41 +155,3 @@ class CancelFlowCommand(Command):
|
|
|
172
155
|
CommandSyntaxManager.get_syntax_version(),
|
|
173
156
|
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
174
157
|
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
def cancel_all_pending_clarification_options(
|
|
178
|
-
initial_top_frame: ClarifyPatternFlowStackFrame,
|
|
179
|
-
original_stack: DialogueStack,
|
|
180
|
-
canceled_frames: List[str],
|
|
181
|
-
all_flows: FlowsList,
|
|
182
|
-
stack: DialogueStack,
|
|
183
|
-
) -> List[FlowCancelled]:
|
|
184
|
-
"""Cancel all pending clarification options.
|
|
185
|
-
|
|
186
|
-
This is a special case when the assistant asks the user to clarify
|
|
187
|
-
which pending digression flow to start after the completion of an active flow.
|
|
188
|
-
If the user chooses to cancel all options, this function takes care of
|
|
189
|
-
updating the stack by removing all pending flow stack frames
|
|
190
|
-
listed as clarification options.
|
|
191
|
-
"""
|
|
192
|
-
clarification_names = set(initial_top_frame.names)
|
|
193
|
-
to_be_canceled_frames = []
|
|
194
|
-
applied_events = []
|
|
195
|
-
for frame in reversed(original_stack.frames):
|
|
196
|
-
if frame.frame_id in canceled_frames:
|
|
197
|
-
continue
|
|
198
|
-
|
|
199
|
-
to_be_canceled_frames.append(frame.frame_id)
|
|
200
|
-
if isinstance(frame, UserFlowStackFrame):
|
|
201
|
-
readable_flow_name = frame.flow(all_flows).readable_name()
|
|
202
|
-
if readable_flow_name in clarification_names:
|
|
203
|
-
stack.push(
|
|
204
|
-
CancelPatternFlowStackFrame(
|
|
205
|
-
canceled_name=readable_flow_name,
|
|
206
|
-
canceled_frames=copy.deepcopy(to_be_canceled_frames),
|
|
207
|
-
)
|
|
208
|
-
)
|
|
209
|
-
applied_events.append(FlowCancelled(frame.flow_id, frame.step_id))
|
|
210
|
-
to_be_canceled_frames.clear()
|
|
211
|
-
|
|
212
|
-
return applied_events
|
|
@@ -11,11 +11,6 @@ from rasa.dialogue_understanding.commands.command_syntax_manager import (
|
|
|
11
11
|
CommandSyntaxManager,
|
|
12
12
|
CommandSyntaxVersion,
|
|
13
13
|
)
|
|
14
|
-
from rasa.dialogue_understanding.patterns.clarify import FLOW_PATTERN_CLARIFICATION
|
|
15
|
-
from rasa.dialogue_understanding.patterns.continue_interrupted import (
|
|
16
|
-
ContinueInterruptedPatternFlowStackFrame,
|
|
17
|
-
)
|
|
18
|
-
from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
|
|
19
14
|
from rasa.dialogue_understanding.stack.frames.flow_stack_frame import (
|
|
20
15
|
FlowStackFrameType,
|
|
21
16
|
UserFlowStackFrame,
|
|
@@ -77,10 +72,6 @@ class StartFlowCommand(Command):
|
|
|
77
72
|
applied_events: List[Event] = []
|
|
78
73
|
|
|
79
74
|
if self.flow in user_flows_on_the_stack(stack):
|
|
80
|
-
top_frame = stack.top()
|
|
81
|
-
if top_frame is not None and top_frame.type() == FLOW_PATTERN_CLARIFICATION:
|
|
82
|
-
return self.change_flow_frame_position_in_the_stack(stack, tracker)
|
|
83
|
-
|
|
84
75
|
structlogger.debug(
|
|
85
76
|
"command_executor.skip_command.already_started_flow", command=self
|
|
86
77
|
)
|
|
@@ -149,35 +140,3 @@ class StartFlowCommand(Command):
|
|
|
149
140
|
CommandSyntaxManager.get_syntax_version(),
|
|
150
141
|
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
151
142
|
)
|
|
152
|
-
|
|
153
|
-
def change_flow_frame_position_in_the_stack(
|
|
154
|
-
self, stack: DialogueStack, tracker: DialogueStateTracker
|
|
155
|
-
) -> List[Event]:
|
|
156
|
-
"""Changes the position of the flow frame in the stack.
|
|
157
|
-
|
|
158
|
-
This is a special case when pattern clarification is the active flow and
|
|
159
|
-
the same flow is selected to start. In this case, the existing flow frame
|
|
160
|
-
should be moved up in the stack.
|
|
161
|
-
"""
|
|
162
|
-
frames = stack.frames[:]
|
|
163
|
-
|
|
164
|
-
for idx, frame in enumerate(frames):
|
|
165
|
-
if isinstance(frame, UserFlowStackFrame) and frame.flow_id == self.flow:
|
|
166
|
-
structlogger.debug(
|
|
167
|
-
"command_executor.change_flow_position_during_clarification",
|
|
168
|
-
command=self,
|
|
169
|
-
index=idx,
|
|
170
|
-
)
|
|
171
|
-
# pop the continue interrupted flow frame if it exists
|
|
172
|
-
next_frame = frames[idx + 1] if idx + 1 < len(frames) else None
|
|
173
|
-
if (
|
|
174
|
-
isinstance(next_frame, ContinueInterruptedPatternFlowStackFrame)
|
|
175
|
-
and next_frame.previous_flow_name == self.flow
|
|
176
|
-
):
|
|
177
|
-
stack.frames.pop(idx + 1)
|
|
178
|
-
# move up the existing flow from the stack
|
|
179
|
-
stack.frames.pop(idx)
|
|
180
|
-
stack.push(frame)
|
|
181
|
-
return tracker.create_stack_updated_events(stack)
|
|
182
|
-
|
|
183
|
-
return []
|
|
@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Set, Text, Tuple
|
|
|
4
4
|
import structlog
|
|
5
5
|
|
|
6
6
|
from rasa.dialogue_understanding.commands import (
|
|
7
|
+
CannotHandleCommand,
|
|
7
8
|
Command,
|
|
8
9
|
CorrectSlotsCommand,
|
|
9
10
|
ErrorCommand,
|
|
@@ -107,6 +108,14 @@ class CommandGenerator:
|
|
|
107
108
|
commands = self._check_commands_against_startable_flows(
|
|
108
109
|
commands, startable_flows
|
|
109
110
|
)
|
|
111
|
+
|
|
112
|
+
# During force slot filling, keep only the command that sets the
|
|
113
|
+
# slot asked by the active collect step.
|
|
114
|
+
# Or return a CannotHandleCommand if no matching command is found.
|
|
115
|
+
commands = self._filter_commands_during_force_slot_filling(
|
|
116
|
+
commands, available_flows, tracker
|
|
117
|
+
)
|
|
118
|
+
|
|
110
119
|
commands_dicts = [command.as_dict() for command in commands]
|
|
111
120
|
message.set(COMMANDS, commands_dicts, add_to_output=True)
|
|
112
121
|
|
|
@@ -370,6 +379,64 @@ class CommandGenerator:
|
|
|
370
379
|
Command.command_from_json(command) for command in message.get(COMMANDS, [])
|
|
371
380
|
]
|
|
372
381
|
|
|
382
|
+
@staticmethod
|
|
383
|
+
def _filter_commands_during_force_slot_filling(
|
|
384
|
+
commands: List[Command],
|
|
385
|
+
available_flows: FlowsList,
|
|
386
|
+
tracker: Optional[DialogueStateTracker] = None,
|
|
387
|
+
) -> List[Command]:
|
|
388
|
+
"""Filter commands during a collect step that has set `force_slot_filling`.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
commands: The commands to filter.
|
|
392
|
+
available_flows: The available flows.
|
|
393
|
+
tracker: The tracker.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
The filtered commands.
|
|
397
|
+
"""
|
|
398
|
+
from rasa.dialogue_understanding.processor.command_processor import (
|
|
399
|
+
get_current_collect_step,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
if tracker is None:
|
|
403
|
+
structlogger.error(
|
|
404
|
+
"command_generator.filter_commands_during_force_slot_filling.tracker_not_found",
|
|
405
|
+
)
|
|
406
|
+
return commands
|
|
407
|
+
|
|
408
|
+
stack = tracker.stack
|
|
409
|
+
step = get_current_collect_step(stack, available_flows)
|
|
410
|
+
|
|
411
|
+
if step is None or not step.force_slot_filling:
|
|
412
|
+
return commands
|
|
413
|
+
|
|
414
|
+
# Retain only the command that sets the slot asked by
|
|
415
|
+
# the active collect step
|
|
416
|
+
filtered_commands: List[Command] = [
|
|
417
|
+
command
|
|
418
|
+
for command in commands
|
|
419
|
+
if (isinstance(command, SetSlotCommand) and command.name == step.collect)
|
|
420
|
+
]
|
|
421
|
+
|
|
422
|
+
if not filtered_commands:
|
|
423
|
+
# If no commands were predicted, we need to return a CannotHandleCommand
|
|
424
|
+
structlogger.debug(
|
|
425
|
+
"command_generator.filter_commands_during_force_slot_filling.no_commands",
|
|
426
|
+
event_info=f"The command generator did not find any SetSlot "
|
|
427
|
+
f"command at the collect step for the slot '{step.collect}'. "
|
|
428
|
+
f"Returning a CannotHandleCommand instead.",
|
|
429
|
+
)
|
|
430
|
+
return [CannotHandleCommand()]
|
|
431
|
+
|
|
432
|
+
structlogger.debug(
|
|
433
|
+
"command_generator.filter_commands_during_force_slot_filling.filtered_commands",
|
|
434
|
+
slot_name=step.collect,
|
|
435
|
+
filtered_commands=filtered_commands,
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
return filtered_commands
|
|
439
|
+
|
|
373
440
|
|
|
374
441
|
def gather_slot_names(commands: List[Command]) -> Set[str]:
|
|
375
442
|
"""Gather all slot names from the commands."""
|
|
@@ -12,9 +12,6 @@ from rasa.dialogue_understanding.commands import (
|
|
|
12
12
|
SetSlotCommand,
|
|
13
13
|
StartFlowCommand,
|
|
14
14
|
)
|
|
15
|
-
from rasa.dialogue_understanding.commands.handle_digressions_command import (
|
|
16
|
-
HandleDigressionsCommand,
|
|
17
|
-
)
|
|
18
15
|
from rasa.dialogue_understanding.constants import KEY_MINIMIZE_NUM_CALLS
|
|
19
16
|
from rasa.dialogue_understanding.generator import CommandGenerator
|
|
20
17
|
from rasa.dialogue_understanding.generator._jinja_filters import to_json_escaped_string
|
|
@@ -610,16 +607,9 @@ class LLMBasedCommandGenerator(
|
|
|
610
607
|
) -> bool:
|
|
611
608
|
"""Check if the LLM current commands should be merged with the prior commands.
|
|
612
609
|
|
|
613
|
-
This can be done if there are no prior start flow commands
|
|
614
|
-
no prior handle digressions commands.
|
|
610
|
+
This can be done if there are no prior start flow commands.
|
|
615
611
|
"""
|
|
616
|
-
|
|
617
|
-
command
|
|
618
|
-
for command in prior_commands
|
|
619
|
-
if isinstance(command, HandleDigressionsCommand)
|
|
620
|
-
]
|
|
621
|
-
|
|
622
|
-
return not prior_start_flow_names and not prior_handle_digressions
|
|
612
|
+
return not prior_start_flow_names
|
|
623
613
|
|
|
624
614
|
def _check_start_flow_command_overlap(
|
|
625
615
|
self,
|