rasa-pro 3.8.17__py3-none-any.whl → 3.9.14__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 (272) hide show
  1. README.md +5 -5
  2. rasa/__main__.py +14 -9
  3. rasa/anonymization/anonymization_pipeline.py +0 -1
  4. rasa/anonymization/anonymization_rule_executor.py +3 -3
  5. rasa/anonymization/utils.py +4 -3
  6. rasa/api.py +2 -2
  7. rasa/cli/arguments/default_arguments.py +1 -1
  8. rasa/cli/arguments/run.py +2 -2
  9. rasa/cli/arguments/test.py +1 -1
  10. rasa/cli/arguments/train.py +10 -10
  11. rasa/cli/e2e_test.py +27 -7
  12. rasa/cli/export.py +0 -1
  13. rasa/cli/license.py +3 -3
  14. rasa/cli/project_templates/calm/actions/action_template.py +1 -1
  15. rasa/cli/project_templates/calm/config.yml +1 -1
  16. rasa/cli/project_templates/calm/credentials.yml +1 -1
  17. rasa/cli/project_templates/calm/data/flows/add_contact.yml +1 -1
  18. rasa/cli/project_templates/calm/data/flows/remove_contact.yml +1 -1
  19. rasa/cli/project_templates/calm/domain/add_contact.yml +8 -2
  20. rasa/cli/project_templates/calm/domain/list_contacts.yml +3 -0
  21. rasa/cli/project_templates/calm/domain/remove_contact.yml +9 -2
  22. rasa/cli/project_templates/calm/domain/shared.yml +5 -0
  23. rasa/cli/project_templates/calm/endpoints.yml +4 -4
  24. rasa/cli/project_templates/default/actions/actions.py +1 -1
  25. rasa/cli/project_templates/default/config.yml +5 -5
  26. rasa/cli/project_templates/default/credentials.yml +1 -1
  27. rasa/cli/project_templates/default/endpoints.yml +4 -4
  28. rasa/cli/project_templates/default/tests/test_stories.yml +1 -1
  29. rasa/cli/project_templates/tutorial/config.yml +1 -1
  30. rasa/cli/project_templates/tutorial/credentials.yml +1 -1
  31. rasa/cli/project_templates/tutorial/data/patterns.yml +6 -0
  32. rasa/cli/project_templates/tutorial/domain.yml +4 -0
  33. rasa/cli/project_templates/tutorial/endpoints.yml +6 -6
  34. rasa/cli/run.py +0 -1
  35. rasa/cli/scaffold.py +3 -2
  36. rasa/cli/studio/download.py +11 -0
  37. rasa/cli/studio/studio.py +180 -24
  38. rasa/cli/studio/upload.py +0 -8
  39. rasa/cli/telemetry.py +18 -6
  40. rasa/cli/utils.py +21 -10
  41. rasa/cli/x.py +3 -2
  42. rasa/core/actions/action.py +90 -315
  43. rasa/core/actions/action_exceptions.py +24 -0
  44. rasa/core/actions/constants.py +3 -0
  45. rasa/core/actions/custom_action_executor.py +188 -0
  46. rasa/core/actions/forms.py +11 -7
  47. rasa/core/actions/grpc_custom_action_executor.py +251 -0
  48. rasa/core/actions/http_custom_action_executor.py +140 -0
  49. rasa/core/actions/loops.py +3 -0
  50. rasa/core/actions/two_stage_fallback.py +1 -1
  51. rasa/core/agent.py +2 -4
  52. rasa/core/brokers/pika.py +1 -2
  53. rasa/core/channels/audiocodes.py +1 -1
  54. rasa/core/channels/botframework.py +0 -1
  55. rasa/core/channels/callback.py +0 -1
  56. rasa/core/channels/console.py +6 -8
  57. rasa/core/channels/development_inspector.py +1 -1
  58. rasa/core/channels/facebook.py +0 -3
  59. rasa/core/channels/hangouts.py +0 -6
  60. rasa/core/channels/inspector/dist/assets/{arc-5623b6dc.js → arc-b6e548fe.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-685c106a.js → c4Diagram-d0fbc5ce-fa03ac9e.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-8cbed007.js → classDiagram-936ed81e-ee67392a.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-5889cf12.js → classDiagram-v2-c3cb15f1-9b283fae.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-24c249d7.js → createText-62fc7601-8b6fcc2a.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-7dd06a75.js → edges-f2ad444c-22e77f4f.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-62c1e54c.js → erDiagram-9d236eb7-60ffc87f.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-ce49b86f.js → flowDb-1972c806-9dd802e4.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4067e48f.js → flowDiagram-7ea5b25a-5fa1912f.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +1 -0
  70. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-59fe4051.js → flowchart-elk-definition-abe16c3d-622a1fd2.js} +1 -1
  71. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-47e3a43b.js → ganttDiagram-9b5ea136-e285a63a.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-5a2ac0d9.js → gitGraphDiagram-99d0ae7c-f237bdca.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-dfb8efc4.js → index-2c4b9a3b-4b03d70e.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/{index-268a75c0.js → index-a5d3e69d.js} +4 -4
  75. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-b0c470f2.js → infoDiagram-736b4530-72a0fa5f.js} +1 -1
  76. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-2edb829a.js → journeyDiagram-df861f2b-82218c41.js} +1 -1
  77. rasa/core/channels/inspector/dist/assets/{layout-b6873d69.js → layout-78cff630.js} +1 -1
  78. rasa/core/channels/inspector/dist/assets/{line-1efc5781.js → line-5038b469.js} +1 -1
  79. rasa/core/channels/inspector/dist/assets/{linear-661e9b94.js → linear-c4fc4098.js} +1 -1
  80. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2d2e727f.js → mindmap-definition-beec6740-c33c8ea6.js} +1 -1
  81. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-9d3ea93d.js → pieDiagram-dbbf0591-a8d03059.js} +1 -1
  82. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-06a178a2.js → quadrantDiagram-4d7f4fd6-6a0e56b2.js} +1 -1
  83. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-0bfedffc.js → requirementDiagram-6fc4c22a-2dc7c7bd.js} +1 -1
  84. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-d76d0a04.js → sankeyDiagram-8f13d901-2360fe39.js} +1 -1
  85. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-37bb4341.js → sequenceDiagram-b655622a-41b9f9ad.js} +1 -1
  86. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-f52f7f57.js → stateDiagram-59f0c015-0aad326f.js} +1 -1
  87. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-4a986a20.js → stateDiagram-v2-2b26beab-9847d984.js} +1 -1
  88. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-7dd9ae12.js → styles-080da4f6-564d890e.js} +1 -1
  89. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-46e1ca14.js → styles-3dcbcfbf-38957613.js} +1 -1
  90. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-4a97439a.js → styles-9c745c82-f0fc6921.js} +1 -1
  91. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-823917a3.js → svgDrawCommon-4835440b-ef3c5a77.js} +1 -1
  92. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-9ea72896.js → timeline-definition-5b62e21b-bf3e91c1.js} +1 -1
  93. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-b631a8b6.js → xychartDiagram-2b33534f-4d4026c0.js} +1 -1
  94. rasa/core/channels/inspector/dist/index.html +1 -1
  95. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +10 -0
  96. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -7
  97. rasa/core/channels/inspector/src/helpers/formatters.ts +3 -2
  98. rasa/core/channels/rest.py +36 -21
  99. rasa/core/channels/rocketchat.py +0 -1
  100. rasa/core/channels/socketio.py +1 -1
  101. rasa/core/channels/telegram.py +3 -3
  102. rasa/core/channels/webexteams.py +0 -1
  103. rasa/core/concurrent_lock_store.py +1 -1
  104. rasa/core/evaluation/marker_base.py +1 -3
  105. rasa/core/evaluation/marker_stats.py +1 -2
  106. rasa/core/featurizers/single_state_featurizer.py +2 -4
  107. rasa/core/featurizers/tracker_featurizers.py +0 -7
  108. rasa/core/information_retrieval/__init__.py +7 -0
  109. rasa/core/information_retrieval/faiss.py +9 -4
  110. rasa/core/information_retrieval/information_retrieval.py +64 -7
  111. rasa/core/information_retrieval/milvus.py +7 -14
  112. rasa/core/information_retrieval/qdrant.py +8 -15
  113. rasa/core/lock_store.py +0 -1
  114. rasa/core/migrate.py +1 -2
  115. rasa/core/nlg/callback.py +3 -4
  116. rasa/core/policies/enterprise_search_policy.py +86 -22
  117. rasa/core/policies/enterprise_search_prompt_template.jinja2 +4 -41
  118. rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +60 -0
  119. rasa/core/policies/flows/flow_executor.py +104 -2
  120. rasa/core/policies/intentless_policy.py +7 -9
  121. rasa/core/policies/memoization.py +3 -3
  122. rasa/core/policies/policy.py +18 -9
  123. rasa/core/policies/rule_policy.py +8 -11
  124. rasa/core/policies/ted_policy.py +28 -30
  125. rasa/core/policies/unexpected_intent_policy.py +1 -2
  126. rasa/core/processor.py +136 -47
  127. rasa/core/run.py +41 -25
  128. rasa/core/secrets_manager/endpoints.py +2 -2
  129. rasa/core/secrets_manager/vault.py +6 -8
  130. rasa/core/test.py +3 -5
  131. rasa/core/tracker_store.py +49 -14
  132. rasa/core/train.py +1 -3
  133. rasa/core/training/interactive.py +9 -6
  134. rasa/core/utils.py +5 -10
  135. rasa/dialogue_understanding/coexistence/intent_based_router.py +11 -4
  136. rasa/dialogue_understanding/coexistence/llm_based_router.py +2 -3
  137. rasa/dialogue_understanding/commands/__init__.py +4 -0
  138. rasa/dialogue_understanding/commands/can_not_handle_command.py +9 -0
  139. rasa/dialogue_understanding/commands/cancel_flow_command.py +9 -0
  140. rasa/dialogue_understanding/commands/change_flow_command.py +38 -0
  141. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +9 -0
  142. rasa/dialogue_understanding/commands/clarify_command.py +9 -0
  143. rasa/dialogue_understanding/commands/correct_slots_command.py +9 -0
  144. rasa/dialogue_understanding/commands/error_command.py +12 -0
  145. rasa/dialogue_understanding/commands/handle_code_change_command.py +9 -0
  146. rasa/dialogue_understanding/commands/human_handoff_command.py +9 -0
  147. rasa/dialogue_understanding/commands/knowledge_answer_command.py +9 -0
  148. rasa/dialogue_understanding/commands/noop_command.py +9 -0
  149. rasa/dialogue_understanding/commands/set_slot_command.py +34 -3
  150. rasa/dialogue_understanding/commands/skip_question_command.py +9 -0
  151. rasa/dialogue_understanding/commands/start_flow_command.py +9 -0
  152. rasa/dialogue_understanding/generator/__init__.py +16 -1
  153. rasa/dialogue_understanding/generator/command_generator.py +92 -6
  154. rasa/dialogue_understanding/generator/constants.py +18 -0
  155. rasa/dialogue_understanding/generator/flow_retrieval.py +7 -5
  156. rasa/dialogue_understanding/generator/llm_based_command_generator.py +467 -0
  157. rasa/dialogue_understanding/generator/llm_command_generator.py +39 -609
  158. rasa/dialogue_understanding/generator/multi_step/__init__.py +0 -0
  159. rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2 +62 -0
  160. rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2 +38 -0
  161. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +827 -0
  162. rasa/dialogue_understanding/generator/nlu_command_adapter.py +69 -8
  163. rasa/dialogue_understanding/generator/single_step/__init__.py +0 -0
  164. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +345 -0
  165. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +44 -39
  166. rasa/dialogue_understanding/processor/command_processor.py +111 -3
  167. rasa/e2e_test/constants.py +1 -0
  168. rasa/e2e_test/e2e_test_case.py +44 -0
  169. rasa/e2e_test/e2e_test_runner.py +114 -11
  170. rasa/e2e_test/e2e_test_schema.yml +18 -0
  171. rasa/engine/caching.py +0 -1
  172. rasa/engine/graph.py +18 -6
  173. rasa/engine/recipes/config_files/default_config.yml +3 -3
  174. rasa/engine/recipes/default_components.py +1 -1
  175. rasa/engine/recipes/default_recipe.py +4 -5
  176. rasa/engine/recipes/recipe.py +1 -1
  177. rasa/engine/runner/dask.py +3 -9
  178. rasa/engine/storage/local_model_storage.py +0 -2
  179. rasa/engine/validation.py +179 -145
  180. rasa/exceptions.py +2 -2
  181. rasa/graph_components/validators/default_recipe_validator.py +3 -5
  182. rasa/hooks.py +0 -1
  183. rasa/model.py +1 -1
  184. rasa/model_training.py +1 -0
  185. rasa/nlu/classifiers/diet_classifier.py +8 -14
  186. rasa/nlu/extractors/crf_entity_extractor.py +4 -4
  187. rasa/nlu/extractors/duckling_entity_extractor.py +1 -1
  188. rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +1 -5
  189. rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +0 -4
  190. rasa/nlu/featurizers/featurizer.py +1 -1
  191. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +2 -4
  192. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +9 -12
  193. rasa/nlu/persistor.py +68 -26
  194. rasa/nlu/selectors/response_selector.py +7 -10
  195. rasa/nlu/test.py +0 -3
  196. rasa/nlu/utils/hugging_face/registry.py +1 -1
  197. rasa/nlu/utils/spacy_utils.py +1 -3
  198. rasa/server.py +22 -7
  199. rasa/shared/constants.py +12 -1
  200. rasa/shared/core/command_payload_reader.py +109 -0
  201. rasa/shared/core/constants.py +4 -5
  202. rasa/shared/core/domain.py +57 -56
  203. rasa/shared/core/events.py +4 -7
  204. rasa/shared/core/flows/flow.py +9 -0
  205. rasa/shared/core/flows/flows_list.py +12 -0
  206. rasa/shared/core/flows/steps/action.py +7 -2
  207. rasa/shared/core/generator.py +12 -11
  208. rasa/shared/core/slot_mappings.py +315 -24
  209. rasa/shared/core/slots.py +4 -2
  210. rasa/shared/core/trackers.py +32 -14
  211. rasa/shared/core/training_data/loading.py +0 -1
  212. rasa/shared/core/training_data/story_reader/story_reader.py +3 -3
  213. rasa/shared/core/training_data/story_reader/yaml_story_reader.py +11 -11
  214. rasa/shared/core/training_data/story_writer/yaml_story_writer.py +5 -3
  215. rasa/shared/core/training_data/structures.py +1 -1
  216. rasa/shared/core/training_data/visualization.py +1 -1
  217. rasa/shared/data.py +58 -1
  218. rasa/shared/exceptions.py +36 -2
  219. rasa/shared/importers/importer.py +1 -2
  220. rasa/shared/importers/rasa.py +0 -1
  221. rasa/shared/nlu/constants.py +2 -0
  222. rasa/shared/nlu/training_data/entities_parser.py +1 -2
  223. rasa/shared/nlu/training_data/formats/dialogflow.py +3 -2
  224. rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -5
  225. rasa/shared/nlu/training_data/formats/readerwriter.py +0 -1
  226. rasa/shared/nlu/training_data/message.py +13 -0
  227. rasa/shared/nlu/training_data/training_data.py +0 -2
  228. rasa/shared/providers/openai/session_handler.py +2 -2
  229. rasa/shared/utils/constants.py +3 -0
  230. rasa/shared/utils/io.py +11 -0
  231. rasa/shared/utils/llm.py +1 -2
  232. rasa/shared/utils/pykwalify_extensions.py +1 -0
  233. rasa/shared/utils/schemas/domain.yml +3 -0
  234. rasa/shared/utils/yaml.py +44 -35
  235. rasa/studio/auth.py +26 -10
  236. rasa/studio/constants.py +2 -0
  237. rasa/studio/data_handler.py +114 -107
  238. rasa/studio/download.py +160 -27
  239. rasa/studio/results_logger.py +137 -0
  240. rasa/studio/train.py +6 -7
  241. rasa/studio/upload.py +159 -134
  242. rasa/telemetry.py +188 -34
  243. rasa/tracing/config.py +18 -3
  244. rasa/tracing/constants.py +26 -2
  245. rasa/tracing/instrumentation/attribute_extractors.py +50 -41
  246. rasa/tracing/instrumentation/instrumentation.py +290 -44
  247. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +7 -5
  248. rasa/tracing/instrumentation/metrics.py +109 -21
  249. rasa/tracing/metric_instrument_provider.py +83 -3
  250. rasa/utils/cli.py +2 -1
  251. rasa/utils/common.py +1 -1
  252. rasa/utils/endpoints.py +1 -2
  253. rasa/utils/io.py +6 -6
  254. rasa/utils/licensing.py +246 -31
  255. rasa/utils/ml_utils.py +1 -1
  256. rasa/utils/tensorflow/data_generator.py +1 -1
  257. rasa/utils/tensorflow/environment.py +1 -1
  258. rasa/utils/tensorflow/model_data.py +9 -11
  259. rasa/utils/tensorflow/model_data_utils.py +499 -500
  260. rasa/utils/tensorflow/models.py +5 -6
  261. rasa/utils/tensorflow/rasa_layers.py +15 -15
  262. rasa/utils/train_utils.py +1 -1
  263. rasa/utils/url_tools.py +53 -0
  264. rasa/validator.py +305 -3
  265. rasa/version.py +1 -1
  266. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/METADATA +22 -22
  267. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/RECORD +271 -253
  268. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-85583a23.js +0 -1
  269. /rasa/dialogue_understanding/generator/{command_prompt_template.jinja2 → single_step/command_prompt_template.jinja2} +0 -0
  270. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/NOTICE +0 -0
  271. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/WHEEL +0 -0
  272. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/entry_points.txt +0 -0
