rasa-pro 3.13.0.dev5__py3-none-any.whl → 3.13.0.dev7__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 (154) hide show
  1. rasa/api.py +4 -0
  2. rasa/cli/arguments/default_arguments.py +13 -1
  3. rasa/cli/arguments/train.py +2 -0
  4. rasa/cli/evaluate.py +1 -1
  5. rasa/cli/export.py +2 -2
  6. rasa/cli/train.py +1 -0
  7. rasa/constants.py +2 -0
  8. rasa/core/agent.py +2 -2
  9. rasa/core/brokers/kafka.py +4 -0
  10. rasa/core/brokers/pika.py +4 -0
  11. rasa/core/brokers/sql.py +1 -1
  12. rasa/core/channels/inspector/.eslintrc.cjs +12 -6
  13. rasa/core/channels/inspector/.prettierrc +5 -0
  14. rasa/core/channels/inspector/README.md +10 -4
  15. rasa/core/channels/inspector/dist/assets/{arc-9f75cc3b.js → arc-c4b064fc.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-7f34db23.js → blockDiagram-38ab4fdb-215b5026.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-948bab2c.js → c4Diagram-3d4e48cf-2b54a0a3.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/channel-3730f5fd.js +1 -0
  19. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-53b0dd0e.js → classDiagram-70f12bd4-daacea5f.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-fdf789e7.js → classDiagram-v2-f2320105-930d4dc2.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/clone-e847561e.js +1 -0
  22. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-87c4ece5.js → createText-2e5e7dd3-83c206ba.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-5a8b0749.js → edges-e0da2a9e-b0eb01d0.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-66da90e2.js → erDiagram-9861fffd-17586500.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-10044f05.js → flowDb-956e92f1-be2a1776.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-f338f66a.js → flowDiagram-66a62f08-c2120ebd.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-efbbfe00.js +1 -0
  28. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-b13140aa.js → flowchart-elk-definition-4a651766-a6ab5c48.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-f2b4a55a.js → ganttDiagram-c361ad54-ef613457.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-dedc298d.js → gitGraphDiagram-72cf32ee-d59185b3.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{graph-4ede11ff.js → graph-0f155405.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{index-3862675e-65549d37.js → index-3862675e-d5f1d1b7.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{index-3a23e736.js → index-47737d3a.js} +123 -123
  34. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-65439671.js → infoDiagram-f8f76790-b07d141f.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-56d03d98.js → journeyDiagram-49397b02-1936d429.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{layout-dd48f7f4.js → layout-dde8d0f3.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{line-1569ad2c.js → line-0c2c7ee0.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{linear-48bf4935.js → linear-35dd89a4.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-688504c1.js → mindmap-definition-fc14e90a-56192851.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-78b6d7e6.js → pieDiagram-8a3498a8-fc21ed78.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-048b84b3.js → quadrantDiagram-120e2f19-25e98518.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-dd67f107.js → requirementDiagram-deff3bca-546ff1f5.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-8128436e.js → sankeyDiagram-04a897e0-02d8b82d.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-1a0d1461.js → sequenceDiagram-704730f1-3ca5a92e.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-46d388ed.js → stateDiagram-587899a1-128ea07c.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-ea42951a.js → stateDiagram-v2-d93cdb3a-95f290af.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-7427ed0c.js → styles-6aaf32cf-4984898a.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-ff5e5a16.js → styles-9a916d00-1bf266ba.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-7b3680cf.js → styles-c10674c1-60521c63.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-f860f2ad.js → svgDrawCommon-08f97a94-a25b6e12.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-2eebf0c8.js → timeline-definition-85554ec2-0fc086bf.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-5d7f4e96.js → xychartDiagram-e933f94c-44ee592e.js} +1 -1
  53. rasa/core/channels/inspector/dist/index.html +1 -1
  54. rasa/core/channels/inspector/package.json +3 -1
  55. rasa/core/channels/inspector/src/App.tsx +91 -90
  56. rasa/core/channels/inspector/src/components/Chat.tsx +45 -41
  57. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +40 -40
  58. rasa/core/channels/inspector/src/components/DialogueInformation.tsx +57 -57
  59. rasa/core/channels/inspector/src/components/DialogueStack.tsx +36 -27
  60. rasa/core/channels/inspector/src/components/ExpandIcon.tsx +4 -4
  61. rasa/core/channels/inspector/src/components/FullscreenButton.tsx +7 -7
  62. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +28 -12
  63. rasa/core/channels/inspector/src/components/NoActiveFlow.tsx +9 -9
  64. rasa/core/channels/inspector/src/components/RasaLogo.tsx +5 -5
  65. rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +55 -60
  66. rasa/core/channels/inspector/src/components/SaraDiagrams.tsx +5 -5
  67. rasa/core/channels/inspector/src/components/Slots.tsx +22 -22
  68. rasa/core/channels/inspector/src/components/Welcome.tsx +28 -31
  69. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +245 -0
  70. rasa/core/channels/inspector/src/helpers/audio/microphone-processor.js +12 -0
  71. rasa/core/channels/inspector/src/helpers/audio/playback-processor.js +36 -0
  72. rasa/core/channels/inspector/src/helpers/conversation.ts +7 -7
  73. rasa/core/channels/inspector/src/helpers/formatters.test.ts +181 -181
  74. rasa/core/channels/inspector/src/helpers/formatters.ts +111 -111
  75. rasa/core/channels/inspector/src/helpers/utils.ts +78 -61
  76. rasa/core/channels/inspector/src/main.tsx +8 -8
  77. rasa/core/channels/inspector/src/theme/Button/Button.ts +8 -8
  78. rasa/core/channels/inspector/src/theme/Heading/Heading.ts +7 -7
  79. rasa/core/channels/inspector/src/theme/Input/Input.ts +9 -9
  80. rasa/core/channels/inspector/src/theme/Link/Link.ts +6 -6
  81. rasa/core/channels/inspector/src/theme/Modal/Modal.ts +13 -13
  82. rasa/core/channels/inspector/src/theme/Table/Table.tsx +10 -10
  83. rasa/core/channels/inspector/src/theme/Tooltip/Tooltip.ts +5 -5
  84. rasa/core/channels/inspector/src/theme/base/breakpoints.ts +7 -7
  85. rasa/core/channels/inspector/src/theme/base/colors.ts +64 -64
  86. rasa/core/channels/inspector/src/theme/base/fonts/fontFaces.css +21 -18
  87. rasa/core/channels/inspector/src/theme/base/radii.ts +8 -8
  88. rasa/core/channels/inspector/src/theme/base/shadows.ts +5 -5
  89. rasa/core/channels/inspector/src/theme/base/sizes.ts +5 -5
  90. rasa/core/channels/inspector/src/theme/base/space.ts +12 -12
  91. rasa/core/channels/inspector/src/theme/base/styles.ts +5 -5
  92. rasa/core/channels/inspector/src/theme/base/typography.ts +12 -12
  93. rasa/core/channels/inspector/src/theme/base/zIndices.ts +3 -3
  94. rasa/core/channels/inspector/src/theme/index.ts +38 -38
  95. rasa/core/channels/inspector/src/types.ts +56 -50
  96. rasa/core/channels/inspector/yarn.lock +5 -0
  97. rasa/core/channels/voice_ready/audiocodes.py +34 -17
  98. rasa/core/evaluation/marker_tracker_loader.py +1 -1
  99. rasa/core/exporter.py +1 -1
  100. rasa/core/nlg/contextual_response_rephraser.py +4 -2
  101. rasa/core/nlg/summarize.py +1 -1
  102. rasa/core/persistor.py +55 -20
  103. rasa/core/policies/enterprise_search_policy.py +7 -4
  104. rasa/core/policies/intentless_policy.py +15 -9
  105. rasa/core/processor.py +2 -2
  106. rasa/core/run.py +7 -2
  107. rasa/core/tracker_stores/__init__.py +0 -0
  108. rasa/core/{auth_retry_tracker_store.py → tracker_stores/auth_retry_tracker_store.py} +5 -1
  109. rasa/core/tracker_stores/dynamo_tracker_store.py +218 -0
  110. rasa/core/tracker_stores/mongo_tracker_store.py +206 -0
  111. rasa/core/tracker_stores/redis_tracker_store.py +219 -0
  112. rasa/core/tracker_stores/sql_tracker_store.py +555 -0
  113. rasa/core/tracker_stores/tracker_store.py +805 -0
  114. rasa/core/utils.py +6 -0
  115. rasa/dialogue_understanding/coexistence/llm_based_router.py +8 -3
  116. rasa/dialogue_understanding/commands/clarify_command.py +2 -2
  117. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  118. rasa/dialogue_understanding/generator/constants.py +2 -2
  119. rasa/dialogue_understanding/generator/llm_based_command_generator.py +1 -1
  120. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +33 -12
  121. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +2 -2
  122. rasa/hooks.py +2 -2
  123. rasa/keys +1 -0
  124. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +4 -2
  125. rasa/model_manager/config.py +3 -1
  126. rasa/model_manager/model_api.py +1 -2
  127. rasa/model_manager/runner_service.py +8 -4
  128. rasa/model_manager/trainer_service.py +1 -0
  129. rasa/model_training.py +12 -3
  130. rasa/nlu/extractors/crf_entity_extractor.py +66 -16
  131. rasa/plugin.py +1 -1
  132. rasa/server.py +6 -2
  133. rasa/shared/constants.py +3 -0
  134. rasa/shared/core/events.py +68 -2
  135. rasa/shared/providers/_configs/azure_openai_client_config.py +4 -0
  136. rasa/shared/providers/_configs/openai_client_config.py +4 -0
  137. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +3 -0
  138. rasa/shared/providers/llm/_base_litellm_client.py +5 -2
  139. rasa/telemetry.py +2 -2
  140. rasa/tracing/config.py +1 -1
  141. rasa/tracing/instrumentation/attribute_extractors.py +1 -1
  142. rasa/tracing/instrumentation/instrumentation.py +1 -1
  143. rasa/utils/licensing.py +1 -2
  144. rasa/version.py +1 -1
  145. {rasa_pro-3.13.0.dev5.dist-info → rasa_pro-3.13.0.dev7.dist-info}/METADATA +4 -4
  146. {rasa_pro-3.13.0.dev5.dist-info → rasa_pro-3.13.0.dev7.dist-info}/RECORD +149 -140
  147. rasa/core/channels/inspector/dist/assets/channel-dfa68278.js +0 -1
  148. rasa/core/channels/inspector/dist/assets/clone-edb7f119.js +0 -1
  149. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-65e7c670.js +0 -1
  150. rasa/core/channels/inspector/src/helpers/audiostream.ts +0 -191
  151. rasa/core/tracker_store.py +0 -1792
  152. {rasa_pro-3.13.0.dev5.dist-info → rasa_pro-3.13.0.dev7.dist-info}/NOTICE +0 -0
  153. {rasa_pro-3.13.0.dev5.dist-info → rasa_pro-3.13.0.dev7.dist-info}/WHEEL +0 -0
  154. {rasa_pro-3.13.0.dev5.dist-info → rasa_pro-3.13.0.dev7.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,805 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from inspect import isawaitable, iscoroutinefunction
5
+ from typing import (
6
+ Any,
7
+ Callable,
8
+ Dict,
9
+ Generic,
10
+ Iterable,
11
+ List,
12
+ Optional,
13
+ Text,
14
+ TypeVar,
15
+ Union,
16
+ )
17
+
18
+ import structlog
19
+
20
+ import rasa.shared.utils.cli
21
+ import rasa.shared.utils.common
22
+ import rasa.shared.utils.io
23
+ import rasa.utils.json_utils
24
+ from rasa.core.brokers.broker import EventBroker
25
+ from rasa.plugin import plugin_manager
26
+ from rasa.shared.core.constants import ACTION_LISTEN_NAME
27
+ from rasa.shared.core.conversation import Dialogue
28
+ from rasa.shared.core.domain import Domain
29
+ from rasa.shared.core.events import Event
30
+ from rasa.shared.core.trackers import (
31
+ ActionExecuted,
32
+ DialogueStateTracker,
33
+ TrackerEventDiffEngine,
34
+ )
35
+ from rasa.shared.exceptions import ConnectionException, RasaException
36
+ from rasa.utils.endpoints import EndpointConfig
37
+
38
+ structlogger = structlog.get_logger(__name__)
39
+
40
+
41
+ def check_if_tracker_store_async(tracker_store: TrackerStore) -> bool:
42
+ """Evaluates if a tracker store object is async based on implementation of methods.
43
+
44
+ :param tracker_store: tracker store object we're evaluating
45
+ :return: if the tracker store correctly implements all async methods
46
+ """
47
+ return all(
48
+ iscoroutinefunction(getattr(tracker_store, method))
49
+ for method in _get_async_tracker_store_methods()
50
+ )
51
+
52
+
53
+ def _get_async_tracker_store_methods() -> List[str]:
54
+ return [
55
+ attribute
56
+ for attribute in dir(TrackerStore)
57
+ if iscoroutinefunction(getattr(TrackerStore, attribute))
58
+ ]
59
+
60
+
61
+ class TrackerDeserialisationException(RasaException):
62
+ """Raised when an error is encountered while deserialising a tracker."""
63
+
64
+
65
+ SerializationType = TypeVar("SerializationType")
66
+
67
+
68
+ class SerializedTrackerRepresentation(Generic[SerializationType]):
69
+ """Mixin class for specifying different serialization methods per tracker store."""
70
+
71
+ @staticmethod
72
+ def serialise_tracker(tracker: DialogueStateTracker) -> SerializationType:
73
+ """Requires implementation to return representation of tracker."""
74
+ raise NotImplementedError()
75
+
76
+
77
+ class SerializedTrackerAsText(SerializedTrackerRepresentation[Text]):
78
+ """Mixin class that returns the serialized tracker as string."""
79
+
80
+ @staticmethod
81
+ def serialise_tracker(tracker: DialogueStateTracker) -> Text:
82
+ """Serializes the tracker, returns representation of the tracker."""
83
+ dialogue = tracker.as_dialogue()
84
+
85
+ return json.dumps(dialogue.as_dict())
86
+
87
+
88
+ class SerializedTrackerAsDict(SerializedTrackerRepresentation[Dict]):
89
+ """Mixin class that returns the serialized tracker as dictionary."""
90
+
91
+ @staticmethod
92
+ def serialise_tracker(tracker: DialogueStateTracker) -> Dict:
93
+ """Serializes the tracker, returns representation of the tracker."""
94
+ d = tracker.as_dialogue().as_dict()
95
+ d.update({"sender_id": tracker.sender_id})
96
+ return d
97
+
98
+
99
+ class TrackerStore:
100
+ """Represents common behavior and interface for all `TrackerStore`s."""
101
+
102
+ def __init__(
103
+ self,
104
+ domain: Optional[Domain],
105
+ event_broker: Optional[EventBroker] = None,
106
+ **kwargs: Dict[Text, Any],
107
+ ) -> None:
108
+ """Create a TrackerStore.
109
+
110
+ Args:
111
+ domain: The `Domain` to initialize the `DialogueStateTracker`.
112
+ event_broker: An event broker to publish any new events to another
113
+ destination.
114
+ kwargs: Additional kwargs.
115
+ """
116
+ self._domain = domain or Domain.empty()
117
+ self.event_broker = event_broker
118
+ self.max_event_history: Optional[int] = None
119
+
120
+ @staticmethod
121
+ def create(
122
+ obj: Union[TrackerStore, EndpointConfig, None],
123
+ domain: Optional[Domain] = None,
124
+ event_broker: Optional[EventBroker] = None,
125
+ ) -> TrackerStore:
126
+ """Factory to create a tracker store."""
127
+ if isinstance(obj, TrackerStore):
128
+ return obj
129
+
130
+ import pymongo.errors
131
+ import sqlalchemy.exc
132
+ from botocore.exceptions import BotoCoreError, ClientError
133
+
134
+ try:
135
+ _tracker_store = plugin_manager().hook.create_tracker_store(
136
+ endpoint_config=obj,
137
+ domain=domain,
138
+ event_broker=event_broker,
139
+ )
140
+
141
+ tracker_store = (
142
+ _tracker_store
143
+ if _tracker_store
144
+ else create_tracker_store(obj, domain, event_broker)
145
+ )
146
+
147
+ return tracker_store
148
+ except (
149
+ BotoCoreError,
150
+ ClientError,
151
+ pymongo.errors.ConnectionFailure,
152
+ sqlalchemy.exc.OperationalError,
153
+ ConnectionError,
154
+ pymongo.errors.OperationFailure,
155
+ ) as error:
156
+ raise ConnectionException(
157
+ "Cannot connect to tracker store." + str(error)
158
+ ) from error
159
+
160
+ async def get_or_create_tracker(
161
+ self,
162
+ sender_id: Text,
163
+ max_event_history: Optional[int] = None,
164
+ append_action_listen: bool = True,
165
+ ) -> "DialogueStateTracker":
166
+ """Returns tracker or creates one if the retrieval returns None.
167
+
168
+ Args:
169
+ sender_id: Conversation ID associated with the requested tracker.
170
+ max_event_history: Value to update the tracker store's max event history to.
171
+ append_action_listen: Whether or not to append an initial `action_listen`.
172
+ """
173
+ self.max_event_history = max_event_history
174
+
175
+ tracker = await self.retrieve(sender_id)
176
+
177
+ if tracker is None:
178
+ tracker = await self.create_tracker(
179
+ sender_id, append_action_listen=append_action_listen
180
+ )
181
+
182
+ return tracker
183
+
184
+ def init_tracker(self, sender_id: Text) -> "DialogueStateTracker":
185
+ """Returns a Dialogue State Tracker."""
186
+ return DialogueStateTracker(
187
+ sender_id,
188
+ self.domain.slots,
189
+ max_event_history=self.max_event_history,
190
+ )
191
+
192
+ async def create_tracker(
193
+ self, sender_id: Text, append_action_listen: bool = True
194
+ ) -> DialogueStateTracker:
195
+ """Creates a new tracker for `sender_id`.
196
+
197
+ The tracker begins with a `SessionStarted` event and is initially listening.
198
+
199
+ Args:
200
+ sender_id: Conversation ID associated with the tracker.
201
+ append_action_listen: Whether or not to append an initial `action_listen`.
202
+
203
+ Returns:
204
+ The newly created tracker for `sender_id`.
205
+ """
206
+ tracker = self.init_tracker(sender_id)
207
+
208
+ if append_action_listen:
209
+ tracker.update(ActionExecuted(ACTION_LISTEN_NAME))
210
+
211
+ await self.save(tracker)
212
+
213
+ return tracker
214
+
215
+ async def save(self, tracker: DialogueStateTracker) -> None:
216
+ """Save method that will be overridden by specific tracker."""
217
+ raise NotImplementedError()
218
+
219
+ async def exists(self, conversation_id: Text) -> bool:
220
+ """Checks if tracker exists for the specified ID.
221
+
222
+ This method may be overridden by the specific tracker store for
223
+ faster implementations.
224
+
225
+ Args:
226
+ conversation_id: Conversation ID to check if the tracker exists.
227
+
228
+ Returns:
229
+ `True` if the tracker exists, `False` otherwise.
230
+ """
231
+ return await self.retrieve(conversation_id) is not None
232
+
233
+ async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
234
+ """Retrieves tracker for the latest conversation session.
235
+
236
+ This method will be overridden by the specific tracker store.
237
+
238
+ Args:
239
+ sender_id: Conversation ID to fetch the tracker for.
240
+
241
+ Returns:
242
+ Tracker containing events from the latest conversation sessions.
243
+ """
244
+ raise NotImplementedError()
245
+
246
+ async def delete(self, sender_id: str) -> None:
247
+ """Delete tracker for the given sender_id.
248
+
249
+ Args:
250
+ sender_id: Conversation ID to delete the tracker for.
251
+ """
252
+ raise NotImplementedError()
253
+
254
+ async def retrieve_full_tracker(
255
+ self, conversation_id: Text
256
+ ) -> Optional[DialogueStateTracker]:
257
+ """Retrieve method for fetching all tracker events.
258
+
259
+ Fetches events across conversation sessions. The default implementation
260
+ uses `self.retrieve()`.
261
+
262
+ Args:
263
+ conversation_id: The conversation ID to retrieve the tracker for.
264
+
265
+ Returns:
266
+ The fetch tracker containing all events across session starts.
267
+ """
268
+ return await self.retrieve(conversation_id)
269
+
270
+ async def get_or_create_full_tracker(
271
+ self,
272
+ sender_id: Text,
273
+ append_action_listen: bool = True,
274
+ ) -> "DialogueStateTracker":
275
+ """Returns tracker or creates one if the retrieval returns None.
276
+
277
+ Args:
278
+ sender_id: Conversation ID associated with the requested tracker.
279
+ append_action_listen: Whether to append an initial `action_listen`.
280
+
281
+ Returns:
282
+ The tracker for the conversation ID.
283
+ """
284
+ tracker = await self.retrieve_full_tracker(sender_id)
285
+
286
+ if tracker is None:
287
+ tracker = await self.create_tracker(
288
+ sender_id, append_action_listen=append_action_listen
289
+ )
290
+
291
+ return tracker
292
+
293
+ async def stream_events(self, tracker: DialogueStateTracker) -> None:
294
+ """Streams events to a message broker."""
295
+ if self.event_broker is None:
296
+ structlogger.debug(
297
+ "tracker_store.stream_events.no_broker_configured",
298
+ event_info="No event broker configured. Skipping streaming events.",
299
+ )
300
+ return None
301
+
302
+ if (
303
+ hasattr(self.event_broker, "stream_pii")
304
+ and not self.event_broker.stream_pii
305
+ ):
306
+ # If the event broker is configured to not stream un-anonymized events,
307
+ # skip streaming
308
+ structlogger.debug(
309
+ "tracker_store.stream_events.no_streaming",
310
+ event_info="Un-anonymized events will not be published "
311
+ "to the event broker.",
312
+ )
313
+ return None
314
+
315
+ old_tracker = await self.retrieve(tracker.sender_id)
316
+ new_events = TrackerEventDiffEngine.event_difference(old_tracker, tracker)
317
+
318
+ await self._stream_new_events(self.event_broker, new_events, tracker.sender_id)
319
+
320
+ async def _stream_new_events(
321
+ self,
322
+ event_broker: EventBroker,
323
+ new_events: List[Event],
324
+ sender_id: Text,
325
+ ) -> None:
326
+ """Publishes new tracker events to a message broker."""
327
+ for event in new_events:
328
+ body = {"sender_id": sender_id}
329
+ body.update(event.as_dict())
330
+ event_broker.publish(body)
331
+
332
+ async def keys(self) -> Iterable[Text]:
333
+ """Returns the set of values for the tracker store's primary key."""
334
+ raise NotImplementedError()
335
+
336
+ async def count_conversations(self, after_timestamp: float = 0.0) -> int:
337
+ """Returns the number of conversations that have occurred after a timestamp.
338
+
339
+ By default, this method returns the number of conversations that
340
+ have occurred after the Unix epoch (i.e. timestamp 0). A conversation
341
+ is considered to have occurred after a timestamp if at least one event
342
+ happened after that timestamp.
343
+ """
344
+ tracker_keys = await self.keys()
345
+
346
+ conversation_count = 0
347
+ for key in tracker_keys:
348
+ tracker = await self.retrieve(key)
349
+ if tracker is None or not tracker.events:
350
+ continue
351
+
352
+ last_event = tracker.events[-1]
353
+ if last_event.timestamp >= after_timestamp:
354
+ conversation_count += 1
355
+
356
+ return conversation_count
357
+
358
+ def deserialise_tracker(
359
+ self, sender_id: Text, serialised_tracker: Union[Text, bytes]
360
+ ) -> Optional[DialogueStateTracker]:
361
+ """Deserializes the tracker and returns it."""
362
+ tracker = self.init_tracker(sender_id)
363
+
364
+ try:
365
+ dialogue = Dialogue.from_parameters(json.loads(serialised_tracker))
366
+ except UnicodeDecodeError as e:
367
+ raise TrackerDeserialisationException(
368
+ "Tracker cannot be deserialised. "
369
+ "Trackers must be serialised as json. "
370
+ "Support for deserialising pickled trackers has been removed."
371
+ ) from e
372
+
373
+ tracker.recreate_from_dialogue(dialogue)
374
+
375
+ return tracker
376
+
377
+ @property
378
+ def domain(self) -> Domain:
379
+ """Returns the domain of the tracker store."""
380
+ return self._domain
381
+
382
+ @domain.setter
383
+ def domain(self, domain: Optional[Domain]) -> None:
384
+ self._domain = domain or Domain.empty()
385
+
386
+
387
+ class InMemoryTrackerStore(TrackerStore, SerializedTrackerAsText):
388
+ """Stores conversation history in memory."""
389
+
390
+ def __init__(
391
+ self,
392
+ domain: Domain,
393
+ event_broker: Optional[EventBroker] = None,
394
+ **kwargs: Dict[Text, Any],
395
+ ) -> None:
396
+ """Initializes the tracker store."""
397
+ self.store: Dict[Text, Text] = {}
398
+ super().__init__(domain, event_broker, **kwargs)
399
+
400
+ async def save(self, tracker: DialogueStateTracker) -> None:
401
+ """Updates and saves the current conversation state."""
402
+ await self.stream_events(tracker)
403
+ serialised = InMemoryTrackerStore.serialise_tracker(tracker)
404
+ self.store[tracker.sender_id] = serialised
405
+
406
+ async def delete(self, sender_id: Text) -> None:
407
+ """Delete tracker for the given sender_id."""
408
+ if sender_id not in self.store:
409
+ structlogger.info(
410
+ "in_memory_tracker_store.delete.no_tracker_for_sender_id",
411
+ event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
412
+ )
413
+ return None
414
+
415
+ del self.store[sender_id]
416
+
417
+ structlogger.info(
418
+ "in_memory_tracker_store.delete.deleted_tracker",
419
+ sender_id=sender_id,
420
+ )
421
+
422
+ async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
423
+ """Returns tracker matching sender_id."""
424
+ return await self._retrieve(sender_id, fetch_all_sessions=False)
425
+
426
+ async def keys(self) -> Iterable[Text]:
427
+ """Returns sender_ids of the Tracker Store in memory."""
428
+ return self.store.keys()
429
+
430
+ async def retrieve_full_tracker(
431
+ self, sender_id: Text
432
+ ) -> Optional[DialogueStateTracker]:
433
+ """Returns tracker matching sender_id.
434
+
435
+ Args:
436
+ sender_id: Conversation ID to fetch the tracker for.
437
+ """
438
+ return await self._retrieve(sender_id, fetch_all_sessions=True)
439
+
440
+ async def _retrieve(
441
+ self, sender_id: Text, fetch_all_sessions: bool
442
+ ) -> Optional[DialogueStateTracker]:
443
+ """Returns tracker matching sender_id.
444
+
445
+ Args:
446
+ sender_id: Conversation ID to fetch the tracker for.
447
+ fetch_all_sessions: Whether to fetch all sessions or only the last one.
448
+ """
449
+ if sender_id not in self.store:
450
+ structlogger.debug(
451
+ "in_memory_tracker_store.retrieve.no_tracker_for_sender_id",
452
+ event_info=f"Could not find tracker for conversation ID '{sender_id}'.",
453
+ )
454
+ return None
455
+
456
+ tracker = self.deserialise_tracker(sender_id, self.store[sender_id])
457
+
458
+ if not tracker:
459
+ structlogger.debug(
460
+ "in_memory_tracker_store.retrieve.failed_to_deserialize_tracker",
461
+ event_info=(
462
+ f"Could not deserialize tracker "
463
+ f"for conversation ID '{sender_id}'.",
464
+ ),
465
+ )
466
+ return None
467
+
468
+ if fetch_all_sessions:
469
+ return tracker
470
+
471
+ # only return the last session
472
+ multiple_tracker_sessions = (
473
+ rasa.shared.core.trackers.get_trackers_for_conversation_sessions(tracker)
474
+ )
475
+
476
+ if len(multiple_tracker_sessions) <= 1:
477
+ return tracker
478
+
479
+ return multiple_tracker_sessions[-1]
480
+
481
+
482
+ def validate_port(port: Any) -> Optional[int]:
483
+ """Ensure that port can be converted to integer.
484
+
485
+ Raises:
486
+ RasaException if port cannot be cast to integer.
487
+ """
488
+ if port is not None and not isinstance(port, int):
489
+ try:
490
+ port = int(port)
491
+ except ValueError as e:
492
+ raise RasaException(f"The port '{port}' cannot be cast to integer.") from e
493
+
494
+ return port
495
+
496
+
497
+ class FailSafeTrackerStore(TrackerStore):
498
+ """Tracker store wrapper.
499
+
500
+ Allows a fallback to a different tracker store in case of errors.
501
+ """
502
+
503
+ def __init__(
504
+ self,
505
+ tracker_store: TrackerStore,
506
+ on_tracker_store_error: Optional[Callable[[Exception], None]] = None,
507
+ fallback_tracker_store: Optional[TrackerStore] = None,
508
+ ) -> None:
509
+ """Create a `FailSafeTrackerStore`.
510
+
511
+ Args:
512
+ tracker_store: Primary tracker store.
513
+ on_tracker_store_error: Callback which is called when there is an error
514
+ in the primary tracker store.
515
+ fallback_tracker_store: Fallback tracker store.
516
+ """
517
+ self._fallback_tracker_store: Optional[TrackerStore] = fallback_tracker_store
518
+ self._tracker_store = tracker_store
519
+ self._on_tracker_store_error = on_tracker_store_error
520
+
521
+ super().__init__(tracker_store.domain, tracker_store.event_broker)
522
+
523
+ @property
524
+ def domain(self) -> Domain:
525
+ """Returns the domain of the primary tracker store."""
526
+ return self._tracker_store.domain
527
+
528
+ @domain.setter
529
+ def domain(self, domain: Domain) -> None:
530
+ self._tracker_store.domain = domain
531
+
532
+ if self._fallback_tracker_store:
533
+ self._fallback_tracker_store.domain = domain
534
+
535
+ @property
536
+ def fallback_tracker_store(self) -> TrackerStore:
537
+ """Returns the fallback tracker store."""
538
+ if not self._fallback_tracker_store:
539
+ self._fallback_tracker_store = InMemoryTrackerStore(
540
+ self._tracker_store.domain, self._tracker_store.event_broker
541
+ )
542
+
543
+ return self._fallback_tracker_store
544
+
545
+ def on_tracker_store_error(self, error: Exception) -> None:
546
+ """Calls the callback when there is an error in the primary tracker store."""
547
+ if self._on_tracker_store_error:
548
+ self._on_tracker_store_error(error)
549
+ else:
550
+ structlogger.error(
551
+ "fail_safe_tracker_store.tracker_store_error",
552
+ event_info=(
553
+ f"Error happened when trying to save conversation tracker to "
554
+ f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
555
+ f"the '{InMemoryTrackerStore.__name__}'. Please "
556
+ f"investigate the following error: {error}."
557
+ ),
558
+ )
559
+
560
+ async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
561
+ """Calls `retrieve` method of primary tracker store."""
562
+ try:
563
+ return await self._tracker_store.retrieve(sender_id)
564
+ except Exception as e:
565
+ self.on_tracker_store_retrieve_error(e)
566
+ return None
567
+
568
+ async def keys(self) -> Iterable[Text]:
569
+ """Calls `keys` method of primary tracker store."""
570
+ try:
571
+ return await self._tracker_store.keys()
572
+ except Exception as e:
573
+ self.on_tracker_store_error(e)
574
+ return []
575
+
576
+ async def save(self, tracker: DialogueStateTracker) -> None:
577
+ """Calls `save` method of primary tracker store."""
578
+ try:
579
+ await self._tracker_store.save(tracker)
580
+ except Exception as e:
581
+ self.on_tracker_store_error(e)
582
+ await self.fallback_tracker_store.save(tracker)
583
+
584
+ async def delete(self, sender_id: Text) -> None:
585
+ """Delete tracker for the given sender_id."""
586
+ await self._tracker_store.delete(sender_id)
587
+
588
+ async def retrieve_full_tracker(
589
+ self, sender_id: Text
590
+ ) -> Optional[DialogueStateTracker]:
591
+ """Calls `retrieve_full_tracker` method of primary tracker store.
592
+
593
+ Args:
594
+ sender_id: The sender id of the tracker to retrieve.
595
+ """
596
+ try:
597
+ return await self._tracker_store.retrieve_full_tracker(sender_id)
598
+ except Exception as e:
599
+ self.on_tracker_store_retrieve_error(e)
600
+ return None
601
+
602
+ def on_tracker_store_retrieve_error(self, error: Exception) -> None:
603
+ """Calls `_on_tracker_store_error` callable attribute if set.
604
+
605
+ Otherwise, logs the error.
606
+
607
+ Args:
608
+ error: The error that occurred.
609
+ """
610
+ if self._on_tracker_store_error:
611
+ self._on_tracker_store_error(error)
612
+ else:
613
+ structlogger.error(
614
+ "fail_safe_tracker_store.tracker_store_retrieve_error",
615
+ event_info=(
616
+ f"Error happened when trying to retrieve conversation tracker from "
617
+ f"'{self._tracker_store.__class__.__name__}'. Falling back to use "
618
+ f"the '{InMemoryTrackerStore.__name__}'."
619
+ ),
620
+ exec_info=error,
621
+ )
622
+
623
+
624
+ def _create_from_endpoint_config(
625
+ endpoint_config: Optional[EndpointConfig] = None,
626
+ domain: Optional[Domain] = None,
627
+ event_broker: Optional[EventBroker] = None,
628
+ ) -> TrackerStore:
629
+ """Given an endpoint configuration, create a proper tracker store object."""
630
+ from rasa.core.tracker_stores.dynamo_tracker_store import DynamoTrackerStore
631
+ from rasa.core.tracker_stores.mongo_tracker_store import MongoTrackerStore
632
+ from rasa.core.tracker_stores.redis_tracker_store import (
633
+ RedisTrackerStore,
634
+ )
635
+ from rasa.core.tracker_stores.sql_tracker_store import SQLTrackerStore
636
+
637
+ domain = domain or Domain.empty()
638
+
639
+ if endpoint_config is None or endpoint_config.type is None:
640
+ # default tracker store if no type is set
641
+ tracker_store: TrackerStore = InMemoryTrackerStore(domain, event_broker)
642
+ elif endpoint_config.type.lower() == "redis":
643
+ tracker_store = RedisTrackerStore(
644
+ domain=domain,
645
+ host=endpoint_config.url,
646
+ event_broker=event_broker,
647
+ **endpoint_config.kwargs,
648
+ )
649
+ elif endpoint_config.type.lower() == "mongod":
650
+ tracker_store = MongoTrackerStore(
651
+ domain=domain,
652
+ host=endpoint_config.url,
653
+ event_broker=event_broker,
654
+ **endpoint_config.kwargs,
655
+ )
656
+ elif endpoint_config.type.lower() == "sql":
657
+ tracker_store = SQLTrackerStore(
658
+ domain=domain,
659
+ host=endpoint_config.url,
660
+ event_broker=event_broker,
661
+ **endpoint_config.kwargs,
662
+ )
663
+ elif endpoint_config.type.lower() == "dynamo":
664
+ tracker_store = DynamoTrackerStore(
665
+ domain=domain, event_broker=event_broker, **endpoint_config.kwargs
666
+ )
667
+ else:
668
+ tracker_store = _load_from_module_name_in_endpoint_config(
669
+ domain, endpoint_config, event_broker
670
+ )
671
+
672
+ structlogger.debug(
673
+ "tracker_store.create_tracker_store_from_endpoint_config",
674
+ eventi_info=f"Connected to {tracker_store.__class__.__name__}.",
675
+ )
676
+
677
+ return tracker_store
678
+
679
+
680
+ def _load_from_module_name_in_endpoint_config(
681
+ domain: Domain, store: EndpointConfig, event_broker: Optional[EventBroker] = None
682
+ ) -> TrackerStore:
683
+ """Initializes a custom tracker.
684
+
685
+ Defaults to the InMemoryTrackerStore if the module path can not be found.
686
+
687
+ Args:
688
+ domain: defines the universe in which the assistant operates
689
+ store: the specific tracker store
690
+ event_broker: an event broker to publish events
691
+
692
+ Returns:
693
+ a tracker store from a specified type in a stores endpoint configuration
694
+ """
695
+ try:
696
+ tracker_store_class = rasa.shared.utils.common.class_from_module_path(
697
+ store.type
698
+ )
699
+
700
+ return tracker_store_class(
701
+ host=store.url, domain=domain, event_broker=event_broker, **store.kwargs
702
+ )
703
+ except (AttributeError, ImportError):
704
+ rasa.shared.utils.io.raise_warning(
705
+ f"Tracker store with type '{store.type}' not found. "
706
+ f"Using `InMemoryTrackerStore` instead."
707
+ )
708
+ return InMemoryTrackerStore(domain)
709
+
710
+
711
+ def create_tracker_store(
712
+ endpoint_config: Optional[EndpointConfig],
713
+ domain: Optional[Domain] = None,
714
+ event_broker: Optional[EventBroker] = None,
715
+ ) -> TrackerStore:
716
+ """Creates a tracker store based on the current configuration."""
717
+ tracker_store = _create_from_endpoint_config(endpoint_config, domain, event_broker)
718
+
719
+ if not check_if_tracker_store_async(tracker_store):
720
+ rasa.shared.utils.io.raise_deprecation_warning(
721
+ f"Tracker store implementation "
722
+ f"{tracker_store.__class__.__name__} "
723
+ f"is not asynchronous. Non-asynchronous tracker stores "
724
+ f"are currently deprecated and will be removed in 4.0. "
725
+ f"Please make the following methods async: "
726
+ f"{_get_async_tracker_store_methods()}"
727
+ )
728
+ tracker_store = AwaitableTrackerStore(tracker_store)
729
+
730
+ return tracker_store
731
+
732
+
733
+ class AwaitableTrackerStore(TrackerStore):
734
+ """Wraps a tracker store so it can be implemented with async overrides."""
735
+
736
+ def __init__(
737
+ self,
738
+ tracker_store: TrackerStore,
739
+ ) -> None:
740
+ """Create a `AwaitableTrackerStore`.
741
+
742
+ Args:
743
+ tracker_store: the wrapped tracker store.
744
+ """
745
+ self._tracker_store = tracker_store
746
+
747
+ super().__init__(tracker_store.domain, tracker_store.event_broker)
748
+
749
+ @property
750
+ def domain(self) -> Domain:
751
+ """Returns the domain of the primary tracker store."""
752
+ return self._tracker_store.domain
753
+
754
+ @domain.setter
755
+ def domain(self, domain: Optional[Domain]) -> None:
756
+ """Setter method to modify the wrapped tracker store's domain field."""
757
+ self._tracker_store.domain = domain or Domain.empty()
758
+
759
+ @staticmethod
760
+ def create(
761
+ obj: Union[TrackerStore, EndpointConfig, None],
762
+ domain: Optional[Domain] = None,
763
+ event_broker: Optional[EventBroker] = None,
764
+ ) -> TrackerStore:
765
+ """Wrapper to call `create` method of primary tracker store."""
766
+ if isinstance(obj, TrackerStore):
767
+ return AwaitableTrackerStore(obj)
768
+ elif isinstance(obj, EndpointConfig):
769
+ return AwaitableTrackerStore(_create_from_endpoint_config(obj))
770
+ else:
771
+ raise ValueError(
772
+ f"{type(obj).__name__} supplied "
773
+ f"but expected object of type {TrackerStore.__name__} or "
774
+ f"of type {EndpointConfig.__name__}."
775
+ )
776
+
777
+ async def retrieve(self, sender_id: Text) -> Optional[DialogueStateTracker]:
778
+ """Wrapper to call `retrieve` method of primary tracker store."""
779
+ result = self._tracker_store.retrieve(sender_id)
780
+ return (
781
+ await result if isawaitable(result) else result # type: ignore[return-value, misc]
782
+ )
783
+
784
+ async def keys(self) -> Iterable[Text]:
785
+ """Wrapper to call `keys` method of primary tracker store."""
786
+ result = self._tracker_store.keys()
787
+ return await result if isawaitable(result) else result
788
+
789
+ async def save(self, tracker: DialogueStateTracker) -> None:
790
+ """Wrapper to call `save` method of primary tracker store."""
791
+ result = self._tracker_store.save(tracker)
792
+ return await result if isawaitable(result) else result
793
+
794
+ async def delete(self, sender_id: Text) -> None:
795
+ """Delete tracker for the given sender_id."""
796
+ await self._tracker_store.delete(sender_id)
797
+
798
+ async def retrieve_full_tracker(
799
+ self, conversation_id: Text
800
+ ) -> Optional[DialogueStateTracker]:
801
+ """Wrapper to call `retrieve_full_tracker` method of primary tracker store."""
802
+ result = self._tracker_store.retrieve_full_tracker(conversation_id)
803
+ return (
804
+ await result if isawaitable(result) else result # type: ignore[return-value, misc]
805
+ )