rasa-pro 3.14.0.dev20250922__py3-none-any.whl → 3.14.0rc1__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 (290) hide show
  1. rasa/__main__.py +15 -3
  2. rasa/agents/__init__.py +0 -0
  3. rasa/agents/agent_factory.py +122 -0
  4. rasa/agents/agent_manager.py +211 -0
  5. rasa/agents/constants.py +43 -0
  6. rasa/agents/core/__init__.py +0 -0
  7. rasa/agents/core/agent_protocol.py +107 -0
  8. rasa/agents/core/types.py +81 -0
  9. rasa/agents/exceptions.py +38 -0
  10. rasa/agents/protocol/__init__.py +5 -0
  11. rasa/agents/protocol/a2a/__init__.py +0 -0
  12. rasa/agents/protocol/a2a/a2a_agent.py +879 -0
  13. rasa/agents/protocol/mcp/__init__.py +0 -0
  14. rasa/agents/protocol/mcp/mcp_base_agent.py +726 -0
  15. rasa/agents/protocol/mcp/mcp_open_agent.py +327 -0
  16. rasa/agents/protocol/mcp/mcp_task_agent.py +522 -0
  17. rasa/agents/schemas/__init__.py +13 -0
  18. rasa/agents/schemas/agent_input.py +38 -0
  19. rasa/agents/schemas/agent_output.py +26 -0
  20. rasa/agents/schemas/agent_tool_result.py +65 -0
  21. rasa/agents/schemas/agent_tool_schema.py +186 -0
  22. rasa/agents/templates/__init__.py +0 -0
  23. rasa/agents/templates/mcp_open_agent_prompt_template.jinja2 +20 -0
  24. rasa/agents/templates/mcp_task_agent_prompt_template.jinja2 +22 -0
  25. rasa/agents/utils.py +206 -0
  26. rasa/agents/validation.py +485 -0
  27. rasa/api.py +24 -9
  28. rasa/builder/config.py +6 -2
  29. rasa/builder/guardrails/{lakera.py → clients.py} +55 -5
  30. rasa/builder/guardrails/constants.py +3 -0
  31. rasa/builder/guardrails/models.py +45 -10
  32. rasa/builder/guardrails/policy_checker.py +324 -0
  33. rasa/builder/guardrails/utils.py +42 -276
  34. rasa/builder/llm_service.py +32 -5
  35. rasa/builder/models.py +1 -0
  36. rasa/builder/project_generator.py +6 -1
  37. rasa/builder/service.py +16 -13
  38. rasa/builder/training_service.py +18 -24
  39. rasa/builder/validation_service.py +1 -1
  40. rasa/cli/arguments/default_arguments.py +12 -0
  41. rasa/cli/arguments/run.py +2 -0
  42. rasa/cli/arguments/train.py +2 -0
  43. rasa/cli/data.py +10 -8
  44. rasa/cli/dialogue_understanding_test.py +10 -7
  45. rasa/cli/e2e_test.py +9 -6
  46. rasa/cli/evaluate.py +4 -2
  47. rasa/cli/export.py +5 -2
  48. rasa/cli/inspect.py +8 -4
  49. rasa/cli/interactive.py +5 -4
  50. rasa/cli/llm_fine_tuning.py +11 -6
  51. rasa/cli/project_templates/tutorial/credentials.yml +10 -0
  52. rasa/cli/run.py +12 -10
  53. rasa/cli/scaffold.py +4 -4
  54. rasa/cli/shell.py +9 -5
  55. rasa/cli/studio/studio.py +1 -1
  56. rasa/cli/test.py +34 -14
  57. rasa/cli/train.py +41 -28
  58. rasa/cli/utils.py +1 -393
  59. rasa/cli/validation/__init__.py +0 -0
  60. rasa/cli/validation/bot_config.py +223 -0
  61. rasa/cli/validation/config_path_validation.py +257 -0
  62. rasa/cli/x.py +8 -4
  63. rasa/constants.py +7 -1
  64. rasa/core/actions/action.py +51 -10
  65. rasa/core/actions/grpc_custom_action_executor.py +1 -1
  66. rasa/core/agent.py +19 -2
  67. rasa/core/available_agents.py +229 -0
  68. rasa/core/channels/__init__.py +82 -35
  69. rasa/core/channels/development_inspector.py +3 -3
  70. rasa/core/channels/inspector/README.md +25 -13
  71. rasa/core/channels/inspector/dist/assets/{arc-35222594.js → arc-6177260a.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-a0efbfd3.js → blockDiagram-38ab4fdb-b054f038.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-0584c0f2.js → c4Diagram-3d4e48cf-f25427d5.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/channel-bf9cbb34.js +1 -0
  75. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-39f40dbe.js → classDiagram-70f12bd4-c7a2af53.js} +1 -1
  76. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-1ad755f3.js → classDiagram-v2-f2320105-58db65c0.js} +1 -1
  77. rasa/core/channels/inspector/dist/assets/clone-8f9083bb.js +1 -0
  78. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-b0f4f0fe.js → createText-2e5e7dd3-088372e2.js} +1 -1
  79. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-9039bff9.js → edges-e0da2a9e-58676240.js} +1 -1
  80. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-65c9b127.js → erDiagram-9861fffd-0c14d7c6.js} +1 -1
  81. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-4f08b38e.js → flowDb-956e92f1-ea63f85c.js} +1 -1
  82. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-e95c362a.js → flowDiagram-66a62f08-a2af48cd.js} +1 -1
  83. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-9ecd5b59.js +1 -0
  84. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-703c3015.js → flowchart-elk-definition-4a651766-6937abe7.js} +1 -1
  85. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-699328ea.js → ganttDiagram-c361ad54-7473f357.js} +1 -1
  86. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-04cf4b05.js → gitGraphDiagram-72cf32ee-d0c9405e.js} +1 -1
  87. rasa/core/channels/inspector/dist/assets/{graph-ee94449e.js → graph-0a6f8466.js} +1 -1
  88. rasa/core/channels/inspector/dist/assets/{index-3862675e-940162b4.js → index-3862675e-7610671a.js} +1 -1
  89. rasa/core/channels/inspector/dist/assets/index-74e01d94.js +1354 -0
  90. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-c79c2866.js → infoDiagram-f8f76790-be397dc7.js} +1 -1
  91. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-84489d30.js → journeyDiagram-49397b02-4cefbf62.js} +1 -1
  92. rasa/core/channels/inspector/dist/assets/{layout-a9aa9858.js → layout-e7fbc2bf.js} +1 -1
  93. rasa/core/channels/inspector/dist/assets/{line-eb73cf26.js → line-a8aa457c.js} +1 -1
  94. rasa/core/channels/inspector/dist/assets/{linear-b3399f9a.js → linear-3351e0d2.js} +1 -1
  95. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-b095bf1a.js → mindmap-definition-fc14e90a-b8cbf605.js} +1 -1
  96. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-07644b66.js → pieDiagram-8a3498a8-f327f774.js} +1 -1
  97. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-573a3f9c.js → quadrantDiagram-120e2f19-2854c591.js} +1 -1
  98. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-d457e1e1.js → requirementDiagram-deff3bca-964985d5.js} +1 -1
  99. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-9d26e1a2.js → sankeyDiagram-04a897e0-edeb4f33.js} +1 -1
  100. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3a9cde10.js → sequenceDiagram-704730f1-fcf70125.js} +1 -1
  101. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-4f3e8cec.js → stateDiagram-587899a1-0e770395.js} +1 -1
  102. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-e617e5bf.js → stateDiagram-v2-d93cdb3a-af8dcd22.js} +1 -1
  103. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-eab30d2f.js → styles-6aaf32cf-36a9e70d.js} +1 -1
  104. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-09994be2.js → styles-9a916d00-884a8b5b.js} +1 -1
  105. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-b7110364.js → styles-c10674c1-dc097813.js} +1 -1
  106. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-3ebc92ad.js → svgDrawCommon-08f97a94-5a2c7eed.js} +1 -1
  107. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-7d13d2f2.js → timeline-definition-85554ec2-e89c4f6e.js} +1 -1
  108. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-488385e1.js → xychartDiagram-e933f94c-afb6fe56.js} +1 -1
  109. rasa/core/channels/inspector/dist/index.html +1 -1
  110. rasa/core/channels/inspector/package.json +18 -18
  111. rasa/core/channels/inspector/src/App.tsx +29 -4
  112. rasa/core/channels/inspector/src/components/DialogueAgentStack.tsx +108 -0
  113. rasa/core/channels/inspector/src/components/{DialogueStack.tsx → DialogueHistoryStack.tsx} +4 -2
  114. rasa/core/channels/inspector/src/helpers/audio/audiostream.ts +7 -4
  115. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -0
  116. rasa/core/channels/inspector/src/helpers/formatters.ts +24 -3
  117. rasa/core/channels/inspector/src/helpers/utils.test.ts +127 -0
  118. rasa/core/channels/inspector/src/helpers/utils.ts +66 -1
  119. rasa/core/channels/inspector/src/theme/base/styles.ts +19 -1
  120. rasa/core/channels/inspector/src/types.ts +21 -0
  121. rasa/core/channels/inspector/yarn.lock +336 -189
  122. rasa/core/channels/studio_chat.py +6 -6
  123. rasa/core/channels/telegram.py +4 -9
  124. rasa/core/channels/voice_stream/genesys.py +1 -1
  125. rasa/core/channels/voice_stream/tts/deepgram.py +140 -0
  126. rasa/core/channels/voice_stream/twilio_media_streams.py +5 -1
  127. rasa/core/channels/voice_stream/voice_channel.py +3 -0
  128. rasa/core/config/__init__.py +0 -0
  129. rasa/core/{available_endpoints.py → config/available_endpoints.py} +51 -16
  130. rasa/core/config/configuration.py +260 -0
  131. rasa/core/config/credentials.py +19 -0
  132. rasa/core/config/message_procesing_config.py +34 -0
  133. rasa/core/constants.py +4 -0
  134. rasa/core/policies/enterprise_search_policy.py +5 -3
  135. rasa/core/policies/flow_policy.py +4 -4
  136. rasa/core/policies/flows/agent_executor.py +632 -0
  137. rasa/core/policies/flows/flow_executor.py +136 -75
  138. rasa/core/policies/flows/mcp_tool_executor.py +298 -0
  139. rasa/core/policies/intentless_policy.py +1 -1
  140. rasa/core/policies/ted_policy.py +20 -12
  141. rasa/core/policies/unexpected_intent_policy.py +6 -0
  142. rasa/core/processor.py +68 -44
  143. rasa/core/run.py +37 -8
  144. rasa/core/test.py +4 -0
  145. rasa/core/tracker_stores/tracker_store.py +3 -7
  146. rasa/core/train.py +1 -1
  147. rasa/core/training/interactive.py +20 -18
  148. rasa/core/training/story_conflict.py +5 -5
  149. rasa/core/utils.py +22 -23
  150. rasa/dialogue_understanding/commands/__init__.py +8 -0
  151. rasa/dialogue_understanding/commands/cancel_flow_command.py +19 -5
  152. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +21 -2
  153. rasa/dialogue_understanding/commands/clarify_command.py +20 -2
  154. rasa/dialogue_understanding/commands/continue_agent_command.py +91 -0
  155. rasa/dialogue_understanding/commands/knowledge_answer_command.py +21 -2
  156. rasa/dialogue_understanding/commands/restart_agent_command.py +162 -0
  157. rasa/dialogue_understanding/commands/start_flow_command.py +68 -7
  158. rasa/dialogue_understanding/commands/utils.py +124 -2
  159. rasa/dialogue_understanding/generator/command_parser.py +4 -0
  160. rasa/dialogue_understanding/generator/llm_based_command_generator.py +50 -12
  161. rasa/dialogue_understanding/generator/llm_command_generator.py +1 -1
  162. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +1 -1
  163. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +66 -0
  164. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +66 -0
  165. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +89 -0
  166. rasa/dialogue_understanding/generator/prompt_templates/agent_command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +88 -0
  167. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +42 -7
  168. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +40 -3
  169. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +20 -3
  170. rasa/dialogue_understanding/patterns/cancel.py +27 -6
  171. rasa/dialogue_understanding/patterns/clarify.py +3 -14
  172. rasa/dialogue_understanding/patterns/continue_interrupted.py +239 -6
  173. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +46 -8
  174. rasa/dialogue_understanding/processor/command_processor.py +136 -15
  175. rasa/dialogue_understanding/stack/dialogue_stack.py +98 -2
  176. rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +57 -0
  177. rasa/dialogue_understanding/stack/utils.py +57 -3
  178. rasa/dialogue_understanding/utils.py +24 -4
  179. rasa/dialogue_understanding_test/du_test_runner.py +8 -3
  180. rasa/e2e_test/e2e_test_runner.py +13 -3
  181. rasa/engine/caching.py +2 -2
  182. rasa/engine/constants.py +1 -1
  183. rasa/engine/recipes/default_components.py +138 -49
  184. rasa/engine/recipes/default_recipe.py +108 -11
  185. rasa/engine/runner/dask.py +8 -5
  186. rasa/engine/validation.py +19 -6
  187. rasa/graph_components/validators/default_recipe_validator.py +86 -28
  188. rasa/hooks.py +5 -5
  189. rasa/llm_fine_tuning/utils.py +2 -2
  190. rasa/model_training.py +60 -47
  191. rasa/nlu/classifiers/diet_classifier.py +198 -98
  192. rasa/nlu/classifiers/logistic_regression_classifier.py +1 -4
  193. rasa/nlu/classifiers/mitie_intent_classifier.py +3 -0
  194. rasa/nlu/classifiers/sklearn_intent_classifier.py +1 -3
  195. rasa/nlu/extractors/crf_entity_extractor.py +9 -10
  196. rasa/nlu/extractors/mitie_entity_extractor.py +3 -0
  197. rasa/nlu/extractors/spacy_entity_extractor.py +3 -0
  198. rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +4 -0
  199. rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +5 -0
  200. rasa/nlu/featurizers/dense_featurizer/mitie_featurizer.py +2 -0
  201. rasa/nlu/featurizers/dense_featurizer/spacy_featurizer.py +3 -0
  202. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +4 -2
  203. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +4 -0
  204. rasa/nlu/selectors/response_selector.py +10 -2
  205. rasa/nlu/tokenizers/jieba_tokenizer.py +3 -4
  206. rasa/nlu/tokenizers/mitie_tokenizer.py +3 -2
  207. rasa/nlu/tokenizers/spacy_tokenizer.py +3 -2
  208. rasa/nlu/utils/mitie_utils.py +3 -0
  209. rasa/nlu/utils/spacy_utils.py +3 -2
  210. rasa/plugin.py +8 -8
  211. rasa/privacy/privacy_manager.py +12 -3
  212. rasa/server.py +15 -3
  213. rasa/shared/agents/__init__.py +0 -0
  214. rasa/shared/agents/auth/__init__.py +0 -0
  215. rasa/shared/agents/auth/agent_auth_factory.py +105 -0
  216. rasa/shared/agents/auth/agent_auth_manager.py +92 -0
  217. rasa/shared/agents/auth/auth_strategy/__init__.py +19 -0
  218. rasa/shared/agents/auth/auth_strategy/agent_auth_strategy.py +52 -0
  219. rasa/shared/agents/auth/auth_strategy/api_key_auth_strategy.py +42 -0
  220. rasa/shared/agents/auth/auth_strategy/bearer_token_auth_strategy.py +28 -0
  221. rasa/shared/agents/auth/auth_strategy/oauth2_auth_strategy.py +167 -0
  222. rasa/shared/agents/auth/constants.py +12 -0
  223. rasa/shared/agents/auth/types.py +12 -0
  224. rasa/shared/agents/utils.py +35 -0
  225. rasa/shared/constants.py +8 -0
  226. rasa/shared/core/constants.py +16 -1
  227. rasa/shared/core/domain.py +0 -7
  228. rasa/shared/core/events.py +327 -0
  229. rasa/shared/core/flows/constants.py +5 -0
  230. rasa/shared/core/flows/flows_list.py +21 -5
  231. rasa/shared/core/flows/flows_yaml_schema.json +119 -184
  232. rasa/shared/core/flows/steps/call.py +49 -5
  233. rasa/shared/core/flows/steps/collect.py +98 -13
  234. rasa/shared/core/flows/validation.py +372 -8
  235. rasa/shared/core/flows/yaml_flows_io.py +3 -2
  236. rasa/shared/core/slots.py +2 -2
  237. rasa/shared/core/trackers.py +5 -2
  238. rasa/shared/exceptions.py +16 -0
  239. rasa/shared/importers/rasa.py +1 -1
  240. rasa/shared/importers/utils.py +9 -3
  241. rasa/shared/providers/llm/_base_litellm_client.py +41 -9
  242. rasa/shared/providers/llm/litellm_router_llm_client.py +8 -4
  243. rasa/shared/providers/llm/llm_client.py +7 -3
  244. rasa/shared/providers/llm/llm_response.py +66 -0
  245. rasa/shared/providers/llm/self_hosted_llm_client.py +8 -4
  246. rasa/shared/utils/common.py +24 -0
  247. rasa/shared/utils/health_check/health_check.py +7 -3
  248. rasa/shared/utils/llm.py +39 -16
  249. rasa/shared/utils/mcp/__init__.py +0 -0
  250. rasa/shared/utils/mcp/server_connection.py +247 -0
  251. rasa/shared/utils/mcp/utils.py +20 -0
  252. rasa/shared/utils/schemas/events.py +42 -0
  253. rasa/shared/utils/yaml.py +3 -1
  254. rasa/studio/pull/pull.py +3 -2
  255. rasa/studio/train.py +8 -7
  256. rasa/studio/upload.py +3 -6
  257. rasa/telemetry.py +69 -5
  258. rasa/tracing/config.py +45 -12
  259. rasa/tracing/constants.py +14 -0
  260. rasa/tracing/instrumentation/attribute_extractors.py +142 -9
  261. rasa/tracing/instrumentation/instrumentation.py +626 -21
  262. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +4 -4
  263. rasa/tracing/instrumentation/metrics.py +32 -0
  264. rasa/tracing/metric_instrument_provider.py +68 -0
  265. rasa/utils/common.py +92 -1
  266. rasa/utils/endpoints.py +11 -2
  267. rasa/utils/log_utils.py +96 -5
  268. rasa/utils/ml_utils.py +1 -1
  269. rasa/utils/tensorflow/__init__.py +7 -0
  270. rasa/utils/tensorflow/callback.py +136 -101
  271. rasa/utils/tensorflow/crf.py +1 -1
  272. rasa/utils/tensorflow/data_generator.py +21 -8
  273. rasa/utils/tensorflow/layers.py +21 -11
  274. rasa/utils/tensorflow/metrics.py +7 -3
  275. rasa/utils/tensorflow/models.py +56 -8
  276. rasa/utils/tensorflow/rasa_layers.py +8 -6
  277. rasa/utils/tensorflow/transformer.py +2 -3
  278. rasa/utils/train_utils.py +54 -24
  279. rasa/validator.py +5 -5
  280. rasa/version.py +1 -1
  281. {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc1.dist-info}/METADATA +46 -41
  282. {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc1.dist-info}/RECORD +285 -226
  283. rasa/builder/scrape_rasa_docs.py +0 -97
  284. rasa/core/channels/inspector/dist/assets/channel-8e08bed9.js +0 -1
  285. rasa/core/channels/inspector/dist/assets/clone-78c82dea.js +0 -1
  286. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-2b08f601.js +0 -1
  287. rasa/core/channels/inspector/dist/assets/index-c941dcb3.js +0 -1336
  288. {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc1.dist-info}/NOTICE +0 -0
  289. {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc1.dist-info}/WHEEL +0 -0
  290. {rasa_pro-3.14.0.dev20250922.dist-info → rasa_pro-3.14.0rc1.dist-info}/entry_points.txt +0 -0
