rasa-pro 3.13.0.dev3__py3-none-any.whl → 3.13.0.dev5__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/__main__.py +3 -1
- rasa/cli/inspect.py +8 -4
- rasa/cli/project_templates/default/config.yml +5 -32
- rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_cancels_during_a_correction.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_changes_mind_on_a_whim.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_handle.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_name.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_adds_contact_to_their_list.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_lists_contacts.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact.yml +1 -1
- rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact_from_list.yml +1 -1
- rasa/cli/project_templates/default/endpoints.yml +18 -2
- rasa/cli/scaffold.py +3 -4
- rasa/cli/studio/download.py +1 -1
- rasa/cli/studio/upload.py +0 -6
- rasa/core/channels/channel.py +68 -5
- rasa/core/channels/inspector/dist/assets/{arc-c7691751.js → arc-9f75cc3b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-ab99dff7.js → blockDiagram-38ab4fdb-7f34db23.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-08c35a6b.js → c4Diagram-3d4e48cf-948bab2c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-dfa68278.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-9e9c71c9.js → classDiagram-70f12bd4-53b0dd0e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-15e7e2bf.js → classDiagram-v2-f2320105-fdf789e7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-edb7f119.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-9c105cb1.js → createText-2e5e7dd3-87c4ece5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-77e89e48.js → edges-e0da2a9e-5a8b0749.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-7a011646.js → erDiagram-9861fffd-66da90e2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-b6f105ac.js → flowDb-956e92f1-10044f05.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ce4f18c2.js → flowDiagram-66a62f08-f338f66a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-65e7c670.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-cb5f6da4.js → flowchart-elk-definition-4a651766-b13140aa.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-e4d19e28.js → ganttDiagram-c361ad54-f2b4a55a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-727b1c33.js → gitGraphDiagram-72cf32ee-dedc298d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-6e2ab9a7.js → graph-4ede11ff.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-84ec700f.js → index-3862675e-65549d37.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-098a1a24.js → index-3a23e736.js} +142 -129
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-78dda442.js → infoDiagram-f8f76790-65439671.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-f1cc6dd1.js → journeyDiagram-49397b02-56d03d98.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-d98dcd0c.js → layout-dd48f7f4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-838e3d82.js → line-1569ad2c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-eae72406.js → linear-48bf4935.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-c96fd84b.js → mindmap-definition-fc14e90a-688504c1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-c936d4e2.js → pieDiagram-8a3498a8-78b6d7e6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b338eb8f.js → quadrantDiagram-120e2f19-048b84b3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-c6b6c0d5.js → requirementDiagram-deff3bca-dd67f107.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-b9372e19.js → sankeyDiagram-04a897e0-8128436e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-479e0a3f.js → sequenceDiagram-704730f1-1a0d1461.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-fd26eebc.js → stateDiagram-587899a1-46d388ed.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-3233e0ae.js → stateDiagram-v2-d93cdb3a-ea42951a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-1fdd392b.js → styles-6aaf32cf-7427ed0c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-6d7bfa1b.js → styles-9a916d00-ff5e5a16.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-f86aab11.js → styles-c10674c1-7b3680cf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-e3e49d7a.js → svgDrawCommon-08f97a94-f860f2ad.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-6fe08b4d.js → timeline-definition-85554ec2-2eebf0c8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-c2e06fd6.js → xychartDiagram-e933f94c-5d7f4e96.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +3 -2
- rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
- rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
- rasa/core/channels/inspector/src/types.ts +1 -1
- rasa/core/channels/voice_ready/audiocodes.py +41 -15
- rasa/core/channels/voice_ready/twilio_voice.py +48 -1
- rasa/core/channels/voice_stream/tts/azure.py +11 -2
- rasa/core/channels/voice_stream/twilio_media_streams.py +101 -26
- rasa/core/channels/voice_stream/voice_channel.py +28 -2
- rasa/core/concurrent_lock_store.py +24 -10
- rasa/core/lock_store.py +151 -60
- rasa/dialogue_understanding_test/du_test_case.py +16 -8
- rasa/plugin.py +0 -3
- rasa/shared/constants.py +1 -0
- rasa/shared/core/domain.py +165 -11
- rasa/shared/core/flows/flow.py +155 -131
- rasa/shared/core/flows/flow_step.py +19 -3
- rasa/shared/core/flows/flow_step_links.py +15 -0
- rasa/shared/core/flows/flow_step_sequence.py +6 -0
- rasa/shared/core/flows/nlu_trigger.py +13 -0
- rasa/shared/core/flows/steps/action.py +7 -4
- rasa/shared/core/flows/steps/call.py +11 -4
- rasa/shared/core/flows/steps/collect.py +27 -6
- rasa/shared/core/flows/steps/internal.py +6 -1
- rasa/shared/core/flows/steps/link.py +7 -4
- rasa/shared/core/flows/steps/no_operation.py +7 -4
- rasa/shared/core/flows/steps/set_slots.py +8 -4
- rasa/shared/core/flows/yaml_flows_io.py +106 -5
- rasa/shared/importers/importer.py +8 -0
- rasa/shared/providers/_utils.py +83 -0
- rasa/shared/providers/llm/_base_litellm_client.py +6 -3
- rasa/shared/providers/llm/azure_openai_llm_client.py +6 -68
- rasa/shared/providers/router/_base_litellm_router_client.py +53 -1
- rasa/shared/utils/common.py +42 -0
- rasa/studio/download/domains.py +49 -0
- rasa/studio/download/download.py +439 -0
- rasa/studio/download/flows.py +359 -0
- rasa/studio/results_logger.py +6 -1
- rasa/studio/upload.py +69 -5
- rasa/utils/common.py +36 -0
- rasa/utils/endpoints.py +22 -1
- rasa/utils/licensing.py +1 -1
- rasa/validator.py +1 -2
- rasa/version.py +1 -1
- {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/METADATA +8 -8
- {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/RECORD +119 -125
- rasa/cli/project_templates/calm/config.yml +0 -10
- rasa/cli/project_templates/calm/credentials.yml +0 -33
- rasa/cli/project_templates/calm/endpoints.yml +0 -58
- rasa/cli/project_templates/default/actions/actions.py +0 -27
- rasa/cli/project_templates/default/data/nlu.yml +0 -91
- rasa/cli/project_templates/default/data/rules.yml +0 -13
- rasa/cli/project_templates/default/data/stories.yml +0 -30
- rasa/cli/project_templates/default/domain.yml +0 -34
- rasa/cli/project_templates/default/tests/test_stories.yml +0 -91
- rasa/core/channels/inspector/dist/assets/channel-11268142.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-ff7f2ce7.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-cba7ae20.js +0 -1
- rasa/studio/download.py +0 -489
- /rasa/cli/project_templates/{calm → default}/actions/action_template.py +0 -0
- /rasa/cli/project_templates/{calm → default}/actions/add_contact.py +0 -0
- /rasa/cli/project_templates/{calm → default}/actions/db.py +0 -0
- /rasa/cli/project_templates/{calm → default}/actions/list_contacts.py +0 -0
- /rasa/cli/project_templates/{calm → default}/actions/remove_contact.py +0 -0
- /rasa/cli/project_templates/{calm → default}/data/flows/add_contact.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/data/flows/list_contacts.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/data/flows/remove_contact.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/db/contacts.json +0 -0
- /rasa/cli/project_templates/{calm → default}/domain/add_contact.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/domain/list_contacts.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/domain/remove_contact.yml +0 -0
- /rasa/cli/project_templates/{calm → default}/domain/shared.yml +0 -0
- /rasa/{cli/project_templates/calm/actions → studio/download}/__init__.py +0 -0
- {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/WHEEL +0 -0
- {rasa_pro-3.13.0.dev3.dist-info → rasa_pro-3.13.0.dev5.dist-info}/entry_points.txt +0 -0
rasa/core/lock_store.py
CHANGED
|
@@ -2,18 +2,27 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import json
|
|
5
|
-
import logging
|
|
6
5
|
import os
|
|
7
6
|
from contextlib import asynccontextmanager
|
|
8
|
-
from typing import AsyncGenerator, Dict, Optional, Text, Union
|
|
7
|
+
from typing import Any, AsyncGenerator, Dict, Literal, Optional, Text, Union
|
|
8
|
+
|
|
9
|
+
import structlog
|
|
10
|
+
from pydantic import (
|
|
11
|
+
AnyUrl,
|
|
12
|
+
BaseModel,
|
|
13
|
+
Field,
|
|
14
|
+
NonNegativeInt,
|
|
15
|
+
model_validator,
|
|
16
|
+
)
|
|
9
17
|
|
|
10
18
|
import rasa.shared.utils.common
|
|
11
19
|
from rasa.core.constants import DEFAULT_LOCK_LIFETIME
|
|
12
20
|
from rasa.core.lock import TicketLock
|
|
13
21
|
from rasa.shared.exceptions import ConnectionException, RasaException
|
|
22
|
+
from rasa.shared.utils.io import raise_deprecation_warning
|
|
14
23
|
from rasa.utils.endpoints import EndpointConfig
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
structlogger = structlog.getLogger(__name__)
|
|
17
26
|
|
|
18
27
|
|
|
19
28
|
def _get_lock_lifetime() -> int:
|
|
@@ -76,7 +85,10 @@ class LockStore:
|
|
|
76
85
|
|
|
77
86
|
Creates a new lock if none is found.
|
|
78
87
|
"""
|
|
79
|
-
|
|
88
|
+
structlogger.debug(
|
|
89
|
+
"lock_store.issue_ticket",
|
|
90
|
+
event_info=f"Issuing ticket for conversation '{conversation_id}'.",
|
|
91
|
+
)
|
|
80
92
|
try:
|
|
81
93
|
lock = self.get_or_create_lock(conversation_id)
|
|
82
94
|
ticket = lock.issue_ticket(lock_lifetime)
|
|
@@ -109,7 +121,10 @@ class LockStore:
|
|
|
109
121
|
async def _acquire_lock(
|
|
110
122
|
self, conversation_id: Text, ticket: int, wait_time_in_seconds: float
|
|
111
123
|
) -> TicketLock:
|
|
112
|
-
|
|
124
|
+
structlogger.debug(
|
|
125
|
+
"lock_store._acquiring_lock_for_conversation",
|
|
126
|
+
event_info=f"Acquiring lock for conversation '{conversation_id}'.",
|
|
127
|
+
)
|
|
113
128
|
while True:
|
|
114
129
|
# fetch lock in every iteration because lock might no longer exist
|
|
115
130
|
lock = self.get_lock(conversation_id)
|
|
@@ -120,16 +135,22 @@ class LockStore:
|
|
|
120
135
|
|
|
121
136
|
# acquire lock if it isn't locked
|
|
122
137
|
if not lock.is_locked(ticket):
|
|
123
|
-
|
|
138
|
+
structlogger.debug(
|
|
139
|
+
"lock_store._acquired_lock_for_conversation",
|
|
140
|
+
event_info=f"Acquired lock for conversation '{conversation_id}'.",
|
|
141
|
+
)
|
|
124
142
|
return lock
|
|
125
143
|
|
|
126
144
|
items_before_this = ticket - (lock.now_serving or 0)
|
|
127
145
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
146
|
+
structlogger.debug(
|
|
147
|
+
"lock_store._retrying_lock_acquisition",
|
|
148
|
+
event_info=(
|
|
149
|
+
f"Failed to acquire lock for conversation ID '{conversation_id}' "
|
|
150
|
+
f"because {items_before_this} other item(s) for this "
|
|
151
|
+
f"conversation ID have to be finished processing first. "
|
|
152
|
+
f"Retrying in {wait_time_in_seconds} seconds ..."
|
|
153
|
+
),
|
|
133
154
|
)
|
|
134
155
|
|
|
135
156
|
# sleep and update lock
|
|
@@ -186,9 +207,99 @@ class LockStore:
|
|
|
186
207
|
@staticmethod
|
|
187
208
|
def _log_deletion(conversation_id: Text, deletion_successful: bool) -> None:
|
|
188
209
|
if deletion_successful:
|
|
189
|
-
|
|
210
|
+
structlogger.debug(
|
|
211
|
+
"lock_store._deleted_lock_for_conversation",
|
|
212
|
+
event_info=f"Deleted lock for conversation '{conversation_id}'.",
|
|
213
|
+
)
|
|
190
214
|
else:
|
|
191
|
-
|
|
215
|
+
structlogger.debug(
|
|
216
|
+
"lock_store._failed_to_delete_lock_for_conversation",
|
|
217
|
+
event_info=(
|
|
218
|
+
f"Could not delete lock for conversation '{conversation_id}'."
|
|
219
|
+
),
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
class RedisLockStoreConfig(BaseModel):
|
|
224
|
+
host: Union[AnyUrl, Literal["localhost"]] = Field(
|
|
225
|
+
default="localhost", description="The host of the redis server."
|
|
226
|
+
)
|
|
227
|
+
port: NonNegativeInt = Field(
|
|
228
|
+
default=6379, ge=0, le=65535, description="The port of the redis server."
|
|
229
|
+
)
|
|
230
|
+
db: NonNegativeInt = Field(
|
|
231
|
+
default=0,
|
|
232
|
+
ge=0,
|
|
233
|
+
description="The name of the database within Redis "
|
|
234
|
+
"which should be used by Rasa",
|
|
235
|
+
)
|
|
236
|
+
username: Optional[str] = Field(
|
|
237
|
+
default=None,
|
|
238
|
+
description="The username which should be used for "
|
|
239
|
+
"authentication with the Redis database.",
|
|
240
|
+
)
|
|
241
|
+
password: Optional[str] = Field(
|
|
242
|
+
default=None,
|
|
243
|
+
description="The username which should be used for "
|
|
244
|
+
"authentication with the Redis database.",
|
|
245
|
+
)
|
|
246
|
+
use_ssl: bool = Field(
|
|
247
|
+
default=False,
|
|
248
|
+
serialization_alias="ssl",
|
|
249
|
+
description="True if SSL should be used for the connection to Redis.",
|
|
250
|
+
)
|
|
251
|
+
ssl_certfile: Optional[str] = Field(
|
|
252
|
+
default=None,
|
|
253
|
+
description="Path to the SSL certificate file.",
|
|
254
|
+
)
|
|
255
|
+
ssl_keyfile: Optional[str] = Field(
|
|
256
|
+
default=None, description="Path to the SSL private key file."
|
|
257
|
+
)
|
|
258
|
+
ssl_ca_certs: Optional[str] = Field(
|
|
259
|
+
default=None, description="Path to the SSL CA certificate file."
|
|
260
|
+
)
|
|
261
|
+
key_prefix: Optional[str] = Field(
|
|
262
|
+
default=None,
|
|
263
|
+
description="Prefix to prepend to all keys "
|
|
264
|
+
"used by the lock store. Must be alphanumeric.",
|
|
265
|
+
)
|
|
266
|
+
socket_timeout: float = Field(
|
|
267
|
+
default=DEFAULT_SOCKET_TIMEOUT_IN_SECONDS,
|
|
268
|
+
description="Timeout in seconds after which an exception "
|
|
269
|
+
"will be raised in case Redis doesn't respond "
|
|
270
|
+
"within `socket_timeout` seconds.",
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
@model_validator(mode="before")
|
|
274
|
+
@classmethod
|
|
275
|
+
def validate_url_and_host_properties(cls, data: Any) -> Any:
|
|
276
|
+
if isinstance(data, dict):
|
|
277
|
+
if bool(data.get("url", None)) and bool(data.get("host", None)):
|
|
278
|
+
raise RasaException(
|
|
279
|
+
"You cannot specify both 'url' and 'host' in the Redis lock store "
|
|
280
|
+
"configuration. Please use only one of them."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
if data.get("url", None):
|
|
284
|
+
raise_deprecation_warning(
|
|
285
|
+
"The 'url' property in the redis lock store "
|
|
286
|
+
"configuration is deprecated. Please use 'host' instead."
|
|
287
|
+
)
|
|
288
|
+
data["host"] = data.pop("url")
|
|
289
|
+
return data
|
|
290
|
+
|
|
291
|
+
@model_validator(mode="after")
|
|
292
|
+
def verify_username_password(self) -> RedisLockStoreConfig:
|
|
293
|
+
if bool(self.username) ^ bool(self.password):
|
|
294
|
+
raise ValueError(
|
|
295
|
+
f"Expected username and password. "
|
|
296
|
+
f"Found: username: {'<has value>' if self.username else '<N/A>'}, "
|
|
297
|
+
f"password: {'<has value>' if self.password else '<N/A>'}"
|
|
298
|
+
)
|
|
299
|
+
return self
|
|
300
|
+
|
|
301
|
+
def to_strict_redis(self) -> Dict[str, Any]:
|
|
302
|
+
return self.model_dump(by_alias=True, exclude={"key_prefix"})
|
|
192
303
|
|
|
193
304
|
|
|
194
305
|
class RedisLockStore(LockStore):
|
|
@@ -196,57 +307,28 @@ class RedisLockStore(LockStore):
|
|
|
196
307
|
|
|
197
308
|
def __init__(
|
|
198
309
|
self,
|
|
199
|
-
|
|
200
|
-
port: int = 6379,
|
|
201
|
-
db: int = 1,
|
|
202
|
-
username: Optional[Text] = None,
|
|
203
|
-
password: Optional[Text] = None,
|
|
204
|
-
use_ssl: bool = False,
|
|
205
|
-
ssl_certfile: Optional[Text] = None,
|
|
206
|
-
ssl_keyfile: Optional[Text] = None,
|
|
207
|
-
ssl_ca_certs: Optional[Text] = None,
|
|
208
|
-
key_prefix: Optional[Text] = None,
|
|
209
|
-
socket_timeout: float = DEFAULT_SOCKET_TIMEOUT_IN_SECONDS,
|
|
310
|
+
config: RedisLockStoreConfig = RedisLockStoreConfig(),
|
|
210
311
|
) -> None:
|
|
211
312
|
"""Create a lock store which uses Redis for persistence.
|
|
212
313
|
|
|
213
314
|
Args:
|
|
214
|
-
|
|
215
|
-
port: The port of the redis server.
|
|
216
|
-
db: The name of the database within Redis which should be used by Rasa
|
|
217
|
-
Open Source.
|
|
218
|
-
username: The username which should be used for authentication with the
|
|
219
|
-
Redis database.
|
|
220
|
-
password: The password which should be used for authentication with the
|
|
221
|
-
Redis database.
|
|
222
|
-
use_ssl: `True` if SSL should be used for the connection to Redis.
|
|
223
|
-
ssl_certfile: Path to the SSL certificate file.
|
|
224
|
-
ssl_keyfile: Path to the SSL private key file.
|
|
225
|
-
ssl_ca_certs: Path to the SSL CA certificate file.
|
|
226
|
-
key_prefix: prefix to prepend to all keys used by the lock store. Must be
|
|
227
|
-
alphanumeric.
|
|
228
|
-
socket_timeout: Timeout in seconds after which an exception will be raised
|
|
229
|
-
in case Redis doesn't respond within `socket_timeout` seconds.
|
|
315
|
+
config: Redis lock store configuration.
|
|
230
316
|
"""
|
|
231
317
|
import redis
|
|
232
318
|
|
|
233
|
-
self.
|
|
234
|
-
|
|
235
|
-
port=int(port),
|
|
236
|
-
db=int(db),
|
|
237
|
-
username=username,
|
|
238
|
-
password=password,
|
|
239
|
-
ssl=use_ssl,
|
|
240
|
-
ssl_certfile=ssl_certfile,
|
|
241
|
-
ssl_keyfile=ssl_keyfile,
|
|
242
|
-
ssl_ca_certs=ssl_ca_certs,
|
|
243
|
-
socket_timeout=socket_timeout,
|
|
244
|
-
)
|
|
319
|
+
self.config = config
|
|
320
|
+
self.red = redis.StrictRedis(**self.config.to_strict_redis())
|
|
245
321
|
|
|
246
322
|
self.key_prefix = DEFAULT_REDIS_LOCK_STORE_KEY_PREFIX
|
|
247
|
-
if key_prefix:
|
|
248
|
-
|
|
249
|
-
|
|
323
|
+
if self.config.key_prefix:
|
|
324
|
+
structlogger.debug(
|
|
325
|
+
"redis_lock_store._set_key_prefix.non_default_key_prefix",
|
|
326
|
+
event_info=(
|
|
327
|
+
f"Setting non-default "
|
|
328
|
+
f"redis key prefix: '{self.config.key_prefix}'.",
|
|
329
|
+
),
|
|
330
|
+
)
|
|
331
|
+
self._set_key_prefix(self.config.key_prefix)
|
|
250
332
|
|
|
251
333
|
super().__init__()
|
|
252
334
|
|
|
@@ -254,9 +336,13 @@ class RedisLockStore(LockStore):
|
|
|
254
336
|
if isinstance(key_prefix, str) and key_prefix.isalnum():
|
|
255
337
|
self.key_prefix = key_prefix + ":" + DEFAULT_REDIS_LOCK_STORE_KEY_PREFIX
|
|
256
338
|
else:
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
339
|
+
structlogger.warning(
|
|
340
|
+
"redis_lock_store._set_key_prefix.default_instead_of_invalid_key_prefix",
|
|
341
|
+
event_info=(
|
|
342
|
+
f"Omitting provided non-alphanumeric "
|
|
343
|
+
f"redis key prefix: '{key_prefix}'. "
|
|
344
|
+
f"Using default '{self.key_prefix}' instead."
|
|
345
|
+
),
|
|
260
346
|
)
|
|
261
347
|
|
|
262
348
|
def get_lock(self, conversation_id: Text) -> Optional[TicketLock]:
|
|
@@ -313,7 +399,9 @@ def _create_from_endpoint_config(
|
|
|
313
399
|
|
|
314
400
|
lock_store: LockStore = InMemoryLockStore()
|
|
315
401
|
elif endpoint_config.type == "redis":
|
|
316
|
-
|
|
402
|
+
config = RedisLockStoreConfig.model_validate(endpoint_config.to_dict())
|
|
403
|
+
|
|
404
|
+
lock_store = RedisLockStore(config)
|
|
317
405
|
elif endpoint_config.type == "concurrent_redis":
|
|
318
406
|
from rasa.core.concurrent_lock_store import ConcurrentRedisLockStore
|
|
319
407
|
|
|
@@ -321,7 +409,10 @@ def _create_from_endpoint_config(
|
|
|
321
409
|
else:
|
|
322
410
|
lock_store = _load_from_module_name_in_endpoint_config(endpoint_config)
|
|
323
411
|
|
|
324
|
-
|
|
412
|
+
structlogger.debug(
|
|
413
|
+
"lock_store._create_from_endpoint_config.lock_store_connected",
|
|
414
|
+
event_info=f"Connected to lock store '{lock_store.__class__.__name__}'.",
|
|
415
|
+
)
|
|
325
416
|
|
|
326
417
|
return lock_store
|
|
327
418
|
|
|
@@ -261,19 +261,27 @@ class DialogueUnderstandingTestStep(BaseModel):
|
|
|
261
261
|
# Safely extract commands from the step.
|
|
262
262
|
commands = []
|
|
263
263
|
for command in step.get(KEY_COMMANDS, []):
|
|
264
|
+
parsed_commands = None
|
|
264
265
|
try:
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
default_commands_to_remove=remove_default_commands,
|
|
272
|
-
)
|
|
266
|
+
parsed_commands = parse_commands(
|
|
267
|
+
command,
|
|
268
|
+
flows,
|
|
269
|
+
clarify_options_optional=True,
|
|
270
|
+
additional_commands=custom_command_classes,
|
|
271
|
+
default_commands_to_remove=remove_default_commands,
|
|
273
272
|
)
|
|
274
273
|
except (IndexError, ValueError) as e:
|
|
275
274
|
raise ValueError(f"Failed to parse command '{command}': {e}") from e
|
|
276
275
|
|
|
276
|
+
if not parsed_commands:
|
|
277
|
+
raise ValueError(
|
|
278
|
+
f"Failed to parse command '{command}': command parser returned "
|
|
279
|
+
f"None. Please make sure that you are using the correct command "
|
|
280
|
+
f"syntax and the command arguments are valid."
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
commands.extend(parsed_commands)
|
|
284
|
+
|
|
277
285
|
# Construct the DialogueUnderstandingTestStep
|
|
278
286
|
return DialogueUnderstandingTestStep(
|
|
279
287
|
actor=ACTOR_USER if ACTOR_USER in step else ACTOR_BOT,
|
rasa/plugin.py
CHANGED
|
@@ -32,11 +32,8 @@ def plugin_manager() -> pluggy.PluginManager:
|
|
|
32
32
|
|
|
33
33
|
def init_hooks(manager: pluggy.PluginManager) -> None:
|
|
34
34
|
"""Initialise hooks into rasa."""
|
|
35
|
-
import rasa.utils.licensing
|
|
36
35
|
from rasa import hooks
|
|
37
36
|
|
|
38
|
-
rasa.utils.licensing.validate_license_from_env()
|
|
39
|
-
|
|
40
37
|
manager.register(hooks)
|
|
41
38
|
|
|
42
39
|
|
rasa/shared/constants.py
CHANGED
|
@@ -238,6 +238,7 @@ EXTRA_PARAMETERS_KEY = "extra_parameters"
|
|
|
238
238
|
MODEL_GROUP_ID_KEY = "model_group_id"
|
|
239
239
|
MODEL_LIST_KEY = "model_list"
|
|
240
240
|
LITELLM_PARAMS_KEY = "litellm_params"
|
|
241
|
+
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY = "missing_keys"
|
|
241
242
|
|
|
242
243
|
LLM_API_HEALTH_CHECK_ENV_VAR = "LLM_API_HEALTH_CHECK"
|
|
243
244
|
LLM_API_HEALTH_CHECK_DEFAULT_VALUE = "false"
|
rasa/shared/core/domain.py
CHANGED
|
@@ -134,6 +134,22 @@ ALL_DOMAIN_KEYS = [
|
|
|
134
134
|
|
|
135
135
|
PREV_PREFIX = "prev_"
|
|
136
136
|
|
|
137
|
+
MERGE_FUNC_MAPPING: Dict[Text, Callable[..., Any]] = {
|
|
138
|
+
KEY_ACTIONS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
139
|
+
KEY_RESPONSES: rasa.shared.utils.common.merge_dicts,
|
|
140
|
+
KEY_SLOTS: rasa.shared.utils.common.merge_dicts,
|
|
141
|
+
KEY_INTENTS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
142
|
+
KEY_ENTITIES: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
143
|
+
KEY_E2E_ACTIONS: rasa.shared.utils.common.merge_lists,
|
|
144
|
+
KEY_FORMS: rasa.shared.utils.common.merge_dicts,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
DICT_DATA_KEYS = [
|
|
148
|
+
key
|
|
149
|
+
for key, value in MERGE_FUNC_MAPPING.items()
|
|
150
|
+
if value == rasa.shared.utils.common.merge_dicts
|
|
151
|
+
]
|
|
152
|
+
|
|
137
153
|
# State is a dictionary with keys (USER, PREVIOUS_ACTION, SLOTS, ACTIVE_LOOP)
|
|
138
154
|
# representing the origin of a SubState;
|
|
139
155
|
# the values are SubStates, that contain the information needed for featurization
|
|
@@ -466,17 +482,7 @@ class Domain:
|
|
|
466
482
|
|
|
467
483
|
duplicates: Dict[Text, List[Text]] = {}
|
|
468
484
|
|
|
469
|
-
|
|
470
|
-
KEY_INTENTS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
471
|
-
KEY_ENTITIES: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
472
|
-
KEY_ACTIONS: rasa.shared.utils.common.merge_lists_of_dicts,
|
|
473
|
-
KEY_E2E_ACTIONS: rasa.shared.utils.common.merge_lists,
|
|
474
|
-
KEY_FORMS: rasa.shared.utils.common.merge_dicts,
|
|
475
|
-
KEY_RESPONSES: rasa.shared.utils.common.merge_dicts,
|
|
476
|
-
KEY_SLOTS: rasa.shared.utils.common.merge_dicts,
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
for key, merge_func in merge_func_mappings.items():
|
|
485
|
+
for key, merge_func in MERGE_FUNC_MAPPING.items():
|
|
480
486
|
duplicates[key] = rasa.shared.utils.common.extract_duplicates(
|
|
481
487
|
combined.get(key, []), domain_dict.get(key, [])
|
|
482
488
|
)
|
|
@@ -494,6 +500,74 @@ class Domain:
|
|
|
494
500
|
|
|
495
501
|
return combined
|
|
496
502
|
|
|
503
|
+
def partial_merge(self, other: Domain) -> Domain:
|
|
504
|
+
"""
|
|
505
|
+
Returns a new Domain with intersection-based merging:
|
|
506
|
+
- For each domain section only overwrite items that already exist in self.
|
|
507
|
+
- Brand-new items in `other` are ignored.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
other: The domain to merge with.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
A new Domain object with the merged content.
|
|
514
|
+
"""
|
|
515
|
+
updated_self = copy.deepcopy(self.as_dict())
|
|
516
|
+
other_dict = other.as_dict()
|
|
517
|
+
|
|
518
|
+
keys_to_merge = MERGE_FUNC_MAPPING.keys()
|
|
519
|
+
for key in keys_to_merge:
|
|
520
|
+
if key in DICT_DATA_KEYS:
|
|
521
|
+
# Merge dictionaries
|
|
522
|
+
self_val = updated_self.get(key, {})
|
|
523
|
+
other_val = other_dict.get(key, {})
|
|
524
|
+
updated_self[key] = rasa.shared.utils.common.partial_merge_dict(
|
|
525
|
+
self_val, other_val
|
|
526
|
+
)
|
|
527
|
+
else:
|
|
528
|
+
# Merge lists
|
|
529
|
+
self_val = updated_self.get(key, [])
|
|
530
|
+
other_val = other_dict.get(key, [])
|
|
531
|
+
is_same_item_fn = SAME_ITEM_FUNCTIONS.get(key, default_is_same_item)
|
|
532
|
+
updated_self[key] = rasa.shared.utils.common.partial_merge_list(
|
|
533
|
+
self_val, other_val, is_same_item_fn
|
|
534
|
+
)
|
|
535
|
+
|
|
536
|
+
return Domain.from_dict(updated_self)
|
|
537
|
+
|
|
538
|
+
def difference(self, other: Domain) -> Domain:
|
|
539
|
+
"""
|
|
540
|
+
Returns a new Domain containing items in `self` that are NOT in `other`,
|
|
541
|
+
using simple equality checks for dict/list items.
|
|
542
|
+
|
|
543
|
+
Args:
|
|
544
|
+
other: The domain to compare with.
|
|
545
|
+
|
|
546
|
+
Returns:
|
|
547
|
+
A new Domain object with the difference content.
|
|
548
|
+
"""
|
|
549
|
+
self_dict = self.as_dict()
|
|
550
|
+
other_dict = other.as_dict()
|
|
551
|
+
|
|
552
|
+
difference_dict = {}
|
|
553
|
+
for key in MERGE_FUNC_MAPPING.keys():
|
|
554
|
+
is_dict = key in DICT_DATA_KEYS
|
|
555
|
+
self_val = self_dict.get(key, {} if is_dict else [])
|
|
556
|
+
other_val = other_dict.get(key, {} if is_dict else [])
|
|
557
|
+
|
|
558
|
+
if is_dict and isinstance(self_val, dict) and isinstance(other_val, dict):
|
|
559
|
+
difference_dict[key] = {
|
|
560
|
+
k: v
|
|
561
|
+
for k, v in self_val.items()
|
|
562
|
+
if k not in other_val or v != other_val[k]
|
|
563
|
+
}
|
|
564
|
+
else:
|
|
565
|
+
difference_dict[key] = [
|
|
566
|
+
item for item in self_val if item not in other_val
|
|
567
|
+
] # type: ignore[assignment]
|
|
568
|
+
|
|
569
|
+
return Domain.from_dict(difference_dict)
|
|
570
|
+
|
|
497
571
|
def _preprocess_domain_dict(
|
|
498
572
|
self,
|
|
499
573
|
data: Dict,
|
|
@@ -2120,6 +2194,11 @@ class Domain:
|
|
|
2120
2194
|
"""Remove all builtin slots from the domain."""
|
|
2121
2195
|
self.slots = [slot for slot in self.slots if not slot.is_builtin]
|
|
2122
2196
|
|
|
2197
|
+
def __eq__(self, other: object) -> bool:
|
|
2198
|
+
if isinstance(other, Domain):
|
|
2199
|
+
return self.as_dict() == other.as_dict()
|
|
2200
|
+
return False
|
|
2201
|
+
|
|
2123
2202
|
|
|
2124
2203
|
def warn_about_duplicates_found_during_domain_merging(
|
|
2125
2204
|
duplicates: Dict[Text, List[Text]],
|
|
@@ -2178,3 +2257,78 @@ def _validate_forms(forms: Union[Dict, List]) -> None:
|
|
|
2178
2257
|
f"the keyword `{REQUIRED_SLOTS_KEY}` is required. "
|
|
2179
2258
|
f"Please see {DOCS_URL_FORMS} for more information."
|
|
2180
2259
|
)
|
|
2260
|
+
|
|
2261
|
+
|
|
2262
|
+
def is_same_entity(e1: Any, e2: Any) -> bool:
|
|
2263
|
+
"""Check if two entities are the 'same' (string or dict).
|
|
2264
|
+
|
|
2265
|
+
Args:
|
|
2266
|
+
e1: First entity to compare.
|
|
2267
|
+
e2: Second entity to compare.
|
|
2268
|
+
|
|
2269
|
+
Returns:
|
|
2270
|
+
True if the entities are the same, False otherwise.
|
|
2271
|
+
"""
|
|
2272
|
+
if isinstance(e1, str) and isinstance(e2, str):
|
|
2273
|
+
return e1 == e2
|
|
2274
|
+
|
|
2275
|
+
if isinstance(e1, dict) and isinstance(e2, dict):
|
|
2276
|
+
return (
|
|
2277
|
+
e1.get(ENTITY_ATTRIBUTE_TYPE) == e2.get(ENTITY_ATTRIBUTE_TYPE)
|
|
2278
|
+
and e1.get(ENTITY_ATTRIBUTE_ROLE) == e2.get(ENTITY_ATTRIBUTE_ROLE)
|
|
2279
|
+
and e1.get(ENTITY_ATTRIBUTE_GROUP) == e2.get(ENTITY_ATTRIBUTE_GROUP)
|
|
2280
|
+
)
|
|
2281
|
+
|
|
2282
|
+
return False
|
|
2283
|
+
|
|
2284
|
+
|
|
2285
|
+
def is_same_intent(i1: Any, i2: Any) -> bool:
|
|
2286
|
+
"""Check if two intents are the 'same' (string or dict).
|
|
2287
|
+
|
|
2288
|
+
Args:
|
|
2289
|
+
i1: First intent to compare.
|
|
2290
|
+
i2: Second intent to compare.
|
|
2291
|
+
|
|
2292
|
+
Returns:
|
|
2293
|
+
True if the intents are the same, False otherwise.
|
|
2294
|
+
"""
|
|
2295
|
+
if isinstance(i1, str) and isinstance(i2, str):
|
|
2296
|
+
return i1 == i2
|
|
2297
|
+
|
|
2298
|
+
if isinstance(i1, dict) and isinstance(i2, dict):
|
|
2299
|
+
key1, key2 = next(iter(i1.keys())), next(iter((i2.keys())))
|
|
2300
|
+
return key1 == key2
|
|
2301
|
+
|
|
2302
|
+
return False
|
|
2303
|
+
|
|
2304
|
+
|
|
2305
|
+
def is_same_action(a1: Any, a2: Any) -> bool:
|
|
2306
|
+
"""Check if two actions are the 'same' (string or dict).
|
|
2307
|
+
|
|
2308
|
+
Args:
|
|
2309
|
+
a1: First action to compare.
|
|
2310
|
+
a2: Second action to compare.
|
|
2311
|
+
|
|
2312
|
+
Returns:
|
|
2313
|
+
True if the actions are the same, False otherwise.
|
|
2314
|
+
"""
|
|
2315
|
+
if isinstance(a1, str) and isinstance(a2, str):
|
|
2316
|
+
return a1 == a2
|
|
2317
|
+
|
|
2318
|
+
if isinstance(a1, dict) and isinstance(a2, dict):
|
|
2319
|
+
key1, key2 = next(iter((a1.keys()))), next(iter((a2.keys())))
|
|
2320
|
+
return key1 == key2
|
|
2321
|
+
|
|
2322
|
+
return False
|
|
2323
|
+
|
|
2324
|
+
|
|
2325
|
+
def default_is_same_item(a: Any, b: Any) -> bool:
|
|
2326
|
+
"""Fallback exact equality check if a key doesn't need special handling."""
|
|
2327
|
+
return a == b
|
|
2328
|
+
|
|
2329
|
+
|
|
2330
|
+
SAME_ITEM_FUNCTIONS: Dict[Text, Callable[[Any, Any], bool]] = {
|
|
2331
|
+
KEY_ENTITIES: is_same_entity,
|
|
2332
|
+
KEY_INTENTS: is_same_intent,
|
|
2333
|
+
KEY_ACTIONS: is_same_action,
|
|
2334
|
+
}
|