rasa-pro 3.8.18__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 (278) hide show
  1. README.md +6 -42
  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/constants.py +1 -1
  43. rasa/core/actions/action.py +90 -315
  44. rasa/core/actions/action_exceptions.py +24 -0
  45. rasa/core/actions/constants.py +3 -0
  46. rasa/core/actions/custom_action_executor.py +188 -0
  47. rasa/core/actions/forms.py +11 -7
  48. rasa/core/actions/grpc_custom_action_executor.py +251 -0
  49. rasa/core/actions/http_custom_action_executor.py +140 -0
  50. rasa/core/actions/loops.py +3 -0
  51. rasa/core/actions/two_stage_fallback.py +1 -1
  52. rasa/core/agent.py +2 -4
  53. rasa/core/brokers/pika.py +1 -2
  54. rasa/core/channels/audiocodes.py +1 -1
  55. rasa/core/channels/botframework.py +0 -1
  56. rasa/core/channels/callback.py +0 -1
  57. rasa/core/channels/console.py +6 -8
  58. rasa/core/channels/development_inspector.py +1 -1
  59. rasa/core/channels/facebook.py +0 -3
  60. rasa/core/channels/hangouts.py +0 -6
  61. rasa/core/channels/inspector/dist/assets/{arc-5623b6dc.js → arc-b6e548fe.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-685c106a.js → c4Diagram-d0fbc5ce-fa03ac9e.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-8cbed007.js → classDiagram-936ed81e-ee67392a.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-5889cf12.js → classDiagram-v2-c3cb15f1-9b283fae.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-24c249d7.js → createText-62fc7601-8b6fcc2a.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-7dd06a75.js → edges-f2ad444c-22e77f4f.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-62c1e54c.js → erDiagram-9d236eb7-60ffc87f.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-ce49b86f.js → flowDb-1972c806-9dd802e4.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4067e48f.js → flowDiagram-7ea5b25a-5fa1912f.js} +1 -1
  70. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +1 -0
  71. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-59fe4051.js → flowchart-elk-definition-abe16c3d-622a1fd2.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-47e3a43b.js → ganttDiagram-9b5ea136-e285a63a.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-5a2ac0d9.js → gitGraphDiagram-99d0ae7c-f237bdca.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-dfb8efc4.js → index-2c4b9a3b-4b03d70e.js} +1 -1
  75. rasa/core/channels/inspector/dist/assets/{index-268a75c0.js → index-a5d3e69d.js} +4 -4
  76. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-b0c470f2.js → infoDiagram-736b4530-72a0fa5f.js} +1 -1
  77. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-2edb829a.js → journeyDiagram-df861f2b-82218c41.js} +1 -1
  78. rasa/core/channels/inspector/dist/assets/{layout-b6873d69.js → layout-78cff630.js} +1 -1
  79. rasa/core/channels/inspector/dist/assets/{line-1efc5781.js → line-5038b469.js} +1 -1
  80. rasa/core/channels/inspector/dist/assets/{linear-661e9b94.js → linear-c4fc4098.js} +1 -1
  81. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2d2e727f.js → mindmap-definition-beec6740-c33c8ea6.js} +1 -1
  82. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-9d3ea93d.js → pieDiagram-dbbf0591-a8d03059.js} +1 -1
  83. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-06a178a2.js → quadrantDiagram-4d7f4fd6-6a0e56b2.js} +1 -1
  84. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-0bfedffc.js → requirementDiagram-6fc4c22a-2dc7c7bd.js} +1 -1
  85. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-d76d0a04.js → sankeyDiagram-8f13d901-2360fe39.js} +1 -1
  86. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-37bb4341.js → sequenceDiagram-b655622a-41b9f9ad.js} +1 -1
  87. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-f52f7f57.js → stateDiagram-59f0c015-0aad326f.js} +1 -1
  88. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-4a986a20.js → stateDiagram-v2-2b26beab-9847d984.js} +1 -1
  89. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-7dd9ae12.js → styles-080da4f6-564d890e.js} +1 -1
  90. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-46e1ca14.js → styles-3dcbcfbf-38957613.js} +1 -1
  91. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-4a97439a.js → styles-9c745c82-f0fc6921.js} +1 -1
  92. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-823917a3.js → svgDrawCommon-4835440b-ef3c5a77.js} +1 -1
  93. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-9ea72896.js → timeline-definition-5b62e21b-bf3e91c1.js} +1 -1
  94. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-b631a8b6.js → xychartDiagram-2b33534f-4d4026c0.js} +1 -1
  95. rasa/core/channels/inspector/dist/index.html +1 -1
  96. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +10 -0
  97. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -7
  98. rasa/core/channels/inspector/src/helpers/formatters.ts +3 -2
  99. rasa/core/channels/rest.py +36 -21
  100. rasa/core/channels/rocketchat.py +0 -1
  101. rasa/core/channels/socketio.py +1 -1
  102. rasa/core/channels/telegram.py +3 -3
  103. rasa/core/channels/webexteams.py +0 -1
  104. rasa/core/concurrent_lock_store.py +1 -1
  105. rasa/core/evaluation/marker_base.py +1 -3
  106. rasa/core/evaluation/marker_stats.py +1 -2
  107. rasa/core/featurizers/single_state_featurizer.py +3 -26
  108. rasa/core/featurizers/tracker_featurizers.py +18 -122
  109. rasa/core/information_retrieval/__init__.py +7 -0
  110. rasa/core/information_retrieval/faiss.py +9 -4
  111. rasa/core/information_retrieval/information_retrieval.py +64 -7
  112. rasa/core/information_retrieval/milvus.py +7 -14
  113. rasa/core/information_retrieval/qdrant.py +8 -15
  114. rasa/core/lock_store.py +0 -1
  115. rasa/core/migrate.py +1 -2
  116. rasa/core/nlg/callback.py +3 -4
  117. rasa/core/policies/enterprise_search_policy.py +86 -22
  118. rasa/core/policies/enterprise_search_prompt_template.jinja2 +4 -41
  119. rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +60 -0
  120. rasa/core/policies/flows/flow_executor.py +104 -2
  121. rasa/core/policies/intentless_policy.py +7 -9
  122. rasa/core/policies/memoization.py +3 -3
  123. rasa/core/policies/policy.py +18 -9
  124. rasa/core/policies/rule_policy.py +8 -11
  125. rasa/core/policies/ted_policy.py +61 -88
  126. rasa/core/policies/unexpected_intent_policy.py +8 -17
  127. rasa/core/processor.py +136 -47
  128. rasa/core/run.py +41 -25
  129. rasa/core/secrets_manager/endpoints.py +2 -2
  130. rasa/core/secrets_manager/vault.py +6 -8
  131. rasa/core/test.py +3 -5
  132. rasa/core/tracker_store.py +49 -14
  133. rasa/core/train.py +1 -3
  134. rasa/core/training/interactive.py +9 -6
  135. rasa/core/utils.py +5 -10
  136. rasa/dialogue_understanding/coexistence/intent_based_router.py +11 -4
  137. rasa/dialogue_understanding/coexistence/llm_based_router.py +2 -3
  138. rasa/dialogue_understanding/commands/__init__.py +4 -0
  139. rasa/dialogue_understanding/commands/can_not_handle_command.py +9 -0
  140. rasa/dialogue_understanding/commands/cancel_flow_command.py +9 -0
  141. rasa/dialogue_understanding/commands/change_flow_command.py +38 -0
  142. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +9 -0
  143. rasa/dialogue_understanding/commands/clarify_command.py +9 -0
  144. rasa/dialogue_understanding/commands/correct_slots_command.py +9 -0
  145. rasa/dialogue_understanding/commands/error_command.py +12 -0
  146. rasa/dialogue_understanding/commands/handle_code_change_command.py +9 -0
  147. rasa/dialogue_understanding/commands/human_handoff_command.py +9 -0
  148. rasa/dialogue_understanding/commands/knowledge_answer_command.py +9 -0
  149. rasa/dialogue_understanding/commands/noop_command.py +9 -0
  150. rasa/dialogue_understanding/commands/set_slot_command.py +34 -3
  151. rasa/dialogue_understanding/commands/skip_question_command.py +9 -0
  152. rasa/dialogue_understanding/commands/start_flow_command.py +9 -0
  153. rasa/dialogue_understanding/generator/__init__.py +16 -1
  154. rasa/dialogue_understanding/generator/command_generator.py +92 -6
  155. rasa/dialogue_understanding/generator/constants.py +18 -0
  156. rasa/dialogue_understanding/generator/flow_retrieval.py +7 -5
  157. rasa/dialogue_understanding/generator/llm_based_command_generator.py +467 -0
  158. rasa/dialogue_understanding/generator/llm_command_generator.py +39 -609
  159. rasa/dialogue_understanding/generator/multi_step/__init__.py +0 -0
  160. rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2 +62 -0
  161. rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2 +38 -0
  162. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +827 -0
  163. rasa/dialogue_understanding/generator/nlu_command_adapter.py +69 -8
  164. rasa/dialogue_understanding/generator/single_step/__init__.py +0 -0
  165. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +345 -0
  166. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +44 -39
  167. rasa/dialogue_understanding/processor/command_processor.py +111 -3
  168. rasa/e2e_test/constants.py +1 -0
  169. rasa/e2e_test/e2e_test_case.py +44 -0
  170. rasa/e2e_test/e2e_test_runner.py +114 -11
  171. rasa/e2e_test/e2e_test_schema.yml +18 -0
  172. rasa/engine/caching.py +0 -1
  173. rasa/engine/graph.py +18 -6
  174. rasa/engine/recipes/config_files/default_config.yml +3 -3
  175. rasa/engine/recipes/default_components.py +1 -1
  176. rasa/engine/recipes/default_recipe.py +4 -5
  177. rasa/engine/recipes/recipe.py +1 -1
  178. rasa/engine/runner/dask.py +3 -9
  179. rasa/engine/storage/local_model_storage.py +0 -2
  180. rasa/engine/validation.py +179 -145
  181. rasa/exceptions.py +2 -2
  182. rasa/graph_components/validators/default_recipe_validator.py +3 -5
  183. rasa/hooks.py +0 -1
  184. rasa/model.py +1 -1
  185. rasa/model_training.py +1 -0
  186. rasa/nlu/classifiers/diet_classifier.py +33 -52
  187. rasa/nlu/classifiers/logistic_regression_classifier.py +9 -22
  188. rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
  189. rasa/nlu/extractors/crf_entity_extractor.py +54 -97
  190. rasa/nlu/extractors/duckling_entity_extractor.py +1 -1
  191. rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +1 -5
  192. rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +0 -4
  193. rasa/nlu/featurizers/featurizer.py +1 -1
  194. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +18 -49
  195. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +26 -64
  196. rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
  197. rasa/nlu/persistor.py +68 -26
  198. rasa/nlu/selectors/response_selector.py +7 -10
  199. rasa/nlu/test.py +0 -3
  200. rasa/nlu/utils/hugging_face/registry.py +1 -1
  201. rasa/nlu/utils/spacy_utils.py +1 -3
  202. rasa/server.py +22 -7
  203. rasa/shared/constants.py +12 -1
  204. rasa/shared/core/command_payload_reader.py +109 -0
  205. rasa/shared/core/constants.py +4 -5
  206. rasa/shared/core/domain.py +57 -56
  207. rasa/shared/core/events.py +4 -7
  208. rasa/shared/core/flows/flow.py +9 -0
  209. rasa/shared/core/flows/flows_list.py +12 -0
  210. rasa/shared/core/flows/steps/action.py +7 -2
  211. rasa/shared/core/generator.py +12 -11
  212. rasa/shared/core/slot_mappings.py +315 -24
  213. rasa/shared/core/slots.py +4 -2
  214. rasa/shared/core/trackers.py +32 -14
  215. rasa/shared/core/training_data/loading.py +0 -1
  216. rasa/shared/core/training_data/story_reader/story_reader.py +3 -3
  217. rasa/shared/core/training_data/story_reader/yaml_story_reader.py +11 -11
  218. rasa/shared/core/training_data/story_writer/yaml_story_writer.py +5 -3
  219. rasa/shared/core/training_data/structures.py +1 -1
  220. rasa/shared/core/training_data/visualization.py +1 -1
  221. rasa/shared/data.py +58 -1
  222. rasa/shared/exceptions.py +36 -2
  223. rasa/shared/importers/importer.py +1 -2
  224. rasa/shared/importers/rasa.py +0 -1
  225. rasa/shared/nlu/constants.py +2 -0
  226. rasa/shared/nlu/training_data/entities_parser.py +1 -2
  227. rasa/shared/nlu/training_data/features.py +2 -120
  228. rasa/shared/nlu/training_data/formats/dialogflow.py +3 -2
  229. rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -5
  230. rasa/shared/nlu/training_data/formats/readerwriter.py +0 -1
  231. rasa/shared/nlu/training_data/message.py +13 -0
  232. rasa/shared/nlu/training_data/training_data.py +0 -2
  233. rasa/shared/providers/openai/session_handler.py +2 -2
  234. rasa/shared/utils/constants.py +3 -0
  235. rasa/shared/utils/io.py +11 -1
  236. rasa/shared/utils/llm.py +1 -2
  237. rasa/shared/utils/pykwalify_extensions.py +1 -0
  238. rasa/shared/utils/schemas/domain.yml +3 -0
  239. rasa/shared/utils/yaml.py +44 -35
  240. rasa/studio/auth.py +26 -10
  241. rasa/studio/constants.py +2 -0
  242. rasa/studio/data_handler.py +114 -107
  243. rasa/studio/download.py +160 -27
  244. rasa/studio/results_logger.py +137 -0
  245. rasa/studio/train.py +6 -7
  246. rasa/studio/upload.py +159 -134
  247. rasa/telemetry.py +188 -34
  248. rasa/tracing/config.py +18 -3
  249. rasa/tracing/constants.py +26 -2
  250. rasa/tracing/instrumentation/attribute_extractors.py +50 -41
  251. rasa/tracing/instrumentation/instrumentation.py +290 -44
  252. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +7 -5
  253. rasa/tracing/instrumentation/metrics.py +109 -21
  254. rasa/tracing/metric_instrument_provider.py +83 -3
  255. rasa/utils/cli.py +2 -1
  256. rasa/utils/common.py +1 -1
  257. rasa/utils/endpoints.py +1 -2
  258. rasa/utils/io.py +72 -6
  259. rasa/utils/licensing.py +246 -31
  260. rasa/utils/ml_utils.py +1 -1
  261. rasa/utils/tensorflow/data_generator.py +1 -1
  262. rasa/utils/tensorflow/environment.py +1 -1
  263. rasa/utils/tensorflow/model_data.py +201 -12
  264. rasa/utils/tensorflow/model_data_utils.py +499 -500
  265. rasa/utils/tensorflow/models.py +5 -6
  266. rasa/utils/tensorflow/rasa_layers.py +15 -15
  267. rasa/utils/train_utils.py +1 -1
  268. rasa/utils/url_tools.py +53 -0
  269. rasa/validator.py +305 -3
  270. rasa/version.py +1 -1
  271. {rasa_pro-3.8.18.dist-info → rasa_pro-3.9.14.dist-info}/METADATA +25 -61
  272. {rasa_pro-3.8.18.dist-info → rasa_pro-3.9.14.dist-info}/RECORD +276 -259
  273. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-85583a23.js +0 -1
  274. rasa/utils/tensorflow/feature_array.py +0 -370
  275. /rasa/dialogue_understanding/generator/{command_prompt_template.jinja2 → single_step/command_prompt_template.jinja2} +0 -0
  276. {rasa_pro-3.8.18.dist-info → rasa_pro-3.9.14.dist-info}/NOTICE +0 -0
  277. {rasa_pro-3.8.18.dist-info → rasa_pro-3.9.14.dist-info}/WHEEL +0 -0
  278. {rasa_pro-3.8.18.dist-info → rasa_pro-3.9.14.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,188 @@
1
+ import abc
2
+ import logging
3
+ from typing import TYPE_CHECKING, Any, Dict, Text
4
+
5
+ import rasa
6
+ from rasa.core.actions.action_exceptions import DomainNotFound
7
+ from rasa.core.actions.constants import DEFAULT_SELECTIVE_DOMAIN, SELECTIVE_DOMAIN
8
+ from rasa.shared.constants import DOCS_BASE_URL
9
+ from rasa.shared.exceptions import RasaException
10
+ from rasa.utils.endpoints import EndpointConfig
11
+
12
+ if TYPE_CHECKING:
13
+ from rasa.shared.core.domain import Domain
14
+ from rasa.shared.core.trackers import DialogueStateTracker
15
+
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ class CustomActionExecutor(abc.ABC):
21
+ """Interface for custom action executors.
22
+
23
+ Provides an abstraction layer for executing custom actions
24
+ regardless of the communication protocol.
25
+ """
26
+
27
+ @abc.abstractmethod
28
+ async def run(
29
+ self,
30
+ tracker: "DialogueStateTracker",
31
+ domain: "Domain",
32
+ include_domain: bool = False,
33
+ ) -> Dict[Text, Any]:
34
+ """Executes the custom action.
35
+
36
+ Args:
37
+ tracker: The current state of the dialogue.
38
+ domain: The domain object containing domain-specific information.
39
+ include_domain: If True, the domain information is included in the request.
40
+
41
+ Returns:
42
+ The response from the execution of the custom action.
43
+ """
44
+ pass
45
+
46
+
47
+ class NoEndpointCustomActionExecutor(CustomActionExecutor):
48
+ """Implementation of a custom action executor when endpoint is not set.
49
+
50
+ Used to handle the case where no endpoint is configured.
51
+
52
+ Raises RasaException when executed.
53
+ """
54
+
55
+ def __init__(self, action_name: str) -> None:
56
+ """Initializes the custom action executor.
57
+
58
+ Args:
59
+ action_name: The name of the custom action.
60
+ """
61
+ self.action_name = action_name
62
+
63
+ async def run(
64
+ self,
65
+ tracker: "DialogueStateTracker",
66
+ domain: "Domain",
67
+ include_domain: bool = False,
68
+ ) -> Dict[Text, Any]:
69
+ """Executes the custom action.
70
+
71
+ Args:
72
+ tracker: The current state of the dialogue.
73
+ domain: The domain object containing domain-specific information.
74
+ include_domain: If True, the domain information
75
+ is included in the request.
76
+
77
+ Returns:
78
+ The response from the execution of the custom action.
79
+ """
80
+ raise RasaException(
81
+ f"Failed to execute custom action '{self.action_name}' "
82
+ f"because no endpoint is configured to run this "
83
+ f"custom action. Please take a look at "
84
+ f"the docs and set an endpoint configuration via the "
85
+ f"--endpoints flag. "
86
+ f"{DOCS_BASE_URL}/custom-actions"
87
+ )
88
+
89
+
90
+ class CustomActionRequestWriter:
91
+ """Writes the request payload for a custom action."""
92
+
93
+ def __init__(self, action_name: str, action_endpoint: EndpointConfig) -> None:
94
+ """Initializes the request writer.
95
+
96
+ Args:
97
+ action_name: The name of the custom action.
98
+ action_endpoint: The endpoint configuration for the action server.
99
+ """
100
+ self.action_name = action_name
101
+ self.action_endpoint = action_endpoint
102
+
103
+ def _is_selective_domain_enabled(self) -> bool:
104
+ """Check if selective domain handling is enabled.
105
+
106
+ Returns:
107
+ True if selective domain handling is enabled, otherwise False.
108
+ """
109
+ if self.action_endpoint is None:
110
+ return False
111
+ return bool(
112
+ self.action_endpoint.kwargs.get(SELECTIVE_DOMAIN, DEFAULT_SELECTIVE_DOMAIN)
113
+ )
114
+
115
+ def create(
116
+ self,
117
+ tracker: "DialogueStateTracker",
118
+ domain: "Domain",
119
+ include_domain: bool = False,
120
+ ) -> Dict[str, Any]:
121
+ """Create the JSON payload for the action server request.
122
+
123
+ Args:
124
+ tracker: The current state of the dialogue.
125
+ domain: The domain object containing domain-specific information.
126
+ include_domain: If True, the domain information is included in the request.
127
+
128
+ Returns:
129
+ A JSON payload to be sent to the action server.
130
+ """
131
+ from rasa.shared.core.trackers import EventVerbosity
132
+
133
+ tracker_state = tracker.current_state(EventVerbosity.ALL)
134
+
135
+ result = {
136
+ "next_action": self.action_name,
137
+ "sender_id": tracker.sender_id,
138
+ "tracker": tracker_state,
139
+ "version": rasa.__version__,
140
+ }
141
+
142
+ if include_domain and (
143
+ not self._is_selective_domain_enabled()
144
+ or domain.does_custom_action_explicitly_need_domain(self.action_name)
145
+ ):
146
+ result["domain"] = domain.as_dict()
147
+
148
+ result["domain_digest"] = domain.fingerprint()
149
+
150
+ return result
151
+
152
+
153
+ class RetryCustomActionExecutor(CustomActionExecutor):
154
+ """Retries the execution of a custom action."""
155
+
156
+ def __init__(self, custom_action_executor: CustomActionExecutor) -> None:
157
+ self._custom_action_executor = custom_action_executor
158
+
159
+ async def run(
160
+ self,
161
+ tracker: "DialogueStateTracker",
162
+ domain: "Domain",
163
+ include_domain: bool = False,
164
+ ) -> Dict[Text, Any]:
165
+ """Runs the wrapped custom action executor.
166
+
167
+ First request to the action server is made with/without the domain
168
+ as specified by the `include_domain` parameter.
169
+
170
+ If the action server responds with a `DomainNotFound` error, by running the
171
+ custom action executor again with the domain information.
172
+
173
+ Args:
174
+ tracker: The current state of the dialogue.
175
+ domain: The domain object containing domain-specific information.
176
+ include_domain: If True, the domain information is included in the request
177
+
178
+ Returns:
179
+ The response from the execution of the custom action.
180
+ """
181
+ try:
182
+ return await self._custom_action_executor.run(
183
+ tracker, domain, include_domain=include_domain
184
+ )
185
+ except DomainNotFound:
186
+ return await self._custom_action_executor.run(
187
+ tracker, domain, include_domain=True
188
+ )
@@ -11,7 +11,8 @@ from rasa.core.channels import OutputChannel
11
11
  from rasa.shared.core.domain import Domain, KEY_SLOTS
12
12
  from rasa.shared.core.constants import SlotMappingType, SLOT_MAPPINGS, MAPPING_TYPE
13
13
 
14
- from rasa.core.actions.action import ActionExecutionRejection, RemoteAction
14
+ from rasa.core.actions.action import RemoteAction
15
+ from rasa.core.actions.action_exceptions import ActionExecutionRejection
15
16
  from rasa.shared.core.constants import (
16
17
  ACTION_EXTRACT_SLOTS,
17
18
  ACTION_LISTEN_NAME,
@@ -293,7 +294,8 @@ class FormAction(LoopAction):
293
294
  + [SlotSet(REQUESTED_SLOT, self.get_slot_to_fill(current_tracker))]
294
295
  # Insert form execution event so that it's clearly distinguishable which
295
296
  # events were newly added.
296
- + [ActionExecuted(self.name())] + additional_events,
297
+ + [ActionExecuted(self.name())]
298
+ + additional_events,
297
299
  slots=domain.slots,
298
300
  )
299
301
 
@@ -565,6 +567,7 @@ class FormAction(LoopAction):
565
567
  nlg: "NaturalLanguageGenerator",
566
568
  tracker: "DialogueStateTracker",
567
569
  domain: "Domain",
570
+ metadata: Optional[Dict[Text, Any]] = None,
568
571
  ) -> List[Event]:
