rasa-pro 3.10.15__py3-none-any.whl → 3.11.0__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 +31 -15
- rasa/api.py +12 -2
- rasa/cli/arguments/default_arguments.py +24 -4
- rasa/cli/arguments/run.py +15 -0
- rasa/cli/arguments/shell.py +5 -1
- rasa/cli/arguments/train.py +17 -9
- rasa/cli/evaluate.py +7 -7
- rasa/cli/inspect.py +19 -7
- rasa/cli/interactive.py +1 -0
- rasa/cli/project_templates/calm/config.yml +5 -7
- rasa/cli/project_templates/calm/endpoints.yml +15 -2
- rasa/cli/project_templates/tutorial/config.yml +8 -5
- rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
- rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
- rasa/cli/project_templates/tutorial/domain.yml +14 -0
- rasa/cli/project_templates/tutorial/endpoints.yml +5 -0
- rasa/cli/run.py +7 -0
- rasa/cli/scaffold.py +4 -2
- rasa/cli/studio/upload.py +0 -15
- rasa/cli/train.py +14 -53
- rasa/cli/utils.py +14 -11
- rasa/cli/x.py +7 -7
- rasa/constants.py +3 -1
- rasa/core/actions/action.py +77 -33
- rasa/core/actions/action_hangup.py +29 -0
- rasa/core/actions/action_repeat_bot_messages.py +89 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +5 -1
- rasa/core/actions/http_custom_action_executor.py +4 -0
- rasa/core/agent.py +2 -2
- rasa/core/brokers/kafka.py +3 -1
- rasa/core/brokers/pika.py +3 -1
- rasa/core/channels/__init__.py +10 -6
- rasa/core/channels/channel.py +41 -4
- rasa/core/channels/development_inspector.py +150 -46
- rasa/core/channels/inspector/README.md +1 -1
- rasa/core/channels/inspector/dist/assets/{arc-b6e548fe.js → arc-bc141fb2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-fa03ac9e.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-ee67392a.js → classDiagram-936ed81e-55366915.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-9b283fae.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-8b6fcc2a.js → createText-62fc7601-b0ec81d6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-22e77f4f.js → edges-f2ad444c-6166330c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-60ffc87f.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-9dd802e4.js → flowDb-1972c806-fca3bfe4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-5fa1912f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-622a1fd2.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-e285a63a.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-f237bdca.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-4b03d70e.js → index-2c4b9a3b-f55afcdf.js} +1 -1
- rasa/core/channels/inspector/dist/assets/index-e7cef9de.js +1317 -0
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-72a0fa5f.js → infoDiagram-736b4530-124d4a14.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-82218c41.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-78cff630.js → layout-b9885fb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-5038b469.js → line-7c59abb6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-c4fc4098.js → linear-4776f780.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-c33c8ea6.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-a8d03059.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-6a0e56b2.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-2dc7c7bd.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-2360fe39.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-41b9f9ad.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-0aad326f.js → stateDiagram-59f0c015-042b3137.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-9847d984.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-564d890e.js → styles-080da4f6-23ffa4fc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-38957613.js → styles-3dcbcfbf-94f59763.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-f0fc6921.js → styles-9c745c82-78a6bebc.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-ef3c5a77.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-bf3e91c1.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-4d4026c0.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +18 -15
- rasa/core/channels/inspector/index.html +17 -14
- rasa/core/channels/inspector/package.json +5 -1
- rasa/core/channels/inspector/src/App.tsx +118 -68
- rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +11 -10
- rasa/core/channels/inspector/src/components/DialogueStack.tsx +10 -25
- rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -3
- rasa/core/channels/inspector/src/helpers/audiostream.ts +165 -0
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +10 -0
- rasa/core/channels/inspector/src/helpers/formatters.ts +107 -41
- rasa/core/channels/inspector/src/helpers/utils.ts +92 -7
- rasa/core/channels/inspector/src/types.ts +21 -1
- rasa/core/channels/inspector/yarn.lock +94 -1
- rasa/core/channels/rest.py +51 -46
- rasa/core/channels/socketio.py +28 -1
- rasa/core/channels/telegram.py +1 -1
- rasa/core/channels/twilio.py +1 -1
- rasa/core/channels/{audiocodes.py → voice_ready/audiocodes.py} +122 -69
- rasa/core/channels/{voice_aware → voice_ready}/jambonz.py +26 -8
- rasa/core/channels/{voice_aware → voice_ready}/jambonz_protocol.py +57 -5
- rasa/core/channels/{twilio_voice.py → voice_ready/twilio_voice.py} +64 -28
- rasa/core/channels/voice_ready/utils.py +37 -0
- rasa/core/channels/voice_stream/asr/__init__.py +0 -0
- rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
- rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
- rasa/core/channels/voice_stream/asr/azure.py +129 -0
- rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
- rasa/core/channels/voice_stream/audio_bytes.py +8 -0
- rasa/core/channels/voice_stream/browser_audio.py +107 -0
- rasa/core/channels/voice_stream/call_state.py +23 -0
- rasa/core/channels/voice_stream/tts/__init__.py +0 -0
- rasa/core/channels/voice_stream/tts/azure.py +106 -0
- rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
- rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
- rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
- rasa/core/channels/voice_stream/util.py +57 -0
- rasa/core/channels/voice_stream/voice_channel.py +427 -0
- rasa/core/information_retrieval/qdrant.py +1 -0
- rasa/core/nlg/contextual_response_rephraser.py +45 -17
- rasa/{nlu → core}/persistor.py +203 -68
- rasa/core/policies/enterprise_search_policy.py +119 -63
- rasa/core/policies/flows/flow_executor.py +15 -22
- rasa/core/policies/intentless_policy.py +83 -28
- rasa/core/processor.py +25 -0
- rasa/core/run.py +12 -2
- rasa/core/secrets_manager/constants.py +4 -0
- rasa/core/secrets_manager/factory.py +8 -0
- rasa/core/secrets_manager/vault.py +11 -1
- rasa/core/training/interactive.py +33 -34
- rasa/core/utils.py +47 -21
- rasa/dialogue_understanding/coexistence/llm_based_router.py +41 -14
- rasa/dialogue_understanding/commands/__init__.py +6 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
- rasa/dialogue_understanding/commands/session_end_command.py +61 -0
- rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +5 -0
- rasa/dialogue_understanding/generator/constants.py +2 -0
- rasa/dialogue_understanding/generator/flow_retrieval.py +47 -9
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +38 -15
- rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +35 -13
- rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +3 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +60 -13
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +53 -0
- rasa/dialogue_understanding/patterns/repeat.py +37 -0
- rasa/dialogue_understanding/patterns/user_silence.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +21 -1
- rasa/e2e_test/aggregate_test_stats_calculator.py +1 -11
- rasa/e2e_test/assertions.py +136 -61
- rasa/e2e_test/assertions_schema.yml +23 -0
- rasa/e2e_test/e2e_test_case.py +85 -6
- rasa/e2e_test/e2e_test_runner.py +2 -3
- rasa/engine/graph.py +0 -1
- rasa/engine/loader.py +12 -0
- rasa/engine/recipes/config_files/default_config.yml +0 -3
- rasa/engine/recipes/default_recipe.py +0 -1
- rasa/engine/recipes/graph_recipe.py +0 -1
- rasa/engine/runner/dask.py +2 -2
- rasa/engine/storage/local_model_storage.py +12 -42
- rasa/engine/storage/storage.py +1 -5
- rasa/engine/validation.py +527 -74
- rasa/model_manager/__init__.py +0 -0
- rasa/model_manager/config.py +40 -0
- rasa/model_manager/model_api.py +559 -0
- rasa/model_manager/runner_service.py +286 -0
- rasa/model_manager/socket_bridge.py +146 -0
- rasa/model_manager/studio_jwt_auth.py +86 -0
- rasa/model_manager/trainer_service.py +325 -0
- rasa/model_manager/utils.py +87 -0
- rasa/model_manager/warm_rasa_process.py +187 -0
- rasa/model_service.py +112 -0
- rasa/model_training.py +42 -23
- rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
- rasa/server.py +4 -2
- rasa/shared/constants.py +60 -8
- rasa/shared/core/constants.py +13 -0
- rasa/shared/core/domain.py +107 -50
- rasa/shared/core/events.py +29 -0
- rasa/shared/core/flows/flow.py +5 -0
- rasa/shared/core/flows/flows_list.py +19 -6
- rasa/shared/core/flows/flows_yaml_schema.json +10 -0
- rasa/shared/core/flows/utils.py +39 -0
- rasa/shared/core/flows/validation.py +121 -0
- rasa/shared/core/flows/yaml_flows_io.py +15 -27
- rasa/shared/core/slots.py +5 -0
- rasa/shared/importers/importer.py +59 -41
- rasa/shared/importers/multi_project.py +23 -11
- rasa/shared/importers/rasa.py +12 -3
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/importers/utils.py +3 -1
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
- rasa/shared/nlu/training_data/training_data.py +18 -19
- rasa/shared/providers/_configs/litellm_router_client_config.py +220 -0
- rasa/shared/providers/_configs/model_group_config.py +167 -0
- rasa/shared/providers/_configs/openai_client_config.py +1 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
- rasa/shared/providers/_configs/utils.py +16 -0
- rasa/shared/providers/_utils.py +79 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +13 -29
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
- rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
- rasa/shared/providers/llm/_base_litellm_client.py +34 -22
- rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
- rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
- rasa/shared/providers/llm/litellm_router_llm_client.py +182 -0
- rasa/shared/providers/llm/rasa_llm_client.py +112 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +5 -29
- rasa/shared/providers/mappings.py +19 -0
- rasa/shared/providers/router/__init__.py +0 -0
- rasa/shared/providers/router/_base_litellm_router_client.py +183 -0
- rasa/shared/providers/router/router_client.py +73 -0
- rasa/shared/utils/common.py +40 -24
- 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 +258 -0
- rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
- rasa/shared/utils/io.py +27 -6
- rasa/shared/utils/llm.py +353 -43
- rasa/shared/utils/schemas/events.py +2 -0
- rasa/shared/utils/schemas/model_config.yml +0 -10
- rasa/shared/utils/yaml.py +181 -38
- rasa/studio/data_handler.py +3 -1
- rasa/studio/upload.py +160 -74
- rasa/telemetry.py +94 -17
- rasa/tracing/config.py +3 -1
- rasa/tracing/instrumentation/attribute_extractors.py +95 -18
- rasa/tracing/instrumentation/instrumentation.py +121 -0
- rasa/utils/common.py +5 -0
- rasa/utils/endpoints.py +27 -1
- rasa/utils/io.py +8 -16
- rasa/utils/log_utils.py +9 -2
- rasa/utils/sanic_error_handler.py +32 -0
- rasa/validator.py +110 -4
- rasa/version.py +1 -1
- {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/METADATA +14 -12
- {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/RECORD +234 -183
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +0 -1
- rasa/core/channels/inspector/dist/assets/index-a5d3e69d.js +0 -1040
- rasa/core/channels/voice_aware/utils.py +0 -20
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +0 -407
- /rasa/core/channels/{voice_aware → voice_ready}/__init__.py +0 -0
- /rasa/core/channels/{voice_native → voice_stream}/__init__.py +0 -0
- {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/NOTICE +0 -0
- {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/WHEEL +0 -0
- {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.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
|
|
|
@@ -452,11 +453,6 @@ 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."""
|
|
455
|
-
step_index = kwargs.get("step_index")
|
|
456
|
-
original_turn_events, turn_events = _get_turn_events_based_on_step_index(
|
|
457
|
-
step_index, turn_events, prior_events
|
|
458
|
-
)
|
|
459
|
-
|
|
460
456
|
try:
|
|
461
457
|
matching_event = next(
|
|
462
458
|
event
|
|
@@ -469,7 +465,7 @@ class ActionExecutedAssertion(Assertion):
|
|
|
469
465
|
error_message += assertion_order_error_message
|
|
470
466
|
|
|
471
467
|
return self._generate_assertion_failure(
|
|
472
|
-
error_message, prior_events,
|
|
468
|
+
error_message, prior_events, turn_events, self.line
|
|
473
469
|
)
|
|
474
470
|
|
|
475
471
|
return None, matching_event
|
|
@@ -524,11 +520,6 @@ class SlotWasSetAssertion(Assertion):
|
|
|
524
520
|
"""Run the slot_was_set assertion on the given events for that user turn."""
|
|
525
521
|
matching_event = None
|
|
526
522
|
|
|
527
|
-
step_index = kwargs.get("step_index")
|
|
528
|
-
original_turn_events, turn_events = _get_turn_events_based_on_step_index(
|
|
529
|
-
step_index, turn_events, prior_events
|
|
530
|
-
)
|
|
531
|
-
|
|
532
523
|
for slot in self.slots:
|
|
533
524
|
matching_events = [
|
|
534
525
|
event
|
|
@@ -567,7 +558,7 @@ class SlotWasSetAssertion(Assertion):
|
|
|
567
558
|
error_message += assertion_order_error_message
|
|
568
559
|
|
|
569
560
|
return self._generate_assertion_failure(
|
|
570
|
-
error_message, prior_events,
|
|
561
|
+
error_message, prior_events, turn_events, slot.line
|
|
571
562
|
)
|
|
572
563
|
|
|
573
564
|
return None, matching_event
|
|
@@ -605,11 +596,6 @@ class SlotWasNotSetAssertion(Assertion):
|
|
|
605
596
|
"""Run the slot_was_not_set assertion on the given events for that user turn."""
|
|
606
597
|
matching_event = None
|
|
607
598
|
|
|
608
|
-
step_index = kwargs.get("step_index")
|
|
609
|
-
original_turn_events, turn_events = _get_turn_events_based_on_step_index(
|
|
610
|
-
step_index, turn_events, prior_events
|
|
611
|
-
)
|
|
612
|
-
|
|
613
599
|
for slot in self.slots:
|
|
614
600
|
matching_events = [
|
|
615
601
|
event
|
|
@@ -645,7 +631,7 @@ class SlotWasNotSetAssertion(Assertion):
|
|
|
645
631
|
error_message += assertion_order_error_message
|
|
646
632
|
|
|
647
633
|
return self._generate_assertion_failure(
|
|
648
|
-
error_message, prior_events,
|
|
634
|
+
error_message, prior_events, turn_events, slot.line
|
|
649
635
|
)
|
|
650
636
|
|
|
651
637
|
return None, matching_event
|
|
@@ -737,11 +723,7 @@ class BotUtteredAssertion(Assertion):
|
|
|
737
723
|
) -> Tuple[Optional[AssertionFailure], Optional[Event]]:
|
|
738
724
|
"""Run the bot_uttered assertion on the given events for that user turn."""
|
|
739
725
|
matching_event = None
|
|
740
|
-
|
|
741
|
-
step_index = kwargs.get("step_index")
|
|
742
|
-
original_turn_events, turn_events = _get_turn_events_based_on_step_index(
|
|
743
|
-
step_index, turn_events, prior_events
|
|
744
|
-
)
|
|
726
|
+
error_messages = []
|
|
745
727
|
|
|
746
728
|
if self.utter_name is not None:
|
|
747
729
|
try:
|
|
@@ -752,11 +734,8 @@ class BotUtteredAssertion(Assertion):
|
|
|
752
734
|
and event.metadata.get("utter_action") == self.utter_name
|
|
753
735
|
)
|
|
754
736
|
except StopIteration:
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
return self._generate_assertion_failure(
|
|
759
|
-
error_message, prior_events, original_turn_events, self.line
|
|
737
|
+
error_messages.append(
|
|
738
|
+
f"Bot did not utter '{self.utter_name}' response."
|
|
760
739
|
)
|
|
761
740
|
|
|
762
741
|
if self.text_matches is not None:
|
|
@@ -768,16 +747,11 @@ class BotUtteredAssertion(Assertion):
|
|
|
768
747
|
if isinstance(event, BotUttered) and pattern.search(event.text)
|
|
769
748
|
)
|
|
770
749
|
except StopIteration:
|
|
771
|
-
|
|
750
|
+
error_messages.append(
|
|
772
751
|
f"Bot did not utter any response which "
|
|
773
752
|
f"matches the provided text pattern "
|
|
774
753
|
f"'{self.text_matches}'."
|
|
775
754
|
)
|
|
776
|
-
error_message += assertion_order_error_message
|
|
777
|
-
|
|
778
|
-
return self._generate_assertion_failure(
|
|
779
|
-
error_message, prior_events, original_turn_events, self.line
|
|
780
|
-
)
|
|
781
755
|
|
|
782
756
|
if self.buttons:
|
|
783
757
|
try:
|
|
@@ -787,13 +761,16 @@ class BotUtteredAssertion(Assertion):
|
|
|
787
761
|
if isinstance(event, BotUttered) and self._buttons_match(event)
|
|
788
762
|
)
|
|
789
763
|
except StopIteration:
|
|
790
|
-
|
|
764
|
+
error_messages.append(
|
|
791
765
|
"Bot did not utter any response with the expected buttons."
|
|
792
766
|
)
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
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
|
+
)
|
|
797
774
|
|
|
798
775
|
return None, matching_event
|
|
799
776
|
|
|
@@ -823,6 +800,126 @@ class BotUtteredAssertion(Assertion):
|
|
|
823
800
|
return hash(json.dumps(self.as_dict()))
|
|
824
801
|
|
|
825
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
|
+
|
|
826
923
|
@dataclass
|
|
827
924
|
class GenerativeResponseMixin(Assertion):
|
|
828
925
|
"""Mixin class for storing generative response assertions."""
|
|
@@ -1199,25 +1296,3 @@ def _find_matching_generative_events(turn_events: List[Event]) -> List[BotUttere
|
|
|
1199
1296
|
and event.metadata.get(UTTER_SOURCE_METADATA_KEY)
|
|
1200
1297
|
in ELIGIBLE_UTTER_SOURCE_METADATA
|
|
1201
1298
|
]
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
def _get_turn_events_based_on_step_index(
|
|
1205
|
-
step_index: int, turn_events: List[Event], prior_events: List[Event]
|
|
1206
|
-
) -> Tuple[List[Event], List[Event]]:
|
|
1207
|
-
"""Get the turn events based on the step index.
|
|
1208
|
-
|
|
1209
|
-
For the first step, we need to include the prior events as well
|
|
1210
|
-
in the same user turn. For the subsequent steps, we only need the
|
|
1211
|
-
events that follow the user uttered event on which the tracker
|
|
1212
|
-
was originally sliced by.
|
|
1213
|
-
|
|
1214
|
-
Returns:
|
|
1215
|
-
List[Event]: The copy of turn_events
|
|
1216
|
-
List[Event]: The turn events based on the step index
|
|
1217
|
-
|
|
1218
|
-
"""
|
|
1219
|
-
original_turn_events = turn_events[:]
|
|
1220
|
-
if step_index == 0:
|
|
1221
|
-
return original_turn_events, prior_events + turn_events
|
|
1222
|
-
|
|
1223
|
-
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_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/e2e_test/e2e_test_runner.py
CHANGED
|
@@ -16,6 +16,7 @@ import rasa.shared.utils.io
|
|
|
16
16
|
from rasa.core.channels import CollectingOutputChannel, UserMessage
|
|
17
17
|
from rasa.core.constants import ACTIVE_FLOW_METADATA_KEY, STEP_ID_METADATA_KEY
|
|
18
18
|
from rasa.core.exceptions import AgentNotReady
|
|
19
|
+
from rasa.core.persistor import StorageType
|
|
19
20
|
from rasa.core.utils import AvailableEndpoints
|
|
20
21
|
from rasa.e2e_test.constants import TEST_CASE_NAME, TEST_FILE_NAME
|
|
21
22
|
from rasa.e2e_test.e2e_config import create_llm_judge_config
|
|
@@ -34,7 +35,6 @@ from rasa.e2e_test.e2e_test_result import (
|
|
|
34
35
|
TestResult,
|
|
35
36
|
)
|
|
36
37
|
from rasa.llm_fine_tuning.conversations import Conversation
|
|
37
|
-
from rasa.nlu.persistor import StorageType
|
|
38
38
|
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
39
39
|
from rasa.shared.core.events import (
|
|
40
40
|
ActionExecuted,
|
|
@@ -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
|
|
445
|
+
for step in 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,7 +490,6 @@ 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,
|
|
494
493
|
)
|
|
495
494
|
|
|
496
495
|
if assertion_failure:
|
rasa/engine/graph.py
CHANGED
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,
|
|
@@ -233,7 +233,6 @@ class DefaultV1Recipe(Recipe):
|
|
|
233
233
|
training_type=training_type,
|
|
234
234
|
assistant_id=config.get(ASSISTANT_ID_KEY),
|
|
235
235
|
language=config.get("language"),
|
|
236
|
-
spaces=config.get("spaces"),
|
|
237
236
|
core_target=core_target,
|
|
238
237
|
nlu_target=f"run_{RegexMessageHandler.__name__}",
|
|
239
238
|
)
|
rasa/engine/runner/dask.py
CHANGED
|
@@ -220,7 +220,7 @@ async def execute_dask_graph(dsk: Dict[str, Any], result: List[str]) -> Any:
|
|
|
220
220
|
# if start_state_from_dask fails, we will have something
|
|
221
221
|
# to pass to the final block.
|
|
222
222
|
state = {}
|
|
223
|
-
keyorder = dask.local.order(dsk)
|
|
223
|
+
keyorder = dask.local.order(dsk)
|
|
224
224
|
|
|
225
225
|
state = dask.local.start_state_from_dask(dsk, cache=cache, sortkey=keyorder.get) # type:ignore[no-untyped-call]
|
|
226
226
|
|
|
@@ -235,7 +235,7 @@ async def execute_dask_graph(dsk: Dict[str, Any], result: List[str]) -> Any:
|
|
|
235
235
|
# Notify task is running
|
|
236
236
|
state["running"].add(key)
|
|
237
237
|
|
|
238
|
-
dependencies = dask.local.get_dependencies(dsk, key)
|
|
238
|
+
dependencies = dask.local.get_dependencies(dsk, key)
|
|
239
239
|
# Prep args to send
|
|
240
240
|
data = {dep: state["cache"][dep] for dep in dependencies}
|
|
241
241
|
|