rasa-pro 3.12.0.dev12__py3-none-any.whl → 3.12.0rc1__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/anonymization/anonymization_rule_executor.py +16 -10
- rasa/cli/data.py +16 -0
- rasa/cli/inspect.py +20 -1
- rasa/cli/project_templates/calm/config.yml +2 -2
- rasa/cli/project_templates/calm/endpoints.yml +2 -2
- rasa/cli/shell.py +3 -3
- rasa/cli/utils.py +12 -0
- rasa/core/actions/action.py +99 -193
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/action_run_slot_rejections.py +16 -4
- rasa/core/actions/forms.py +10 -5
- rasa/core/channels/__init__.py +4 -0
- rasa/core/channels/studio_chat.py +19 -0
- rasa/core/channels/telegram.py +42 -24
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_ready/utils.py +1 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +10 -4
- rasa/core/channels/voice_stream/asr/azure.py +14 -1
- rasa/core/channels/voice_stream/asr/deepgram.py +20 -4
- rasa/core/channels/voice_stream/audiocodes.py +264 -0
- rasa/core/channels/voice_stream/browser_audio.py +5 -1
- rasa/core/channels/voice_stream/call_state.py +10 -1
- rasa/core/channels/voice_stream/genesys.py +335 -0
- rasa/core/channels/voice_stream/tts/azure.py +11 -2
- rasa/core/channels/voice_stream/tts/cartesia.py +29 -10
- rasa/core/channels/voice_stream/twilio_media_streams.py +2 -1
- rasa/core/channels/voice_stream/voice_channel.py +25 -3
- rasa/core/constants.py +2 -0
- rasa/core/migrate.py +2 -2
- rasa/core/nlg/contextual_response_rephraser.py +18 -1
- rasa/core/nlg/generator.py +83 -15
- rasa/core/nlg/response.py +6 -3
- rasa/core/nlg/translate.py +55 -0
- rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +1 -1
- rasa/core/policies/flows/flow_executor.py +47 -46
- rasa/core/processor.py +72 -9
- rasa/core/run.py +4 -3
- rasa/dialogue_understanding/commands/can_not_handle_command.py +20 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +80 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +20 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/clarify_command.py +29 -3
- rasa/dialogue_understanding/commands/command.py +1 -16
- rasa/dialogue_understanding/commands/command_syntax_manager.py +55 -0
- 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 +20 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +20 -2
- rasa/dialogue_understanding/commands/prompt_command.py +94 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +20 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +29 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +20 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +61 -2
- rasa/dialogue_understanding/commands/utils.py +98 -4
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/__init__.py +2 -0
- rasa/dialogue_understanding/generator/command_generator.py +110 -73
- rasa/dialogue_understanding/generator/command_parser.py +16 -13
- rasa/dialogue_understanding/generator/constants.py +3 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +170 -5
- rasa/dialogue_understanding/generator/llm_command_generator.py +5 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +26 -4
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +44 -3
- rasa/dialogue_understanding/generator/prompt_templates/__init__.py +0 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +60 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +77 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +68 -0
- rasa/dialogue_understanding/generator/{single_step/command_prompt_template.jinja2 → prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2} +1 -1
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +460 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +12 -318
- rasa/dialogue_understanding/generator/utils.py +32 -1
- rasa/dialogue_understanding/patterns/collect_information.py +1 -1
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +78 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/patterns/validate_slot.py +65 -0
- rasa/dialogue_understanding/processor/command_processor.py +154 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/README.md +50 -0
- rasa/dialogue_understanding_test/du_test_case.py +28 -8
- rasa/dialogue_understanding_test/du_test_result.py +13 -9
- rasa/dialogue_understanding_test/io.py +14 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +3 -3
- rasa/e2e_test/utils/io.py +0 -37
- rasa/engine/graph.py +1 -0
- rasa/engine/language.py +140 -0
- rasa/engine/recipes/config_files/default_config.yml +4 -0
- rasa/engine/recipes/default_recipe.py +2 -0
- rasa/engine/recipes/graph_recipe.py +2 -0
- rasa/engine/storage/local_model_storage.py +1 -0
- rasa/engine/storage/storage.py +4 -1
- rasa/model_manager/runner_service.py +7 -4
- rasa/model_manager/socket_bridge.py +7 -6
- rasa/model_manager/warm_rasa_process.py +0 -1
- rasa/model_training.py +24 -27
- rasa/shared/constants.py +15 -13
- rasa/shared/core/constants.py +30 -3
- rasa/shared/core/domain.py +13 -20
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/constants.py +11 -0
- rasa/shared/core/flows/flow.py +100 -19
- rasa/shared/core/flows/flows_yaml_schema.json +69 -3
- rasa/shared/core/flows/steps/collect.py +19 -37
- rasa/shared/core/flows/utils.py +43 -4
- rasa/shared/core/flows/validation.py +1 -1
- rasa/shared/core/slot_mappings.py +350 -111
- rasa/shared/core/slots.py +154 -3
- rasa/shared/core/trackers.py +77 -2
- rasa/shared/importers/importer.py +50 -2
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/nlu/training_data/schemas/responses.yml +19 -12
- rasa/shared/providers/_configs/azure_entra_id_config.py +541 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +138 -3
- rasa/shared/providers/_configs/client_config.py +3 -1
- rasa/shared/providers/_configs/default_litellm_client_config.py +3 -1
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +3 -1
- rasa/shared/providers/_configs/litellm_router_client_config.py +3 -1
- rasa/shared/providers/_configs/model_group_config.py +4 -2
- rasa/shared/providers/_configs/oauth_config.py +33 -0
- rasa/shared/providers/_configs/openai_client_config.py +3 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +3 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +3 -1
- rasa/shared/providers/constants.py +6 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +28 -3
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +3 -1
- rasa/shared/providers/llm/_base_litellm_client.py +42 -17
- rasa/shared/providers/llm/azure_openai_llm_client.py +81 -25
- rasa/shared/providers/llm/default_litellm_llm_client.py +3 -1
- rasa/shared/providers/llm/litellm_router_llm_client.py +29 -8
- rasa/shared/providers/llm/llm_client.py +23 -7
- rasa/shared/providers/llm/openai_llm_client.py +9 -3
- rasa/shared/providers/llm/rasa_llm_client.py +11 -2
- rasa/shared/providers/llm/self_hosted_llm_client.py +30 -11
- rasa/shared/providers/router/_base_litellm_router_client.py +3 -1
- rasa/shared/providers/router/router_client.py +3 -1
- rasa/shared/utils/constants.py +3 -0
- rasa/shared/utils/llm.py +31 -8
- rasa/shared/utils/pykwalify_extensions.py +24 -0
- rasa/shared/utils/schemas/domain.yml +26 -1
- rasa/telemetry.py +45 -14
- rasa/tracing/config.py +2 -0
- rasa/tracing/constants.py +12 -0
- rasa/tracing/instrumentation/instrumentation.py +36 -0
- rasa/tracing/instrumentation/metrics.py +41 -0
- rasa/tracing/metric_instrument_provider.py +40 -0
- rasa/utils/common.py +0 -1
- rasa/validator.py +561 -89
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/METADATA +2 -1
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/RECORD +153 -134
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev12.dist-info → rasa_pro-3.12.0rc1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import Any, Dict, List
|
|
5
|
+
|
|
6
|
+
from rasa.dialogue_understanding.stack.frames import (
|
|
7
|
+
PatternFlowStackFrame,
|
|
8
|
+
)
|
|
9
|
+
from rasa.shared.constants import (
|
|
10
|
+
RASA_DEFAULT_FLOW_PATTERN_PREFIX,
|
|
11
|
+
REFILL_UTTER,
|
|
12
|
+
REJECTIONS,
|
|
13
|
+
)
|
|
14
|
+
from rasa.shared.core.slots import SlotRejection
|
|
15
|
+
|
|
16
|
+
FLOW_PATTERN_VALIDATE_SLOT = RASA_DEFAULT_FLOW_PATTERN_PREFIX + "validate_slot"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class ValidateSlotPatternFlowStackFrame(PatternFlowStackFrame):
|
|
21
|
+
"""A pattern flow stack frame to validate slots."""
|
|
22
|
+
|
|
23
|
+
flow_id: str = FLOW_PATTERN_VALIDATE_SLOT
|
|
24
|
+
"""The ID of the flow."""
|
|
25
|
+
validate: str = ""
|
|
26
|
+
"""The slot that should be validated."""
|
|
27
|
+
refill_utter: str = ""
|
|
28
|
+
"""The utter action that should be executed to ask the user to refill the
|
|
29
|
+
information."""
|
|
30
|
+
refill_action: str = ""
|
|
31
|
+
"""The action that should be executed to ask the user to refill the
|
|
32
|
+
information."""
|
|
33
|
+
rejections: List[SlotRejection] = field(default_factory=list)
|
|
34
|
+
"""The predicate check that should be applied to the filled slot.
|
|
35
|
+
If a predicate check fails, its `utter` action indicated under rejections
|
|
36
|
+
will be executed.
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def type(cls) -> str:
|
|
41
|
+
"""Returns the type of the frame."""
|
|
42
|
+
return FLOW_PATTERN_VALIDATE_SLOT
|
|
43
|
+
|
|
44
|
+
@staticmethod
|
|
45
|
+
def from_dict(data: Dict[str, Any]) -> ValidateSlotPatternFlowStackFrame:
|
|
46
|
+
"""Creates a `DialogueStackFrame` from a dictionary.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
data: The dictionary to create the `DialogueStackFrame` from.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
The created `DialogueStackFrame`.
|
|
53
|
+
"""
|
|
54
|
+
rejections = [
|
|
55
|
+
SlotRejection.from_dict(rejection) for rejection in data.get(REJECTIONS, [])
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
return ValidateSlotPatternFlowStackFrame(
|
|
59
|
+
frame_id=data["frame_id"],
|
|
60
|
+
step_id=data["step_id"],
|
|
61
|
+
validate=data["validate"],
|
|
62
|
+
refill_action=data["refill_action"],
|
|
63
|
+
refill_utter=data[REFILL_UTTER],
|
|
64
|
+
rejections=rejections,
|
|
65
|
+
)
|
|
@@ -18,7 +18,13 @@ from rasa.dialogue_understanding.commands import (
|
|
|
18
18
|
from rasa.dialogue_understanding.commands.handle_code_change_command import (
|
|
19
19
|
HandleCodeChangeCommand,
|
|
20
20
|
)
|
|
21
|
+
from rasa.dialogue_understanding.commands.handle_digressions_command import (
|
|
22
|
+
HandleDigressionsCommand,
|
|
23
|
+
)
|
|
21
24
|
from rasa.dialogue_understanding.commands.set_slot_command import SetSlotExtractor
|
|
25
|
+
from rasa.dialogue_understanding.commands.utils import (
|
|
26
|
+
create_validate_frames_from_slot_set_events,
|
|
27
|
+
)
|
|
22
28
|
from rasa.dialogue_understanding.patterns.chitchat import FLOW_PATTERN_CHITCHAT
|
|
23
29
|
from rasa.dialogue_understanding.patterns.collect_information import (
|
|
24
30
|
CollectInformationPatternFlowStackFrame,
|
|
@@ -26,6 +32,9 @@ from rasa.dialogue_understanding.patterns.collect_information import (
|
|
|
26
32
|
from rasa.dialogue_understanding.patterns.correction import (
|
|
27
33
|
CorrectionPatternFlowStackFrame,
|
|
28
34
|
)
|
|
35
|
+
from rasa.dialogue_understanding.patterns.validate_slot import (
|
|
36
|
+
ValidateSlotPatternFlowStackFrame,
|
|
37
|
+
)
|
|
29
38
|
from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
|
|
30
39
|
from rasa.dialogue_understanding.stack.frames import (
|
|
31
40
|
BaseFlowStackFrame,
|
|
@@ -47,6 +56,7 @@ from rasa.shared.core.constants import (
|
|
|
47
56
|
from rasa.shared.core.events import Event, SlotSet
|
|
48
57
|
from rasa.shared.core.flows import FlowsList
|
|
49
58
|
from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
|
|
59
|
+
from rasa.shared.core.slot_mappings import SlotMapping
|
|
50
60
|
from rasa.shared.core.slots import Slot
|
|
51
61
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
52
62
|
from rasa.shared.core.training_data.structures import StoryGraph
|
|
@@ -230,18 +240,51 @@ def execute_commands(
|
|
|
230
240
|
# and then pushing the commands onto the stack in the reversed order.
|
|
231
241
|
reversed_commands = list(reversed(commands))
|
|
232
242
|
|
|
243
|
+
# we need to keep track of the ValidateSlotPatternFlowStackFrame that
|
|
244
|
+
# should be pushed onto the stack before executing the StartFlowCommands.
|
|
245
|
+
# This is necessary to make sure that slots filled before the start of a
|
|
246
|
+
# flow can be immediately validated without waiting till the flow is started
|
|
247
|
+
# and completed.
|
|
248
|
+
stack_frames_to_follow_commands: List[ValidateSlotPatternFlowStackFrame] = []
|
|
249
|
+
|
|
233
250
|
validate_state_of_commands(commands)
|
|
234
251
|
|
|
235
252
|
for command in reversed_commands:
|
|
236
253
|
new_events = command.run_command_on_tracker(
|
|
237
254
|
tracker, all_flows, original_tracker
|
|
238
255
|
)
|
|
256
|
+
|
|
257
|
+
_, stack_frames_to_follow_commands = (
|
|
258
|
+
create_validate_frames_from_slot_set_events(
|
|
259
|
+
tracker, new_events, stack_frames_to_follow_commands
|
|
260
|
+
)
|
|
261
|
+
)
|
|
262
|
+
|
|
239
263
|
events.extend(new_events)
|
|
240
264
|
tracker.update_with_events(new_events)
|
|
241
265
|
|
|
266
|
+
new_events = push_stack_frames_to_follow_commands(
|
|
267
|
+
tracker, stack_frames_to_follow_commands
|
|
268
|
+
)
|
|
269
|
+
events.extend(new_events)
|
|
270
|
+
|
|
242
271
|
return remove_duplicated_set_slots(events)
|
|
243
272
|
|
|
244
273
|
|
|
274
|
+
def push_stack_frames_to_follow_commands(
|
|
275
|
+
tracker: DialogueStateTracker, stack_frames: List
|
|
276
|
+
) -> List[Event]:
|
|
277
|
+
"""Push stack frames to follow commands."""
|
|
278
|
+
new_events = []
|
|
279
|
+
|
|
280
|
+
for frame in stack_frames:
|
|
281
|
+
stack = tracker.stack
|
|
282
|
+
stack.push(frame)
|
|
283
|
+
new_events.extend(tracker.create_stack_updated_events(stack))
|
|
284
|
+
tracker.update_with_events(new_events)
|
|
285
|
+
return new_events
|
|
286
|
+
|
|
287
|
+
|
|
245
288
|
def remove_duplicated_set_slots(events: List[Event]) -> List[Event]:
|
|
246
289
|
"""Removes duplicated set slot events.
|
|
247
290
|
|
|
@@ -395,6 +438,28 @@ def clean_up_commands(
|
|
|
395
438
|
command=command,
|
|
396
439
|
)
|
|
397
440
|
|
|
441
|
+
elif isinstance(command, StartFlowCommand) and active_flow is not None:
|
|
442
|
+
# push handle digressions command if we are at a collect step of
|
|
443
|
+
# a flow and a new flow is started
|
|
444
|
+
collect_info = get_current_collect_step(tracker.stack, all_flows)
|
|
445
|
+
current_flow = all_flows.flow_by_id(active_flow)
|
|
446
|
+
current_flow_condition = current_flow and (
|
|
447
|
+
current_flow.ask_confirm_digressions or current_flow.block_digressions
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
if collect_info and (
|
|
451
|
+
collect_info.ask_confirm_digressions
|
|
452
|
+
or collect_info.block_digressions
|
|
453
|
+
or current_flow_condition
|
|
454
|
+
):
|
|
455
|
+
clean_commands.append(HandleDigressionsCommand(flow=command.flow))
|
|
456
|
+
structlogger.debug(
|
|
457
|
+
"command_processor.clean_up_commands.push_handle_digressions",
|
|
458
|
+
command=command,
|
|
459
|
+
)
|
|
460
|
+
else:
|
|
461
|
+
clean_commands.append(command)
|
|
462
|
+
|
|
398
463
|
# handle chitchat command differently from other free-form answer commands
|
|
399
464
|
elif isinstance(command, ChitChatAnswerCommand):
|
|
400
465
|
clean_commands = clean_up_chitchat_command(
|
|
@@ -527,13 +592,48 @@ def clean_up_slot_command(
|
|
|
527
592
|
)
|
|
528
593
|
return resulting_commands
|
|
529
594
|
|
|
530
|
-
if not should_slot_be_set(slot, command):
|
|
595
|
+
if not should_slot_be_set(slot, command, resulting_commands):
|
|
596
|
+
structlogger.debug(
|
|
597
|
+
"command_processor.clean_up_slot_command.skip_command.extractor_"
|
|
598
|
+
"does_not_match_slot_mapping",
|
|
599
|
+
extractor=command.extractor,
|
|
600
|
+
slot_name=slot.name,
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# prevent adding a cannot handle command in case commands_so_far already
|
|
604
|
+
# contains a valid prior set slot command for the same slot whose current
|
|
605
|
+
# slot command was rejected by should_slot_be_set
|
|
606
|
+
slot_command_exists_already = any(
|
|
607
|
+
isinstance(command, SetSlotCommand) and command.name == slot.name
|
|
608
|
+
for command in resulting_commands
|
|
609
|
+
)
|
|
610
|
+
|
|
531
611
|
cannot_handle = CannotHandleCommand(reason=CANNOT_HANDLE_REASON)
|
|
532
|
-
if cannot_handle not in resulting_commands:
|
|
612
|
+
if not slot_command_exists_already and cannot_handle not in resulting_commands:
|
|
533
613
|
resulting_commands.append(cannot_handle)
|
|
534
614
|
|
|
535
615
|
return resulting_commands
|
|
536
616
|
|
|
617
|
+
if (
|
|
618
|
+
slot.filled_by == SetSlotExtractor.NLU.value
|
|
619
|
+
and command.extractor == SetSlotExtractor.LLM.value
|
|
620
|
+
):
|
|
621
|
+
allow_nlu_correction = any(
|
|
622
|
+
[
|
|
623
|
+
mapping.allow_nlu_correction is True
|
|
624
|
+
for mapping in slot.mappings
|
|
625
|
+
if mapping.type == SlotMappingType.FROM_LLM
|
|
626
|
+
]
|
|
627
|
+
)
|
|
628
|
+
|
|
629
|
+
if not allow_nlu_correction:
|
|
630
|
+
structlogger.debug(
|
|
631
|
+
"command_processor.clean_up_slot_command"
|
|
632
|
+
".skip_command.disallow_llm_correction_of_nlu_set_value",
|
|
633
|
+
command=command,
|
|
634
|
+
)
|
|
635
|
+
return resulting_commands
|
|
636
|
+
|
|
537
637
|
if command.name in slots_so_far and command.name != ROUTE_TO_CALM_SLOT:
|
|
538
638
|
current_collect_info = get_current_collect_step(stack, all_flows)
|
|
539
639
|
|
|
@@ -574,7 +674,7 @@ def clean_up_slot_command(
|
|
|
574
674
|
)
|
|
575
675
|
|
|
576
676
|
# Group all corrections into one command
|
|
577
|
-
corrected_slot = CorrectedSlot(command.name, command.value)
|
|
677
|
+
corrected_slot = CorrectedSlot(command.name, command.value, command.extractor)
|
|
578
678
|
for c in resulting_commands:
|
|
579
679
|
if isinstance(c, CorrectSlotsCommand):
|
|
580
680
|
c.corrected_slots.append(corrected_slot)
|
|
@@ -658,7 +758,9 @@ def clean_up_chitchat_command(
|
|
|
658
758
|
return resulting_commands
|
|
659
759
|
|
|
660
760
|
|
|
661
|
-
def should_slot_be_set(
|
|
761
|
+
def should_slot_be_set(
|
|
762
|
+
slot: Slot, command: SetSlotCommand, commands_so_far: Optional[List[Command]] = None
|
|
763
|
+
) -> bool:
|
|
662
764
|
"""Check if a slot should be set by a command."""
|
|
663
765
|
if command.extractor == SetSlotExtractor.COMMAND_PAYLOAD_READER.value:
|
|
664
766
|
# if the command is issued by the command payload reader, it means the slot
|
|
@@ -666,37 +768,61 @@ def should_slot_be_set(slot: Slot, command: SetSlotCommand) -> bool:
|
|
|
666
768
|
# we can always set it
|
|
667
769
|
return True
|
|
668
770
|
|
|
771
|
+
if commands_so_far is None:
|
|
772
|
+
commands_so_far = []
|
|
773
|
+
|
|
774
|
+
set_slot_commands_so_far = [
|
|
775
|
+
command
|
|
776
|
+
for command in commands_so_far
|
|
777
|
+
if isinstance(command, SetSlotCommand) and command.name == slot.name
|
|
778
|
+
]
|
|
779
|
+
|
|
669
780
|
slot_mappings = slot.mappings
|
|
670
781
|
|
|
671
|
-
if not
|
|
672
|
-
slot_mappings = [
|
|
782
|
+
if not slot.mappings:
|
|
783
|
+
slot_mappings = [SlotMapping(type=SlotMappingType.FROM_LLM)]
|
|
673
784
|
|
|
674
|
-
for mapping in slot_mappings
|
|
675
|
-
mapping_type = SlotMappingType(
|
|
676
|
-
mapping.get("type", SlotMappingType.FROM_LLM.value)
|
|
677
|
-
)
|
|
785
|
+
mapping_types = [mapping.type for mapping in slot_mappings]
|
|
678
786
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
787
|
+
slot_has_nlu_mapping = any(
|
|
788
|
+
[mapping_type.is_predefined_type() for mapping_type in mapping_types]
|
|
789
|
+
)
|
|
790
|
+
slot_has_llm_mapping = any(
|
|
791
|
+
[mapping_type == SlotMappingType.FROM_LLM for mapping_type in mapping_types]
|
|
792
|
+
)
|
|
793
|
+
slot_has_controlled_mapping = any(
|
|
794
|
+
[mapping_type == SlotMappingType.CONTROLLED for mapping_type in mapping_types]
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
if set_slot_commands_so_far and command.extractor == SetSlotExtractor.LLM.value:
|
|
798
|
+
# covers the following scenarios:
|
|
799
|
+
# scenario 1: NLU mapping extracts a value for slot_a → If LLM extracts a value for slot_a, it is discarded. # noqa: E501
|
|
800
|
+
# scenario 2: NLU mapping is unable to extract a value for slot_a → If LLM extracts a value for slot_a, it is accepted. # noqa: E501
|
|
801
|
+
command_has_nlu_extractor = any(
|
|
802
|
+
[
|
|
803
|
+
command.extractor == SetSlotExtractor.NLU.value
|
|
804
|
+
for command in set_slot_commands_so_far
|
|
805
|
+
]
|
|
686
806
|
)
|
|
807
|
+
return not command_has_nlu_extractor and slot_has_llm_mapping
|
|
687
808
|
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
809
|
+
if (
|
|
810
|
+
slot_has_nlu_mapping
|
|
811
|
+
and command.extractor == SetSlotExtractor.LLM.value
|
|
812
|
+
and not slot_has_llm_mapping
|
|
813
|
+
):
|
|
814
|
+
return False
|
|
692
815
|
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
816
|
+
if (
|
|
817
|
+
slot_has_llm_mapping
|
|
818
|
+
and command.extractor == SetSlotExtractor.NLU.value
|
|
819
|
+
and not slot_has_nlu_mapping
|
|
820
|
+
):
|
|
821
|
+
return False
|
|
822
|
+
|
|
823
|
+
if slot_has_controlled_mapping and not (
|
|
824
|
+
slot_has_nlu_mapping or slot_has_llm_mapping
|
|
825
|
+
):
|
|
700
826
|
return False
|
|
701
827
|
|
|
702
828
|
return True
|
|
@@ -5,7 +5,10 @@ from rasa.dialogue_understanding.commands import Command
|
|
|
5
5
|
from rasa.dialogue_understanding.constants import (
|
|
6
6
|
RASA_RECORD_COMMANDS_AND_PROMPTS_ENV_VAR_NAME,
|
|
7
7
|
)
|
|
8
|
+
from rasa.shared.constants import ROUTE_TO_CALM_SLOT
|
|
9
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
8
10
|
from rasa.shared.nlu.constants import (
|
|
11
|
+
COMMANDS,
|
|
9
12
|
KEY_COMPONENT_NAME,
|
|
10
13
|
KEY_LLM_RESPONSE_METADATA,
|
|
11
14
|
KEY_PROMPT_NAME,
|
|
@@ -13,6 +16,7 @@ from rasa.shared.nlu.constants import (
|
|
|
13
16
|
KEY_USER_PROMPT,
|
|
14
17
|
PREDICTED_COMMANDS,
|
|
15
18
|
PROMPTS,
|
|
19
|
+
SET_SLOT_COMMAND,
|
|
16
20
|
)
|
|
17
21
|
from rasa.shared.nlu.training_data.message import Message
|
|
18
22
|
from rasa.shared.providers.llm.llm_response import LLMResponse
|
|
@@ -131,3 +135,30 @@ def add_prompt_to_message_parse_data(
|
|
|
131
135
|
|
|
132
136
|
# Update the message with the new prompts list.
|
|
133
137
|
message.set(PROMPTS, prompts, add_to_output=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def _handle_via_nlu_in_coexistence(
|
|
141
|
+
tracker: Optional[DialogueStateTracker], message: Message
|
|
142
|
+
) -> bool:
|
|
143
|
+
"""Check if the message should be handled by the NLU subsystem in coexistence mode.""" # noqa: E501
|
|
144
|
+
if not tracker:
|
|
145
|
+
return False
|
|
146
|
+
|
|
147
|
+
if not tracker.has_coexistence_routing_slot:
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
value = tracker.get_slot(ROUTE_TO_CALM_SLOT)
|
|
151
|
+
if value is not None:
|
|
152
|
+
return not value
|
|
153
|
+
|
|
154
|
+
# routing slot has been reset so we need to check
|
|
155
|
+
# the command issued by the Router component
|
|
156
|
+
if message.get(COMMANDS):
|
|
157
|
+
for command in message.get(COMMANDS):
|
|
158
|
+
if (
|
|
159
|
+
command.get("command") == SET_SLOT_COMMAND
|
|
160
|
+
and command.get("name") == ROUTE_TO_CALM_SLOT
|
|
161
|
+
):
|
|
162
|
+
return not command.get("value")
|
|
163
|
+
|
|
164
|
+
return False
|
|
@@ -377,3 +377,53 @@ Test cases in **to_review** may require manual intervention because the E2E test
|
|
|
377
377
|
Review these cases to ensure that the converted test cases are correct and the list of commands and
|
|
378
378
|
bot responses is complete.
|
|
379
379
|
|
|
380
|
+
|
|
381
|
+
## Converting DUT test from one DSL to a another DSL
|
|
382
|
+
|
|
383
|
+
If you need to transform your commands from one DSL format to another
|
|
384
|
+
(for instance, updating `StartFlow(flow_name)` to `start flow_name` or `SetSlot(slot_name, slot_value)` to `set slot_name slot_value`),
|
|
385
|
+
you can use a standalone Python script:
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
python convert_dut_dsl.py --dut-tests-dir <path> --output-dir <path> --dsl-mappings <path>
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
The script has the following required parameters:
|
|
392
|
+
|
|
393
|
+
- `--dut-tests-dir <path>`: The directory (relative or absolute) containing your
|
|
394
|
+
existing Dialogue Understanding Tests (DUT). The script will look for `.yaml` or
|
|
395
|
+
`.yml` files within this folder (and subfolders).
|
|
396
|
+
- `--output-dir <path>`: The directory where transformed files will be saved. The folder
|
|
397
|
+
structure from your `dut-tests-dir` is preserved.
|
|
398
|
+
- `--dsl-mappings <path>`: The YAML file defining your DSL mapping rules.
|
|
399
|
+
|
|
400
|
+
The YAML file containing the mappings must adhere to the following format:
|
|
401
|
+
```yaml
|
|
402
|
+
mappings:
|
|
403
|
+
|
|
404
|
+
- from_dsl_regex: "^StartFlow\\(([^)]*)\\)$"
|
|
405
|
+
to_dsl_pattern: "start {1}"
|
|
406
|
+
|
|
407
|
+
- from_dsl_regex: "^SetSlot\\(([^,]+),\\s*(.*)\\)$"
|
|
408
|
+
to_dsl_pattern: "set {1} {2}"
|
|
409
|
+
|
|
410
|
+
- from_dsl_regex: "Clarify\(([\"\'a-zA-Z0-9_, ]*)\)"
|
|
411
|
+
to_dsl_pattern: "clarify {1}"
|
|
412
|
+
input_separators:
|
|
413
|
+
- ","
|
|
414
|
+
- " "
|
|
415
|
+
output_separator: " "
|
|
416
|
+
|
|
417
|
+
# ... add more mappings here
|
|
418
|
+
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
- `from_dsl_regex`: A regular expression (string) used to match the old DSL command.
|
|
422
|
+
Must include any necessary anchors (like ^ and $) and capturing groups ( ... ) for
|
|
423
|
+
dynamic parts.
|
|
424
|
+
- `to_dsl_pattern`: A string that contains placeholders like `{1}`, `{2}`, etc. Each
|
|
425
|
+
placeholder corresponds to a capturing group in from_dsl_regex, in order of
|
|
426
|
+
appearance.
|
|
427
|
+
- `input_separators`: Optional list of separators of the captured groups that can be replaced
|
|
428
|
+
with the `output_separator`
|
|
429
|
+
- `output_separator`: Output separator to replace separators from the list of `input_separators` in the captured group.
|
|
@@ -2,7 +2,7 @@ from typing import Any, Dict, Iterator, List, Optional, Tuple
|
|
|
2
2
|
|
|
3
3
|
from pydantic import BaseModel, Field
|
|
4
4
|
|
|
5
|
-
from rasa.dialogue_understanding.commands import
|
|
5
|
+
from rasa.dialogue_understanding.commands.prompt_command import PromptCommand
|
|
6
6
|
from rasa.dialogue_understanding.generator.command_parser import parse_commands
|
|
7
7
|
from rasa.dialogue_understanding_test.command_comparison import are_command_lists_equal
|
|
8
8
|
from rasa.dialogue_understanding_test.constants import (
|
|
@@ -30,6 +30,7 @@ from rasa.shared.nlu.constants import (
|
|
|
30
30
|
KEY_USAGE = "usage"
|
|
31
31
|
KEY_PROMPT_TOKENS = "prompt_tokens"
|
|
32
32
|
KEY_COMPLETION_TOKENS = "completion_tokens"
|
|
33
|
+
KEY_CHOICES = "choices"
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
class DialogueUnderstandingOutput(BaseModel):
|
|
@@ -65,11 +66,18 @@ class DialogueUnderstandingOutput(BaseModel):
|
|
|
65
66
|
"""
|
|
66
67
|
|
|
67
68
|
# Dict with component name as key and list of commands as value
|
|
68
|
-
commands: Dict[str, List[
|
|
69
|
+
commands: Dict[str, List[PromptCommand]]
|
|
69
70
|
# List of prompts
|
|
70
71
|
prompts: Optional[List[Dict[str, Any]]] = None
|
|
71
72
|
|
|
72
|
-
|
|
73
|
+
class Config:
|
|
74
|
+
"""Skip validation for PromptCommand protocol as pydantic does not know how to
|
|
75
|
+
serialize or handle instances of a protocol.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
arbitrary_types_allowed = True
|
|
79
|
+
|
|
80
|
+
def get_predicted_commands(self) -> List[PromptCommand]:
|
|
73
81
|
"""Get all commands from the output."""
|
|
74
82
|
return [
|
|
75
83
|
command
|
|
@@ -144,6 +152,11 @@ class DialogueUnderstandingOutput(BaseModel):
|
|
|
144
152
|
KEY_COMPLETION_TOKENS
|
|
145
153
|
)
|
|
146
154
|
|
|
155
|
+
choices = prompt_data.get(KEY_LLM_RESPONSE_METADATA, {}).get(KEY_CHOICES)
|
|
156
|
+
if choices and len(choices) > 0:
|
|
157
|
+
# Add the action list returned by the LLM to the prompt_info
|
|
158
|
+
prompt_info[KEY_CHOICES] = choices[0]
|
|
159
|
+
|
|
147
160
|
data[component_name].append(prompt_info)
|
|
148
161
|
|
|
149
162
|
return data
|
|
@@ -155,9 +168,16 @@ class DialogueUnderstandingTestStep(BaseModel):
|
|
|
155
168
|
template: Optional[str] = None
|
|
156
169
|
line: Optional[int] = None
|
|
157
170
|
metadata_name: Optional[str] = None
|
|
158
|
-
commands: Optional[List[
|
|
171
|
+
commands: Optional[List[PromptCommand]] = None
|
|
159
172
|
dialogue_understanding_output: Optional[DialogueUnderstandingOutput] = None
|
|
160
173
|
|
|
174
|
+
class Config:
|
|
175
|
+
"""Skip validation for PromptCommand protocol as pydantic does not know how to
|
|
176
|
+
serialize or handle instances of a protocol.
|
|
177
|
+
"""
|
|
178
|
+
|
|
179
|
+
arbitrary_types_allowed = True
|
|
180
|
+
|
|
161
181
|
def as_dict(self) -> Dict[str, Any]:
|
|
162
182
|
if self.actor == ACTOR_USER:
|
|
163
183
|
if self.commands:
|
|
@@ -178,7 +198,7 @@ class DialogueUnderstandingTestStep(BaseModel):
|
|
|
178
198
|
def from_dict(
|
|
179
199
|
step: Dict[str, Any],
|
|
180
200
|
flows: FlowsList,
|
|
181
|
-
custom_command_classes: List[
|
|
201
|
+
custom_command_classes: List[PromptCommand] = [],
|
|
182
202
|
remove_default_commands: List[str] = [],
|
|
183
203
|
) -> "DialogueUnderstandingTestStep":
|
|
184
204
|
"""Creates a DialogueUnderstandingTestStep from a dictionary.
|
|
@@ -224,7 +244,7 @@ class DialogueUnderstandingTestStep(BaseModel):
|
|
|
224
244
|
commands=commands,
|
|
225
245
|
)
|
|
226
246
|
|
|
227
|
-
def get_predicted_commands(self) -> List[
|
|
247
|
+
def get_predicted_commands(self) -> List[PromptCommand]:
|
|
228
248
|
"""Get all predicted commands from the test case."""
|
|
229
249
|
if self.dialogue_understanding_output is None:
|
|
230
250
|
return []
|
|
@@ -314,7 +334,7 @@ class DialogueUnderstandingTestCase(BaseModel):
|
|
|
314
334
|
input_test_case: Dict[str, Any],
|
|
315
335
|
flows: FlowsList,
|
|
316
336
|
file: Optional[str] = None,
|
|
317
|
-
custom_command_classes: List[
|
|
337
|
+
custom_command_classes: List[PromptCommand] = [],
|
|
318
338
|
remove_default_commands: List[str] = [],
|
|
319
339
|
) -> "DialogueUnderstandingTestCase":
|
|
320
340
|
"""Creates a DialogueUnderstandingTestCase from a dictionary.
|
|
@@ -361,7 +381,7 @@ class DialogueUnderstandingTestCase(BaseModel):
|
|
|
361
381
|
|
|
362
382
|
return [step.to_str() for step in steps]
|
|
363
383
|
|
|
364
|
-
def get_expected_commands(self) -> List[
|
|
384
|
+
def get_expected_commands(self) -> List[PromptCommand]:
|
|
365
385
|
"""Get all commands from the test steps."""
|
|
366
386
|
return [
|
|
367
387
|
command
|
|
@@ -5,16 +5,13 @@ from typing import Any, Dict, List, Optional, Text
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
|
|
8
|
-
from rasa.dialogue_understanding.commands import
|
|
8
|
+
from rasa.dialogue_understanding.commands.prompt_command import PromptCommand
|
|
9
9
|
from rasa.dialogue_understanding_test.du_test_case import (
|
|
10
10
|
DialogueUnderstandingTestCase,
|
|
11
11
|
DialogueUnderstandingTestStep,
|
|
12
12
|
)
|
|
13
13
|
from rasa.dialogue_understanding_test.utils import get_command_comparison
|
|
14
|
-
from rasa.shared.nlu.constants import
|
|
15
|
-
KEY_SYSTEM_PROMPT,
|
|
16
|
-
KEY_USER_PROMPT,
|
|
17
|
-
)
|
|
14
|
+
from rasa.shared.nlu.constants import KEY_SYSTEM_PROMPT, KEY_USER_PROMPT
|
|
18
15
|
|
|
19
16
|
if typing.TYPE_CHECKING:
|
|
20
17
|
from rasa.dialogue_understanding_test.command_metric_calculation import (
|
|
@@ -46,7 +43,7 @@ class DialogueUnderstandingTestResult(BaseModel):
|
|
|
46
43
|
passed: bool
|
|
47
44
|
error_line: Optional[int] = None
|
|
48
45
|
|
|
49
|
-
def get_expected_commands(self) -> List[
|
|
46
|
+
def get_expected_commands(self) -> List[PromptCommand]:
|
|
50
47
|
return self.test_case.get_expected_commands()
|
|
51
48
|
|
|
52
49
|
|
|
@@ -60,10 +57,17 @@ class FailedTestStep(BaseModel):
|
|
|
60
57
|
pass_status: bool
|
|
61
58
|
command_generators: List[str]
|
|
62
59
|
prompts: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
63
|
-
expected_commands: List[
|
|
64
|
-
predicted_commands: Dict[str, List[
|
|
60
|
+
expected_commands: List[PromptCommand]
|
|
61
|
+
predicted_commands: Dict[str, List[PromptCommand]]
|
|
65
62
|
conversation_with_diff: List[str]
|
|
66
63
|
|
|
64
|
+
class Config:
|
|
65
|
+
"""Skip validation for PromptCommand protocol as pydantic does not know how to
|
|
66
|
+
serialize or handle instances of a protocol.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
arbitrary_types_allowed = True
|
|
70
|
+
|
|
67
71
|
@classmethod
|
|
68
72
|
def from_dialogue_understanding_test_step(
|
|
69
73
|
cls,
|
|
@@ -74,7 +78,7 @@ class FailedTestStep(BaseModel):
|
|
|
74
78
|
user_utterance = step.text or ""
|
|
75
79
|
line_number = step.line or -1
|
|
76
80
|
|
|
77
|
-
predicted_commands: Dict[str, List[
|
|
81
|
+
predicted_commands: Dict[str, List[PromptCommand]] = {}
|
|
78
82
|
prompts: Optional[Dict[str, List[Dict[str, Any]]]] = None
|
|
79
83
|
command_generators: List[str] = []
|
|
80
84
|
|
|
@@ -8,6 +8,7 @@ import rasa.shared.data
|
|
|
8
8
|
from rasa.dialogue_understanding_test.command_metric_calculation import CommandMetrics
|
|
9
9
|
from rasa.dialogue_understanding_test.constants import SCHEMA_FILE_PATH
|
|
10
10
|
from rasa.dialogue_understanding_test.du_test_case import (
|
|
11
|
+
KEY_CHOICES,
|
|
11
12
|
KEY_COMPLETION_TOKENS,
|
|
12
13
|
KEY_PROMPT_TOKENS,
|
|
13
14
|
)
|
|
@@ -309,6 +310,7 @@ def print_failed_cases(
|
|
|
309
310
|
print_prompt(step)
|
|
310
311
|
rich.print("\n[red3]-- CONVERSATION --[/red3]")
|
|
311
312
|
rich.print("\n".join(step.conversation_with_diff))
|
|
313
|
+
print_llm_output(step)
|
|
312
314
|
|
|
313
315
|
|
|
314
316
|
def print_prompt(step: FailedTestStep) -> None:
|
|
@@ -341,6 +343,18 @@ def print_prompt(step: FailedTestStep) -> None:
|
|
|
341
343
|
)
|
|
342
344
|
|
|
343
345
|
|
|
346
|
+
def print_llm_output(step: FailedTestStep) -> None:
|
|
347
|
+
if not step.prompts:
|
|
348
|
+
return
|
|
349
|
+
|
|
350
|
+
for component, component_prompts in step.prompts.items():
|
|
351
|
+
for prompt_data in component_prompts:
|
|
352
|
+
if KEY_CHOICES in prompt_data:
|
|
353
|
+
rich.print("\n[red3]-- CHOICES --[/red3]")
|
|
354
|
+
rich.print(prompt_data.get(KEY_CHOICES))
|
|
355
|
+
rich.print("[red3]-------------[/red3]")
|
|
356
|
+
|
|
357
|
+
|
|
344
358
|
def print_command_summary(metrics: Dict[str, CommandMetrics]) -> None:
|
|
345
359
|
"""Print the command summary.
|
|
346
360
|
|
|
@@ -20,7 +20,7 @@ from rasa.dialogue_understanding_test.test_case_simulation.exception import (
|
|
|
20
20
|
)
|
|
21
21
|
from rasa.dialogue_understanding_test.utils import filter_metadata
|
|
22
22
|
from rasa.e2e_test.e2e_test_case import Fixture, Metadata
|
|
23
|
-
from rasa.shared.core.constants import
|
|
23
|
+
from rasa.shared.core.constants import SlotMappingType
|
|
24
24
|
from rasa.shared.core.events import BotUttered, SlotSet, UserUttered
|
|
25
25
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
26
26
|
from rasa.shared.nlu.constants import COMMANDS, ENTITIES, INTENT
|
|
@@ -328,8 +328,8 @@ class TestCaseTrackerSimulator:
|
|
|
328
328
|
command.extractor = SetSlotExtractor.COMMAND_PAYLOAD_READER.value
|
|
329
329
|
# Use the SetSlotExtractor.NLU extractor if the slot mapping type is
|
|
330
330
|
# not FROM_LLM.
|
|
331
|
-
elif SlotMappingType.FROM_LLM
|
|
332
|
-
mapping
|
|
331
|
+
elif SlotMappingType.FROM_LLM not in [
|
|
332
|
+
mapping.type for mapping in slot_definition.mappings
|
|
333
333
|
]:
|
|
334
334
|
command.extractor = SetSlotExtractor.NLU.value
|
|
335
335
|
|