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.

Files changed (238) hide show
  1. rasa/__main__.py +31 -15
  2. rasa/api.py +12 -2
  3. rasa/cli/arguments/default_arguments.py +24 -4
  4. rasa/cli/arguments/run.py +15 -0
  5. rasa/cli/arguments/shell.py +5 -1
  6. rasa/cli/arguments/train.py +17 -9
  7. rasa/cli/evaluate.py +7 -7
  8. rasa/cli/inspect.py +19 -7
  9. rasa/cli/interactive.py +1 -0
  10. rasa/cli/project_templates/calm/config.yml +5 -7
  11. rasa/cli/project_templates/calm/endpoints.yml +15 -2
  12. rasa/cli/project_templates/tutorial/config.yml +8 -5
  13. rasa/cli/project_templates/tutorial/data/flows.yml +1 -1
  14. rasa/cli/project_templates/tutorial/data/patterns.yml +5 -0
  15. rasa/cli/project_templates/tutorial/domain.yml +14 -0
  16. rasa/cli/project_templates/tutorial/endpoints.yml +5 -0
  17. rasa/cli/run.py +7 -0
  18. rasa/cli/scaffold.py +4 -2
  19. rasa/cli/studio/upload.py +0 -15
  20. rasa/cli/train.py +14 -53
  21. rasa/cli/utils.py +14 -11
  22. rasa/cli/x.py +7 -7
  23. rasa/constants.py +3 -1
  24. rasa/core/actions/action.py +77 -33
  25. rasa/core/actions/action_hangup.py +29 -0
  26. rasa/core/actions/action_repeat_bot_messages.py +89 -0
  27. rasa/core/actions/e2e_stub_custom_action_executor.py +5 -1
  28. rasa/core/actions/http_custom_action_executor.py +4 -0
  29. rasa/core/agent.py +2 -2
  30. rasa/core/brokers/kafka.py +3 -1
  31. rasa/core/brokers/pika.py +3 -1
  32. rasa/core/channels/__init__.py +10 -6
  33. rasa/core/channels/channel.py +41 -4
  34. rasa/core/channels/development_inspector.py +150 -46
  35. rasa/core/channels/inspector/README.md +1 -1
  36. rasa/core/channels/inspector/dist/assets/{arc-b6e548fe.js → arc-bc141fb2.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-fa03ac9e.js → c4Diagram-d0fbc5ce-be2db283.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-ee67392a.js → classDiagram-936ed81e-55366915.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-9b283fae.js → classDiagram-v2-c3cb15f1-bb529518.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-8b6fcc2a.js → createText-62fc7601-b0ec81d6.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-22e77f4f.js → edges-f2ad444c-6166330c.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-60ffc87f.js → erDiagram-9d236eb7-5ccc6a8e.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-9dd802e4.js → flowDb-1972c806-fca3bfe4.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-5fa1912f.js → flowDiagram-7ea5b25a-4739080f.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-736177bf.js +1 -0
  46. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-622a1fd2.js → flowchart-elk-definition-abe16c3d-7c1b0e0f.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-e285a63a.js → ganttDiagram-9b5ea136-772fd050.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-f237bdca.js → gitGraphDiagram-99d0ae7c-8eae1dc9.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-4b03d70e.js → index-2c4b9a3b-f55afcdf.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/index-e7cef9de.js +1317 -0
  51. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-72a0fa5f.js → infoDiagram-736b4530-124d4a14.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-82218c41.js → journeyDiagram-df861f2b-7c4fae44.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{layout-78cff630.js → layout-b9885fb6.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{line-5038b469.js → line-7c59abb6.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{linear-c4fc4098.js → linear-4776f780.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-c33c8ea6.js → mindmap-definition-beec6740-2332c46c.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-a8d03059.js → pieDiagram-dbbf0591-8fb39303.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-6a0e56b2.js → quadrantDiagram-4d7f4fd6-3c7180a2.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-2dc7c7bd.js → requirementDiagram-6fc4c22a-e910bcb8.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-2360fe39.js → sankeyDiagram-8f13d901-ead16c89.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-41b9f9ad.js → sequenceDiagram-b655622a-29a02a19.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-0aad326f.js → stateDiagram-59f0c015-042b3137.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-9847d984.js → stateDiagram-v2-2b26beab-2178c0f3.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-564d890e.js → styles-080da4f6-23ffa4fc.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-38957613.js → styles-3dcbcfbf-94f59763.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-f0fc6921.js → styles-9c745c82-78a6bebc.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-ef3c5a77.js → svgDrawCommon-4835440b-eae2a6f6.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-bf3e91c1.js → timeline-definition-5b62e21b-5c968d92.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-4d4026c0.js → xychartDiagram-2b33534f-fd3db0d5.js} +1 -1
  70. rasa/core/channels/inspector/dist/index.html +18 -15
  71. rasa/core/channels/inspector/index.html +17 -14
  72. rasa/core/channels/inspector/package.json +5 -1
  73. rasa/core/channels/inspector/src/App.tsx +118 -68
  74. rasa/core/channels/inspector/src/components/Chat.tsx +95 -0
  75. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +11 -10
  76. rasa/core/channels/inspector/src/components/DialogueStack.tsx +10 -25
  77. rasa/core/channels/inspector/src/components/LoadingSpinner.tsx +6 -3
  78. rasa/core/channels/inspector/src/helpers/audiostream.ts +165 -0
  79. rasa/core/channels/inspector/src/helpers/formatters.test.ts +10 -0
  80. rasa/core/channels/inspector/src/helpers/formatters.ts +107 -41
  81. rasa/core/channels/inspector/src/helpers/utils.ts +92 -7
  82. rasa/core/channels/inspector/src/types.ts +21 -1
  83. rasa/core/channels/inspector/yarn.lock +94 -1
  84. rasa/core/channels/rest.py +51 -46
  85. rasa/core/channels/socketio.py +28 -1
  86. rasa/core/channels/telegram.py +1 -1
  87. rasa/core/channels/twilio.py +1 -1
  88. rasa/core/channels/{audiocodes.py → voice_ready/audiocodes.py} +122 -69
  89. rasa/core/channels/{voice_aware → voice_ready}/jambonz.py +26 -8
  90. rasa/core/channels/{voice_aware → voice_ready}/jambonz_protocol.py +57 -5
  91. rasa/core/channels/{twilio_voice.py → voice_ready/twilio_voice.py} +64 -28
  92. rasa/core/channels/voice_ready/utils.py +37 -0
  93. rasa/core/channels/voice_stream/asr/__init__.py +0 -0
  94. rasa/core/channels/voice_stream/asr/asr_engine.py +89 -0
  95. rasa/core/channels/voice_stream/asr/asr_event.py +18 -0
  96. rasa/core/channels/voice_stream/asr/azure.py +129 -0
  97. rasa/core/channels/voice_stream/asr/deepgram.py +90 -0
  98. rasa/core/channels/voice_stream/audio_bytes.py +8 -0
  99. rasa/core/channels/voice_stream/browser_audio.py +107 -0
  100. rasa/core/channels/voice_stream/call_state.py +23 -0
  101. rasa/core/channels/voice_stream/tts/__init__.py +0 -0
  102. rasa/core/channels/voice_stream/tts/azure.py +106 -0
  103. rasa/core/channels/voice_stream/tts/cartesia.py +118 -0
  104. rasa/core/channels/voice_stream/tts/tts_cache.py +27 -0
  105. rasa/core/channels/voice_stream/tts/tts_engine.py +58 -0
  106. rasa/core/channels/voice_stream/twilio_media_streams.py +173 -0
  107. rasa/core/channels/voice_stream/util.py +57 -0
  108. rasa/core/channels/voice_stream/voice_channel.py +427 -0
  109. rasa/core/information_retrieval/qdrant.py +1 -0
  110. rasa/core/nlg/contextual_response_rephraser.py +45 -17
  111. rasa/{nlu → core}/persistor.py +203 -68
  112. rasa/core/policies/enterprise_search_policy.py +119 -63
  113. rasa/core/policies/flows/flow_executor.py +15 -22
  114. rasa/core/policies/intentless_policy.py +83 -28
  115. rasa/core/processor.py +25 -0
  116. rasa/core/run.py +12 -2
  117. rasa/core/secrets_manager/constants.py +4 -0
  118. rasa/core/secrets_manager/factory.py +8 -0
  119. rasa/core/secrets_manager/vault.py +11 -1
  120. rasa/core/training/interactive.py +33 -34
  121. rasa/core/utils.py +47 -21
  122. rasa/dialogue_understanding/coexistence/llm_based_router.py +41 -14
  123. rasa/dialogue_understanding/commands/__init__.py +6 -0
  124. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +60 -0
  125. rasa/dialogue_understanding/commands/session_end_command.py +61 -0
  126. rasa/dialogue_understanding/commands/user_silence_command.py +59 -0
  127. rasa/dialogue_understanding/commands/utils.py +5 -0
  128. rasa/dialogue_understanding/generator/constants.py +2 -0
  129. rasa/dialogue_understanding/generator/flow_retrieval.py +47 -9
  130. rasa/dialogue_understanding/generator/llm_based_command_generator.py +38 -15
  131. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  132. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +35 -13
  133. rasa/dialogue_understanding/generator/single_step/command_prompt_template.jinja2 +3 -0
  134. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +60 -13
  135. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +53 -0
  136. rasa/dialogue_understanding/patterns/repeat.py +37 -0
  137. rasa/dialogue_understanding/patterns/user_silence.py +37 -0
  138. rasa/dialogue_understanding/processor/command_processor.py +21 -1
  139. rasa/e2e_test/aggregate_test_stats_calculator.py +1 -11
  140. rasa/e2e_test/assertions.py +136 -61
  141. rasa/e2e_test/assertions_schema.yml +23 -0
  142. rasa/e2e_test/e2e_test_case.py +85 -6
  143. rasa/e2e_test/e2e_test_runner.py +2 -3
  144. rasa/engine/graph.py +0 -1
  145. rasa/engine/loader.py +12 -0
  146. rasa/engine/recipes/config_files/default_config.yml +0 -3
  147. rasa/engine/recipes/default_recipe.py +0 -1
  148. rasa/engine/recipes/graph_recipe.py +0 -1
  149. rasa/engine/runner/dask.py +2 -2
  150. rasa/engine/storage/local_model_storage.py +12 -42
  151. rasa/engine/storage/storage.py +1 -5
  152. rasa/engine/validation.py +527 -74
  153. rasa/model_manager/__init__.py +0 -0
  154. rasa/model_manager/config.py +40 -0
  155. rasa/model_manager/model_api.py +559 -0
  156. rasa/model_manager/runner_service.py +286 -0
  157. rasa/model_manager/socket_bridge.py +146 -0
  158. rasa/model_manager/studio_jwt_auth.py +86 -0
  159. rasa/model_manager/trainer_service.py +325 -0
  160. rasa/model_manager/utils.py +87 -0
  161. rasa/model_manager/warm_rasa_process.py +187 -0
  162. rasa/model_service.py +112 -0
  163. rasa/model_training.py +42 -23
  164. rasa/nlu/tokenizers/whitespace_tokenizer.py +3 -14
  165. rasa/server.py +4 -2
  166. rasa/shared/constants.py +60 -8
  167. rasa/shared/core/constants.py +13 -0
  168. rasa/shared/core/domain.py +107 -50
  169. rasa/shared/core/events.py +29 -0
  170. rasa/shared/core/flows/flow.py +5 -0
  171. rasa/shared/core/flows/flows_list.py +19 -6
  172. rasa/shared/core/flows/flows_yaml_schema.json +10 -0
  173. rasa/shared/core/flows/utils.py +39 -0
  174. rasa/shared/core/flows/validation.py +121 -0
  175. rasa/shared/core/flows/yaml_flows_io.py +15 -27
  176. rasa/shared/core/slots.py +5 -0
  177. rasa/shared/importers/importer.py +59 -41
  178. rasa/shared/importers/multi_project.py +23 -11
  179. rasa/shared/importers/rasa.py +12 -3
  180. rasa/shared/importers/remote_importer.py +196 -0
  181. rasa/shared/importers/utils.py +3 -1
  182. rasa/shared/nlu/training_data/formats/rasa_yaml.py +18 -3
  183. rasa/shared/nlu/training_data/training_data.py +18 -19
  184. rasa/shared/providers/_configs/litellm_router_client_config.py +220 -0
  185. rasa/shared/providers/_configs/model_group_config.py +167 -0
  186. rasa/shared/providers/_configs/openai_client_config.py +1 -1
  187. rasa/shared/providers/_configs/rasa_llm_client_config.py +73 -0
  188. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -0
  189. rasa/shared/providers/_configs/utils.py +16 -0
  190. rasa/shared/providers/_utils.py +79 -0
  191. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +13 -29
  192. rasa/shared/providers/embedding/azure_openai_embedding_client.py +54 -21
  193. rasa/shared/providers/embedding/default_litellm_embedding_client.py +24 -0
  194. rasa/shared/providers/embedding/litellm_router_embedding_client.py +135 -0
  195. rasa/shared/providers/llm/_base_litellm_client.py +34 -22
  196. rasa/shared/providers/llm/azure_openai_llm_client.py +50 -29
  197. rasa/shared/providers/llm/default_litellm_llm_client.py +24 -0
  198. rasa/shared/providers/llm/litellm_router_llm_client.py +182 -0
  199. rasa/shared/providers/llm/rasa_llm_client.py +112 -0
  200. rasa/shared/providers/llm/self_hosted_llm_client.py +5 -29
  201. rasa/shared/providers/mappings.py +19 -0
  202. rasa/shared/providers/router/__init__.py +0 -0
  203. rasa/shared/providers/router/_base_litellm_router_client.py +183 -0
  204. rasa/shared/providers/router/router_client.py +73 -0
  205. rasa/shared/utils/common.py +40 -24
  206. rasa/shared/utils/health_check/__init__.py +0 -0
  207. rasa/shared/utils/health_check/embeddings_health_check_mixin.py +31 -0
  208. rasa/shared/utils/health_check/health_check.py +258 -0
  209. rasa/shared/utils/health_check/llm_health_check_mixin.py +31 -0
  210. rasa/shared/utils/io.py +27 -6
  211. rasa/shared/utils/llm.py +353 -43
  212. rasa/shared/utils/schemas/events.py +2 -0
  213. rasa/shared/utils/schemas/model_config.yml +0 -10
  214. rasa/shared/utils/yaml.py +181 -38
  215. rasa/studio/data_handler.py +3 -1
  216. rasa/studio/upload.py +160 -74
  217. rasa/telemetry.py +94 -17
  218. rasa/tracing/config.py +3 -1
  219. rasa/tracing/instrumentation/attribute_extractors.py +95 -18
  220. rasa/tracing/instrumentation/instrumentation.py +121 -0
  221. rasa/utils/common.py +5 -0
  222. rasa/utils/endpoints.py +27 -1
  223. rasa/utils/io.py +8 -16
  224. rasa/utils/log_utils.py +9 -2
  225. rasa/utils/sanic_error_handler.py +32 -0
  226. rasa/validator.py +110 -4
  227. rasa/version.py +1 -1
  228. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/METADATA +14 -12
  229. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/RECORD +234 -183
  230. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +0 -1
  231. rasa/core/channels/inspector/dist/assets/index-a5d3e69d.js +0 -1040
  232. rasa/core/channels/voice_aware/utils.py +0 -20
  233. rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +0 -407
  234. /rasa/core/channels/{voice_aware → voice_ready}/__init__.py +0 -0
  235. /rasa/core/channels/{voice_native → voice_stream}/__init__.py +0 -0
  236. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/NOTICE +0 -0
  237. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.dist-info}/WHEEL +0 -0
  238. {rasa_pro-3.10.15.dist-info → rasa_pro-3.11.0.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
 
@@ -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, original_turn_events, self.line
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, original_turn_events, slot.line
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, original_turn_events, slot.line
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
- error_message = f"Bot did not utter '{self.utter_name}' response."
756
- error_message += assertion_order_error_message
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
- error_message = (
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
- error_message = (
764
+ error_messages.append(
791
765
  "Bot did not utter any response with the expected buttons."
792
766
  )
793
- error_message += assertion_order_error_message
794
- return self._generate_assertion_failure(
795
- error_message, prior_events, original_turn_events, self.line
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:
@@ -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
- logger = logging.getLogger(__name__)
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
- logger.debug(
347
- f"Could not find `UserUttered` event in the ActualStepOutput: "
348
- f"{self}"
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
- return TestCase(
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."""
@@ -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 index, step in enumerate(test_case.steps):
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
@@ -634,4 +634,3 @@ class GraphModelConfiguration:
634
634
  language: Optional[Text]
635
635
  core_target: Optional[Text]
636
636
  nlu_target: Optional[Text]
637
- spaces: Optional[Dict[Text, Text]] = None
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,
@@ -35,9 +35,6 @@ pipeline:
35
35
  policies:
36
36
  - name: MemoizationPolicy
37
37
  - name: RulePolicy
38
- - name: UnexpecTEDIntentPolicy
39
- max_history: 5
40
- epochs: 100
41
38
  - name: TEDPolicy
42
39
  max_history: 5
43
40
  epochs: 100
@@ -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
  )
@@ -73,7 +73,6 @@ class GraphV1Recipe(Recipe):
73
73
  training_type=training_type,
74
74
  assistant_id=config.get(ASSISTANT_ID_KEY),
75
75
  language=config.get("language"),
76
- spaces=config.get("spaces"),
77
76
  core_target=core_target,
78
77
  nlu_target=nlu_target,
79
78
  )
@@ -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) # type:ignore[no-untyped-call]
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) # type:ignore[no-untyped-call]
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