@@ -419,9 +419,9 @@ class AugmentedMemoizationPolicy(MemoizationPolicy):
419
419
  logger.debug("Launch DeLorean...")
420
420
 
421
421
  # Truncate the tracker based on `max_history`
422
- truncated_tracker: Optional[
423
- DialogueStateTracker
424
- ] = _trim_tracker_by_max_history(tracker, self.config[POLICY_MAX_HISTORY])
422
+ truncated_tracker: Optional[DialogueStateTracker] = (
423
+ _trim_tracker_by_max_history(tracker, self.config[POLICY_MAX_HISTORY])
424
+ )
425
425
  truncated_tracker = self._strip_leading_events_until_action_executed(
426
426
  truncated_tracker
427
427
  )
@@ -1,12 +1,10 @@
1
1
  from __future__ import annotations
2
+
2
3
  import abc
3
4
  import copy
4
5
  import logging
5
6
  from enum import Enum
6
7
  from pathlib import Path
7
-
8
- from rasa.shared.constants import ROUTE_TO_CALM_SLOT
9
- from rasa.shared.core.events import Event
10
8
  from typing import (
11
9
  Any,
12
10
  List,
@@ -21,6 +19,8 @@ from typing import (
21
19
 
22
20
  import numpy as np
23
21
 
22
+ from rasa.shared.constants import ROUTE_TO_CALM_SLOT
23
+ from rasa.shared.core.events import Event
24
24
  from rasa.engine.graph import GraphComponent, ExecutionContext
25
25
  from rasa.engine.storage.resource import Resource
26
26
  from rasa.engine.storage.storage import ModelStorage
@@ -40,14 +40,12 @@ from rasa.core.constants import (
40
40
  from rasa.shared.core.constants import USER, SLOTS, PREVIOUS_ACTION, ACTIVE_LOOP
41
41
  import rasa.shared.utils.common
42
42
 
43
-
44
43
  if TYPE_CHECKING:
45
44
  from rasa.shared.nlu.training_data.features import Features
46
45
  from rasa.core.featurizers.tracker_featurizers import TrackerFeaturizer
47
46
  from rasa.core.featurizers.tracker_featurizers import MaxHistoryTrackerFeaturizer
48
47
  from rasa.dialogue_understanding.stack.frames import DialogueStackFrame
49
48
 
50
-
51
49
  logger = logging.getLogger(__name__)
52
50
 
53
51
  TrackerListTypeVar = TypeVar(
@@ -137,10 +135,20 @@ class Policy(GraphComponent):
137
135
  def should_abstain_in_coexistence(
138
136
  self, tracker: DialogueStateTracker, is_calm_policy: bool
139
137
  ) -> bool:
140
- """Whether a policy should abstain making predictions in coexistence."""
138
+ """Whether a policy should abstain making predictions in coexistence.
139
+
140
+ A calm policy should run when the routing slot is set to True.
141
+ A nlu-based policy should run when the routing slot is set to False or None.
142
+ """
143
+ if is_calm_policy:
144
+ return tracker.has_coexistence_routing_slot and (
145
+ tracker.get_slot(ROUTE_TO_CALM_SLOT) is False
146
+ or tracker.get_slot(ROUTE_TO_CALM_SLOT) is None
147
+ )
148
+
141
149
  return (
142
150
  tracker.has_coexistence_routing_slot
143
- and tracker.get_slot(ROUTE_TO_CALM_SLOT) != is_calm_policy
151
+ and tracker.get_slot(ROUTE_TO_CALM_SLOT) is True
144
152
  )
145
153
 
146
154
  def __init__(
@@ -299,8 +307,9 @@ class Policy(GraphComponent):
299
307
  max_training_samples = kwargs.get("max_training_samples")
300
308
  if max_training_samples is not None:
301
309
  logger.debug(
302
- "Limit training data to {} training samples."
303
- "".format(max_training_samples)
310
+ "Limit training data to {} training samples.".format(
311
+ max_training_samples
312
+ )
304
313
  )
305
314
  state_features = state_features[:max_training_samples]
306
315
  label_ids = label_ids[:max_training_samples]
@@ -60,7 +60,7 @@ logger = logging.getLogger(__name__)
60
60
  structlogger = structlog.get_logger()
61
61
 
62
62
 
63
- # These are Rasa Open Source default actions and overrule everything at any time.
63
+ # These are Rasa Pro default actions and overrule everything at any time.
64
64
  DEFAULT_ACTION_MAPPINGS = {
65
65
  USER_INTENT_RESTART: ACTION_RESTART_NAME,
66
66
  USER_INTENT_BACK: ACTION_BACK_NAME,
@@ -271,15 +271,13 @@ class RulePolicy(MemoizationPolicy):
271
271
  if (
272
272
  # loop is predicted after action_listen in unhappy path,
273
273
  # therefore no validation is needed
274
- is_prev_action_listen_in_state(states[-1])
275
- and action == active_loop
274
+ is_prev_action_listen_in_state(states[-1]) and action == active_loop
276
275
  ):
277
276
  lookup[feature_key] = LOOP_WAS_INTERRUPTED
278
277
  elif (
279
278
  # some action other than active_loop is predicted in unhappy path,
280
279
  # therefore active_loop shouldn't be predicted by the rule
281
- not is_prev_action_listen_in_state(states[-1])
282
- and action != active_loop
280
+ not is_prev_action_listen_in_state(states[-1]) and action != active_loop
283
281
  ):
284
282
  lookup[feature_key] = DO_NOT_PREDICT_LOOP_ACTION
285
283
  return lookup
@@ -777,10 +775,10 @@ class RulePolicy(MemoizationPolicy):
777
775
  trackers_as_actions = rule_trackers_as_actions + story_trackers_as_actions
778
776
 
779
777
  # negative rules are not anti-rules, they are auxiliary to actual rules
780
- self.lookup[
781
- RULES_FOR_LOOP_UNHAPPY_PATH
782
- ] = self._create_loop_unhappy_lookup_from_states(
783
- trackers_as_states, trackers_as_actions
778
+ self.lookup[RULES_FOR_LOOP_UNHAPPY_PATH] = (
779
+ self._create_loop_unhappy_lookup_from_states(
780
+ trackers_as_states, trackers_as_actions
781
+ )
784
782
  )
785
783
 
786
784
  def train(
@@ -955,7 +953,6 @@ class RulePolicy(MemoizationPolicy):
955
953
  def _find_action_from_loop_happy_path(
956
954
  tracker: DialogueStateTracker,
957
955
  ) -> Tuple[Optional[Text], Optional[Text]]:
958
-
959
956
  active_loop_name = tracker.active_loop_name
960
957
  if active_loop_name is None:
961
958
  return None, None
@@ -1132,7 +1129,7 @@ class RulePolicy(MemoizationPolicy):
1132
1129
  tracker, domain, use_text_for_last_user_input=True
1133
1130
  )
1134
1131
 
1135
- # Rasa Open Source default actions overrule anything. If users want to achieve
1132
+ # Rasa Pro default actions overrule anything. If users want to achieve
1136
1133
  # the same, they need to write a rule or make sure that their loop rejects
1137
1134
  # accordingly.
1138
1135
  (
@@ -468,7 +468,7 @@ class TEDPolicy(Policy):
468
468
 
469
469
  @staticmethod
470
470
  def _should_extract_entities(
471
- entity_tags: List[List[Dict[Text, List[Features]]]]
471
+ entity_tags: List[List[Dict[Text, List[Features]]]],
472
472
  ) -> bool:
473
473
  for turns_tags in entity_tags:
474
474
  for turn_tags in turns_tags:
@@ -1092,7 +1092,7 @@ class TEDPolicy(Policy):
1092
1092
 
1093
1093
  model = None
1094
1094
 
1095
- with (contextlib.nullcontext() if config["use_gpu"] else tf.device("/cpu:0")):
1095
+ with contextlib.nullcontext() if config["use_gpu"] else tf.device("/cpu:0"):
1096
1096
  model = cls._load_tf_model(
1097
1097
  model_utilities,
1098
1098
  model_data_example,
@@ -1266,19 +1266,19 @@ class TED(TransformerRasaModel):
1266
1266
  )
1267
1267
  self._prepare_encoding_layers(name)
1268
1268
 
1269
- self._tf_layers[
1270
- f"transformer.{DIALOGUE}"
1271
- ] = rasa_layers.prepare_transformer_layer(
1272
- attribute_name=DIALOGUE,
1273
- config=self.config,
1274
- num_layers=self.config[NUM_TRANSFORMER_LAYERS][DIALOGUE],
1275
- units=self.config[TRANSFORMER_SIZE][DIALOGUE],
1276
- drop_rate=self.config[DROP_RATE_DIALOGUE],
1277
- # use bidirectional transformer, because
1278
- # we will invert dialogue sequence so that the last turn is located
1279
- # at the first position and would always have
1280
- # exactly the same positional encoding
1281
- unidirectional=not self.max_history_featurizer_is_used,
1269
+ self._tf_layers[f"transformer.{DIALOGUE}"] = (
1270
+ rasa_layers.prepare_transformer_layer(
1271
+ attribute_name=DIALOGUE,
1272
+ config=self.config,
1273
+ num_layers=self.config[NUM_TRANSFORMER_LAYERS][DIALOGUE],
1274
+ units=self.config[TRANSFORMER_SIZE][DIALOGUE],
1275
+ drop_rate=self.config[DROP_RATE_DIALOGUE],
1276
+ # use bidirectional transformer, because
1277
+ # we will invert dialogue sequence so that the last turn is located
1278
+ # at the first position and would always have
1279
+ # exactly the same positional encoding
1280
+ unidirectional=not self.max_history_featurizer_is_used,
1281
+ )
1282
1282
  )
1283
1283
 
1284
1284
  self._prepare_label_classification_layers(DIALOGUE)
@@ -1308,23 +1308,23 @@ class TED(TransformerRasaModel):
1308
1308
  # Attributes with sequence-level features also have sentence-level features,
1309
1309
  # all these need to be combined and further processed.
1310
1310
  if attribute_name in SEQUENCE_FEATURES_TO_ENCODE:
1311
- self._tf_layers[
1312
- f"sequence_layer.{attribute_name}"
1313
- ] = rasa_layers.RasaSequenceLayer(
1314
- attribute_name, attribute_signature, config_to_use
1311
+ self._tf_layers[f"sequence_layer.{attribute_name}"] = (
1312
+ rasa_layers.RasaSequenceLayer(
1313
+ attribute_name, attribute_signature, config_to_use
1314
+ )
1315
1315
  )
1316
1316
  # Attributes without sequence-level features require some actual feature
1317
1317
  # processing only if they have sentence-level features. Attributes with no
1318
1318
  # sequence- and sentence-level features (dialogue, entity_tags, label) are
1319
1319
  # skipped here.
1320
1320
  elif SENTENCE in attribute_signature:
1321
- self._tf_layers[
1322
- f"sparse_dense_concat_layer.{attribute_name}"
1323
- ] = rasa_layers.ConcatenateSparseDenseFeatures(
1324
- attribute=attribute_name,
1325
- feature_type=SENTENCE,
1326
- feature_type_signature=attribute_signature[SENTENCE],
1327
- config=config_to_use,
1321
+ self._tf_layers[f"sparse_dense_concat_layer.{attribute_name}"] = (
1322
+ rasa_layers.ConcatenateSparseDenseFeatures(
1323
+ attribute=attribute_name,
1324
+ feature_type=SENTENCE,
1325
+ feature_type_signature=attribute_signature[SENTENCE],
1326
+ config=config_to_use,
1327
+ )
1328
1328
  )
1329
1329
 
1330
1330
  def _prepare_encoding_layers(self, name: Text) -> None:
@@ -1360,7 +1360,7 @@ class TED(TransformerRasaModel):
1360
1360
 
1361
1361
  @staticmethod
1362
1362
  def _compute_dialogue_indices(
1363
- tf_batch_data: Dict[Text, Dict[Text, List[tf.Tensor]]]
1363
+ tf_batch_data: Dict[Text, Dict[Text, List[tf.Tensor]]],
1364
1364
  ) -> None:
1365
1365
  dialogue_lengths = tf.cast(tf_batch_data[DIALOGUE][LENGTH][0], dtype=tf.int32)
1366
1366
  # wrap in a list, because that's the structure of tf_batch_data
@@ -1399,7 +1399,7 @@ class TED(TransformerRasaModel):
1399
1399
 
1400
1400
  @staticmethod
1401
1401
  def _collect_label_attribute_encodings(
1402
- all_labels_encoded: Dict[Text, tf.Tensor]
1402
+ all_labels_encoded: Dict[Text, tf.Tensor],
1403
1403
  ) -> tf.Tensor:
1404
1404
  # Initialize with at least one attribute first
1405
1405
  # so that the subsequent TF ops are simplified.
@@ -1928,7 +1928,6 @@ class TED(TransformerRasaModel):
1928
1928
  text_output: tf.Tensor,
1929
1929
  text_sequence_lengths: tf.Tensor,
1930
1930
  ) -> tf.Tensor:
1931
-
1932
1931
  text_transformed, text_mask, text_sequence_lengths = self._reshape_for_entities(
1933
1932
  tf_batch_data,
1934
1933
  dialogue_transformer_output,
@@ -2131,7 +2130,6 @@ class TED(TransformerRasaModel):
2131
2130
  text_output: tf.Tensor,
2132
2131
  text_sequence_lengths: tf.Tensor,
2133
2132
  ) -> Tuple[tf.Tensor, tf.Tensor]:
2134
-
2135
2133
  text_transformed, _, text_sequence_lengths = self._reshape_for_entities(
2136
2134
  tf_batch_data,
2137
2135
  dialogue_transformer_output,
@@ -794,7 +794,6 @@ class UnexpecTEDIntentPolicy(TEDPolicy):
794
794
  }
795
795
 
796
796
  for index, all_pos_labels in enumerate(label_ids):
797
-
798
797
  for candidate_label_id in unique_label_ids:
799
798
  if candidate_label_id in all_pos_labels:
800
799
  label_id_scores[candidate_label_id][POSITIVE_SCORES_KEY].append(
@@ -809,7 +808,7 @@ class UnexpecTEDIntentPolicy(TEDPolicy):
809
808
 
810
809
  @staticmethod
811
810
  def _compute_label_quantiles(
812
- label_id_scores: Dict[int, Dict[Text, List[float]]]
811
+ label_id_scores: Dict[int, Dict[Text, List[float]]],
813
812
  ) -> Dict[int, List[float]]:
814
813
  """Computes multiple quantiles for each label id.
815
814
 
rasa/core/processor.py CHANGED
@@ -3,15 +3,21 @@ import copy
3
3
  import logging
4
4
  import structlog
5
5
  import os
6
- import re
7
6
  from pathlib import Path
8
7
  import tarfile
9
8
  import time
10
9
  from types import LambdaType
11
10
  from typing import Any, Dict, List, Optional, TYPE_CHECKING, Text, Tuple, Union
12
11
 
12
+ from rasa.core.actions.action_exceptions import ActionExecutionRejection
13
+ from rasa.core.actions.forms import FormAction
13
14
  from rasa.core.http_interpreter import RasaNLUHttpInterpreter
14
- from rasa.dialogue_understanding.commands import SetSlotCommand, CannotHandleCommand
15
+ from rasa.dialogue_understanding.commands import (
16
+ Command,
17
+ NoopCommand,
18
+ SetSlotCommand,
19
+ CannotHandleCommand,
20
+ )
15
21
  from rasa.engine import loader
16
22
  from rasa.engine.constants import (
17
23
  PLACEHOLDER_MESSAGE,
@@ -24,7 +30,7 @@ from rasa.engine.storage.storage import ModelMetadata
24
30
  from rasa.model import get_latest_model
25
31
  from rasa.plugin import plugin_manager
26
32
  from rasa.shared.core.flows import FlowsList
27
- from rasa.shared.data import TrainingType
33
+ from rasa.shared.data import TrainingType, create_regex_pattern_reader
28
34
  import rasa.shared.utils.io
29
35
  import rasa.core.actions.action
30
36
  from rasa.core import jobs
@@ -73,9 +79,6 @@ import rasa.core.tracker_store
73
79
  import rasa.core.actions.action
74
80
  import rasa.shared.core.trackers
75
81
  from rasa.shared.core.trackers import DialogueStateTracker, EventVerbosity
76
- from rasa.shared.core.training_data.story_reader.yaml_story_reader import (
77
- YAMLStoryReader,
78
- )
79
82
  from rasa.shared.nlu.constants import (
80
83
  COMMANDS,
81
84
  ENTITIES,
@@ -143,7 +146,7 @@ class MessageProcessor:
143
146
 
144
147
  @staticmethod
145
148
  def _load_model(
146
- model_path: Union[Text, Path]
149
+ model_path: Union[Text, Path],
147
150
  ) -> Tuple[Text, ModelMetadata, GraphRunner]:
148
151
  """Unpacks a model from a given path using the graph model loader."""
149
152
  try:
@@ -185,7 +188,10 @@ class MessageProcessor:
185
188
  )
186
189
  return None
187
190
 
188
- tracker = await self.run_action_extract_slots(message.output_channel, tracker)
191
+ if not self.message_contains_commands(tracker.latest_message):
192
+ tracker = await self.run_action_extract_slots(
193
+ message.output_channel, tracker
194
+ )
189
195
 
190
196
  await self._run_prediction_loop(message.output_channel, tracker)
191
197
 
@@ -213,8 +219,10 @@ class MessageProcessor:
213
219
  action_extract_slots = rasa.core.actions.action.action_for_name_or_text(
214
220
  ACTION_EXTRACT_SLOTS, self.domain, self.action_endpoint
215
221
  )
222
+ metadata = await self._add_flows_to_metadata()
223
+
216
224
  extraction_events = await action_extract_slots.run(
217
- output_channel, self.nlg, tracker, self.domain
225
+ output_channel, self.nlg, tracker, self.domain, metadata
218
226
  )
219
227
 
220
228
  await self._send_bot_messages(extraction_events, tracker, output_channel)
@@ -735,28 +743,37 @@ class MessageProcessor:
735
743
  if self.http_interpreter:
736
744
  parse_data = await self.http_interpreter.parse(message)
737
745
  else:
738
- msg = YAMLStoryReader.unpack_regex_message(
739
- message=Message({TEXT: message.text}), domain=self.domain
740
- )
746
+ regex_reader = create_regex_pattern_reader(message, self.domain)
741
747
 
742
- # Invalid use of slash syntax, sanitize the message before passing
743
- # it to the graph
748
+ processed_message = Message({TEXT: message.text})
749
+ if regex_reader:
750
+ processed_message = regex_reader.unpack_regex_message(
751
+ message=processed_message, domain=self.domain
752
+ )
753
+
754
+ # Invalid use of slash syntax
744
755
  if (
745
- msg.data.get(TEXT, "").strip().startswith("/")
746
- and msg.data.get(INTENT) is None
756
+ processed_message.starts_with_slash_syntax()
757
+ and not processed_message.has_intent()
758
+ and not processed_message.has_commands()
747
759
  ):
748
- message = self._sanitize_message(message)
760
+ parse_data = self._parse_invalid_use_of_slash_syntax(
761
+ processed_message, tracker, only_output_properties
762
+ )
749
763
 
750
- # No intent trigger used. Pass message to graph.
751
- if msg.data.get(INTENT) is None:
764
+ # Intent or commands are not explicitly present. Pass message to graph.
765
+ elif not (
766
+ processed_message.has_intent() or processed_message.has_commands()
767
+ ):
752
768
  parse_data = await self._parse_message_with_graph(
753
769
  message, tracker, only_output_properties
754
770
  )
755
771
 
756
- # Intent trigger is used.
772
+ # Intents or commands are presents. Bypasses the standard parsing
773
+ # pipeline.
757
774
  else:
758
- parse_data = await self._parse_message_with_intent_trigger(
759
- msg, tracker, only_output_properties
775
+ parse_data = await self._parse_message_with_commands_and_intents(
776
+ processed_message, tracker, only_output_properties
760
777
  )
761
778
 
762
779
  self._update_full_retrieval_intent(parse_data)
@@ -771,36 +788,68 @@ class MessageProcessor:
771
788
 
772
789
  return parse_data
773
790
 
774
- def _sanitize_message(self, message: UserMessage) -> UserMessage:
775
- """Sanitize user message by removing prepended slashes before the
776
- actual content.
777
- """
778
- # Regex pattern to match leading slashes and any whitespace before
779
- # actual content
780
- pattern = r"^[/\s]+"
781
- # Remove the matched pattern from the beginning of the message
782
- message.text = re.sub(pattern, "", message.text).strip()
783
- return message
784
-
785
- async def _parse_message_with_intent_trigger(
791
+ def _parse_invalid_use_of_slash_syntax(
786
792
  self,
787
793
  message: Message,
788
794
  tracker: Optional[DialogueStateTracker] = None,
789
795
  only_output_properties: bool = True,
790
796
  ) -> Dict[Text, Any]:
791
- """Parses a message with intent triggers."""
797
+ structlogger.warning(
798
+ "processor.message.parse.invalid_use_of_slash_syntax",
799
+ event_info=(
800
+ "Message starts with '/', but no intents or commands are"
801
+ "passed. Returning CannotHandleCommand() as a fallback."
802
+ ),
803
+ message=message.get(TEXT),
804
+ )
792
805
  parse_data: Dict[Text, Any] = {
793
806
  TEXT: "",
794
807
  INTENT: {INTENT_NAME_KEY: None, PREDICTED_CONFIDENCE_KEY: 0.0},
795
808
  ENTITIES: [],
796
- COMMANDS: [],
797
809
  }
798
810
  parse_data.update(
799
811
  message.as_dict(only_output_properties=only_output_properties)
800
812
  )
813
+ commands = parse_data.get(COMMANDS, [])
814
+ commands += [
815
+ CannotHandleCommand(RASA_PATTERN_CANNOT_HANDLE_INVALID_INTENT).as_dict()
816
+ ]
817
+
818
+ if (
819
+ tracker is not None
820
+ and tracker.has_coexistence_routing_slot
821
+ and tracker.get_slot(ROUTE_TO_CALM_SLOT) is None
822
+ ):
823
+ # if we are currently not routing to either CALM or dm1
824
+ # we make a sticky routing to CALM
825
+ commands += [SetSlotCommand(ROUTE_TO_CALM_SLOT, True).as_dict()]
826
+
827
+ parse_data[COMMANDS] = commands
828
+ return parse_data
829
+
830
+ async def _parse_message_with_commands_and_intents(
831
+ self,
832
+ message: Message,
833
+ tracker: Optional[DialogueStateTracker] = None,
834
+ only_output_properties: bool = True,
835
+ ) -> Dict[Text, Any]:
836
+ """Parses the message to handle commands or intent trigger."""
837
+ parse_data: Dict[Text, Any] = {
838
+ TEXT: "",
839
+ INTENT: {INTENT_NAME_KEY: None, PREDICTED_CONFIDENCE_KEY: 0.0},
840
+ ENTITIES: [],
841
+ }
842
+ parse_data.update(
843
+ message.as_dict(only_output_properties=only_output_properties)
844
+ )
845
+
846
+ commands = parse_data.get(COMMANDS, [])
847
+
848
+ # add commands from intent payloads
849
+ if tracker and not commands:
850
+ nlu_adapted_commands = await self._nlu_to_commands(parse_data, tracker)
851
+ commands += nlu_adapted_commands
801
852
 
802
- if tracker:
803
- commands = await self._nlu_to_commands(parse_data, tracker)
804
853
  if (
805
854
  tracker.has_coexistence_routing_slot
806
855
  and tracker.get_slot(ROUTE_TO_CALM_SLOT) is None
@@ -809,11 +858,13 @@ class MessageProcessor:
809
858
  # we make a sticky routing to CALM if there are any commands
810
859
  # from the trigger intent parsing
811
860
  # or a sticky routing to dm1 if there are no commands
812
- commands.append(
813
- SetSlotCommand(ROUTE_TO_CALM_SLOT, len(commands) > 0).as_dict()
814
- )
815
- parse_data[COMMANDS] = commands
861
+ commands += [
862
+ SetSlotCommand(
863
+ ROUTE_TO_CALM_SLOT, len(nlu_adapted_commands) > 0
864
+ ).as_dict()
865
+ ]
816
866
 
867
+ parse_data[COMMANDS] = commands
817
868
  return parse_data
818
869
 
819
870
  def _update_full_retrieval_intent(self, parse_data: Dict[Text, Any]) -> None:
@@ -847,7 +898,7 @@ class MessageProcessor:
847
898
  )
848
899
 
849
900
  commands = NLUCommandAdapter.convert_nlu_to_commands(
850
- Message(parse_data), tracker, await self.get_flows()
901
+ Message(parse_data), tracker, await self.get_flows(), self.domain
851
902
  )
852
903
 
853
904
  # if there are no converted commands and parsed data contains invalid intent
@@ -912,7 +963,6 @@ class MessageProcessor:
912
963
  async def _handle_message_with_tracker(
913
964
  self, message: UserMessage, tracker: DialogueStateTracker
914
965
  ) -> None:
915
-
916
966
  if message.parse_data:
917
967
  parse_data = message.parse_data
918
968
  else:
@@ -1154,6 +1204,17 @@ class MessageProcessor:
1154
1204
  results = await self.graph_runner.run(inputs={}, targets=[target])
1155
1205
  return results[target]
1156
1206
 
1207
+ async def _add_flows_to_metadata(self) -> Dict[Text, Any]:
1208
+ """Convert the flows to metadata."""
1209
+ flows = await self.get_flows()
1210
+ flows_metadata = {}
1211
+ for flow in flows.underlying_flows:
1212
+ flow_as_json = flow.as_json()
1213
+ flow_as_json.pop("id")
1214
+ flows_metadata[flow.id] = flow_as_json
1215
+
1216
+ return {"all_flows": flows_metadata}
1217
+
1157
1218
  async def _run_action(
1158
1219
  self,
1159
1220
  action: rasa.core.actions.action.Action,
@@ -1172,18 +1233,25 @@ class MessageProcessor:
1172
1233
 
1173
1234
  run_args = inspect.getfullargspec(action.run).args
1174
1235
  if "metadata" in run_args:
1236
+ metadata: Optional[Dict] = prediction.action_metadata
1237
+
1238
+ if isinstance(action, FormAction):
1239
+ flows_metadata = await self._add_flows_to_metadata()
1240
+ metadata = prediction.action_metadata or {}
1241
+ metadata.update(flows_metadata)
1242
+
1175
1243
  events = await action.run(
1176
1244
  output_channel,
1177
1245
  nlg,
1178
1246
  temporary_tracker,
1179
1247
  self.domain,
1180
- metadata=prediction.action_metadata,
1248
+ metadata=metadata,
1181
1249
  )
1182
1250
  else:
1183
1251
  events = await action.run(
1184
1252
  output_channel, nlg, temporary_tracker, self.domain
1185
1253
  )
1186
- except rasa.core.actions.action.ActionExecutionRejection:
1254
+ except ActionExecutionRejection:
1187
1255
  events = [
1188
1256
  ActionExecutionRejected(
1189
1257
  action.name(), prediction.policy_name, prediction.max_confidence
@@ -1312,7 +1380,7 @@ class MessageProcessor:
1312
1380
 
1313
1381
  logger.error(
1314
1382
  f"Trying to run unknown follow-up action '{followup_action}'. "
1315
- "Instead of running that, Rasa Open Source will ignore the action "
1383
+ "Instead of running that, Rasa Pro will ignore the action "
1316
1384
  "and predict the next action."
1317
1385
  )
1318
1386
 
@@ -1329,3 +1397,24 @@ class MessageProcessor:
1329
1397
  )
1330
1398
  policy_prediction = results[target]
1331
1399
  return policy_prediction
1400
+
1401
+ @staticmethod
1402
+ def message_contains_commands(latest_message: Optional[UserUttered]) -> bool:
1403
+ """Check if the latest message contains commands."""
1404
+ if latest_message is None:
1405
+ return False
1406
+
1407
+ commands = [
1408
+ Command.command_from_json(command) for command in latest_message.commands
1409
+ ]
1410
+ filtered_commands = [
1411
+ command
1412
+ for command in commands
1413
+ if not (
1414
+ isinstance(command, SetSlotCommand)
1415
+ and command.name == ROUTE_TO_CALM_SLOT
1416
+ )
1417
+ and not isinstance(command, NoopCommand)
1418
+ ]
1419
+
1420
+ return len(filtered_commands) > 0