@@ -1,11 +1,13 @@
1
1
  from __future__ import annotations
2
2
 
3
+ from abc import ABC, abstractmethod
3
4
  from dataclasses import dataclass
4
5
  from typing import Any, Dict, List, Optional, Set, Text, Union
5
6
 
6
7
  import structlog
7
8
 
8
9
  from rasa.shared.constants import ACTION_ASK_PREFIX, UTTER_ASK_PREFIX
10
+ from rasa.shared.core.constants import GLOBAL_SILENCE_TIMEOUT_DEFAULT_VALUE
9
11
  from rasa.shared.core.flows.flow_step import FlowStep
10
12
  from rasa.shared.core.slots import SlotRejection
11
13
  from rasa.shared.exceptions import RasaException
@@ -16,7 +18,93 @@ DEFAULT_FORCE_SLOT_FILLING = False
16
18
 
17
19
  logger = structlog.get_logger(__name__)
18
20
 
19
- SilenceTimeoutInstructionType = Union[int, float, Dict[str, Any]]
21
+ SilenceTimeoutInstructionType = Union[int, float, Dict[str, float]]
22
+
23
+
24
+ class SilenceTimeout(ABC):
25
+ @abstractmethod
26
+ def get_silence_for_channel(self, channel_name: str) -> float:
27
+ pass
28
+
29
+ @abstractmethod
30
+ def to_json(self) -> Dict[str, Any]:
31
+ pass
32
+
33
+
34
+ class SingleSilenceTimeout(SilenceTimeout):
35
+ def __init__(
36
+ self,
37
+ silence_timeout: float,
38
+ ):
39
+ SingleSilenceTimeout._validate(silence_timeout)
40
+ self.silence_timeout = silence_timeout
41
+
42
+ def get_silence_for_channel(self, channel_name: str) -> float:
43
+ return self.silence_timeout
44
+
45
+ def to_json(self) -> Dict[str, Any]:
46
+ return {
47
+ "silence_timeout": self.silence_timeout,
48
+ }
49
+
50
+ def __eq__(self, other: Any) -> bool:
51
+ if not isinstance(other, SingleSilenceTimeout):
52
+ return False
53
+ return self.silence_timeout == other.silence_timeout
54
+
55
+ @staticmethod
56
+ def _validate(silence_timeout: float) -> None:
57
+ if silence_timeout and silence_timeout < 0:
58
+ raise RasaException(
59
+ f"Invalid silence timeout value: {silence_timeout}. "
60
+ "Silence timeout must be a non-negative number."
61
+ )
62
+
63
+ @classmethod
64
+ def from_json(cls, silence_timeout: float) -> SingleSilenceTimeout:
65
+ SingleSilenceTimeout._validate(silence_timeout)
66
+ return cls(silence_timeout)
67
+
68
+
69
+ class PerChannelSilenceTimeout(SilenceTimeout):
70
+ def __init__(
71
+ self,
72
+ channel_silence_timeouts: Dict[str, float],
73
+ ):
74
+ self.silence_timeouts = channel_silence_timeouts
75
+
76
+ def get_silence_for_channel(self, channel_name: str) -> float:
77
+ return self.silence_timeouts.get(
78
+ channel_name, GLOBAL_SILENCE_TIMEOUT_DEFAULT_VALUE
79
+ )
80
+
81
+ def to_json(self) -> Dict[str, Any]:
82
+ return {
83
+ "silence_timeout": self.silence_timeouts,
84
+ }
85
+
86
+ def __eq__(self, other: Any) -> bool:
87
+ if not isinstance(other, PerChannelSilenceTimeout):
88
+ return False
89
+ return self.silence_timeouts == other.silence_timeouts
90
+
91
+ @staticmethod
92
+ def _validate(silence_timeout_json: Dict[str, Any]) -> None:
93
+ for channel, timeout in silence_timeout_json.items():
94
+ if not isinstance(timeout, (int, float)) or timeout < 0:
95
+ raise RasaException(
96
+ f"Invalid silence timeout value: {timeout} for "
97
+ f"channel '{channel}'. "
98
+ "If defined at collect step, silence timeout "
99
+ "must be a non-negative number."
100
+ )
101
+
102
+ @classmethod
103
+ def from_json(
104
+ cls, channel_silence_timeouts: Dict[str, Any]
105
+ ) -> PerChannelSilenceTimeout:
106
+ PerChannelSilenceTimeout._validate(channel_silence_timeouts)
107
+ return cls(channel_silence_timeouts)
20
108
 
