rasa-pro 3.11.0a4.dev3__py3-none-any.whl → 3.11.0rc1__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 +22 -12
- rasa/api.py +1 -1
- rasa/cli/arguments/default_arguments.py +1 -2
- rasa/cli/arguments/shell.py +5 -1
- rasa/cli/e2e_test.py +1 -1
- rasa/cli/evaluate.py +8 -8
- rasa/cli/inspect.py +4 -4
- rasa/cli/llm_fine_tuning.py +1 -1
- rasa/cli/project_templates/calm/config.yml +5 -7
- rasa/cli/project_templates/calm/endpoints.yml +8 -0
- rasa/cli/project_templates/tutorial/config.yml +8 -5
- rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
- rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
- rasa/cli/project_templates/tutorial/domain.yml +14 -0
- rasa/cli/project_templates/tutorial/endpoints.yml +7 -7
- rasa/cli/run.py +1 -1
- rasa/cli/scaffold.py +4 -2
- rasa/cli/utils.py +5 -0
- rasa/cli/x.py +8 -8
- rasa/constants.py +1 -1
- rasa/core/channels/channel.py +3 -0
- rasa/core/channels/inspector/dist/assets/{arc-6852c607.js → arc-bc141fb2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-acc952b2.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-848a7597.js → classDiagram-936ed81e-55366915.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-a73d3e68.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-e5ee049d.js → createText-62fc7601-b0ec81d6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-771e517e.js → edges-f2ad444c-6166330c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-aa347178.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-651fc57d.js → flowDb-1972c806-fca3bfe4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-ca67804f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-2dbc568d.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-25a65bd8.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-fdc7378d.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-6f1fd606.js → index-2c4b9a3b-f55afcdf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-efdd30c1.js → index-e7cef9de.js} +68 -68
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-cb1a041a.js → infoDiagram-736b4530-124d4a14.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-14609879.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-2490f52b.js → layout-b9885fb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-40186f1f.js → line-7c59abb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-08814e93.js → linear-4776f780.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-1a534584.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-72397b61.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3bb0b6a3.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-57334f61.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-111e1297.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-10bcfe62.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-acaf7513.js → stateDiagram-59f0c015-042b3137.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-3ec2a235.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-62730289.js → styles-080da4f6-23ffa4fc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-5284ee76.js → styles-3dcbcfbf-94f59763.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-642435e3.js → styles-9c745c82-78a6bebc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-b250a350.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-c2b147ed.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-f92cfea9.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/App.tsx +1 -1
- rasa/core/channels/inspector/src/helpers/audiostream.ts +77 -16
- rasa/core/channels/socketio.py +2 -1
- rasa/core/channels/telegram.py +1 -1
- rasa/core/channels/twilio.py +1 -1
- rasa/core/channels/voice_ready/jambonz.py +2 -2
- rasa/core/channels/voice_stream/asr/asr_event.py +5 -0
- rasa/core/channels/voice_stream/asr/azure.py +122 -0
- rasa/core/channels/voice_stream/asr/deepgram.py +16 -6
- rasa/core/channels/voice_stream/audio_bytes.py +1 -0
- rasa/core/channels/voice_stream/browser_audio.py +31 -8
- rasa/core/channels/voice_stream/call_state.py +23 -0
- rasa/core/channels/voice_stream/tts/azure.py +6 -2
- rasa/core/channels/voice_stream/tts/cartesia.py +10 -6
- rasa/core/channels/voice_stream/tts/tts_engine.py +1 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +27 -18
- rasa/core/channels/voice_stream/util.py +4 -4
- rasa/core/channels/voice_stream/voice_channel.py +177 -39
- rasa/core/featurizers/single_state_featurizer.py +22 -1
- rasa/core/featurizers/tracker_featurizers.py +115 -18
- rasa/core/nlg/contextual_response_rephraser.py +16 -22
- rasa/core/persistor.py +86 -39
- rasa/core/policies/enterprise_search_policy.py +159 -60
- rasa/core/policies/flows/flow_executor.py +7 -4
- rasa/core/policies/intentless_policy.py +120 -22
- rasa/core/policies/ted_policy.py +58 -33
- rasa/core/policies/unexpected_intent_policy.py +15 -7
- rasa/core/processor.py +25 -0
- rasa/core/training/interactive.py +34 -35
- rasa/core/utils.py +8 -3
- rasa/dialogue_understanding/coexistence/llm_based_router.py +58 -16
- rasa/dialogue_understanding/commands/change_flow_command.py +6 -0
- rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +5 -0
- rasa/dialogue_understanding/generator/constants.py +4 -0
- rasa/dialogue_understanding/generator/flow_retrieval.py +65 -3
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +68 -26
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +57 -8
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +64 -7
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +39 -0
- rasa/dialogue_understanding/patterns/user_silence.py +37 -0
- rasa/e2e_test/e2e_test_runner.py +4 -2
- rasa/e2e_test/utils/io.py +1 -1
- rasa/engine/validation.py +297 -7
- rasa/model_manager/config.py +15 -3
- rasa/model_manager/model_api.py +15 -7
- rasa/model_manager/runner_service.py +8 -6
- rasa/model_manager/socket_bridge.py +6 -3
- rasa/model_manager/trainer_service.py +7 -5
- rasa/model_manager/utils.py +28 -7
- rasa/model_service.py +6 -2
- rasa/model_training.py +2 -0
- rasa/nlu/classifiers/diet_classifier.py +38 -25
- rasa/nlu/classifiers/logistic_regression_classifier.py +22 -9
- rasa/nlu/classifiers/sklearn_intent_classifier.py +37 -16
- rasa/nlu/extractors/crf_entity_extractor.py +93 -50
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -16
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +52 -17
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +5 -3
- rasa/shared/constants.py +36 -3
- rasa/shared/core/constants.py +7 -0
- rasa/shared/core/domain.py +26 -0
- rasa/shared/core/flows/flow.py +5 -0
- rasa/shared/core/flows/flows_yaml_schema.json +10 -0
- rasa/shared/core/flows/utils.py +39 -0
- rasa/shared/core/flows/validation.py +96 -0
- rasa/shared/core/slots.py +5 -0
- rasa/shared/nlu/training_data/features.py +120 -2
- rasa/shared/providers/_configs/azure_openai_client_config.py +5 -3
- rasa/shared/providers/_configs/litellm_router_client_config.py +200 -0
- rasa/shared/providers/_configs/model_group_config.py +167 -0
- rasa/shared/providers/_configs/openai_client_config.py +1 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
- rasa/shared/providers/_configs/utils.py +16 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +12 -15
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
- rasa/shared/providers/llm/_base_litellm_client.py +31 -30
- rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
- rasa/shared/providers/llm/litellm_router_llm_client.py +127 -0
- rasa/shared/providers/llm/rasa_llm_client.py +112 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +1 -1
- rasa/shared/providers/mappings.py +19 -0
- rasa/shared/providers/router/__init__.py +0 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +149 -0
- rasa/shared/providers/router/router_client.py +73 -0
- rasa/shared/utils/common.py +8 -0
- rasa/shared/utils/health_check.py +533 -0
- rasa/shared/utils/io.py +28 -6
- rasa/shared/utils/llm.py +350 -46
- rasa/shared/utils/yaml.py +11 -13
- rasa/studio/upload.py +64 -20
- rasa/telemetry.py +80 -17
- rasa/tracing/instrumentation/attribute_extractors.py +74 -17
- rasa/utils/io.py +0 -66
- rasa/utils/log_utils.py +9 -2
- rasa/utils/tensorflow/feature_array.py +366 -0
- rasa/utils/tensorflow/model_data.py +2 -193
- rasa/validator.py +70 -0
- rasa/version.py +1 -1
- {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc1.dist-info}/METADATA +10 -10
- {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc1.dist-info}/RECORD +162 -146
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-587d82d8.js +0 -1
- {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0a4.dev3.dist-info → rasa_pro-3.11.0rc1.dist-info}/entry_points.txt +0 -0
rasa/shared/utils/llm.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from copy import deepcopy
|
|
1
3
|
from functools import wraps
|
|
2
4
|
from typing import (
|
|
3
5
|
Any,
|
|
@@ -11,14 +13,19 @@ from typing import (
|
|
|
11
13
|
Union,
|
|
12
14
|
cast,
|
|
13
15
|
)
|
|
14
|
-
|
|
16
|
+
|
|
15
17
|
import structlog
|
|
16
18
|
|
|
17
19
|
import rasa.shared.utils.io
|
|
20
|
+
from rasa.core.utils import AvailableEndpoints
|
|
18
21
|
from rasa.shared.constants import (
|
|
19
22
|
RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_TOO_LONG,
|
|
20
23
|
RASA_PATTERN_INTERNAL_ERROR_USER_INPUT_EMPTY,
|
|
21
24
|
PROVIDER_CONFIG_KEY,
|
|
25
|
+
MODEL_GROUP_CONFIG_KEY,
|
|
26
|
+
MODEL_GROUP_ID_CONFIG_KEY,
|
|
27
|
+
MODELS_CONFIG_KEY,
|
|
28
|
+
ROUTER_CONFIG_KEY,
|
|
22
29
|
)
|
|
23
30
|
from rasa.shared.core.events import BotUttered, UserUttered
|
|
24
31
|
from rasa.shared.core.slots import Slot, BooleanSlot, CategoricalSlot
|
|
@@ -28,7 +35,7 @@ from rasa.shared.engine.caching import (
|
|
|
28
35
|
from rasa.shared.exceptions import (
|
|
29
36
|
FileIOException,
|
|
30
37
|
FileNotFoundException,
|
|
31
|
-
|
|
38
|
+
InvalidConfigException,
|
|
32
39
|
)
|
|
33
40
|
from rasa.shared.providers._configs.azure_openai_client_config import (
|
|
34
41
|
is_azure_openai_config,
|
|
@@ -51,11 +58,11 @@ from rasa.shared.providers.mappings import (
|
|
|
51
58
|
HUGGINGFACE_LOCAL_EMBEDDING_PROVIDER,
|
|
52
59
|
get_client_config_class_from_provider,
|
|
53
60
|
)
|
|
54
|
-
from rasa.shared.utils.cli import print_error_and_exit
|
|
55
61
|
|
|
56
62
|
if TYPE_CHECKING:
|
|
57
63
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
58
64
|
|
|
65
|
+
|
|
59
66
|
structlogger = structlog.get_logger()
|
|
60
67
|
|
|
61
68
|
USER = "USER"
|
|
@@ -76,6 +83,8 @@ DEFAULT_OPENAI_MAX_GENERATED_TOKENS = 256
|
|
|
76
83
|
|
|
77
84
|
DEFAULT_MAX_USER_INPUT_CHARACTERS = 420
|
|
78
85
|
|
|
86
|
+
DEPLOYMENT_CENTRIC_PROVIDERS = [AZURE_OPENAI_PROVIDER]
|
|
87
|
+
|
|
79
88
|
# Placeholder messages used in the transcript for
|
|
80
89
|
# instances where user input results in an error
|
|
81
90
|
ERROR_PLACEHOLDER = {
|
|
@@ -244,7 +253,75 @@ def sanitize_message_for_prompt(text: Optional[str]) -> str:
|
|
|
244
253
|
def combine_custom_and_default_config(
|
|
245
254
|
custom_config: Optional[Dict[str, Any]], default_config: Dict[str, Any]
|
|
246
255
|
) -> Dict[Text, Any]:
|
|
247
|
-
"""Merges the given
|
|
256
|
+
"""Merges the given model configuration with the default configuration.
|
|
257
|
+
|
|
258
|
+
This method supports both single model configurations and model group configurations
|
|
259
|
+
(configs that have the `models` key).
|
|
260
|
+
|
|
261
|
+
If `custom_config` is a single model configuration, it merges `custom_config` with
|
|
262
|
+
`default_config`, which is also a single model configuration.
|
|
263
|
+
|
|
264
|
+
If `custom_config` is a model group configuration (contains the `models` key), it
|
|
265
|
+
applies the merging process to each model configuration within the group
|
|
266
|
+
individually, merging each with the `default_config`.
|
|
267
|
+
|
|
268
|
+
Note that `default_config` is always a single model configuration.
|
|
269
|
+
|
|
270
|
+
The method ensures that the provider is set and all deprecated keys are resolved,
|
|
271
|
+
resulting in a valid client configuration.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
custom_config: The custom configuration containing values to overwrite defaults.
|
|
275
|
+
Can be a single model configuration or a model group configuration with a
|
|
276
|
+
`models` key.
|
|
277
|
+
default_config: The default configuration, which is a single model
|
|
278
|
+
configuration.
|
|
279
|
+
|
|
280
|
+
Returns:
|
|
281
|
+
The merged configuration, either a single model configuration or a model group
|
|
282
|
+
configuration with merged models.
|
|
283
|
+
"""
|
|
284
|
+
if custom_config and MODELS_CONFIG_KEY in custom_config:
|
|
285
|
+
return _combine_model_groups_configs_with_default_config(
|
|
286
|
+
custom_config, default_config
|
|
287
|
+
)
|
|
288
|
+
else:
|
|
289
|
+
return _combine_single_model_configs(custom_config, default_config)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _combine_model_groups_configs_with_default_config(
|
|
293
|
+
model_group_config: Dict[str, Any], default_config: Dict[str, Any]
|
|
294
|
+
) -> Dict[Text, Any]:
|
|
295
|
+
"""Merges each model configuration within a model group with the default
|
|
296
|
+
configuration.
|
|
297
|
+
|
|
298
|
+
This method processes model group configurations by applying the merging process to
|
|
299
|
+
each model configuration within the group individually.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
model_group_config: The model group configuration containing a list of model
|
|
303
|
+
configurations under the `models` key.
|
|
304
|
+
default_config: The default configuration for a single model.
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
The merged model group configuration with each model configuration merged
|
|
308
|
+
with the default configuration.
|
|
309
|
+
"""
|
|
310
|
+
model_group_config = deepcopy(model_group_config)
|
|
311
|
+
model_group_config_combined_with_defaults = [
|
|
312
|
+
_combine_single_model_configs(model_config, default_config)
|
|
313
|
+
for model_config in model_group_config[MODELS_CONFIG_KEY]
|
|
314
|
+
]
|
|
315
|
+
# Update the custom models config with the combined config.
|
|
316
|
+
model_group_config[MODELS_CONFIG_KEY] = model_group_config_combined_with_defaults
|
|
317
|
+
return model_group_config
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@_cache_combine_custom_and_default_configs
|
|
321
|
+
def _combine_single_model_configs(
|
|
322
|
+
custom_config: Optional[Dict[str, Any]], default_config: Dict[str, Any]
|
|
323
|
+
) -> Dict[Text, Any]:
|
|
324
|
+
"""Merges the given model config with the default config.
|
|
248
325
|
|
|
249
326
|
This method guarantees that the provider is set and all the deprecated keys are
|
|
250
327
|
resolved. Hence, produces only a valid client config.
|
|
@@ -329,6 +406,105 @@ def llm_factory(
|
|
|
329
406
|
) -> LLMClient:
|
|
330
407
|
"""Creates an LLM from the given config.
|
|
331
408
|
|
|
409
|
+
If the config is using the old syntax, e.g. defining the llm client directly in
|
|
410
|
+
config.yaml, then standalone client is initialised (no routing).
|
|
411
|
+
|
|
412
|
+
If the config uses the using the new, model group syntax, defined in the
|
|
413
|
+
endpoints.yml, then router client is initialised if there are more than one model
|
|
414
|
+
within the group.
|
|
415
|
+
|
|
416
|
+
Examples:
|
|
417
|
+
The config below will result in a standalone client:
|
|
418
|
+
```
|
|
419
|
+
{
|
|
420
|
+
"provider": "openai",
|
|
421
|
+
"model": "gpt-4",
|
|
422
|
+
"timeout": 10,
|
|
423
|
+
"num_retries": 3,
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
The config below will also result in a standalone client:
|
|
428
|
+
```
|
|
429
|
+
{
|
|
430
|
+
"id": "model-group-id",
|
|
431
|
+
"models": [
|
|
432
|
+
{"provider": "openai", "model": "gpt-4", "api_key": "test"},
|
|
433
|
+
],
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
The config below will result in a router client:
|
|
438
|
+
```
|
|
439
|
+
{
|
|
440
|
+
"id": "test-model-group-id",
|
|
441
|
+
"models": [
|
|
442
|
+
{"provider": "openai", "model": "gpt-4", "api_key": "test"},
|
|
443
|
+
{
|
|
444
|
+
"provider": "azure",
|
|
445
|
+
"deployment": "test-deployment",
|
|
446
|
+
"api_key": "test",
|
|
447
|
+
"api_base": "test-api-base",
|
|
448
|
+
},
|
|
449
|
+
],
|
|
450
|
+
"router": {"routing_strategy": "test"},
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
Args:
|
|
455
|
+
custom_config: The custom config containing values to overwrite defaults.
|
|
456
|
+
default_config: The default config.
|
|
457
|
+
|
|
458
|
+
Returns:
|
|
459
|
+
Instantiated client based on the configuration.
|
|
460
|
+
"""
|
|
461
|
+
if custom_config:
|
|
462
|
+
if ROUTER_CONFIG_KEY in custom_config:
|
|
463
|
+
return llm_router_factory(custom_config, default_config)
|
|
464
|
+
if MODELS_CONFIG_KEY in custom_config:
|
|
465
|
+
return llm_client_factory(
|
|
466
|
+
custom_config[MODELS_CONFIG_KEY][0], default_config
|
|
467
|
+
)
|
|
468
|
+
return llm_client_factory(custom_config, default_config)
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def llm_router_factory(
|
|
472
|
+
router_config: Dict[str, Any], default_model_config: Dict[str, Any], **kwargs: Any
|
|
473
|
+
) -> LLMClient:
|
|
474
|
+
"""Creates an LLM Router using the provided configurations.
|
|
475
|
+
|
|
476
|
+
This function initializes an LLM Router based on the given router configuration,
|
|
477
|
+
which includes multiple model configurations. For each model specified in the router
|
|
478
|
+
configuration, any missing parameters are supplemented using the default model
|
|
479
|
+
configuration.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
router_config: The full router configuration containing multiple model
|
|
483
|
+
configurations. Each model's configuration can override parameters from the
|
|
484
|
+
default model configuration.
|
|
485
|
+
default_model_config: The default configuration parameters for a single model.
|
|
486
|
+
These defaults are used to fill in any missing parameters in each model's
|
|
487
|
+
configuration within the router.
|
|
488
|
+
|
|
489
|
+
Returns:
|
|
490
|
+
An instance that conforms to both `LLMClient` and `RouterClient` protocols
|
|
491
|
+
representing the configured LLM Router.
|
|
492
|
+
"""
|
|
493
|
+
from rasa.shared.providers.llm.litellm_router_llm_client import (
|
|
494
|
+
LiteLLMRouterLLMClient,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
combined_config = _combine_model_groups_configs_with_default_config(
|
|
498
|
+
router_config, default_model_config
|
|
499
|
+
)
|
|
500
|
+
return LiteLLMRouterLLMClient.from_config(combined_config)
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def llm_client_factory(
|
|
504
|
+
custom_config: Optional[Dict[str, Any]], default_config: Dict[str, Any]
|
|
505
|
+
) -> LLMClient:
|
|
506
|
+
"""Creates an LLM from the given config.
|
|
507
|
+
|
|
332
508
|
Args:
|
|
333
509
|
custom_config: The custom config containing values to overwrite defaults
|
|
334
510
|
default_config: The default config.
|
|
@@ -350,6 +526,110 @@ def llm_factory(
|
|
|
350
526
|
@_cache_factory
|
|
351
527
|
def embedder_factory(
|
|
352
528
|
custom_config: Optional[Dict[str, Any]], default_config: Dict[str, Any]
|
|
529
|
+
) -> EmbeddingClient:
|
|
530
|
+
"""Creates an embedding client from the given config.
|
|
531
|
+
|
|
532
|
+
If the config is using the old syntax, e.g. defining the llm client directly in
|
|
533
|
+
config.yaml, then standalone client is initialised (no routing).
|
|
534
|
+
|
|
535
|
+
If the config uses the using the new, model group syntax, defined in the
|
|
536
|
+
endpoints.yml, then router client is initialised if there are more than one model
|
|
537
|
+
within the group and the router is defined.
|
|
538
|
+
|
|
539
|
+
Examples:
|
|
540
|
+
The config below will result in a standalone client:
|
|
541
|
+
```
|
|
542
|
+
{
|
|
543
|
+
"provider": "openai",
|
|
544
|
+
"model": "text-embedding-3-small",
|
|
545
|
+
"timeout": 10,
|
|
546
|
+
"num_retries": 3,
|
|
547
|
+
}
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
The config below will also result in a standalone client:
|
|
551
|
+
```
|
|
552
|
+
{
|
|
553
|
+
"id": "model-group-id",
|
|
554
|
+
"models": [
|
|
555
|
+
{
|
|
556
|
+
"provider": "openai",
|
|
557
|
+
"model": "test-embedding-3-small",
|
|
558
|
+
"api_key": "test"
|
|
559
|
+
},
|
|
560
|
+
],
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
The config below will result in a router client:
|
|
565
|
+
```
|
|
566
|
+
{
|
|
567
|
+
"id": "test-model-group-id",
|
|
568
|
+
"models": [
|
|
569
|
+
{"provider": "openai", "model": "gpt-4", "api_key": "test"},
|
|
570
|
+
{
|
|
571
|
+
"provider": "azure",
|
|
572
|
+
"deployment": "test-deployment",
|
|
573
|
+
"api_key": "test",
|
|
574
|
+
"api_base": "test-api-base",
|
|
575
|
+
},
|
|
576
|
+
],
|
|
577
|
+
"router": {"routing_strategy": "test"},
|
|
578
|
+
}
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
custom_config: The custom config containing values to overwrite defaults.
|
|
583
|
+
default_config: The default config.
|
|
584
|
+
|
|
585
|
+
Returns:
|
|
586
|
+
Instantiated client based on the configuration.
|
|
587
|
+
"""
|
|
588
|
+
if custom_config:
|
|
589
|
+
if ROUTER_CONFIG_KEY in custom_config:
|
|
590
|
+
return embedder_router_factory(custom_config, default_config)
|
|
591
|
+
if MODELS_CONFIG_KEY in custom_config:
|
|
592
|
+
return embedder_client_factory(
|
|
593
|
+
custom_config[MODELS_CONFIG_KEY][0], default_config
|
|
594
|
+
)
|
|
595
|
+
return embedder_client_factory(custom_config, default_config)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def embedder_router_factory(
|
|
599
|
+
router_config: Dict[str, Any], default_model_config: Dict[str, Any], **kwargs: Any
|
|
600
|
+
) -> EmbeddingClient:
|
|
601
|
+
"""Creates an Embedder Router using the provided configurations.
|
|
602
|
+
|
|
603
|
+
This function initializes an Embedder Router based on the given router
|
|
604
|
+
configuration, which includes multiple model configurations. For each model
|
|
605
|
+
specified in the router configuration, any missing parameters are supplemented using
|
|
606
|
+
the default model configuration.
|
|
607
|
+
|
|
608
|
+
Args:
|
|
609
|
+
router_config: The full router configuration containing multiple model
|
|
610
|
+
configurations. Each model's configuration can override parameters from the
|
|
611
|
+
default model configuration.
|
|
612
|
+
default_model_config: The default configuration parameters for a single model.
|
|
613
|
+
These defaults are used to fill in any missing parameters in each model's
|
|
614
|
+
configuration within the router.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
An instance that conforms to both `EmbeddingClient` and `RouterClient` protocols
|
|
618
|
+
representing the configured Embedding Router.
|
|
619
|
+
"""
|
|
620
|
+
from rasa.shared.providers.embedding.litellm_router_embedding_client import (
|
|
621
|
+
LiteLLMRouterEmbeddingClient,
|
|
622
|
+
)
|
|
623
|
+
|
|
624
|
+
combined_config = _combine_model_groups_configs_with_default_config(
|
|
625
|
+
router_config, default_model_config
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
return LiteLLMRouterEmbeddingClient.from_config(combined_config)
|
|
629
|
+
|
|
630
|
+
|
|
631
|
+
def embedder_client_factory(
|
|
632
|
+
custom_config: Optional[Dict[str, Any]], default_config: Dict[str, Any]
|
|
353
633
|
) -> EmbeddingClient:
|
|
354
634
|
"""Creates an Embedder from the given config.
|
|
355
635
|
|
|
@@ -405,49 +685,73 @@ def allowed_values_for_slot(slot: Slot) -> Union[str, None]:
|
|
|
405
685
|
return None
|
|
406
686
|
|
|
407
687
|
|
|
408
|
-
def
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
log_source_component: str,
|
|
413
|
-
) -> LLMClient:
|
|
414
|
-
"""Validate llm configuration."""
|
|
415
|
-
try:
|
|
416
|
-
return llm_factory(custom_llm_config, default_llm_config)
|
|
417
|
-
except (ProviderClientValidationError, ValueError) as e:
|
|
418
|
-
structlogger.error(
|
|
419
|
-
f"{log_source_function}.llm_instantiation_failed",
|
|
420
|
-
message="Unable to instantiate LLM client.",
|
|
421
|
-
error=e,
|
|
422
|
-
)
|
|
423
|
-
print_error_and_exit(
|
|
424
|
-
f"Unable to create the LLM client for component - {log_source_component}. "
|
|
425
|
-
f"Please make sure you specified the required environment variables. "
|
|
426
|
-
f"Error: {e}"
|
|
427
|
-
)
|
|
688
|
+
def resolve_model_client_config(
|
|
689
|
+
model_config: Optional[Dict[str, Any]], component_name: Optional[str] = None
|
|
690
|
+
) -> Optional[Dict[str, Any]]:
|
|
691
|
+
"""Resolve the model group in the model config.
|
|
428
692
|
|
|
693
|
+
If the config is pointing to a model group, the corresponding model group
|
|
694
|
+
of the endpoints.yml is returned.
|
|
695
|
+
If the config is using the old syntax, e.g. defining the llm
|
|
696
|
+
directly in config.yml, the config is returned as is.
|
|
429
697
|
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
f"{
|
|
447
|
-
|
|
448
|
-
|
|
698
|
+
Args:
|
|
699
|
+
model_config: The model config to be resolved.
|
|
700
|
+
component_name: The name of the component.
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
The resolved llm config.
|
|
704
|
+
"""
|
|
705
|
+
|
|
706
|
+
def _raise_invalid_config_exception(reason: str) -> None:
|
|
707
|
+
"""Helper function to raise InvalidConfigException with a formatted message."""
|
|
708
|
+
if component_name:
|
|
709
|
+
message = (
|
|
710
|
+
f"Could not resolve model group '{model_group_id}'"
|
|
711
|
+
f" for component '{component_name}'."
|
|
712
|
+
)
|
|
713
|
+
else:
|
|
714
|
+
message = f"Could not resolve model group '{model_group_id}'."
|
|
715
|
+
message += f" {reason}"
|
|
716
|
+
raise InvalidConfigException(message)
|
|
717
|
+
|
|
718
|
+
if model_config is None:
|
|
719
|
+
return None
|
|
720
|
+
|
|
721
|
+
if MODEL_GROUP_CONFIG_KEY not in model_config:
|
|
722
|
+
return model_config
|
|
723
|
+
|
|
724
|
+
model_group_id = model_config.get(MODEL_GROUP_CONFIG_KEY)
|
|
725
|
+
|
|
726
|
+
endpoints = AvailableEndpoints.get_instance()
|
|
727
|
+
if endpoints.model_groups is None:
|
|
728
|
+
_raise_invalid_config_exception(
|
|
729
|
+
reason=(
|
|
730
|
+
"No model group with that id found in endpoints.yml. "
|
|
731
|
+
"Please make sure to define the model group."
|
|
732
|
+
)
|
|
449
733
|
)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
734
|
+
|
|
735
|
+
copy_model_groups = deepcopy(endpoints.model_groups)
|
|
736
|
+
model_group = [
|
|
737
|
+
model_group
|
|
738
|
+
for model_group in copy_model_groups # type: ignore[union-attr]
|
|
739
|
+
if model_group.get(MODEL_GROUP_ID_CONFIG_KEY) == model_group_id
|
|
740
|
+
]
|
|
741
|
+
|
|
742
|
+
if len(model_group) == 0:
|
|
743
|
+
_raise_invalid_config_exception(
|
|
744
|
+
reason=(
|
|
745
|
+
"No model group with that id found in endpoints.yml. "
|
|
746
|
+
"Please make sure to define the model group."
|
|
747
|
+
)
|
|
453
748
|
)
|
|
749
|
+
if len(model_group) > 1:
|
|
750
|
+
_raise_invalid_config_exception(
|
|
751
|
+
reason=(
|
|
752
|
+
"Multiple model groups with that id found in endpoints.yml. "
|
|
753
|
+
"Please make sure to define the model group just once."
|
|
754
|
+
)
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
return model_group[0]
|
rasa/shared/utils/yaml.py
CHANGED
|
@@ -17,8 +17,8 @@ from pykwalify.core import Core
|
|
|
17
17
|
from pykwalify.errors import SchemaError
|
|
18
18
|
from ruamel import yaml as yaml
|
|
19
19
|
from ruamel.yaml import RoundTripRepresenter, YAMLError
|
|
20
|
-
from ruamel.yaml.constructor import DuplicateKeyError, BaseConstructor, ScalarNode
|
|
21
20
|
from ruamel.yaml.comments import CommentedSeq, CommentedMap
|
|
21
|
+
from ruamel.yaml.constructor import DuplicateKeyError, BaseConstructor, ScalarNode
|
|
22
22
|
from ruamel.yaml.loader import SafeLoader
|
|
23
23
|
|
|
24
24
|
from rasa.shared.constants import (
|
|
@@ -31,8 +31,7 @@ from rasa.shared.constants import (
|
|
|
31
31
|
LATEST_TRAINING_DATA_FORMAT_VERSION,
|
|
32
32
|
SCHEMA_EXTENSIONS_FILE,
|
|
33
33
|
RESPONSES_SCHEMA_FILE,
|
|
34
|
-
|
|
35
|
-
RESOLVED_VALUE,
|
|
34
|
+
API_KEY,
|
|
36
35
|
)
|
|
37
36
|
from rasa.shared.exceptions import (
|
|
38
37
|
YamlException,
|
|
@@ -60,6 +59,7 @@ YAML_VERSION = (1, 2)
|
|
|
60
59
|
READ_YAML_FILE_CACHE_MAXSIZE = os.environ.get(
|
|
61
60
|
READ_YAML_FILE_CACHE_MAXSIZE_ENV_VAR, DEFAULT_READ_YAML_FILE_CACHE_MAXSIZE
|
|
62
61
|
)
|
|
62
|
+
SENSITIVE_DATA = [API_KEY]
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
@dataclass
|
|
@@ -86,11 +86,15 @@ def replace_environment_variables() -> None:
|
|
|
86
86
|
env_var_pattern = re.compile(r"^(.*)\$\{(.*)\}(.*)$")
|
|
87
87
|
yaml.Resolver.add_implicit_resolver("!env_var", env_var_pattern, None)
|
|
88
88
|
|
|
89
|
-
def env_var_constructor(
|
|
90
|
-
loader: BaseConstructor, node: ScalarNode
|
|
91
|
-
) -> Union[dict, str]:
|
|
89
|
+
def env_var_constructor(loader: BaseConstructor, node: ScalarNode) -> str:
|
|
92
90
|
"""Process environment variables found in the YAML."""
|
|
93
91
|
value = loader.construct_scalar(node)
|
|
92
|
+
|
|
93
|
+
# get key of current node
|
|
94
|
+
key_node = list(loader.constructed_objects)[-1]
|
|
95
|
+
if isinstance(key_node, ScalarNode) and key_node.value in SENSITIVE_DATA:
|
|
96
|
+
return value
|
|
97
|
+
|
|
94
98
|
expanded_vars = os.path.expandvars(value)
|
|
95
99
|
not_expanded = [
|
|
96
100
|
w for w in expanded_vars.split() if w.startswith("$") and w in value
|
|
@@ -102,11 +106,6 @@ def replace_environment_variables() -> None:
|
|
|
102
106
|
f"Please make sure to also set these "
|
|
103
107
|
f"environment variables: '{not_expanded}'."
|
|
104
108
|
)
|
|
105
|
-
if expanded_vars:
|
|
106
|
-
# if the environment variable is referenced using the ${} syntax
|
|
107
|
-
# then we return a dictionary with the original value and the resolved,
|
|
108
|
-
# value. So that the graph components can use the original value.
|
|
109
|
-
return {ORIGINAL_VALUE: value, RESOLVED_VALUE: expanded_vars}
|
|
110
109
|
return expanded_vars
|
|
111
110
|
|
|
112
111
|
yaml.SafeConstructor.add_constructor("!env_var", env_var_constructor)
|
|
@@ -426,8 +425,7 @@ def validate_raw_yaml_using_schema_file_with_responses(
|
|
|
426
425
|
|
|
427
426
|
|
|
428
427
|
def process_content(content: str) -> str:
|
|
429
|
-
"""
|
|
430
|
-
Process the content to handle both Windows paths and emojis.
|
|
428
|
+
"""Process the content to handle both Windows paths and emojis.
|
|
431
429
|
Windows paths are processed by escaping backslashes but emojis are left untouched.
|
|
432
430
|
|
|
433
431
|
Args:
|
rasa/studio/upload.py
CHANGED
|
@@ -61,24 +61,29 @@ def handle_upload(args: argparse.Namespace) -> None:
|
|
|
61
61
|
rasa.shared.utils.cli.print_error_and_exit(
|
|
62
62
|
"No GraphQL endpoint found in config. Please run `rasa studio config`."
|
|
63
63
|
)
|
|
64
|
-
|
|
65
|
-
structlogger.info(
|
|
66
|
-
"rasa.studio.upload.loading_data", event_info="Loading data..."
|
|
67
|
-
)
|
|
64
|
+
return
|
|
68
65
|
|
|
69
|
-
|
|
70
|
-
|
|
66
|
+
if not is_auth_working(endpoint):
|
|
67
|
+
rasa.shared.utils.cli.print_error_and_exit(
|
|
68
|
+
"Authentication is invalid or expired. Please run `rasa studio login`."
|
|
71
69
|
)
|
|
70
|
+
return
|
|
72
71
|
|
|
73
|
-
|
|
74
|
-
args.config, "config", DEFAULT_CONFIG_PATH
|
|
75
|
-
)
|
|
72
|
+
structlogger.info("rasa.studio.upload.loading_data", event_info="Loading data...")
|
|
76
73
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
74
|
+
args.domain = rasa.cli.utils.get_validated_path(
|
|
75
|
+
args.domain, "domain", DEFAULT_DOMAIN_PATHS
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
args.config = rasa.cli.utils.get_validated_path(
|
|
79
|
+
args.config, "config", DEFAULT_CONFIG_PATH
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
# check safely if args.calm is set and not fail if not
|
|
83
|
+
if hasattr(args, "calm") and args.calm:
|
|
84
|
+
upload_calm_assistant(args, endpoint)
|
|
85
|
+
else:
|
|
86
|
+
upload_nlu_assistant(args, endpoint)
|
|
82
87
|
|
|
83
88
|
|
|
84
89
|
config_keys = [
|
|
@@ -121,7 +126,11 @@ def _get_assistant_name(config: Dict[Text, Any]) -> str:
|
|
|
121
126
|
),
|
|
122
127
|
)
|
|
123
128
|
|
|
124
|
-
structlogger.info(
|
|
129
|
+
structlogger.info(
|
|
130
|
+
"rasa.studio.upload.name_selected",
|
|
131
|
+
event_info=f"Uploading assistant with the name '{assistant_name}'.",
|
|
132
|
+
assistant_name=assistant_name,
|
|
133
|
+
)
|
|
125
134
|
return assistant_name
|
|
126
135
|
|
|
127
136
|
|
|
@@ -215,7 +224,9 @@ def upload_calm_assistant(args: argparse.Namespace, endpoint: str) -> StudioResu
|
|
|
215
224
|
nlu_yaml=nlu_examples_yaml,
|
|
216
225
|
)
|
|
217
226
|
|
|
218
|
-
structlogger.info(
|
|
227
|
+
structlogger.info(
|
|
228
|
+
"rasa.studio.upload.calm", event_info="Uploading to Rasa Studio..."
|
|
229
|
+
)
|
|
219
230
|
return make_request(endpoint, graphql_req)
|
|
220
231
|
|
|
221
232
|
|
|
@@ -233,7 +244,10 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
|
|
|
233
244
|
Returns:
|
|
234
245
|
None
|
|
235
246
|
"""
|
|
236
|
-
structlogger.info(
|
|
247
|
+
structlogger.info(
|
|
248
|
+
"rasa.studio.upload.nlu_data_read",
|
|
249
|
+
event_info="Found DM1 assistant data, parsing...",
|
|
250
|
+
)
|
|
237
251
|
importer = TrainingDataImporter.load_from_dict(
|
|
238
252
|
domain_path=args.domain, training_data_paths=args.data, config_path=args.config
|
|
239
253
|
)
|
|
@@ -250,7 +264,9 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
|
|
|
250
264
|
|
|
251
265
|
assistant_name = _get_assistant_name(config)
|
|
252
266
|
|
|
253
|
-
structlogger.info(
|
|
267
|
+
structlogger.info(
|
|
268
|
+
"rasa.studio.upload.nlu_data_validate", event_info="Validating data..."
|
|
269
|
+
)
|
|
254
270
|
_check_for_missing_primitives(
|
|
255
271
|
intents, entities, intents_from_files, entities_from_files
|
|
256
272
|
)
|
|
@@ -267,10 +283,33 @@ def upload_nlu_assistant(args: argparse.Namespace, endpoint: str) -> StudioResul
|
|
|
267
283
|
|
|
268
284
|
graphql_req = build_request(assistant_name, nlu_examples_yaml, domain_yaml)
|
|
269
285
|
|
|
270
|
-
structlogger.info(
|
|
286
|
+
structlogger.info(
|
|
287
|
+
"rasa.studio.upload.nlu", event_info="Uploading to Rasa Studio..."
|
|
288
|
+
)
|
|
271
289
|
return make_request(endpoint, graphql_req)
|
|
272
290
|
|
|
273
291
|
|
|
292
|
+
def is_auth_working(endpoint: str) -> bool:
|
|
293
|
+
"""Send a test request to Studio to check if auth is working."""
|
|
294
|
+
result = make_request(
|
|
295
|
+
endpoint,
|
|
296
|
+
{
|
|
297
|
+
"operationName": "LicenseDetails",
|
|
298
|
+
"query": (
|
|
299
|
+
"query LicenseDetails {\n"
|
|
300
|
+
" licenseDetails {\n"
|
|
301
|
+
" valid\n"
|
|
302
|
+
" scopes\n"
|
|
303
|
+
" __typename\n"
|
|
304
|
+
" }\n"
|
|
305
|
+
"}"
|
|
306
|
+
),
|
|
307
|
+
"variables": {},
|
|
308
|
+
},
|
|
309
|
+
)
|
|
310
|
+
return result.was_successful
|
|
311
|
+
|
|
312
|
+
|
|
274
313
|
def make_request(endpoint: str, graphql_req: Dict) -> StudioResult:
|
|
275
314
|
"""Makes a request to the studio endpoint to upload data.
|
|
276
315
|
|
|
@@ -301,7 +340,12 @@ def _add_missing_entities(
|
|
|
301
340
|
for entity in entities_from_intents:
|
|
302
341
|
if entity not in entities:
|
|
303
342
|
structlogger.warning(
|
|
304
|
-
|
|
343
|
+
"rasa.studio.upload.adding_missing_entity",
|
|
344
|
+
event_info=(
|
|
345
|
+
f"Adding entity '{entity}' to upload "
|
|
346
|
+
"since it is used in an intent."
|
|
347
|
+
),
|
|
348
|
+
entity=entity,
|
|
305
349
|
)
|
|
306
350
|
all_entities.append(entity)
|
|
307
351
|
return all_entities
|