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.

Files changed (48) hide show
  1. rasa/__main__.py +9 -3
  2. rasa/cli/studio/upload.py +0 -15
  3. rasa/cli/utils.py +1 -1
  4. rasa/core/channels/development_inspector.py +4 -1
  5. rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
  6. rasa/core/channels/voice_stream/asr/azure.py +11 -2
  7. rasa/core/channels/voice_stream/asr/deepgram.py +4 -3
  8. rasa/core/channels/voice_stream/tts/azure.py +3 -1
  9. rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
  10. rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
  11. rasa/core/information_retrieval/qdrant.py +1 -0
  12. rasa/core/persistor.py +93 -49
  13. rasa/core/policies/flows/flow_executor.py +18 -8
  14. rasa/core/processor.py +7 -5
  15. rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
  16. rasa/e2e_test/assertions.py +133 -16
  17. rasa/e2e_test/assertions_schema.yml +23 -0
  18. rasa/e2e_test/e2e_test_runner.py +2 -2
  19. rasa/engine/loader.py +12 -0
  20. rasa/engine/validation.py +291 -79
  21. rasa/model_manager/config.py +8 -0
  22. rasa/model_manager/model_api.py +166 -61
  23. rasa/model_manager/runner_service.py +31 -26
  24. rasa/model_manager/trainer_service.py +14 -23
  25. rasa/model_manager/warm_rasa_process.py +187 -0
  26. rasa/model_service.py +3 -5
  27. rasa/model_training.py +3 -1
  28. rasa/shared/constants.py +22 -0
  29. rasa/shared/core/domain.py +8 -5
  30. rasa/shared/core/flows/yaml_flows_io.py +13 -4
  31. rasa/shared/importers/importer.py +19 -2
  32. rasa/shared/importers/rasa.py +5 -1
  33. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  34. rasa/shared/providers/_utils.py +79 -0
  35. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  36. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  37. rasa/shared/utils/common.py +29 -2
  38. rasa/shared/utils/health_check/health_check.py +26 -24
  39. rasa/shared/utils/yaml.py +116 -31
  40. rasa/studio/data_handler.py +3 -1
  41. rasa/studio/upload.py +119 -57
  42. rasa/validator.py +40 -4
  43. rasa/version.py +1 -1
  44. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/METADATA +2 -2
  45. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/RECORD +48 -46
  46. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/NOTICE +0 -0
  47. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/WHEEL +0 -0
  48. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.1.dist-info}/entry_points.txt +0 -0
@@ -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
- error_message = f"Bot did not utter '{self.utter_name}' response."
736
- error_message += assertion_order_error_message
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
- error_message = (
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
- error_message = (
764
+ error_messages.append(
771
765
  "Bot did not utter any response with the expected buttons."
772
766
  )
773
- error_message += assertion_order_error_message
774
- return self._generate_assertion_failure(
775
- error_message, prior_events, turn_events, self.line
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:
@@ -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,