rasa-pro 3.12.0.dev10__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.
- rasa/core/actions/action.py +3 -17
- rasa/core/actions/forms.py +2 -4
- rasa/core/channels/voice_ready/audiocodes.py +23 -42
- rasa/core/channels/voice_stream/tts/azure.py +1 -2
- rasa/core/migrate.py +2 -2
- rasa/core/policies/flows/flow_executor.py +1 -33
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +4 -62
- 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 +2 -11
- 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 +15 -7
- rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +2 -43
- rasa/dialogue_understanding/commands/utils.py +1 -1
- rasa/dialogue_understanding/constants.py +0 -1
- rasa/dialogue_understanding/generator/command_generator.py +76 -10
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +2 -126
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +2 -10
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +2 -4
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +79 -53
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +19 -11
- rasa/dialogue_understanding/patterns/correction.py +1 -13
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +2 -62
- rasa/dialogue_understanding/processor/command_processor.py +28 -117
- rasa/dialogue_understanding/utils.py +0 -31
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +2 -2
- rasa/shared/core/constants.py +1 -22
- rasa/shared/core/domain.py +4 -6
- rasa/shared/core/events.py +2 -13
- rasa/shared/core/flows/flow.py +0 -17
- rasa/shared/core/flows/flows_yaml_schema.json +0 -38
- rasa/shared/core/flows/steps/collect.py +1 -18
- rasa/shared/core/flows/utils.py +1 -16
- rasa/shared/core/slot_mappings.py +6 -6
- rasa/shared/core/slots.py +0 -19
- rasa/shared/core/trackers.py +1 -3
- rasa/shared/nlu/constants.py +0 -1
- rasa/shared/utils/llm.py +1 -1
- rasa/shared/utils/schemas/domain.yml +1 -0
- rasa/validator.py +22 -172
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev10.dist-info → rasa_pro-3.12.0.dev12.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0.dev10.dist-info → rasa_pro-3.12.0.dev12.dist-info}/RECORD +52 -55
- rasa/core/actions/action_handle_digressions.py +0 -142
- rasa/dialogue_understanding/commands/handle_digressions_command.py +0 -150
- rasa/dialogue_understanding/patterns/handle_digressions.py +0 -81
- {rasa_pro-3.12.0.dev10.dist-info → rasa_pro-3.12.0.dev12.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev10.dist-info → rasa_pro-3.12.0.dev12.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev10.dist-info → rasa_pro-3.12.0.dev12.dist-info}/entry_points.txt +0 -0
|
@@ -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"
|
|
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"
|
|
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
|
|
|
@@ -6,17 +6,18 @@ import structlog
|
|
|
6
6
|
from rasa.dialogue_understanding.commands import (
|
|
7
7
|
Command,
|
|
8
8
|
ErrorCommand,
|
|
9
|
+
SetSlotCommand,
|
|
9
10
|
StartFlowCommand,
|
|
10
11
|
)
|
|
11
|
-
from rasa.dialogue_understanding.
|
|
12
|
-
_handle_via_nlu_in_coexistence,
|
|
13
|
-
)
|
|
12
|
+
from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
|
|
14
13
|
from rasa.shared.constants import (
|
|
15
14
|
RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_EMPTY,
|
|
16
15
|
RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_TOO_LONG,
|
|
17
16
|
)
|
|
17
|
+
from rasa.shared.core.constants import SlotMappingType
|
|
18
18
|
from rasa.shared.core.domain import Domain
|
|
19
19
|
from rasa.shared.core.flows import FlowsList
|
|
20
|
+
from rasa.shared.core.slot_mappings import SlotFillingManager
|
|
20
21
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
21
22
|
from rasa.shared.nlu.constants import (
|
|
22
23
|
COMMANDS,
|
|
@@ -91,9 +92,9 @@ class CommandGenerator:
|
|
|
91
92
|
)
|
|
92
93
|
|
|
93
94
|
for message in messages:
|
|
94
|
-
if
|
|
95
|
-
#
|
|
96
|
-
#
|
|
95
|
+
if message.get(COMMANDS):
|
|
96
|
+
# do not overwrite commands if they are already present
|
|
97
|
+
# i.e. another command generator already predicted commands
|
|
97
98
|
continue
|
|
98
99
|
|
|
99
100
|
commands = await self._evaluate_and_predict(
|
|
@@ -105,6 +106,9 @@ class CommandGenerator:
|
|
|
105
106
|
commands = self._check_commands_against_startable_flows(
|
|
106
107
|
commands, startable_flows
|
|
107
108
|
)
|
|
109
|
+
commands = self._check_commands_against_slot_mappings(
|
|
110
|
+
commands, tracker, domain
|
|
111
|
+
)
|
|
108
112
|
commands_dicts = [command.as_dict() for command in commands]
|
|
109
113
|
message.set(COMMANDS, commands_dicts, add_to_output=True)
|
|
110
114
|
|
|
@@ -274,8 +278,70 @@ class CommandGenerator:
|
|
|
274
278
|
return len(message.get(TEXT, "").strip()) == 0
|
|
275
279
|
|
|
276
280
|
@staticmethod
|
|
277
|
-
def
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
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
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
slot_filling_manager = SlotFillingManager(domain, tracker)
|
|
309
|
+
slots_to_be_removed = []
|
|
310
|
+
|
|
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
|
+
)
|
|
281
345
|
]
|
|
346
|
+
|
|
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)
|
|
@@ -8,10 +8,7 @@ from jinja2 import Template
|
|
|
8
8
|
import rasa.shared.utils.io
|
|
9
9
|
from rasa.dialogue_understanding.commands import (
|
|
10
10
|
Command,
|
|
11
|
-
SetSlotCommand,
|
|
12
|
-
StartFlowCommand,
|
|
13
11
|
)
|
|
14
|
-
from rasa.dialogue_understanding.constants import KEY_MINIMIZE_NUM_CALLS
|
|
15
12
|
from rasa.dialogue_understanding.generator import CommandGenerator
|
|
16
13
|
from rasa.dialogue_understanding.generator.constants import (
|
|
17
14
|
DEFAULT_LLM_CONFIG,
|
|
@@ -21,20 +18,13 @@ from rasa.dialogue_understanding.generator.constants import (
|
|
|
21
18
|
LLM_CONFIG_KEY,
|
|
22
19
|
)
|
|
23
20
|
from rasa.dialogue_understanding.generator.flow_retrieval import FlowRetrieval
|
|
24
|
-
from rasa.dialogue_understanding.stack.utils import top_flow_frame
|
|
25
21
|
from rasa.engine.graph import ExecutionContext, GraphComponent
|
|
26
22
|
from rasa.engine.recipes.default_recipe import DefaultV1Recipe
|
|
27
23
|
from rasa.engine.storage.resource import Resource
|
|
28
24
|
from rasa.engine.storage.storage import ModelStorage
|
|
29
|
-
from rasa.shared.core.constants import (
|
|
30
|
-
KEY_MAPPING_TYPE,
|
|
31
|
-
SetSlotExtractor,
|
|
32
|
-
SlotMappingType,
|
|
33
|
-
)
|
|
34
25
|
from rasa.shared.core.domain import Domain
|
|
35
26
|
from rasa.shared.core.flows import Flow, FlowsList, FlowStep
|
|
36
27
|
from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
|
|
37
|
-
from rasa.shared.core.slot_mappings import SlotFillingManager
|
|
38
28
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
39
29
|
from rasa.shared.exceptions import FileIOException, ProviderClientAPIException
|
|
40
30
|
from rasa.shared.nlu.constants import FLOWS_IN_PROMPT
|
|
@@ -367,7 +357,8 @@ class LLMBasedCommandGenerator(
|
|
|
367
357
|
"slots": slots_with_info,
|
|
368
358
|
}
|
|
369
359
|
)
|
|
370
|
-
|
|
360
|
+
|
|
361
|
+
return sorted(result, key=lambda x: x["name"])
|
|
371
362
|
|
|
372
363
|
@staticmethod
|
|
373
364
|
def is_extractable(
|
|
@@ -463,118 +454,3 @@ class LLMBasedCommandGenerator(
|
|
|
463
454
|
if isinstance(current_step, CollectInformationFlowStep)
|
|
464
455
|
else (None, None)
|
|
465
456
|
)
|
|
466
|
-
|
|
467
|
-
@staticmethod
|
|
468
|
-
def _prior_commands_contain_start_flow(prior_commands: List[Command]) -> bool:
|
|
469
|
-
return any(isinstance(command, StartFlowCommand) for command in prior_commands)
|
|
470
|
-
|
|
471
|
-
@staticmethod
|
|
472
|
-
def _prior_commands_contain_set_slot_for_active_collect_step(
|
|
473
|
-
prior_commands: List[Command],
|
|
474
|
-
flows: FlowsList,
|
|
475
|
-
tracker: DialogueStateTracker,
|
|
476
|
-
) -> bool:
|
|
477
|
-
latest_user_frame = top_flow_frame(tracker.stack, ignore_call_frames=False)
|
|
478
|
-
|
|
479
|
-
if latest_user_frame is None:
|
|
480
|
-
return False
|
|
481
|
-
|
|
482
|
-
active_flow = latest_user_frame.flow(flows)
|
|
483
|
-
active_step = active_flow.step_by_id(latest_user_frame.step_id)
|
|
484
|
-
|
|
485
|
-
if not isinstance(active_step, CollectInformationFlowStep):
|
|
486
|
-
return False
|
|
487
|
-
|
|
488
|
-
return any(
|
|
489
|
-
command.name == active_step.collect
|
|
490
|
-
for command in prior_commands
|
|
491
|
-
if isinstance(command, SetSlotCommand)
|
|
492
|
-
)
|
|
493
|
-
|
|
494
|
-
def _should_skip_llm_call(
|
|
495
|
-
self,
|
|
496
|
-
prior_commands: List[Command],
|
|
497
|
-
flows: FlowsList,
|
|
498
|
-
tracker: DialogueStateTracker,
|
|
499
|
-
) -> bool:
|
|
500
|
-
"""Skip invoking the LLM.
|
|
501
|
-
|
|
502
|
-
This returns True if the bot builder sets the property
|
|
503
|
-
KEY_MINIMIZE_NUM_CALLS to True and the prior commands
|
|
504
|
-
either contain a StartFlowCommand or a SetSlot command
|
|
505
|
-
for the current collect step.
|
|
506
|
-
"""
|
|
507
|
-
return self.config.get(KEY_MINIMIZE_NUM_CALLS, False) and (
|
|
508
|
-
self._prior_commands_contain_start_flow(prior_commands)
|
|
509
|
-
or self._prior_commands_contain_set_slot_for_active_collect_step(
|
|
510
|
-
prior_commands, flows, tracker
|
|
511
|
-
)
|
|
512
|
-
)
|
|
513
|
-
|
|
514
|
-
@staticmethod
|
|
515
|
-
def _check_commands_against_slot_mappings(
|
|
516
|
-
commands: List[Command],
|
|
517
|
-
tracker: DialogueStateTracker,
|
|
518
|
-
domain: Optional[Domain] = None,
|
|
519
|
-
) -> List[Command]:
|
|
520
|
-
"""Check if the LLM-issued slot commands are fillable.
|
|
521
|
-
|
|
522
|
-
The LLM-issued slot commands are fillable if the slot
|
|
523
|
-
mappings are satisfied (in particular the mapping conditions).
|
|
524
|
-
"""
|
|
525
|
-
if not domain:
|
|
526
|
-
return commands
|
|
527
|
-
|
|
528
|
-
llm_fillable_slots = [
|
|
529
|
-
tracker.slots.get(command.name)
|
|
530
|
-
for command in commands
|
|
531
|
-
if isinstance(command, SetSlotCommand)
|
|
532
|
-
and command.extractor == SetSlotExtractor.LLM.value
|
|
533
|
-
and tracker.slots.get(command.name) is not None
|
|
534
|
-
]
|
|
535
|
-
|
|
536
|
-
if not llm_fillable_slots:
|
|
537
|
-
return commands
|
|
538
|
-
|
|
539
|
-
slot_filling_manager = SlotFillingManager(domain, tracker)
|
|
540
|
-
slots_to_be_removed = []
|
|
541
|
-
|
|
542
|
-
structlogger.debug(
|
|
543
|
-
"command_processor.check_commands_against_slot_mappings.active_flow",
|
|
544
|
-
active_flow=tracker.active_flow,
|
|
545
|
-
)
|
|
546
|
-
|
|
547
|
-
for slot in llm_fillable_slots:
|
|
548
|
-
should_fill_slot = False
|
|
549
|
-
for mapping in slot.mappings: # type: ignore[union-attr]
|
|
550
|
-
mapping_type = SlotMappingType(mapping.get(KEY_MAPPING_TYPE))
|
|
551
|
-
|
|
552
|
-
should_fill_slot = slot_filling_manager.should_fill_slot(
|
|
553
|
-
slot.name, # type: ignore[union-attr]
|
|
554
|
-
mapping_type,
|
|
555
|
-
mapping,
|
|
556
|
-
)
|
|
557
|
-
|
|
558
|
-
if should_fill_slot:
|
|
559
|
-
break
|
|
560
|
-
|
|
561
|
-
if not should_fill_slot:
|
|
562
|
-
structlogger.debug(
|
|
563
|
-
"command_processor.check_commands_against_slot_mappings.slot_not_fillable",
|
|
564
|
-
slot_name=slot.name, # type: ignore[union-attr]
|
|
565
|
-
)
|
|
566
|
-
slots_to_be_removed.append(slot.name) # type: ignore[union-attr]
|
|
567
|
-
|
|
568
|
-
if not slots_to_be_removed:
|
|
569
|
-
return commands
|
|
570
|
-
|
|
571
|
-
filtered_commands = [
|
|
572
|
-
command
|
|
573
|
-
for command in commands
|
|
574
|
-
if not (
|
|
575
|
-
isinstance(command, SetSlotCommand)
|
|
576
|
-
and command.name in slots_to_be_removed
|
|
577
|
-
)
|
|
578
|
-
]
|
|
579
|
-
|
|
580
|
-
return filtered_commands
|
|
@@ -190,14 +190,9 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
190
190
|
Returns:
|
|
191
191
|
The commands generated by the llm.
|
|
192
192
|
"""
|
|
193
|
-
prior_commands = self._get_prior_commands(message)
|
|
194
|
-
|
|
195
193
|
if tracker is None or flows.is_empty():
|
|
196
194
|
# cannot do anything if there are no flows or no tracker
|
|
197
|
-
return
|
|
198
|
-
|
|
199
|
-
if self._should_skip_llm_call(prior_commands, flows, tracker):
|
|
200
|
-
return prior_commands
|
|
195
|
+
return []
|
|
201
196
|
|
|
202
197
|
try:
|
|
203
198
|
commands = await self._predict_commands_with_multi_step(
|
|
@@ -226,10 +221,7 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
226
221
|
commands=commands,
|
|
227
222
|
)
|
|
228
223
|
|
|
229
|
-
|
|
230
|
-
commands = self._check_commands_against_slot_mappings(commands, tracker, domain)
|
|
231
|
-
|
|
232
|
-
return prior_commands + commands
|
|
224
|
+
return commands
|
|
233
225
|
|
|
234
226
|
@classmethod
|
|
235
227
|
def parse_commands(
|
|
@@ -98,11 +98,9 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
|
|
|
98
98
|
Returns:
|
|
99
99
|
The commands triggered by NLU.
|
|
100
100
|
"""
|
|
101
|
-
prior_commands = self._get_prior_commands(message)
|
|
102
|
-
|
|
103
101
|
if tracker is None or flows.is_empty():
|
|
104
102
|
# cannot do anything if there are no flows or no tracker
|
|
105
|
-
return
|
|
103
|
+
return []
|
|
106
104
|
|
|
107
105
|
domain = kwargs.get("domain", None)
|
|
108
106
|
commands = self.convert_nlu_to_commands(message, tracker, flows, domain)
|
|
@@ -148,7 +146,7 @@ class NLUCommandAdapter(GraphComponent, CommandGenerator):
|
|
|
148
146
|
commands=commands,
|
|
149
147
|
)
|
|
150
148
|
|
|
151
|
-
return
|
|
149
|
+
return commands
|
|
152
150
|
|
|
153
151
|
@staticmethod
|
|
154
152
|
def convert_nlu_to_commands(
|
|
@@ -1,58 +1,84 @@
|
|
|
1
|
+
## Task Description
|
|
1
2
|
Your task is to analyze the current conversation context and generate a list of actions to start new business processes that we call flows, to extract slots, or respond to small talk and knowledge requests.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
{% for flow in available_flows %}
|
|
5
|
-
{{ flow.name }}: {{ flow.description }}
|
|
6
|
-
{% for slot in flow.slots -%}
|
|
7
|
-
slot: {{ slot.name }}{% if slot.description %} ({{ slot.description }}){% endif %}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
|
|
8
|
-
{% endfor %}
|
|
9
|
-
{%- endfor %}
|
|
4
|
+
--
|
|
10
5
|
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
## Available Actions:
|
|
7
|
+
* `start flow flow_name`: Starting a flow. For example, `start flow transfer_money` or `start flow list_contacts`
|
|
8
|
+
* `set slot slot_name slot_value`: Slot setting. For example, `set slot transfer_money_recipient Freddy`. Can be used to correct and change previously set values
|
|
9
|
+
* `cancel flow`: Cancelling the current flow
|
|
10
|
+
* `disambiguate flows flow_name1 flow_name2 ... flow_name_n`: Disambiguate which flow should be started when user input is ambiguous by listing the potential flows as options. For example, `clarify flows list_contacts add_contact remove_contact ...` if the user just wrote "contacts".
|
|
11
|
+
* `provide info`: Responding to the user's questions by supplying relevant information, such as answering FAQs or explaining services
|
|
12
|
+
* `offtopic reply`: Responding to casual or social user messages that are unrelated to any flows, engaging in friendly conversation and addressing off-topic remarks.
|
|
13
|
+
* `hand over`: Handing over to a human, in case the user seems frustrated or explicitly asks to speak to one
|
|
14
|
+
|
|
15
|
+
--
|
|
16
|
+
|
|
17
|
+
## General Tips
|
|
18
|
+
* Do not fill slots with abstract values or placeholders.
|
|
19
|
+
* Only use information provided by the user.
|
|
20
|
+
* Use clarification in ambiguous cases.
|
|
21
|
+
* Multiple flows can be started. If a user wants to digress into a second flow, you do not need to cancel the current flow.
|
|
22
|
+
* Strictly adhere to the provided action format.
|
|
23
|
+
* For categorical slots try to match the user message with potential slot values. Use "other" if you cannot match it
|
|
24
|
+
* Focus on the last message and take it one step at a time.
|
|
25
|
+
* Use the previous conversation steps only to aid understanding.
|
|
26
|
+
|
|
27
|
+
--
|
|
28
|
+
|
|
29
|
+
## Available Flows and Slots
|
|
30
|
+
Use the following structured date:
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"flows": [
|
|
34
|
+
{% for flow in available_flows %}{
|
|
35
|
+
"name": "{{ flow.name }}",
|
|
36
|
+
"description": "{{ flow.description }}"{% if flow.slots %},
|
|
37
|
+
"slots": [{% for slot in flow.slots %}
|
|
38
|
+
{
|
|
39
|
+
"name": "{{ slot.name }}"{% if slot.description %},
|
|
40
|
+
"description": "{{ slot.description }}"{% endif %}{% if slot.allowed_values %},
|
|
41
|
+
"allowed_values": {{ slot.allowed_values }}{% endif %}
|
|
42
|
+
}{% if not loop.last %},{% endif %}{% endfor %}
|
|
43
|
+
]{% endif %}
|
|
44
|
+
}{% if not loop.last %},
|
|
45
|
+
{% endif %}{% endfor %}
|
|
46
|
+
]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
--
|
|
51
|
+
|
|
52
|
+
## Current State
|
|
53
|
+
{% if current_flow != None %}Use the following structured date:
|
|
54
|
+
```json
|
|
55
|
+
{
|
|
56
|
+
"active_flow": "{{ current_flow }}",
|
|
57
|
+
"current_step": {
|
|
58
|
+
"requested_slot": "{{ current_slot }}",
|
|
59
|
+
"requested_slot_description": "{{ current_slot_description }}"
|
|
60
|
+
},
|
|
61
|
+
"slots": [{% for slot in flow_slots %}
|
|
62
|
+
{
|
|
63
|
+
"name": "{{ slot.name }}",
|
|
64
|
+
"value": "{{ slot.value }}",
|
|
65
|
+
"type": "{{ slot.type }}"{% if slot.description %},
|
|
66
|
+
"description": "{{ slot.description }}"{% endif %}{% if slot.allowed_values %},
|
|
67
|
+
"allowed_values": "{{ slot.allowed_values }}"{% endif %}
|
|
68
|
+
}{% if not loop.last %},{% endif %}{% endfor %}
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
```{% else %}
|
|
72
|
+
You are currently not inside any flow.{% endif %}
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Conversation History
|
|
13
77
|
{{ current_conversation }}
|
|
14
78
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Here are the slots of the currently active flow:
|
|
22
|
-
{% for slot in flow_slots -%}
|
|
23
|
-
- name: {{ slot.name }}, value: {{ slot.value }}, type: {{ slot.type }}, description: {{ slot.description}}{% if slot.allowed_values %}, allowed values: {{ slot.allowed_values }}{% endif %}
|
|
24
|
-
{% endfor %}
|
|
25
|
-
{% endif %}
|
|
26
|
-
{% else %}
|
|
27
|
-
You are currently not in any flow and so there are no active slots.
|
|
28
|
-
This means you can only set a slot if you first start a flow that requires that slot.
|
|
29
|
-
{% endif %}
|
|
30
|
-
If you start a flow, first start the flow and then optionally fill that flow's slots with information the user provided in their message.
|
|
31
|
-
|
|
32
|
-
The user just said """{{ user_message }}""".
|
|
33
|
-
|
|
34
|
-
===
|
|
35
|
-
Based on this information generate a list of actions you want to take. Your job is to start flows and to fill slots where appropriate. Any logic of what happens afterwards is handled by the flow engine. These are your available actions:
|
|
36
|
-
* Slot setting, described by "SetSlot(slot_name, slot_value)". An example would be "SetSlot(recipient, Freddy)"
|
|
37
|
-
* Starting another flow, described by "StartFlow(flow_name)". An example would be "StartFlow(transfer_money)"
|
|
38
|
-
* Cancelling the current flow, described by "CancelFlow()"
|
|
39
|
-
* Clarifying which flow should be started. An example would be Clarify(list_contacts, add_contact, remove_contact) if the user just wrote "contacts" and there are multiple potential candidates. It also works with a single flow name to confirm you understood correctly, as in Clarify(transfer_money).
|
|
40
|
-
* Intercepting and handle user messages with the intent to bypass the current step in the flow, described by "SkipQuestion()". Examples of user skip phrases are: "Go to the next question", "Ask me something else".
|
|
41
|
-
* Responding to knowledge-oriented user messages, described by "SearchAndReply()"
|
|
42
|
-
* Responding to a casual, non-task-oriented user message, described by "ChitChat()".
|
|
43
|
-
* Handing off to a human, in case the user seems frustrated or explicitly asks to speak to one, described by "HumanHandoff()".
|
|
44
|
-
* Repeat the last bot messages, described by "RepeatLastBotMessages()". This is useful when the user asks to repeat the last bot messages.
|
|
45
|
-
|
|
46
|
-
===
|
|
47
|
-
Write out the actions you want to take, one per line, in the order they should take place.
|
|
48
|
-
Do not fill slots with abstract values or placeholders.
|
|
49
|
-
Only use information provided by the user.
|
|
50
|
-
Only start a flow if it's completely clear what the user wants. Imagine you were a person reading this message. If it's not 100% clear, clarify the next step.
|
|
51
|
-
Don't be overly confident. Take a conservative approach and clarify before proceeding.
|
|
52
|
-
If the user asks for two things which seem contradictory, clarify before starting a flow.
|
|
53
|
-
If it's not clear whether the user wants to skip the step or to cancel the flow, cancel the flow.
|
|
54
|
-
Strictly adhere to the provided action types listed above.
|
|
55
|
-
Focus on the last message and take it one step at a time.
|
|
56
|
-
Use the previous conversation steps only to aid understanding.
|
|
57
|
-
|
|
58
|
-
Your action list:
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## Task
|
|
82
|
+
Create an action list with one action per line in response to the users last message: """{{ user_message }}""".
|
|
83
|
+
|
|
84
|
+
Your action list:
|
|
@@ -51,6 +51,7 @@ from rasa.shared.utils.llm import (
|
|
|
51
51
|
sanitize_message_for_prompt,
|
|
52
52
|
tracker_as_readable_transcript,
|
|
53
53
|
)
|
|
54
|
+
from rasa.utils.beta import BetaNotEnabledException, ensure_beta_feature_is_enabled
|
|
54
55
|
from rasa.utils.log_utils import log_llm
|
|
55
56
|
|
|
56
57
|
COMMAND_PROMPT_FILE_NAME = "command_prompt.jinja2"
|
|
@@ -110,6 +111,7 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
110
111
|
)
|
|
111
112
|
|
|
112
113
|
self.trace_prompt_tokens = self.config.get("trace_prompt_tokens", False)
|
|
114
|
+
self.repeat_command_enabled = self.is_repeat_command_enabled()
|
|
113
115
|
|
|
114
116
|
### Implementations of LLMBasedCommandGenerator parent
|
|
115
117
|
@staticmethod
|
|
@@ -196,14 +198,9 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
196
198
|
Returns:
|
|
197
199
|
The commands generated by the llm.
|
|
198
200
|
"""
|
|
199
|
-
prior_commands = self._get_prior_commands(message)
|
|
200
|
-
|
|
201
201
|
if tracker is None or flows.is_empty():
|
|
202
202
|
# cannot do anything if there are no flows or no tracker
|
|
203
|
-
return
|
|
204
|
-
|
|
205
|
-
if self._should_skip_llm_call(prior_commands, flows, tracker):
|
|
206
|
-
return prior_commands
|
|
203
|
+
return []
|
|
207
204
|
|
|
208
205
|
try:
|
|
209
206
|
commands = await self._predict_commands(message, flows, tracker)
|
|
@@ -212,7 +209,7 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
212
209
|
# "predict" the ErrorCommand
|
|
213
210
|
commands = [ErrorCommand()]
|
|
214
211
|
|
|
215
|
-
if not commands
|
|
212
|
+
if not commands:
|
|
216
213
|
# no commands are parsed or there's an invalid command
|
|
217
214
|
structlogger.warning(
|
|
218
215
|
"single_step_llm_command_generator.predict_commands",
|
|
@@ -233,10 +230,7 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
233
230
|
commands=commands,
|
|
234
231
|
)
|
|
235
232
|
|
|
236
|
-
|
|
237
|
-
commands = self._check_commands_against_slot_mappings(commands, tracker, domain)
|
|
238
|
-
|
|
239
|
-
return prior_commands + commands
|
|
233
|
+
return commands
|
|
240
234
|
|
|
241
235
|
async def _predict_commands(
|
|
242
236
|
self,
|
|
@@ -412,6 +406,20 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
412
406
|
"current_slot": current_slot,
|
|
413
407
|
"current_slot_description": current_slot_description,
|
|
414
408
|
"user_message": latest_user_message,
|
|
409
|
+
"is_repeat_command_enabled": self.repeat_command_enabled,
|
|
415
410
|
}
|
|
416
411
|
|
|
417
412
|
return self.compile_template(self.prompt_template).render(**inputs)
|
|
413
|
+
|
|
414
|
+
def is_repeat_command_enabled(self) -> bool:
|
|
415
|
+
"""Check for feature flag"""
|
|
416
|
+
RASA_PRO_BETA_REPEAT_COMMAND_ENV_VAR_NAME = "RASA_PRO_BETA_REPEAT_COMMAND"
|
|
417
|
+
try:
|
|
418
|
+
ensure_beta_feature_is_enabled(
|
|
419
|
+
"Repeat Command",
|
|
420
|
+
env_flag=RASA_PRO_BETA_REPEAT_COMMAND_ENV_VAR_NAME,
|
|
421
|
+
)
|
|
422
|
+
except BetaNotEnabledException:
|
|
423
|
+
return False
|
|
424
|
+
|
|
425
|
+
return True
|
|
@@ -54,8 +54,6 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
|
|
|
54
54
|
"""The ID of the flow to reset to."""
|
|
55
55
|
reset_step_id: Optional[str] = None
|
|
56
56
|
"""The ID of the step to reset to."""
|
|
57
|
-
new_slot_values: List[Any] = field(default_factory=list)
|
|
58
|
-
"""The new values for the corrected slots."""
|
|
59
57
|
|
|
60
58
|
@classmethod
|
|
61
59
|
def type(cls) -> str:
|
|
@@ -72,10 +70,6 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
|
|
|
72
70
|
Returns:
|
|
73
71
|
The created `DialogueStackFrame`.
|
|
74
72
|
"""
|
|
75
|
-
new_slot_values = [
|
|
76
|
-
val.get("value") for _, val in data["corrected_slots"].items()
|
|
77
|
-
]
|
|
78
|
-
|
|
79
73
|
return CorrectionPatternFlowStackFrame(
|
|
80
74
|
frame_id=data["frame_id"],
|
|
81
75
|
step_id=data["step_id"],
|
|
@@ -83,7 +77,6 @@ class CorrectionPatternFlowStackFrame(PatternFlowStackFrame):
|
|
|
83
77
|
corrected_slots=data["corrected_slots"],
|
|
84
78
|
reset_flow_id=data["reset_flow_id"],
|
|
85
79
|
reset_step_id=data["reset_step_id"],
|
|
86
|
-
new_slot_values=new_slot_values,
|
|
87
80
|
)
|
|
88
81
|
|
|
89
82
|
|
|
@@ -125,12 +118,7 @@ class ActionCorrectFlowSlot(action.Action):
|
|
|
125
118
|
)
|
|
126
119
|
events.extend(tracker.create_stack_updated_events(updated_stack))
|
|
127
120
|
|
|
128
|
-
events.extend(
|
|
129
|
-
[
|
|
130
|
-
SlotSet(name, value=val.get("value"), filled_by=val.get("filled_by"))
|
|
131
|
-
for name, val in top.corrected_slots.items()
|
|
132
|
-
]
|
|
133
|
-
)
|
|
121
|
+
events.extend([SlotSet(k, v) for k, v in top.corrected_slots.items()])
|
|
134
122
|
return events
|
|
135
123
|
|
|
136
124
|
|