rasa-pro 3.13.0rc3__py3-none-any.whl → 3.13.1a2__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 (67) hide show
  1. rasa/builder/README.md +120 -0
  2. rasa/builder/__init__.py +0 -0
  3. rasa/builder/config.py +69 -0
  4. rasa/builder/create_openai_vector_store.py +228 -0
  5. rasa/builder/exceptions.py +49 -0
  6. rasa/builder/llm-helper-schema.json +69 -0
  7. rasa/builder/llm_context.py +81 -0
  8. rasa/builder/llm_helper_prompt.jinja2 +245 -0
  9. rasa/builder/llm_service.py +327 -0
  10. rasa/builder/logging_utils.py +51 -0
  11. rasa/builder/main.py +61 -0
  12. rasa/builder/models.py +174 -0
  13. rasa/builder/project_generator.py +264 -0
  14. rasa/builder/scrape_rasa_docs.py +97 -0
  15. rasa/builder/service.py +447 -0
  16. rasa/builder/skill_to_bot_prompt.jinja2 +164 -0
  17. rasa/builder/training_service.py +123 -0
  18. rasa/builder/validation_service.py +79 -0
  19. rasa/cli/project_templates/finance/config.yml +17 -0
  20. rasa/cli/project_templates/finance/credentials.yml +33 -0
  21. rasa/cli/project_templates/finance/data/flows/transfer_money.yml +5 -0
  22. rasa/cli/project_templates/finance/data/patterns/pattern_session_start.yml +7 -0
  23. rasa/cli/project_templates/finance/domain.yml +7 -0
  24. rasa/cli/project_templates/finance/endpoints.yml +58 -0
  25. rasa/cli/project_templates/plain/config.yml +17 -0
  26. rasa/cli/project_templates/plain/credentials.yml +33 -0
  27. rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +7 -0
  28. rasa/cli/project_templates/plain/domain.yml +5 -0
  29. rasa/cli/project_templates/plain/endpoints.yml +58 -0
  30. rasa/cli/project_templates/telecom/config.yml +17 -0
  31. rasa/cli/project_templates/telecom/credentials.yml +33 -0
  32. rasa/cli/project_templates/telecom/data/flows/upgrade_contract.yml +5 -0
  33. rasa/cli/project_templates/telecom/data/patterns/pattern_session_start.yml +7 -0
  34. rasa/cli/project_templates/telecom/domain.yml +7 -0
  35. rasa/cli/project_templates/telecom/endpoints.yml +58 -0
  36. rasa/cli/scaffold.py +19 -3
  37. rasa/core/actions/action.py +5 -3
  38. rasa/core/channels/studio_chat.py +29 -8
  39. rasa/core/policies/flows/flow_executor.py +8 -1
  40. rasa/core/tracker_stores/auth_retry_tracker_store.py +64 -3
  41. rasa/core/tracker_stores/dynamo_tracker_store.py +10 -0
  42. rasa/core/tracker_stores/mongo_tracker_store.py +17 -0
  43. rasa/core/tracker_stores/redis_tracker_store.py +23 -0
  44. rasa/core/tracker_stores/sql_tracker_store.py +27 -0
  45. rasa/core/tracker_stores/tracker_store.py +36 -2
  46. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +1 -1
  47. rasa/model_manager/model_api.py +2 -2
  48. rasa/model_manager/runner_service.py +1 -1
  49. rasa/model_manager/trainer_service.py +12 -9
  50. rasa/model_manager/utils.py +1 -29
  51. rasa/privacy/privacy_manager.py +19 -16
  52. rasa/shared/core/domain.py +62 -15
  53. rasa/shared/core/flows/flow_step.py +7 -1
  54. rasa/shared/core/flows/yaml_flows_io.py +16 -8
  55. rasa/shared/core/slots.py +4 -0
  56. rasa/shared/importers/importer.py +6 -0
  57. rasa/shared/importers/static.py +63 -0
  58. rasa/telemetry.py +2 -1
  59. rasa/utils/io.py +27 -9
  60. rasa/utils/log_utils.py +5 -1
  61. rasa/validator.py +7 -3
  62. rasa/version.py +1 -1
  63. {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/METADATA +3 -3
  64. {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/RECORD +67 -31
  65. {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/NOTICE +0 -0
  66. {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/WHEEL +0 -0
  67. {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/entry_points.txt +0 -0
@@ -49,7 +49,7 @@ if TYPE_CHECKING:
49
49
  structlogger = structlog.get_logger()
50
50
 
51
51
 
52
- def tracker_as_dump(tracker: "DialogueStateTracker") -> str:
52
+ def tracker_as_dump(tracker: "DialogueStateTracker") -> Dict[str, Any]:
53
53
  """Create a dump of the tracker state."""
54
54
  from rasa.shared.core.trackers import get_trackers_for_conversation_sessions
55
55
 
@@ -60,8 +60,9 @@ def tracker_as_dump(tracker: "DialogueStateTracker") -> str:
60
60
  else:
61
61
  last_tracker = multiple_tracker_sessions[-1]
62
62
 
63
- state = last_tracker.current_state(EventVerbosity.AFTER_RESTART)
64
- return json.dumps(state)
63
+ # TODO: this is a bug: the bridge converts this back to json, but it
64
+ # should be json in the first place
65
+ return last_tracker.current_state(EventVerbosity.AFTER_RESTART)
65
66
 
66
67
 
67
68
  def does_need_action_prediction(tracker: "DialogueStateTracker") -> bool:
@@ -148,6 +149,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
148
149
  from rasa.core.agent import Agent
149
150
 
150
151
  self.agent: Optional[Agent] = None
152
+ self.latest_tracker_session_id = None
151
153
 
152
154
  # Initialize the SocketIO input channel
153
155
  SocketIOInput.__init__(
@@ -211,6 +213,11 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
211
213
  if not self.sio:
212
214
  structlogger.error("studio_chat.on_tracker_updated.sio_not_initialized")
213
215
  return
216
+
217
+ # we need the latest session id to use it for the llm helper to get the
218
+ # most recent conversation the user had with the bot.
219
+ self.latest_tracker_session_id = sender_id
220
+
214
221
  await self.sio.emit("tracker", tracker_dump, room=sender_id)
215
222
 
216
223
  async def on_message_proxy(
@@ -222,7 +229,14 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
222
229
 
223
230
  Triggers a tracker update notification after processing the message.
224
231
  """
225
- await on_new_message(message)
232
+ try:
233
+ await on_new_message(message)
234
+ except Exception as e:
235
+ structlogger.exception(
236
+ "studio_chat.on_new_message.error",
237
+ error=str(e),
238
+ sender_id=message.sender_id,
239
+ )
226
240
 
227
241
  if not self.agent:
228
242
  structlogger.error("studio_chat.on_message_proxy.agent_not_initialized")
@@ -275,7 +289,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
275
289
  async def collect_call_parameters(
276
290
  self, channel_websocket: "Websocket"
277
291
  ) -> Optional[CallParameters]:
278
- """Voice method to collect call parameters"""
292
+ """Voice method to collect call parameters."""
279
293
  session_id = channel_websocket.session_id
280
294
  return CallParameters(session_id, "local", "local", stream_id=session_id)
281
295
 
@@ -305,7 +319,7 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
305
319
  def create_output_channel(
306
320
  self, voice_websocket: "Websocket", tts_engine: TTSEngine
307
321
  ) -> VoiceOutputChannel:
308
- """Create a voice output channel"""
322
+ """Create a voice output channel."""
309
323
  return StudioVoiceOutputChannel(
310
324
  voice_websocket,
311
325
  tts_engine,
@@ -432,8 +446,15 @@ class StudioChatInput(SocketIOInput, VoiceInputChannel):
432
446
  ws.put_message(data)
433
447
  return
434
448
 
435
- # Handle text messages
436
- await self.handle_user_message(sid, data, on_new_message)
449
+ try:
450
+ # Handle text messages
451
+ await self.handle_user_message(sid, data, on_new_message)
452
+ except Exception as e:
453
+ structlogger.exception(
454
+ "studio_chat.sio.handle_message.error",
455
+ error=str(e),
456
+ sid=sid,
457
+ )
437
458
 
438
459
  @self.sio.on("update_tracker", namespace=self.namespace)
439
460
  async def on_update_tracker(sid: Text, data: Dict) -> None:
@@ -740,7 +740,14 @@ def _run_action_step(
740
740
  # do not log about non-existing validation actions of collect steps
741
741
  utter_action_name = render_template_variables("{{context.utter}}", context)
742
742
  if utter_action_name not in available_actions:
743
- structlogger.warning("flow.step.run.action.unknown", action=action_name)
743
+ structlogger.warning(
744
+ "flow.step.run.action.unknown",
745
+ action=action_name,
746
+ event_info=(
747
+ f"The action '{action_name}' is not defined in the domain but "
748
+ f"getting triggered by the flow '{step.flow_id}'."
749
+ ),
750
+ )
744
751
  return ContinueFlowWithNextStep(events=initial_events)
745
752
 
746
753
 
@@ -92,6 +92,29 @@ class AuthRetryTrackerStore(TrackerStore):
92
92
  )
93
93
  return None
94
94
 
95
+ async def retrieve_full_tracker(
96
+ self, sender_id: Text
97
+ ) -> Optional["DialogueStateTracker"]:
98
+ """Retries retrieving the full tracker if it fails."""
99
+ # add + 1 to retries because the retries are additional to the first attempt
100
+ for _ in range(self.retries + 1):
101
+ try:
102
+ return await self._tracker_store.retrieve_full_tracker(sender_id)
103
+ except Exception as e:
104
+ logger.warning(
105
+ f"Failed to retrieve full tracker for {sender_id}. Retrying...",
106
+ exc_info=e,
107
+ )
108
+ self._tracker_store = self.recreate_tracker_store(
109
+ self.domain, self.event_broker
110
+ )
111
+
112
+ logger.error(
113
+ f"Failed to retrieve full tracker for {sender_id} "
114
+ f"after {self.retries} retries."
115
+ )
116
+ return None
117
+
95
118
  async def save(self, tracker: "DialogueStateTracker") -> None:
96
119
  """Retries saving the tracker if it fails."""
97
120
  # add + 1 to retries because the retries are additional to the first attempt
@@ -120,6 +143,44 @@ class AuthRetryTrackerStore(TrackerStore):
120
143
  endpoint_config = EndpointResolver.update_config(self.endpoint_config)
121
144
  return create_tracker_store(endpoint_config, domain, event_broker)
122
145
 
123
- async def delete(self, sender_id: Text) -> None:
124
- """Delete tracker for the given sender_id."""
125
- await self._tracker_store.delete(sender_id)
146
+ async def delete(self, sender_id: str) -> None:
147
+ """Retries deleting the tracker for the given sender_id."""
148
+ # add + 1 to retries because the retries are additional to the first attempt
149
+ for _ in range(self.retries + 1):
150
+ try:
151
+ await self._tracker_store.delete(sender_id)
152
+ break
153
+ except Exception as e:
154
+ logger.warning(
155
+ f"Failed to delete tracker for {sender_id}. Retrying...",
156
+ exc_info=e,
157
+ )
158
+ self._tracker_store = self.recreate_tracker_store(
159
+ self.domain, self.event_broker
160
+ )
161
+ else:
162
+ logger.error(
163
+ f"Failed to delete tracker for {sender_id} "
164
+ f"after {self.retries} retries."
165
+ )
166
+
167
+ async def update(self, tracker: DialogueStateTracker) -> None:
168
+ """Retries replacing the tracker if it fails."""
169
+ # add + 1 to retries because the retries are additional to the first attempt
170
+ for _ in range(self.retries + 1):
171
+ try:
172
+ await self._tracker_store.update(tracker)
173
+ break
174
+ except Exception as e:
175
+ logger.warning(
176
+ f"Failed to replace tracker for {tracker.sender_id}. Retrying...",
177
+ exc_info=e,
178
+ )
179
+ self._tracker_store = self.recreate_tracker_store(
180
+ self.domain, self.event_broker
181
+ )
182
+ else:
183
+ logger.error(
184
+ f"Failed to replace tracker for {tracker.sender_id} "
185
+ f"after {self.retries} retries."
186
+ )
@@ -216,3 +216,13 @@ class DynamoTrackerStore(TrackerStore, SerializedTrackerAsDict):
216
216
  sender_ids.extend([i["sender_id"] for i in response["Items"]])
217
217
 
218
218
  return sender_ids
219
+
220
+ async def update(self, tracker: DialogueStateTracker) -> None:
221
+ """Overwrites the tracker for the given sender_id."""
222
+ serialized = self.serialise_tracker(tracker)
223
+ self.db.put_item(Item=serialized)
224
+
225
+ structlogger.info(
226
+ "dynamo_tracker_store.replace.replaced_tracker",
227
+ sender_id=tracker.sender_id,
228
+ )
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import itertools
4
+ from datetime import datetime
4
5
  from typing import Any, Dict, Iterable, Iterator, List, Optional, Text
5
6
 
6
7
  import structlog
@@ -204,3 +205,19 @@ class MongoTrackerStore(TrackerStore, SerializedTrackerAsText):
204
205
  async def keys(self) -> Iterable[Text]:
205
206
  """Returns sender_ids of the Mongo Tracker Store."""
206
207
  return [c["sender_id"] for c in self.conversations.find()]
208
+
209
+ async def update(self, tracker: DialogueStateTracker) -> None:
210
+ """Overwrites the tracker for the given sender_id."""
211
+ self.conversations.replace_one(
212
+ {"sender_id": tracker.sender_id},
213
+ tracker.current_state(EventVerbosity.ALL),
214
+ upsert=True,
215
+ )
216
+
217
+ first_event_timestamp = str(datetime.fromtimestamp(tracker.events[0].timestamp))
218
+
219
+ structlogger.info(
220
+ "redis_tracker_store.update.updated_tracker",
221
+ sender_id=tracker.sender_id,
222
+ first_event_timestamp=first_event_timestamp,
223
+ )
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from datetime import datetime
3
4
  from typing import Any, Dict, Iterable, Optional, Text
4
5
 
5
6
  import structlog
@@ -227,3 +228,25 @@ class RedisTrackerStore(TrackerStore, SerializedTrackerAsText):
227
228
  merged.update(new_event)
228
229
 
229
230
  return merged
231
+
232
+ async def update(self, tracker: DialogueStateTracker) -> None:
233
+ """Overwrites the tracker for the given sender_id."""
234
+ serialised_tracker = self.serialise_tracker(tracker)
235
+
236
+ # if the sender_id starts with the key prefix, we remove it
237
+ # this is used to avoid storing the prefix twice
238
+ sender_id = tracker.sender_id
239
+ if sender_id.startswith(self.key_prefix):
240
+ sender_id = sender_id[len(self.key_prefix) :]
241
+
242
+ self.red.set(
243
+ self.key_prefix + sender_id, serialised_tracker, ex=self.record_exp
244
+ )
245
+
246
+ first_event_timestamp = str(datetime.fromtimestamp(tracker.events[0].timestamp))
247
+
248
+ structlogger.info(
249
+ "redis_tracker_store.update.updated_tracker",
250
+ sender_id=tracker.sender_id,
251
+ first_event_timestamp=first_event_timestamp,
252
+ )
@@ -4,6 +4,7 @@ import contextlib
4
4
  import itertools
5
5
  import json
6
6
  import os
7
+ from datetime import datetime
7
8
  from time import sleep
8
9
  from typing import (
9
10
  TYPE_CHECKING,
@@ -553,3 +554,29 @@ class SQLTrackerStore(TrackerStore, SerializedTrackerAsText):
553
554
  return itertools.islice(
554
555
  tracker.events, number_of_events_since_last_session, len(tracker.events)
555
556
  )
557
+
558
+ async def update(self, tracker_to_keep: DialogueStateTracker) -> None:
559
+ """Overwrite the tracker in the SQL tracker store."""
560
+ with self.session_scope() as session:
561
+ # Delete events whose timestamp are older
562
+ # than the first event of the tracker to keep.
563
+ statement = sa.delete(self.SQLEvent).where(
564
+ self.SQLEvent.sender_id == tracker_to_keep.sender_id,
565
+ self.SQLEvent.timestamp < tracker_to_keep.events[0].timestamp
566
+ if tracker_to_keep.events
567
+ else 0,
568
+ )
569
+
570
+ result = session.execute(statement)
571
+ session.commit()
572
+
573
+ first_event_timestamp = str(
574
+ datetime.fromtimestamp(tracker_to_keep.events[0].timestamp)
575
+ )
576
+
577
+ structlogger.info(
578
+ "sql_tracker_store.update.updated_tracker",
579
+ sender_id=tracker_to_keep.sender_id,
580
+ first_event_timestamp=first_event_timestamp,
581
+ event_info=f"{result.rowcount} rows removed from tracker.",
582
+ )
@@ -251,6 +251,14 @@ class TrackerStore:
251
251
  """
252
252
  raise NotImplementedError()
253
253
 
254
+ async def update(self, tracker: DialogueStateTracker) -> None:
255
+ """Replace an existing tracker with a new one.
256
+
257
+ Args:
258
+ tracker: The tracker to update.
259
+ """
260
+ raise NotImplementedError()
261
+
254
262
  async def retrieve_full_tracker(
255
263
  self, conversation_id: Text
256
264
  ) -> Optional[DialogueStateTracker]:
@@ -478,6 +486,14 @@ class InMemoryTrackerStore(TrackerStore, SerializedTrackerAsText):
478
486
 
479
487
  return multiple_tracker_sessions[-1]
480
488
 
489
+ async def update(self, tracker: DialogueStateTracker) -> None:
490
+ """Replace an existing tracker with a new one.
491
+
492
+ Args:
493
+ tracker: The tracker to update.
494
+ """
495
+ await self.save(tracker)
496
+
481
497
 
482
498
  def validate_port(port: Any) -> Optional[int]:
483
499
  """Ensure that port can be converted to integer.
@@ -583,7 +599,19 @@ class FailSafeTrackerStore(TrackerStore):
583
599
 
584
600
  async def delete(self, sender_id: Text) -> None:
585
601
  """Delete tracker for the given sender_id."""
586
- await self._tracker_store.delete(sender_id)
602
+ try:
603
+ await self._tracker_store.delete(sender_id)
604
+ except Exception as e:
605
+ self.on_tracker_store_error(e)
606
+ await self.fallback_tracker_store.delete(sender_id)
607
+
608
+ async def update(self, tracker: DialogueStateTracker) -> None:
609
+ """Replace an existing tracker with a new one."""
610
+ try:
611
+ await self._tracker_store.update(tracker)
612
+ except Exception as e:
613
+ self.on_tracker_store_error(e)
614
+ await self.fallback_tracker_store.update(tracker)
587
615
 
588
616
  async def retrieve_full_tracker(
589
617
  self, sender_id: Text
@@ -793,7 +821,13 @@ class AwaitableTrackerStore(TrackerStore):
793
821
 
794
822
  async def delete(self, sender_id: Text) -> None:
795
823
  """Delete tracker for the given sender_id."""
796
- await self._tracker_store.delete(sender_id)
824
+ result = self._tracker_store.delete(sender_id)
825
+ return await result if isawaitable(result) else result
826
+
827
+ async def update(self, tracker: DialogueStateTracker) -> None:
828
+ """Replace an existing tracker with a new one."""
829
+ result = self._tracker_store.update(tracker)
830
+ return await result if isawaitable(result) else result
797
831
 
798
832
  async def retrieve_full_tracker(
799
833
  self, conversation_id: Text
@@ -249,7 +249,7 @@ flows:
249
249
  next: END
250
250
 
251
251
  pattern_repeat_bot_messages:
252
- description: Voice conversation repair pattern to repeat bot messages
252
+ description: Conversation repair flow for repeating previous messages
253
253
  name: pattern repeat bot messages
254
254
  steps:
255
255
  - action: action_repeat_bot_messages
@@ -43,7 +43,6 @@ from rasa.model_manager.utils import (
43
43
  get_logs_content,
44
44
  logs_base_path,
45
45
  models_base_path,
46
- subpath,
47
46
  )
48
47
  from rasa.model_manager.warm_rasa_process import (
49
48
  initialize_warm_rasa_process,
@@ -53,6 +52,7 @@ from rasa.server import ErrorResponse
53
52
  from rasa.shared.exceptions import InvalidConfigException
54
53
  from rasa.shared.utils.yaml import dump_obj_as_yaml_to_string
55
54
  from rasa.studio.upload import build_calm_import_parts
55
+ from rasa.utils.io import subpath
56
56
 
57
57
  dotenv.load_dotenv()
58
58
 
@@ -105,7 +105,7 @@ async def update_status_of_all_bots() -> None:
105
105
  await update_bot_status(bot)
106
106
 
107
107
 
108
- def base_server_url(request: Request) -> str:
108
+ def base_server_url(request: Optional[Request]) -> str:
109
109
  """Return the base URL of the server."""
110
110
  if SERVER_BASE_URL:
111
111
  return SERVER_BASE_URL.rstrip("/")
@@ -15,11 +15,11 @@ from rasa.model_manager import config
15
15
  from rasa.model_manager.utils import (
16
16
  logs_path,
17
17
  models_base_path,
18
- subpath,
19
18
  write_encoded_data_to_file,
20
19
  )
21
20
  from rasa.model_manager.warm_rasa_process import start_rasa_process
22
21
  from rasa.studio.prompts import handle_prompts
22
+ from rasa.utils.io import subpath
23
23
 
24
24
  structlogger = structlog.get_logger()
25
25
 
@@ -14,7 +14,6 @@ from rasa.model_manager.utils import (
14
14
  ensure_base_directory_exists,
15
15
  logs_path,
16
16
  models_base_path,
17
- subpath,
18
17
  write_encoded_data_to_file,
19
18
  )
20
19
  from rasa.model_manager.warm_rasa_process import (
@@ -22,6 +21,7 @@ from rasa.model_manager.warm_rasa_process import (
22
21
  )
23
22
  from rasa.model_training import generate_random_model_name
24
23
  from rasa.studio.prompts import handle_prompts
24
+ from rasa.utils.io import subpath
25
25
 
26
26
  structlogger = structlog.get_logger()
27
27
 
@@ -53,6 +53,15 @@ class TrainingSession(BaseModel):
53
53
  """Check if the training is running."""
54
54
  return self.status == TrainingSessionStatus.RUNNING
55
55
 
56
+ def has_just_finished(self) -> bool:
57
+ if not self.is_status_indicating_alive():
58
+ # skip if the training is not running
59
+ return False
60
+ if self.process.poll() is None:
61
+ # process is still running
62
+ return False
63
+ return True
64
+
56
65
  def model_path(self) -> str:
57
66
  """Return the path to the model."""
58
67
  return subpath(models_base_path(), f"{self.model_name}.tar.gz")
@@ -89,14 +98,8 @@ def terminate_training(training: TrainingSession) -> None:
89
98
 
90
99
 
91
100
  def update_training_status(training: TrainingSession) -> None:
92
- if not training.is_status_indicating_alive():
93
- # skip if the training is not running
94
- return
95
- if training.process.poll() is None:
96
- # process is still running
97
- return
98
-
99
- complete_training(training)
101
+ if training.has_just_finished():
102
+ complete_training(training)
100
103
 
101
104
 
102
105
  def complete_training(training: TrainingSession) -> None:
@@ -5,15 +5,11 @@ from typing import Optional
5
5
  import structlog
6
6
 
7
7
  from rasa.model_manager import config
8
- from rasa.shared.exceptions import RasaException
8
+ from rasa.utils.io import subpath
9
9
 
10
10
  structlogger = structlog.get_logger()
11
11
 
12
12
 
13
- class InvalidPathException(RasaException):
14
- """Raised if a path is invalid - e.g. path traversal is detected."""
15
-
16
-
17
13
  def write_encoded_data_to_file(encoded_data: bytes, file: str) -> None:
18
14
  """Write base64 encoded data to a file."""
19
15
  # create the directory if it does not exist of the parent directory
@@ -53,30 +49,6 @@ def logs_path(action_id: str) -> str:
53
49
  return subpath(logs_base_path(), f"{action_id}.txt")
54
50
 
55
51
 
56
- def subpath(parent: str, child: str) -> str:
57
- """Return the path to the child directory of the parent directory.
58
-
59
- Ensures, that child doesn't navigate to parent directories. Prevents
60
- path traversal. Raises an InvalidPathException if the path is invalid.
61
-
62
- Based on Snyk's directory traversal mitigation:
63
- https://learn.snyk.io/lesson/directory-traversal/
64
- """
65
- safe_path = os.path.abspath(os.path.join(parent, child))
66
- parent = os.path.abspath(parent)
67
-
68
- common_base = os.path.commonpath([parent, safe_path])
69
- if common_base != parent:
70
- raise InvalidPathException(f"Invalid path: {safe_path}")
71
-
72
- if os.path.basename(safe_path) != child:
73
- raise InvalidPathException(
74
- f"Invalid path - path traversal detected: {safe_path}"
75
- )
76
-
77
- return safe_path
78
-
79
-
80
52
  def get_logs_content(action_id: str) -> Optional[str]:
81
53
  """Return the content of the log file for a given action id."""
82
54
  try:
@@ -12,7 +12,7 @@ import structlog
12
12
  from apscheduler.schedulers.background import BackgroundScheduler
13
13
 
14
14
  import rasa.shared.core.trackers
15
- from rasa.core.tracker_stores.tracker_store import FailSafeTrackerStore, TrackerStore
15
+ from rasa.core.tracker_stores.tracker_store import TrackerStore
16
16
  from rasa.privacy.constants import (
17
17
  TEXT_KEY,
18
18
  USER_CHAT_INACTIVITY_IN_MINUTES_ENV_VAR_NAME,
@@ -25,7 +25,7 @@ from rasa.privacy.privacy_config import (
25
25
  )
26
26
  from rasa.privacy.privacy_filter import PrivacyFilter
27
27
  from rasa.shared.core.events import Event, SlotSet, UserUttered, split_events
28
- from rasa.shared.core.trackers import DialogueStateTracker
28
+ from rasa.shared.core.trackers import DialogueStateTracker, EventVerbosity
29
29
 
30
30
  if TYPE_CHECKING:
31
31
  from asyncio import AbstractEventLoop
@@ -97,7 +97,7 @@ class BackgroundPrivacyManager:
97
97
  else TrackerStore.create(None)
98
98
  )
99
99
 
100
- self.tracker_store = FailSafeTrackerStore(tracker_store)
100
+ self.tracker_store = tracker_store
101
101
 
102
102
  self.event_brokers: List["EventBroker"] = []
103
103
  self.event_loop = event_loop
@@ -264,15 +264,16 @@ class BackgroundPrivacyManager:
264
264
  )
265
265
  return None
266
266
 
267
- latest_message = tracker.latest_message
268
-
269
- if latest_message is None or not latest_message.text:
267
+ latest_user_message = tracker.get_last_event_for(
268
+ UserUttered, event_verbosity=EventVerbosity.ALL
269
+ )
270
+ if latest_user_message is None or not latest_user_message.text:
270
271
  structlogger.debug(
271
272
  "rasa.privacy_manager.no_user_message.skipping_processing",
272
273
  )
273
274
  return None
274
275
 
275
- return latest_message
276
+ return latest_user_message
276
277
 
277
278
  @staticmethod
278
279
  def _has_session_been_anonymized(events: List[Event]) -> bool:
@@ -360,9 +361,13 @@ class BackgroundPrivacyManager:
360
361
  full_tracker
361
362
  )
362
363
 
363
- await self.tracker_store.delete(sender_id=key)
364
-
365
364
  if not events_to_be_retained:
365
+ await self.tracker_store.delete(sender_id=key)
366
+ structlogger.info(
367
+ "rasa.privacy_manager.tracker_session_deleted",
368
+ sender_id=full_tracker.sender_id,
369
+ triggered_by="deletion_cron_job",
370
+ )
366
371
  continue
367
372
 
368
373
  tracker = DialogueStateTracker.from_events(
@@ -370,12 +375,13 @@ class BackgroundPrivacyManager:
370
375
  evts=events_to_be_retained,
371
376
  slots=full_tracker.slots.values(),
372
377
  )
373
- await self.tracker_store.save(tracker)
378
+ await self.tracker_store.update(tracker)
374
379
 
375
380
  structlogger.info(
376
- "rasa.privacy_manager.save_tracker_after_deletion",
381
+ "rasa.privacy_manager.overwritten_tracker",
377
382
  sender_id=key,
378
- event_info="Saved tracker with events not scheduled "
383
+ event_info="Deleted eligible events and saved "
384
+ "tracker with events not scheduled "
379
385
  "for deletion yet.",
380
386
  )
381
387
 
@@ -527,10 +533,7 @@ class BackgroundPrivacyManager:
527
533
  last_event_timestamp=last_event_timestamp,
528
534
  triggered_by="anonymization_cron_job",
529
535
  )
530
- tracker = DialogueStateTracker.from_events(
531
- session.sender_id, session.events
532
- )
533
- events = self.process_events(tracker, process_all=True)
536
+ events = self.process_events(session, process_all=True)
534
537
  processed_events.extend(events)
535
538
  else:
536
539
  # If the session is not valid for anonymization,