rasa-pro 3.11.0rc3__py3-none-any.whl → 3.11.1__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 +9 -3
- rasa/cli/studio/upload.py +0 -15
- rasa/cli/utils.py +1 -1
- rasa/core/channels/development_inspector.py +4 -1
- rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
- rasa/core/channels/voice_stream/asr/azure.py +11 -2
- rasa/core/channels/voice_stream/asr/deepgram.py +4 -3
- rasa/core/channels/voice_stream/tts/azure.py +3 -1
- rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
- rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
- rasa/core/information_retrieval/qdrant.py +1 -0
- rasa/core/persistor.py +93 -49
- rasa/core/policies/flows/flow_executor.py +18 -8
- rasa/core/processor.py +7 -5
- rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
- rasa/e2e_test/assertions.py +133 -16
- rasa/e2e_test/assertions_schema.yml +23 -0
- rasa/e2e_test/e2e_test_runner.py +2 -2
- rasa/engine/loader.py +12 -0
- rasa/engine/validation.py +291 -79
- rasa/model_manager/config.py +8 -0
- rasa/model_manager/model_api.py +166 -61
- rasa/model_manager/runner_service.py +31 -26
- rasa/model_manager/trainer_service.py +14 -23
- rasa/model_manager/warm_rasa_process.py +187 -0
- rasa/model_service.py +3 -5
- rasa/model_training.py +3 -1
- rasa/shared/constants.py +22 -0
- rasa/shared/core/domain.py +8 -5
- rasa/shared/core/flows/yaml_flows_io.py +13 -4
- rasa/shared/importers/importer.py +19 -2
- rasa/shared/importers/rasa.py +5 -1
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
- rasa/shared/providers/_utils.py +79 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
- rasa/shared/utils/common.py +29 -2
- rasa/shared/utils/health_check/health_check.py +26 -24
- rasa/shared/utils/yaml.py +116 -31
- rasa/studio/data_handler.py +3 -1
- rasa/studio/upload.py +119 -57
- rasa/validator.py +40 -4
- rasa/version.py +1 -1
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/METADATA +2 -2
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/RECORD +48 -46
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/WHEEL +0 -0
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/entry_points.txt +0 -0
rasa/e2e_test/assertions.py
CHANGED
|
@@ -71,6 +71,7 @@ class AssertionType(Enum):
|
|
|
71
71
|
SLOT_WAS_SET = "slot_was_set"
|
|
72
72
|
SLOT_WAS_NOT_SET = "slot_was_not_set"
|
|
73
73
|
BOT_UTTERED = "bot_uttered"
|
|
74
|
+
BOT_DID_NOT_UTTER = "bot_did_not_utter"
|
|
74
75
|
GENERATIVE_RESPONSE_IS_RELEVANT = "generative_response_is_relevant"
|
|
75
76
|
GENERATIVE_RESPONSE_IS_GROUNDED = "generative_response_is_grounded"
|
|
76
77
|
|
|
@@ -722,6 +723,7 @@ class BotUtteredAssertion(Assertion):
|
|
|
722
723
|
) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
|
|
723
724
|
"""Run the bot_uttered assertion on the given events for that user turn."""
|
|
724
725
|
matching_event = None
|
|
726
|
+
error_messages = []
|
|
725
727
|
|
|
726
728
|
if self.utter_name is not None:
|
|
727
729
|
try:
|
|
@@ -732,11 +734,8 @@ class BotUtteredAssertion(Assertion):
|
|
|
732
734
|
and event.metadata.get("utter_action") == self.utter_name
|
|
733
735
|
)
|
|
734
736
|
except StopIteration:
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
return self._generate_assertion_failure(
|
|
739
|
-
error_message, prior_events, turn_events, self.line
|
|
737
|
+
error_messages.append(
|
|
738
|
+
f"Bot did not utter '{self.utter_name}' response."
|
|
740
739
|
)
|
|
741
740
|
|
|
742
741
|
if self.text_matches is not None:
|
|
@@ -748,16 +747,11 @@ class BotUtteredAssertion(Assertion):
|
|
|
748
747
|
if isinstance(event, BotUttered) and pattern.search(event.text)
|
|
749
748
|
)
|
|
750
749
|
except StopIteration:
|
|
751
|
-
|
|
750
|
+
error_messages.append(
|
|
752
751
|
f"Bot did not utter any response which "
|
|
753
752
|
f"matches the provided text pattern "
|
|
754
753
|
f"'{self.text_matches}'."
|
|
755
754
|
)
|
|
756
|
-
error_message += assertion_order_error_message
|
|
757
|
-
|
|
758
|
-
return self._generate_assertion_failure(
|
|
759
|
-
error_message, prior_events, turn_events, self.line
|
|
760
|
-
)
|
|
761
755
|
|
|
762
756
|
if self.buttons:
|
|
763
757
|
try:
|
|
@@ -767,13 +761,16 @@ class BotUtteredAssertion(Assertion):
|
|
|
767
761
|
if isinstance(event, BotUttered) and self._buttons_match(event)
|
|
768
762
|
)
|
|
769
763
|
except StopIteration:
|
|
770
|
-
|
|
764
|
+
error_messages.append(
|
|
771
765
|
"Bot did not utter any response with the expected buttons."
|
|
772
766
|
)
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
767
|
+
|
|
768
|
+
if error_messages:
|
|
769
|
+
error_message = " ".join(error_messages)
|
|
770
|
+
error_message += assertion_order_error_message
|
|
771
|
+
return self._generate_assertion_failure(
|
|
772
|
+
error_message, prior_events, turn_events, self.line
|
|
773
|
+
)
|
|
777
774
|
|
|
778
775
|
return None, matching_event
|
|
779
776
|
|
|
@@ -803,6 +800,126 @@ class BotUtteredAssertion(Assertion):
|
|
|
803
800
|
return hash(json.dumps(self.as_dict()))
|
|
804
801
|
|
|
805
802
|
|
|
803
|
+
@dataclass
|
|
804
|
+
class BotDidNotUtterAssertion(Assertion):
|
|
805
|
+
"""Class for the 'bot_did_not_utter' assertion."""
|
|
806
|
+
|
|
807
|
+
utter_name: Optional[str] = None
|
|
808
|
+
text_matches: Optional[str] = None
|
|
809
|
+
buttons: Optional[List[AssertedButton]] = None
|
|
810
|
+
line: Optional[int] = None
|
|
811
|
+
|
|
812
|
+
@classmethod
|
|
813
|
+
def type(cls) -> str:
|
|
814
|
+
return AssertionType.BOT_DID_NOT_UTTER.value
|
|
815
|
+
|
|
816
|
+
@staticmethod
|
|
817
|
+
def from_dict(assertion_dict: Dict[Text, Any]) -> BotDidNotUtterAssertion:
|
|
818
|
+
"""Creates a BotDidNotUtterAssertion from a dictionary."""
|
|
819
|
+
assertion_dict = assertion_dict.get(AssertionType.BOT_DID_NOT_UTTER.value, {})
|
|
820
|
+
utter_name = assertion_dict.get("utter_name")
|
|
821
|
+
text_matches = assertion_dict.get("text_matches")
|
|
822
|
+
buttons = [
|
|
823
|
+
AssertedButton.from_dict(button)
|
|
824
|
+
for button in assertion_dict.get("buttons", [])
|
|
825
|
+
]
|
|
826
|
+
|
|
827
|
+
if not utter_name and not text_matches and not buttons:
|
|
828
|
+
raise RasaException(
|
|
829
|
+
"A 'bot_did_not_utter' assertion is empty. "
|
|
830
|
+
"It should contain at least one of the allowed properties: "
|
|
831
|
+
"'utter_name', 'text_matches', or 'buttons'."
|
|
832
|
+
)
|
|
833
|
+
|
|
834
|
+
return BotDidNotUtterAssertion(
|
|
835
|
+
utter_name=utter_name,
|
|
836
|
+
text_matches=text_matches,
|
|
837
|
+
buttons=buttons,
|
|
838
|
+
line=assertion_dict.lc.line + 1 if hasattr(assertion_dict, "lc") else None,
|
|
839
|
+
)
|
|
840
|
+
|
|
841
|
+
def run(
|
|
842
|
+
self,
|
|
843
|
+
turn_events: List[Event],
|
|
844
|
+
prior_events: List[Event],
|
|
845
|
+
assertion_order_error_message: str = "",
|
|
846
|
+
**kwargs: Any,
|
|
847
|
+
) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
|
|
848
|
+
"""Checks that the bot did not utter the specified messages or buttons."""
|
|
849
|
+
for event in turn_events:
|
|
850
|
+
if isinstance(event, BotUttered):
|
|
851
|
+
error_messages = []
|
|
852
|
+
if self._utter_name_matches(event):
|
|
853
|
+
error_messages.append(
|
|
854
|
+
f"Bot uttered a forbidden utterance '{self.utter_name}'."
|
|
855
|
+
)
|
|
856
|
+
if self._text_matches(event):
|
|
857
|
+
error_messages.append(
|
|
858
|
+
f"Bot uttered a forbidden message matching "
|
|
859
|
+
f"the pattern '{self.text_matches}'."
|
|
860
|
+
)
|
|
861
|
+
if self._buttons_match(event):
|
|
862
|
+
error_messages.append(
|
|
863
|
+
"Bot uttered a forbidden response with specified buttons."
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
if error_messages:
|
|
867
|
+
error_message = " ".join(error_messages)
|
|
868
|
+
error_message += assertion_order_error_message
|
|
869
|
+
return self._generate_assertion_failure(
|
|
870
|
+
error_message, prior_events, turn_events, self.line
|
|
871
|
+
)
|
|
872
|
+
return None, None
|
|
873
|
+
|
|
874
|
+
def _utter_name_matches(self, event: BotUttered) -> bool:
|
|
875
|
+
if self.utter_name is not None:
|
|
876
|
+
if event.metadata.get("utter_action") == self.utter_name:
|
|
877
|
+
return True
|
|
878
|
+
return False
|
|
879
|
+
|
|
880
|
+
def _text_matches(self, event: BotUttered) -> bool:
|
|
881
|
+
if self.text_matches is not None:
|
|
882
|
+
pattern = re.compile(self.text_matches)
|
|
883
|
+
if pattern.search(event.text):
|
|
884
|
+
return True
|
|
885
|
+
return False
|
|
886
|
+
|
|
887
|
+
def _buttons_match(self, event: BotUttered) -> bool:
|
|
888
|
+
"""Check if the bot response contains any of the forbidden buttons."""
|
|
889
|
+
if self.buttons is None:
|
|
890
|
+
return False
|
|
891
|
+
|
|
892
|
+
actual_buttons = event.data.get("buttons", [])
|
|
893
|
+
if not actual_buttons:
|
|
894
|
+
return False
|
|
895
|
+
|
|
896
|
+
for actual_button in actual_buttons:
|
|
897
|
+
if any(
|
|
898
|
+
self._is_forbidden_button(actual_button, forbidden_button)
|
|
899
|
+
for forbidden_button in self.buttons
|
|
900
|
+
):
|
|
901
|
+
return True
|
|
902
|
+
return False
|
|
903
|
+
|
|
904
|
+
@staticmethod
|
|
905
|
+
def _is_forbidden_button(
|
|
906
|
+
actual_button: Dict[str, Any], forbidden_button: AssertedButton
|
|
907
|
+
) -> bool:
|
|
908
|
+
"""Check if the button matches any of the forbidden buttons."""
|
|
909
|
+
actual_title = actual_button.get("title")
|
|
910
|
+
actual_payload = actual_button.get("payload")
|
|
911
|
+
|
|
912
|
+
title_matches = forbidden_button.title == actual_title
|
|
913
|
+
payload_matches = forbidden_button.payload == actual_payload
|
|
914
|
+
if title_matches and payload_matches:
|
|
915
|
+
return True
|
|
916
|
+
return False
|
|
917
|
+
|
|
918
|
+
def __hash__(self) -> int:
|
|
919
|
+
"""Hash method to ensure the assertion is hashable."""
|
|
920
|
+
return hash(json.dumps(self.as_dict()))
|
|
921
|
+
|
|
922
|
+
|
|
806
923
|
@dataclass
|
|
807
924
|
class GenerativeResponseMixin(Assertion):
|
|
808
925
|
"""Mixin class for storing generative response assertions."""
|
|
@@ -83,6 +83,29 @@ schema;assertions:
|
|
|
83
83
|
text_matches:
|
|
84
84
|
type: str
|
|
85
85
|
nullable: false
|
|
86
|
+
bot_did_not_utter:
|
|
87
|
+
type: map
|
|
88
|
+
nullable: false
|
|
89
|
+
mapping:
|
|
90
|
+
utter_name:
|
|
91
|
+
type: str
|
|
92
|
+
nullable: false
|
|
93
|
+
buttons:
|
|
94
|
+
type: seq
|
|
95
|
+
nullable: false
|
|
96
|
+
matching: "all"
|
|
97
|
+
sequence:
|
|
98
|
+
- type: map
|
|
99
|
+
mapping:
|
|
100
|
+
title:
|
|
101
|
+
type: str
|
|
102
|
+
nullable: false
|
|
103
|
+
payload:
|
|
104
|
+
type: str
|
|
105
|
+
nullable: false
|
|
106
|
+
text_matches:
|
|
107
|
+
type: str
|
|
108
|
+
nullable: false
|
|
86
109
|
generative_response_is_relevant:
|
|
87
110
|
type: map
|
|
88
111
|
mapping:
|
rasa/e2e_test/e2e_test_runner.py
CHANGED
|
@@ -136,7 +136,7 @@ class E2ETestRunner:
|
|
|
136
136
|
return turns
|
|
137
137
|
|
|
138
138
|
tracker = await self.agent.processor.fetch_tracker_with_initial_session(
|
|
139
|
-
sender_id
|
|
139
|
+
sender_id, output_channel=collector
|
|
140
140
|
)
|
|
141
141
|
# turn -1 i used to contain events that happen during
|
|
142
142
|
# the start of the session and before the first user message
|
|
@@ -826,7 +826,7 @@ class E2ETestRunner:
|
|
|
826
826
|
return
|
|
827
827
|
|
|
828
828
|
tracker = await self.agent.processor.fetch_tracker_with_initial_session(
|
|
829
|
-
sender_id
|
|
829
|
+
sender_id, output_channel=CollectingOutputChannel()
|
|
830
830
|
)
|
|
831
831
|
|
|
832
832
|
for fixture in fixtures:
|
rasa/engine/loader.py
CHANGED
|
@@ -4,6 +4,10 @@ from typing import Tuple, Type
|
|
|
4
4
|
from rasa.engine.graph import ExecutionContext
|
|
5
5
|
from rasa.engine.runner.interface import GraphRunner
|
|
6
6
|
from rasa.engine.storage.storage import ModelMetadata, ModelStorage
|
|
7
|
+
from rasa.engine.validation import (
|
|
8
|
+
validate_model_client_configuration_setup_during_inference_time,
|
|
9
|
+
validate_model_group_configuration_setup,
|
|
10
|
+
)
|
|
7
11
|
|
|
8
12
|
|
|
9
13
|
def load_predict_graph_runner(
|
|
@@ -26,6 +30,14 @@ def load_predict_graph_runner(
|
|
|
26
30
|
model_storage, model_metadata = model_storage_class.from_model_archive(
|
|
27
31
|
storage_path=storage_path, model_archive_path=model_archive_path
|
|
28
32
|
)
|
|
33
|
+
|
|
34
|
+
# Components using LLMs or embeddings can reference model groups defined in
|
|
35
|
+
# the endpoints.yml file for their client configurations. To ensure they will work
|
|
36
|
+
# properly validate model group references before loading
|
|
37
|
+
# the components.
|
|
38
|
+
validate_model_group_configuration_setup()
|
|
39
|
+
validate_model_client_configuration_setup_during_inference_time(model_metadata)
|
|
40
|
+
|
|
29
41
|
runner = graph_runner_class.create(
|
|
30
42
|
graph_schema=model_metadata.predict_schema,
|
|
31
43
|
model_storage=model_storage,
|