rasa-pro 3.12.0.dev9__py3-none-any.whl → 3.12.0.dev10__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 +17 -3
- rasa/core/actions/action_handle_digressions.py +142 -0
- rasa/core/actions/forms.py +4 -2
- rasa/core/channels/voice_ready/audiocodes.py +42 -23
- rasa/core/channels/voice_stream/tts/azure.py +2 -1
- rasa/core/migrate.py +2 -2
- rasa/core/policies/flows/flow_executor.py +33 -1
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -2
- rasa/dialogue_understanding/commands/cancel_flow_command.py +62 -4
- rasa/dialogue_understanding/commands/change_flow_command.py +2 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/clarify_command.py +2 -2
- rasa/dialogue_understanding/commands/correct_slots_command.py +11 -2
- rasa/dialogue_understanding/commands/handle_digressions_command.py +150 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +2 -2
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -2
- rasa/dialogue_understanding/commands/set_slot_command.py +7 -15
- rasa/dialogue_understanding/commands/skip_question_command.py +2 -2
- rasa/dialogue_understanding/commands/start_flow_command.py +43 -2
- rasa/dialogue_understanding/commands/utils.py +1 -1
- rasa/dialogue_understanding/constants.py +1 -0
- rasa/dialogue_understanding/generator/command_generator.py +10 -76
- rasa/dialogue_understanding/generator/command_parser.py +1 -1
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +126 -2
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +10 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +4 -2
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +40 -40
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -19
- rasa/dialogue_understanding/patterns/correction.py +13 -1
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +62 -2
- rasa/dialogue_understanding/patterns/handle_digressions.py +81 -0
- rasa/dialogue_understanding/processor/command_processor.py +117 -28
- rasa/dialogue_understanding/utils.py +31 -0
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +2 -2
- rasa/shared/core/constants.py +22 -1
- rasa/shared/core/domain.py +6 -4
- rasa/shared/core/events.py +13 -2
- rasa/shared/core/flows/flow.py +17 -0
- rasa/shared/core/flows/flows_yaml_schema.json +38 -0
- rasa/shared/core/flows/steps/collect.py +18 -1
- rasa/shared/core/flows/utils.py +16 -1
- rasa/shared/core/slot_mappings.py +6 -6
- rasa/shared/core/slots.py +19 -0
- rasa/shared/core/trackers.py +3 -1
- rasa/shared/nlu/constants.py +1 -0
- rasa/shared/providers/llm/_base_litellm_client.py +0 -40
- rasa/shared/utils/llm.py +1 -86
- rasa/shared/utils/schemas/domain.yml +0 -1
- rasa/validator.py +172 -22
- rasa/version.py +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev10.dist-info}/METADATA +1 -1
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev10.dist-info}/RECORD +56 -53
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev10.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev10.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev9.dist-info → rasa_pro-3.12.0.dev10.dist-info}/entry_points.txt +0 -0
|
@@ -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 KEY_MAPPING_TYPE, 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
|
|
@@ -329,7 +329,7 @@ class TestCaseTrackerSimulator:
|
|
|
329
329
|
# Use the SetSlotExtractor.NLU extractor if the slot mapping type is
|
|
330
330
|
# not FROM_LLM.
|
|
331
331
|
elif SlotMappingType.FROM_LLM.value not in [
|
|
332
|
-
mapping[
|
|
332
|
+
mapping[KEY_MAPPING_TYPE] for mapping in slot_definition.mappings
|
|
333
333
|
]:
|
|
334
334
|
command.extractor = SetSlotExtractor.NLU.value
|
|
335
335
|
|
rasa/shared/core/constants.py
CHANGED
|
@@ -51,6 +51,8 @@ ACTION_TRIGGER_CHITCHAT = "action_trigger_chitchat"
|
|
|
51
51
|
ACTION_RESET_ROUTING = "action_reset_routing"
|
|
52
52
|
ACTION_HANGUP = "action_hangup"
|
|
53
53
|
ACTION_REPEAT_BOT_MESSAGES = "action_repeat_bot_messages"
|
|
54
|
+
ACTION_BLOCK_DIGRESSION = "action_block_digression"
|
|
55
|
+
ACTION_CONTINUE_DIGRESSION = "action_continue_digression"
|
|
54
56
|
|
|
55
57
|
ACTION_METADATA_EXECUTION_SUCCESS = "execution_success"
|
|
56
58
|
ACTION_METADATA_EXECUTION_ERROR_MESSAGE = "execution_error_message"
|
|
@@ -81,6 +83,8 @@ DEFAULT_ACTION_NAMES = [
|
|
|
81
83
|
ACTION_RESET_ROUTING,
|
|
82
84
|
ACTION_HANGUP,
|
|
83
85
|
ACTION_REPEAT_BOT_MESSAGES,
|
|
86
|
+
ACTION_BLOCK_DIGRESSION,
|
|
87
|
+
ACTION_CONTINUE_DIGRESSION,
|
|
84
88
|
]
|
|
85
89
|
|
|
86
90
|
ACTION_SHOULD_SEND_DOMAIN = "send_domain"
|
|
@@ -137,7 +141,8 @@ DEFAULT_SLOT_NAMES = {
|
|
|
137
141
|
|
|
138
142
|
SLOT_MAPPINGS = "mappings"
|
|
139
143
|
MAPPING_CONDITIONS = "conditions"
|
|
140
|
-
|
|
144
|
+
KEY_MAPPING_TYPE = "type"
|
|
145
|
+
KEY_ALLOW_NLU_CORRECTION = "allow_nlu_correction"
|
|
141
146
|
|
|
142
147
|
|
|
143
148
|
class SlotMappingType(Enum):
|
|
@@ -159,6 +164,18 @@ class SlotMappingType(Enum):
|
|
|
159
164
|
return not (self == SlotMappingType.CUSTOM or self == SlotMappingType.FROM_LLM)
|
|
160
165
|
|
|
161
166
|
|
|
167
|
+
class SetSlotExtractor(Enum):
|
|
168
|
+
"""The extractors that can set a slot."""
|
|
169
|
+
|
|
170
|
+
LLM = "LLM"
|
|
171
|
+
COMMAND_PAYLOAD_READER = "CommandPayloadReader"
|
|
172
|
+
NLU = "NLU"
|
|
173
|
+
CUSTOM = "CUSTOM"
|
|
174
|
+
|
|
175
|
+
def __str__(self) -> str:
|
|
176
|
+
return self.value
|
|
177
|
+
|
|
178
|
+
|
|
162
179
|
# the keys for `State` (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
|
|
163
180
|
# represent the origin of a `SubState`
|
|
164
181
|
USER = "user"
|
|
@@ -181,3 +198,7 @@ POLICY_NAME_RULE = "RulePolicy"
|
|
|
181
198
|
CLASSIFIER_NAME_FALLBACK = "FallbackClassifier"
|
|
182
199
|
|
|
183
200
|
POLICIES_THAT_EXTRACT_ENTITIES = {"TEDPolicy"}
|
|
201
|
+
|
|
202
|
+
# digression constants
|
|
203
|
+
KEY_ASK_CONFIRM_DIGRESSIONS = "ask_confirm_digressions"
|
|
204
|
+
KEY_BLOCK_DIGRESSIONS = "block_digressions"
|
rasa/shared/core/domain.py
CHANGED
|
@@ -47,9 +47,9 @@ from rasa.shared.constants import (
|
|
|
47
47
|
from rasa.shared.core.constants import (
|
|
48
48
|
ACTION_SHOULD_SEND_DOMAIN,
|
|
49
49
|
ACTIVE_LOOP,
|
|
50
|
+
KEY_MAPPING_TYPE,
|
|
50
51
|
KNOWLEDGE_BASE_SLOT_NAMES,
|
|
51
52
|
MAPPING_CONDITIONS,
|
|
52
|
-
MAPPING_TYPE,
|
|
53
53
|
SLOT_MAPPINGS,
|
|
54
54
|
SlotMappingType,
|
|
55
55
|
)
|
|
@@ -596,7 +596,7 @@ class Domain:
|
|
|
596
596
|
),
|
|
597
597
|
)
|
|
598
598
|
slot_dict[slot_name][SLOT_MAPPINGS] = [
|
|
599
|
-
{
|
|
599
|
+
{KEY_MAPPING_TYPE: SlotMappingType.FROM_LLM.value}
|
|
600
600
|
]
|
|
601
601
|
|
|
602
602
|
slot = slot_class(slot_name, **slot_dict[slot_name])
|
|
@@ -1570,7 +1570,9 @@ class Domain:
|
|
|
1570
1570
|
|
|
1571
1571
|
for mapping in slot.mappings:
|
|
1572
1572
|
mapping_conditions = mapping.get(MAPPING_CONDITIONS)
|
|
1573
|
-
if mapping[
|
|
1573
|
+
if mapping[KEY_MAPPING_TYPE] != str(
|
|
1574
|
+
SlotMappingType.FROM_ENTITY
|
|
1575
|
+
) or (
|
|
1574
1576
|
mapping_conditions
|
|
1575
1577
|
and mapping_conditions[0].get(ACTIVE_LOOP) is not None
|
|
1576
1578
|
):
|
|
@@ -2021,7 +2023,7 @@ class Domain:
|
|
|
2021
2023
|
for slot in self.slots:
|
|
2022
2024
|
total_mappings += len(slot.mappings)
|
|
2023
2025
|
for mapping in slot.mappings:
|
|
2024
|
-
if mapping[
|
|
2026
|
+
if mapping[KEY_MAPPING_TYPE] == str(SlotMappingType.CUSTOM):
|
|
2025
2027
|
custom_mappings += 1
|
|
2026
2028
|
|
|
2027
2029
|
if MAPPING_CONDITIONS in mapping:
|
rasa/shared/core/events.py
CHANGED
|
@@ -1032,6 +1032,7 @@ class SlotSet(Event):
|
|
|
1032
1032
|
value: Optional[Any] = None,
|
|
1033
1033
|
timestamp: Optional[float] = None,
|
|
1034
1034
|
metadata: Optional[Dict[Text, Any]] = None,
|
|
1035
|
+
filled_by: Optional[str] = None,
|
|
1035
1036
|
) -> None:
|
|
1036
1037
|
"""Creates event to set slot.
|
|
1037
1038
|
|
|
@@ -1043,6 +1044,7 @@ class SlotSet(Event):
|
|
|
1043
1044
|
"""
|
|
1044
1045
|
self.key = key
|
|
1045
1046
|
self.value = value
|
|
1047
|
+
self._filled_by = filled_by
|
|
1046
1048
|
super().__init__(timestamp, metadata)
|
|
1047
1049
|
|
|
1048
1050
|
def __repr__(self) -> Text:
|
|
@@ -1060,6 +1062,14 @@ class SlotSet(Event):
|
|
|
1060
1062
|
|
|
1061
1063
|
return (self.key, self.value) == (other.key, other.value)
|
|
1062
1064
|
|
|
1065
|
+
@property
|
|
1066
|
+
def filled_by(self) -> Optional[str]:
|
|
1067
|
+
return self._filled_by
|
|
1068
|
+
|
|
1069
|
+
@filled_by.setter
|
|
1070
|
+
def filled_by(self, value: str) -> None:
|
|
1071
|
+
self._filled_by = value
|
|
1072
|
+
|
|
1063
1073
|
def as_story_string(self) -> Text:
|
|
1064
1074
|
"""Returns text representation of event."""
|
|
1065
1075
|
props = json.dumps({self.key: self.value}, ensure_ascii=False)
|
|
@@ -1081,7 +1091,7 @@ class SlotSet(Event):
|
|
|
1081
1091
|
def as_dict(self) -> Dict[Text, Any]:
|
|
1082
1092
|
"""Returns serialized event."""
|
|
1083
1093
|
d = super().as_dict()
|
|
1084
|
-
d.update({"name": self.key, "value": self.value})
|
|
1094
|
+
d.update({"name": self.key, "value": self.value, "filled_by": self.filled_by})
|
|
1085
1095
|
return d
|
|
1086
1096
|
|
|
1087
1097
|
@classmethod
|
|
@@ -1092,13 +1102,14 @@ class SlotSet(Event):
|
|
|
1092
1102
|
parameters.get("value"),
|
|
1093
1103
|
parameters.get("timestamp"),
|
|
1094
1104
|
parameters.get("metadata"),
|
|
1105
|
+
filled_by=parameters.get("filled_by"),
|
|
1095
1106
|
)
|
|
1096
1107
|
except KeyError as e:
|
|
1097
1108
|
raise ValueError(f"Failed to parse set slot event. {e}")
|
|
1098
1109
|
|
|
1099
1110
|
def apply_to(self, tracker: "DialogueStateTracker") -> None:
|
|
1100
1111
|
"""Applies event to current conversation state."""
|
|
1101
|
-
tracker._set_slot(self.key, self.value)
|
|
1112
|
+
tracker._set_slot(self.key, self.value, self.filled_by)
|
|
1102
1113
|
|
|
1103
1114
|
|
|
1104
1115
|
class Restarted(AlwaysEqualEventMixin):
|
rasa/shared/core/flows/flow.py
CHANGED
|
@@ -11,6 +11,10 @@ from pypred import Predicate
|
|
|
11
11
|
|
|
12
12
|
import rasa.shared.utils.io
|
|
13
13
|
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
14
|
+
from rasa.shared.core.constants import (
|
|
15
|
+
KEY_ASK_CONFIRM_DIGRESSIONS,
|
|
16
|
+
KEY_BLOCK_DIGRESSIONS,
|
|
17
|
+
)
|
|
14
18
|
from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
|
|
15
19
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
16
20
|
from rasa.shared.core.flows.flow_step_links import (
|
|
@@ -33,6 +37,7 @@ from rasa.shared.core.flows.steps.constants import (
|
|
|
33
37
|
START_STEP,
|
|
34
38
|
)
|
|
35
39
|
from rasa.shared.core.flows.steps.continuation import ContinueFlowStep
|
|
40
|
+
from rasa.shared.core.flows.utils import extract_digression_prop
|
|
36
41
|
from rasa.shared.core.slots import Slot
|
|
37
42
|
|
|
38
43
|
structlogger = structlog.get_logger()
|
|
@@ -62,6 +67,10 @@ class Flow:
|
|
|
62
67
|
"""The path to the file where the flow is stored."""
|
|
63
68
|
persisted_slots: List[str] = field(default_factory=list)
|
|
64
69
|
"""The list of slots that should be persisted after the flow ends."""
|
|
70
|
+
ask_confirm_digressions: List[str] = field(default_factory=list)
|
|
71
|
+
"""The flow ids for which the assistant should ask for confirmation."""
|
|
72
|
+
block_digressions: List[str] = field(default_factory=list)
|
|
73
|
+
"""The flow ids that the assistant should block from digressing to."""
|
|
65
74
|
|
|
66
75
|
@staticmethod
|
|
67
76
|
def from_json(
|
|
@@ -98,6 +107,10 @@ class Flow:
|
|
|
98
107
|
# data. When the model is trained, take the provided file_path.
|
|
99
108
|
file_path=data.get("file_path") if "file_path" in data else file_path,
|
|
100
109
|
persisted_slots=data.get("persisted_slots", []),
|
|
110
|
+
ask_confirm_digressions=extract_digression_prop(
|
|
111
|
+
KEY_ASK_CONFIRM_DIGRESSIONS, data
|
|
112
|
+
),
|
|
113
|
+
block_digressions=extract_digression_prop(KEY_BLOCK_DIGRESSIONS, data),
|
|
101
114
|
)
|
|
102
115
|
|
|
103
116
|
def get_full_name(self) -> str:
|
|
@@ -172,6 +185,10 @@ class Flow:
|
|
|
172
185
|
data["file_path"] = self.file_path
|
|
173
186
|
if self.persisted_slots:
|
|
174
187
|
data["persisted_slots"] = self.persisted_slots
|
|
188
|
+
if self.ask_confirm_digressions:
|
|
189
|
+
data[KEY_ASK_CONFIRM_DIGRESSIONS] = self.ask_confirm_digressions
|
|
190
|
+
if self.block_digressions:
|
|
191
|
+
data[KEY_BLOCK_DIGRESSIONS] = self.block_digressions
|
|
175
192
|
|
|
176
193
|
return data
|
|
177
194
|
|
|
@@ -217,6 +217,12 @@
|
|
|
217
217
|
"reset_after_flow_ends": {
|
|
218
218
|
"type": "boolean"
|
|
219
219
|
},
|
|
220
|
+
"ask_confirm_digressions": {
|
|
221
|
+
"$ref": "#/$defs/ask_confirm_digressions"
|
|
222
|
+
},
|
|
223
|
+
"block_digressions": {
|
|
224
|
+
"$ref": "#/$defs/block_digressions"
|
|
225
|
+
},
|
|
220
226
|
"utter": {
|
|
221
227
|
"type": "string"
|
|
222
228
|
},
|
|
@@ -247,6 +253,32 @@
|
|
|
247
253
|
}
|
|
248
254
|
}
|
|
249
255
|
},
|
|
256
|
+
"ask_confirm_digressions": {
|
|
257
|
+
"oneOf": [
|
|
258
|
+
{
|
|
259
|
+
"type": "boolean"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "array",
|
|
263
|
+
"items": {
|
|
264
|
+
"type": "string"
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
]
|
|
268
|
+
},
|
|
269
|
+
"block_digressions": {
|
|
270
|
+
"oneOf": [
|
|
271
|
+
{
|
|
272
|
+
"type": "boolean"
|
|
273
|
+
},
|
|
274
|
+
{
|
|
275
|
+
"type": "array",
|
|
276
|
+
"items": {
|
|
277
|
+
"type": "string"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
]
|
|
281
|
+
},
|
|
250
282
|
"flow": {
|
|
251
283
|
"required": [
|
|
252
284
|
"steps",
|
|
@@ -282,6 +314,12 @@
|
|
|
282
314
|
},
|
|
283
315
|
"persisted_slots": {
|
|
284
316
|
"$ref": "#/$defs/persisted_slots"
|
|
317
|
+
},
|
|
318
|
+
"ask_confirm_digressions": {
|
|
319
|
+
"$ref": "#/$defs/ask_confirm_digressions"
|
|
320
|
+
},
|
|
321
|
+
"block_digressions": {
|
|
322
|
+
"$ref": "#/$defs/block_digressions"
|
|
285
323
|
}
|
|
286
324
|
}
|
|
287
325
|
},
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from dataclasses import dataclass
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
4
|
from typing import Any, Dict, List, Set, Text
|
|
5
5
|
|
|
6
6
|
from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
|
|
7
|
+
from rasa.shared.core.constants import (
|
|
8
|
+
KEY_ASK_CONFIRM_DIGRESSIONS,
|
|
9
|
+
KEY_BLOCK_DIGRESSIONS,
|
|
10
|
+
)
|
|
7
11
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
12
|
+
from rasa.shared.core.flows.utils import extract_digression_prop
|
|
8
13
|
|
|
9
14
|
|
|
10
15
|
@dataclass
|
|
@@ -59,6 +64,10 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
59
64
|
"""Whether to always ask the question even if the slot is already filled."""
|
|
60
65
|
reset_after_flow_ends: bool = True
|
|
61
66
|
"""Whether to reset the slot value at the end of the flow."""
|
|
67
|
+
ask_confirm_digressions: List[str] = field(default_factory=list)
|
|
68
|
+
"""The flow id digressions for which the assistant should ask for confirmation."""
|
|
69
|
+
block_digressions: List[str] = field(default_factory=list)
|
|
70
|
+
"""The flow id digressions that should be blocked during the flow step."""
|
|
62
71
|
|
|
63
72
|
@classmethod
|
|
64
73
|
def from_json(
|
|
@@ -86,6 +95,10 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
86
95
|
SlotRejection.from_dict(rejection)
|
|
87
96
|
for rejection in data.get("rejections", [])
|
|
88
97
|
],
|
|
98
|
+
ask_confirm_digressions=extract_digression_prop(
|
|
99
|
+
KEY_ASK_CONFIRM_DIGRESSIONS, data
|
|
100
|
+
),
|
|
101
|
+
block_digressions=extract_digression_prop(KEY_BLOCK_DIGRESSIONS, data),
|
|
89
102
|
**base.__dict__,
|
|
90
103
|
)
|
|
91
104
|
|
|
@@ -101,6 +114,10 @@ class CollectInformationFlowStep(FlowStep):
|
|
|
101
114
|
data["ask_before_filling"] = self.ask_before_filling
|
|
102
115
|
data["reset_after_flow_ends"] = self.reset_after_flow_ends
|
|
103
116
|
data["rejections"] = [rejection.as_dict() for rejection in self.rejections]
|
|
117
|
+
data["ask_confirm_digressions"] = self.ask_confirm_digressions
|
|
118
|
+
data["block_digressions"] = (
|
|
119
|
+
self.block_digressions if self.block_digressions else False
|
|
120
|
+
)
|
|
104
121
|
|
|
105
122
|
return data
|
|
106
123
|
|
rasa/shared/core/flows/utils.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
from typing import Set
|
|
1
|
+
from typing import Any, Dict, List, Set
|
|
2
2
|
|
|
3
3
|
from rasa.shared.utils.io import raise_deprecation_warning
|
|
4
4
|
|
|
5
5
|
RESET_PROPERTY_NAME = "reset_after_flow_ends"
|
|
6
6
|
PERSIST_PROPERTY_NAME = "persisted_slots"
|
|
7
|
+
ALL_LABEL = "ALL"
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def warn_deprecated_collect_step_config(flow_id: str, collect_step: str) -> None:
|
|
@@ -38,3 +39,17 @@ def get_invalid_slot_persistence_config_error_message(
|
|
|
38
39
|
f"are neither used in a collect step nor a set_slot step of the flow. "
|
|
39
40
|
f"Please remove such slots from the '{PERSIST_PROPERTY_NAME}' property."
|
|
40
41
|
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def extract_digression_prop(prop: str, data: Dict[str, Any]) -> List[str]:
|
|
45
|
+
"""Extracts the digression property from the data.
|
|
46
|
+
|
|
47
|
+
There can be two types of properties: ask_confirm_digressions and
|
|
48
|
+
block_digressions.
|
|
49
|
+
"""
|
|
50
|
+
digression_property = data.get(prop, [])
|
|
51
|
+
|
|
52
|
+
if isinstance(digression_property, bool):
|
|
53
|
+
digression_property = [ALL_LABEL] if digression_property else []
|
|
54
|
+
|
|
55
|
+
return digression_property
|
|
@@ -6,8 +6,8 @@ from rasa.shared.constants import DOCS_URL_NLU_BASED_SLOTS, IGNORED_INTENTS
|
|
|
6
6
|
from rasa.shared.core.constants import (
|
|
7
7
|
ACTIVE_FLOW,
|
|
8
8
|
ACTIVE_LOOP,
|
|
9
|
+
KEY_MAPPING_TYPE,
|
|
9
10
|
MAPPING_CONDITIONS,
|
|
10
|
-
MAPPING_TYPE,
|
|
11
11
|
REQUESTED_SLOT,
|
|
12
12
|
SLOT_MAPPINGS,
|
|
13
13
|
SlotMappingType,
|
|
@@ -59,11 +59,11 @@ class SlotMapping:
|
|
|
59
59
|
)
|
|
60
60
|
|
|
61
61
|
try:
|
|
62
|
-
mapping_type = SlotMappingType(mapping.get(
|
|
62
|
+
mapping_type = SlotMappingType(mapping.get(KEY_MAPPING_TYPE))
|
|
63
63
|
except ValueError:
|
|
64
64
|
raise InvalidDomain(
|
|
65
65
|
f"Your domain uses an invalid slot mapping of type "
|
|
66
|
-
f"'{mapping.get(
|
|
66
|
+
f"'{mapping.get(KEY_MAPPING_TYPE)}' for slot '{slot_name}'. Please see "
|
|
67
67
|
f"{DOCS_URL_NLU_BASED_SLOTS} for more information."
|
|
68
68
|
)
|
|
69
69
|
|
|
@@ -299,7 +299,7 @@ class SlotFillingManager:
|
|
|
299
299
|
def _verify_mapping_conditions(
|
|
300
300
|
self, mapping: Dict[Text, Any], slot_name: Text
|
|
301
301
|
) -> bool:
|
|
302
|
-
if mapping.get(MAPPING_CONDITIONS) and mapping[
|
|
302
|
+
if mapping.get(MAPPING_CONDITIONS) and mapping[KEY_MAPPING_TYPE] != str(
|
|
303
303
|
SlotMappingType.FROM_TRIGGER_INTENT
|
|
304
304
|
):
|
|
305
305
|
if not self._matches_mapping_conditions(mapping, slot_name):
|
|
@@ -374,7 +374,7 @@ class SlotFillingManager:
|
|
|
374
374
|
) -> bool:
|
|
375
375
|
from rasa.core.actions.forms import FormAction
|
|
376
376
|
|
|
377
|
-
if mapping[
|
|
377
|
+
if mapping[KEY_MAPPING_TYPE] != str(SlotMappingType.FROM_ENTITY):
|
|
378
378
|
return False
|
|
379
379
|
|
|
380
380
|
form_name = self.tracker.active_loop_name
|
|
@@ -495,7 +495,7 @@ def extract_slot_value(
|
|
|
495
495
|
|
|
496
496
|
for mapping in slot.mappings:
|
|
497
497
|
mapping_type = SlotMappingType(
|
|
498
|
-
mapping.get(
|
|
498
|
+
mapping.get(KEY_MAPPING_TYPE, SlotMappingType.FROM_LLM.value)
|
|
499
499
|
)
|
|
500
500
|
|
|
501
501
|
if mapping_type in [SlotMappingType.FROM_LLM, SlotMappingType.CUSTOM]:
|
rasa/shared/core/slots.py
CHANGED
|
@@ -41,6 +41,7 @@ class Slot(ABC):
|
|
|
41
41
|
influence_conversation: bool = True,
|
|
42
42
|
is_builtin: bool = False,
|
|
43
43
|
shared_for_coexistence: bool = False,
|
|
44
|
+
filled_by: Optional[str] = None,
|
|
44
45
|
) -> None:
|
|
45
46
|
"""Create a Slot.
|
|
46
47
|
|
|
@@ -57,6 +58,7 @@ class Slot(ABC):
|
|
|
57
58
|
such as `return_value`.
|
|
58
59
|
shared_for_coexistence: If `True` the slot is not forgotten after either
|
|
59
60
|
dm1 or CALM finishes.
|
|
61
|
+
filled_by: The name of the extractor that fills the slot.
|
|
60
62
|
"""
|
|
61
63
|
self.name = name
|
|
62
64
|
self.mappings = mappings
|
|
@@ -67,6 +69,7 @@ class Slot(ABC):
|
|
|
67
69
|
self._has_been_set = False
|
|
68
70
|
self.is_builtin = is_builtin
|
|
69
71
|
self.shared_for_coexistence = shared_for_coexistence
|
|
72
|
+
self._filled_by = filled_by
|
|
70
73
|
|
|
71
74
|
def feature_dimensionality(self) -> int:
|
|
72
75
|
"""How many features this single slot creates.
|
|
@@ -132,6 +135,16 @@ class Slot(ABC):
|
|
|
132
135
|
self._value = value
|
|
133
136
|
self._has_been_set = True
|
|
134
137
|
|
|
138
|
+
@property
|
|
139
|
+
def filled_by(self) -> Optional[str]:
|
|
140
|
+
"""Gets the slot's latest value extractor."""
|
|
141
|
+
return self._filled_by
|
|
142
|
+
|
|
143
|
+
@filled_by.setter
|
|
144
|
+
def filled_by(self, extractor: str) -> None:
|
|
145
|
+
"""Sets the slot's latest value extractor."""
|
|
146
|
+
self._filled_by = extractor
|
|
147
|
+
|
|
135
148
|
def has_same_coerced_value(self, other_value: Any) -> bool:
|
|
136
149
|
"""Checks if the coerced value of is the same as the slot value.
|
|
137
150
|
|
|
@@ -215,6 +228,7 @@ class FloatSlot(Slot):
|
|
|
215
228
|
influence_conversation: bool = True,
|
|
216
229
|
is_builtin: bool = False,
|
|
217
230
|
shared_for_coexistence: bool = False,
|
|
231
|
+
filled_by: Optional[str] = None,
|
|
218
232
|
) -> None:
|
|
219
233
|
"""Creates a FloatSlot.
|
|
220
234
|
|
|
@@ -230,6 +244,7 @@ class FloatSlot(Slot):
|
|
|
230
244
|
influence_conversation,
|
|
231
245
|
is_builtin,
|
|
232
246
|
shared_for_coexistence,
|
|
247
|
+
filled_by=filled_by,
|
|
233
248
|
)
|
|
234
249
|
self.max_value = max_value
|
|
235
250
|
self.min_value = min_value
|
|
@@ -387,6 +402,7 @@ class CategoricalSlot(Slot):
|
|
|
387
402
|
influence_conversation: bool = True,
|
|
388
403
|
is_builtin: bool = False,
|
|
389
404
|
shared_for_coexistence: bool = False,
|
|
405
|
+
filled_by: Optional[str] = None,
|
|
390
406
|
) -> None:
|
|
391
407
|
"""Creates a `Categorical Slot` (see parent class for detailed docstring)."""
|
|
392
408
|
super().__init__(
|
|
@@ -397,6 +413,7 @@ class CategoricalSlot(Slot):
|
|
|
397
413
|
influence_conversation,
|
|
398
414
|
is_builtin,
|
|
399
415
|
shared_for_coexistence,
|
|
416
|
+
filled_by=filled_by,
|
|
400
417
|
)
|
|
401
418
|
if values and None in values:
|
|
402
419
|
rasa.shared.utils.io.raise_warning(
|
|
@@ -607,6 +624,7 @@ class AnySlot(Slot):
|
|
|
607
624
|
influence_conversation: bool = False,
|
|
608
625
|
is_builtin: bool = False,
|
|
609
626
|
shared_for_coexistence: bool = False,
|
|
627
|
+
filled_by: Optional[str] = None,
|
|
610
628
|
) -> None:
|
|
611
629
|
"""Creates an `Any Slot` (see parent class for detailed docstring).
|
|
612
630
|
|
|
@@ -630,6 +648,7 @@ class AnySlot(Slot):
|
|
|
630
648
|
influence_conversation,
|
|
631
649
|
is_builtin,
|
|
632
650
|
shared_for_coexistence,
|
|
651
|
+
filled_by=filled_by,
|
|
633
652
|
)
|
|
634
653
|
|
|
635
654
|
def __eq__(self, other: Any) -> bool:
|
rasa/shared/core/trackers.py
CHANGED
|
@@ -916,11 +916,13 @@ class DialogueStateTracker:
|
|
|
916
916
|
continue
|
|
917
917
|
slot.reset()
|
|
918
918
|
|
|
919
|
-
def _set_slot(self, key: Text, value: Any) -> None:
|
|
919
|
+
def _set_slot(self, key: Text, value: Any, filled_by: Optional[str] = None) -> None:
|
|
920
920
|
"""Sets the value of a slot if that slot exists."""
|
|
921
921
|
if key in self.slots:
|
|
922
922
|
slot = self.slots[key]
|
|
923
923
|
slot.value = value
|
|
924
|
+
if filled_by is not None:
|
|
925
|
+
slot.filled_by = filled_by
|
|
924
926
|
else:
|
|
925
927
|
logger.error(
|
|
926
928
|
f"Tried to set non existent slot '{key}'. Make sure you "
|
rasa/shared/nlu/constants.py
CHANGED
|
@@ -181,46 +181,6 @@ class _BaseLiteLLMClient:
|
|
|
181
181
|
)
|
|
182
182
|
raise ProviderClientAPIException(e, message)
|
|
183
183
|
|
|
184
|
-
@suppress_logs(log_level=logging.WARNING)
|
|
185
|
-
async def acompletion_with_system(
|
|
186
|
-
self, formatted_messages: Union[List[str], str]
|
|
187
|
-
) -> LLMResponse:
|
|
188
|
-
"""Asynchronously generate completions for given list of messages.
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
messages: List of messages or a single message to generate the
|
|
192
|
-
completion for.
|
|
193
|
-
|
|
194
|
-
Returns:
|
|
195
|
-
List of message completions.
|
|
196
|
-
|
|
197
|
-
Raises:
|
|
198
|
-
ProviderClientAPIException: If the API request fails.
|
|
199
|
-
"""
|
|
200
|
-
try:
|
|
201
|
-
# formatted_messages = self._format_messages(messages)
|
|
202
|
-
arguments = resolve_environment_variables(self._completion_fn_args)
|
|
203
|
-
response = await acompletion(messages=formatted_messages, **arguments)
|
|
204
|
-
return self._format_response(response)
|
|
205
|
-
except Exception as e:
|
|
206
|
-
message = ""
|
|
207
|
-
from rasa.shared.providers.llm.self_hosted_llm_client import (
|
|
208
|
-
SelfHostedLLMClient,
|
|
209
|
-
)
|
|
210
|
-
|
|
211
|
-
if isinstance(self, SelfHostedLLMClient):
|
|
212
|
-
message = (
|
|
213
|
-
"If you are using 'provider=self-hosted' to call a hosted vllm "
|
|
214
|
-
"server make sure your config is correctly setup. You should have "
|
|
215
|
-
"the following mandatory keys in your config: "
|
|
216
|
-
"provider=self-hosted; "
|
|
217
|
-
"model='<your-vllm-model-name>'; "
|
|
218
|
-
"api_base='your-hosted-vllm-serv'."
|
|
219
|
-
"In case you are getting OpenAI connection errors, such as missing "
|
|
220
|
-
"API key, your configuration is incorrect."
|
|
221
|
-
)
|
|
222
|
-
raise ProviderClientAPIException(e, message)
|
|
223
|
-
|
|
224
184
|
def _format_messages(self, messages: Union[List[str], str]) -> List[Dict[str, str]]:
|
|
225
185
|
"""Formats messages (or a single message) to OpenAI format."""
|
|
226
186
|
if isinstance(messages, str):
|
rasa/shared/utils/llm.py
CHANGED
|
@@ -6,7 +6,6 @@ from typing import (
|
|
|
6
6
|
Any,
|
|
7
7
|
Callable,
|
|
8
8
|
Dict,
|
|
9
|
-
List,
|
|
10
9
|
Optional,
|
|
11
10
|
Text,
|
|
12
11
|
Type,
|
|
@@ -238,90 +237,6 @@ def tracker_as_readable_transcript(
|
|
|
238
237
|
return "\n".join(transcript)
|
|
239
238
|
|
|
240
239
|
|
|
241
|
-
def sanitize_command_for_prompt(cmd_dict):
|
|
242
|
-
command = ""
|
|
243
|
-
if cmd_dict["command"] == "start flow":
|
|
244
|
-
command = f"start {cmd_dict['flow']}"
|
|
245
|
-
elif cmd_dict["command"] == "set slot":
|
|
246
|
-
command = f"set {cmd_dict['name']} {cmd_dict['value']}"
|
|
247
|
-
elif cmd_dict["command"] == "skip question":
|
|
248
|
-
command = "skip"
|
|
249
|
-
elif cmd_dict["command"] == "clarify":
|
|
250
|
-
command = f"clarify {' '.join(cmd_dict['options'])}"
|
|
251
|
-
elif cmd_dict["command"] == "knowledge":
|
|
252
|
-
command = "search"
|
|
253
|
-
elif cmd_dict["command"] == "chitchat":
|
|
254
|
-
command = "chat"
|
|
255
|
-
elif cmd_dict["command"] == "cancel flow":
|
|
256
|
-
command = "cancel"
|
|
257
|
-
else:
|
|
258
|
-
command = cmd_dict["command"]
|
|
259
|
-
|
|
260
|
-
return command
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
def tracker_as_message_list(
|
|
264
|
-
tracker: "DialogueStateTracker",
|
|
265
|
-
max_turns: Optional[int] = 20,
|
|
266
|
-
) -> List[str]:
|
|
267
|
-
"""Creates a readable dialogue from a tracker.
|
|
268
|
-
|
|
269
|
-
Args:
|
|
270
|
-
tracker: the tracker to convert
|
|
271
|
-
max_turns: the maximum number of turns to include in the transcript
|
|
272
|
-
|
|
273
|
-
Example:
|
|
274
|
-
>>> tracker = Tracker(
|
|
275
|
-
... sender_id="test",
|
|
276
|
-
... slots=[],
|
|
277
|
-
... events=[
|
|
278
|
-
... UserUttered("hello"),
|
|
279
|
-
... BotUttered("hi"),
|
|
280
|
-
... ],
|
|
281
|
-
... )
|
|
282
|
-
>>> tracker_as_readable_transcript(tracker)
|
|
283
|
-
USER: hello
|
|
284
|
-
AI: hi
|
|
285
|
-
|
|
286
|
-
Returns:
|
|
287
|
-
A string representing the transcript of the tracker
|
|
288
|
-
"""
|
|
289
|
-
messages = []
|
|
290
|
-
|
|
291
|
-
# using `applied_events` rather than `events` means that only events after the
|
|
292
|
-
# most recent `Restart` or `SessionStarted` are included in the transcript
|
|
293
|
-
# last_commands = None
|
|
294
|
-
for event in tracker.applied_events():
|
|
295
|
-
if isinstance(event, UserUttered):
|
|
296
|
-
if event.has_triggered_error:
|
|
297
|
-
first_error = event.error_commands[0]
|
|
298
|
-
error_type = first_error.get("error_type")
|
|
299
|
-
message = ERROR_PLACEHOLDER.get(
|
|
300
|
-
error_type, ERROR_PLACEHOLDER["default"]
|
|
301
|
-
)
|
|
302
|
-
else:
|
|
303
|
-
message = sanitize_message_for_prompt(event.text)
|
|
304
|
-
# last_commands = event.commands
|
|
305
|
-
messages.append({"role": "user", "content": message})
|
|
306
|
-
# messages.append({"role": "system", "content": ' \n '.join([sanitize_command_for_prompt(cmd) for cmd in last_commands])}) # noqa: E501
|
|
307
|
-
# transcript.append(f"{human_prefix}: {message}")
|
|
308
|
-
|
|
309
|
-
elif isinstance(event, BotUttered):
|
|
310
|
-
messages.append(
|
|
311
|
-
{
|
|
312
|
-
"role": "assistant",
|
|
313
|
-
"content": f"{sanitize_message_for_prompt(event.text)}",
|
|
314
|
-
}
|
|
315
|
-
)
|
|
316
|
-
# transcript.append(f"{ai_prefix}: {sanitize_message_for_prompt(event.text)}") # noqa: E501
|
|
317
|
-
|
|
318
|
-
if max_turns:
|
|
319
|
-
messages = messages[-max_turns:]
|
|
320
|
-
# transcript = transcript[-max_turns:]
|
|
321
|
-
|
|
322
|
-
return messages
|
|
323
|
-
|
|
324
|
-
|
|
325
240
|
def sanitize_message_for_prompt(text: Optional[str]) -> str:
|
|
326
241
|
"""Removes new lines from a string.
|
|
327
242
|
|
|
@@ -765,7 +680,7 @@ def allowed_values_for_slot(slot: Slot) -> Union[str, None]:
|
|
|
765
680
|
if isinstance(slot, BooleanSlot):
|
|
766
681
|
return str([True, False])
|
|
767
682
|
if isinstance(slot, CategoricalSlot):
|
|
768
|
-
return str([v for v in slot.values if v != "__other__"]
|
|
683
|
+
return str([v for v in slot.values if v != "__other__"])
|
|
769
684
|
else:
|
|
770
685
|
return None
|
|
771
686
|
|