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.
- rasa/__main__.py +9 -3
- rasa/cli/llm_fine_tuning.py +19 -11
- rasa/cli/project_templates/tutorial/config.yml +1 -3
- rasa/cli/project_templates/tutorial/endpoints.yml +8 -3
- rasa/cli/studio/upload.py +0 -15
- rasa/cli/train.py +9 -0
- rasa/cli/utils.py +1 -1
- rasa/core/channels/development_inspector.py +4 -1
- rasa/core/channels/inspector/dist/assets/{arc-bc141fb2.js → arc-861ddd57.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-be2db283.js → c4Diagram-d0fbc5ce-921f02db.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-55366915.js → classDiagram-936ed81e-b436c4f8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-bb529518.js → classDiagram-v2-c3cb15f1-511a23cb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-b0ec81d6.js → createText-62fc7601-ef476ecd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-6166330c.js → edges-f2ad444c-f1878e0a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-5ccc6a8e.js → erDiagram-9d236eb7-fac75185.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-fca3bfe4.js → flowDb-1972c806-201c5bbc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4739080f.js → flowDiagram-7ea5b25a-f904ae41.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-b080d6f2.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-7c1b0e0f.js → flowchart-elk-definition-abe16c3d-1813da66.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-772fd050.js → ganttDiagram-9b5ea136-872af172.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-8eae1dc9.js → gitGraphDiagram-99d0ae7c-34a0af5a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-f55afcdf.js → index-2c4b9a3b-42ba3e3d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-e7cef9de.js → index-37817b51.js} +68 -68
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-124d4a14.js → infoDiagram-736b4530-6b731386.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-7c4fae44.js → journeyDiagram-df861f2b-e8579ac6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-b9885fb6.js → layout-89e6403a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-7c59abb6.js → line-dc73d3fc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-4776f780.js → linear-f5b1d2bc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2332c46c.js → mindmap-definition-beec6740-82cb74fa.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-8fb39303.js → pieDiagram-dbbf0591-bdf5f29b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-3c7180a2.js → quadrantDiagram-4d7f4fd6-c7a0cbe4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-e910bcb8.js → requirementDiagram-6fc4c22a-7ec5410f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-ead16c89.js → sankeyDiagram-8f13d901-caee5554.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-29a02a19.js → sequenceDiagram-b655622a-2935f8db.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-042b3137.js → stateDiagram-59f0c015-8f5d9693.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-2178c0f3.js → stateDiagram-v2-2b26beab-d565d1de.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-23ffa4fc.js → styles-080da4f6-75ad421d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-94f59763.js → styles-3dcbcfbf-7e764226.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-78a6bebc.js → styles-9c745c82-7a4e0e61.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-eae2a6f6.js → svgDrawCommon-4835440b-4019d1bf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-5c968d92.js → timeline-definition-5b62e21b-01ea12df.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-fd3db0d5.js → xychartDiagram-2b33534f-89407137.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +1 -1
- rasa/core/channels/inspector/src/helpers/audiostream.ts +28 -2
- rasa/core/channels/voice_stream/asr/asr_engine.py +19 -1
- rasa/core/channels/voice_stream/asr/azure.py +13 -3
- 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/core/utils.py +3 -1
- rasa/e2e_test/aggregate_test_stats_calculator.py +11 -1
- rasa/e2e_test/assertions.py +183 -19
- rasa/e2e_test/assertions_schema.yml +23 -0
- rasa/e2e_test/e2e_test_runner.py +4 -3
- rasa/engine/graph.py +9 -3
- rasa/engine/loader.py +12 -0
- rasa/engine/validation.py +345 -85
- 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 +27 -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.2.dist-info}/METADATA +2 -2
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/RECORD +91 -89
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +0 -1
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/NOTICE +0 -0
- {rasa_pro-3.11.0rc3.dist-info → rasa_pro-3.11.2.dist-info}/WHEEL +0 -0
- {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(
|
|
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
|
|
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)
|
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
|
|
|
@@ -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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
784
|
+
error_messages.append(
|
|
771
785
|
"Bot did not utter any response with the expected buttons."
|
|
772
786
|
)
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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:
|
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
|
|
@@ -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
|
|
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,
|