rasa-pro 3.12.0.dev13__py3-none-any.whl → 3.12.0rc2__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.
- README.md +10 -13
- rasa/anonymization/anonymization_rule_executor.py +16 -10
- rasa/cli/data.py +16 -0
- rasa/cli/project_templates/calm/config.yml +2 -2
- rasa/cli/project_templates/calm/domain/list_contacts.yml +1 -2
- rasa/cli/project_templates/calm/domain/remove_contact.yml +1 -2
- rasa/cli/project_templates/calm/domain/shared.yml +1 -4
- rasa/cli/project_templates/calm/endpoints.yml +2 -2
- rasa/cli/utils.py +12 -0
- rasa/core/actions/action.py +84 -191
- rasa/core/actions/action_handle_digressions.py +35 -13
- rasa/core/actions/action_run_slot_rejections.py +16 -4
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/studio_chat.py +19 -0
- rasa/core/channels/telegram.py +42 -24
- 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 +4 -1
- rasa/core/channels/voice_stream/call_state.py +3 -0
- rasa/core/channels/voice_stream/genesys.py +6 -2
- rasa/core/channels/voice_stream/tts/azure.py +9 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +14 -8
- rasa/core/channels/voice_stream/voice_channel.py +23 -2
- rasa/core/constants.py +2 -0
- 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 +19 -7
- rasa/core/processor.py +71 -9
- rasa/dialogue_understanding/commands/can_not_handle_command.py +20 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +24 -6
- 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/handle_digressions_command.py +1 -7
- 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 +24 -2
- rasa/dialogue_understanding/commands/skip_question_command.py +20 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +22 -2
- rasa/dialogue_understanding/commands/utils.py +71 -4
- rasa/dialogue_understanding/generator/__init__.py +2 -0
- rasa/dialogue_understanding/generator/command_parser.py +15 -12
- rasa/dialogue_understanding/generator/constants.py +3 -0
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -5
- rasa/dialogue_understanding/generator/llm_command_generator.py +5 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +17 -3
- rasa/dialogue_understanding/generator/prompt_templates/__init__.py +0 -0
- rasa/dialogue_understanding/generator/{single_step → prompt_templates}/command_prompt_template.jinja2 +2 -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/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +84 -0
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +522 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +12 -310
- rasa/dialogue_understanding/patterns/collect_information.py +1 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +16 -0
- rasa/dialogue_understanding/patterns/validate_slot.py +65 -0
- rasa/dialogue_understanding/processor/command_processor.py +39 -0
- rasa/dialogue_understanding/stack/utils.py +38 -0
- rasa/dialogue_understanding_test/du_test_case.py +58 -18
- rasa/dialogue_understanding_test/du_test_result.py +14 -10
- rasa/dialogue_understanding_test/io.py +14 -0
- rasa/e2e_test/assertions.py +6 -8
- rasa/e2e_test/llm_judge_prompts/answer_relevance_prompt_template.jinja2 +5 -1
- rasa/e2e_test/llm_judge_prompts/groundedness_prompt_template.jinja2 +4 -0
- 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/llm_fine_tuning/conversations.py +1 -1
- rasa/model_manager/runner_service.py +7 -4
- rasa/model_manager/socket_bridge.py +7 -6
- rasa/shared/constants.py +15 -13
- rasa/shared/core/constants.py +2 -0
- rasa/shared/core/flows/constants.py +11 -0
- rasa/shared/core/flows/flow.py +83 -19
- rasa/shared/core/flows/flows_yaml_schema.json +31 -3
- rasa/shared/core/flows/steps/collect.py +1 -36
- rasa/shared/core/flows/utils.py +28 -4
- rasa/shared/core/flows/validation.py +1 -1
- rasa/shared/core/slot_mappings.py +208 -5
- rasa/shared/core/slots.py +137 -1
- rasa/shared/core/trackers.py +74 -1
- rasa/shared/importers/importer.py +50 -2
- 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 +33 -7
- rasa/shared/utils/pykwalify_extensions.py +24 -0
- rasa/shared/utils/schemas/domain.yml +26 -0
- rasa/telemetry.py +2 -1
- 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/validator.py +372 -7
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc2.dist-info}/METADATA +13 -14
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc2.dist-info}/RECORD +139 -124
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc2.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc2.dist-info}/entry_points.txt +0 -0
|
@@ -1,23 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import copy
|
|
4
|
-
import
|
|
5
|
-
from typing import
|
|
4
|
+
from enum import Enum
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
Dict,
|
|
9
|
+
List,
|
|
10
|
+
Optional,
|
|
11
|
+
Set,
|
|
12
|
+
Text,
|
|
13
|
+
Tuple,
|
|
14
|
+
Union,
|
|
15
|
+
cast,
|
|
16
|
+
)
|
|
6
17
|
|
|
18
|
+
import structlog
|
|
7
19
|
from pydantic import BaseModel, Field
|
|
8
20
|
|
|
9
21
|
import rasa.shared.utils.io
|
|
10
22
|
from rasa.shared.constants import DOCS_URL_NLU_BASED_SLOTS, IGNORED_INTENTS
|
|
11
23
|
from rasa.shared.core.constants import (
|
|
24
|
+
ACTION_EXTRACT_SLOTS,
|
|
25
|
+
ACTION_VALIDATE_SLOT_MAPPINGS,
|
|
12
26
|
ACTIVE_LOOP,
|
|
13
27
|
KEY_ACTION,
|
|
28
|
+
KEY_COEXISTENCE_SYSTEM,
|
|
14
29
|
KEY_MAPPING_TYPE,
|
|
15
30
|
KEY_RUN_ACTION_EVERY_TURN,
|
|
16
31
|
MAPPING_CONDITIONS,
|
|
17
32
|
REQUESTED_SLOT,
|
|
18
33
|
SlotMappingType,
|
|
19
34
|
)
|
|
35
|
+
from rasa.shared.core.events import BotUttered, Event, SlotSet
|
|
20
36
|
from rasa.shared.core.slots import ListSlot, Slot
|
|
37
|
+
from rasa.shared.exceptions import RasaException
|
|
21
38
|
from rasa.shared.nlu.constants import (
|
|
22
39
|
ENTITIES,
|
|
23
40
|
ENTITY_ATTRIBUTE_GROUP,
|
|
@@ -30,13 +47,14 @@ from rasa.shared.nlu.constants import (
|
|
|
30
47
|
)
|
|
31
48
|
|
|
32
49
|
if TYPE_CHECKING:
|
|
50
|
+
from rasa.core.channels.channel import OutputChannel
|
|
51
|
+
from rasa.core.nlg import NaturalLanguageGenerator
|
|
33
52
|
from rasa.shared.core.domain import Domain
|
|
34
53
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
35
54
|
from rasa.shared.nlu.training_data.message import Message
|
|
36
55
|
from rasa.utils.endpoints import EndpointConfig
|
|
37
56
|
|
|
38
|
-
|
|
39
|
-
logger = logging.getLogger(__name__)
|
|
57
|
+
structlogger = structlog.get_logger()
|
|
40
58
|
|
|
41
59
|
|
|
42
60
|
class SlotMappingCondition(BaseModel):
|
|
@@ -58,6 +76,12 @@ class SlotMappingCondition(BaseModel):
|
|
|
58
76
|
return self.model_dump(exclude_none=True)
|
|
59
77
|
|
|
60
78
|
|
|
79
|
+
class CoexistenceSystemType(Enum):
|
|
80
|
+
NLU = "NLU"
|
|
81
|
+
CALM = "CALM"
|
|
82
|
+
SHARED = "SHARED"
|
|
83
|
+
|
|
84
|
+
|
|
61
85
|
class SlotMapping(BaseModel):
|
|
62
86
|
"""Defines functionality for the available slot mappings."""
|
|
63
87
|
|
|
@@ -71,6 +95,7 @@ class SlotMapping(BaseModel):
|
|
|
71
95
|
value: Optional[Any] = None
|
|
72
96
|
allow_nlu_correction: Optional[bool] = None
|
|
73
97
|
run_action_every_turn: Optional[str] = None
|
|
98
|
+
coexistence_system: Optional[CoexistenceSystemType] = None
|
|
74
99
|
|
|
75
100
|
@staticmethod
|
|
76
101
|
def from_dict(data: Dict[str, Any], slot_name: str) -> SlotMapping:
|
|
@@ -92,10 +117,16 @@ class SlotMapping(BaseModel):
|
|
|
92
117
|
|
|
93
118
|
run_action_every_turn = data_copy.pop(KEY_RUN_ACTION_EVERY_TURN, None)
|
|
94
119
|
|
|
120
|
+
coexistence_system = data_copy.pop(KEY_COEXISTENCE_SYSTEM, None)
|
|
121
|
+
coexistence_system_type = (
|
|
122
|
+
CoexistenceSystemType(coexistence_system) if coexistence_system else None
|
|
123
|
+
)
|
|
124
|
+
|
|
95
125
|
return SlotMapping(
|
|
96
126
|
type=mapping_type,
|
|
97
127
|
conditions=conditions,
|
|
98
128
|
run_action_every_turn=run_action_every_turn,
|
|
129
|
+
coexistence_system=coexistence_system_type,
|
|
99
130
|
**data_copy,
|
|
100
131
|
)
|
|
101
132
|
|
|
@@ -324,6 +355,7 @@ class SlotFillingManager:
|
|
|
324
355
|
self.tracker = tracker
|
|
325
356
|
self.message = message
|
|
326
357
|
self._action_endpoint = action_endpoint
|
|
358
|
+
self.executed_custom_actions: Set[str] = set()
|
|
327
359
|
|
|
328
360
|
def is_slot_mapping_valid(
|
|
329
361
|
self,
|
|
@@ -524,6 +556,173 @@ class SlotFillingManager:
|
|
|
524
556
|
|
|
525
557
|
return True
|
|
526
558
|
|
|
559
|
+
@staticmethod
|
|
560
|
+
def should_fill_slot_in_coexistence(
|
|
561
|
+
is_calm_system: bool,
|
|
562
|
+
slot: Slot,
|
|
563
|
+
calm_slot_names: Set[str],
|
|
564
|
+
) -> bool:
|
|
565
|
+
"""Check if a slot should be filled in a coexistence assistant."""
|
|
566
|
+
if slot.shared_for_coexistence:
|
|
567
|
+
return True
|
|
568
|
+
|
|
569
|
+
coexistence_systems = [
|
|
570
|
+
mapping.coexistence_system
|
|
571
|
+
for mapping in slot.mappings
|
|
572
|
+
if mapping.type == SlotMappingType.CONTROLLED
|
|
573
|
+
]
|
|
574
|
+
|
|
575
|
+
if not is_calm_system and (
|
|
576
|
+
slot.name in calm_slot_names
|
|
577
|
+
or CoexistenceSystemType.CALM in coexistence_systems
|
|
578
|
+
):
|
|
579
|
+
return False
|
|
580
|
+
|
|
581
|
+
if is_calm_system and (
|
|
582
|
+
slot.name not in calm_slot_names
|
|
583
|
+
or CoexistenceSystemType.NLU in coexistence_systems
|
|
584
|
+
):
|
|
585
|
+
return False
|
|
586
|
+
|
|
587
|
+
return True
|
|
588
|
+
|
|
589
|
+
async def run_action_at_every_turn(
|
|
590
|
+
self,
|
|
591
|
+
slot: Slot,
|
|
592
|
+
output_channel: "OutputChannel",
|
|
593
|
+
nlg: "NaturalLanguageGenerator",
|
|
594
|
+
) -> List[Event]:
|
|
595
|
+
"""Runs a custom action at every turn to fill a slot.
|
|
596
|
+
|
|
597
|
+
This executes only if the slot has a controlled mapping type
|
|
598
|
+
with the `run_action_every_turn` key set.
|
|
599
|
+
"""
|
|
600
|
+
slot_events: List[Event] = []
|
|
601
|
+
for mapping in slot.mappings:
|
|
602
|
+
should_fill_controlled_slot = mapping.type == SlotMappingType.CONTROLLED
|
|
603
|
+
|
|
604
|
+
if not should_fill_controlled_slot:
|
|
605
|
+
continue
|
|
606
|
+
|
|
607
|
+
custom_events = await self._execute_custom_action(
|
|
608
|
+
mapping,
|
|
609
|
+
output_channel,
|
|
610
|
+
nlg,
|
|
611
|
+
)
|
|
612
|
+
slot_events.extend(custom_events)
|
|
613
|
+
|
|
614
|
+
return slot_events
|
|
615
|
+
|
|
616
|
+
async def _execute_custom_action(
|
|
617
|
+
self,
|
|
618
|
+
mapping: "SlotMapping",
|
|
619
|
+
output_channel: "OutputChannel",
|
|
620
|
+
nlg: "NaturalLanguageGenerator",
|
|
621
|
+
) -> List[Event]:
|
|
622
|
+
custom_action = mapping.run_action_every_turn
|
|
623
|
+
|
|
624
|
+
if not custom_action or custom_action in self.executed_custom_actions:
|
|
625
|
+
return []
|
|
626
|
+
|
|
627
|
+
slot_events = await self._run_custom_action(custom_action, output_channel, nlg)
|
|
628
|
+
|
|
629
|
+
self.executed_custom_actions.add(custom_action)
|
|
630
|
+
|
|
631
|
+
return slot_events
|
|
632
|
+
|
|
633
|
+
async def _run_custom_action(
|
|
634
|
+
self,
|
|
635
|
+
custom_action: str,
|
|
636
|
+
output_channel: "OutputChannel",
|
|
637
|
+
nlg: "NaturalLanguageGenerator",
|
|
638
|
+
recreate_tracker: bool = False,
|
|
639
|
+
) -> List[Event]:
|
|
640
|
+
from rasa.core.actions.action import RemoteAction
|
|
641
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
642
|
+
from rasa.utils.endpoints import ClientResponseError
|
|
643
|
+
|
|
644
|
+
slot_events: List[Event] = []
|
|
645
|
+
remote_action = RemoteAction(custom_action, self._action_endpoint)
|
|
646
|
+
disallowed_types = set()
|
|
647
|
+
|
|
648
|
+
tracker = (
|
|
649
|
+
DialogueStateTracker.from_events(
|
|
650
|
+
self.tracker.sender_id,
|
|
651
|
+
self.tracker.events_after_latest_restart() + slot_events,
|
|
652
|
+
slots=self.domain.slots,
|
|
653
|
+
)
|
|
654
|
+
if recreate_tracker
|
|
655
|
+
else self.tracker
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
try:
|
|
659
|
+
custom_events = await remote_action.run(
|
|
660
|
+
output_channel, nlg, tracker, self.domain
|
|
661
|
+
)
|
|
662
|
+
for event in custom_events:
|
|
663
|
+
if isinstance(event, SlotSet):
|
|
664
|
+
slot_events.append(event)
|
|
665
|
+
elif isinstance(event, BotUttered):
|
|
666
|
+
slot_events.append(event)
|
|
667
|
+
else:
|
|
668
|
+
disallowed_types.add(event.type_name)
|
|
669
|
+
except (RasaException, ClientResponseError) as e:
|
|
670
|
+
structlogger.warning(
|
|
671
|
+
"slot_filling_manager.run_custom_action_failed",
|
|
672
|
+
failed_custom_action=custom_action,
|
|
673
|
+
event_info=f"Failed to execute custom action '{custom_action}' "
|
|
674
|
+
f"as a result of error '{e!s}'. The default action "
|
|
675
|
+
f"'{ACTION_EXTRACT_SLOTS}' failed to fill slots with custom "
|
|
676
|
+
f"mappings.",
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
for type_name in disallowed_types:
|
|
680
|
+
structlogger.info(
|
|
681
|
+
"slot_filling_manager.run_custom_action_disallowed_event",
|
|
682
|
+
custom_action_name=custom_action,
|
|
683
|
+
disallowed_evet_type=type_name,
|
|
684
|
+
event_info=f"Running custom action '{custom_action}' has resulted "
|
|
685
|
+
f"in an event of type '{type_name}'. This is "
|
|
686
|
+
f"disallowed and the tracker will not be "
|
|
687
|
+
f"updated with this event.",
|
|
688
|
+
)
|
|
689
|
+
|
|
690
|
+
return slot_events
|
|
691
|
+
|
|
692
|
+
async def execute_validation_action(
|
|
693
|
+
self,
|
|
694
|
+
extraction_events: List[Event],
|
|
695
|
+
output_channel: "OutputChannel",
|
|
696
|
+
nlg: "NaturalLanguageGenerator",
|
|
697
|
+
) -> List[Event]:
|
|
698
|
+
slot_events: List[SlotSet] = [
|
|
699
|
+
event for event in extraction_events if isinstance(event, SlotSet)
|
|
700
|
+
]
|
|
701
|
+
|
|
702
|
+
slot_candidates = "\n".join([e.key for e in slot_events])
|
|
703
|
+
structlogger.debug(
|
|
704
|
+
"slot_filling_manager.execute_validation_action",
|
|
705
|
+
slot_candidates=slot_candidates,
|
|
706
|
+
event_info=f"Validating extracted slots: {slot_candidates}",
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
if ACTION_VALIDATE_SLOT_MAPPINGS not in self.domain.user_actions:
|
|
710
|
+
return cast(List[Event], slot_events)
|
|
711
|
+
|
|
712
|
+
validate_events = await self._run_custom_action(
|
|
713
|
+
ACTION_VALIDATE_SLOT_MAPPINGS, output_channel, nlg, recreate_tracker=True
|
|
714
|
+
)
|
|
715
|
+
validated_slot_names = [
|
|
716
|
+
event.key for event in validate_events if isinstance(event, SlotSet)
|
|
717
|
+
]
|
|
718
|
+
|
|
719
|
+
# If the custom action doesn't return a SlotSet event for an extracted slot
|
|
720
|
+
# candidate we assume that it was valid. The custom action has to return a
|
|
721
|
+
# SlotSet(slot_name, None) event to mark a Slot as invalid.
|
|
722
|
+
return validate_events + [
|
|
723
|
+
event for event in slot_events if event.key not in validated_slot_names
|
|
724
|
+
]
|
|
725
|
+
|
|
527
726
|
|
|
528
727
|
def extract_slot_value(
|
|
529
728
|
slot: Slot, slot_filling_manager: SlotFillingManager
|
|
@@ -554,7 +753,11 @@ def extract_slot_value(
|
|
|
554
753
|
value is not None
|
|
555
754
|
or slot_filling_manager.tracker.get_slot(slot.name) is not None
|
|
556
755
|
):
|
|
557
|
-
|
|
756
|
+
structlogger.debug(
|
|
757
|
+
"slot_filling_manager.extract_slot_value",
|
|
758
|
+
slot_name=slot.name,
|
|
759
|
+
event_info=f"Extracted value '{value}' for slot '{slot.name}'.",
|
|
760
|
+
)
|
|
558
761
|
|
|
559
762
|
is_extracted = True
|
|
560
763
|
return value, is_extracted
|
rasa/shared/core/slots.py
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import logging
|
|
2
4
|
from abc import ABC, abstractmethod
|
|
5
|
+
from dataclasses import dataclass
|
|
3
6
|
from typing import Any, Dict, List, Optional, Text, Type
|
|
4
7
|
|
|
8
|
+
from pydantic import BaseModel, Field
|
|
9
|
+
|
|
5
10
|
import rasa.shared.core.constants
|
|
6
11
|
import rasa.shared.utils.common
|
|
7
12
|
import rasa.shared.utils.io
|
|
@@ -9,6 +14,9 @@ from rasa.shared.constants import (
|
|
|
9
14
|
DOCS_URL_CATEGORICAL_SLOTS,
|
|
10
15
|
DOCS_URL_NLU_BASED_SLOTS,
|
|
11
16
|
DOCS_URL_SLOTS,
|
|
17
|
+
REFILL_UTTER,
|
|
18
|
+
REJECTIONS,
|
|
19
|
+
UTTER_ASK_PREFIX,
|
|
12
20
|
)
|
|
13
21
|
from rasa.shared.exceptions import RasaException
|
|
14
22
|
|
|
@@ -23,6 +31,80 @@ class InvalidSlotConfigError(RasaException, ValueError):
|
|
|
23
31
|
"""Raised if a slot's config is invalid."""
|
|
24
32
|
|
|
25
33
|
|
|
34
|
+
@dataclass
|
|
35
|
+
class SlotRejection:
|
|
36
|
+
"""A pair of validation condition and an utterance for the case of failure."""
|
|
37
|
+
|
|
38
|
+
if_: str
|
|
39
|
+
"""The condition that should be checked."""
|
|
40
|
+
utter: str
|
|
41
|
+
"""The utterance that should be executed if the condition is met."""
|
|
42
|
+
|
|
43
|
+
@staticmethod
|
|
44
|
+
def from_dict(data: Dict[str, Any]) -> SlotRejection:
|
|
45
|
+
"""Create a SlotRejection object from serialized data.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
data: data for a SlotRejection object in a serialized format
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
A SlotRejection object
|
|
52
|
+
"""
|
|
53
|
+
return SlotRejection(
|
|
54
|
+
if_=data["if"],
|
|
55
|
+
utter=data["utter"],
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
59
|
+
"""Serialize the SlotRejection object.
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
the SlotRejection object as serialized data
|
|
63
|
+
"""
|
|
64
|
+
return {
|
|
65
|
+
"if": self.if_,
|
|
66
|
+
"utter": self.utter,
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SlotValidation(BaseModel):
|
|
71
|
+
rejections: List[SlotRejection] = Field(alias=REJECTIONS)
|
|
72
|
+
"""how the slot value is validated using predicate evaluation."""
|
|
73
|
+
refill_utter: str = Field(alias=REFILL_UTTER)
|
|
74
|
+
"""The utterance that the assistant uses to ask for the slot."""
|
|
75
|
+
|
|
76
|
+
@staticmethod
|
|
77
|
+
def from_dict(data: Dict[str, Any]) -> SlotValidation:
|
|
78
|
+
"""Creates a SlotValidation object from serialised data.
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
data: data for a SlotValidation object in a serialized format
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
A SlotValidation object
|
|
85
|
+
"""
|
|
86
|
+
rejections = data.get(REJECTIONS)
|
|
87
|
+
if rejections is not None:
|
|
88
|
+
rejections = [
|
|
89
|
+
SlotRejection.from_dict(rejection) for rejection in rejections
|
|
90
|
+
]
|
|
91
|
+
|
|
92
|
+
return SlotValidation(
|
|
93
|
+
rejections=rejections, refill_utter=data.get(REFILL_UTTER)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
97
|
+
"""Serialize the SlotValidation object.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
the SlotValidation object as serialized data
|
|
101
|
+
"""
|
|
102
|
+
return {
|
|
103
|
+
REJECTIONS: [rejection.as_dict() for rejection in self.rejections],
|
|
104
|
+
REFILL_UTTER: self.refill_utter,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
|
|
26
108
|
class Slot(ABC):
|
|
27
109
|
"""Key-value store for storing information during a conversation."""
|
|
28
110
|
|
|
@@ -42,6 +124,7 @@ class Slot(ABC):
|
|
|
42
124
|
is_builtin: bool = False,
|
|
43
125
|
shared_for_coexistence: bool = False,
|
|
44
126
|
filled_by: Optional[str] = None,
|
|
127
|
+
validation: Optional[Dict[str, Any]] = None,
|
|
45
128
|
) -> None:
|
|
46
129
|
"""Create a Slot.
|
|
47
130
|
|
|
@@ -59,6 +142,8 @@ class Slot(ABC):
|
|
|
59
142
|
shared_for_coexistence: If `True` the slot is not forgotten after either
|
|
60
143
|
dm1 or CALM finishes.
|
|
61
144
|
filled_by: The name of the extractor that fills the slot.
|
|
145
|
+
validation: The validation rules that should be used to validate
|
|
146
|
+
slot values.
|
|
62
147
|
"""
|
|
63
148
|
from rasa.shared.core.slot_mappings import SlotMapping
|
|
64
149
|
|
|
@@ -73,6 +158,12 @@ class Slot(ABC):
|
|
|
73
158
|
self.shared_for_coexistence = shared_for_coexistence
|
|
74
159
|
self._filled_by = filled_by
|
|
75
160
|
|
|
161
|
+
if validation:
|
|
162
|
+
validation.setdefault(REFILL_UTTER, f"{UTTER_ASK_PREFIX}{self.name}")
|
|
163
|
+
self.validation = (
|
|
164
|
+
SlotValidation.from_dict(validation) if validation else validation
|
|
165
|
+
)
|
|
166
|
+
|
|
76
167
|
def feature_dimensionality(self) -> int:
|
|
77
168
|
"""How many features this single slot creates.
|
|
78
169
|
|
|
@@ -191,12 +282,15 @@ class Slot(ABC):
|
|
|
191
282
|
|
|
192
283
|
def persistence_info(self) -> Dict[str, Any]:
|
|
193
284
|
"""Returns relevant information to persist this slot."""
|
|
194
|
-
|
|
285
|
+
persistence_info_dict = {
|
|
195
286
|
"type": rasa.shared.utils.common.module_path_from_instance(self),
|
|
196
287
|
"initial_value": self.initial_value,
|
|
197
288
|
"influence_conversation": self.influence_conversation,
|
|
198
289
|
"mappings": [mapping.as_dict() for mapping in self.mappings],
|
|
199
290
|
}
|
|
291
|
+
if self.validation:
|
|
292
|
+
persistence_info_dict["validation"] = self.validation.as_dict() # type: ignore
|
|
293
|
+
return persistence_info_dict
|
|
200
294
|
|
|
201
295
|
def fingerprint(self) -> Text:
|
|
202
296
|
"""Returns a unique hash for the slot which is stable across python runs.
|
|
@@ -213,6 +307,10 @@ class Slot(ABC):
|
|
|
213
307
|
return False
|
|
214
308
|
return self.name == other.name and self.value == other.value
|
|
215
309
|
|
|
310
|
+
def requires_validation(self) -> bool:
|
|
311
|
+
"""Indicates if the slot requires validation."""
|
|
312
|
+
return True if self.validation else False
|
|
313
|
+
|
|
216
314
|
|
|
217
315
|
class FloatSlot(Slot):
|
|
218
316
|
"""A slot storing a float value."""
|
|
@@ -231,6 +329,7 @@ class FloatSlot(Slot):
|
|
|
231
329
|
is_builtin: bool = False,
|
|
232
330
|
shared_for_coexistence: bool = False,
|
|
233
331
|
filled_by: Optional[str] = None,
|
|
332
|
+
validation: Optional[Dict[str, Any]] = None,
|
|
234
333
|
) -> None:
|
|
235
334
|
"""Creates a FloatSlot.
|
|
236
335
|
|
|
@@ -247,6 +346,7 @@ class FloatSlot(Slot):
|
|
|
247
346
|
is_builtin,
|
|
248
347
|
shared_for_coexistence,
|
|
249
348
|
filled_by=filled_by,
|
|
349
|
+
validation=validation,
|
|
250
350
|
)
|
|
251
351
|
self.max_value = max_value
|
|
252
352
|
self.min_value = min_value
|
|
@@ -405,6 +505,7 @@ class CategoricalSlot(Slot):
|
|
|
405
505
|
is_builtin: bool = False,
|
|
406
506
|
shared_for_coexistence: bool = False,
|
|
407
507
|
filled_by: Optional[str] = None,
|
|
508
|
+
validation: Optional[Dict[str, Any]] = None,
|
|
408
509
|
) -> None:
|
|
409
510
|
"""Creates a `Categorical Slot` (see parent class for detailed docstring)."""
|
|
410
511
|
super().__init__(
|
|
@@ -416,6 +517,7 @@ class CategoricalSlot(Slot):
|
|
|
416
517
|
is_builtin,
|
|
417
518
|
shared_for_coexistence,
|
|
418
519
|
filled_by=filled_by,
|
|
520
|
+
validation=validation,
|
|
419
521
|
)
|
|
420
522
|
if values and None in values:
|
|
421
523
|
rasa.shared.utils.io.raise_warning(
|
|
@@ -627,6 +729,7 @@ class AnySlot(Slot):
|
|
|
627
729
|
is_builtin: bool = False,
|
|
628
730
|
shared_for_coexistence: bool = False,
|
|
629
731
|
filled_by: Optional[str] = None,
|
|
732
|
+
validation: Optional[Dict[str, Any]] = None,
|
|
630
733
|
) -> None:
|
|
631
734
|
"""Creates an `Any Slot` (see parent class for detailed docstring).
|
|
632
735
|
|
|
@@ -651,6 +754,7 @@ class AnySlot(Slot):
|
|
|
651
754
|
is_builtin,
|
|
652
755
|
shared_for_coexistence,
|
|
653
756
|
filled_by=filled_by,
|
|
757
|
+
validation=validation,
|
|
654
758
|
)
|
|
655
759
|
|
|
656
760
|
def __eq__(self, other: Any) -> bool:
|
|
@@ -673,3 +777,35 @@ class AnySlot(Slot):
|
|
|
673
777
|
f"implement a custom slot type by subclassing '{Slot.__name__}'. "
|
|
674
778
|
f"See the documentation for more information: {DOCS_URL_NLU_BASED_SLOTS}"
|
|
675
779
|
)
|
|
780
|
+
|
|
781
|
+
|
|
782
|
+
class StrictCategoricalSlot(CategoricalSlot):
|
|
783
|
+
"""A categorical slot that strictly enforces allowed values."""
|
|
784
|
+
|
|
785
|
+
type_name = "strict_categorical"
|
|
786
|
+
|
|
787
|
+
def coerce_value(self, value: Any) -> Any:
|
|
788
|
+
"""Coerce the value to one of the allowed ones or raise an error if invalid."""
|
|
789
|
+
if value is None:
|
|
790
|
+
return value
|
|
791
|
+
|
|
792
|
+
for allowed_value in self.values:
|
|
793
|
+
# Allowed values are always stored as strings, so we can use casefold().
|
|
794
|
+
if value.casefold() == allowed_value.casefold():
|
|
795
|
+
return allowed_value
|
|
796
|
+
|
|
797
|
+
raise InvalidSlotConfigError(
|
|
798
|
+
f"Value '{value}' is not allowed for the slot '{self.name}'. "
|
|
799
|
+
f"Allowed values are: {self.values}"
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
@Slot.value.setter # type: ignore[attr-defined,misc]
|
|
803
|
+
def value(self, value: Any) -> None:
|
|
804
|
+
"""Set the slot's value using strict coercion."""
|
|
805
|
+
coerced_value = self.coerce_value(value)
|
|
806
|
+
super(StrictCategoricalSlot, self.__class__).value.fset(self, coerced_value)
|
|
807
|
+
|
|
808
|
+
def add_default_value(self) -> None:
|
|
809
|
+
# StrictCategoricalSlot enforces validation against a specified set of values,
|
|
810
|
+
# so default values should not be automatically added.
|
|
811
|
+
pass
|
rasa/shared/core/trackers.py
CHANGED
|
@@ -6,6 +6,7 @@ import os
|
|
|
6
6
|
import time
|
|
7
7
|
from collections import deque
|
|
8
8
|
from enum import Enum
|
|
9
|
+
from functools import cached_property
|
|
9
10
|
from typing import (
|
|
10
11
|
TYPE_CHECKING,
|
|
11
12
|
Any,
|
|
@@ -26,6 +27,7 @@ from typing import (
|
|
|
26
27
|
)
|
|
27
28
|
|
|
28
29
|
import rasa.shared.utils.io
|
|
30
|
+
from rasa.engine.language import Language
|
|
29
31
|
from rasa.shared.constants import (
|
|
30
32
|
ASSISTANT_ID_KEY,
|
|
31
33
|
DEFAULT_SENDER_ID,
|
|
@@ -37,6 +39,7 @@ from rasa.shared.core.constants import (
|
|
|
37
39
|
ACTION_SESSION_START_NAME,
|
|
38
40
|
ACTIVE_LOOP,
|
|
39
41
|
FOLLOWUP_ACTION,
|
|
42
|
+
LANGUAGE_SLOT,
|
|
40
43
|
LOOP_NAME,
|
|
41
44
|
PREVIOUS_ACTION,
|
|
42
45
|
SHOULD_NOT_BE_SET,
|
|
@@ -61,7 +64,8 @@ from rasa.shared.core.events import (
|
|
|
61
64
|
UserUttered,
|
|
62
65
|
)
|
|
63
66
|
from rasa.shared.core.flows import FlowsList
|
|
64
|
-
from rasa.shared.core.slots import AnySlot, Slot
|
|
67
|
+
from rasa.shared.core.slots import AnySlot, Slot, StrictCategoricalSlot
|
|
68
|
+
from rasa.shared.exceptions import RasaException
|
|
65
69
|
from rasa.shared.nlu.constants import (
|
|
66
70
|
ACTION_NAME,
|
|
67
71
|
ACTION_TEXT,
|
|
@@ -1097,6 +1101,75 @@ class DialogueStateTracker:
|
|
|
1097
1101
|
|
|
1098
1102
|
return FlowsList(active_flows)
|
|
1099
1103
|
|
|
1104
|
+
@cached_property
|
|
1105
|
+
def supported_languages(self) -> List[Language]:
|
|
1106
|
+
"""Returns the supported languages for this model configuration
|
|
1107
|
+
|
|
1108
|
+
Returns:
|
|
1109
|
+
A list of supported languages.
|
|
1110
|
+
"""
|
|
1111
|
+
if LANGUAGE_SLOT not in self.slots:
|
|
1112
|
+
raise RasaException(
|
|
1113
|
+
f"The required slot '{LANGUAGE_SLOT}' is missing from the tracker. "
|
|
1114
|
+
f"Please ensure that a slot named '{LANGUAGE_SLOT}' exists."
|
|
1115
|
+
)
|
|
1116
|
+
|
|
1117
|
+
language_slot = self.slots[LANGUAGE_SLOT]
|
|
1118
|
+
|
|
1119
|
+
if not isinstance(language_slot, StrictCategoricalSlot):
|
|
1120
|
+
raise RasaException(
|
|
1121
|
+
f"The slot '{LANGUAGE_SLOT}' must be of type "
|
|
1122
|
+
f"'{StrictCategoricalSlot.type_name}'. "
|
|
1123
|
+
f"Please update the slot configuration accordingly."
|
|
1124
|
+
)
|
|
1125
|
+
|
|
1126
|
+
return [
|
|
1127
|
+
Language.from_language_code(language_code)
|
|
1128
|
+
for language_code in language_slot.values
|
|
1129
|
+
]
|
|
1130
|
+
|
|
1131
|
+
@property
|
|
1132
|
+
def current_language(self) -> Optional[Language]:
|
|
1133
|
+
"""Get the language of the current conversation.
|
|
1134
|
+
|
|
1135
|
+
Returns:
|
|
1136
|
+
The language of the current conversation or `None` if not set.
|
|
1137
|
+
"""
|
|
1138
|
+
language_code = self.get_slot("language")
|
|
1139
|
+
if not language_code:
|
|
1140
|
+
return None
|
|
1141
|
+
|
|
1142
|
+
supported_languages = self.supported_languages or []
|
|
1143
|
+
matching_language = (
|
|
1144
|
+
language
|
|
1145
|
+
for language in supported_languages
|
|
1146
|
+
if language.code == language_code
|
|
1147
|
+
)
|
|
1148
|
+
return next(matching_language, None)
|
|
1149
|
+
|
|
1150
|
+
@property
|
|
1151
|
+
def default_language(self) -> Language:
|
|
1152
|
+
"""Get the assistant's default language.
|
|
1153
|
+
|
|
1154
|
+
Returns:
|
|
1155
|
+
The assistant's default language.
|
|
1156
|
+
|
|
1157
|
+
Raises:
|
|
1158
|
+
RasaException: If no default language is defined in the config.
|
|
1159
|
+
"""
|
|
1160
|
+
supported_languages = self.supported_languages or []
|
|
1161
|
+
matching_language = (
|
|
1162
|
+
language for language in supported_languages if language.is_default is True
|
|
1163
|
+
)
|
|
1164
|
+
try:
|
|
1165
|
+
return next(matching_language)
|
|
1166
|
+
except StopIteration:
|
|
1167
|
+
raise RasaException(
|
|
1168
|
+
"No default language configured. "
|
|
1169
|
+
"Please configure the `language` parameter in config.yml file. "
|
|
1170
|
+
"Example: `language: en`."
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1100
1173
|
|
|
1101
1174
|
class TrackerEventDiffEngine:
|
|
1102
1175
|
"""Computes event difference of two trackers."""
|