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
|
@@ -24,6 +24,7 @@ structlogger = structlog.get_logger()
|
|
|
24
24
|
class CartesiaTTSConfig(TTSEngineConfig):
|
|
25
25
|
model_id: Optional[str] = None
|
|
26
26
|
version: Optional[str] = None
|
|
27
|
+
endpoint: Optional[str] = None
|
|
27
28
|
|
|
28
29
|
|
|
29
30
|
class CartesiaTTS(TTSEngine[CartesiaTTSConfig]):
|
|
@@ -38,11 +39,6 @@ class CartesiaTTS(TTSEngine[CartesiaTTSConfig]):
|
|
|
38
39
|
if self.__class__.session is None or self.__class__.session.closed:
|
|
39
40
|
self.__class__.session = aiohttp.ClientSession(timeout=timeout)
|
|
40
41
|
|
|
41
|
-
@staticmethod
|
|
42
|
-
def get_tts_endpoint() -> str:
|
|
43
|
-
"""Create the endpoint string for cartesia."""
|
|
44
|
-
return "https://api.cartesia.ai/tts/sse"
|
|
45
|
-
|
|
46
42
|
@staticmethod
|
|
47
43
|
def get_request_body(text: str, config: CartesiaTTSConfig) -> Dict:
|
|
48
44
|
"""Create the request body for cartesia."""
|
|
@@ -79,7 +75,7 @@ class CartesiaTTS(TTSEngine[CartesiaTTSConfig]):
|
|
|
79
75
|
config = self.config.merge(config)
|
|
80
76
|
payload = self.get_request_body(text, config)
|
|
81
77
|
headers = self.get_request_headers(config)
|
|
82
|
-
url = self.
|
|
78
|
+
url = self.config.endpoint
|
|
83
79
|
if self.session is None:
|
|
84
80
|
raise ConnectionException("Client session is not initialized")
|
|
85
81
|
try:
|
|
@@ -101,13 +97,22 @@ class CartesiaTTS(TTSEngine[CartesiaTTSConfig]):
|
|
|
101
97
|
channel_bytes
|
|
102
98
|
)
|
|
103
99
|
return
|
|
100
|
+
elif response.status == 401:
|
|
101
|
+
structlogger.error(
|
|
102
|
+
"cartesia.synthesize.rest.unauthorized",
|
|
103
|
+
status_code=response.status,
|
|
104
|
+
)
|
|
105
|
+
raise TTSError(
|
|
106
|
+
"Unauthorized. Please make sure you have the correct API key."
|
|
107
|
+
)
|
|
104
108
|
else:
|
|
109
|
+
response_text = await response.text()
|
|
105
110
|
structlogger.error(
|
|
106
111
|
"cartesia.synthesize.rest.failed",
|
|
107
112
|
status_code=response.status,
|
|
108
|
-
msg=
|
|
113
|
+
msg=response_text,
|
|
109
114
|
)
|
|
110
|
-
raise TTSError(f"TTS failed: {
|
|
115
|
+
raise TTSError(f"TTS failed: {response_text}")
|
|
111
116
|
except ClientConnectorError as e:
|
|
112
117
|
raise TTSError(e)
|
|
113
118
|
except TimeoutError as e:
|
|
@@ -125,6 +130,7 @@ class CartesiaTTS(TTSEngine[CartesiaTTSConfig]):
|
|
|
125
130
|
timeout=10,
|
|
126
131
|
model_id="sonic-english",
|
|
127
132
|
version="2024-06-10",
|
|
133
|
+
endpoint="https://api.cartesia.ai/tts/sse",
|
|
128
134
|
)
|
|
129
135
|
|
|
130
136
|
@classmethod
|
|
@@ -148,6 +148,19 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
148
148
|
await self.voice_websocket.send(marker_message)
|
|
149
149
|
self.latest_message_id = mark_id
|
|
150
150
|
|
|
151
|
+
async def send_start_marker(self, recipient_id: str) -> None:
|
|
152
|
+
"""Send a marker message before the first audio chunk."""
|
|
153
|
+
# Default implementation uses the generic marker message
|
|
154
|
+
await self.send_marker_message(recipient_id)
|
|
155
|
+
|
|
156
|
+
async def send_intermediate_marker(self, recipient_id: str) -> None:
|
|
157
|
+
"""Send a marker message during audio streaming."""
|
|
158
|
+
await self.send_marker_message(recipient_id)
|
|
159
|
+
|
|
160
|
+
async def send_end_marker(self, recipient_id: str) -> None:
|
|
161
|
+
"""Send a marker message after the last audio chunk."""
|
|
162
|
+
await self.send_marker_message(recipient_id)
|
|
163
|
+
|
|
151
164
|
def update_silence_timeout(self) -> None:
|
|
152
165
|
"""Updates the silence timeout for the session."""
|
|
153
166
|
if self.tracker_state:
|
|
@@ -173,6 +186,13 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
173
186
|
cached_audio_bytes = self.tts_cache.get(text)
|
|
174
187
|
collected_audio_bytes = RasaAudioBytes(b"")
|
|
175
188
|
seconds_marker = -1
|
|
189
|
+
|
|
190
|
+
# Send start marker before first chunk
|
|
191
|
+
try:
|
|
192
|
+
await self.send_start_marker(recipient_id)
|
|
193
|
+
except (WebsocketClosed, ServerError):
|
|
194
|
+
call_state.connection_failed = True # type: ignore[attr-defined]
|
|
195
|
+
|
|
176
196
|
if cached_audio_bytes:
|
|
177
197
|
audio_stream = self.chunk_audio(cached_audio_bytes)
|
|
178
198
|
else:
|
|
@@ -189,15 +209,16 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
189
209
|
await self.send_audio_bytes(recipient_id, audio_bytes)
|
|
190
210
|
full_seconds_of_audio = len(collected_audio_bytes) // HERTZ
|
|
191
211
|
if full_seconds_of_audio > seconds_marker:
|
|
192
|
-
await self.
|
|
212
|
+
await self.send_intermediate_marker(recipient_id)
|
|
193
213
|
seconds_marker = full_seconds_of_audio
|
|
194
214
|
|
|
195
215
|
except (WebsocketClosed, ServerError):
|
|
196
216
|
# ignore sending error, and keep collecting and caching audio bytes
|
|
197
217
|
call_state.connection_failed = True # type: ignore[attr-defined]
|
|
198
218
|
collected_audio_bytes = RasaAudioBytes(collected_audio_bytes + audio_bytes)
|
|
219
|
+
|
|
199
220
|
try:
|
|
200
|
-
await self.
|
|
221
|
+
await self.send_end_marker(recipient_id)
|
|
201
222
|
except (WebsocketClosed, ServerError):
|
|
202
223
|
# ignore sending error
|
|
203
224
|
pass
|
rasa/core/constants.py
CHANGED
|
@@ -110,3 +110,5 @@ UTTER_SOURCE_METADATA_KEY = "utter_source"
|
|
|
110
110
|
DOMAIN_GROUND_TRUTH_METADATA_KEY = "domain_ground_truth"
|
|
111
111
|
ACTIVE_FLOW_METADATA_KEY = "active_flow"
|
|
112
112
|
STEP_ID_METADATA_KEY = "step_id"
|
|
113
|
+
KEY_IS_CALM_SYSTEM = "is_calm_system"
|
|
114
|
+
KEY_IS_COEXISTENCE_ASSISTANT = "is_coexistence_assistant"
|
|
@@ -64,7 +64,7 @@ DEFAULT_LLM_CONFIG = {
|
|
|
64
64
|
DEFAULT_RESPONSE_VARIATION_PROMPT_TEMPLATE = """The following is a conversation with
|
|
65
65
|
an AI assistant. The assistant is helpful, creative, clever, and very friendly.
|
|
66
66
|
Rephrase the suggested AI response staying close to the original message and retaining
|
|
67
|
-
its meaning. Use simple
|
|
67
|
+
its meaning. Use simple {{language}}.
|
|
68
68
|
|
|
69
69
|
Context / previous conversation with the user:
|
|
70
70
|
{{history}}
|
|
@@ -164,6 +164,22 @@ class ContextualResponseRephraser(
|
|
|
164
164
|
response[PROMPTS] = prompts
|
|
165
165
|
return response
|
|
166
166
|
|
|
167
|
+
@staticmethod
|
|
168
|
+
def get_language_label(tracker: DialogueStateTracker) -> str:
|
|
169
|
+
"""Fetches the label of the language to be used for the rephraser.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
tracker: The tracker to get the language from.
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The label of the current language, or "English" if no language is set.
|
|
176
|
+
"""
|
|
177
|
+
return (
|
|
178
|
+
tracker.current_language.label
|
|
179
|
+
if tracker.current_language
|
|
180
|
+
else tracker.default_language.label
|
|
181
|
+
)
|
|
182
|
+
|
|
167
183
|
def _last_message_if_human(self, tracker: DialogueStateTracker) -> Optional[str]:
|
|
168
184
|
"""Returns the latest message from the tracker.
|
|
169
185
|
|
|
@@ -281,6 +297,7 @@ class ContextualResponseRephraser(
|
|
|
281
297
|
suggested_response=response_text,
|
|
282
298
|
current_input=current_input,
|
|
283
299
|
slots=tracker.current_slot_values(),
|
|
300
|
+
language=self.get_language_label(tracker),
|
|
284
301
|
)
|
|
285
302
|
log_llm(
|
|
286
303
|
logger=structlogger,
|
rasa/core/nlg/generator.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from typing import Any, Dict, List, Optional, Text, Union
|
|
3
2
|
|
|
3
|
+
import structlog
|
|
4
|
+
from jinja2 import Template
|
|
5
|
+
from pypred import Predicate
|
|
6
|
+
|
|
4
7
|
import rasa.shared.utils.common
|
|
5
8
|
import rasa.shared.utils.io
|
|
6
9
|
from rasa.shared.constants import CHANNEL, RESPONSE_CONDITION
|
|
@@ -8,7 +11,7 @@ from rasa.shared.core.domain import Domain
|
|
|
8
11
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
9
12
|
from rasa.utils.endpoints import EndpointConfig
|
|
10
13
|
|
|
11
|
-
|
|
14
|
+
structlogger = structlog.get_logger()
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
class NaturalLanguageGenerator:
|
|
@@ -74,7 +77,11 @@ def _create_from_endpoint_config(
|
|
|
74
77
|
else:
|
|
75
78
|
nlg = _load_from_module_name_in_endpoint_config(endpoint_config, domain)
|
|
76
79
|
|
|
77
|
-
|
|
80
|
+
structlogger.debug(
|
|
81
|
+
"rasa.core.nlg.generator.create",
|
|
82
|
+
nlg_class_name=nlg.__class__.__name__,
|
|
83
|
+
event_info=f"Instantiated NLG to '{nlg.__class__.__name__}'.",
|
|
84
|
+
)
|
|
78
85
|
return nlg
|
|
79
86
|
|
|
80
87
|
|
|
@@ -112,18 +119,15 @@ class ResponseVariationFilter:
|
|
|
112
119
|
) -> bool:
|
|
113
120
|
"""Checks if the conditional response variation matches the filled slots."""
|
|
114
121
|
constraints = response.get(RESPONSE_CONDITION, [])
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
122
|
+
if isinstance(constraints, str) and not _evaluate_predicate(
|
|
123
|
+
constraints, filled_slots
|
|
124
|
+
):
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
elif isinstance(constraints, list):
|
|
128
|
+
for constraint in constraints:
|
|
129
|
+
if not _evaluate_and_deprecate_condition(constraint, filled_slots):
|
|
121
130
|
return False
|
|
122
|
-
# slot values can be of different data types
|
|
123
|
-
# such as int, float, bool, etc. hence, this check
|
|
124
|
-
# executes when slot values are not strings
|
|
125
|
-
elif filled_slots_value != value:
|
|
126
|
-
return False
|
|
127
131
|
|
|
128
132
|
return True
|
|
129
133
|
|
|
@@ -180,7 +184,21 @@ class ResponseVariationFilter:
|
|
|
180
184
|
if conditional_no_channel:
|
|
181
185
|
return conditional_no_channel
|
|
182
186
|
|
|
183
|
-
|
|
187
|
+
if default_no_channel:
|
|
188
|
+
return default_no_channel
|
|
189
|
+
|
|
190
|
+
# if there is no response variation selected,
|
|
191
|
+
# return the internal error response to prevent
|
|
192
|
+
# the bot from staying silent
|
|
193
|
+
structlogger.error(
|
|
194
|
+
"rasa.core.nlg.generator.responses_for_utter_action.no_response",
|
|
195
|
+
utter_action=utter_action,
|
|
196
|
+
event_info=f"No response variation selected for the predicted "
|
|
197
|
+
f"utterance {utter_action}. Please check you have provided "
|
|
198
|
+
f"a default variation and that all the conditions are valid. "
|
|
199
|
+
f"Returning the internal error response.",
|
|
200
|
+
)
|
|
201
|
+
return self.responses.get("utter_internal_error_rasa", [])
|
|
184
202
|
|
|
185
203
|
def get_response_variation_id(
|
|
186
204
|
self,
|
|
@@ -228,3 +246,53 @@ class ResponseVariationFilter:
|
|
|
228
246
|
response_ids.add(response_variation_id)
|
|
229
247
|
|
|
230
248
|
return True
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _evaluate_and_deprecate_condition(
|
|
252
|
+
constraint: Dict[Text, Any], filled_slots: Dict[Text, Any]
|
|
253
|
+
) -> bool:
|
|
254
|
+
"""Evaluates the condition of a response variation."""
|
|
255
|
+
rasa.shared.utils.io.raise_deprecation_warning(
|
|
256
|
+
"Using a dictionary as a condition in a response variation is deprecated. "
|
|
257
|
+
"Please use a pypred string predicate instead. "
|
|
258
|
+
"Dictionary conditions will be removed in Rasa Open Source 4.0.0 .",
|
|
259
|
+
warn_until_version="4.0.0",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
name = constraint["name"]
|
|
263
|
+
value = constraint["value"]
|
|
264
|
+
filled_slots_value = filled_slots.get(name)
|
|
265
|
+
if isinstance(filled_slots_value, str) and isinstance(value, str):
|
|
266
|
+
if filled_slots_value.casefold() != value.casefold():
|
|
267
|
+
return False
|
|
268
|
+
# slot values can be of different data types
|
|
269
|
+
# such as int, float, bool, etc. hence, this check
|
|
270
|
+
# executes when slot values are not strings
|
|
271
|
+
elif filled_slots_value != value:
|
|
272
|
+
return False
|
|
273
|
+
|
|
274
|
+
return True
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def _evaluate_predicate(constraint: str, filled_slots: Dict[Text, Any]) -> bool:
|
|
278
|
+
"""Evaluates the condition of a response variation."""
|
|
279
|
+
context = {"slots": filled_slots}
|
|
280
|
+
document = context.copy()
|
|
281
|
+
try:
|
|
282
|
+
rendered_template = Template(constraint).render(context)
|
|
283
|
+
predicate = Predicate(rendered_template)
|
|
284
|
+
result = predicate.evaluate(document)
|
|
285
|
+
structlogger.debug(
|
|
286
|
+
"rasa.core.nlg.generator.evaluate_conditional_response_predicate",
|
|
287
|
+
predicate=predicate.description(),
|
|
288
|
+
result=result,
|
|
289
|
+
)
|
|
290
|
+
return result
|
|
291
|
+
except (TypeError, Exception) as e:
|
|
292
|
+
structlogger.error(
|
|
293
|
+
"rasa.core.nlg.generator.evaluate_conditional_response_predicate.error",
|
|
294
|
+
predicate=constraint,
|
|
295
|
+
document=document,
|
|
296
|
+
error=str(e),
|
|
297
|
+
)
|
|
298
|
+
return False
|
rasa/core/nlg/response.py
CHANGED
|
@@ -49,9 +49,12 @@ class TemplatedNaturalLanguageGenerator(NaturalLanguageGenerator):
|
|
|
49
49
|
selected_response = np.random.choice(suitable_responses)
|
|
50
50
|
condition = selected_response.get(RESPONSE_CONDITION)
|
|
51
51
|
if condition:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
if isinstance(condition, list):
|
|
53
|
+
formatted_response_conditions = (
|
|
54
|
+
self._format_response_conditions(condition)
|
|
55
|
+
)
|
|
56
|
+
else:
|
|
57
|
+
formatted_response_conditions = condition
|
|
55
58
|
logger.debug(
|
|
56
59
|
"Selecting response variation with conditions:"
|
|
57
60
|
f"{formatted_response_conditions}"
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Optional, Text
|
|
2
|
+
|
|
3
|
+
from rasa.engine.language import Language
|
|
4
|
+
from rasa.shared.core.flows.constants import KEY_TRANSLATION
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def get_translated_text(
|
|
8
|
+
text: Optional[Text],
|
|
9
|
+
translation: Dict[Text, Any],
|
|
10
|
+
language: Optional[Language] = None,
|
|
11
|
+
) -> Optional[Text]:
|
|
12
|
+
"""Get the translated text from the message.
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
text: The default text to use if no translation is found.
|
|
16
|
+
translation: The translations for the text.
|
|
17
|
+
language: The language to use for the translation.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
The translated text if found, otherwise the default text.
|
|
21
|
+
"""
|
|
22
|
+
language_code = language.code if language else None
|
|
23
|
+
return translation.get(language_code, text)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_translated_buttons(
|
|
27
|
+
buttons: Optional[List[Dict[Text, Any]]], language: Optional[Language] = None
|
|
28
|
+
) -> Optional[List[Dict[Text, Any]]]:
|
|
29
|
+
"""Get the translated buttons from the message.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
buttons: The default buttons to use if no translation is found.
|
|
33
|
+
language: The language to use for the translation.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The translated buttons if found; otherwise, the default buttons.
|
|
37
|
+
"""
|
|
38
|
+
if buttons is None:
|
|
39
|
+
return None
|
|
40
|
+
|
|
41
|
+
language_code = language.code if language else None
|
|
42
|
+
translated_buttons = []
|
|
43
|
+
for button in buttons:
|
|
44
|
+
translation = button.get(KEY_TRANSLATION, {})
|
|
45
|
+
language_translation = translation.get(language_code, {})
|
|
46
|
+
|
|
47
|
+
# Maintain the original key order to ensure
|
|
48
|
+
# accurate comparisons of BotUtter events.
|
|
49
|
+
translated_button = {
|
|
50
|
+
key: language_translation.get(key, button.get(key))
|
|
51
|
+
for key, value in button.items()
|
|
52
|
+
if key != KEY_TRANSLATION
|
|
53
|
+
}
|
|
54
|
+
translated_buttons.append(translated_button)
|
|
55
|
+
return translated_buttons
|
|
@@ -4,7 +4,7 @@ If the answer is not known or cannot be determined from the provided documents o
|
|
|
4
4
|
Use the following documents to answer the question:
|
|
5
5
|
{% for doc in docs %}
|
|
6
6
|
{{ loop.cycle("*")}}. {{ doc.metadata }}
|
|
7
|
-
{{ doc.
|
|
7
|
+
{{ doc.text }}
|
|
8
8
|
{% endfor %}
|
|
9
9
|
|
|
10
10
|
{% if citation_enabled %}
|
|
@@ -85,9 +85,8 @@ from rasa.shared.core.flows.steps import (
|
|
|
85
85
|
NoOperationFlowStep,
|
|
86
86
|
SetSlotsFlowStep,
|
|
87
87
|
)
|
|
88
|
-
from rasa.shared.core.flows.steps.collect import SlotRejection
|
|
89
88
|
from rasa.shared.core.flows.steps.constants import START_STEP
|
|
90
|
-
from rasa.shared.core.slots import Slot
|
|
89
|
+
from rasa.shared.core.slots import Slot, SlotRejection
|
|
91
90
|
from rasa.shared.core.trackers import (
|
|
92
91
|
DialogueStateTracker,
|
|
93
92
|
)
|
|
@@ -243,7 +242,10 @@ def events_for_collect_step_execution(
|
|
|
243
242
|
|
|
244
243
|
|
|
245
244
|
def trigger_pattern_continue_interrupted(
|
|
246
|
-
current_frame: DialogueStackFrame,
|
|
245
|
+
current_frame: DialogueStackFrame,
|
|
246
|
+
stack: DialogueStack,
|
|
247
|
+
flows: FlowsList,
|
|
248
|
+
tracker: DialogueStateTracker,
|
|
247
249
|
) -> List[Event]:
|
|
248
250
|
"""Trigger the pattern to continue an interrupted flow if needed."""
|
|
249
251
|
events: List[Event] = []
|
|
@@ -266,7 +268,9 @@ def trigger_pattern_continue_interrupted(
|
|
|
266
268
|
):
|
|
267
269
|
stack.push(
|
|
268
270
|
ContinueInterruptedPatternFlowStackFrame(
|
|
269
|
-
previous_flow_name=interrupted_user_flow.readable_name(
|
|
271
|
+
previous_flow_name=interrupted_user_flow.readable_name(
|
|
272
|
+
language=tracker.current_language
|
|
273
|
+
),
|
|
270
274
|
)
|
|
271
275
|
)
|
|
272
276
|
events.append(
|
|
@@ -309,6 +313,9 @@ def trigger_pattern_completed(
|
|
|
309
313
|
or isinstance(current_frame, SearchPatternFlowStackFrame)
|
|
310
314
|
):
|
|
311
315
|
completed_flow = current_frame.flow(flows)
|
|
316
|
+
if not completed_flow.run_pattern_completed:
|
|
317
|
+
return
|
|
318
|
+
|
|
312
319
|
completed_flow_name = completed_flow.readable_name() if completed_flow else None
|
|
313
320
|
stack.push(
|
|
314
321
|
CompletedPatternFlowStackFrame(
|
|
@@ -670,7 +677,7 @@ def _run_end_step(
|
|
|
670
677
|
trigger_pattern_clarification(current_frame, stack, flows)
|
|
671
678
|
else:
|
|
672
679
|
resumed_events = trigger_pattern_continue_interrupted(
|
|
673
|
-
current_frame, stack, flows
|
|
680
|
+
current_frame, stack, flows, tracker
|
|
674
681
|
)
|
|
675
682
|
reset_events: List[Event] = reset_scoped_slots(current_frame, flow, tracker)
|
|
676
683
|
return ContinueFlowWithNextStep(
|
rasa/core/processor.py
CHANGED
|
@@ -25,6 +25,7 @@ from rasa.core.channels.channel import (
|
|
|
25
25
|
OutputChannel,
|
|
26
26
|
UserMessage,
|
|
27
27
|
)
|
|
28
|
+
from rasa.core.constants import KEY_IS_CALM_SYSTEM, KEY_IS_COEXISTENCE_ASSISTANT
|
|
28
29
|
from rasa.core.http_interpreter import RasaNLUHttpInterpreter
|
|
29
30
|
from rasa.core.lock_store import LockStore
|
|
30
31
|
from rasa.core.nlg import NaturalLanguageGenerator
|
|
@@ -35,6 +36,12 @@ from rasa.dialogue_understanding.commands import (
|
|
|
35
36
|
NoopCommand,
|
|
36
37
|
SetSlotCommand,
|
|
37
38
|
)
|
|
39
|
+
from rasa.dialogue_understanding.commands.utils import (
|
|
40
|
+
create_validate_frames_from_slot_set_events,
|
|
41
|
+
)
|
|
42
|
+
from rasa.dialogue_understanding.patterns.validate_slot import (
|
|
43
|
+
ValidateSlotPatternFlowStackFrame,
|
|
44
|
+
)
|
|
38
45
|
from rasa.dialogue_understanding.utils import add_commands_to_message_parse_data
|
|
39
46
|
from rasa.engine import loader
|
|
40
47
|
from rasa.engine.constants import (
|
|
@@ -201,10 +208,7 @@ class MessageProcessor:
|
|
|
201
208
|
)
|
|
202
209
|
return None
|
|
203
210
|
|
|
204
|
-
|
|
205
|
-
tracker = await self.run_action_extract_slots(
|
|
206
|
-
message.output_channel, tracker
|
|
207
|
-
)
|
|
211
|
+
tracker = await self.run_action_extract_slots(message.output_channel, tracker)
|
|
208
212
|
|
|
209
213
|
await self._run_prediction_loop(message.output_channel, tracker)
|
|
210
214
|
|
|
@@ -218,7 +222,9 @@ class MessageProcessor:
|
|
|
218
222
|
return None
|
|
219
223
|
|
|
220
224
|
async def run_action_extract_slots(
|
|
221
|
-
self,
|
|
225
|
+
self,
|
|
226
|
+
output_channel: OutputChannel,
|
|
227
|
+
tracker: DialogueStateTracker,
|
|
222
228
|
) -> DialogueStateTracker:
|
|
223
229
|
"""Run action to extract slots and update the tracker accordingly.
|
|
224
230
|
|
|
@@ -233,6 +239,10 @@ class MessageProcessor:
|
|
|
233
239
|
ACTION_EXTRACT_SLOTS, self.domain, self.action_endpoint
|
|
234
240
|
)
|
|
235
241
|
metadata = await self._add_flows_to_metadata()
|
|
242
|
+
metadata[KEY_IS_CALM_SYSTEM] = self.message_contains_commands(
|
|
243
|
+
tracker.latest_message
|
|
244
|
+
)
|
|
245
|
+
metadata[KEY_IS_COEXISTENCE_ASSISTANT] = self._is_coexistence_assistant(tracker)
|
|
236
246
|
|
|
237
247
|
extraction_events = await action_extract_slots.run(
|
|
238
248
|
output_channel, self.nlg, tracker, self.domain, metadata
|
|
@@ -1251,6 +1261,12 @@ class MessageProcessor:
|
|
|
1251
1261
|
# events and return values are used to update
|
|
1252
1262
|
# the tracker state after an action has been taken
|
|
1253
1263
|
try:
|
|
1264
|
+
validate_frames: List[ValidateSlotPatternFlowStackFrame] = []
|
|
1265
|
+
# check if the last action was a correction action
|
|
1266
|
+
# before validating the corrected slots
|
|
1267
|
+
if tracker.latest_action_name == ACTION_CORRECT_FLOW_SLOT:
|
|
1268
|
+
tracker, validate_frames = self.validate_corrected_slots(tracker)
|
|
1269
|
+
|
|
1254
1270
|
# Use temporary tracker as we might need to discard the policy events in
|
|
1255
1271
|
# case of a rejection.
|
|
1256
1272
|
temporary_tracker = tracker.copy()
|
|
@@ -1262,6 +1278,12 @@ class MessageProcessor:
|
|
|
1262
1278
|
|
|
1263
1279
|
if isinstance(action, FormAction):
|
|
1264
1280
|
flows_metadata = await self._add_flows_to_metadata()
|
|
1281
|
+
flows_metadata[KEY_IS_CALM_SYSTEM] = self.message_contains_commands(
|
|
1282
|
+
temporary_tracker.latest_message
|
|
1283
|
+
)
|
|
1284
|
+
flows_metadata[KEY_IS_COEXISTENCE_ASSISTANT] = (
|
|
1285
|
+
self._is_coexistence_assistant(temporary_tracker)
|
|
1286
|
+
)
|
|
1265
1287
|
metadata = prediction.action_metadata or {}
|
|
1266
1288
|
metadata.update(flows_metadata)
|
|
1267
1289
|
|
|
@@ -1276,6 +1298,14 @@ class MessageProcessor:
|
|
|
1276
1298
|
events = await action.run(
|
|
1277
1299
|
output_channel, nlg, temporary_tracker, self.domain
|
|
1278
1300
|
)
|
|
1301
|
+
|
|
1302
|
+
if validate_frames:
|
|
1303
|
+
stack = tracker.stack
|
|
1304
|
+
for frame in validate_frames:
|
|
1305
|
+
stack.push(frame)
|
|
1306
|
+
new_events = tracker.create_stack_updated_events(stack)
|
|
1307
|
+
tracker.update_with_events(new_events)
|
|
1308
|
+
|
|
1279
1309
|
self._log_action_and_events_on_tracker(tracker, action, events, prediction)
|
|
1280
1310
|
except ActionExecutionRejection:
|
|
1281
1311
|
events = [
|
|
@@ -1479,18 +1509,51 @@ class MessageProcessor:
|
|
|
1479
1509
|
Returns:
|
|
1480
1510
|
bool: True if any node in the graph schema uses `FlowPolicy`.
|
|
1481
1511
|
"""
|
|
1512
|
+
flow_policy_class_path = "rasa.core.policies.flow_policy.FlowPolicy"
|
|
1513
|
+
return self._is_component_present_in_graph_nodes(flow_policy_class_path)
|
|
1514
|
+
|
|
1515
|
+
@staticmethod
|
|
1516
|
+
def _is_coexistence_assistant(tracker: DialogueStateTracker) -> bool:
|
|
1517
|
+
"""Inspect the tracker to decide if we are in coexistence.
|
|
1518
|
+
|
|
1519
|
+
Returns:
|
|
1520
|
+
bool: True if the tracker contains the routine slot.
|
|
1521
|
+
"""
|
|
1522
|
+
return tracker.slots.get(ROUTE_TO_CALM_SLOT) is not None
|
|
1523
|
+
|
|
1524
|
+
def _is_component_present_in_graph_nodes(self, component_path: Text) -> bool:
|
|
1525
|
+
"""Check if a component is present in the graph nodes.
|
|
1526
|
+
|
|
1527
|
+
Args:
|
|
1528
|
+
component_path: The path of the component to check for.
|
|
1529
|
+
|
|
1530
|
+
Returns:
|
|
1531
|
+
`True` if the component is present in the graph nodes, `False` otherwise.
|
|
1532
|
+
"""
|
|
1482
1533
|
# Get the graph schema's nodes from the graph runner.
|
|
1483
1534
|
nodes: dict[str, Any] = self.graph_runner._graph_schema.nodes # type: ignore[attr-defined]
|
|
1484
1535
|
|
|
1485
|
-
flow_policy_class_path = "rasa.core.policies.flow_policy.FlowPolicy"
|
|
1486
|
-
# Iterate over the nodes and check if any node uses `FlowPolicy`.
|
|
1487
1536
|
for node_name, schema_node in nodes.items():
|
|
1488
1537
|
if (
|
|
1489
1538
|
schema_node.uses is not None
|
|
1490
1539
|
and f"{schema_node.uses.__module__}.{schema_node.uses.__name__}"
|
|
1491
|
-
==
|
|
1540
|
+
== component_path
|
|
1492
1541
|
):
|
|
1493
1542
|
return True
|
|
1494
1543
|
|
|
1495
|
-
# Return False if no node is found using `FlowPolicy`.
|
|
1496
1544
|
return False
|
|
1545
|
+
|
|
1546
|
+
def validate_corrected_slots(
|
|
1547
|
+
self,
|
|
1548
|
+
tracker: DialogueStateTracker,
|
|
1549
|
+
) -> Tuple[DialogueStateTracker, List[ValidateSlotPatternFlowStackFrame]]:
|
|
1550
|
+
"""Validate the slots that were corrected in the tracker."""
|
|
1551
|
+
prior_tracker_events = list(reversed(tracker.events))
|
|
1552
|
+
tracker, validate_frames = create_validate_frames_from_slot_set_events(
|
|
1553
|
+
tracker,
|
|
1554
|
+
prior_tracker_events,
|
|
1555
|
+
should_break=True,
|
|
1556
|
+
update_corrected_slots=True,
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
return tracker, validate_frames
|
|
@@ -5,6 +5,10 @@ from dataclasses import dataclass
|
|
|
5
5
|
from typing import Any, Dict, List, Optional, Text
|
|
6
6
|
|
|
7
7
|
from rasa.dialogue_understanding.commands.command import Command
|
|
8
|
+
from rasa.dialogue_understanding.commands.command_syntax_manager import (
|
|
9
|
+
CommandSyntaxManager,
|
|
10
|
+
CommandSyntaxVersion,
|
|
11
|
+
)
|
|
8
12
|
from rasa.dialogue_understanding.patterns.cannot_handle import (
|
|
9
13
|
CannotHandlePatternFlowStackFrame,
|
|
10
14
|
)
|
|
@@ -74,7 +78,14 @@ class CannotHandleCommand(Command):
|
|
|
74
78
|
|
|
75
79
|
def to_dsl(self) -> str:
|
|
76
80
|
"""Converts the command to a DSL string."""
|
|
77
|
-
|
|
81
|
+
mapper = {
|
|
82
|
+
CommandSyntaxVersion.v1: "CannotHandle()",
|
|
83
|
+
CommandSyntaxVersion.v2: "cannot handle",
|
|
84
|
+
}
|
|
85
|
+
return mapper.get(
|
|
86
|
+
CommandSyntaxManager.get_syntax_version(),
|
|
87
|
+
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
88
|
+
)
|
|
78
89
|
|
|
79
90
|
@classmethod
|
|
80
91
|
def from_dsl(cls, match: re.Match, **kwargs: Any) -> CannotHandleCommand:
|
|
@@ -86,4 +97,11 @@ class CannotHandleCommand(Command):
|
|
|
86
97
|
|
|
87
98
|
@staticmethod
|
|
88
99
|
def regex_pattern() -> str:
|
|
89
|
-
|
|
100
|
+
mapper = {
|
|
101
|
+
CommandSyntaxVersion.v1: r"CannotHandle\(\)",
|
|
102
|
+
CommandSyntaxVersion.v2: r"^[^\w]*cannot handle$",
|
|
103
|
+
}
|
|
104
|
+
return mapper.get(
|
|
105
|
+
CommandSyntaxManager.get_syntax_version(),
|
|
106
|
+
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
107
|
+
)
|
|
@@ -8,12 +8,14 @@ from typing import Any, Dict, List
|
|
|
8
8
|
import structlog
|
|
9
9
|
|
|
10
10
|
from rasa.dialogue_understanding.commands.command import Command
|
|
11
|
+
from rasa.dialogue_understanding.commands.command_syntax_manager import (
|
|
12
|
+
CommandSyntaxManager,
|
|
13
|
+
CommandSyntaxVersion,
|
|
14
|
+
)
|
|
11
15
|
from rasa.dialogue_understanding.patterns.cancel import CancelPatternFlowStackFrame
|
|
12
16
|
from rasa.dialogue_understanding.patterns.clarify import ClarifyPatternFlowStackFrame
|
|
13
17
|
from rasa.dialogue_understanding.stack.dialogue_stack import DialogueStack
|
|
14
|
-
from rasa.dialogue_understanding.stack.frames import
|
|
15
|
-
UserFlowStackFrame,
|
|
16
|
-
)
|
|
18
|
+
from rasa.dialogue_understanding.stack.frames import UserFlowStackFrame
|
|
17
19
|
from rasa.dialogue_understanding.stack.frames.flow_stack_frame import FlowStackFrameType
|
|
18
20
|
from rasa.dialogue_understanding.stack.utils import top_user_flow_frame
|
|
19
21
|
from rasa.shared.core.events import Event, FlowCancelled
|
|
@@ -111,7 +113,9 @@ class CancelFlowCommand(Command):
|
|
|
111
113
|
|
|
112
114
|
stack.push(
|
|
113
115
|
CancelPatternFlowStackFrame(
|
|
114
|
-
canceled_name=current_flow.readable_name(
|
|
116
|
+
canceled_name=current_flow.readable_name(
|
|
117
|
+
language=tracker.current_language
|
|
118
|
+
),
|
|
115
119
|
canceled_frames=canceled_frames,
|
|
116
120
|
)
|
|
117
121
|
)
|
|
@@ -144,7 +148,14 @@ class CancelFlowCommand(Command):
|
|
|
144
148
|
|
|
145
149
|
def to_dsl(self) -> str:
|
|
146
150
|
"""Converts the command to a DSL string."""
|
|
147
|
-
|
|
151
|
+
mapper = {
|
|
152
|
+
CommandSyntaxVersion.v1: "CancelFlow()",
|
|
153
|
+
CommandSyntaxVersion.v2: "cancel flow",
|
|
154
|
+
}
|
|
155
|
+
return mapper.get(
|
|
156
|
+
CommandSyntaxManager.get_syntax_version(),
|
|
157
|
+
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
158
|
+
)
|
|
148
159
|
|
|
149
160
|
@classmethod
|
|
150
161
|
def from_dsl(cls, match: re.Match, **kwargs: Any) -> CancelFlowCommand:
|
|
@@ -153,7 +164,14 @@ class CancelFlowCommand(Command):
|
|
|
153
164
|
|
|
154
165
|
@staticmethod
|
|
155
166
|
def regex_pattern() -> str:
|
|
156
|
-
|
|
167
|
+
mapper = {
|
|
168
|
+
CommandSyntaxVersion.v1: r"CancelFlow\(\)",
|
|
169
|
+
CommandSyntaxVersion.v2: r"^[^\w]*cancel flow$",
|
|
170
|
+
}
|
|
171
|
+
return mapper.get(
|
|
172
|
+
CommandSyntaxManager.get_syntax_version(),
|
|
173
|
+
mapper[CommandSyntaxManager.get_default_syntax_version()],
|
|
174
|
+
)
|
|
157
175
|
|
|
158
176
|
|
|
159
177
|
def cancel_all_pending_clarification_options(
|