21
109
 
22
110
  @dataclass
@@ -37,7 +125,7 @@ class CollectInformationFlowStep(FlowStep):
37
125
  """Whether to reset the slot value at the end of the flow."""
38
126
  force_slot_filling: bool = False
39
127
  """Whether to keep only the SetSlot command for the collected slot."""
40
- silence_timeout: Optional[float] = None
128
+ silence_timeout: Optional[SilenceTimeout] = None
41
129
  """The silence timeout for the collect information step."""
42
130
 
43
131
  @classmethod
@@ -79,25 +167,22 @@ class CollectInformationFlowStep(FlowStep):
79
167
  @staticmethod
80
168
  def _deserialise_silence_timeout(
81
169
  silence_timeout_json: Optional[SilenceTimeoutInstructionType],
82
- ) -> Optional[float]:
170
+ ) -> Optional[SilenceTimeout]:
83
171
  """Deserialize silence timeout from JSON."""
84
172
  if not silence_timeout_json:
85
173
  return None
86
174
 
87
- if not isinstance(silence_timeout_json, (int, float)):
175
+ if not isinstance(silence_timeout_json, (int, float, dict)):
88
176
  raise RasaException(
89
177
  f"Invalid silence timeout value: {silence_timeout_json}. "
90
- "If defined at collect step, silence timeout must be a number."
178
+ "If defined at collect step, silence timeout must be a number "
179
+ "or a map between channel names and timeout values."
91
180
  )
