rasa-pro 3.11.0rc1__py3-none-any.whl → 3.11.0rc3__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/cli/inspect.py +2 -0
- rasa/cli/studio/studio.py +18 -8
- rasa/core/actions/action_repeat_bot_messages.py +17 -0
- rasa/core/channels/channel.py +17 -0
- rasa/core/channels/development_inspector.py +4 -1
- rasa/core/channels/voice_ready/audiocodes.py +15 -4
- rasa/core/channels/voice_ready/jambonz.py +13 -2
- rasa/core/channels/voice_ready/twilio_voice.py +6 -21
- rasa/core/channels/voice_stream/asr/asr_event.py +1 -1
- rasa/core/channels/voice_stream/asr/azure.py +5 -7
- rasa/core/channels/voice_stream/asr/deepgram.py +13 -11
- rasa/core/channels/voice_stream/voice_channel.py +61 -19
- rasa/core/nlg/contextual_response_rephraser.py +20 -12
- rasa/core/policies/enterprise_search_policy.py +32 -72
- rasa/core/policies/intentless_policy.py +34 -72
- rasa/dialogue_understanding/coexistence/llm_based_router.py +18 -33
- rasa/dialogue_understanding/generator/constants.py +0 -2
- rasa/dialogue_understanding/generator/flow_retrieval.py +33 -50
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -40
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +18 -20
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +19 -1
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +26 -22
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +9 -0
- rasa/dialogue_understanding/processor/command_processor.py +21 -1
- rasa/e2e_test/e2e_test_case.py +85 -6
- rasa/engine/validation.py +88 -60
- rasa/model_service.py +3 -0
- rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
- rasa/server.py +3 -1
- rasa/shared/constants.py +5 -5
- rasa/shared/core/constants.py +1 -1
- rasa/shared/core/domain.py +0 -26
- rasa/shared/core/flows/flows_list.py +5 -1
- rasa/shared/providers/_configs/litellm_router_client_config.py +29 -9
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +6 -14
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +1 -1
- rasa/shared/providers/llm/_base_litellm_client.py +32 -1
- rasa/shared/providers/llm/litellm_router_llm_client.py +56 -1
- rasa/shared/providers/llm/self_hosted_llm_client.py +4 -28
- rasa/shared/providers/router/_base_litellm_router_client.py +35 -1
- rasa/shared/utils/common.py +1 -1
- rasa/shared/utils/health_check/__init__.py +0 -0
- rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
- rasa/shared/utils/health_check/health_check.py +256 -0
- rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
- rasa/shared/utils/llm.py +5 -2
- rasa/shared/utils/yaml.py +102 -62
- rasa/studio/auth.py +3 -5
- rasa/studio/config.py +13 -4
- rasa/studio/constants.py +1 -0
- rasa/studio/data_handler.py +10 -3
- rasa/studio/upload.py +21 -10
- rasa/telemetry.py +15 -1
- rasa/tracing/config.py +3 -1
- rasa/tracing/instrumentation/attribute_extractors.py +20 -0
- rasa/tracing/instrumentation/instrumentation.py +121 -0
- rasa/utils/common.py +5 -0
- rasa/utils/io.py +8 -16
- rasa/utils/sanic_error_handler.py +32 -0
- rasa/version.py +1 -1
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc3.dist-info}/METADATA +3 -2
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc3.dist-info}/RECORD +65 -61
- rasa/shared/utils/health_check.py +0 -533
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc3.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc3.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc3.dist-info}/entry_points.txt +0 -0
rasa/e2e_test/e2e_test_case.py
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import logging
|
|
2
1
|
from collections import OrderedDict
|
|
2
|
+
from collections import defaultdict
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from typing import Any, Dict, List, Optional, Text, Union
|
|
5
5
|
|
|
6
|
+
import structlog
|
|
7
|
+
|
|
6
8
|
from rasa.e2e_test.assertions import Assertion
|
|
7
9
|
from rasa.e2e_test.constants import (
|
|
8
10
|
KEY_ASSERTIONS,
|
|
@@ -20,10 +22,11 @@ from rasa.e2e_test.constants import (
|
|
|
20
22
|
KEY_USER_INPUT,
|
|
21
23
|
)
|
|
22
24
|
from rasa.e2e_test.stub_custom_action import StubCustomAction
|
|
25
|
+
from rasa.shared.constants import DOCS_BASE_URL
|
|
23
26
|
from rasa.shared.core.events import BotUttered, SlotSet, UserUttered
|
|
24
27
|
from rasa.shared.exceptions import RasaException
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
structlogger = structlog.get_logger(__name__)
|
|
27
30
|
|
|
28
31
|
|
|
29
32
|
@dataclass(frozen=True)
|
|
@@ -343,9 +346,10 @@ class ActualStepOutput:
|
|
|
343
346
|
try:
|
|
344
347
|
return self.user_uttered_events[0]
|
|
345
348
|
except IndexError:
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
f"
|
|
349
|
+
structlogger.debug(
|
|
350
|
+
"e2e_test_case.get_user_uttered_event.no_user_uttered_event",
|
|
351
|
+
event_info=f"Could not find `UserUttered` event in the "
|
|
352
|
+
f"ActualStepOutput: {self}",
|
|
349
353
|
)
|
|
350
354
|
return None
|
|
351
355
|
return None
|
|
@@ -395,7 +399,7 @@ class TestCase:
|
|
|
395
399
|
else:
|
|
396
400
|
steps.append(TestStep.from_dict(step))
|
|
397
401
|
|
|
398
|
-
|
|
402
|
+
test_case = TestCase(
|
|
399
403
|
name=input_test_case.get(KEY_TEST_CASE, "default"),
|
|
400
404
|
steps=steps,
|
|
401
405
|
file=file,
|
|
@@ -405,6 +409,81 @@ class TestCase:
|
|
|
405
409
|
fixture_names=input_test_case.get(KEY_FIXTURES),
|
|
406
410
|
metadata_name=input_test_case.get(KEY_METADATA),
|
|
407
411
|
)
|
|
412
|
+
test_case.validate()
|
|
413
|
+
return test_case
|
|
414
|
+
|
|
415
|
+
def validate(self) -> None:
|
|
416
|
+
"""Validates the test case.
|
|
417
|
+
|
|
418
|
+
This method calls all validation methods required for the test case.
|
|
419
|
+
"""
|
|
420
|
+
if self.uses_assertions():
|
|
421
|
+
self.validate_duplicate_user_messages_metadata()
|
|
422
|
+
|
|
423
|
+
def validate_duplicate_user_messages_metadata(self) -> None:
|
|
424
|
+
"""Validates that duplicate user messages use metadata correctly.
|
|
425
|
+
|
|
426
|
+
Ensures that each duplicate user message uses unique metadata.
|
|
427
|
+
|
|
428
|
+
Raises warnings if any issues are found.
|
|
429
|
+
"""
|
|
430
|
+
docs_link = (
|
|
431
|
+
f"{DOCS_BASE_URL}/testing/e2e-testing-assertions/assertions-how-to-guide/"
|
|
432
|
+
"#how-to-handle-duplicate-user-text-messages-in-the-same-test-case"
|
|
433
|
+
)
|
|
434
|
+
no_metadata_event_info = (
|
|
435
|
+
"Test case '{name}' has duplicate user steps with text '{text}', "
|
|
436
|
+
"and user step at line {line} lacks metadata. When using "
|
|
437
|
+
"duplicate user messages, metadata should be set on each step to ensure "
|
|
438
|
+
f"correct processing. Please refer to the documentation: {docs_link}"
|
|
439
|
+
)
|
|
440
|
+
non_unique_metadata_event_info = (
|
|
441
|
+
"Test case '{name}' has duplicate user steps with text '{text}', "
|
|
442
|
+
"and user step at line {line} has duplicate metadata "
|
|
443
|
+
"name '{metadata_name}'. Metadata names should be unique for each user "
|
|
444
|
+
"step among duplicates. This may cause issues in processing "
|
|
445
|
+
f"user messages. Please refer to the documentation: {docs_link}"
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
# Use dict[str, list] structure to group steps by user message text to easily
|
|
449
|
+
# identify and validate instances with duplicate messages and their metadata.
|
|
450
|
+
message_steps = defaultdict(list)
|
|
451
|
+
|
|
452
|
+
# Collect user steps by text
|
|
453
|
+
for step in self.steps:
|
|
454
|
+
if step.actor == KEY_USER_INPUT and step.text:
|
|
455
|
+
message_steps[step.text].append(step)
|
|
456
|
+
|
|
457
|
+
# Check for duplicate messages
|
|
458
|
+
for text, steps in message_steps.items():
|
|
459
|
+
if len(steps) <= 1:
|
|
460
|
+
continue
|
|
461
|
+
|
|
462
|
+
metadata_names_used = set()
|
|
463
|
+
for step in steps:
|
|
464
|
+
if not step.metadata_name:
|
|
465
|
+
structlogger.warning(
|
|
466
|
+
"e2e_test_case.validate_duplicate_user_messages_metadata.no_metadata",
|
|
467
|
+
event_info=no_metadata_event_info.format(
|
|
468
|
+
name=self.name,
|
|
469
|
+
text=text,
|
|
470
|
+
line=step.line,
|
|
471
|
+
),
|
|
472
|
+
)
|
|
473
|
+
break
|
|
474
|
+
elif step.metadata_name in metadata_names_used:
|
|
475
|
+
structlogger.warning(
|
|
476
|
+
"e2e_test_case.validate_duplicate_user_messages_metadata.non_unique_metadata",
|
|
477
|
+
event_info=non_unique_metadata_event_info.format(
|
|
478
|
+
name=self.name,
|
|
479
|
+
text=text,
|
|
480
|
+
line=step.line,
|
|
481
|
+
metadata_name=step.metadata_name,
|
|
482
|
+
),
|
|
483
|
+
)
|
|
484
|
+
break
|
|
485
|
+
else:
|
|
486
|
+
metadata_names_used.add(step.metadata_name)
|
|
408
487
|
|
|
409
488
|
def as_dict(self) -> Dict[Text, Any]:
|
|
410
489
|
"""Returns the test case as a dictionary."""
|
rasa/engine/validation.py
CHANGED
|
@@ -18,11 +18,10 @@ from typing import (
|
|
|
18
18
|
List,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
+
import rasa.utils.common
|
|
21
22
|
import structlog
|
|
22
23
|
import typing_utils
|
|
23
|
-
|
|
24
|
-
import rasa.utils.common
|
|
25
|
-
from rasa.core import IntentlessPolicy
|
|
24
|
+
from rasa.core import IntentlessPolicy, ContextualResponseRephraser
|
|
26
25
|
from rasa.core.policies.policy import PolicyPrediction
|
|
27
26
|
from rasa.core.utils import AvailableEndpoints
|
|
28
27
|
from rasa.dialogue_understanding.coexistence.constants import (
|
|
@@ -67,11 +66,13 @@ from rasa.shared.constants import (
|
|
|
67
66
|
MODEL_GROUP_ID_CONFIG_KEY,
|
|
68
67
|
ROUTER_CONFIG_KEY,
|
|
69
68
|
MODELS_CONFIG_KEY,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
69
|
+
MODEL_GROUP_CONFIG_KEY,
|
|
70
|
+
ROUTING_STRATEGY_CONFIG_KEY,
|
|
71
|
+
VALID_ROUTING_STRATEGIES,
|
|
72
|
+
ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE,
|
|
73
|
+
ROUTING_STRATEGIES_NOT_REQUIRING_CACHE,
|
|
74
74
|
REDIS_HOST_CONFIG_KEY,
|
|
75
|
+
USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY,
|
|
75
76
|
)
|
|
76
77
|
from rasa.shared.core.constants import ACTION_RESET_ROUTING, ACTION_TRIGGER_CHITCHAT
|
|
77
78
|
from rasa.shared.core.domain import Domain
|
|
@@ -890,13 +891,13 @@ def _validate_component_model_client_config(
|
|
|
890
891
|
# no llm configuration present
|
|
891
892
|
return
|
|
892
893
|
|
|
893
|
-
if
|
|
894
|
+
if MODEL_GROUP_CONFIG_KEY in component_config[key]:
|
|
894
895
|
model_group_syntax_used.append(True)
|
|
895
|
-
model_group_ids.append(component_config[key][
|
|
896
|
+
model_group_ids.append(component_config[key][MODEL_GROUP_CONFIG_KEY])
|
|
896
897
|
|
|
897
898
|
if len(component_config[key]) > 1:
|
|
898
899
|
print_error_and_exit(
|
|
899
|
-
f"You specified a '{
|
|
900
|
+
f"You specified a '{MODEL_GROUP_CONFIG_KEY}' for the '{key}' "
|
|
900
901
|
f"config key for the component "
|
|
901
902
|
f"'{component_name or component_config['name']}'. "
|
|
902
903
|
"No other parameters are allowed under the "
|
|
@@ -908,10 +909,9 @@ def _validate_component_model_client_config(
|
|
|
908
909
|
# check that api_key is not set in config
|
|
909
910
|
if API_KEY in component_config[key]:
|
|
910
911
|
print_error_and_exit(
|
|
911
|
-
f"You specified '{API_KEY}' in the config for"
|
|
912
|
-
f"{component_name or component_config['name']}, which "
|
|
913
|
-
"
|
|
914
|
-
"environment variables."
|
|
912
|
+
f"You specified '{API_KEY}' in the config for "
|
|
913
|
+
f"'{component_name or component_config['name']}', which is not allowed."
|
|
914
|
+
" Set API keys through environment variables."
|
|
915
915
|
)
|
|
916
916
|
|
|
917
917
|
|
|
@@ -936,34 +936,46 @@ def validate_model_client_configuration_setup(config: Dict[str, Any]) -> None:
|
|
|
936
936
|
model_group_syntax_used: List[bool] = []
|
|
937
937
|
model_group_ids: List[str] = []
|
|
938
938
|
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
for component in config["pipeline"]:
|
|
943
|
-
for key in [LLM_CONFIG_KEY, EMBEDDINGS_CONFIG_KEY]:
|
|
944
|
-
_validate_component_model_client_config(
|
|
945
|
-
component, key, model_group_syntax_used, model_group_ids
|
|
946
|
-
)
|
|
939
|
+
for outer_key in ["pipeline", "policies"]:
|
|
940
|
+
if outer_key not in config or config[outer_key] is None:
|
|
941
|
+
continue
|
|
947
942
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if FLOW_RETRIEVAL_KEY in component:
|
|
951
|
-
if EMBEDDINGS_CONFIG_KEY in component[FLOW_RETRIEVAL_KEY]:
|
|
943
|
+
for component in config[outer_key]:
|
|
944
|
+
for key in [LLM_CONFIG_KEY, EMBEDDINGS_CONFIG_KEY]:
|
|
952
945
|
_validate_component_model_client_config(
|
|
953
|
-
component
|
|
954
|
-
EMBEDDINGS_CONFIG_KEY,
|
|
955
|
-
model_group_syntax_used,
|
|
956
|
-
model_group_ids,
|
|
957
|
-
component["name"] + "." + FLOW_RETRIEVAL_KEY,
|
|
946
|
+
component, key, model_group_syntax_used, model_group_ids
|
|
958
947
|
)
|
|
959
948
|
|
|
949
|
+
# as flow retrieval is not a component itself, we need to
|
|
950
|
+
# check it separately
|
|
951
|
+
if FLOW_RETRIEVAL_KEY in component:
|
|
952
|
+
if EMBEDDINGS_CONFIG_KEY in component[FLOW_RETRIEVAL_KEY]:
|
|
953
|
+
_validate_component_model_client_config(
|
|
954
|
+
component[FLOW_RETRIEVAL_KEY],
|
|
955
|
+
EMBEDDINGS_CONFIG_KEY,
|
|
956
|
+
model_group_syntax_used,
|
|
957
|
+
model_group_ids,
|
|
958
|
+
component["name"] + "." + FLOW_RETRIEVAL_KEY,
|
|
959
|
+
)
|
|
960
|
+
|
|
961
|
+
# also include the ContextualResponseRephraser component
|
|
962
|
+
endpoints = AvailableEndpoints.get_instance()
|
|
963
|
+
if endpoints.nlg is not None:
|
|
964
|
+
_validate_component_model_client_config(
|
|
965
|
+
endpoints.nlg.kwargs,
|
|
966
|
+
LLM_CONFIG_KEY,
|
|
967
|
+
model_group_syntax_used,
|
|
968
|
+
model_group_ids,
|
|
969
|
+
ContextualResponseRephraser.__name__,
|
|
970
|
+
)
|
|
971
|
+
|
|
960
972
|
if not is_uniform_bool_list(model_group_syntax_used):
|
|
961
973
|
print_error_and_exit(
|
|
962
974
|
"Some of your components refer to an LLM using the "
|
|
963
|
-
f"'{
|
|
964
|
-
f"define the LLM under the '{LLM_CONFIG_KEY}' or the "
|
|
975
|
+
f"'{MODEL_GROUP_CONFIG_KEY}' parameter, other components directly"
|
|
976
|
+
f" define the LLM under the '{LLM_CONFIG_KEY}' or the "
|
|
965
977
|
f"'{EMBEDDINGS_CONFIG_KEY}' key. You cannot use"
|
|
966
|
-
"
|
|
978
|
+
" both types of definitions. Please chose one syntax "
|
|
967
979
|
"and update your config."
|
|
968
980
|
)
|
|
969
981
|
|
|
@@ -1042,31 +1054,42 @@ def _validate_model_group_router_setting(
|
|
|
1042
1054
|
if ROUTER_CONFIG_KEY not in model_group:
|
|
1043
1055
|
continue
|
|
1044
1056
|
|
|
1057
|
+
for model_config in model_group.get(MODELS_CONFIG_KEY, []):
|
|
1058
|
+
if USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY in model_config:
|
|
1059
|
+
print_error_and_exit(
|
|
1060
|
+
f"You defined the '{USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY}' in "
|
|
1061
|
+
f"the model group '{model_group[MODEL_GROUP_ID_CONFIG_KEY]}'. This "
|
|
1062
|
+
f"key is not allowed in the model configuration as the router is "
|
|
1063
|
+
f"defined. Please remove this key from your model configuration "
|
|
1064
|
+
f"and update it in the '{ROUTER_CONFIG_KEY} configuration, as it "
|
|
1065
|
+
f"is a router level setting."
|
|
1066
|
+
)
|
|
1067
|
+
|
|
1045
1068
|
router_config = model_group[ROUTER_CONFIG_KEY]
|
|
1046
|
-
if
|
|
1047
|
-
|
|
1048
|
-
if
|
|
1069
|
+
if ROUTING_STRATEGY_CONFIG_KEY in router_config:
|
|
1070
|
+
routing_strategy = router_config.get(ROUTING_STRATEGY_CONFIG_KEY)
|
|
1071
|
+
if routing_strategy and routing_strategy not in VALID_ROUTING_STRATEGIES:
|
|
1049
1072
|
print_error_and_exit(
|
|
1050
|
-
f"The
|
|
1051
|
-
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' is not
|
|
1052
|
-
f"Valid
|
|
1073
|
+
f"The routing strategy '{routing_strategy}' you defined for the "
|
|
1074
|
+
f"model group '{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' is not "
|
|
1075
|
+
f"valid. Valid routing strategies are categorized as follows:\n"
|
|
1053
1076
|
f"- Strategies requiring Redis caching: "
|
|
1054
|
-
f"{', '.join(
|
|
1077
|
+
f"{', '.join(ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE)}\n"
|
|
1055
1078
|
f"- Strategies not requiring caching: "
|
|
1056
|
-
f"{', '.join(
|
|
1079
|
+
f"{', '.join(ROUTING_STRATEGIES_NOT_REQUIRING_CACHE)}"
|
|
1057
1080
|
)
|
|
1058
1081
|
if (
|
|
1059
|
-
|
|
1082
|
+
routing_strategy in ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE
|
|
1060
1083
|
and REDIS_HOST_CONFIG_KEY not in router_config
|
|
1061
1084
|
):
|
|
1062
1085
|
structlogger.warning(
|
|
1063
|
-
"validation.
|
|
1086
|
+
"validation.routing_strategy.redis_host_not_defined",
|
|
1064
1087
|
event_info=(
|
|
1065
|
-
f"The
|
|
1066
|
-
f" to be defined. Without a Redis host, the system
|
|
1067
|
-
f"'in-memory' caching. Please add the
|
|
1068
|
-
f" to the router configuration for
|
|
1069
|
-
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}'."
|
|
1088
|
+
f"The routing strategy '{routing_strategy}' requires a Redis "
|
|
1089
|
+
f"host to be defined. Without a Redis host, the system "
|
|
1090
|
+
f"defaults to 'in-memory' caching. Please add the "
|
|
1091
|
+
f"'{REDIS_HOST_CONFIG_KEY}' to the router configuration for "
|
|
1092
|
+
f"the model group '{model_group[MODEL_GROUP_ID_CONFIG_KEY]}'."
|
|
1070
1093
|
),
|
|
1071
1094
|
)
|
|
1072
1095
|
|
|
@@ -1106,17 +1129,22 @@ def _validate_api_key_is_an_environment_variable(
|
|
|
1106
1129
|
for model_group in model_groups:
|
|
1107
1130
|
for model_config in model_group[MODELS_CONFIG_KEY]:
|
|
1108
1131
|
for key, value in model_config.items():
|
|
1109
|
-
if
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1132
|
+
if key == API_KEY:
|
|
1133
|
+
if isinstance(value, str):
|
|
1134
|
+
if not re.match(r"\${(\w+)}", value):
|
|
1135
|
+
print_error_and_exit(
|
|
1136
|
+
f"You defined the '{API_KEY}' in model group "
|
|
1137
|
+
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' as a "
|
|
1138
|
+
f"string. The '{API_KEY}' must be set as an environment"
|
|
1139
|
+
f" variable. Please update your config."
|
|
1140
|
+
)
|
|
1141
|
+
else:
|
|
1142
|
+
print_error_and_exit(
|
|
1143
|
+
f"You should define the '{API_KEY}' in model group "
|
|
1144
|
+
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' using the "
|
|
1145
|
+
f"environment variable syntax - ${{ENV_VARIABLE_NAME}}. "
|
|
1146
|
+
f"Please update your config."
|
|
1147
|
+
)
|
|
1120
1148
|
|
|
1121
1149
|
|
|
1122
1150
|
def validate_model_group_configuration_setup() -> None:
|
rasa/model_service.py
CHANGED
|
@@ -14,6 +14,7 @@ import rasa.utils.licensing
|
|
|
14
14
|
from urllib.parse import urlparse
|
|
15
15
|
|
|
16
16
|
from rasa.utils.log_utils import configure_structlog
|
|
17
|
+
from rasa.utils.sanic_error_handler import register_custom_sanic_error_handler
|
|
17
18
|
|
|
18
19
|
structlogger = structlog.get_logger()
|
|
19
20
|
|
|
@@ -104,6 +105,8 @@ def main() -> None:
|
|
|
104
105
|
# list all routes
|
|
105
106
|
list_routes(app)
|
|
106
107
|
|
|
108
|
+
register_custom_sanic_error_handler(app)
|
|
109
|
+
|
|
107
110
|
app.run(host="0.0.0.0", port=MODEL_SERVICE_PORT, legacy=True, motd=False)
|
|
108
111
|
|
|
109
112
|
|
|
@@ -43,8 +43,6 @@ class WhitespaceTokenizer(Tokenizer):
|
|
|
43
43
|
def __init__(self, config: Dict[Text, Any]) -> None:
|
|
44
44
|
"""Initialize the tokenizer."""
|
|
45
45
|
super().__init__(config)
|
|
46
|
-
self.emoji_pattern = rasa.utils.io.get_emoji_regex()
|
|
47
|
-
|
|
48
46
|
if "case_sensitive" in self._config:
|
|
49
47
|
rasa.shared.utils.io.raise_warning(
|
|
50
48
|
"The option 'case_sensitive' was moved from the tokenizers to the "
|
|
@@ -64,18 +62,9 @@ class WhitespaceTokenizer(Tokenizer):
|
|
|
64
62
|
# Path to the dictionaries on the local filesystem.
|
|
65
63
|
return cls(config)
|
|
66
64
|
|
|
67
|
-
def remove_emoji(self, text: Text) -> Text:
|
|
68
|
-
"""Remove emoji if the full text, aka token, matches the emoji regex."""
|
|
69
|
-
match = self.emoji_pattern.fullmatch(text)
|
|
70
|
-
|
|
71
|
-
if match is not None:
|
|
72
|
-
return ""
|
|
73
|
-
|
|
74
|
-
return text
|
|
75
|
-
|
|
76
65
|
def tokenize(self, message: Message, attribute: Text) -> List[Token]:
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
original_text = message.get(attribute)
|
|
67
|
+
text = rasa.utils.io.remove_emojis(original_text)
|
|
79
68
|
# we need to use regex instead of re, because of
|
|
80
69
|
# https://stackoverflow.com/questions/12746458/python-unicode-regular-expression-matching-failing-with-some-unicode-characters
|
|
81
70
|
|
|
@@ -94,11 +83,11 @@ class WhitespaceTokenizer(Tokenizer):
|
|
|
94
83
|
text,
|
|
95
84
|
).split()
|
|
96
85
|
|
|
97
|
-
words = [self.remove_emoji(w) for w in words]
|
|
98
86
|
words = [w for w in words if w]
|
|
99
87
|
|
|
100
88
|
# if we removed everything like smiles `:)`, use the whole text as 1 token
|
|
101
89
|
if not words:
|
|
90
|
+
text = original_text
|
|
102
91
|
words = [text]
|
|
103
92
|
|
|
104
93
|
tokens = self._convert_words_to_tokens(words, text)
|
rasa/server.py
CHANGED
|
@@ -78,6 +78,7 @@ from rasa.shared.utils.schemas.events import EVENTS_SCHEMA
|
|
|
78
78
|
from rasa.shared.utils.yaml import validate_training_data
|
|
79
79
|
from rasa.utils.common import TempDirectoryPath, get_temp_dir_name
|
|
80
80
|
from rasa.utils.endpoints import EndpointConfig
|
|
81
|
+
from rasa.utils.sanic_error_handler import register_custom_sanic_error_handler
|
|
81
82
|
|
|
82
83
|
if TYPE_CHECKING:
|
|
83
84
|
from ssl import SSLContext
|
|
@@ -528,7 +529,7 @@ def add_root_route(app: Sanic) -> None:
|
|
|
528
529
|
<p>Hello from Rasa: {rasa.__version__}</p>
|
|
529
530
|
<a href="./webhooks/inspector/inspect.html">Go to the inspector</a>
|
|
530
531
|
<script>
|
|
531
|
-
window.location.replace("./webhooks/
|
|
532
|
+
window.location.replace("./webhooks/socketio/inspect.html");
|
|
532
533
|
</script>
|
|
533
534
|
</body>
|
|
534
535
|
</html>
|
|
@@ -687,6 +688,7 @@ def create_app(
|
|
|
687
688
|
app = Sanic("rasa_server")
|
|
688
689
|
app.config.RESPONSE_TIMEOUT = response_timeout
|
|
689
690
|
configure_cors(app, cors_origins)
|
|
691
|
+
register_custom_sanic_error_handler(app)
|
|
690
692
|
|
|
691
693
|
# Reset Sanic warnings filter that allows the triggering of Sanic warnings
|
|
692
694
|
warnings.filterwarnings("ignore", category=DeprecationWarning, module=r"sanic.*")
|
rasa/shared/constants.py
CHANGED
|
@@ -184,19 +184,19 @@ N_REPHRASES_CONFIG_KEY = "n"
|
|
|
184
184
|
USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY = "use_chat_completions_endpoint"
|
|
185
185
|
|
|
186
186
|
ROUTER_CONFIG_KEY = "router"
|
|
187
|
-
|
|
187
|
+
ROUTING_STRATEGY_CONFIG_KEY = "routing_strategy"
|
|
188
188
|
REDIS_HOST_CONFIG_KEY = "redis_host"
|
|
189
|
-
|
|
189
|
+
ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE = [
|
|
190
190
|
"cost-based-routing",
|
|
191
191
|
"usage-based-routing",
|
|
192
192
|
]
|
|
193
|
-
|
|
193
|
+
ROUTING_STRATEGIES_NOT_REQUIRING_CACHE = [
|
|
194
194
|
"latency-based-routing",
|
|
195
195
|
"least-busy",
|
|
196
196
|
"simple-shuffle",
|
|
197
197
|
]
|
|
198
|
-
|
|
199
|
-
|
|
198
|
+
VALID_ROUTING_STRATEGIES = (
|
|
199
|
+
ROUTING_STRATEGIES_REQUIRING_REDIS_CACHE + ROUTING_STRATEGIES_NOT_REQUIRING_CACHE
|
|
200
200
|
)
|
|
201
201
|
|
|
202
202
|
MODELS_CONFIG_KEY = "models"
|
rasa/shared/core/constants.py
CHANGED
|
@@ -110,8 +110,8 @@ FLOW_SLOT_NAMES = [FLOW_HASHES_SLOT]
|
|
|
110
110
|
|
|
111
111
|
# slots for audio timeout
|
|
112
112
|
SLOT_SILENCE_TIMEOUT = "silence_timeout"
|
|
113
|
-
SILENCE_TIMEOUT_DEFAULT_VALUE = 6.0
|
|
114
113
|
SLOT_CONSECUTIVE_SILENCE_TIMEOUTS = "consecutive_silence_timeouts"
|
|
114
|
+
SILENCE_TIMEOUT_DEFAULT_VALUE = 6.0
|
|
115
115
|
SILENCE_SLOTS = [SLOT_SILENCE_TIMEOUT, SLOT_CONSECUTIVE_SILENCE_TIMEOUTS]
|
|
116
116
|
# slots for knowledge base
|
|
117
117
|
SLOT_LISTED_ITEMS = "knowledge_base_listed_objects"
|
rasa/shared/core/domain.py
CHANGED
|
@@ -3,7 +3,6 @@ from __future__ import annotations
|
|
|
3
3
|
import collections
|
|
4
4
|
import copy
|
|
5
5
|
import json
|
|
6
|
-
import math
|
|
7
6
|
import os
|
|
8
7
|
from dataclasses import dataclass
|
|
9
8
|
from functools import cached_property
|
|
@@ -58,7 +57,6 @@ from rasa.shared.core.events import SlotSet, UserUttered
|
|
|
58
57
|
from rasa.shared.core.slots import (
|
|
59
58
|
AnySlot,
|
|
60
59
|
CategoricalSlot,
|
|
61
|
-
FloatSlot,
|
|
62
60
|
ListSlot,
|
|
63
61
|
Slot,
|
|
64
62
|
TextSlot,
|
|
@@ -1084,7 +1082,6 @@ class Domain:
|
|
|
1084
1082
|
self._add_knowledge_base_slots()
|
|
1085
1083
|
self._add_categorical_slot_default_value()
|
|
1086
1084
|
self._add_session_metadata_slot()
|
|
1087
|
-
self._add_audio_slots()
|
|
1088
1085
|
|
|
1089
1086
|
def _add_categorical_slot_default_value(self) -> None:
|
|
1090
1087
|
"""Add a default value to all categorical slots.
|
|
@@ -1139,29 +1136,6 @@ class Domain:
|
|
|
1139
1136
|
)
|
|
1140
1137
|
)
|
|
1141
1138
|
|
|
1142
|
-
def _add_audio_slots(self) -> None:
|
|
1143
|
-
"""Add slots relevant for audio channels."""
|
|
1144
|
-
self.slots.append(
|
|
1145
|
-
FloatSlot(
|
|
1146
|
-
rasa.shared.core.constants.SLOT_SILENCE_TIMEOUT,
|
|
1147
|
-
mappings=[],
|
|
1148
|
-
influence_conversation=False,
|
|
1149
|
-
is_builtin=True,
|
|
1150
|
-
initial_value=rasa.shared.core.constants.SILENCE_TIMEOUT_DEFAULT_VALUE,
|
|
1151
|
-
max_value=math.inf,
|
|
1152
|
-
)
|
|
1153
|
-
)
|
|
1154
|
-
self.slots.append(
|
|
1155
|
-
FloatSlot(
|
|
1156
|
-
rasa.shared.core.constants.SLOT_CONSECUTIVE_SILENCE_TIMEOUTS,
|
|
1157
|
-
mappings=[],
|
|
1158
|
-
influence_conversation=False,
|
|
1159
|
-
is_builtin=True,
|
|
1160
|
-
initial_value=0.0,
|
|
1161
|
-
max_value=math.inf,
|
|
1162
|
-
)
|
|
1163
|
-
)
|
|
1164
|
-
|
|
1165
1139
|
def _add_knowledge_base_slots(self) -> None:
|
|
1166
1140
|
"""Add slots for the knowledge base action to slots.
|
|
1167
1141
|
|
|
@@ -234,12 +234,16 @@ class FlowsList:
|
|
|
234
234
|
[f for f in self.underlying_flows if not f.is_startable_only_via_link()]
|
|
235
235
|
)
|
|
236
236
|
|
|
237
|
-
def available_slot_names(
|
|
237
|
+
def available_slot_names(
|
|
238
|
+
self, ask_before_filling: Optional[bool] = None
|
|
239
|
+
) -> Set[str]:
|
|
238
240
|
"""Get all slot names collected by flows."""
|
|
239
241
|
return {
|
|
240
242
|
step.collect
|
|
241
243
|
for flow in self.underlying_flows
|
|
242
244
|
for step in flow.get_collect_steps()
|
|
245
|
+
if ask_before_filling is None
|
|
246
|
+
or step.ask_before_filling == ask_before_filling
|
|
243
247
|
}
|
|
244
248
|
|
|
245
249
|
def available_custom_actions(self) -> Set[str]:
|
|
@@ -14,6 +14,7 @@ from rasa.shared.constants import (
|
|
|
14
14
|
API_TYPE_CONFIG_KEY,
|
|
15
15
|
MODEL_CONFIG_KEY,
|
|
16
16
|
MODEL_LIST_KEY,
|
|
17
|
+
USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY,
|
|
17
18
|
)
|
|
18
19
|
from rasa.shared.providers._configs.model_group_config import (
|
|
19
20
|
ModelGroupConfig,
|
|
@@ -29,6 +30,7 @@ _LITELLM_UNSUPPORTED_KEYS = [
|
|
|
29
30
|
PROVIDER_CONFIG_KEY,
|
|
30
31
|
DEPLOYMENT_CONFIG_KEY,
|
|
31
32
|
API_TYPE_CONFIG_KEY,
|
|
33
|
+
USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY,
|
|
32
34
|
]
|
|
33
35
|
|
|
34
36
|
|
|
@@ -84,6 +86,7 @@ class LiteLLMRouterClientConfig:
|
|
|
84
86
|
|
|
85
87
|
_model_group_config: ModelGroupConfig
|
|
86
88
|
router: Dict[str, Any]
|
|
89
|
+
_use_chat_completions_endpoint: bool = True
|
|
87
90
|
extra_parameters: dict = field(default_factory=dict)
|
|
88
91
|
|
|
89
92
|
@property
|
|
@@ -98,6 +101,14 @@ class LiteLLMRouterClientConfig:
|
|
|
98
101
|
def litellm_model_list(self) -> List[Dict[str, Any]]:
|
|
99
102
|
return self._convert_models_to_litellm_model_list()
|
|
100
103
|
|
|
104
|
+
@property
|
|
105
|
+
def litellm_router_settings(self) -> Dict[str, Any]:
|
|
106
|
+
return self._convert_router_to_litellm_router_settings()
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def use_chat_completions_endpoint(self) -> bool:
|
|
110
|
+
return self._use_chat_completions_endpoint
|
|
111
|
+
|
|
101
112
|
def __post_init__(self) -> None:
|
|
102
113
|
if not self.router:
|
|
103
114
|
message = "Router cannot be empty."
|
|
@@ -121,7 +132,6 @@ class LiteLLMRouterClientConfig:
|
|
|
121
132
|
Returns:
|
|
122
133
|
LiteLLMRouterClientConfig
|
|
123
134
|
"""
|
|
124
|
-
|
|
125
135
|
model_group_config = ModelGroupConfig.from_dict(config)
|
|
126
136
|
|
|
127
137
|
# Copy config to avoid mutating the original
|
|
@@ -130,13 +140,18 @@ class LiteLLMRouterClientConfig:
|
|
|
130
140
|
config_copy.pop(MODEL_GROUP_ID_CONFIG_KEY, None)
|
|
131
141
|
config_copy.pop(MODELS_CONFIG_KEY, None)
|
|
132
142
|
# Get the router settings
|
|
133
|
-
router_settings = config_copy.pop(ROUTER_CONFIG_KEY,
|
|
143
|
+
router_settings = config_copy.pop(ROUTER_CONFIG_KEY, {})
|
|
144
|
+
# Get the use_chat_completions_endpoint setting
|
|
145
|
+
use_chat_completions_endpoint = router_settings.get(
|
|
146
|
+
USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY, True
|
|
147
|
+
)
|
|
134
148
|
# The rest is considered as extra parameters
|
|
135
149
|
extra_parameters = config_copy
|
|
136
150
|
|
|
137
151
|
this = LiteLLMRouterClientConfig(
|
|
138
152
|
_model_group_config=model_group_config,
|
|
139
153
|
router=router_settings,
|
|
154
|
+
_use_chat_completions_endpoint=use_chat_completions_endpoint,
|
|
140
155
|
extra_parameters=extra_parameters,
|
|
141
156
|
)
|
|
142
157
|
return this
|
|
@@ -150,14 +165,17 @@ class LiteLLMRouterClientConfig:
|
|
|
150
165
|
return d
|
|
151
166
|
|
|
152
167
|
def to_litellm_dict(self) -> dict:
|
|
153
|
-
|
|
154
|
-
d = {
|
|
168
|
+
return {
|
|
155
169
|
**self.extra_parameters,
|
|
156
170
|
MODEL_GROUP_ID_CONFIG_KEY: self.model_group_id,
|
|
157
|
-
MODEL_LIST_KEY:
|
|
158
|
-
ROUTER_CONFIG_KEY: self.
|
|
171
|
+
MODEL_LIST_KEY: self._convert_models_to_litellm_model_list(),
|
|
172
|
+
ROUTER_CONFIG_KEY: self._convert_router_to_litellm_router_settings(),
|
|
159
173
|
}
|
|
160
|
-
|
|
174
|
+
|
|
175
|
+
def _convert_router_to_litellm_router_settings(self) -> Dict[str, Any]:
|
|
176
|
+
_router_settings_copy = copy.deepcopy(self.router)
|
|
177
|
+
_router_settings_copy.pop(USE_CHAT_COMPLETIONS_ENDPOINT_CONFIG_KEY, None)
|
|
178
|
+
return _router_settings_copy
|
|
161
179
|
|
|
162
180
|
def _convert_models_to_litellm_model_list(self) -> List[Dict[str, Any]]:
|
|
163
181
|
litellm_model_list = []
|
|
@@ -172,7 +190,7 @@ class LiteLLMRouterClientConfig:
|
|
|
172
190
|
prefix = get_prefix_from_provider(provider)
|
|
173
191
|
|
|
174
192
|
# Determine whether to use model or deployment key based on the provider.
|
|
175
|
-
|
|
193
|
+
litellm_model_name = (
|
|
176
194
|
litellm_model_config[DEPLOYMENT_CONFIG_KEY]
|
|
177
195
|
if provider in DEPLOYMENT_CENTRIC_PROVIDERS
|
|
178
196
|
else litellm_model_config[MODEL_CONFIG_KEY]
|
|
@@ -180,7 +198,9 @@ class LiteLLMRouterClientConfig:
|
|
|
180
198
|
|
|
181
199
|
# Set 'model' to a provider prefixed model name e.g. openai/gpt-4
|
|
182
200
|
litellm_model_config[MODEL_CONFIG_KEY] = (
|
|
183
|
-
|
|
201
|
+
litellm_model_name
|
|
202
|
+
if f"{prefix}/" in litellm_model_name
|
|
203
|
+
else f"{prefix}/{litellm_model_name}"
|
|
184
204
|
)
|
|
185
205
|
|
|
186
206
|
# Remove parameters that are None and not supported by LiteLLM.
|