569
572
  """Activate form if the form is called for the first time.
570
573
 
@@ -596,7 +599,7 @@ class FormAction(LoopAction):
596
599
  )
597
600
 
598
601
  extraction_events = await action_extract_slots.run(
599
- output_channel, nlg, tracker, domain
602
+ output_channel, nlg, tracker, domain, metadata
600
603
  )
601
604
 
602
605
  events_as_str = "\n".join(str(e) for e in extraction_events)
@@ -712,9 +715,7 @@ class FormAction(LoopAction):
712
715
  if isinstance(event, ActiveLoop)
713
716
  ),
714
717
  None,
715
- ) == ActiveLoop(
716
- None
717
- )
718
+ ) == ActiveLoop(None)
718
719
 
719
720
  async def deactivate(self, *args: Any, **kwargs: Any) -> List[Event]:
720
721
  """Deactivates form."""
@@ -727,11 +728,14 @@ class FormAction(LoopAction):
727
728
  nlg: "NaturalLanguageGenerator",
728
729
  tracker: "DialogueStateTracker",
729
730
  domain: "Domain",
731
+ metadata: Optional[Dict[Text, Any]] = None,
730
732
  ) -> List[Event]:
731
733
  events = self._default_activation_events()
732
734
 
733
735
  temp_tracker = tracker.copy()
734
736
  temp_tracker.update_with_events(events)
735
- events += await self.activate(output_channel, nlg, temp_tracker, domain)
737
+ events += await self.activate(
738
+ output_channel, nlg, temp_tracker, domain, metadata
739
+ )
736
740
 
737
741
  return events
@@ -0,0 +1,251 @@
1
+ import json
2
+ from typing import TYPE_CHECKING, Any, Dict, List, Tuple
3
+ from urllib.parse import urlparse
4
+
5
+ import grpc
6
+ import structlog
7
+ from google.protobuf.json_format import MessageToDict, Parse, ParseDict
8
+ from rasa_sdk.grpc_errors import ResourceNotFound, ResourceNotFoundType
9
+ from rasa_sdk.grpc_py import action_webhook_pb2, action_webhook_pb2_grpc
10
+
11
+ from rasa.core.actions.action_exceptions import DomainNotFound
12
+ from rasa.core.actions.constants import SSL_CLIENT_CERT_FIELD, SSL_CLIENT_KEY_FIELD
13
+ from rasa.core.actions.custom_action_executor import (
14
+ CustomActionExecutor,
15
+ CustomActionRequestWriter,
16
+ )
17
+ from rasa.shared.exceptions import RasaException
18
+ from rasa.shared.utils.io import file_as_bytes
19
+ from rasa.utils.endpoints import EndpointConfig
20
+
21
+ if TYPE_CHECKING:
22
+ from rasa.shared.core.domain import Domain
23
+ from rasa.shared.core.trackers import DialogueStateTracker
24
+
25
+ structlogger = structlog.get_logger(__name__)
26
+
27
+
28
+ class GRPCCustomActionExecutor(CustomActionExecutor):
29
+ """gRPC-based implementation of the CustomActionExecutor.
30
+
31
+ Executes custom actions by making gRPC requests to the action endpoint.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ action_name: str,
37
+ action_endpoint: EndpointConfig,
38
+ ) -> None:
39
+ """Initializes the gRPC custom action executor.
40
+
41
+ Args:
42
+ action_name: Name of the custom action.
43
+ action_endpoint: Endpoint configuration of the custom action.
44
+ """
45
+ self.action_name = action_name
46
+ self.request_writer = CustomActionRequestWriter(action_name, action_endpoint)
47
+ self.action_endpoint = action_endpoint
48
+
49
+ parsed_url = urlparse(self.action_endpoint.url)
50
+ self.request_url = parsed_url.netloc
51
+
52
+ self.cert_ca = (
53
+ file_as_bytes(self.action_endpoint.cafile)
54
+ if self.action_endpoint.cafile
55
+ else None
56
+ )
57
+
58
+ self.client_cert = None
59
+ self.client_key = None
60
+
61
+ client_cert_file = self.action_endpoint.kwargs.get(SSL_CLIENT_CERT_FIELD)
62
+ client_key_file = self.action_endpoint.kwargs.get(SSL_CLIENT_KEY_FIELD)
63
+ if client_cert_file and client_key_file:
64
+ self.client_cert = file_as_bytes(client_cert_file)
65
+ self.client_key = file_as_bytes(client_key_file)
66
+ elif client_key_file and not client_cert_file:
67
+ structlogger.error(
68
+ f"rasa.core.actions.grpc_custom_action_executor.{SSL_CLIENT_CERT_FIELD}_missing",
69
+ event_info=(
70
+ f"Client key file '{SSL_CLIENT_KEY_FIELD}' is provided but "
71
+ f"client certificate file '{SSL_CLIENT_CERT_FIELD}' "
72
+ f"is not provided in the endpoint configuration. "
73
+ f"Both fields are required for client TLS authentication."
74
+ f"Continuing without client TLS authentication."
75
+ ),
76
+ )
77
+ elif client_cert_file and not client_key_file:
78
+ structlogger.error(
79
+ f"rasa.core.actions.grpc_custom_action_executor.{SSL_CLIENT_KEY_FIELD}_missing",
80
+ event_info=(
81
+ f"Client certificate file '{SSL_CLIENT_CERT_FIELD}' "
82
+ f" is provided but client key file '{SSL_CLIENT_KEY_FIELD}'"
83
+ f" is not provided in the endpoint configuration. "
84
+ f"Both fields are required for client TLS authentication."
85
+ f"Continuing without client TLS authentication."
86
+ ),
87
+ )
88
+
89
+ async def run(
90
+ self,
91
+ tracker: "DialogueStateTracker",
92
+ domain: "Domain",
93
+ include_domain: bool = False,
94
+ ) -> Dict[str, Any]:
95
+ """Execute the custom action using a gRPC request.
96
+
97
+ Args:
98
+ tracker: Tracker for the current conversation.
99
+ domain: Domain of the assistant.
100
+ include_domain: If True, the domain information is included in the request.
101
+
102
+ Returns:
103
+ Response from the action server.
104
+ """
105
+
106
+ request = self._create_payload(
107
+ tracker=tracker, domain=domain, include_domain=include_domain
108
+ )
109
+
110
+ return self._request(request)
111
+
112
+ def _request(
113
+ self,
114
+ request: action_webhook_pb2.WebhookRequest,
115
+ ) -> Dict[str, Any]:
116
+ """Perform a single gRPC request to the action server.
117
+
118
+ Args:
119
+ request: gRPC Request to be sent to the action server.
120
+
121
+ Returns:
122
+ Response from the action server.
123
+ """
124
+
125
+ client = self._create_grpc_client()
126
+ metadata = self._build_metadata()
127
+ try:
128
+ response = client.Webhook(request, metadata=metadata)
129
+ return MessageToDict(response)
130
+ except grpc.RpcError as rpc_error:
131
+ if not isinstance(rpc_error, grpc.Call):
132
+ raise RasaException(
133
+ f"Failed to execute custom action '{self.action_name}'. "
134
+ f"Unknown error occurred while calling the "
135
+ f"action server over gRPC protocol."
136
+ )
137
+
138
+ status_code = rpc_error.code()
139
+ details = rpc_error.details()
140
+ if status_code is not grpc.StatusCode.NOT_FOUND:
141
+ raise RasaException(
142
+ f"Failed to execute custom action '{self.action_name}'. "
143
+ f"Error: {details}"
144
+ )
145
+
146
+ resource_not_found_error = ResourceNotFound.model_validate_json(details)
147
+ if (
148
+ resource_not_found_error
149
+ and resource_not_found_error.resource_type
150
+ == ResourceNotFoundType.DOMAIN
151
+ ):
152
+ structlogger.error(
153
+ "rasa.core.actions.grpc_custom_action_executor.domain_not_found",
154
+ event_info=(
155
+ f"Failed to execute custom action '{self.action_endpoint}'. "
156
+ f"Could not find domain. {resource_not_found_error.message}"
157
+ ),
158
+ )
159
+ raise DomainNotFound()
160
+ raise RasaException(
161
+ f"Failed to execute custom action '{self.action_name}'. "
162
+ f"Error: {details}"
163
+ )
164
+
165
+ def _build_metadata(self) -> List[Tuple[str, Any]]:
166
+ """Build metadata for the gRPC request.
167
+
168
+ Returns:
169
+ Metadata for the gRPC request.
170
+ """
171
+ metadata = []
172
+ for key, value in self.action_endpoint.headers.items():
173
+ metadata.append((key, value))
174
+ return metadata
175
+
176
+ def _create_payload(
177
+ self,
178
+ tracker: "DialogueStateTracker",
179
+ domain: "Domain",
180
+ include_domain: bool = False,
181
+ ) -> action_webhook_pb2.WebhookRequest:
182
+ """Create the gRPC payload for the action server.
183
+
184
+ Args:
185
+ tracker: Tracker for the current conversation.
186
+ domain: Domain of the assistant.
187
+ include_domain: If True, the domain information is included in the request.
188
+
189
+ Returns:
190
+ gRPC payload for the action server.
191
+ """
192
+ json_body = self.request_writer.create(
193
+ tracker=tracker, domain=domain, include_domain=include_domain
194
+ )
195
+
196
+ request_proto = action_webhook_pb2.WebhookRequest()
197
+
198
+ try:
199
+ return ParseDict(
200
+ js_dict=json_body, message=request_proto, ignore_unknown_fields=True
201
+ )
202
+ except Exception:
203
+ structlogger.warning(
204
+ (
205
+ "rasa.core.actions.grpc_custom_action_executor."
206
+ "create_grpc_payload_from_dict_failed"
207
+ ),
208
+ event_info=(
209
+ "Failed to create gRPC payload from Python dictionary. "
210
+ "Falling back to create payload from JSON intermediary."
211
+ ),
212
+ )
213
+ json_text = json.dumps(json_body)
214
+ return Parse(
215
+ text=json_text, message=request_proto, ignore_unknown_fields=True
216
+ )
217
+
218
+ def _create_grpc_client(
219
+ self,
220
+ ) -> action_webhook_pb2_grpc.ActionServiceStub:
221
+ """Create a gRPC client for the action server.
222
+
223
+ Returns:
224
+ gRPC client for the action server.
225
+ """
226
+ channel = self._create_channel()
227
+ return action_webhook_pb2_grpc.ActionServiceStub(channel)
228
+
229
+ def _create_channel(
230
+ self,
231
+ ) -> grpc.Channel:
232
+ """Create a gRPC channel for the action server.
233
+
234
+ Returns:
235
+ gRPC channel for the action server.
236
+ """
237
+
238
+ compression = grpc.Compression.Gzip
239
+
240
+ if self.cert_ca:
241
+ credentials = grpc.ssl_channel_credentials(
242
+ root_certificates=self.cert_ca,
243
+ private_key=self.client_key,
244
+ certificate_chain=self.client_cert,
245
+ )
246
+ return grpc.secure_channel(
247
+ target=self.request_url,
248
+ credentials=credentials,
249
+ compression=compression,
250
+ )
251
+ return grpc.insecure_channel(target=self.request_url, compression=compression)
@@ -0,0 +1,140 @@
1
+ import json
2
+ import logging
3
+ from typing import TYPE_CHECKING, Any, Dict
4
+
5
+ import aiohttp
6
+
7
+ from rasa.core.actions.action_exceptions import ActionExecutionRejection, DomainNotFound
8
+ from rasa.core.actions.custom_action_executor import (
9
+ CustomActionExecutor,
10
+ CustomActionRequestWriter,
11
+ )
12
+ from rasa.core.constants import (
13
+ COMPRESS_ACTION_SERVER_REQUEST_ENV_NAME,
14
+ DEFAULT_COMPRESS_ACTION_SERVER_REQUEST,
15
+ DEFAULT_REQUEST_TIMEOUT,
16
+ )
17
+ from rasa.shared.exceptions import RasaException
18
+ from rasa.utils.common import get_bool_env_variable
19
+ from rasa.utils.endpoints import ClientResponseError, EndpointConfig
20
+
21
+ if TYPE_CHECKING:
22
+ from rasa.shared.core.domain import Domain
23
+ from rasa.shared.core.trackers import DialogueStateTracker
24
+
25
+
26
+ logger = logging.getLogger(__name__)
27
+
28
+
29
+ class HTTPCustomActionExecutor(CustomActionExecutor):
30
+ """HTTP-based implementation of the CustomActionExecutor.
31
+
32
+ Executes custom actions by making HTTP POST requests to the action endpoint.
33
+ """
34
+
35
+ def __init__(
36
+ self,
37
+ action_name: str,
38
+ action_endpoint: EndpointConfig,
39
+ ) -> None:
40
+ self.action_name = action_name
41
+ self.action_endpoint = action_endpoint
42
+ self.request_writer = CustomActionRequestWriter(action_name, action_endpoint)
43
+ self.should_compress = get_bool_env_variable(
44
+ COMPRESS_ACTION_SERVER_REQUEST_ENV_NAME,
45
+ DEFAULT_COMPRESS_ACTION_SERVER_REQUEST,
46
+ )
47
+
48
+ async def run(
49
+ self,
50
+ tracker: "DialogueStateTracker",
51
+ domain: "Domain",
52
+ include_domain: bool = False,
53
+ ) -> Dict[str, Any]:
54
+ """Execute the custom action using an HTTP POST request.
55
+
56
+ Args:
57
+ tracker: The current state of the dialogue.
58
+ domain: The domain object containing domain-specific information.
59
+ include_domain: If `True`, the domain information
60
+ is included in the request.
61
+
62
+ Returns:
63
+ A dictionary containing the response from the custom action endpoint.
64
+
65
+ Raises:
66
+ RasaException: If an error occurs while making the HTTP request.
67
+ """
68
+ try:
69
+ logger.debug(
70
+ "Calling action endpoint to run action '{}'.".format(self.action_name)
71
+ )
72
+
73
+ json_body = self.request_writer.create(
74
+ tracker=tracker, domain=domain, include_domain=include_domain
75
+ )
76
+
77
+ response = await self._perform_request_with_retries(json_body)
78
+
79
+ if response is None:
80
+ response = {}
81
+
82
+ return response
83
+
84
+ except ClientResponseError as e:
85
+ if e.status == 400:
86
+ response_data = json.loads(e.text)
87
+ exception = ActionExecutionRejection(
88
+ response_data["action_name"], response_data.get("error")
89
+ )
90
+ logger.error(exception.message)
91
+ raise exception
92
+ else:
93
+ raise RasaException(
94
+ f"Failed to execute custom action '{self.action_name}'"
95
+ ) from e
96
+
97
+ except aiohttp.ClientConnectionError as e:
98
+ logger.error(
99
+ f"Failed to run custom action '{self.action_name}'. Couldn't connect "
100
+ f"to the server at '{self.action_endpoint.url}'. "
101
+ f"Is the server running? "
102
+ f"Error: {e}"
103
+ )
104
+ raise RasaException(
105
+ f"Failed to execute custom action '{self.action_name}'. "
106
+ f"Couldn't connect to the server at '{self.action_endpoint.url}."
107
+ )
108
+
109
+ except aiohttp.ClientError as e:
110
+ # not all errors have a status attribute, but
111
+ # helpful to log if they got it
112
+
113
+ # noinspection PyUnresolvedReferences
114
+ status = getattr(e, "status", None)
115
+ raise RasaException(
116
+ "Failed to run custom action '{}'. Action server "
117
+ "responded with a non 200 status code of {}. "
118
+ "Make sure your action server properly runs actions "
119
+ "and returns a 200 once the action is executed. "
120
+ "Error: {}".format(self.action_name, status, e)
121
+ )
122
+
123
+ async def _perform_request_with_retries(
124
+ self,
125
+ json_body: Dict[str, Any],
126
+ ) -> Any:
127
+ """Attempts to perform the request with retries if necessary."""
128
+ assert self.action_endpoint is not None
129
+ try:
130
+ return await self.action_endpoint.request(
131
+ json=json_body,
132
+ method="post",
133
+ timeout=DEFAULT_REQUEST_TIMEOUT,
134
+ compress=self.should_compress,
135
+ )
136
+ except ClientResponseError as e:
137
+ # Repeat the request because Domain was not in the payload
138
+ if e.status == 449:
139
+ raise DomainNotFound()
140
+ raise e
@@ -28,6 +28,7 @@ class LoopAction(Action, ABC):
28
28
  nlg,
