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.
- rasa/builder/README.md +120 -0
- rasa/builder/__init__.py +0 -0
- rasa/builder/config.py +69 -0
- rasa/builder/create_openai_vector_store.py +228 -0
- rasa/builder/exceptions.py +49 -0
- rasa/builder/llm-helper-schema.json +69 -0
- rasa/builder/llm_context.py +81 -0
- rasa/builder/llm_helper_prompt.jinja2 +245 -0
- rasa/builder/llm_service.py +327 -0
- rasa/builder/logging_utils.py +51 -0
- rasa/builder/main.py +61 -0
- rasa/builder/models.py +174 -0
- rasa/builder/project_generator.py +264 -0
- rasa/builder/scrape_rasa_docs.py +97 -0
- rasa/builder/service.py +447 -0
- rasa/builder/skill_to_bot_prompt.jinja2 +164 -0
- rasa/builder/training_service.py +123 -0
- rasa/builder/validation_service.py +79 -0
- rasa/cli/project_templates/finance/config.yml +17 -0
- rasa/cli/project_templates/finance/credentials.yml +33 -0
- rasa/cli/project_templates/finance/data/flows/transfer_money.yml +5 -0
- rasa/cli/project_templates/finance/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/finance/domain.yml +7 -0
- rasa/cli/project_templates/finance/endpoints.yml +58 -0
- rasa/cli/project_templates/plain/config.yml +17 -0
- rasa/cli/project_templates/plain/credentials.yml +33 -0
- rasa/cli/project_templates/plain/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/plain/domain.yml +5 -0
- rasa/cli/project_templates/plain/endpoints.yml +58 -0
- rasa/cli/project_templates/telecom/config.yml +17 -0
- rasa/cli/project_templates/telecom/credentials.yml +33 -0
- rasa/cli/project_templates/telecom/data/flows/upgrade_contract.yml +5 -0
- rasa/cli/project_templates/telecom/data/patterns/pattern_session_start.yml +7 -0
- rasa/cli/project_templates/telecom/domain.yml +7 -0
- rasa/cli/project_templates/telecom/endpoints.yml +58 -0
- rasa/cli/scaffold.py +19 -3
- rasa/core/actions/action.py +5 -3
- rasa/core/channels/studio_chat.py +29 -8
- rasa/core/policies/flows/flow_executor.py +8 -1
- rasa/core/tracker_stores/auth_retry_tracker_store.py +64 -3
- rasa/core/tracker_stores/dynamo_tracker_store.py +10 -0
- rasa/core/tracker_stores/mongo_tracker_store.py +17 -0
- rasa/core/tracker_stores/redis_tracker_store.py +23 -0
- rasa/core/tracker_stores/sql_tracker_store.py +27 -0
- rasa/core/tracker_stores/tracker_store.py +36 -2
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +1 -1
- rasa/model_manager/model_api.py +2 -2
- rasa/model_manager/runner_service.py +1 -1
- rasa/model_manager/trainer_service.py +12 -9
- rasa/model_manager/utils.py +1 -29
- rasa/privacy/privacy_manager.py +19 -16
- rasa/shared/core/domain.py +62 -15
- rasa/shared/core/flows/flow_step.py +7 -1
- rasa/shared/core/flows/yaml_flows_io.py +16 -8
- rasa/shared/core/slots.py +4 -0
- rasa/shared/importers/importer.py +6 -0
- rasa/shared/importers/static.py +63 -0
- rasa/telemetry.py +2 -1
- rasa/utils/io.py +27 -9
- rasa/utils/log_utils.py +5 -1
- rasa/validator.py +7 -3
- rasa/version.py +1 -1
- {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/METADATA +3 -3
- {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/RECORD +67 -31
- {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0rc3.dist-info → rasa_pro-3.13.1a2.dist-info}/WHEEL +0 -0
- {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
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
|
|
436
|
-
|
|
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(
|
|
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:
|
|
124
|
-
"""
|
|
125
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
rasa/model_manager/model_api.py
CHANGED
|
@@ -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
|
|
93
|
-
|
|
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:
|
rasa/model_manager/utils.py
CHANGED
|
@@ -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.
|
|
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:
|
rasa/privacy/privacy_manager.py
CHANGED
|
@@ -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
|
|
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 =
|
|
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
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
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.
|
|
378
|
+
await self.tracker_store.update(tracker)
|
|
374
379
|
|
|
375
380
|
structlogger.info(
|
|
376
|
-
"rasa.privacy_manager.
|
|
381
|
+
"rasa.privacy_manager.overwritten_tracker",
|
|
377
382
|
sender_id=key,
|
|
378
|
-
event_info="
|
|
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
|
-
|
|
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,
|