rasa-pro 3.12.0.dev13__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/project_templates/calm/config.yml +2 -2
- 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_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 +12 -5
- rasa/core/processor.py +72 -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/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 +20 -2
- rasa/dialogue_understanding/commands/utils.py +98 -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 +16 -2
- 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 +460 -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_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/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/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 +131 -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 +30 -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.0rc1.dist-info}/METADATA +2 -1
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc1.dist-info}/RECORD +128 -113
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0.dev13.dist-info → rasa_pro-3.12.0rc1.dist-info}/entry_points.txt +0 -0
rasa/shared/core/flows/flow.py
CHANGED
|
@@ -7,14 +7,29 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Any, Dict, List, Optional, Set, Text, Union
|
|
8
8
|
|
|
9
9
|
import structlog
|
|
10
|
+
from pydantic import BaseModel
|
|
10
11
|
from pypred import Predicate
|
|
11
12
|
|
|
12
13
|
import rasa.shared.utils.io
|
|
14
|
+
from rasa.engine.language import Language
|
|
13
15
|
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
14
16
|
from rasa.shared.core.constants import (
|
|
15
17
|
KEY_ASK_CONFIRM_DIGRESSIONS,
|
|
16
18
|
KEY_BLOCK_DIGRESSIONS,
|
|
17
19
|
)
|
|
20
|
+
from rasa.shared.core.flows.constants import (
|
|
21
|
+
KEY_ALWAYS_INCLUDE_IN_PROMPT,
|
|
22
|
+
KEY_DESCRIPTION,
|
|
23
|
+
KEY_FILE_PATH,
|
|
24
|
+
KEY_ID,
|
|
25
|
+
KEY_IF,
|
|
26
|
+
KEY_NAME,
|
|
27
|
+
KEY_NLU_TRIGGER,
|
|
28
|
+
KEY_PERSISTED_SLOTS,
|
|
29
|
+
KEY_RUN_PATTERN_COMPLETED,
|
|
30
|
+
KEY_STEPS,
|
|
31
|
+
KEY_TRANSLATION,
|
|
32
|
+
)
|
|
18
33
|
from rasa.shared.core.flows.flow_path import FlowPath, FlowPathsList, PathNode
|
|
19
34
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
20
35
|
from rasa.shared.core.flows.flow_step_links import (
|
|
@@ -43,6 +58,16 @@ from rasa.shared.core.slots import Slot
|
|
|
43
58
|
structlogger = structlog.get_logger()
|
|
44
59
|
|
|
45
60
|
|
|
61
|
+
class FlowLanguageTranslation(BaseModel):
|
|
62
|
+
"""Represents the translation of the flow properties in a specific language."""
|
|
63
|
+
|
|
64
|
+
name: str
|
|
65
|
+
"""The human-readable name of the flow."""
|
|
66
|
+
|
|
67
|
+
class Config:
|
|
68
|
+
extra = "ignore"
|
|
69
|
+
|
|
70
|
+
|
|
46
71
|
@dataclass
|
|
47
72
|
class Flow:
|
|
48
73
|
"""Represents the configuration of a flow."""
|
|
@@ -53,6 +78,8 @@ class Flow:
|
|
|
53
78
|
"""The human-readable name of the flow."""
|
|
54
79
|
description: Optional[Text] = None
|
|
55
80
|
"""The description of the flow."""
|
|
81
|
+
translation: Dict[Text, FlowLanguageTranslation] = field(default_factory=dict)
|
|
82
|
+
"""The translation of the flow properties in different languages."""
|
|
56
83
|
guard_condition: Optional[Text] = None
|
|
57
84
|
"""The condition that needs to be fulfilled for the flow to be startable."""
|
|
58
85
|
step_sequence: FlowStepSequence = field(default_factory=FlowStepSequence.empty)
|
|
@@ -71,6 +98,8 @@ class Flow:
|
|
|
71
98
|
"""The flow ids for which the assistant should ask for confirmation."""
|
|
72
99
|
block_digressions: List[str] = field(default_factory=list)
|
|
73
100
|
"""The flow ids that the assistant should block from digressing to."""
|
|
101
|
+
run_pattern_completed: bool = True
|
|
102
|
+
"""Whether the pattern_completed flow should be run after the flow ends."""
|
|
74
103
|
|
|
75
104
|
@staticmethod
|
|
76
105
|
def from_json(
|
|
@@ -88,6 +117,8 @@ class Flow:
|
|
|
88
117
|
Returns:
|
|
89
118
|
A Flow object.
|
|
90
119
|
"""
|
|
120
|
+
from rasa.shared.core.flows.utils import extract_translations
|
|
121
|
+
|
|
91
122
|
step_sequence = FlowStepSequence.from_json(flow_id, data.get("steps"))
|
|
92
123
|
nlu_triggers = NLUTriggers.from_json(data.get("nlu_trigger"))
|
|
93
124
|
|
|
@@ -96,21 +127,25 @@ class Flow:
|
|
|
96
127
|
|
|
97
128
|
return Flow(
|
|
98
129
|
id=flow_id,
|
|
99
|
-
custom_name=data.get(
|
|
100
|
-
description=data.get(
|
|
101
|
-
always_include_in_prompt=data.get(
|
|
102
|
-
# str or bool are permitted in the flow schema but internally we want a str
|
|
103
|
-
guard_condition=str(data[
|
|
130
|
+
custom_name=data.get(KEY_NAME),
|
|
131
|
+
description=data.get(KEY_DESCRIPTION),
|
|
132
|
+
always_include_in_prompt=data.get(KEY_ALWAYS_INCLUDE_IN_PROMPT),
|
|
133
|
+
# str or bool are permitted in the flow schema, but internally we want a str
|
|
134
|
+
guard_condition=str(data[KEY_IF]) if KEY_IF in data else None,
|
|
104
135
|
step_sequence=Flow.resolve_default_ids(step_sequence),
|
|
105
136
|
nlu_triggers=nlu_triggers,
|
|
106
137
|
# If we are reading the flows in after training the file_path is part of
|
|
107
138
|
# data. When the model is trained, take the provided file_path.
|
|
108
|
-
file_path=data.get(
|
|
109
|
-
persisted_slots=data.get(
|
|
139
|
+
file_path=data.get(KEY_FILE_PATH) if KEY_FILE_PATH in data else file_path,
|
|
140
|
+
persisted_slots=data.get(KEY_PERSISTED_SLOTS, []),
|
|
110
141
|
ask_confirm_digressions=extract_digression_prop(
|
|
111
142
|
KEY_ASK_CONFIRM_DIGRESSIONS, data
|
|
112
143
|
),
|
|
113
144
|
block_digressions=extract_digression_prop(KEY_BLOCK_DIGRESSIONS, data),
|
|
145
|
+
run_pattern_completed=data.get(KEY_RUN_PATTERN_COMPLETED, True),
|
|
146
|
+
translation=extract_translations(
|
|
147
|
+
translation_data=data.get(KEY_TRANSLATION, {})
|
|
148
|
+
),
|
|
114
149
|
)
|
|
115
150
|
|
|
116
151
|
def get_full_name(self) -> str:
|
|
@@ -168,33 +203,62 @@ class Flow:
|
|
|
168
203
|
The Flow object as serialized data.
|
|
169
204
|
"""
|
|
170
205
|
data: Dict[Text, Any] = {
|
|
171
|
-
|
|
172
|
-
|
|
206
|
+
KEY_ID: self.id,
|
|
207
|
+
KEY_STEPS: self.step_sequence.as_json(),
|
|
173
208
|
}
|
|
174
209
|
if self.custom_name is not None:
|
|
175
|
-
data[
|
|
210
|
+
data[KEY_NAME] = self.custom_name
|
|
176
211
|
if self.description is not None:
|
|
177
|
-
data[
|
|
212
|
+
data[KEY_DESCRIPTION] = self.description
|
|
178
213
|
if self.guard_condition is not None:
|
|
179
|
-
data[
|
|
214
|
+
data[KEY_IF] = self.guard_condition
|
|
180
215
|
if self.always_include_in_prompt is not None:
|
|
181
|
-
data[
|
|
216
|
+
data[KEY_ALWAYS_INCLUDE_IN_PROMPT] = self.always_include_in_prompt
|
|
182
217
|
if self.nlu_triggers:
|
|
183
|
-
data[
|
|
218
|
+
data[KEY_NLU_TRIGGER] = self.nlu_triggers.as_json()
|
|
184
219
|
if self.file_path:
|
|
185
|
-
data[
|
|
220
|
+
data[KEY_FILE_PATH] = self.file_path
|
|
186
221
|
if self.persisted_slots:
|
|
187
|
-
data[
|
|
222
|
+
data[KEY_PERSISTED_SLOTS] = self.persisted_slots
|
|
188
223
|
if self.ask_confirm_digressions:
|
|
189
224
|
data[KEY_ASK_CONFIRM_DIGRESSIONS] = self.ask_confirm_digressions
|
|
190
225
|
if self.block_digressions:
|
|
191
226
|
data[KEY_BLOCK_DIGRESSIONS] = self.block_digressions
|
|
227
|
+
if self.run_pattern_completed is not None:
|
|
228
|
+
data["run_pattern_completed"] = self.run_pattern_completed
|
|
229
|
+
if self.translation:
|
|
230
|
+
data[KEY_TRANSLATION] = {
|
|
231
|
+
language_code: translation.dict()
|
|
232
|
+
for language_code, translation in self.translation.items()
|
|
233
|
+
}
|
|
192
234
|
|
|
193
235
|
return data
|
|
194
236
|
|
|
195
|
-
def
|
|
196
|
-
"""Returns the
|
|
197
|
-
|
|
237
|
+
def localized_name(self, language: Optional[Language] = None) -> Optional[Text]:
|
|
238
|
+
"""Returns the language specific flow name or None.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
language: Preferred language code.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Flow name in the specified language or None.
|
|
245
|
+
"""
|
|
246
|
+
language_code = language.code if language else None
|
|
247
|
+
translation = self.translation.get(language_code)
|
|
248
|
+
return translation.name if translation else None
|
|
249
|
+
|
|
250
|
+
def readable_name(self, language: Optional[Language] = None) -> str:
|
|
251
|
+
"""
|
|
252
|
+
Returns the flow's name in the specified language if available; otherwise
|
|
253
|
+
falls back to the flow's name, and finally the flow's ID.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
language: Preferred language code.
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
string: the localized name, the default name, or the flow's ID.
|
|
260
|
+
"""
|
|
261
|
+
return self.localized_name(language) or self.name or self.id
|
|
198
262
|
|
|
199
263
|
def step_by_id(self, step_id: Optional[Text]) -> Optional[FlowStep]:
|
|
200
264
|
"""Returns the step with the given id."""
|
|
@@ -288,9 +288,37 @@
|
|
|
288
288
|
"additionalProperties": false,
|
|
289
289
|
"schema_name": "dictionary with flow properties",
|
|
290
290
|
"properties": {
|
|
291
|
+
"name": {
|
|
292
|
+
"type": "string"
|
|
293
|
+
},
|
|
291
294
|
"description": {
|
|
292
295
|
"type": "string"
|
|
293
296
|
},
|
|
297
|
+
"translation": {
|
|
298
|
+
"type": "object",
|
|
299
|
+
"schema_name": "flow translation mapping",
|
|
300
|
+
"properties": {
|
|
301
|
+
"metadata": {
|
|
302
|
+
"type": "object"
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
"patternProperties": {
|
|
306
|
+
"^(?!metadata$).+": {
|
|
307
|
+
"type": "object",
|
|
308
|
+
"additionalProperties": false,
|
|
309
|
+
"required": [
|
|
310
|
+
"name"
|
|
311
|
+
],
|
|
312
|
+
"properties": {
|
|
313
|
+
"name": {
|
|
314
|
+
"type": "string"
|
|
315
|
+
},
|
|
316
|
+
"metadata": {}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
"additionalProperties": false
|
|
321
|
+
},
|
|
294
322
|
"if": {
|
|
295
323
|
"type": [
|
|
296
324
|
"string",
|
|
@@ -300,9 +328,6 @@
|
|
|
300
328
|
"always_include_in_prompt": {
|
|
301
329
|
"type": "boolean"
|
|
302
330
|
},
|
|
303
|
-
"name": {
|
|
304
|
-
"type": "string"
|
|
305
|
-
},
|
|
306
331
|
"nlu_trigger": {
|
|
307
332
|
"$ref": "#/$defs/nlu_trigger"
|
|
308
333
|
},
|
|
@@ -320,6 +345,9 @@
|
|
|
320
345
|
},
|
|
321
346
|
"block_digressions": {
|
|
322
347
|
"$ref": "#/$defs/block_digressions"
|
|
348
|
+
},
|
|
349
|
+
"run_pattern_completed": {
|
|
350
|
+
"type": "boolean"
|
|
323
351
|
}
|
|
324
352
|
}
|
|
325
353
|
},
|
|
@@ -10,42 +10,7 @@ from rasa.shared.core.constants import (
|
|
|
10
10
|
)
|
|
11
11
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
12
12
|
from rasa.shared.core.flows.utils import extract_digression_prop
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
@dataclass
|
|
16
|
-
class SlotRejection:
|
|
17
|
-
"""A pair of validation condition and an utterance for the case of failure."""
|
|
18
|
-
|
|
19
|
-
if_: str
|
|
20
|
-
"""The condition that should be checked."""
|
|
21
|
-
utter: str
|
|
22
|
-
"""The utterance that should be executed if the condition is met."""
|
|
23
|
-
|
|
24
|
-
@staticmethod
|
|
25
|
-
def from_dict(data: Dict[str, Any]) -> SlotRejection:
|
|
26
|
-
"""Create a SlotRejection object from serialized data.
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
data: data for a SlotRejection object in a serialized format
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
A SlotRejection object
|
|
33
|
-
"""
|
|
34
|
-
return SlotRejection(
|
|
35
|
-
if_=data["if"],
|
|
36
|
-
utter=data["utter"],
|
|
37
|
-
)
|
|
38
|
-
|
|
39
|
-
def as_dict(self) -> Dict[str, Any]:
|
|
40
|
-
"""Serialize the SlotRejection object.
|
|
41
|
-
|
|
42
|
-
Returns:
|
|
43
|
-
the SlotRejection object as serialized data
|
|
44
|
-
"""
|
|
45
|
-
return {
|
|
46
|
-
"if": self.if_,
|
|
47
|
-
"utter": self.utter,
|
|
48
|
-
}
|
|
13
|
+
from rasa.shared.core.slots import SlotRejection
|
|
49
14
|
|
|
50
15
|
|
|
51
16
|
@dataclass
|
rasa/shared/core/flows/utils.py
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
from typing import Any, Dict, List, Set
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Dict, List, Set, Text
|
|
2
2
|
|
|
3
3
|
from rasa.shared.utils.io import raise_deprecation_warning
|
|
4
4
|
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from rasa.shared.core.flows.flow import FlowLanguageTranslation
|
|
7
|
+
|
|
8
|
+
|
|
5
9
|
RESET_PROPERTY_NAME = "reset_after_flow_ends"
|
|
6
10
|
PERSIST_PROPERTY_NAME = "persisted_slots"
|
|
7
11
|
ALL_LABEL = "ALL"
|
|
8
12
|
|
|
9
13
|
|
|
10
|
-
def warn_deprecated_collect_step_config(
|
|
14
|
+
def warn_deprecated_collect_step_config() -> None:
|
|
11
15
|
"""Warns about deprecated reset_after_flow_ends usage in collect steps."""
|
|
12
16
|
raise_deprecation_warning(
|
|
13
|
-
f"Configuring '{RESET_PROPERTY_NAME}' in collect
|
|
14
|
-
f"deprecated and will be removed in Rasa Pro 4.0.0. In
|
|
17
|
+
f"Configuring '{RESET_PROPERTY_NAME}' in collect steps is "
|
|
18
|
+
f"deprecated and will be removed in Rasa Pro 4.0.0. In the parent flow, "
|
|
15
19
|
f"please use the '{PERSIST_PROPERTY_NAME}' "
|
|
16
20
|
"property at the flow level instead."
|
|
17
21
|
)
|
|
@@ -53,3 +57,23 @@ def extract_digression_prop(prop: str, data: Dict[str, Any]) -> List[str]:
|
|
|
53
57
|
digression_property = [ALL_LABEL] if digression_property else []
|
|
54
58
|
|
|
55
59
|
return digression_property
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def extract_translations(
|
|
63
|
+
translation_data: Dict[Text, Any],
|
|
64
|
+
) -> Dict[Text, "FlowLanguageTranslation"]:
|
|
65
|
+
"""Extracts translations from a dictionary.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
translation_data: The dictionary containing the translations.
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
A dictionary containing the extracted translations.
|
|
72
|
+
"""
|
|
73
|
+
from rasa.shared.core.flows.flow import FlowLanguageTranslation
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
language_code: FlowLanguageTranslation.parse_obj({**data})
|
|
77
|
+
for language_code, data in translation_data.items()
|
|
78
|
+
if language_code != "metadata"
|
|
79
|
+
}
|
|
@@ -723,7 +723,7 @@ def validate_slot_persistence_configuration(flow: Flow) -> None:
|
|
|
723
723
|
flow_slots.add(step.collect)
|
|
724
724
|
if not step.reset_after_flow_ends:
|
|
725
725
|
collect_step = step.collect
|
|
726
|
-
warn_deprecated_collect_step_config(
|
|
726
|
+
warn_deprecated_collect_step_config()
|
|
727
727
|
if has_flow_level_persistence:
|
|
728
728
|
raise DuplicateSlotPersistConfigException(flow_id, collect_step)
|
|
729
729
|
|
|
@@ -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
|