29
29
  tracker,
30
30
  domain,
31
+ metadata,
31
32
  )
32
33
 
33
34
  if not await self.is_done(output_channel, nlg, tracker, domain, events):
@@ -60,6 +61,7 @@ class LoopAction(Action, ABC):
60
61
  nlg: "NaturalLanguageGenerator",
61
62
  tracker: "DialogueStateTracker",
62
63
  domain: "Domain",
64
+ metadata: Optional[Dict[Text, Any]] = None,
63
65
  ) -> List[Event]:
64
66
  # can be overwritten
65
67
  return []
@@ -104,6 +106,7 @@ class LoopAction(Action, ABC):
104
106
  nlg: "NaturalLanguageGenerator",
105
107
  tracker: "DialogueStateTracker",
106
108
  domain: "Domain",
109
+ metadata: Optional[Dict[Text, Any]] = None,
107
110
  ) -> List[Event]:
108
111
  events = self._default_activation_events()
109
112
  events += await self.activate(output_channel, nlg, tracker, domain)
@@ -181,6 +181,6 @@ def _message_clarification(tracker: DialogueStateTracker) -> List[Event]:
181
181
  )
182
182
 
183
183
  clarification = copy.deepcopy(latest_message)
184
- clarification.parse_data[INTENT][PREDICTED_CONFIDENCE_KEY] = 1.0 # type: ignore[literal-required] # noqa E501
184
+ clarification.parse_data[INTENT][PREDICTED_CONFIDENCE_KEY] = 1.0 # type: ignore[literal-required]
185
185
  clarification.timestamp = time.time()
186
186
  return [ActionExecuted(ACTION_LISTEN_NAME), clarification]