92
181
 
93
- silence_timeout = silence_timeout_json
182
+ if isinstance(silence_timeout_json, dict):
183
+ return PerChannelSilenceTimeout.from_json(silence_timeout_json)
94
184
 
95
- if silence_timeout and silence_timeout < 0:
96
- raise RasaException(
97
- f"Invalid silence timeout value: {silence_timeout}. "
98
- "Silence timeout must be a non-negative number."
99
- )
100
- return silence_timeout
185
+ return SingleSilenceTimeout.from_json(silence_timeout_json)
101
186
 
102
187
  @staticmethod
103
188
  def _default_utter(collect: str) -> str:
@@ -119,7 +204,7 @@ class CollectInformationFlowStep(FlowStep):
119
204
  data["rejections"] = [rejection.as_dict() for rejection in self.rejections]
120
205
  data["force_slot_filling"] = self.force_slot_filling
121
206
  if self.silence_timeout:
122
- data["silence_timeout"] = self.silence_timeout
207
+ data.update(self.silence_timeout.to_json())
123
208
 
124
209
  return super().as_json(step_properties=data)
125
210
 
@@ -4,7 +4,9 @@ import re
4
4
  import typing
5
5
  from collections import defaultdict
6
6
  from dataclasses import dataclass
7
- from typing import List, Optional, Set, Text
7
+ from typing import Iterator, List, Optional, Set, Text, Tuple
8
+
9
+ from jinja2 import Template
8
10
 
