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.

Files changed (70) hide show
  1. README.md +10 -13
  2. rasa/cli/dialogue_understanding_test.py +5 -8
  3. rasa/cli/llm_fine_tuning.py +47 -12
  4. rasa/cli/project_templates/calm/domain/list_contacts.yml +1 -2
  5. rasa/cli/project_templates/calm/domain/remove_contact.yml +1 -2
  6. rasa/cli/project_templates/calm/domain/shared.yml +1 -4
  7. rasa/core/actions/action_handle_digressions.py +35 -13
  8. rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
  9. rasa/core/channels/voice_stream/audiocodes.py +19 -6
  10. rasa/core/channels/voice_stream/call_state.py +3 -9
  11. rasa/core/channels/voice_stream/genesys.py +40 -55
  12. rasa/core/channels/voice_stream/voice_channel.py +61 -39
  13. rasa/core/policies/flows/flow_executor.py +7 -2
  14. rasa/core/processor.py +0 -1
  15. rasa/core/tracker_store.py +123 -34
  16. rasa/dialogue_understanding/commands/can_not_handle_command.py +1 -1
  17. rasa/dialogue_understanding/commands/cancel_flow_command.py +1 -1
  18. rasa/dialogue_understanding/commands/change_flow_command.py +1 -1
  19. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +1 -1
  20. rasa/dialogue_understanding/commands/clarify_command.py +1 -1
  21. rasa/dialogue_understanding/commands/command_syntax_manager.py +1 -1
  22. rasa/dialogue_understanding/commands/handle_digressions_command.py +1 -7
  23. rasa/dialogue_understanding/commands/human_handoff_command.py +1 -1
  24. rasa/dialogue_understanding/commands/knowledge_answer_command.py +1 -1
  25. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +1 -1
  26. rasa/dialogue_understanding/commands/set_slot_command.py +2 -1
  27. rasa/dialogue_understanding/commands/skip_question_command.py +1 -1
  28. rasa/dialogue_understanding/commands/start_flow_command.py +3 -1
  29. rasa/dialogue_understanding/commands/utils.py +2 -32
  30. rasa/dialogue_understanding/generator/command_parser.py +41 -0
  31. rasa/dialogue_understanding/generator/constants.py +7 -2
  32. rasa/dialogue_understanding/generator/llm_based_command_generator.py +9 -2
  33. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +1 -1
  34. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +29 -48
  35. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_fallback_other_models_template.jinja2 +57 -0
  36. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +23 -50
  37. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +141 -27
  38. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +32 -18
  39. rasa/dialogue_understanding/processor/command_processor.py +43 -23
  40. rasa/dialogue_understanding/stack/utils.py +49 -6
  41. rasa/dialogue_understanding_test/du_test_case.py +30 -10
  42. rasa/dialogue_understanding_test/du_test_result.py +1 -1
  43. rasa/e2e_test/assertions.py +6 -8
  44. rasa/e2e_test/llm_judge_prompts/answer_relevance_prompt_template.jinja2 +5 -1
  45. rasa/e2e_test/llm_judge_prompts/groundedness_prompt_template.jinja2 +4 -0
  46. rasa/engine/language.py +67 -25
  47. rasa/llm_fine_tuning/conversations.py +3 -31
  48. rasa/llm_fine_tuning/llm_data_preparation_module.py +5 -3
  49. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +18 -13
  50. rasa/llm_fine_tuning/paraphrasing_module.py +6 -2
  51. rasa/llm_fine_tuning/train_test_split_module.py +27 -27
  52. rasa/llm_fine_tuning/utils.py +7 -0
  53. rasa/shared/constants.py +4 -0
  54. rasa/shared/core/domain.py +2 -0
  55. rasa/shared/core/slots.py +6 -0
  56. rasa/shared/providers/_configs/azure_entra_id_config.py +8 -8
  57. rasa/shared/providers/llm/litellm_router_llm_client.py +1 -0
  58. rasa/shared/providers/llm/openai_llm_client.py +2 -2
  59. rasa/shared/providers/router/_base_litellm_router_client.py +38 -7
  60. rasa/shared/utils/llm.py +69 -10
  61. rasa/telemetry.py +13 -3
  62. rasa/tracing/instrumentation/attribute_extractors.py +2 -5
  63. rasa/validator.py +2 -2
  64. rasa/version.py +1 -1
  65. {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/METADATA +12 -14
  66. {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/RECORD +69 -68
  67. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_default.jinja2 +0 -68
  68. {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/NOTICE +0 -0
  69. {rasa_pro-3.12.0rc1.dist-info → rasa_pro-3.12.0rc3.dist-info}/WHEEL +0 -0
  70. {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
- try:
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, and keep collecting and caching audio bytes
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 handle_silence_timeout(
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.handle_silence_timeout(
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 consume_asr_events() -> None:
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
- audio_forwarding_task = asyncio.create_task(consume_audio_bytes())
404
- asr_event_task = asyncio.create_task(consume_asr_events())
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
- [audio_forwarding_task, asr_event_task],
419
+ tasks,
407
420
  return_when=asyncio.FIRST_COMPLETED,
408
421
  )
409
- if not audio_forwarding_task.done():
410
- audio_forwarding_task.cancel()
411
- if not asr_event_task.done():
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 == FlowStackFrameType.CALL:
291
- # we want to return to the flow that called the current flow
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
@@ -1553,7 +1553,6 @@ class MessageProcessor:
1553
1553
  tracker,
1554
1554
  prior_tracker_events,
1555
1555
  should_break=True,
1556
- update_corrected_slots=True,
1557
1556
  )
1558
1557
 
1559
1558
  return tracker, validate_frames
@@ -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
- logger = logging.getLogger(__name__)
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
- logger.debug("No event broker configured. Skipping streaming events.")
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Setting non-default redis key prefix: '{key_prefix}'.")
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
- logger.warning(
512
- f"Omitting provided non-alphanumeric redis key prefix: '{key_prefix}'. "
513
- f"Using default '{self.key_prefix}' instead."
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
- logger.debug(f"Could not find tracker for conversation ID '{sender_id}'.")
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
- logger.debug(f"Using PostgreSQL schema '{schema_name}'.")
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
- logger.debug(f"Attempting to connect to database via '{self.engine.url!r}'.")
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
- logger.error(f"Could not create tables: {e}")
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
- logger.warning(error)
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
- logger.debug(f"Connection to SQL database '{db}' successful.")
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 not self.engine.dialect.name == "postgresql":
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
- logger.error(f"Could not create database '{database_name}': {e}")
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
- logger.debug(f"Recreating tracker from sender id '{sender_id}'")
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
- logger.debug(
1325
- f"Can't retrieve tracker matching "
1326
- f"sender id '{sender_id}' from SQL storage. "
1327
- f"Returning `None` instead."
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
- logger.debug(f"Tracker with sender_id '{tracker.sender_id}' stored to database")
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
- logger.error(
1473
- f"Error happened when trying to save conversation tracker to "
1474
- f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
1475
- f"the '{InMemoryTrackerStore.__name__}'. Please "
1476
- f"investigate the following error: {error}."
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
- logger.error(
1529
- f"Error happened when trying to retrieve conversation tracker from "
1530
- f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
1531
- f"the '{InMemoryTrackerStore.__name__}'. Please "
1532
- f"investigate the following error: {error}."
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
- logger.debug(f"Connected to {tracker_store.__class__.__name__}.")
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"^[^\w]*cannot handle$",
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"^[^\w]*cancel flow$",
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"^[^\w]*change$",
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"^[^\w]*offtopic reply$",
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"^[^\w]*disambiguate flows ([\"\'a-zA-Z0-9_, ]*)$"
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.warn(
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 user_flows_on_the_stack(stack):
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"^[^\w]*hand over$",
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"^[^\w]*provide info$",
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"^[^\w]*repeat message$",
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"""^[^\w]*set slot ['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]? ['"]?(.+?)['"]?$""" # noqa: E501
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"^[^\w]*skip question$",
100
+ CommandSyntaxVersion.v2: r"""^[\s\W\d]*skip question['"`]*$""",
101
101
  }
102
102
  return mapper.get(
103
103
  CommandSyntaxManager.get_syntax_version(),