rasa-pro 3.11.0rc3__py3-none-any.whl → 3.11.2__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 (92) hide show
  1. rasa/__main__.py +9 -3
  2. rasa/cli/llm_fine_tuning.py +19 -11
  3. rasa/cli/project_templates/tutorial/config.yml +1 -3
  4. rasa/cli/project_templates/tutorial/endpoints.yml +8 -3
  5. rasa/cli/studio/upload.py +0 -15
  6. rasa/cli/train.py +9 -0
  7. rasa/cli/utils.py +1 -1
  8. rasa/core/channels/development_inspector.py +4 -1
  9. rasa/core/channels/inspector/dist/assets/{arc-bc141fb2.js → arc-861ddd57.js} +1 -1
  10. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-be2db283.js → c4Diagram-d0fbc5ce-921f02db.js} +1 -1
  11. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-55366915.js → classDiagram-936ed81e-b436c4f8.js} +1 -1
  12. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-bb529518.js → classDiagram-v2-c3cb15f1-511a23cb.js} +1 -1
  13. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-b0ec81d6.js → createText-62fc7601-ef476ecd.js} +1 -1
  14. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-6166330c.js → edges-f2ad444c-f1878e0a.js} +1 -1
  15. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-5ccc6a8e.js → erDiagram-9d236eb7-fac75185.js} +1 -1
  16. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-fca3bfe4.js → flowDb-1972c806-201c5bbc.js} +1 -1
  17. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4739080f.js → flowDiagram-7ea5b25a-f904ae41.js} +1 -1
  18. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-b080d6f2.js +1 -0
  19. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-7c1b0e0f.js → flowchart-elk-definition-abe16c3d-1813da66.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-772fd050.js → ganttDiagram-9b5ea136-872af172.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-8eae1dc9.js → gitGraphDiagram-99d0ae7c-34a0af5a.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-f55afcdf.js → index-2c4b9a3b-42ba3e3d.js} +1 -1
  23. rasa/core/channels/inspector/dist/assets/{index-e7cef9de.js → index-37817b51.js} +68 -68
  24. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-124d4a14.js → infoDiagram-736b4530-6b731386.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-7c4fae44.js → journeyDiagram-df861f2b-e8579ac6.js} +1 -1
  26. rasa/core/channels/inspector/dist/assets/{layout-b9885fb6.js → layout-89e6403a.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{line-7c59abb6.js → line-dc73d3fc.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{linear-4776f780.js → linear-f5b1d2bc.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2332c46c.js → mindmap-definition-beec6740-82cb74fa.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-8fb39303.js → pieDiagram-dbbf0591-bdf5f29b.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3c7180a2.js → quadrantDiagram-4d7f4fd6-c7a0cbe4.js} +1 -1
  32. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-e910bcb8.js → requirementDiagram-6fc4c22a-7ec5410f.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-ead16c89.js → sankeyDiagram-8f13d901-caee5554.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-29a02a19.js → sequenceDiagram-b655622a-2935f8db.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-042b3137.js → stateDiagram-59f0c015-8f5d9693.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-2178c0f3.js → stateDiagram-v2-2b26beab-d565d1de.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-23ffa4fc.js → styles-080da4f6-75ad421d.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-94f59763.js → styles-3dcbcfbf-7e764226.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-78a6bebc.js → styles-9c745c82-7a4e0e61.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-eae2a6f6.js → svgDrawCommon-4835440b-4019d1bf.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-5c968d92.js → timeline-definition-5b62e21b-01ea12df.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-fd3db0d5.js → xychartDiagram-2b33534f-89407137.js} +1 -1
  43. rasa/core/channels/inspector/dist/index.html +1 -1
  44. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +1 -1
  45. rasa/core/channels/inspector/src/helpers/audiostream.ts +28 -2
  46. rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
  47. rasa/core/channels/voice_stream/asr/azure.py +13 -3
  48. rasa/core/channels/voice_stream/asr/deepgram.py +4 -3
  49. rasa/core/channels/voice_stream/tts/azure.py +3 -1
  50. rasa/core/channels/voice_stream/tts/cartesia.py +3 -3
  51. rasa/core/channels/voice_stream/tts/tts_engine.py +10 -1
  52. rasa/core/information_retrieval/qdrant.py +1 -0
  53. rasa/core/persistor.py +93 -49
  54. rasa/core/policies/flows/flow_executor.py +18 -8
  55. rasa/core/processor.py +7 -5
  56. rasa/core/utils.py +3 -1
  57. rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
  58. rasa/e2e_test/assertions.py +183 -19
  59. rasa/e2e_test/assertions_schema.yml +23 -0
  60. rasa/e2e_test/e2e_test_runner.py +4 -3
  61. rasa/engine/graph.py +9 -3
  62. rasa/engine/loader.py +12 -0
  63. rasa/engine/validation.py +345 -85
  64. rasa/model_manager/config.py +8 -0
  65. rasa/model_manager/model_api.py +166 -61
  66. rasa/model_manager/runner_service.py +31 -26
  67. rasa/model_manager/trainer_service.py +14 -23
  68. rasa/model_manager/warm_rasa_process.py +187 -0
  69. rasa/model_service.py +3 -5
  70. rasa/model_training.py +3 -1
  71. rasa/shared/constants.py +27 -0
  72. rasa/shared/core/domain.py +8 -5
  73. rasa/shared/core/flows/yaml_flows_io.py +13 -4
  74. rasa/shared/importers/importer.py +19 -2
  75. rasa/shared/importers/rasa.py +5 -1
  76. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  77. rasa/shared/providers/_utils.py +79 -0
  78. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  79. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  80. rasa/shared/utils/common.py +29 -2
  81. rasa/shared/utils/health_check/health_check.py +26 -24
  82. rasa/shared/utils/yaml.py +116 -31
  83. rasa/studio/data_handler.py +3 -1
  84. rasa/studio/upload.py +119 -57
  85. rasa/validator.py +40 -4
  86. rasa/version.py +1 -1
  87. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/METADATA +2 -2
  88. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/RECORD +91 -89
  89. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +0 -1
  90. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/NOTICE +0 -0
  91. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/WHEEL +0 -0
  92. {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/entry_points.txt +0 -0
rasa/core/utils.py CHANGED
@@ -233,7 +233,9 @@ class AvailableEndpoints:
233
233
  self.model_groups = model_groups
234
234
 
235
235
  @classmethod
236
- def get_instance(cls, endpoint_file: Optional[Text] = None) -> "AvailableEndpoints":
236
+ def get_instance(
237
+ cls, endpoint_file: Optional[Text] = DEFAULT_ENDPOINTS_PATH
238
+ ) -> "AvailableEndpoints":
237
239
  """Get the singleton instance of AvailableEndpoints."""
238
240
  # Ensure that the instance is initialized only once.
239
241
  if cls._instance is None:
@@ -35,6 +35,7 @@ class AggregateTestStatsCalculator:
35
35
  self.test_cases = test_cases
36
36
 
37
37
  self.failed_assertion_set: Set["Assertion"] = set()
38
+ self.failed_test_cases_without_assertion_failure: Set[str] = set()
38
39
  self.passed_count_mapping = {
39
40
  subclass_type: 0
40
41
  for subclass_type in _get_all_assertion_subclasses().keys()
@@ -89,8 +90,14 @@ class AggregateTestStatsCalculator:
89
90
  passed_test_case_names = [
90
91
  passed.test_case.name for passed in self.passed_results
91
92
  ]
93
+ # We filter out test cases that failed without an assertion failure
94
+ filtered_test_cases = [
95
+ test_case
96
+ for test_case in self.test_cases
97
+ if test_case.name not in self.failed_test_cases_without_assertion_failure
98
+ ]
92
99
 
93
- for test_case in self.test_cases:
100
+ for test_case in filtered_test_cases:
94
101
  if test_case.name in passed_test_case_names:
95
102
  for step in test_case.steps:
96
103
  if step.assertions is None:
@@ -118,6 +125,9 @@ class AggregateTestStatsCalculator:
118
125
  "no_assertion_failure_in_failed_result",
119
126
  test_case=failed.test_case.name,
120
127
  )
128
+ self.failed_test_cases_without_assertion_failure.add(
129
+ failed.test_case.name
130
+ )
121
131
  continue
122
132
 
123
133
  self.failed_assertion_set.add(failed.assertion_failure.assertion)
@@ -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
 
@@ -452,6 +453,11 @@ class ActionExecutedAssertion(Assertion):
452
453
  **kwargs: Any,
453
454
  ) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
454
455
  """Run the action executed assertion on the given events for that user turn."""
456
+ step_index = kwargs.get("step_index")
457
+ original_turn_events, turn_events = _get_turn_events_based_on_step_index(
458
+ step_index, turn_events, prior_events
459
+ )
460
+
455
461
  try:
456
462
  matching_event = next(
457
463
  event
@@ -464,7 +470,7 @@ class ActionExecutedAssertion(Assertion):
464
470
  error_message += assertion_order_error_message
465
471
 
466
472
  return self._generate_assertion_failure(
467
- error_message, prior_events, turn_events, self.line
473
+ error_message, prior_events, original_turn_events, self.line
468
474
  )
469
475
 
470
476
  return None, matching_event
@@ -519,6 +525,11 @@ class SlotWasSetAssertion(Assertion):
519
525
  """Run the slot_was_set assertion on the given events for that user turn."""
520
526
  matching_event = None
521
527
 
528
+ step_index = kwargs.get("step_index")
529
+ original_turn_events, turn_events = _get_turn_events_based_on_step_index(
530
+ step_index, turn_events, prior_events
531
+ )
532
+
522
533
  for slot in self.slots:
523
534
  matching_events = [
524
535
  event
@@ -557,7 +568,7 @@ class SlotWasSetAssertion(Assertion):
557
568
  error_message += assertion_order_error_message
558
569
 
559
570
  return self._generate_assertion_failure(
560
- error_message, prior_events, turn_events, slot.line
571
+ error_message, prior_events, original_turn_events, slot.line
561
572
  )
562
573
 
563
574
  return None, matching_event
@@ -595,6 +606,11 @@ class SlotWasNotSetAssertion(Assertion):
595
606
  """Run the slot_was_not_set assertion on the given events for that user turn."""
596
607
  matching_event = None
597
608
 
609
+ step_index = kwargs.get("step_index")
610
+ original_turn_events, turn_events = _get_turn_events_based_on_step_index(
611
+ step_index, turn_events, prior_events
612
+ )
613
+
598
614
  for slot in self.slots:
599
615
  matching_events = [
600
616
  event
@@ -630,7 +646,7 @@ class SlotWasNotSetAssertion(Assertion):
630
646
  error_message += assertion_order_error_message
631
647
 
632
648
  return self._generate_assertion_failure(
633
- error_message, prior_events, turn_events, slot.line
649
+ error_message, prior_events, original_turn_events, slot.line
634
650
  )
635
651
 
636
652
  return None, matching_event
@@ -722,6 +738,12 @@ class BotUtteredAssertion(Assertion):
722
738
  ) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
723
739
  """Run the bot_uttered assertion on the given events for that user turn."""
724
740
  matching_event = None
741
+ error_messages = []
742
+
743
+ step_index = kwargs.get("step_index")
744
+ original_turn_events, turn_events = _get_turn_events_based_on_step_index(
745
+ step_index, turn_events, prior_events
746
+ )
725
747
 
726
748
  if self.utter_name is not None:
727
749
  try:
@@ -732,11 +754,8 @@ class BotUtteredAssertion(Assertion):
732
754
  and event.metadata.get("utter_action") == self.utter_name
733
755
  )
734
756
  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
757
+ error_messages.append(
758
+ f"Bot did not utter '{self.utter_name}' response."
740
759
  )
741
760
 
742
761
  if self.text_matches is not None:
@@ -748,16 +767,11 @@ class BotUtteredAssertion(Assertion):
748
767
  if isinstance(event, BotUttered) and pattern.search(event.text)
749
768
  )
750
769
  except StopIteration:
751
- error_message = (
770
+ error_messages.append(
752
771
  f"Bot did not utter any response which "
753
772
  f"matches the provided text pattern "
754
773
  f"'{self.text_matches}'."
755
774
  )
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
775
 
762
776
  if self.buttons:
763
777
  try:
@@ -767,13 +781,16 @@ class BotUtteredAssertion(Assertion):
767
781
  if isinstance(event, BotUttered) and self._buttons_match(event)
768
782
  )
769
783
  except StopIteration:
770
- error_message = (
784
+ error_messages.append(
771
785
  "Bot did not utter any response with the expected buttons."
772
786
  )
773
- error_message += assertion_order_error_message
774
- return self._generate_assertion_failure(
775
- error_message, prior_events, turn_events, self.line
776
- )
787
+
788
+ if error_messages:
789
+ error_message = " ".join(error_messages)
790
+ error_message += assertion_order_error_message
791
+ return self._generate_assertion_failure(
792
+ error_message, prior_events, original_turn_events, self.line
793
+ )
777
794
 
778
795
  return None, matching_event
779
796
 
@@ -803,6 +820,131 @@ class BotUtteredAssertion(Assertion):
803
820
  return hash(json.dumps(self.as_dict()))
804
821
 
805
822
 
823
+ @dataclass
824
+ class BotDidNotUtterAssertion(Assertion):
825
+ """Class for the 'bot_did_not_utter' assertion."""
826
+
827
+ utter_name: Optional[str] = None
828
+ text_matches: Optional[str] = None
829
+ buttons: Optional[List[AssertedButton]] = None
830
+ line: Optional[int] = None
831
+
832
+ @classmethod
833
+ def type(cls) -> str:
834
+ return AssertionType.BOT_DID_NOT_UTTER.value
835
+
836
+ @staticmethod
837
+ def from_dict(assertion_dict: Dict[Text, Any]) -> BotDidNotUtterAssertion:
838
+ """Creates a BotDidNotUtterAssertion from a dictionary."""
839
+ assertion_dict = assertion_dict.get(AssertionType.BOT_DID_NOT_UTTER.value, {})
840
+ utter_name = assertion_dict.get("utter_name")
841
+ text_matches = assertion_dict.get("text_matches")
842
+ buttons = [
843
+ AssertedButton.from_dict(button)
844
+ for button in assertion_dict.get("buttons", [])
845
+ ]
846
+
847
+ if not utter_name and not text_matches and not buttons:
848
+ raise RasaException(
849
+ "A 'bot_did_not_utter' assertion is empty. "
850
+ "It should contain at least one of the allowed properties: "
851
+ "'utter_name', 'text_matches', or 'buttons'."
852
+ )
853
+
854
+ return BotDidNotUtterAssertion(
855
+ utter_name=utter_name,
856
+ text_matches=text_matches,
857
+ buttons=buttons,
858
+ line=assertion_dict.lc.line + 1 if hasattr(assertion_dict, "lc") else None,
859
+ )
860
+
861
+ def run(
862
+ self,
863
+ turn_events: List[Event],
864
+ prior_events: List[Event],
865
+ assertion_order_error_message: str = "",
866
+ **kwargs: Any,
867
+ ) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
868
+ """Checks that the bot did not utter the specified messages or buttons."""
869
+ step_index = kwargs.get("step_index")
870
+ original_turn_events, turn_events = _get_turn_events_based_on_step_index(
871
+ step_index, turn_events, prior_events
872
+ )
873
+
874
+ for event in turn_events:
875
+ if isinstance(event, BotUttered):
876
+ error_messages = []
877
+ if self._utter_name_matches(event):
878
+ error_messages.append(
879
+ f"Bot uttered a forbidden utterance '{self.utter_name}'."
880
+ )
881
+ if self._text_matches(event):
882
+ error_messages.append(
883
+ f"Bot uttered a forbidden message matching "
884
+ f"the pattern '{self.text_matches}'."
885
+ )
886
+ if self._buttons_match(event):
887
+ error_messages.append(
888
+ "Bot uttered a forbidden response with specified buttons."
889
+ )
890
+
891
+ if error_messages:
892
+ error_message = " ".join(error_messages)
893
+ error_message += assertion_order_error_message
894
+ return self._generate_assertion_failure(
895
+ error_message, prior_events, original_turn_events, self.line
896
+ )
897
+ return None, None
898
+
899
+ def _utter_name_matches(self, event: BotUttered) -> bool:
900
+ if self.utter_name is not None:
901
+ if event.metadata.get("utter_action") == self.utter_name:
902
+ return True
903
+ return False
904
+
905
+ def _text_matches(self, event: BotUttered) -> bool:
906
+ if self.text_matches is not None:
907
+ pattern = re.compile(self.text_matches)
908
+ if pattern.search(event.text):
909
+ return True
910
+ return False
911
+
912
+ def _buttons_match(self, event: BotUttered) -> bool:
913
+ """Check if the bot response contains any of the forbidden buttons."""
914
+ if self.buttons is None:
915
+ return False
916
+
917
+ actual_buttons = event.data.get("buttons", [])
918
+ if not actual_buttons:
919
+ return False
920
+
921
+ for actual_button in actual_buttons:
922
+ if any(
923
+ self._is_forbidden_button(actual_button, forbidden_button)
924
+ for forbidden_button in self.buttons
925
+ ):
926
+ return True
927
+ return False
928
+
929
+ @staticmethod
930
+ def _is_forbidden_button(
931
+ actual_button: Dict[str, Any], forbidden_button: AssertedButton
932
+ ) -> bool:
933
+ """Check if the button matches any of the forbidden buttons."""
934
+ actual_title = actual_button.get("title")
935
+ actual_payload = actual_button.get("payload")
936
+
937
+ title_matches = forbidden_button.title == actual_title
938
+ payload_matches = forbidden_button.payload == actual_payload
939
+ if title_matches and payload_matches:
940
+ return True
941
+ return False
942
+
943
+ def __hash__(self) -> int:
944
+ """Hash method to ensure the assertion is hashable."""
945
+ return hash(json.dumps(self.as_dict()))
946
+
947
+
806
948
  @dataclass
807
949
  class GenerativeResponseMixin(Assertion):
808
950
  """Mixin class for storing generative response assertions."""
@@ -1179,3 +1321,25 @@ def _find_matching_generative_events(turn_events: List[Event]) -> List[BotUttere
1179
1321
  and event.metadata.get(UTTER_SOURCE_METADATA_KEY)
1180
1322
  in ELIGIBLE_UTTER_SOURCE_METADATA
1181
1323
  ]
1324
+
1325
+
1326
+ def _get_turn_events_based_on_step_index(
1327
+ step_index: int, turn_events: List[Event], prior_events: List[Event]
1328
+ ) -> Tuple[List[Event], List[Event]]:
1329
+ """Get the turn events based on the step index.
1330
+
1331
+ For the first step, we need to include the prior events as well
1332
+ in the same user turn. For the subsequent steps, we only need the
1333
+ events that follow the user uttered event on which the tracker
1334
+ was originally sliced by.
1335
+
1336
+ Returns:
1337
+ List[Event]: The copy of turn_events
1338
+ List[Event]: The turn events based on the step index
1339
+
1340
+ """
1341
+ original_turn_events = turn_events[:]
1342
+ if step_index == 0:
1343
+ return original_turn_events, prior_events + turn_events
1344
+
1345
+ return original_turn_events, turn_events
@@ -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
@@ -442,7 +442,7 @@ class E2ETestRunner:
442
442
  assertion_failure_found = False
443
443
  input_metadata = input_metadata if input_metadata else []
444
444
 
445
- for step in test_case.steps:
445
+ for index, step in enumerate(test_case.steps):
446
446
  if not step.assertions:
447
447
  structlogger.debug(
448
448
  "e2e_test_runner.run_assertions.no_assertions.skipping_step",
@@ -490,6 +490,7 @@ class E2ETestRunner:
490
490
  assertion_order_error_message=assertion_order_error_msg,
491
491
  llm_judge_config=self.llm_judge_config,
492
492
  step_text=step.text,
493
+ step_index=index,
493
494
  )
494
495
 
495
496
  if assertion_failure:
@@ -826,7 +827,7 @@ class E2ETestRunner:
826
827
  return
827
828
 
828
829
  tracker = await self.agent.processor.fetch_tracker_with_initial_session(
829
- sender_id
830
+ sender_id, output_channel=CollectingOutputChannel()
830
831
  )
831
832
 
832
833
  for fixture in fixtures:
rasa/engine/graph.py CHANGED
@@ -67,6 +67,14 @@ class SchemaNode:
67
67
  is_input: bool = False
68
68
  resource: Optional[Resource] = None
69
69
 
70
+ def matches_type(self, node_type: Type, include_subtypes: bool = True) -> bool:
71
+ """Checks if schema node's 'uses' is of specified node type.
72
+ By default, it also checks for subtypes of the specified node type.
73
+ """
74
+ return (self.uses is node_type) or (
75
+ include_subtypes and issubclass(self.uses, node_type)
76
+ )
77
+
70
78
 
71
79
  @dataclass
72
80
  class GraphSchema:
@@ -166,9 +174,7 @@ class GraphSchema:
166
174
  By default, it also checks for subtypes of the specified node type.
167
175
  """
168
176
  for node in self.nodes.values():
169
- if (node.uses is node_type) or (
170
- include_subtypes and issubclass(node.uses, node_type)
171
- ):
177
+ if node.matches_type(node_type, include_subtypes):
172
178
  return True
173
179
  return False
174
180
 
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,