9
11
  from rasa.shared.constants import (
10
12
  RASA_DEFAULT_FLOW_PATTERN_PREFIX,
@@ -12,6 +14,11 @@ from rasa.shared.constants import (
12
14
  RASA_PATTERN_HUMAN_HANDOFF,
13
15
  RASA_PATTERN_INTERNAL_ERROR,
14
16
  )
17
+ from rasa.shared.core.flows.constants import (
18
+ KEY_MAPPING_INPUT,
19
+ KEY_MAPPING_OUTPUT,
20
+ KEY_MAPPING_SLOT,
21
+ )
15
22
  from rasa.shared.core.flows.flow import Flow
16
23
  from rasa.shared.core.flows.flow_step import (
17
24
  FlowStep,
@@ -35,6 +42,7 @@ from rasa.shared.core.flows.utils import (
35
42
  from rasa.shared.exceptions import RasaException
36
43
 
37
44
  if typing.TYPE_CHECKING:
45
+ from rasa.shared.core.domain import Domain
38
46
  from rasa.shared.core.flows.flows_list import FlowsList
39
47
 
40
48
  FLOW_ID_REGEX = r"""^[a-zA-Z0-9_][a-zA-Z0-9_-]*?$"""
@@ -107,6 +115,47 @@ class DuplicatedStepIdException(RasaException):
107
115
  )
108
116
 
109
117
 
118
+ class InvalidExitIfConditionException(RasaException):
119
+ """Raised when an exit_if condition is invalid."""
120
+
121
+ def __init__(self, step_id: str, flow_id: str, condition: str, reason: str) -> None:
122
+ """Initializes the exception."""
123
+ self.step_id = step_id
124
+ self.flow_id = flow_id
125
+ self.condition = condition
126
+ self.reason = reason
127
+
128
+ def __str__(self) -> str:
129
+ """Return a string representation of the exception."""
130
+ return (
131
+ f"Invalid exit_if condition '{self.condition}' in step '{self.step_id}' "
132
+ f"of flow '{self.flow_id}': {self.reason}. "
133
+ f"Please ensure that exit_if conditions contain at least one slot "
134
+ f"reference and use only defined slots with valid predicates."
135
+ )
136
+
137
+
138
+ class ExitIfExclusivityException(RasaException):
139
+ """Raised when a call step with exit_if has other properties."""
140
+
141
+ def __init__(
142
+ self, step_id: str, flow_id: str, conflicting_properties: List[str]
143
+ ) -> None:
144
+ """Initializes the exception."""
145
+ self.step_id = step_id
146
+ self.flow_id = flow_id
147
+ self.conflicting_properties = conflicting_properties
148
+
149
+ def __str__(self) -> str:
150
+ """Return a string representation of the exception."""
151
+ conflicting_properties_str = ", ".join(self.conflicting_properties)
152
+ return (
153
+ f"Call step '{self.step_id}' in flow '{self.flow_id}' has an 'exit_if' "
154
+ f"property but also has other properties: {conflicting_properties_str}. "
155
+ f"A call step with 'exit_if' cannot have any other properties."
156
+ )
157
+
158
+
110
159
  class DuplicatedFlowIdException(RasaException):
111
160
  """Raised when a flow is using the same id as another flow."""
112
161
 
@@ -261,7 +310,7 @@ class NoLinkAllowedInCalledFlowException(RasaException):
261
310
  )
262
311
 
263
312
 
264
- class UnresolvedFlowException(RasaException):
313
+ class UnresolvedLinkFlowException(RasaException):
265
314
  """Raised when a flow is called or linked from another flow but doesn't exist."""
266
315
 
267
316
  def __init__(self, flow_id: str, calling_flow_id: str, step_id: str) -> None:
@@ -273,13 +322,91 @@ class UnresolvedFlowException(RasaException):
273
322
  def __str__(self) -> str:
274
323
  """Return a string representation of the exception."""
275
324
  return (
276
- f"Flow '{self.flow_id}' is called or linked from flow "
325
+ f"Flow '{self.flow_id}' is linked from flow "
277
326
  f"'{self.calling_flow_id}' in step '{self.step_id}', "
278
327
  f"but it doesn't exist. "
279
328
  f"Please make sure that a flow with id '{self.flow_id}' exists."
280
329
  )
281
330
 
282
331
 
332
+ class UnresolvedCallStepException(RasaException):
333
+ """Raised when a call step doesn't have a reference to an existing flow or agent."""
334
+
335
+ def __init__(
336
+ self, call_step_argument: str, calling_flow_id: str, step_id: str
337
+ ) -> None:
338
+ """Initializes the exception."""
339
+ self.call_step_argument = call_step_argument
340
+ self.calling_flow_id = calling_flow_id
341
+ self.step_id = step_id
342
+
343
+ def __str__(self) -> str:
344
+ """Return a string representation of the exception."""
345
+ return (
346
+ f"The call step '{self.step_id}' in flow '{self.calling_flow_id}' "
347
+ f"is invalid: there is no flow or agent with the "
348
+ f"id '{self.call_step_argument}'. "
349
+ f"Please make sure that the call step argument is a valid flow id "
350
+ f"or an agent id."
351
+ )
352
+
353
+
354
+ class InvalidMCPServerReferenceException(RasaException):
355
+ """Raised when a call step references a non-existent MCP server."""
356
+
357
+ def __init__(
358
+ self, mcp_server_name: str, calling_flow_id: str, step_id: str
359
+ ) -> None:
360
+ """Initializes the exception."""
361
+ self.mcp_server_name = mcp_server_name
362
+ self.calling_flow_id = calling_flow_id
363
+ self.step_id = step_id
364
+
365
+ def __str__(self) -> str:
366
+ """Return a string representation of the exception."""
367
+ return (
368
+ f"Call step '{self.step_id}' in flow '{self.calling_flow_id}' "
369
+ f"references MCP server '{self.mcp_server_name}' which does not exist "
370
+ f"in endpoints.yml. Please make sure that the MCP server is properly "
371
+ f"configured in endpoints.yml."
372
+ )
373
+
374
+
375
+ class InvalidMCPMappingSlotException(RasaException):
376
+ """Raised when MCP tool mapping references non-existent slots."""
377
+
378
+ def __init__(
379
+ self,
380
+ step_id: str,
381
+ flow_id: str,
382
+ invalid_input_slots: Set[str],
383
+ invalid_output_slots: Set[str],
384
+ ) -> None:
385
+ """Initializes the exception."""
386
+ self.step_id = step_id
387
+ self.flow_id = flow_id
388
+ self.invalid_input_slots = invalid_input_slots
389
+ self.invalid_output_slots = invalid_output_slots
390
+
391
+ def __str__(self) -> str:
392
+ """Return a string representation of the exception."""
393
+ error_parts = []
394
+
395
+ if self.invalid_input_slots:
396
+ input_slots_str = ", ".join(sorted(self.invalid_input_slots))
397
+ error_parts.append(f"input slots: {input_slots_str}")
398
+
399
+ if self.invalid_output_slots:
400
+ output_slots_str = ", ".join(sorted(self.invalid_output_slots))
401
+ error_parts.append(f"output slots: {output_slots_str}")
402
+
403
+ slots_info = " ".join(error_parts)
404
+ return (
405
+ f"Call step '{self.step_id}' in flow '{self.flow_id}' has slots not "
406
+ f"defined in the domain: {slots_info}"
407
+ )
408
+
409
+
283
410
  class UnresolvedFlowStepIdException(RasaException):
284
411
  """Raised when a flow step is referenced, but its id can not be resolved."""
285
412
 
@@ -562,15 +689,201 @@ def validate_link_in_call_restriction(flows: "FlowsList") -> None:
562
689
  raise NoLinkAllowedInCalledFlowException(step.id, flow.id, step.call)
563
690
 
564
691
 
565
- def validate_called_flows_exists(flows: "FlowsList") -> None:
566
- """Validates that all called flows exist."""
692
+ def validate_call_steps(flows: "FlowsList") -> None:
693
+ """Validates that all called flows/agents exist and properties are valid."""
567
694
  for flow in flows.underlying_flows:
568
695
  for step in flow.steps:
569
696
  if not isinstance(step, CallFlowStep):
570
697
  continue
571
698
 
572
- if flows.flow_by_id(step.call) is None:
573
- raise UnresolvedFlowException(step.call, flow.id, step.id)
699
+ _is_step_calling_agent = step.is_calling_agent()
700
+ _is_step_calling_mcp_tool = step.has_mcp_tool_properties()
701
+
702
+ if (
703
+ not _is_step_calling_agent
704
+ and flows.flow_by_id(step.call) is None
705
+ and not _is_step_calling_mcp_tool
706
+ ):
707
+ raise UnresolvedCallStepException(step.call, flow.id, step.id)
708
+
709
+ if step.exit_if and not _is_step_calling_agent:
710
+ # exit_if is only allowed for call steps that call an agent
711
+ raise RasaException(
712
+ f"Call step '{step.id}' in flow '{flow.id}' has an 'exit_if' "
713
+ f"condition, but it is not calling an agent. "
714
+ f"'exit_if' is only allowed for call steps that call an agent."
715
+ )
716
+
717
+
718
+ def validate_mcp_server_references(flows: "FlowsList") -> None:
719
+ """Validates that MCP server references in call steps are valid."""
720
+ for flow in flows.underlying_flows:
721
+ for step in flow.steps:
722
+ if not isinstance(step, CallFlowStep):
723
+ continue
724
+
725
+ # Only validate call steps that are trying to call MCP tools
726
+ if step.has_mcp_tool_properties():
727
+ # Check if the referenced MCP server exists
728
+ from rasa.shared.utils.mcp.utils import mcp_server_exists
729
+
730
+ if not mcp_server_exists(step.mcp_server):
731
+ raise InvalidMCPServerReferenceException(
732
+ step.mcp_server, flow.id, step.id
733
+ )
734
+
735
+
736
+ def validate_mcp_mapping_slots(
737
+ flows: "FlowsList", domain: Optional["Domain"] = None
738
+ ) -> None:
739
+ """Validates that slots referenced in MCP tool mapping are defined in the domain.
740
+
741
+ This function validates that all slots referenced in the input and output
742
+ mapping of MCP tool calls are defined in the domain.
743
+
744
+ Args:
745
+ flows: The flows to validate.
746
+ domain: The domain with slot definitions. If None, slot validation is skipped.
747
+
748
+ Raises:
749
+ InvalidMCPMappingSlotException: If any MCP mapping references undefined slots.
750
+ """
751
+ if domain is None:
752
+ return
753
+
754
+ domain_slots = {slot.name for slot in domain.slots}
755
+
756
+ for flow in flows.underlying_flows:
757
+ for step in flow.steps:
758
+ if not isinstance(step, CallFlowStep):
759
+ continue
760
+
761
+ # Only validate call steps that are calling MCP tools
762
+ if not step.has_mcp_tool_properties() or step.mapping is None:
763
+ continue
764
+
765
+ # Extract slot names from input mapping
766
+ input_slots = set()
767
+ if KEY_MAPPING_INPUT in step.mapping and isinstance(
768
+ step.mapping[KEY_MAPPING_INPUT], list
769
+ ):
770
+ for input_item in step.mapping[KEY_MAPPING_INPUT]:
771
+ input_slots.add(input_item[KEY_MAPPING_SLOT])
772
+
773
+ # Extract slot names from output mapping
774
+ output_slots = set()
775
+ if KEY_MAPPING_OUTPUT in step.mapping and isinstance(
776
+ step.mapping[KEY_MAPPING_OUTPUT], list
777
+ ):
778
+ for output_item in step.mapping[KEY_MAPPING_OUTPUT]:
779
+ output_slots.add(output_item[KEY_MAPPING_SLOT])
780
+
781
+ # Check for invalid slots in both input and output
782
+ invalid_input_slots = input_slots - domain_slots
783
+ invalid_output_slots = output_slots - domain_slots
784
+
785
+ # Raise exception if any invalid slots are found
786
+ if invalid_input_slots or invalid_output_slots:
787
+ raise InvalidMCPMappingSlotException(
788
+ step.id, flow.id, invalid_input_slots, invalid_output_slots
789
+ )
790
+
791
+
792
+ def _get_call_steps_with_exit_if(
793
+ flows: "FlowsList",
794
+ ) -> Iterator[Tuple["CallFlowStep", "Flow"]]:
795
+ """Helper function to get all call steps with exit_if properties.
796
+
797
+ Args:
798
+ flows: The flows to search through.
799
+
800
+ Yields:
801
+ Tuples of (call_step, flow) for steps that have exit_if properties.
802
+ """
803
+ for flow in flows.underlying_flows:
804
+ for step in flow.steps:
805
+ if isinstance(step, CallFlowStep) and step.exit_if:
806
+ yield step, flow
807
+
808
+
809
+ def validate_exit_if_conditions(
810
+ flows: "FlowsList", domain: Optional["Domain"] = None
811
+ ) -> None:
812
+ """Validates that exit_if conditions are valid.
813
+
814
+ This function validates:
815
+ - Each condition contains at least one slot reference
816
+ - Only defined slots are used within the conditions
817
+ - The predicates are valid
818
+
819
+ Args:
820
+ flows: The flows to validate.
821
+ domain: The domain with slot definitions. If None, slot validation is skipped.
822
+
823
+ Raises:
824
+ InvalidExitIfConditionException: If any exit_if condition is invalid.
825
+ """
826
+ for step, flow in _get_call_steps_with_exit_if(flows):
827
+ for condition in step.exit_if: # type: ignore[union-attr]
828
+ if not isinstance(condition, str):
829
+ raise InvalidExitIfConditionException(
830
+ step.id, flow.id, str(condition), "Condition must be a string"
831
+ )
832
+
833
+ # Check if condition contains at least one slot reference
834
+ slot_references_regex = re.compile(r"\bslots\.\w+")
835
+ slot_references = slot_references_regex.findall(condition)
836
+ if not slot_references:
837
+ raise InvalidExitIfConditionException(
838
+ step.id,
839
+ flow.id,
840
+ condition,
841
+ "Condition must contain at least one slot reference "
842
+ "(e.g., 'slots.slot_name')",
843
+ )
844
+
845
+ # Validate predicate syntax using pypred (always, regardless of domain)
846
+ _validate_predicate_syntax_with_pypred(step.id, flow.id, condition)
847
+
848
+ # Validate slot names if domain is provided
849
+ if domain:
850
+ domain_slots = {slot.name: slot for slot in domain.slots}
851
+ for slot_ref in slot_references:
852
+ slot_name = slot_ref.split(".")[1]
853
+ if slot_name not in domain_slots:
854
+ raise InvalidExitIfConditionException(
855
+ step.id,
856
+ flow.id,
857
+ condition,
858
+ f"Slot '{slot_name}' is not defined in the domain",
859
+ )
860
+
861
+
862
+ def validate_exit_if_exclusivity(flows: "FlowsList") -> None:
863
+ """Validates that call steps with exit_if don't have other properties.
864
+
865
+ This function validates that call steps with an exit_if property cannot have
866
+ any other properties besides the required 'call' property and standard flow
867
+ step properties (id, next, metadata, description).
868
+
869
+ Args:
870
+ flows: The flows to validate.
871
+
872
+ Raises:
873
+ ExitIfExclusivityException: If a call step with exit_if has other properties.
874
+ """
875
+ for step, flow in _get_call_steps_with_exit_if(flows):
876
+ # Check for conflicting properties
877
+ conflicting_properties = []
878
+
879
+ # Check for MCP-related properties
880
+ if step.mcp_server is not None:
881
+ conflicting_properties.append("mcp_server")
882
+ if step.mapping is not None:
883
+ conflicting_properties.append("mapping")
884
+
885
+ if conflicting_properties:
886
+ raise ExitIfExclusivityException(step.id, flow.id, conflicting_properties)
574
887
 
575
888
 
576
889
  def validate_linked_flows_exists(flows: "FlowsList") -> None:
@@ -594,7 +907,7 @@ def validate_linked_flows_exists(flows: "FlowsList") -> None:
594
907
  flow.is_rasa_default_flow and step.link == RASA_PATTERN_CHITCHAT
595
908
  )
