rasa-pro 3.11.0rc1__py3-none-any.whl → 3.11.0rc2__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/voice_ready/audiocodes.py +12 -0
- rasa/core/channels/voice_ready/jambonz.py +13 -2
- rasa/core/channels/voice_ready/twilio_voice.py +6 -21
- rasa/core/channels/voice_stream/voice_channel.py +13 -1
- rasa/core/nlg/contextual_response_rephraser.py +18 -10
- rasa/core/policies/enterprise_search_policy.py +27 -67
- rasa/core/policies/intentless_policy.py +25 -67
- 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 +24 -21
- rasa/dialogue_understanding/processor/command_processor.py +21 -1
- rasa/e2e_test/e2e_test_case.py +85 -6
- rasa/engine/validation.py +57 -41
- rasa/model_service.py +3 -0
- rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
- rasa/server.py +3 -1
- rasa/shared/core/flows/flows_list.py +5 -1
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +6 -14
- rasa/shared/providers/llm/_base_litellm_client.py +6 -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 +12 -0
- rasa/tracing/config.py +2 -0
- 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.0rc2.dist-info}/METADATA +3 -2
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc2.dist-info}/RECORD +51 -47
- rasa/shared/utils/health_check.py +0 -533
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc2.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0rc1.dist-info → rasa_pro-3.11.0rc2.dist-info}/entry_points.txt +0 -0
|
@@ -23,7 +23,6 @@ from rasa.dialogue_understanding.generator.constants import (
|
|
|
23
23
|
USER_INPUT_CONFIG_KEY,
|
|
24
24
|
FLOW_RETRIEVAL_KEY,
|
|
25
25
|
DEFAULT_LLM_CONFIG,
|
|
26
|
-
TRAINED_MODEL_NAME_CONFIG_KEY,
|
|
27
26
|
)
|
|
28
27
|
from rasa.dialogue_understanding.generator.flow_retrieval import (
|
|
29
28
|
FlowRetrieval,
|
|
@@ -54,7 +53,6 @@ from rasa.shared.utils.llm import (
|
|
|
54
53
|
sanitize_message_for_prompt,
|
|
55
54
|
resolve_model_client_config,
|
|
56
55
|
)
|
|
57
|
-
from rasa.shared.utils.health_check import perform_inference_time_llm_health_check
|
|
58
56
|
from rasa.utils.beta import ensure_beta_feature_is_enabled, BetaNotEnabledException
|
|
59
57
|
from rasa.utils.log_utils import log_llm
|
|
60
58
|
|
|
@@ -64,6 +62,7 @@ DEFAULT_COMMAND_PROMPT_TEMPLATE = importlib.resources.read_text(
|
|
|
64
62
|
"rasa.dialogue_understanding.generator.single_step",
|
|
65
63
|
"command_prompt_template.jinja2",
|
|
66
64
|
)
|
|
65
|
+
SINGLE_STEP_LLM_COMMAND_GENERATOR_CONFIG_FILE = "config.json"
|
|
67
66
|
|
|
68
67
|
structlogger = structlog.get_logger()
|
|
69
68
|
|
|
@@ -137,6 +136,16 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
137
136
|
**kwargs: Any,
|
|
138
137
|
) -> "SingleStepLLMCommandGenerator":
|
|
139
138
|
"""Loads trained component (see parent class for full docstring)."""
|
|
139
|
+
|
|
140
|
+
# Perform health check of the LLM API endpoint
|
|
141
|
+
llm_config = resolve_model_client_config(config.get(LLM_CONFIG_KEY, {}))
|
|
142
|
+
cls.perform_llm_health_check(
|
|
143
|
+
llm_config,
|
|
144
|
+
DEFAULT_LLM_CONFIG,
|
|
145
|
+
"single_step_llm_command_generator.load",
|
|
146
|
+
SingleStepLLMCommandGenerator.__name__,
|
|
147
|
+
)
|
|
148
|
+
|
|
140
149
|
# load prompt template from the model storage.
|
|
141
150
|
prompt_template = cls.load_prompt_template_from_model_storage(
|
|
142
151
|
model_storage, resource, COMMAND_PROMPT_FILE_NAME
|
|
@@ -150,34 +159,28 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
150
159
|
command_generator.config, model_storage, resource
|
|
151
160
|
)
|
|
152
161
|
|
|
153
|
-
persisted_config = cls.load_config_from_model_storage(model_storage, resource)
|
|
154
|
-
train_model_name = (
|
|
155
|
-
persisted_config.get(TRAINED_MODEL_NAME_CONFIG_KEY, None)
|
|
156
|
-
if persisted_config
|
|
157
|
-
else None
|
|
158
|
-
)
|
|
159
|
-
perform_inference_time_llm_health_check(
|
|
160
|
-
command_generator.config.get(LLM_CONFIG_KEY),
|
|
161
|
-
DEFAULT_LLM_CONFIG,
|
|
162
|
-
train_model_name,
|
|
163
|
-
"single_step_llm_command_generator.load",
|
|
164
|
-
SingleStepLLMCommandGenerator.__name__,
|
|
165
|
-
)
|
|
166
|
-
|
|
167
162
|
return command_generator
|
|
168
163
|
|
|
169
164
|
def persist(self) -> None:
|
|
170
165
|
"""Persist this component to disk for future loading."""
|
|
171
|
-
|
|
172
|
-
|
|
166
|
+
self._persist_prompt_template()
|
|
167
|
+
self._persist_config()
|
|
168
|
+
if self.flow_retrieval is not None:
|
|
169
|
+
self.flow_retrieval.persist()
|
|
173
170
|
|
|
171
|
+
def _persist_prompt_template(self) -> None:
|
|
172
|
+
"""Persist prompt template for future loading."""
|
|
174
173
|
with self._model_storage.write_to(self._resource) as path:
|
|
175
174
|
rasa.shared.utils.io.write_text_file(
|
|
176
175
|
self.prompt_template, path / COMMAND_PROMPT_FILE_NAME
|
|
177
176
|
)
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
177
|
+
|
|
178
|
+
def _persist_config(self) -> None:
|
|
179
|
+
"""Persist config as a source of truth for resolved clients."""
|
|
180
|
+
with self._model_storage.write_to(self._resource) as path:
|
|
181
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(
|
|
182
|
+
path / SINGLE_STEP_LLM_COMMAND_GENERATOR_CONFIG_FILE, self.config
|
|
183
|
+
)
|
|
181
184
|
|
|
182
185
|
async def predict_commands(
|
|
183
186
|
self,
|
|
@@ -8,6 +8,7 @@ from rasa.dialogue_understanding.commands import (
|
|
|
8
8
|
Command,
|
|
9
9
|
CorrectSlotsCommand,
|
|
10
10
|
CorrectedSlot,
|
|
11
|
+
RepeatBotMessagesCommand,
|
|
11
12
|
SetSlotCommand,
|
|
12
13
|
StartFlowCommand,
|
|
13
14
|
FreeFormAnswerCommand,
|
|
@@ -422,6 +423,9 @@ def clean_up_commands(
|
|
|
422
423
|
elif not tracker.has_coexistence_routing_slot and len(clean_commands) > 1:
|
|
423
424
|
clean_commands = filter_cannot_handle_command_for_skipped_slots(clean_commands)
|
|
424
425
|
|
|
426
|
+
clean_commands = ensure_max_number_of_command_type(
|
|
427
|
+
clean_commands, RepeatBotMessagesCommand, 1
|
|
428
|
+
)
|
|
425
429
|
structlogger.debug(
|
|
426
430
|
"command_processor.clean_up_commands.final_commands",
|
|
427
431
|
command=clean_commands,
|
|
@@ -430,6 +434,22 @@ def clean_up_commands(
|
|
|
430
434
|
return clean_commands
|
|
431
435
|
|
|
432
436
|
|
|
437
|
+
def ensure_max_number_of_command_type(
|
|
438
|
+
commands: List[Command], command_type: Type[Command], n: int
|
|
439
|
+
) -> List[Command]:
|
|
440
|
+
"""Ensures that for a given command type only the first n stay in the list."""
|
|
441
|
+
filtered: List[Command] = []
|
|
442
|
+
count = 0
|
|
443
|
+
for c in commands:
|
|
444
|
+
if isinstance(c, command_type):
|
|
445
|
+
if count >= n:
|
|
446
|
+
continue
|
|
447
|
+
else:
|
|
448
|
+
count += 1
|
|
449
|
+
filtered.append(c)
|
|
450
|
+
return filtered
|
|
451
|
+
|
|
452
|
+
|
|
433
453
|
def clean_up_clarify_command(
|
|
434
454
|
commands_so_far: List[Command],
|
|
435
455
|
all_commands: List[Command],
|
|
@@ -452,7 +472,7 @@ def clean_up_clarify_command(
|
|
|
452
472
|
if not (isinstance(c, SetSlotCommand) and c.name == ROUTE_TO_CALM_SLOT)
|
|
453
473
|
]
|
|
454
474
|
|
|
455
|
-
# if
|
|
475
|
+
# if all commands are clarify commands, add the first one only, otherwise add none
|
|
456
476
|
if all(
|
|
457
477
|
isinstance(c, ClarifyCommand) for c in commands_without_route_to_calm_set_slot
|
|
458
478
|
):
|
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,6 +66,7 @@ from rasa.shared.constants import (
|
|
|
67
66
|
MODEL_GROUP_ID_CONFIG_KEY,
|
|
68
67
|
ROUTER_CONFIG_KEY,
|
|
69
68
|
MODELS_CONFIG_KEY,
|
|
69
|
+
MODEL_GROUP_CONFIG_KEY,
|
|
70
70
|
ROUTER_STRATEGY_CONFIG_KEY,
|
|
71
71
|
VALID_ROUTER_STRATEGIES,
|
|
72
72
|
ROUTER_STRATEGIES_REQUIRING_REDIS_CACHE,
|
|
@@ -890,13 +890,13 @@ def _validate_component_model_client_config(
|
|
|
890
890
|
# no llm configuration present
|
|
891
891
|
return
|
|
892
892
|
|
|
893
|
-
if
|
|
893
|
+
if MODEL_GROUP_CONFIG_KEY in component_config[key]:
|
|
894
894
|
model_group_syntax_used.append(True)
|
|
895
|
-
model_group_ids.append(component_config[key][
|
|
895
|
+
model_group_ids.append(component_config[key][MODEL_GROUP_CONFIG_KEY])
|
|
896
896
|
|
|
897
897
|
if len(component_config[key]) > 1:
|
|
898
898
|
print_error_and_exit(
|
|
899
|
-
f"You specified a '{
|
|
899
|
+
f"You specified a '{MODEL_GROUP_CONFIG_KEY}' for the '{key}' "
|
|
900
900
|
f"config key for the component "
|
|
901
901
|
f"'{component_name or component_config['name']}'. "
|
|
902
902
|
"No other parameters are allowed under the "
|
|
@@ -908,10 +908,9 @@ def _validate_component_model_client_config(
|
|
|
908
908
|
# check that api_key is not set in config
|
|
909
909
|
if API_KEY in component_config[key]:
|
|
910
910
|
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."
|
|
911
|
+
f"You specified '{API_KEY}' in the config for "
|
|
912
|
+
f"'{component_name or component_config['name']}', which is not allowed."
|
|
913
|
+
" Set API keys through environment variables."
|
|
915
914
|
)
|
|
916
915
|
|
|
917
916
|
|
|
@@ -936,34 +935,46 @@ def validate_model_client_configuration_setup(config: Dict[str, Any]) -> None:
|
|
|
936
935
|
model_group_syntax_used: List[bool] = []
|
|
937
936
|
model_group_ids: List[str] = []
|
|
938
937
|
|
|
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
|
-
)
|
|
938
|
+
for outer_key in ["pipeline", "policies"]:
|
|
939
|
+
if outer_key not in config or config[outer_key] is None:
|
|
940
|
+
continue
|
|
947
941
|
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
if FLOW_RETRIEVAL_KEY in component:
|
|
951
|
-
if EMBEDDINGS_CONFIG_KEY in component[FLOW_RETRIEVAL_KEY]:
|
|
942
|
+
for component in config[outer_key]:
|
|
943
|
+
for key in [LLM_CONFIG_KEY, EMBEDDINGS_CONFIG_KEY]:
|
|
952
944
|
_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,
|
|
945
|
+
component, key, model_group_syntax_used, model_group_ids
|
|
958
946
|
)
|
|
959
947
|
|
|
948
|
+
# as flow retrieval is not a component itself, we need to
|
|
949
|
+
# check it separately
|
|
950
|
+
if FLOW_RETRIEVAL_KEY in component:
|
|
951
|
+
if EMBEDDINGS_CONFIG_KEY in component[FLOW_RETRIEVAL_KEY]:
|
|
952
|
+
_validate_component_model_client_config(
|
|
953
|
+
component[FLOW_RETRIEVAL_KEY],
|
|
954
|
+
EMBEDDINGS_CONFIG_KEY,
|
|
955
|
+
model_group_syntax_used,
|
|
956
|
+
model_group_ids,
|
|
957
|
+
component["name"] + "." + FLOW_RETRIEVAL_KEY,
|
|
958
|
+
)
|
|
959
|
+
|
|
960
|
+
# also include the ContextualResponseRephraser component
|
|
961
|
+
endpoints = AvailableEndpoints.get_instance()
|
|
962
|
+
if endpoints.nlg is not None:
|
|
963
|
+
_validate_component_model_client_config(
|
|
964
|
+
endpoints.nlg.kwargs,
|
|
965
|
+
LLM_CONFIG_KEY,
|
|
966
|
+
model_group_syntax_used,
|
|
967
|
+
model_group_ids,
|
|
968
|
+
ContextualResponseRephraser.__name__,
|
|
969
|
+
)
|
|
970
|
+
|
|
960
971
|
if not is_uniform_bool_list(model_group_syntax_used):
|
|
961
972
|
print_error_and_exit(
|
|
962
973
|
"Some of your components refer to an LLM using the "
|
|
963
|
-
f"'{
|
|
964
|
-
f"define the LLM under the '{LLM_CONFIG_KEY}' or the "
|
|
974
|
+
f"'{MODEL_GROUP_CONFIG_KEY}' parameter, other components directly"
|
|
975
|
+
f" define the LLM under the '{LLM_CONFIG_KEY}' or the "
|
|
965
976
|
f"'{EMBEDDINGS_CONFIG_KEY}' key. You cannot use"
|
|
966
|
-
"
|
|
977
|
+
" both types of definitions. Please chose one syntax "
|
|
967
978
|
"and update your config."
|
|
968
979
|
)
|
|
969
980
|
|
|
@@ -1106,17 +1117,22 @@ def _validate_api_key_is_an_environment_variable(
|
|
|
1106
1117
|
for model_group in model_groups:
|
|
1107
1118
|
for model_config in model_group[MODELS_CONFIG_KEY]:
|
|
1108
1119
|
for key, value in model_config.items():
|
|
1109
|
-
if
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
+
if key == API_KEY:
|
|
1121
|
+
if isinstance(value, str):
|
|
1122
|
+
if not re.match(r"\${(\w+)}", value):
|
|
1123
|
+
print_error_and_exit(
|
|
1124
|
+
f"You defined the '{API_KEY}' in model group "
|
|
1125
|
+
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' as a "
|
|
1126
|
+
f"string. The '{API_KEY}' must be set as an environment"
|
|
1127
|
+
f" variable. Please update your config."
|
|
1128
|
+
)
|
|
1129
|
+
else:
|
|
1130
|
+
print_error_and_exit(
|
|
1131
|
+
f"You should define the '{API_KEY}' in model group "
|
|
1132
|
+
f"'{model_group[MODEL_GROUP_ID_CONFIG_KEY]}' using the "
|
|
1133
|
+
f"environment variable syntax - ${{ENV_VARIABLE_NAME}}. "
|
|
1134
|
+
f"Please update your config."
|
|
1135
|
+
)
|
|
1120
1136
|
|
|
1121
1137
|
|
|
1122
1138
|
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.*")
|
|
@@ -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]:
|
|
@@ -6,6 +6,7 @@ import litellm
|
|
|
6
6
|
import structlog
|
|
7
7
|
from litellm import aembedding, embedding, validate_environment
|
|
8
8
|
|
|
9
|
+
from rasa.shared.constants import API_BASE_CONFIG_KEY, API_KEY
|
|
9
10
|
from rasa.shared.exceptions import (
|
|
10
11
|
ProviderClientAPIException,
|
|
11
12
|
ProviderClientValidationError,
|
|
@@ -81,11 +82,14 @@ class _BaseLiteLLMEmbeddingClient:
|
|
|
81
82
|
ProviderClientValidationError if validation fails.
|
|
82
83
|
"""
|
|
83
84
|
self._validate_environment_variables()
|
|
84
|
-
self._validate_api_key_not_in_config()
|
|
85
85
|
|
|
86
86
|
def _validate_environment_variables(self) -> None:
|
|
87
87
|
"""Validate that the required environment variables are set."""
|
|
88
|
-
validation_info = validate_environment(
|
|
88
|
+
validation_info = validate_environment(
|
|
89
|
+
self._litellm_model_name,
|
|
90
|
+
api_key=self._litellm_extra_parameters.get(API_KEY),
|
|
91
|
+
api_base=self._litellm_extra_parameters.get(API_BASE_CONFIG_KEY),
|
|
92
|
+
)
|
|
89
93
|
if missing_environment_variables := validation_info.get(
|
|
90
94
|
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY
|
|
91
95
|
):
|
|
@@ -100,18 +104,6 @@ class _BaseLiteLLMEmbeddingClient:
|
|
|
100
104
|
)
|
|
101
105
|
raise ProviderClientValidationError(event_info)
|
|
102
106
|
|
|
103
|
-
def _validate_api_key_not_in_config(self) -> None:
|
|
104
|
-
if "api_key" in self._litellm_extra_parameters:
|
|
105
|
-
event_info = (
|
|
106
|
-
"API Key is set through `api_key` extra parameter."
|
|
107
|
-
"Set API keys through environment variables."
|
|
108
|
-
)
|
|
109
|
-
structlogger.error(
|
|
110
|
-
"base_litellm_client.validate_api_key_not_in_config",
|
|
111
|
-
event_info=event_info,
|
|
112
|
-
)
|
|
113
|
-
raise ProviderClientValidationError(event_info)
|
|
114
|
-
|
|
115
107
|
def validate_documents(self, documents: List[str]) -> None:
|
|
116
108
|
"""Validates a list of documents to ensure they are suitable for embedding.
|
|
117
109
|
|
|
@@ -9,6 +9,7 @@ from litellm import (
|
|
|
9
9
|
validate_environment,
|
|
10
10
|
)
|
|
11
11
|
|
|
12
|
+
from rasa.shared.constants import API_BASE_CONFIG_KEY, API_KEY
|
|
12
13
|
from rasa.shared.exceptions import (
|
|
13
14
|
ProviderClientAPIException,
|
|
14
15
|
ProviderClientValidationError,
|
|
@@ -101,7 +102,11 @@ class _BaseLiteLLMClient:
|
|
|
101
102
|
|
|
102
103
|
def _validate_environment_variables(self) -> None:
|
|
103
104
|
"""Validate that the required environment variables are set."""
|
|
104
|
-
validation_info = validate_environment(
|
|
105
|
+
validation_info = validate_environment(
|
|
106
|
+
self._litellm_model_name,
|
|
107
|
+
api_key=self._litellm_extra_parameters.get(API_KEY),
|
|
108
|
+
api_base=self._litellm_extra_parameters.get(API_BASE_CONFIG_KEY),
|
|
109
|
+
)
|
|
105
110
|
if missing_environment_variables := validation_info.get(
|
|
106
111
|
_VALIDATE_ENVIRONMENT_MISSING_KEYS_KEY
|
|
107
112
|
):
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from typing import Optional, Dict, Any
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class EmbeddingsHealthCheckMixin:
|
|
5
|
+
"""Mixin class that provides methods for performing embeddings health checks during
|
|
6
|
+
training and inference within components.
|
|
7
|
+
|
|
8
|
+
This mixin offers static methods that wrap the following health check functions:
|
|
9
|
+
- `perform_embeddings_health_check`
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def perform_embeddings_health_check(
|
|
14
|
+
custom_embeddings_config: Optional[Dict[str, Any]],
|
|
15
|
+
default_embeddings_config: Dict[str, Any],
|
|
16
|
+
log_source_method: str,
|
|
17
|
+
log_source_component: str,
|
|
18
|
+
) -> None:
|
|
19
|
+
"""Wraps the `perform_embeddings_health_check` function to enable
|
|
20
|
+
tracing and instrumentation.
|
|
21
|
+
"""
|
|
22
|
+
from rasa.shared.utils.health_check.health_check import (
|
|
23
|
+
perform_embeddings_health_check,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
perform_embeddings_health_check(
|
|
27
|
+
custom_embeddings_config,
|
|
28
|
+
default_embeddings_config,
|
|
29
|
+
log_source_method,
|
|
30
|
+
log_source_component,
|
|
31
|
+
)
|