rasa-pro 3.12.0rc1__py3-none-any.whl → 3.12.0rc3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +10 -13
- rasa/cli/dialogue_understanding_test.py +5 -8
- rasa/cli/llm_fine_tuning.py +47 -12
- rasa/cli/project_templates/calm/domain/list_contacts.yml +1 -2
- rasa/cli/project_templates/calm/domain/remove_contact.yml +1 -2
- rasa/cli/project_templates/calm/domain/shared.yml +1 -4
- rasa/core/actions/action_handle_digressions.py +35 -13
- rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
- rasa/core/channels/voice_stream/audiocodes.py +19 -6
- rasa/core/channels/voice_stream/call_state.py +3 -9
- rasa/core/channels/voice_stream/genesys.py +40 -55
- rasa/core/channels/voice_stream/voice_channel.py +61 -39
- rasa/core/policies/flows/flow_executor.py +7 -2
- rasa/core/processor.py +0 -1
- rasa/core/tracker_store.py +123 -34
- rasa/dialogue_understanding/commands/can_not_handle_command.py +1 -1
- rasa/dialogue_understanding/commands/cancel_flow_command.py +1 -1
- rasa/dialogue_understanding/commands/change_flow_command.py +1 -1
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +1 -1
- rasa/dialogue_understanding/commands/clarify_command.py +1 -1
- rasa/dialogue_understanding/commands/command_syntax_manager.py +1 -1
- rasa/dialogue_understanding/commands/handle_digressions_command.py +1 -7
- rasa/dialogue_understanding/commands/human_handoff_command.py +1 -1
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +1 -1
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +1 -1
- rasa/dialogue_understanding/commands/set_slot_command.py +2 -1
- rasa/dialogue_understanding/commands/skip_question_command.py +1 -1
- rasa/dialogue_understanding/commands/start_flow_command.py +3 -1
- rasa/dialogue_understanding/commands/utils.py +2 -32
- rasa/dialogue_understanding/generator/command_parser.py +41 -0
- rasa/dialogue_understanding/generator/constants.py +7 -2
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +9 -2
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +1 -1
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +29 -48
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_fallback_other_models_template.jinja2 +57 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +23 -50
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +141 -27
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +32 -18
- rasa/dialogue_understanding/processor/command_processor.py +43 -23
- rasa/dialogue_understanding/stack/utils.py +49 -6
- rasa/dialogue_understanding_test/du_test_case.py +30 -10
- rasa/dialogue_understanding_test/du_test_result.py +1 -1
- rasa/e2e_test/assertions.py +6 -8
- rasa/e2e_test/llm_judge_prompts/answer_relevance_prompt_template.jinja2 +5 -1
- rasa/e2e_test/llm_judge_prompts/groundedness_prompt_template.jinja2 +4 -0
- rasa/engine/language.py +67 -25
- rasa/llm_fine_tuning/conversations.py +3 -31
- rasa/llm_fine_tuning/llm_data_preparation_module.py +5 -3
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +18 -13
- rasa/llm_fine_tuning/paraphrasing_module.py +6 -2
- rasa/llm_fine_tuning/train_test_split_module.py +27 -27
- rasa/llm_fine_tuning/utils.py +7 -0
- rasa/shared/constants.py +4 -0
- rasa/shared/core/domain.py +2 -0
- rasa/shared/core/slots.py +6 -0
- rasa/shared/providers/_configs/azure_entra_id_config.py +8 -8
- rasa/shared/providers/llm/litellm_router_llm_client.py +1 -0
- rasa/shared/providers/llm/openai_llm_client.py +2 -2
- rasa/shared/providers/router/_base_litellm_router_client.py +38 -7
- rasa/shared/utils/llm.py +69 -10
- rasa/telemetry.py +13 -3
- rasa/tracing/instrumentation/attribute_extractors.py +2 -5
- rasa/validator.py +2 -2
- rasa/version.py +1 -1
- {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/METADATA +12 -14
- {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/RECORD +69 -68
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +0 -68
- {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/entry_points.txt +0 -0
|
@@ -17,6 +17,7 @@ from rasa.core.channels.voice_stream.asr.asr_event import (
|
|
|
17
17
|
ASREvent,
|
|
18
18
|
NewTranscript,
|
|
19
19
|
UserIsSpeaking,
|
|
20
|
+
UserSilence,
|
|
20
21
|
)
|
|
21
22
|
from rasa.core.channels.voice_stream.asr.azure import AzureASR
|
|
22
23
|
from rasa.core.channels.voice_stream.asr.deepgram import DeepgramASR
|
|
@@ -120,13 +121,14 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
120
121
|
voice_websocket: Websocket,
|
|
121
122
|
tts_engine: TTSEngine,
|
|
122
123
|
tts_cache: TTSCache,
|
|
124
|
+
min_buffer_size: int = 0,
|
|
123
125
|
):
|
|
124
126
|
super().__init__()
|
|
125
127
|
self.voice_websocket = voice_websocket
|
|
126
128
|
self.tts_engine = tts_engine
|
|
127
129
|
self.tts_cache = tts_cache
|
|
128
|
-
|
|
129
130
|
self.latest_message_id: Optional[str] = None
|
|
131
|
+
self.min_buffer_size = min_buffer_size
|
|
130
132
|
|
|
131
133
|
def rasa_audio_bytes_to_channel_bytes(
|
|
132
134
|
self, rasa_audio_bytes: RasaAudioBytes
|
|
@@ -186,6 +188,7 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
186
188
|
cached_audio_bytes = self.tts_cache.get(text)
|
|
187
189
|
collected_audio_bytes = RasaAudioBytes(b"")
|
|
188
190
|
seconds_marker = -1
|
|
191
|
+
last_sent_offset = 0
|
|
189
192
|
|
|
190
193
|
# Send start marker before first chunk
|
|
191
194
|
try:
|
|
@@ -205,17 +208,37 @@ class VoiceOutputChannel(OutputChannel):
|
|
|
205
208
|
audio_stream = self.chunk_audio(generate_silence())
|
|
206
209
|
|
|
207
210
|
async for audio_bytes in audio_stream:
|
|
208
|
-
|
|
209
|
-
await self.send_audio_bytes(recipient_id, audio_bytes)
|
|
210
|
-
full_seconds_of_audio = len(collected_audio_bytes) // HERTZ
|
|
211
|
-
if full_seconds_of_audio > seconds_marker:
|
|
212
|
-
await self.send_intermediate_marker(recipient_id)
|
|
213
|
-
seconds_marker = full_seconds_of_audio
|
|
211
|
+
collected_audio_bytes = RasaAudioBytes(collected_audio_bytes + audio_bytes)
|
|
214
212
|
|
|
213
|
+
# Check if we have enough new bytes to send
|
|
214
|
+
current_buffer_size = len(collected_audio_bytes) - last_sent_offset
|
|
215
|
+
should_send = current_buffer_size >= self.min_buffer_size
|
|
216
|
+
|
|
217
|
+
if should_send:
|
|
218
|
+
try:
|
|
219
|
+
# Send only the new bytes since last send
|
|
220
|
+
new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
|
|
221
|
+
await self.send_audio_bytes(recipient_id, new_bytes)
|
|
222
|
+
last_sent_offset = len(collected_audio_bytes)
|
|
223
|
+
|
|
224
|
+
full_seconds_of_audio = len(collected_audio_bytes) // HERTZ
|
|
225
|
+
if full_seconds_of_audio > seconds_marker:
|
|
226
|
+
await self.send_intermediate_marker(recipient_id)
|
|
227
|
+
seconds_marker = full_seconds_of_audio
|
|
228
|
+
|
|
229
|
+
except (WebsocketClosed, ServerError):
|
|
230
|
+
# ignore sending error, and keep collecting and caching audio bytes
|
|
231
|
+
call_state.connection_failed = True # type: ignore[attr-defined]
|
|
232
|
+
|
|
233
|
+
# Send any remaining audio not yet sent
|
|
234
|
+
remaining_bytes = len(collected_audio_bytes) - last_sent_offset
|
|
235
|
+
if remaining_bytes > 0:
|
|
236
|
+
try:
|
|
237
|
+
new_bytes = RasaAudioBytes(collected_audio_bytes[last_sent_offset:])
|
|
238
|
+
await self.send_audio_bytes(recipient_id, new_bytes)
|
|
215
239
|
except (WebsocketClosed, ServerError):
|
|
216
|
-
# ignore sending error
|
|
240
|
+
# ignore sending error
|
|
217
241
|
call_state.connection_failed = True # type: ignore[attr-defined]
|
|
218
|
-
collected_audio_bytes = RasaAudioBytes(collected_audio_bytes + audio_bytes)
|
|
219
242
|
|
|
220
243
|
try:
|
|
221
244
|
await self.send_end_marker(recipient_id)
|
|
@@ -265,13 +288,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
265
288
|
self.monitor_silence = monitor_silence
|
|
266
289
|
self.tts_cache = TTSCache(tts_config.get("cache_size", 1000))
|
|
267
290
|
|
|
268
|
-
async def
|
|
269
|
-
self,
|
|
270
|
-
voice_websocket: Websocket,
|
|
271
|
-
on_new_message: Callable[[UserMessage], Awaitable[Any]],
|
|
272
|
-
tts_engine: TTSEngine,
|
|
273
|
-
call_parameters: CallParameters,
|
|
274
|
-
) -> None:
|
|
291
|
+
async def monitor_silence_timeout(self, asr_event_queue: asyncio.Queue) -> None:
|
|
275
292
|
timeout = call_state.silence_timeout
|
|
276
293
|
if not timeout:
|
|
277
294
|
return
|
|
@@ -279,16 +296,8 @@ class VoiceInputChannel(InputChannel):
|
|
|
279
296
|
return
|
|
280
297
|
logger.debug("voice_channel.silence_timeout_watch_started", timeout=timeout)
|
|
281
298
|
await asyncio.sleep(timeout)
|
|
299
|
+
await asr_event_queue.put(UserSilence())
|
|
282
300
|
logger.debug("voice_channel.silence_timeout_tripped")
|
|
283
|
-
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
284
|
-
message = UserMessage(
|
|
285
|
-
"/silence_timeout",
|
|
286
|
-
output_channel,
|
|
287
|
-
call_parameters.stream_id,
|
|
288
|
-
input_channel=self.name(),
|
|
289
|
-
metadata=asdict(call_parameters),
|
|
290
|
-
)
|
|
291
|
-
await on_new_message(message)
|
|
292
301
|
|
|
293
302
|
@staticmethod
|
|
294
303
|
def _cancel_silence_timeout_watcher() -> None:
|
|
@@ -350,6 +359,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
350
359
|
_call_state.set(CallState())
|
|
351
360
|
asr_engine = asr_engine_from_config(self.asr_config)
|
|
352
361
|
tts_engine = tts_engine_from_config(self.tts_config)
|
|
362
|
+
asr_event_queue: asyncio.Queue = asyncio.Queue()
|
|
353
363
|
await asr_engine.connect()
|
|
354
364
|
|
|
355
365
|
call_parameters = await self.collect_call_parameters(channel_websocket)
|
|
@@ -376,12 +386,7 @@ class VoiceInputChannel(InputChannel):
|
|
|
376
386
|
self._cancel_silence_timeout_watcher()
|
|
377
387
|
call_state.silence_timeout_watcher = ( # type: ignore[attr-defined]
|
|
378
388
|
asyncio.create_task(
|
|
379
|
-
self.
|
|
380
|
-
channel_websocket,
|
|
381
|
-
on_new_message,
|
|
382
|
-
tts_engine,
|
|
383
|
-
call_parameters,
|
|
384
|
-
)
|
|
389
|
+
self.monitor_silence_timeout(asr_event_queue)
|
|
385
390
|
)
|
|
386
391
|
)
|
|
387
392
|
if isinstance(channel_action, NewAudioAction):
|
|
@@ -390,8 +395,13 @@ class VoiceInputChannel(InputChannel):
|
|
|
390
395
|
# end stream event came from the other side
|
|
391
396
|
break
|
|
392
397
|
|
|
393
|
-
async def
|
|
398
|
+
async def receive_asr_events() -> None:
|
|
394
399
|
async for event in asr_engine.stream_asr_events():
|
|
400
|
+
await asr_event_queue.put(event)
|
|
401
|
+
|
|
402
|
+
async def handle_asr_events() -> None:
|
|
403
|
+
while True:
|
|
404
|
+
event = await asr_event_queue.get()
|
|
395
405
|
await self.handle_asr_event(
|
|
396
406
|
event,
|
|
397
407
|
channel_websocket,
|
|
@@ -400,16 +410,18 @@ class VoiceInputChannel(InputChannel):
|
|
|
400
410
|
call_parameters,
|
|
401
411
|
)
|
|
402
412
|
|
|
403
|
-
|
|
404
|
-
|
|
413
|
+
tasks = [
|
|
414
|
+
asyncio.create_task(consume_audio_bytes()),
|
|
415
|
+
asyncio.create_task(receive_asr_events()),
|
|
416
|
+
asyncio.create_task(handle_asr_events()),
|
|
417
|
+
]
|
|
405
418
|
await asyncio.wait(
|
|
406
|
-
|
|
419
|
+
tasks,
|
|
407
420
|
return_when=asyncio.FIRST_COMPLETED,
|
|
408
421
|
)
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
asr_event_task.cancel()
|
|
422
|
+
for task in tasks:
|
|
423
|
+
if not task.done():
|
|
424
|
+
task.cancel()
|
|
413
425
|
await tts_engine.close_connection()
|
|
414
426
|
await asr_engine.close_connection()
|
|
415
427
|
await channel_websocket.close()
|
|
@@ -447,3 +459,13 @@ class VoiceInputChannel(InputChannel):
|
|
|
447
459
|
elif isinstance(e, UserIsSpeaking):
|
|
448
460
|
self._cancel_silence_timeout_watcher()
|
|
449
461
|
call_state.is_user_speaking = True # type: ignore[attr-defined]
|
|
462
|
+
elif isinstance(e, UserSilence):
|
|
463
|
+
output_channel = self.create_output_channel(voice_websocket, tts_engine)
|
|
464
|
+
message = UserMessage(
|
|
465
|
+
"/silence_timeout",
|
|
466
|
+
output_channel,
|
|
467
|
+
call_parameters.stream_id,
|
|
468
|
+
input_channel=self.name(),
|
|
469
|
+
metadata=asdict(call_parameters),
|
|
470
|
+
)
|
|
471
|
+
await on_new_message(message)
|
|
@@ -287,8 +287,13 @@ def trigger_pattern_clarification(
|
|
|
287
287
|
if not isinstance(current_frame, UserFlowStackFrame):
|
|
288
288
|
return None
|
|
289
289
|
|
|
290
|
-
if current_frame.frame_type
|
|
291
|
-
|
|
290
|
+
if current_frame.frame_type in [
|
|
291
|
+
FlowStackFrameType.CALL,
|
|
292
|
+
FlowStackFrameType.INTERRUPT,
|
|
293
|
+
]:
|
|
294
|
+
# we want to return to the flow that called
|
|
295
|
+
# the current flow or the flow that was interrupted
|
|
296
|
+
# by the current flow
|
|
292
297
|
return None
|
|
293
298
|
|
|
294
299
|
pending_flows = [
|
rasa/core/processor.py
CHANGED
rasa/core/tracker_store.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
import contextlib
|
|
4
4
|
import itertools
|
|
5
5
|
import json
|
|
6
|
-
import logging
|
|
7
6
|
import os
|
|
8
7
|
from inspect import isawaitable, iscoroutinefunction
|
|
9
8
|
from time import sleep
|
|
@@ -24,6 +23,7 @@ from typing import (
|
|
|
24
23
|
)
|
|
25
24
|
|
|
26
25
|
import sqlalchemy as sa
|
|
26
|
+
import structlog
|
|
27
27
|
from boto3.dynamodb.conditions import Key
|
|
28
28
|
from pymongo.collection import Collection
|
|
29
29
|
|
|
@@ -31,6 +31,7 @@ import rasa.shared.utils.cli
|
|
|
31
31
|
import rasa.shared.utils.common
|
|
32
32
|
import rasa.shared.utils.io
|
|
33
33
|
import rasa.utils.json_utils
|
|
34
|
+
from rasa.constants import DEFAULT_SANIC_WORKERS, ENV_SANIC_WORKERS
|
|
34
35
|
from rasa.core.brokers.broker import EventBroker
|
|
35
36
|
from rasa.core.constants import (
|
|
36
37
|
POSTGRESQL_MAX_OVERFLOW,
|
|
@@ -59,7 +60,7 @@ if TYPE_CHECKING:
|
|
|
59
60
|
from sqlalchemy.engine.url import URL
|
|
60
61
|
from sqlalchemy.orm import Query, Session
|
|
61
62
|
|
|
62
|
-
|
|
63
|
+
structlogger = structlog.get_logger(__name__)
|
|
63
64
|
|
|
64
65
|
# default values of PostgreSQL pool size and max overflow
|
|
65
66
|
POSTGRESQL_DEFAULT_MAX_OVERFLOW = 100
|
|
@@ -315,7 +316,10 @@ class TrackerStore:
|
|
|
315
316
|
async def stream_events(self, tracker: DialogueStateTracker) -> None:
|
|
316
317
|
"""Streams events to a message broker."""
|
|
317
318
|
if self.event_broker is None:
|
|
318
|
-
|
|
319
|
+
structlogger.debug(
|
|
320
|
+
"tracker_store.stream_events.no_broker_configured",
|
|
321
|
+
event_info="No event broker configured. Skipping streaming events.",
|
|
322
|
+
)
|
|
319
323
|
return None
|
|
320
324
|
|
|
321
325
|
old_tracker = await self.retrieve(tracker.sender_id)
|
|
@@ -437,13 +441,22 @@ class InMemoryTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
437
441
|
fetch_all_sessions: Whether to fetch all sessions or only the last one.
|
|
438
442
|
"""
|
|
439
443
|
if sender_id not in self.store:
|
|
440
|
-
|
|
444
|
+
structlogger.debug(
|
|
445
|
+
"in_memory_tracker_store.retrieve.no_tracker_for_sender_id",
|
|
446
|
+
event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
|
|
447
|
+
)
|
|
441
448
|
return None
|
|
442
449
|
|
|
443
450
|
tracker = self.deserialise_tracker(sender_id, self.store[sender_id])
|
|
444
451
|
|
|
445
452
|
if not tracker:
|
|
446
|
-
|
|
453
|
+
structlogger.debug(
|
|
454
|
+
"in_memory_tracker_store.retrieve.failed_to_deserialize_tracker",
|
|
455
|
+
event_info=(
|
|
456
|
+
f"Could not deserialize tracker "
|
|
457
|
+
f"for conversation ID '{sender_id}'.",
|
|
458
|
+
),
|
|
459
|
+
)
|
|
447
460
|
return None
|
|
448
461
|
|
|
449
462
|
if fetch_all_sessions:
|
|
@@ -499,7 +512,10 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
499
512
|
|
|
500
513
|
self.key_prefix = DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
|
|
501
514
|
if key_prefix:
|
|
502
|
-
|
|
515
|
+
structlogger.debug(
|
|
516
|
+
"redis_tracker_store.init.custom_key_prefix",
|
|
517
|
+
event_info=f"Setting non-default redis key prefix: '{key_prefix}'.",
|
|
518
|
+
)
|
|
503
519
|
self._set_key_prefix(key_prefix)
|
|
504
520
|
|
|
505
521
|
super().__init__(domain, event_broker, **kwargs)
|
|
@@ -508,9 +524,13 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
508
524
|
if isinstance(key_prefix, str) and key_prefix.isalnum():
|
|
509
525
|
self.key_prefix = key_prefix + ":" + DEFAULT_REDIS_TRACKER_STORE_KEY_PREFIX
|
|
510
526
|
else:
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
527
|
+
structlogger.warning(
|
|
528
|
+
"redis_tracker_store.init.invalid_key_prefix",
|
|
529
|
+
event_info=(
|
|
530
|
+
f"Omitting provided non-alphanumeric "
|
|
531
|
+
f"redis key prefix: '{key_prefix}'. "
|
|
532
|
+
f"Using default '{self.key_prefix}' instead."
|
|
533
|
+
),
|
|
514
534
|
)
|
|
515
535
|
|
|
516
536
|
def _get_key_prefix(self) -> Text:
|
|
@@ -576,7 +596,10 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
576
596
|
"""
|
|
577
597
|
stored = self.red.get(self.key_prefix + sender_id)
|
|
578
598
|
if stored is None:
|
|
579
|
-
|
|
599
|
+
structlogger.debug(
|
|
600
|
+
"redis_tracker_store.retrieve.no_tracker_for_sender_id",
|
|
601
|
+
event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
|
|
602
|
+
)
|
|
580
603
|
return None
|
|
581
604
|
|
|
582
605
|
tracker = self.deserialise_tracker(sender_id, stored)
|
|
@@ -674,6 +697,31 @@ class DynamoTrackerStore(TrackerStore, SerializedTrackerAsDict):
|
|
|
674
697
|
try:
|
|
675
698
|
self.client.describe_table(TableName=table_name)
|
|
676
699
|
except self.client.exceptions.ResourceNotFoundException:
|
|
700
|
+
sanic_workers_count = int(
|
|
701
|
+
os.environ.get(ENV_SANIC_WORKERS, DEFAULT_SANIC_WORKERS)
|
|
702
|
+
)
|
|
703
|
+
|
|
704
|
+
if sanic_workers_count > 1:
|
|
705
|
+
structlogger.error(
|
|
706
|
+
"dynamo_tracker_store.table_creation_not_supported_in_multi_worker_mode",
|
|
707
|
+
event_info=(
|
|
708
|
+
"DynamoDB table creation is not "
|
|
709
|
+
"supported in multi-worker mode. "
|
|
710
|
+
"Table should already exist.",
|
|
711
|
+
),
|
|
712
|
+
)
|
|
713
|
+
raise RasaException(
|
|
714
|
+
"DynamoDB table creation is not supported in "
|
|
715
|
+
"case of multiple sanic workers. To create the table either "
|
|
716
|
+
"run Rasa with a single worker or create the table manually."
|
|
717
|
+
"Here are the defaults which can be used to "
|
|
718
|
+
"create the table manually: "
|
|
719
|
+
f"Table name: {table_name}, Primary key: sender_id, "
|
|
720
|
+
f"key type `HASH`, attribute type `S` (String), "
|
|
721
|
+
"Provisioned throughput: Read capacity units: 5, "
|
|
722
|
+
"Write capacity units: 5"
|
|
723
|
+
)
|
|
724
|
+
|
|
677
725
|
table = dynamo.create_table(
|
|
678
726
|
TableName=self.table_name,
|
|
679
727
|
KeySchema=[{"AttributeName": "sender_id", "KeyType": "HASH"}],
|
|
@@ -1001,7 +1049,10 @@ def create_engine_kwargs(url: Union[Text, "URL"]) -> Dict[Text, Any]:
|
|
|
1001
1049
|
schema_name = os.environ.get(POSTGRESQL_SCHEMA)
|
|
1002
1050
|
|
|
1003
1051
|
if schema_name:
|
|
1004
|
-
|
|
1052
|
+
structlogger.debug(
|
|
1053
|
+
"postgresql_tracker_store.schema_name",
|
|
1054
|
+
event_inf=f"Using PostgreSQL schema '{schema_name}'.",
|
|
1055
|
+
)
|
|
1005
1056
|
kwargs["connect_args"] = {"options": f"-csearch_path={schema_name}"}
|
|
1006
1057
|
|
|
1007
1058
|
# pool_size and max_overflow can be set to control the number of
|
|
@@ -1114,7 +1165,10 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1114
1165
|
|
|
1115
1166
|
self.engine = sa.create_engine(engine_url, **create_engine_kwargs(engine_url))
|
|
1116
1167
|
|
|
1117
|
-
|
|
1168
|
+
structlogger.debug(
|
|
1169
|
+
"sql_tracker_store.connect_to_sql_database",
|
|
1170
|
+
event_info=f"Attempting to connect to database via '{self.engine.url!r}'.",
|
|
1171
|
+
)
|
|
1118
1172
|
|
|
1119
1173
|
# Database might take a while to come up
|
|
1120
1174
|
while True:
|
|
@@ -1133,7 +1187,11 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1133
1187
|
# Several Rasa services started in parallel may attempt to
|
|
1134
1188
|
# create tables at the same time. That is okay so long as
|
|
1135
1189
|
# the first services finishes the table creation.
|
|
1136
|
-
|
|
1190
|
+
structlogger.error(
|
|
1191
|
+
"sql_tracker_store.create_tables_failed",
|
|
1192
|
+
event_info="Could not create tables",
|
|
1193
|
+
exec_info=e,
|
|
1194
|
+
)
|
|
1137
1195
|
|
|
1138
1196
|
self.sessionmaker = sa.orm.session.sessionmaker(bind=self.engine)
|
|
1139
1197
|
break
|
|
@@ -1141,10 +1199,17 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1141
1199
|
sqlalchemy.exc.OperationalError,
|
|
1142
1200
|
sqlalchemy.exc.IntegrityError,
|
|
1143
1201
|
) as error:
|
|
1144
|
-
|
|
1202
|
+
structlogger.warning(
|
|
1203
|
+
"sql_tracker_store.initialisation_error",
|
|
1204
|
+
event_info="Failed to establish a connection to the SQL database. ",
|
|
1205
|
+
exc_info=error,
|
|
1206
|
+
)
|
|
1145
1207
|
sleep(5)
|
|
1146
1208
|
|
|
1147
|
-
|
|
1209
|
+
structlogger.debug(
|
|
1210
|
+
"sql_tracker_store.connected_to_sql_database",
|
|
1211
|
+
event_info=f"Connection to SQL database '{db}' successful.",
|
|
1212
|
+
)
|
|
1148
1213
|
|
|
1149
1214
|
super().__init__(domain, event_broker, **kwargs)
|
|
1150
1215
|
|
|
@@ -1212,7 +1277,7 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1212
1277
|
"""Creates database `db` and updates engine accordingly."""
|
|
1213
1278
|
from sqlalchemy import create_engine
|
|
1214
1279
|
|
|
1215
|
-
if
|
|
1280
|
+
if self.engine.dialect.name != "postgresql":
|
|
1216
1281
|
rasa.shared.utils.io.raise_warning(
|
|
1217
1282
|
"The parameter 'login_db' can only be used with a postgres database."
|
|
1218
1283
|
)
|
|
@@ -1252,7 +1317,11 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1252
1317
|
sqlalchemy.exc.ProgrammingError,
|
|
1253
1318
|
sqlalchemy.exc.IntegrityError,
|
|
1254
1319
|
) as e:
|
|
1255
|
-
|
|
1320
|
+
structlogger.error(
|
|
1321
|
+
"sql_tracker_store.create_database_failed",
|
|
1322
|
+
event_info=f"Could not create database '{database_name}'",
|
|
1323
|
+
exec_info=e,
|
|
1324
|
+
)
|
|
1256
1325
|
|
|
1257
1326
|
@contextlib.contextmanager
|
|
1258
1327
|
def session_scope(self) -> Generator["Session", None, None]:
|
|
@@ -1316,15 +1385,21 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1316
1385
|
events = [json.loads(event.data) for event in serialised_events]
|
|
1317
1386
|
|
|
1318
1387
|
if self.domain and len(events) > 0:
|
|
1319
|
-
|
|
1388
|
+
structlogger.debug(
|
|
1389
|
+
"sql_tracker_store.recreating_tracker",
|
|
1390
|
+
event_info=f"Recreating tracker from sender id '{sender_id}'",
|
|
1391
|
+
)
|
|
1320
1392
|
return DialogueStateTracker.from_dict(
|
|
1321
1393
|
sender_id, events, self.domain.slots
|
|
1322
1394
|
)
|
|
1323
1395
|
else:
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1396
|
+
structlogger.debug(
|
|
1397
|
+
"sql_tracker_store._retrieve.no_tracker_for_sender_id",
|
|
1398
|
+
event_info=(
|
|
1399
|
+
f"Can't retrieve tracker matching "
|
|
1400
|
+
f"sender id '{sender_id}' from SQL storage. "
|
|
1401
|
+
f"Returning `None` instead.",
|
|
1402
|
+
),
|
|
1328
1403
|
)
|
|
1329
1404
|
return None
|
|
1330
1405
|
|
|
@@ -1401,7 +1476,12 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
|
|
|
1401
1476
|
)
|
|
1402
1477
|
session.commit()
|
|
1403
1478
|
|
|
1404
|
-
|
|
1479
|
+
structlogger.debug(
|
|
1480
|
+
"sql_tracker_store.save_tracker",
|
|
1481
|
+
event_info=(
|
|
1482
|
+
f"Tracker with sender_id " f"'{tracker.sender_id}' stored to database",
|
|
1483
|
+
),
|
|
1484
|
+
)
|
|
1405
1485
|
|
|
1406
1486
|
def _additional_events(
|
|
1407
1487
|
self, session: "Session", tracker: DialogueStateTracker
|
|
@@ -1469,11 +1549,14 @@ class FailSafeTrackerStore(TrackerStore):
|
|
|
1469
1549
|
if self._on_tracker_store_error:
|
|
1470
1550
|
self._on_tracker_store_error(error)
|
|
1471
1551
|
else:
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1552
|
+
structlogger.error(
|
|
1553
|
+
"fail_safe_tracker_store.tracker_store_error",
|
|
1554
|
+
event_info=(
|
|
1555
|
+
f"Error happened when trying to save conversation tracker to "
|
|
1556
|
+
f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
|
|
1557
|
+
f"the '{InMemoryTrackerStore.__name__}'. Please "
|
|
1558
|
+
f"investigate the following error: {error}."
|
|
1559
|
+
),
|
|
1477
1560
|
)
|
|
1478
1561
|
|
|
1479
1562
|
async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
|
|
@@ -1525,11 +1608,14 @@ class FailSafeTrackerStore(TrackerStore):
|
|
|
1525
1608
|
if self._on_tracker_store_error:
|
|
1526
1609
|
self._on_tracker_store_error(error)
|
|
1527
1610
|
else:
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1611
|
+
structlogger.error(
|
|
1612
|
+
"fail_safe_tracker_store.tracker_store_retrieve_error",
|
|
1613
|
+
event_info=(
|
|
1614
|
+
f"Error happened when trying to retrieve conversation tracker from "
|
|
1615
|
+
f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
|
|
1616
|
+
f"the '{InMemoryTrackerStore.__name__}'."
|
|
1617
|
+
),
|
|
1618
|
+
exec_info=error,
|
|
1533
1619
|
)
|
|
1534
1620
|
|
|
1535
1621
|
|
|
@@ -1574,7 +1660,10 @@ def _create_from_endpoint_config(
|
|
|
1574
1660
|
domain, endpoint_config, event_broker
|
|
1575
1661
|
)
|
|
1576
1662
|
|
|
1577
|
-
|
|
1663
|
+
structlogger.debug(
|
|
1664
|
+
"tracker_store.create_tracker_store_from_endpoint_config",
|
|
1665
|
+
eventi_info=f"Connected to {tracker_store.__class__.__name__}.",
|
|
1666
|
+
)
|
|
1578
1667
|
|
|
1579
1668
|
return tracker_store
|
|
1580
1669
|
|
|
@@ -99,7 +99,7 @@ class CannotHandleCommand(Command):
|
|
|
99
99
|
def regex_pattern() -> str:
|
|
100
100
|
mapper = {
|
|
101
101
|
CommandSyntaxVersion.v1: r"CannotHandle\(\)",
|
|
102
|
-
CommandSyntaxVersion.v2: r"^[
|
|
102
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*cannot handle['"`]*$""",
|
|
103
103
|
}
|
|
104
104
|
return mapper.get(
|
|
105
105
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -166,7 +166,7 @@ class CancelFlowCommand(Command):
|
|
|
166
166
|
def regex_pattern() -> str:
|
|
167
167
|
mapper = {
|
|
168
168
|
CommandSyntaxVersion.v1: r"CancelFlow\(\)",
|
|
169
|
-
CommandSyntaxVersion.v2: r"^[
|
|
169
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*cancel flow['"`]*$""",
|
|
170
170
|
}
|
|
171
171
|
return mapper.get(
|
|
172
172
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -70,7 +70,7 @@ class ChangeFlowCommand(Command):
|
|
|
70
70
|
def regex_pattern() -> str:
|
|
71
71
|
mapper = {
|
|
72
72
|
CommandSyntaxVersion.v1: r"ChangeFlow\(\)",
|
|
73
|
-
CommandSyntaxVersion.v2: r"^[
|
|
73
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*change['"`]*$""",
|
|
74
74
|
}
|
|
75
75
|
return mapper.get(
|
|
76
76
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -81,7 +81,7 @@ class ChitChatAnswerCommand(FreeFormAnswerCommand):
|
|
|
81
81
|
def regex_pattern() -> str:
|
|
82
82
|
mapper = {
|
|
83
83
|
CommandSyntaxVersion.v1: r"ChitChat\(\)",
|
|
84
|
-
CommandSyntaxVersion.v2: r"^[
|
|
84
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*offtopic reply['"`]*$""",
|
|
85
85
|
}
|
|
86
86
|
return mapper.get(
|
|
87
87
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -119,7 +119,7 @@ class ClarifyCommand(Command):
|
|
|
119
119
|
mapper = {
|
|
120
120
|
CommandSyntaxVersion.v1: r"Clarify\(([\"\'a-zA-Z0-9_, ]*)\)",
|
|
121
121
|
CommandSyntaxVersion.v2: (
|
|
122
|
-
r"^[
|
|
122
|
+
r"""^[\s\W\d]*disambiguate flows (["'a-zA-Z0-9_, ]*)['"`]*$"""
|
|
123
123
|
),
|
|
124
124
|
}
|
|
125
125
|
return mapper.get(
|
|
@@ -30,7 +30,7 @@ class CommandSyntaxManager:
|
|
|
30
30
|
syntax version remains consistent throughout the lifetime of the generator.
|
|
31
31
|
"""
|
|
32
32
|
if cls._version:
|
|
33
|
-
structlogger.
|
|
33
|
+
structlogger.debug(
|
|
34
34
|
"command_syntax_manager.syntax_version_already_set",
|
|
35
35
|
event_info=(
|
|
36
36
|
f"The command syntax version has already been set. Overwriting "
|
|
@@ -14,7 +14,6 @@ from rasa.dialogue_understanding.patterns.handle_digressions import (
|
|
|
14
14
|
)
|
|
15
15
|
from rasa.dialogue_understanding.stack.utils import (
|
|
16
16
|
top_flow_frame,
|
|
17
|
-
user_flows_on_the_stack,
|
|
18
17
|
)
|
|
19
18
|
from rasa.shared.core.events import Event
|
|
20
19
|
from rasa.shared.core.flows import FlowsList
|
|
@@ -71,12 +70,7 @@ class HandleDigressionsCommand(Command):
|
|
|
71
70
|
stack = tracker.stack
|
|
72
71
|
original_stack = original_tracker.stack
|
|
73
72
|
|
|
74
|
-
if self.flow in
|
|
75
|
-
structlogger.debug(
|
|
76
|
-
"command_executor.skip_command.already_started_flow", command=self
|
|
77
|
-
)
|
|
78
|
-
return []
|
|
79
|
-
elif self.flow not in all_flows.flow_ids:
|
|
73
|
+
if self.flow not in all_flows.flow_ids:
|
|
80
74
|
structlogger.debug(
|
|
81
75
|
"command_executor.push_cannot_handle.start_invalid_flow_id",
|
|
82
76
|
command=self,
|
|
@@ -88,7 +88,7 @@ class HumanHandoffCommand(Command):
|
|
|
88
88
|
def regex_pattern() -> str:
|
|
89
89
|
mapper = {
|
|
90
90
|
CommandSyntaxVersion.v1: r"HumanHandoff\(\)",
|
|
91
|
-
CommandSyntaxVersion.v2: r"^[
|
|
91
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*hand over['"`]*$""",
|
|
92
92
|
}
|
|
93
93
|
return mapper.get(
|
|
94
94
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -81,7 +81,7 @@ class KnowledgeAnswerCommand(FreeFormAnswerCommand):
|
|
|
81
81
|
def regex_pattern() -> str:
|
|
82
82
|
mapper = {
|
|
83
83
|
CommandSyntaxVersion.v1: r"SearchAndReply\(\)",
|
|
84
|
-
CommandSyntaxVersion.v2: r"^[
|
|
84
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*provide info['"`]*$""",
|
|
85
85
|
}
|
|
86
86
|
return mapper.get(
|
|
87
87
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -82,7 +82,7 @@ class RepeatBotMessagesCommand(Command):
|
|
|
82
82
|
def regex_pattern() -> str:
|
|
83
83
|
mapper = {
|
|
84
84
|
CommandSyntaxVersion.v1: r"RepeatLastBotMessages\(\)",
|
|
85
|
-
CommandSyntaxVersion.v2: r"^[
|
|
85
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*repeat message['"`]*$""",
|
|
86
86
|
}
|
|
87
87
|
return mapper.get(
|
|
88
88
|
CommandSyntaxManager.get_syntax_version(),
|
|
@@ -126,6 +126,7 @@ class SetSlotCommand(Command):
|
|
|
126
126
|
if (
|
|
127
127
|
self.name not in slots_of_active_flow
|
|
128
128
|
and self.name != ROUTE_TO_CALM_SLOT
|
|
129
|
+
and not slot.is_builtin
|
|
129
130
|
and self.extractor
|
|
130
131
|
in {
|
|
131
132
|
SetSlotExtractor.LLM.value,
|
|
@@ -189,7 +190,7 @@ class SetSlotCommand(Command):
|
|
|
189
190
|
r"""SetSlot\(['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]?, ?['"]?(.*)['"]?\)"""
|
|
190
191
|
),
|
|
191
192
|
CommandSyntaxVersion.v2: (
|
|
192
|
-
r"""^[
|
|
193
|
+
r"""^[\s\W\d]*set slot ['"`]?([a-zA-Z_][a-zA-Z0-9_-]*)['"`]? ['"`]?(.+?)['"`]*$""" # noqa: E501
|
|
193
194
|
),
|
|
194
195
|
}
|
|
195
196
|
return mapper.get(
|
|
@@ -97,7 +97,7 @@ class SkipQuestionCommand(Command):
|
|
|
97
97
|
def regex_pattern() -> str:
|
|
98
98
|
mapper = {
|
|
99
99
|
CommandSyntaxVersion.v1: r"SkipQuestion\(\)",
|
|
100
|
-
CommandSyntaxVersion.v2: r"^[
|
|
100
|
+
CommandSyntaxVersion.v2: r"""^[\s\W\d]*skip question['"`]*$""",
|
|
101
101
|
}
|
|
102
102
|
return mapper.get(
|
|
103
103
|
CommandSyntaxManager.get_syntax_version(),
|