596
909
  ):
597
- raise UnresolvedFlowException(step.link, flow.id, step.id)
910
+ raise UnresolvedLinkFlowException(step.link, flow.id, step.id)
598
911
 
599
912
 
600
913
  def validate_patterns_are_not_called_or_linked(flows: "FlowsList") -> None:
@@ -694,6 +1007,7 @@ def validate_slot_names_to_be_collected(flow: Flow) -> None:
694
1007
 
695
1008
  def validate_flow_id(flow: Flow) -> None:
696
1009
  """Validates if the flow id comply with a specified regex.
1010
+
697
1011
  Flow IDs can start with an alphanumeric character or an underscore.
698
1012
  Followed by zero or more alphanumeric characters, hyphens, or underscores.
699
1013
 
@@ -751,3 +1065,53 @@ def validate_slot_persistence_configuration(flow: Flow) -> None:
751
1065
  result = _is_persist_slots_valid(persist_slots, flow_slots)
752
1066
  if not result.is_valid:
753
1067
  raise InvalidPersistSlotsException(flow_id, result.invalid_slots)
1068
+
1069
+
1070
+ def _validate_predicate_syntax_with_pypred(
1071
+ step_id: str, flow_id: str, condition: str
1072
+ ) -> None:
1073
+ """Validates predicate syntax using pypred.
1074
+
1075
+ This function validates that the exit_if condition has valid predicate syntax.
1076
+ Pypred catches syntax errors like double operators, invalid expressions, etc.
1077
+
1078
+ Args:
1079
+ step_id: The ID of the step containing the condition.
1080
+ flow_id: The ID of the flow containing the step.
1081
+ condition: The exit_if condition string.
1082
+
1083
+ Raises:
1084
+ InvalidExitIfConditionException: If pypred detects syntax errors.
1085
+ """
1086
+ try:
1087
+ from rasa.utils.pypred import Predicate
1088
+
1089
+ # Create a simple test context for syntax validation.
1090
+ # This context works with all slot types since it's only used for:
1091
+ # 1. Template rendering: Basic structure for Jinja2 template rendering
1092
+ # 2. Syntax validation: Pypred validates predicate syntax
1093
+ # without actual slot values
1094
+ test_context = {"slots": {"test_slot": "test_value"}}
1095
+ rendered_template = Template(condition).render(test_context)
1096
+
1097
+ # Let pypred validate the predicate syntax
1098
+ predicate = Predicate(rendered_template)
1099
+ if not predicate.is_valid():
1100
+ raise InvalidExitIfConditionException(
1101
+ step_id,
1102
+ flow_id,
1103
+ condition,
1104
+ "Invalid predicate syntax",
1105
+ )
1106
+
1107
+ except ImportError:
1108
+ # pypred not available, skip validation
1109
+ pass
1110
+ except Exception as e:
1111
+ # Re-raise pypred errors as predicate syntax errors
1112
+ raise InvalidExitIfConditionException(
1113
+ step_id,
1114
+ flow_id,
1115
+ condition,
1116
+ f"Invalid predicate syntax: {e}",
1117
+ )
@@ -127,7 +127,7 @@ class YAMLFlowsReader:
127
127
  If a schema does not have a `schema_name` set, we will use the
128
128
  `type` instead as a fallback.
129
129
  """
