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
|
@@ -34,17 +34,20 @@ class NoOperationFlowStep(FlowStep):
|
|
|
34
34
|
**base.__dict__,
|
|
35
35
|
)
|
|
36
36
|
|
|
37
|
-
def as_json(self) -> Dict[Text, Any]:
|
|
37
|
+
def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
|
|
38
38
|
"""Serialize the NoOperationFlowStep object
|
|
39
39
|
|
|
40
40
|
Returns:
|
|
41
41
|
the NoOperationFlowStep object as serialized data.
|
|
42
42
|
"""
|
|
43
|
-
|
|
44
|
-
data["noop"] = self.noop
|
|
45
|
-
return data
|
|
43
|
+
return super().as_json(step_properties={"noop": self.noop})
|
|
46
44
|
|
|
47
45
|
@property
|
|
48
46
|
def default_id_postfix(self) -> str:
|
|
49
47
|
"""Returns the default id postfix of the flow step."""
|
|
50
48
|
return "noop"
|
|
49
|
+
|
|
50
|
+
def __eq__(self, other: object) -> bool:
|
|
51
|
+
if isinstance(other, type(self)):
|
|
52
|
+
return self.noop == other.noop and super().__eq__(other)
|
|
53
|
+
return False
|
|
@@ -35,17 +35,21 @@ class SetSlotsFlowStep(FlowStep):
|
|
|
35
35
|
**base.__dict__,
|
|
36
36
|
)
|
|
37
37
|
|
|
38
|
-
def as_json(self) -> Dict[Text, Any]:
|
|
38
|
+
def as_json(self) -> Dict[Text, Any]: # type: ignore[override]
|
|
39
39
|
"""Serialize the SetSlotsFlowStep object
|
|
40
40
|
|
|
41
41
|
Returns:
|
|
42
42
|
the SetSlotsFlowStep object as serialized data
|
|
43
43
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return data
|
|
44
|
+
set_slots = [{slot["key"]: slot["value"]} for slot in self.slots]
|
|
45
|
+
return super().as_json(step_properties={"set_slots": set_slots})
|
|
47
46
|
|
|
48
47
|
@property
|
|
49
48
|
def default_id_postfix(self) -> str:
|
|
50
49
|
"""Returns the default id postfix of the flow step."""
|
|
51
50
|
return "set_slots"
|
|
51
|
+
|
|
52
|
+
def __eq__(self, other: object) -> bool:
|
|
53
|
+
if isinstance(other, type(self)):
|
|
54
|
+
return self.slots == other.slots and super().__eq__(other)
|
|
55
|
+
return False
|
|
@@ -8,8 +8,15 @@ from ruamel import yaml as yaml
|
|
|
8
8
|
import rasa.shared
|
|
9
9
|
import rasa.shared.data
|
|
10
10
|
import rasa.shared.utils.io
|
|
11
|
-
from rasa.shared.core.flows.flow import Flow
|
|
11
|
+
from rasa.shared.core.flows.flow import DEFAULT_RUN_PATTERN_COMPLETED, Flow
|
|
12
|
+
from rasa.shared.core.flows.flow_step import FlowStep
|
|
12
13
|
from rasa.shared.core.flows.flows_list import FlowsList
|
|
14
|
+
from rasa.shared.core.flows.steps import CollectInformationFlowStep
|
|
15
|
+
from rasa.shared.core.flows.steps.collect import (
|
|
16
|
+
DEFAULT_ASK_BEFORE_FILLING,
|
|
17
|
+
DEFAULT_FORCE_SLOT_FILLING,
|
|
18
|
+
DEFAULT_RESET_AFTER_FLOW_ENDS,
|
|
19
|
+
)
|
|
13
20
|
from rasa.shared.exceptions import RasaException, YamlException
|
|
14
21
|
from rasa.shared.utils.yaml import (
|
|
15
22
|
dump_obj_as_yaml_to_string,
|
|
@@ -242,31 +249,43 @@ class YamlFlowsWriter:
|
|
|
242
249
|
"""Class that writes flows information in YAML format."""
|
|
243
250
|
|
|
244
251
|
@staticmethod
|
|
245
|
-
def dumps(
|
|
252
|
+
def dumps(
|
|
253
|
+
flows: List[Flow],
|
|
254
|
+
should_clean_json: bool = False,
|
|
255
|
+
) -> Text:
|
|
246
256
|
"""Dump `Flow`s to YAML.
|
|
247
257
|
|
|
248
258
|
Args:
|
|
249
259
|
flows: The `Flow`s to dump.
|
|
260
|
+
should_clean_json: Flag whether to clean the flow JSON.
|
|
250
261
|
|
|
251
262
|
Returns:
|
|
252
263
|
The dumped YAML.
|
|
253
264
|
"""
|
|
254
265
|
dump = {}
|
|
255
266
|
for flow in flows:
|
|
256
|
-
dumped_flow = flow
|
|
267
|
+
dumped_flow = get_flow_as_json(flow, should_clean_json)
|
|
257
268
|
del dumped_flow["id"]
|
|
258
269
|
dump[flow.id] = dumped_flow
|
|
259
270
|
return dump_obj_as_yaml_to_string({KEY_FLOWS: dump})
|
|
260
271
|
|
|
261
272
|
@staticmethod
|
|
262
|
-
def dump(
|
|
273
|
+
def dump(
|
|
274
|
+
flows: List[Flow],
|
|
275
|
+
filename: Union[Text, Path],
|
|
276
|
+
should_clean_json: bool = False,
|
|
277
|
+
) -> None:
|
|
263
278
|
"""Dump `Flow`s to YAML file.
|
|
264
279
|
|
|
265
280
|
Args:
|
|
266
281
|
flows: The `Flow`s to dump.
|
|
267
282
|
filename: The path to the file to write to.
|
|
283
|
+
should_clean_json: Flag whether to clean the flow JSON.
|
|
268
284
|
"""
|
|
269
|
-
rasa.shared.utils.io.write_text_file(
|
|
285
|
+
rasa.shared.utils.io.write_text_file(
|
|
286
|
+
YamlFlowsWriter.dumps(flows, should_clean_json),
|
|
287
|
+
filename,
|
|
288
|
+
)
|
|
270
289
|
|
|
271
290
|
|
|
272
291
|
def is_flows_file(file_path: Union[Text, Path]) -> bool:
|
|
@@ -403,3 +422,85 @@ def process_yaml_content(yaml_content: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
403
422
|
)
|
|
404
423
|
|
|
405
424
|
return yaml_content
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
def get_flow_as_json(flow: Flow, should_clean_json: bool = False) -> Dict[str, Any]:
|
|
428
|
+
"""
|
|
429
|
+
Clean the Flow JSON by removing default values and empty fields.
|
|
430
|
+
|
|
431
|
+
Args:
|
|
432
|
+
flow: The Flow object to clean.
|
|
433
|
+
should_clean_json: Flag indicating whether to clean the JSON.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
The cleaned Flow JSON as a dictionary.
|
|
437
|
+
"""
|
|
438
|
+
step_id_to_default_id = {}
|
|
439
|
+
flow_data = flow.as_json()
|
|
440
|
+
if not should_clean_json:
|
|
441
|
+
return flow_data
|
|
442
|
+
|
|
443
|
+
def gather_step_ids(step: FlowStep) -> None:
|
|
444
|
+
"""Create a map of step IDs to their default IDs."""
|
|
445
|
+
step_id_to_default_id[step.id] = step.default_id
|
|
446
|
+
for link in step.next.links:
|
|
447
|
+
for child in link.child_steps():
|
|
448
|
+
gather_step_ids(child)
|
|
449
|
+
|
|
450
|
+
def clean_flow_data(data: Dict[str, Any]) -> None:
|
|
451
|
+
"""Remove or omit flow-level fields if they match defaults."""
|
|
452
|
+
for top_level_step in flow.step_sequence.steps:
|
|
453
|
+
gather_step_ids(top_level_step)
|
|
454
|
+
|
|
455
|
+
if data.get("run_pattern_completed") == DEFAULT_RUN_PATTERN_COMPLETED:
|
|
456
|
+
data.pop("run_pattern_completed", None)
|
|
457
|
+
|
|
458
|
+
if not data.get("persisted_slots"):
|
|
459
|
+
data.pop("persisted_slots", None)
|
|
460
|
+
|
|
461
|
+
_remove_keys_recursively(data, ["metadata"])
|
|
462
|
+
|
|
463
|
+
data.pop("file_path", None)
|
|
464
|
+
|
|
465
|
+
steps = data.get("steps", [])
|
|
466
|
+
for i, current_step in enumerate(steps):
|
|
467
|
+
# Look ahead to the next step if we should remove the 'next' field
|
|
468
|
+
next_step_data = steps[i + 1] if i + 1 < len(steps) else None
|
|
469
|
+
clean_step(current_step, next_step_data)
|
|
470
|
+
|
|
471
|
+
def clean_step(current_step: Dict[str, Any], next_step: Dict[str, Any]) -> None:
|
|
472
|
+
"""Remove default fields from a step."""
|
|
473
|
+
# Remove 'next' if it exactly matches the next step's default ID
|
|
474
|
+
if next_step:
|
|
475
|
+
next_id = next_step.get("id")
|
|
476
|
+
default_id = step_id_to_default_id.get(next_id)
|
|
477
|
+
if next_id and current_step.get("next") == default_id:
|
|
478
|
+
current_step.pop("next", None)
|
|
479
|
+
|
|
480
|
+
# Remove 'id' if it equals its own default
|
|
481
|
+
step_id = current_step.get("id")
|
|
482
|
+
if step_id and step_id == step_id_to_default_id.get(step_id):
|
|
483
|
+
current_step.pop("id", None)
|
|
484
|
+
|
|
485
|
+
if "collect" in current_step:
|
|
486
|
+
clean_collect_step(current_step)
|
|
487
|
+
|
|
488
|
+
def clean_collect_step(step_data: Dict[str, Any]) -> None:
|
|
489
|
+
"""Remove default fields from a collect step."""
|
|
490
|
+
slot_name = step_data["collect"]
|
|
491
|
+
default_utter = CollectInformationFlowStep._default_utter(slot_name)
|
|
492
|
+
|
|
493
|
+
if step_data.get("utter") == default_utter:
|
|
494
|
+
step_data.pop("utter", None)
|
|
495
|
+
if step_data.get("ask_before_filling") is DEFAULT_ASK_BEFORE_FILLING:
|
|
496
|
+
step_data.pop("ask_before_filling", None)
|
|
497
|
+
if step_data.get("reset_after_flow_ends") is DEFAULT_RESET_AFTER_FLOW_ENDS:
|
|
498
|
+
step_data.pop("reset_after_flow_ends", None)
|
|
499
|
+
if step_data.get("force_slot_filling") is DEFAULT_FORCE_SLOT_FILLING:
|
|
500
|
+
step_data.pop("force_slot_filling", None)
|
|
501
|
+
if not step_data.get("rejections"):
|
|
502
|
+
step_data.pop("rejections", None)
|
|
503
|
+
|
|
504
|
+
clean_flow_data(flow_data)
|
|
505
|
+
|
|
506
|
+
return flow_data
|
|
@@ -18,6 +18,7 @@ import importlib_resources
|
|
|
18
18
|
|
|
19
19
|
import rasa.shared.constants
|
|
20
20
|
import rasa.shared.core.constants
|
|
21
|
+
import rasa.shared.data
|
|
21
22
|
import rasa.shared.utils.common
|
|
22
23
|
import rasa.shared.utils.io
|
|
23
24
|
from rasa.shared.constants import CONFIG_ADDITIONAL_LANGUAGES_KEY, CONFIG_LANGUAGE_KEY
|
|
@@ -279,6 +280,13 @@ class TrainingDataImporter(ABC):
|
|
|
279
280
|
"""
|
|
280
281
|
raise NotImplementedError
|
|
281
282
|
|
|
283
|
+
@staticmethod
|
|
284
|
+
def get_domain_files(
|
|
285
|
+
data_paths: Optional[Union[List[Text], Text]] = None,
|
|
286
|
+
) -> List[Text]:
|
|
287
|
+
"""Returns the domain file path (see parent class for full docstring)."""
|
|
288
|
+
return rasa.shared.data.get_data_files(data_paths, Domain.is_domain_file)
|
|
289
|
+
|
|
282
290
|
|
|
283
291
|
class NluDataImporter(TrainingDataImporter):
|
|
284
292
|
"""Importer that skips any Core-related file reading."""
|
rasa/shared/providers/_utils.py
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
1
3
|
import structlog
|
|
2
4
|
from litellm import validate_environment
|
|
3
5
|
|
|
4
6
|
from rasa.shared.constants import (
|
|
7
|
+
API_BASE_CONFIG_KEY,
|
|
8
|
+
API_VERSION_CONFIG_KEY,
|
|
5
9
|
AWS_ACCESS_KEY_ID_CONFIG_KEY,
|
|
6
10
|
AWS_ACCESS_KEY_ID_ENV_VAR,
|
|
7
11
|
AWS_REGION_NAME_CONFIG_KEY,
|
|
@@ -10,6 +14,9 @@ from rasa.shared.constants import (
|
|
|
10
14
|
AWS_SECRET_ACCESS_KEY_ENV_VAR,
|
|
11
15
|
AWS_SESSION_TOKEN_CONFIG_KEY,
|
|
12
16
|
AWS_SESSION_TOKEN_ENV_VAR,
|
|
17
|
+
AZURE_API_BASE_ENV_VAR,
|
|
18
|
+
AZURE_API_VERSION_ENV_VAR,
|
|
19
|
+
DEPLOYMENT_CONFIG_KEY,
|
|
13
20
|
)
|
|
14
21
|
from rasa.shared.exceptions import ProviderClientValidationError
|
|
15
22
|
from rasa.shared.providers.embedding._base_litellm_embedding_client import (
|
|
@@ -77,3 +84,79 @@ def validate_aws_setup_for_litellm_clients(
|
|
|
77
84
|
missing_environment_variables=missing_environment_variables,
|
|
78
85
|
)
|
|
79
86
|
raise ProviderClientValidationError(event_info)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def validate_azure_client_setup(
|
|
90
|
+
api_base: Optional[str],
|
|
91
|
+
api_version: Optional[str],
|
|
92
|
+
deployment: Optional[str],
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Validates the Azure setup for LiteLLM Router clients to ensure
|
|
95
|
+
that all required configuration parameters are set.
|
|
96
|
+
Raises:
|
|
97
|
+
ProviderClientValidationError: If any required Azure configurations
|
|
98
|
+
is missing.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
def generate_event_info_for_missing_setting(
|
|
102
|
+
setting: str,
|
|
103
|
+
setting_env_var: Optional[str] = None,
|
|
104
|
+
setting_config_key: Optional[str] = None,
|
|
105
|
+
) -> str:
|
|
106
|
+
"""Generate a part of the message with instructions on what to set
|
|
107
|
+
for the missing client setting.
|
|
108
|
+
"""
|
|
109
|
+
info = "Set {setting} with {options}. "
|
|
110
|
+
options = ""
|
|
111
|
+
if setting_env_var is not None:
|
|
112
|
+
options += f"environment variable '{setting_env_var}'"
|
|
113
|
+
if setting_config_key is not None and setting_env_var is not None:
|
|
114
|
+
options += " or "
|
|
115
|
+
if setting_config_key is not None:
|
|
116
|
+
options += f"config key '{setting_config_key}'"
|
|
117
|
+
|
|
118
|
+
return info.format(setting=setting, options=options)
|
|
119
|
+
|
|
120
|
+
# All required settings for Azure OpenAI client
|
|
121
|
+
settings: Dict[str, Dict[str, Any]] = {
|
|
122
|
+
"API Base": {
|
|
123
|
+
"current_value": api_base,
|
|
124
|
+
"env_var": AZURE_API_BASE_ENV_VAR,
|
|
125
|
+
"config_key": API_BASE_CONFIG_KEY,
|
|
126
|
+
},
|
|
127
|
+
"API Version": {
|
|
128
|
+
"current_value": api_version,
|
|
129
|
+
"env_var": AZURE_API_VERSION_ENV_VAR,
|
|
130
|
+
"config_key": API_VERSION_CONFIG_KEY,
|
|
131
|
+
},
|
|
132
|
+
"Deployment Name": {
|
|
133
|
+
"current_value": deployment,
|
|
134
|
+
"env_var": None,
|
|
135
|
+
"config_key": DEPLOYMENT_CONFIG_KEY,
|
|
136
|
+
},
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
missing_settings = [
|
|
140
|
+
setting_name
|
|
141
|
+
for setting_name, setting_info in settings.items()
|
|
142
|
+
if setting_info["current_value"] is None
|
|
143
|
+
]
|
|
144
|
+
|
|
145
|
+
if missing_settings:
|
|
146
|
+
event_info = f"Client settings not set: {', '.join(missing_settings)}. "
|
|
147
|
+
|
|
148
|
+
for missing_setting in missing_settings:
|
|
149
|
+
if settings[missing_setting]["current_value"] is not None:
|
|
150
|
+
continue
|
|
151
|
+
event_info += generate_event_info_for_missing_setting(
|
|
152
|
+
missing_setting,
|
|
153
|
+
settings[missing_setting]["env_var"],
|
|
154
|
+
settings[missing_setting]["config_key"],
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
structlogger.error(
|
|
158
|
+
"azure_openai_llm_client.not_configured",
|
|
159
|
+
event_info=event_info,
|
|
160
|
+
missing_settings=missing_settings,
|
|
161
|
+
)
|
|
162
|
+
raise ProviderClientValidationError(event_info)
|
|
@@ -7,7 +7,12 @@ from typing import Any, Dict, List, Union, cast
|
|
|
7
7
|
import structlog
|
|
8
8
|
from litellm import acompletion, completion, validate_environment
|
|
9
9
|
|
|
10
|
-
from rasa.shared.constants import
|
|
10
|
+
from rasa.shared.constants import (
|
|
11
|
+
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY,
|
|
12
|
+
API_BASE_CONFIG_KEY,
|
|
13
|
+
API_KEY,
|
|
14
|
+
ROLE_USER,
|
|
15
|
+
)
|
|
11
16
|
from rasa.shared.exceptions import (
|
|
12
17
|
ProviderClientAPIException,
|
|
13
18
|
ProviderClientValidationError,
|
|
@@ -21,8 +26,6 @@ from rasa.shared.utils.io import resolve_environment_variables, suppress_logs
|
|
|
21
26
|
|
|
22
27
|
structlogger = structlog.get_logger()
|
|
23
28
|
|
|
24
|
-
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY = "missing_keys"
|
|
25
|
-
|
|
26
29
|
# Suppress LiteLLM info and debug logs - Global level.
|
|
27
30
|
logging.getLogger("LiteLLM").setLevel(logging.WARNING)
|
|
28
31
|
|
|
@@ -7,15 +7,12 @@ from typing import Any, Dict, Optional
|
|
|
7
7
|
import structlog
|
|
8
8
|
|
|
9
9
|
from rasa.shared.constants import (
|
|
10
|
-
API_BASE_CONFIG_KEY,
|
|
11
10
|
API_KEY,
|
|
12
|
-
API_VERSION_CONFIG_KEY,
|
|
13
11
|
AZURE_API_BASE_ENV_VAR,
|
|
14
12
|
AZURE_API_KEY_ENV_VAR,
|
|
15
13
|
AZURE_API_TYPE_ENV_VAR,
|
|
16
14
|
AZURE_API_VERSION_ENV_VAR,
|
|
17
15
|
AZURE_OPENAI_PROVIDER,
|
|
18
|
-
DEPLOYMENT_CONFIG_KEY,
|
|
19
16
|
OPENAI_API_BASE_ENV_VAR,
|
|
20
17
|
OPENAI_API_KEY_ENV_VAR,
|
|
21
18
|
OPENAI_API_TYPE_ENV_VAR,
|
|
@@ -26,6 +23,7 @@ from rasa.shared.providers._configs.azure_openai_client_config import (
|
|
|
26
23
|
AzureEntraIDOAuthConfig,
|
|
27
24
|
AzureOpenAIClientConfig,
|
|
28
25
|
)
|
|
26
|
+
from rasa.shared.providers._utils import validate_azure_client_setup
|
|
29
27
|
from rasa.shared.providers.constants import (
|
|
30
28
|
DEFAULT_AZURE_API_KEY_NAME,
|
|
31
29
|
LITE_LLM_API_BASE_FIELD,
|
|
@@ -348,68 +346,8 @@ class AzureOpenAILLMClient(_BaseLiteLLMClient):
|
|
|
348
346
|
def validate_client_setup(self) -> None:
|
|
349
347
|
"""Validates that all required configuration parameters are set."""
|
|
350
348
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
)
|
|
356
|
-
"""Generate a part of the message with instructions on what to set
|
|
357
|
-
for the missing client setting.
|
|
358
|
-
"""
|
|
359
|
-
info = "Set {setting} with {options}. "
|
|
360
|
-
options = ""
|
|
361
|
-
if setting_env_var is not None:
|
|
362
|
-
options += f"environment variable '{setting_env_var}'"
|
|
363
|
-
if setting_config_key is not None and setting_env_var is not None:
|
|
364
|
-
options += " or "
|
|
365
|
-
if setting_config_key is not None:
|
|
366
|
-
options += f"config key '{setting_config_key}'"
|
|
367
|
-
|
|
368
|
-
return info.format(setting=setting, options=options)
|
|
369
|
-
|
|
370
|
-
env_var_field = "env_var"
|
|
371
|
-
config_key_field = "config_key"
|
|
372
|
-
current_value_field = "current_value"
|
|
373
|
-
# All required settings for Azure OpenAI client
|
|
374
|
-
settings: Dict[str, Dict[str, Any]] = {
|
|
375
|
-
"API Base": {
|
|
376
|
-
current_value_field: self.api_base,
|
|
377
|
-
env_var_field: AZURE_API_BASE_ENV_VAR,
|
|
378
|
-
config_key_field: API_BASE_CONFIG_KEY,
|
|
379
|
-
},
|
|
380
|
-
"API Version": {
|
|
381
|
-
current_value_field: self.api_version,
|
|
382
|
-
env_var_field: AZURE_API_VERSION_ENV_VAR,
|
|
383
|
-
config_key_field: API_VERSION_CONFIG_KEY,
|
|
384
|
-
},
|
|
385
|
-
"Deployment Name": {
|
|
386
|
-
current_value_field: self.deployment,
|
|
387
|
-
env_var_field: None,
|
|
388
|
-
config_key_field: DEPLOYMENT_CONFIG_KEY,
|
|
389
|
-
},
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
missing_settings = [
|
|
393
|
-
setting_name
|
|
394
|
-
for setting_name, setting_info in settings.items()
|
|
395
|
-
if setting_info[current_value_field] is None
|
|
396
|
-
]
|
|
397
|
-
|
|
398
|
-
if missing_settings:
|
|
399
|
-
event_info = f"Client settings not set: " f"{', '.join(missing_settings)}. "
|
|
400
|
-
|
|
401
|
-
for missing_setting in missing_settings:
|
|
402
|
-
if settings[missing_setting][current_value_field] is not None:
|
|
403
|
-
continue
|
|
404
|
-
event_info += generate_event_info_for_missing_setting(
|
|
405
|
-
missing_setting,
|
|
406
|
-
settings[missing_setting][env_var_field],
|
|
407
|
-
settings[missing_setting][config_key_field],
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
structlogger.error(
|
|
411
|
-
"azure_openai_llm_client.not_configured",
|
|
412
|
-
event_info=event_info,
|
|
413
|
-
missing_settings=missing_settings,
|
|
414
|
-
)
|
|
415
|
-
raise ProviderClientValidationError(event_info)
|
|
349
|
+
return validate_azure_client_setup(
|
|
350
|
+
api_base=self.api_base,
|
|
351
|
+
api_version=self.api_version,
|
|
352
|
+
deployment=self.deployment,
|
|
353
|
+
)
|
|
@@ -5,10 +5,16 @@ from copy import deepcopy
|
|
|
5
5
|
from typing import Any, Dict, List
|
|
6
6
|
|
|
7
7
|
import structlog
|
|
8
|
-
from litellm import Router
|
|
8
|
+
from litellm import Router, validate_environment
|
|
9
9
|
|
|
10
10
|
from rasa.shared.constants import (
|
|
11
|
+
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY,
|
|
12
|
+
API_BASE_CONFIG_KEY,
|
|
11
13
|
API_KEY,
|
|
14
|
+
API_VERSION_CONFIG_KEY,
|
|
15
|
+
AZURE_API_BASE_ENV_VAR,
|
|
16
|
+
AZURE_API_VERSION_ENV_VAR,
|
|
17
|
+
AZURE_OPENAI_PROVIDER,
|
|
12
18
|
LITELLM_PARAMS_KEY,
|
|
13
19
|
MODEL_CONFIG_KEY,
|
|
14
20
|
MODEL_GROUP_ID_CONFIG_KEY,
|
|
@@ -23,6 +29,7 @@ from rasa.shared.providers._configs.azure_entra_id_config import AzureEntraIDOAu
|
|
|
23
29
|
from rasa.shared.providers._configs.litellm_router_client_config import (
|
|
24
30
|
LiteLLMRouterClientConfig,
|
|
25
31
|
)
|
|
32
|
+
from rasa.shared.providers._utils import validate_azure_client_setup
|
|
26
33
|
from rasa.shared.utils.io import resolve_environment_variables
|
|
27
34
|
|
|
28
35
|
structlogger = structlog.get_logger()
|
|
@@ -183,6 +190,7 @@ class _BaseLiteLLMRouterClient:
|
|
|
183
190
|
|
|
184
191
|
def _create_router_client(self) -> Router:
|
|
185
192
|
resolved_model_configurations = self._resolve_env_vars_in_model_configurations()
|
|
193
|
+
self._validate_model_configurations(resolved_model_configurations)
|
|
186
194
|
return Router(model_list=resolved_model_configurations, **self.router_settings)
|
|
187
195
|
|
|
188
196
|
def _has_oauth(self) -> bool:
|
|
@@ -214,3 +222,47 @@ class _BaseLiteLLMRouterClient:
|
|
|
214
222
|
)
|
|
215
223
|
model_configuration_with_resolved_keys.append(resolved_model_configuration)
|
|
216
224
|
return model_configuration_with_resolved_keys
|
|
225
|
+
|
|
226
|
+
def _validate_model_configurations(
|
|
227
|
+
self, resolved_model_configurations: List[Dict[str, Any]]
|
|
228
|
+
) -> None:
|
|
229
|
+
"""Validates the model configurations.
|
|
230
|
+
Args:
|
|
231
|
+
resolved_model_configurations: (List[Dict[str, Any]]) The list of model
|
|
232
|
+
configurations with resolved environment variables.
|
|
233
|
+
Raises:
|
|
234
|
+
ProviderClientValidationError: If the model configurations are invalid.
|
|
235
|
+
"""
|
|
236
|
+
for model_configuration in resolved_model_configurations:
|
|
237
|
+
litellm_params = model_configuration.get(LITELLM_PARAMS_KEY, {})
|
|
238
|
+
|
|
239
|
+
model = litellm_params.get(MODEL_CONFIG_KEY)
|
|
240
|
+
provider, deployment = model.split("/", 1)
|
|
241
|
+
api_base = litellm_params.get(API_BASE_CONFIG_KEY)
|
|
242
|
+
|
|
243
|
+
if provider.lower() == AZURE_OPENAI_PROVIDER:
|
|
244
|
+
validate_azure_client_setup(
|
|
245
|
+
api_base=api_base or os.getenv(AZURE_API_BASE_ENV_VAR),
|
|
246
|
+
api_version=litellm_params.get(API_VERSION_CONFIG_KEY)
|
|
247
|
+
or os.getenv(AZURE_API_VERSION_ENV_VAR),
|
|
248
|
+
deployment=deployment,
|
|
249
|
+
)
|
|
250
|
+
else:
|
|
251
|
+
validation_info = validate_environment(
|
|
252
|
+
model=model,
|
|
253
|
+
api_key=litellm_params.get(API_KEY),
|
|
254
|
+
api_base=api_base,
|
|
255
|
+
)
|
|
256
|
+
if missing_environment_variables := validation_info.get(
|
|
257
|
+
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY
|
|
258
|
+
):
|
|
259
|
+
event_info = (
|
|
260
|
+
f"Environment variables: {missing_environment_variables} "
|
|
261
|
+
f"not set. Required for API calls."
|
|
262
|
+
)
|
|
263
|
+
structlogger.error(
|
|
264
|
+
"base_litellm_router_client.validate_environment_variables",
|
|
265
|
+
event_info=event_info,
|
|
266
|
+
missing_environment_variables=missing_environment_variables,
|
|
267
|
+
)
|
|
268
|
+
raise ProviderClientValidationError(event_info)
|
rasa/shared/utils/common.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import copy
|
|
2
3
|
import functools
|
|
3
4
|
import importlib
|
|
4
5
|
import inspect
|
|
@@ -288,6 +289,47 @@ def merge_lists_of_dicts(
|
|
|
288
289
|
return list(merged_dicts.values())
|
|
289
290
|
|
|
290
291
|
|
|
292
|
+
def partial_merge_list(
|
|
293
|
+
self_list: List[Any],
|
|
294
|
+
other_list: List[Any],
|
|
295
|
+
is_same_item_fn: Callable[[Any, Any], bool],
|
|
296
|
+
) -> List[Any]:
|
|
297
|
+
"""Merges two lists based on a custom intersection logic."""
|
|
298
|
+
matched_other_indices = set()
|
|
299
|
+
result = []
|
|
300
|
+
|
|
301
|
+
for s_item in self_list:
|
|
302
|
+
match = next(
|
|
303
|
+
(
|
|
304
|
+
(i, o_item)
|
|
305
|
+
for i, o_item in enumerate(other_list)
|
|
306
|
+
if i not in matched_other_indices and is_same_item_fn(s_item, o_item)
|
|
307
|
+
),
|
|
308
|
+
None,
|
|
309
|
+
)
|
|
310
|
+
if match:
|
|
311
|
+
i, o_item = match
|
|
312
|
+
result.append(copy.deepcopy(o_item))
|
|
313
|
+
matched_other_indices.add(i)
|
|
314
|
+
else:
|
|
315
|
+
result.append(copy.deepcopy(s_item))
|
|
316
|
+
|
|
317
|
+
return result
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def partial_merge_dict(
|
|
321
|
+
self_d: Dict[Text, Any], other_d: Dict[Text, Any]
|
|
322
|
+
) -> Dict[Text, Any]:
|
|
323
|
+
"""Merges two dictionaries based on a custom intersection logic."""
|
|
324
|
+
merged = {}
|
|
325
|
+
for k, v in self_d.items():
|
|
326
|
+
if k in other_d:
|
|
327
|
+
merged[k] = copy.deepcopy(other_d[k])
|
|
328
|
+
else:
|
|
329
|
+
merged[k] = copy.deepcopy(v)
|
|
330
|
+
return merged
|
|
331
|
+
|
|
332
|
+
|
|
291
333
|
def warn_and_exit_if_module_path_contains_rasa_plus(
|
|
292
334
|
module_path: Text, lookup_path: Optional[str] = None
|
|
293
335
|
) -> None:
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
from rasa.shared.core.domain import (
|
|
5
|
+
Domain,
|
|
6
|
+
)
|
|
7
|
+
from rasa.shared.importers.importer import TrainingDataImporter
|
|
8
|
+
from rasa.studio.constants import STUDIO_DOMAIN_FILENAME
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def merge_domain_with_overwrite(
|
|
14
|
+
data_from_studio: TrainingDataImporter,
|
|
15
|
+
data_local: TrainingDataImporter,
|
|
16
|
+
domain_path: Path,
|
|
17
|
+
) -> None:
|
|
18
|
+
"""
|
|
19
|
+
Merges the domain from Rasa Studio with the local domain.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data_from_studio: The training data importer for the Rasa Studio domain.
|
|
23
|
+
data_local: The training data importer for the local domain.
|
|
24
|
+
domain_path: The path to the domain file or directory.
|
|
25
|
+
"""
|
|
26
|
+
if domain_path.is_file():
|
|
27
|
+
all_local_domain_files = [str(domain_path)]
|
|
28
|
+
domain_path = domain_path.parent
|
|
29
|
+
else:
|
|
30
|
+
all_local_domain_files = data_local.get_domain_files([str(domain_path)])
|
|
31
|
+
|
|
32
|
+
# leftover_domain represents the items in the studio
|
|
33
|
+
# domain that are not in the local domain
|
|
34
|
+
leftover_domain = data_from_studio.get_user_domain()
|
|
35
|
+
for file_path in all_local_domain_files:
|
|
36
|
+
# For each local domain file, we do a partial merge
|
|
37
|
+
local_domain = Domain.from_file(str(file_path))
|
|
38
|
+
updated_local_domain = local_domain.partial_merge(leftover_domain)
|
|
39
|
+
|
|
40
|
+
# If this partial merge made changes, persist them
|
|
41
|
+
if local_domain != updated_local_domain:
|
|
42
|
+
updated_local_domain.persist(file_path)
|
|
43
|
+
|
|
44
|
+
# Remove every item now present in updated_local_domain from leftover_domain
|
|
45
|
+
leftover_domain = leftover_domain.difference(updated_local_domain)
|
|
46
|
+
|
|
47
|
+
# If there are still items in leftover_domain, persist them
|
|
48
|
+
if not leftover_domain.is_empty():
|
|
49
|
+
leftover_domain.persist(domain_path / STUDIO_DOMAIN_FILENAME)
|