130
- return schema.get("schema_name", schema.get("type"))
130
+ return str(schema.get("schema_name", schema.get("type") or "unknown"))
131
131
 
132
132
  def schema_names(schemas: List[Dict[str, Any]]) -> List[str]:
133
133
  """Get the names of the schemas.
@@ -415,7 +415,8 @@ def process_yaml_content(yaml_content: Dict[str, Any]) -> Dict[str, Any]:
415
415
 
416
416
  # Under the "flows" key certain keys cannot have metadata
417
417
  _process_keys_recursively(
418
- yaml_content["flows"], ["nlu_trigger", "set_slots", "metadata"]
418
+ yaml_content["flows"],
419
+ ["nlu_trigger", "set_slots", "metadata", "mapping", "exit_if"],
419
420
  )
420
421
 
421
422
  return yaml_content
rasa/shared/core/slots.py CHANGED
@@ -68,9 +68,9 @@ class SlotRejection:
68
68
 
69
69
 
70
70
  class SlotValidation(BaseModel):
71
- rejections: List[SlotRejection] = Field(alias=REJECTIONS)
71
+ rejections: List[SlotRejection] = Field(alias=REJECTIONS) # type: ignore[literal-required]
72
72
  """how the slot value is validated using predicate evaluation."""
73
- refill_utter: str = Field(alias=REFILL_UTTER)
73
+ refill_utter: str = Field(alias=REFILL_UTTER) # type: ignore[literal-required]
74
74
  """The utterance that the assistant uses to ask for the slot."""
75
75
 
76
76
  @staticmethod
@@ -849,7 +849,7 @@ class DialogueStateTracker:
849
849
  action_names_to_exclude: Optional[List[Text]] = None,
850
850
  skip: int = 0,
851
851
  event_verbosity: EventVerbosity = EventVerbosity.APPLIED,
852
- ) -> Optional["EventTypeAlias"]:
852
+ ) -> Optional[Event]:
853
853
  """Gets the last event of a given type which was actually applied.
854
854
 
855
855
  Args:
@@ -889,9 +889,12 @@ class DialogueStateTracker:
889
889
  Returns:
890
890
  `True` if last executed action had name `name`, otherwise `False`.
891
891
  """
892
- last: Optional[ActionExecuted] = self.get_last_event_for(
892
+ last_event = self.get_last_event_for(
893
893
  ActionExecuted, action_names_to_exclude=[ACTION_LISTEN_NAME], skip=skip
894
894
  )
895
+ last: Optional[ActionExecuted] = (
896
+ last_event if isinstance(last_event, ActionExecuted) else None
897
+ )
895
898
  return last is not None and last.action_name == name
896
899
 
897
900
  ###