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
rasa/studio/auth.py CHANGED
@@ -6,7 +6,7 @@ from pathlib import Path
6
6
  from typing import Any, Dict, List, Optional, Text, Union
7
7
 
8
8
  import jwt
9
- from keycloak import KeycloakOpenID
9
+ from keycloak import KeycloakOpenID, KeycloakError
10
10
  from rasa.shared.exceptions import RasaException
11
11
  from rasa.shared.utils.yaml import read_yaml_file, write_yaml
12
12
 
@@ -17,6 +17,7 @@ from rasa.studio.constants import (
17
17
  KEYCLOAK_REFRESH_EXPIRES_IN_KEY,
18
18
  KEYCLOAK_REFRESH_TOKEN,
19
19
  )
20
+ from rasa.studio.results_logger import with_studio_error_handler, StudioResult
20
21
 
21
22
 
22
23
  class StudioAuth:
@@ -31,26 +32,39 @@ class StudioAuth:
31
32
  realm_name=studio_config.realm_name,
32
33
  )
33
34
 
34
- def login(self, username: Text, password: Text, totp: Optional[int] = None) -> None:
35
+ def health_check(self) -> bool:
36
+ """Check if the Keycloak server is reachable.
35
37
 
38
+ Returns:
39
+ True if the server is reachable, False otherwise.
40
+ """
36
41
  try:
37
- token_dict = self.keycloak_openid.token(
38
- username=username, password=password, totp=totp
39
- )
40
- except Exception as e:
41
- raise RasaException(f"Could not login. Error: {e}")
42
-
42
+ self.keycloak_openid.well_known()
43
+ return True
44
+ except Exception:
45
+ return False
46
+
47
+ @with_studio_error_handler
48
+ def login(
49
+ self, username: Text, password: Text, totp: Optional[int] = None
50
+ ) -> StudioResult:
51
+ token_dict = self.keycloak_openid.token(
52
+ username=username, password=password, totp=totp
53
+ )
43
54
  keycloak_token = self._resolve_token(token_dict)
44
55
 
45
56
  KeycloakTokenWriter.write_token_to_file(
46
57
  keycloak_token, token_file_location=DEFAULT_TOKEN_FILE_PATH
47
58
  )
48
59
 
49
- def refresh_token(self, refresh_token: Text) -> None:
60
+ return StudioResult.success("Login successful.")
61
+
62
+ @with_studio_error_handler
63
+ def refresh_token(self, refresh_token: Text) -> StudioResult:
50
64
  try:
51
65
  token_dict = self.keycloak_openid.refresh_token(refresh_token)
52
66
  except Exception as e:
53
- raise RasaException(f"Could not refresh token. Error: {e}")
67
+ raise KeycloakError(f"Could not refresh token. Error: {e}")
54
68
 
55
69
  keycloak_token = self._resolve_token(token_dict)
56
70
 
@@ -58,6 +72,8 @@ class StudioAuth:
58
72
  keycloak_token, token_file_location=DEFAULT_TOKEN_FILE_PATH
59
73
  )
60
74
 
75
+ return StudioResult.success("Token refreshed successfully.")
76
+
61
77
  @staticmethod
62
78
  def _resolve_token(token_dict: Dict[Text, Any]) -> KeycloakToken:
63
79
  return KeycloakToken(
rasa/studio/constants.py CHANGED
@@ -14,3 +14,5 @@ RASA_STUDIO_CLI_CLIENT_ID_KEY_ENV = "RASA_STUDIO_CLI_CLIENT_ID_KEY"
14
14
  STUDIO_NLU_FILENAME = "studio_nlu.yml"
15
15
  STUDIO_DOMAIN_FILENAME = "studio_domain.yml"
16
16
  STUDIO_FLOWS_FILENAME = "studio_flows.yml"
17
+ STUDIO_CONFIG_FILENAME = "studio_config.yml"
18
+ STUDIO_ENDPOINTS_FILENAME = "studio_endpoints.yml"
@@ -50,7 +50,7 @@ class StudioDataHandler:
50
50
  "query ExportAsEncodedYaml($input: ExportAsEncodedYamlInput!) "
51
51
  "{ exportAsEncodedYaml(input: $input) "
52
52
  "{ ... on ExportModernAsEncodedYamlOutput "
53
- "{ nlu flows domain } "
53
+ "{ nlu flows domain endpoints config } "
54
54
  "... on ExportClassicAsEncodedYamlOutput "
55
55
  "{ nlu domain }}}"
56
56
  ),
@@ -148,6 +148,12 @@ class StudioDataHandler:
148
148
  response = self._make_request(GQL_req)
149
149
  self._extract_data(response)
150
150
 
151
+ def get_config(self) -> Optional[str]:
152
+ return self.config
153
+
154
+ def get_endpoints(self) -> Optional[str]:
155
+ return self.endpoints
156
+
151
157
  def _validate_response(self, response: dict) -> bool:
152
158
  """Validates the response from Rasa Studio.
153
159
 
@@ -184,6 +190,8 @@ class StudioDataHandler:
184
190
  self.nlu = self._decode_response(return_data.get("nlu"))
185
191
  self.domain = self._decode_response(return_data.get("domain"))
186
192
  self.flows = self._decode_response(return_data.get("flows"))
193
+ self.config = self._decode_response(return_data.get("config"))
194
+ self.endpoints = self._decode_response(return_data.get("endpoints"))
187
195
 
188
196
  if not self.has_nlu() and not self.has_flows():
189
197
  raise RasaException("No nlu or flows data in Studio response.")
@@ -195,114 +203,113 @@ class StudioDataHandler:
195
203
  return base64.b64decode(data).decode("utf-8")
196
204
 
197
205
 
198
- class DataDiffGenerator:
199
- """Generate diff between original and studio data."""
200
-
201
- def __init__(
202
- self,
203
- original_domain: Optional[Dict] = None,
204
- studio_domain: Optional[Dict] = None,
205
- original_nlu: Optional[Dict[str, List]] = None,
206
- studio_nlu: Optional[Dict[str, List]] = None,
207
- original_flows: Optional[List[Flow]] = None,
208
- studio_flows: Optional[List[Flow]] = None,
209
- ):
210
- self.original_domain = original_domain
211
- self.studio_domain = studio_domain
212
- self.original_nlu = original_nlu
213
- self.studio_nlu = studio_nlu
214
- self.original_flows = original_flows
215
- self.studio_flows = studio_flows
216
-
217
- def create_new_domain_from_diff(self) -> Dict:
218
- """Create a new domain file from the diff."""
219
- if self.studio_domain is None or self.original_domain is None:
220
- return {}
221
- return self._domain_from_diff_rec(self.studio_domain, self.original_domain)
222
-
223
- @staticmethod
224
- def _domain_from_diff_rec(studio: Dict, original: Dict) -> Dict:
225
- ret_dict = {}
226
- for key in studio:
227
- if key not in original:
228
- ret_dict[key] = studio[key]
229
- elif isinstance(studio[key], dict):
230
- ret_dict[key] = DataDiffGenerator._domain_from_diff_rec(
231
- studio[key], original[key]
232
- )
233
- # remove empty diffs
234
- if not ret_dict[key]:
235
- del ret_dict[key]
236
- elif key not in [KEY_SLOTS, KEY_RESPONSES]:
237
- # copy over the whole key not just the diff in case of items
238
- # to get complete (valid) data not just the diff
239
- ret_dict[key] = studio[key]
240
- elif isinstance(studio[key], list):
241
- ret_dict[key] = []
242
- for item in studio[key]:
243
- if item not in original[key]:
244
- ret_dict[key].append(item)
245
-
246
- # if list is empty, remove it
247
- if not ret_dict[key]:
248
- del ret_dict[key]
249
-
250
- return ret_dict
251
-
252
- def create_new_nlu_from_diff(self) -> Dict:
253
- """Create a new nlu file from the diff."""
254
- if self.studio_nlu is None or self.original_nlu is None:
255
- return {"nlu": []}
256
-
257
- nlu_diff = []
258
- nlu_new_examples = [
259
- new for new in self.studio_nlu["nlu"] if new not in self.original_nlu["nlu"]
260
- ]
261
- if nlu_new_examples:
262
- for new_example in nlu_new_examples:
263
- nlu_diff.append(new_example)
264
- intent = new_example.get("intent")
265
- if intent:
266
- self._diff_nlu_examples(new_example, nlu_diff, "intent", intent)
267
- synonym = new_example.get("synonym")
268
- if synonym:
269
- self._diff_nlu_examples(new_example, nlu_diff, "synonym", synonym)
270
-
271
- return {"nlu": nlu_diff}
272
-
273
- def create_new_flows_from_diff(self) -> List[Flow]:
274
- """Create a new flows file from the diff."""
275
- if self.studio_flows is None or self.original_flows is None:
276
- return []
277
-
278
- flows_new = [new for new in self.studio_flows if new not in self.original_flows]
279
- return flows_new
280
-
281
- def _diff_nlu_examples(
282
- self, new_example: Dict, nlu_diff: List, match_key: str, match_value: str
283
- ) -> None:
284
- """Creates diff of nlu data examples.
285
-
286
- Args:
287
- new_example (Dict): intent or synonym with examples
288
- nlu_diff (List): list of diff examples
289
- match_key (str): intent or synonym name
290
- match_value (str): intent or synonym value
291
- """
292
- orig = list(
293
- filter(
294
- lambda x: x.get(match_key) == match_value,
295
- self.original_nlu["nlu"], # type: ignore[index]
206
+ def combine_domains(
207
+ studio_domain: Dict[str, Any], original_domain: Dict[str, Any]
208
+ ) -> Dict:
209
+ """Create a new domain file from the diff."""
210
+ if studio_domain is None or original_domain is None:
211
+ return {}
212
+ return _combine_domain_keys(studio_domain, original_domain)
213
+
214
+
215
+ def _combine_domain_keys(
216
+ first_domain: Dict[str, Any], second_domain: Dict[str, Any]
217
+ ) -> Dict[str, Any]:
218
+ combined_keys = {}
219
+ for key in first_domain:
220
+ if key not in second_domain:
221
+ combined_keys[key] = first_domain[key]
222
+ elif isinstance(first_domain[key], dict):
223
+ combined_keys[key] = _combine_domain_keys(
224
+ first_domain[key], second_domain[key]
296
225
  )
226
+ # remove empty diffs
227
+ if not combined_keys[key]:
228
+ del combined_keys[key]
229
+ elif key not in [KEY_SLOTS, KEY_RESPONSES]:
230
+ # for all keys except slots and responses, we want to keep the
231
+ # keys from the first domain
232
+ combined_keys[key] = first_domain[key]
233
+ elif isinstance(first_domain[key], list):
234
+ combined_keys[key] = []
235
+ for item in first_domain[key]:
236
+ if item not in second_domain[key]:
237
+ combined_keys[key].append(item)
238
+
239
+ # if list is empty, remove it
240
+ if not combined_keys[key]:
241
+ del combined_keys[key]
242
+
243
+ return combined_keys
244
+
245
+
246
+ def _diff_nlu_examples(
247
+ new_example: Dict,
248
+ nlu_diff: List,
249
+ match_key: str,
250
+ match_value: str,
251
+ original_nlu_examples: List,
252
+ ) -> None:
253
+ """Creates diff of nlu data examples.
254
+
255
+ Args:
256
+ new_example (Dict): intent or synonym with examples
257
+ nlu_diff (List): list of diff examples
258
+ match_key (str): intent or synonym name
259
+ match_value (str): intent or synonym value
260
+ original_nlu_examples (List): original nlu examples
261
+ """
262
+ orig = list(
263
+ filter(
264
+ lambda x: x.get(match_key) == match_value,
265
+ original_nlu_examples,
297
266
  )
298
- if len(orig) == 1:
299
- orig_ex = orig[0]["examples"].split("\n")
300
- new_ex = new_example["examples"].split("\n")
301
- new_example["examples"] = "\n".join(
302
- list(set(new_ex).difference(set(orig_ex)))
303
- )
304
- if not new_example["examples"]:
305
- nlu_diff.remove(new_example)
267
+ )
268
+ if len(orig) == 1:
269
+ orig_ex = orig[0]["examples"].split("\n")
270
+ new_ex = new_example["examples"].split("\n")
271
+ new_example["examples"] = "\n".join(list(set(new_ex).difference(set(orig_ex))))
272
+ if not new_example["examples"]:
273
+ nlu_diff.remove(new_example)
274
+
275
+
276
+ def create_new_nlu_from_diff(
277
+ studio_nlu: Dict[str, Any], original_nlu: Dict[str, Any]
278
+ ) -> Dict:
279
+ """Create a new nlu file from the diff."""
280
+ # `or []` handles the case where the data contains the property as an empty
281
+ # key, example yaml:
282
+ # ```
283
+ # nlu:
284
+ # ```
285
+ # in this case, the yaml parser will return an empty dict (because it
286
+ # can't know that it is supposed to be a list, so we need to convert it
287
+ # to a list
288
+ studio_nlu_data = studio_nlu.get("nlu", []) or []
289
+ original_nlu_data = original_nlu.get("nlu", []) or []
290
+
291
+ nlu_diff = []
292
+ for new in studio_nlu_data:
293
+ if new in original_nlu_data:
294
+ continue
295
+
296
+ nlu_diff.append(new)
297
+ intent = new.get("intent")
298
+ if intent:
299
+ _diff_nlu_examples(new, nlu_diff, "intent", intent, original_nlu_data)
300
+ synonym = new.get("synonym")
301
+ if synonym:
302
+ _diff_nlu_examples(new, nlu_diff, "synonym", synonym, original_nlu_data)
303
+
304
+ return {"nlu": nlu_diff}
305
+
306
+
307
+ def create_new_flows_from_diff(
308
+ studio_flows: List[Flow], original_flows: List[Flow]
309
+ ) -> List[Flow]:
310
+ """Create a new flows file from the diff."""
311
+ flows_new = [new for new in studio_flows if new not in original_flows]
312
+ return flows_new
306
313
 
307
314
 
308
315
  def import_data_from_studio(
rasa/studio/download.py CHANGED
@@ -1,19 +1,24 @@
1
1
  import argparse
2
2
  import logging
3
3
  from pathlib import Path
4
- from typing import Any, Dict, List, Optional, Set
4
+ from typing import Any, Dict, List, Optional, Set, Tuple
5
+
6
+ import questionary
7
+ import structlog
5
8
 
6
9
  import rasa.cli.utils
7
10
  import rasa.shared.utils.cli
8
11
  from rasa.shared.constants import (
9
12
  DEFAULT_DATA_PATH,
10
- DEFAULT_DOMAIN_PATHS,
13
+ DEFAULT_DOMAIN_PATH,
14
+ DEFAULT_CONFIG_PATH,
15
+ DEFAULT_ENDPOINTS_PATH,
11
16
  )
12
17
  from rasa.shared.core.domain import Domain
13
18
  from rasa.shared.core.flows.yaml_flows_io import YamlFlowsWriter
14
19
  from rasa.shared.importers.importer import TrainingDataImporter
15
20
  from rasa.shared.utils.yaml import read_yaml
16
-
21
+ from rasa.studio import data_handler
17
22
  from rasa.studio.config import StudioConfig
18
23
  from rasa.studio.constants import (
19
24
  STUDIO_DOMAIN_FILENAME,
@@ -21,13 +26,119 @@ from rasa.studio.constants import (
21
26
  STUDIO_NLU_FILENAME,
22
27
  )
23
28
  from rasa.studio.data_handler import (
24
- DataDiffGenerator,
25
29
  StudioDataHandler,
26
30
  import_data_from_studio,
27
31
  )
28
32
  from rasa.utils.mapper import RasaPrimitiveStorageMapper
29
33
 
30
34
  logger = logging.getLogger(__name__)
35
+ structlogger = structlog.getLogger(__name__)
36
+
37
+
38
+ def _handle_file_overwrite(
39
+ file_path: Optional[str], default_path: str, file_type: str
40
+ ) -> Tuple[Optional[Path], bool]:
41
+ """Handles the logic for determining whether to
42
+ overwrite an existing file or create a new one.
43
+ Works for config and endpoints at this moment
44
+
45
+ Args:
46
+ file_path (Optional[str]): The path to the file
47
+ provided by the user. Can be None.
48
+ default_path (str): The default path to use if `file_path`
49
+ is None or invalid. Must be a file path.
50
+ file_type (str): The type of the file (e.g., "config",
51
+ "endpoints") for logging and messaging purposes.
52
+
53
+ Returns:
54
+ tuple[Optional[Path], bool]: A tuple containing the
55
+ resolved file path and a boolean
56
+ indicating whether to write the file.
57
+ """
58
+ file_already_exists = rasa.cli.utils.get_validated_path(
59
+ file_path, file_type, default_path, none_is_valid=True
60
+ )
61
+ write_file = False
62
+ path = None
63
+ file_or_default_path = file_path or default_path
64
+
65
+ if file_already_exists is None:
66
+ path = Path(file_or_default_path)
67
+ if path.is_dir():
68
+ path = path / default_path
69
+ return path, True
70
+
71
+ if questionary.confirm(
72
+ f"{file_type.capitalize()} file '{file_or_default_path}' "
73
+ f"already exists. Do you want to overwrite it?"
74
+ ).ask():
75
+ write_file = True
76
+ path = Path(file_or_default_path)
77
+ return path, write_file
78
+
79
+
80
+ def _prepare_data_and_domain_paths(args: argparse.Namespace) -> Tuple[Path, List[Path]]:
81
+ """Handles the logic for preparing the domain and data paths
82
+ based on the provided arguments.
83
+
84
+ Args:
85
+ args (argparse.Namespace): The parsed arguments.
86
+
87
+ Returns:
88
+ tuple[Path, list[Path]]: A tuple containing the domain path
89
+ and a list of data paths.
90
+ """
91
+ # prepare domain
92
+ domain_path = rasa.cli.utils.get_validated_path(
93
+ args.domain, "domain", DEFAULT_DOMAIN_PATH, none_is_valid=True
94
+ )
95
+ domain_or_default_path = args.domain or DEFAULT_DOMAIN_PATH
96
+
97
+ if domain_path is None:
98
+ # If the path is None, use the provided domain path
99
+ domain_path = Path(domain_or_default_path)
100
+ domain_path.touch()
101
+
102
+ if isinstance(domain_path, str):
103
+ domain_path = Path(domain_path)
104
+
105
+ if domain_path.is_file():
106
+ if not args.overwrite:
107
+ domain_path.unlink()
108
+ domain_path.touch()
109
+
110
+ if domain_path.is_dir():
111
+ if not args.overwrite:
112
+ domain_path = domain_path / STUDIO_DOMAIN_FILENAME
113
+ domain_path.touch()
114
+
115
+ # prepare data
116
+ data_paths = []
117
+
118
+ for f in args.data:
119
+ data_path = rasa.cli.utils.get_validated_path(
120
+ f, "data", DEFAULT_DATA_PATH, none_is_valid=True
121
+ )
122
+
123
+ if data_path is None:
124
+ # If the path is None, use the default data path
125
+ data_path = Path(f)
126
+ data_path.mkdir(parents=True, exist_ok=True)
127
+ else:
128
+ data_path = Path(data_path)
129
+
130
+ if data_path.is_file() or data_path.is_dir():
131
+ # If it's a file, add it directly
132
+ data_paths.append(data_path)
133
+ else:
134
+ # If it doesn't exist, create the directory
135
+ data_path.mkdir(parents=True, exist_ok=True)
136
+ data_paths.append(data_path)
137
+
138
+ # Remove duplicates while preserving order
139
+ data_paths = list(dict.fromkeys(data_paths))
140
+
141
+ return domain_path, data_paths
31
142
 
32
143
 
33
144
  def handle_download(args: argparse.Namespace) -> None:
@@ -35,19 +146,43 @@ def handle_download(args: argparse.Namespace) -> None:
35
146
  studio_config=StudioConfig.read_config(), assistant_name=args.assistant_name[0]
36
147
  )
37
148
  handler.request_all_data()
38
- domain_path = rasa.cli.utils.get_validated_path(
39
- args.domain, "domain", DEFAULT_DOMAIN_PATHS, none_is_valid=True
149
+
150
+ domain_path, data_paths = _prepare_data_and_domain_paths(args)
151
+
152
+ # handle config and endpoints
153
+ config_path, write_config = _handle_file_overwrite(
154
+ args.config, DEFAULT_CONFIG_PATH, "config"
155
+ )
156
+ endpoints_path, write_endpoints = _handle_file_overwrite(
157
+ args.endpoints, DEFAULT_ENDPOINTS_PATH, "endpoints"
40
158
  )
41
- domain_path = Path(domain_path)
42
159
 
43
- data_paths = [
44
- Path(
45
- rasa.cli.utils.get_validated_path(
46
- f, "data", DEFAULT_DATA_PATH, none_is_valid=False
47
- )
48
- )
49
- for f in args.data
50
- ]
160
+ # generate log message if we write the config or endpoints
161
+ message_parts = []
162
+
163
+ config_path = config_path if write_config else None
164
+ endpoints_path = endpoints_path if write_endpoints else None
165
+
166
+ if config_path:
167
+ config_data = handler.get_config()
168
+ if not config_data:
169
+ rasa.shared.utils.cli.print_error_and_exit("No config data found.")
170
+ with open(config_path, "w") as f:
171
+ f.write(config_data)
172
+ message_parts.append(f"config to '{config_path}'")
173
+
174
+ if endpoints_path:
175
+ endpoints_data = handler.get_endpoints()
176
+ if not endpoints_data:
177
+ raise ValueError("No endpoints data found.")
178
+
179
+ with open(endpoints_path, "w") as f:
180
+ f.write(endpoints_data)
181
+ message_parts.append(f"endpoints to '{endpoints_path}'")
182
+
183
+ if message_parts:
184
+ message = "Downloaded " + " and ".join(message_parts)
185
+ structlogger.info("studio.download.config_endpoints", event_info=message)
51
186
 
52
187
  if not args.overwrite:
53
188
  _handle_download_no_overwrite(
@@ -74,11 +209,10 @@ def _handle_download_no_overwrite(
74
209
 
75
210
  if domain_path.is_dir():
76
211
  studio_domain_path = domain_path / STUDIO_DOMAIN_FILENAME
77
- diff_eng = DataDiffGenerator(
78
- original_domain=data_original.get_domain().as_dict(),
79
- studio_domain=data_from_studio.get_domain().as_dict(),
212
+ new_domain_data = data_handler.combine_domains(
213
+ data_from_studio.get_domain().as_dict(),
214
+ data_original.get_domain().as_dict(),
80
215
  )
81
- new_domain_data = diff_eng.create_new_domain_from_diff()
82
216
  studio_domain = Domain.from_dict(new_domain_data)
83
217
  if not studio_domain.is_empty():
84
218
  studio_domain.persist(studio_domain_path)
@@ -125,11 +259,10 @@ def _persist_nlu_diff(
125
259
  data_path: Path,
126
260
  ) -> None:
127
261
  """Creates a new nlu file from the diff of original and studio data."""
128
- diff_eng = DataDiffGenerator(
129
- original_nlu=read_yaml(data_original.get_nlu_data().nlu_as_yaml()),
130
- studio_nlu=read_yaml(data_from_studio.get_nlu_data().nlu_as_yaml()),
262
+ new_nlu_data = data_handler.create_new_nlu_from_diff(
263
+ read_yaml(data_from_studio.get_nlu_data().nlu_as_yaml()),
264
+ read_yaml(data_original.get_nlu_data().nlu_as_yaml()),
131
265
  )
132
- new_nlu_data = diff_eng.create_new_nlu_from_diff()
133
266
  if new_nlu_data["nlu"]:
134
267
  pretty_write_nlu_yaml(new_nlu_data, data_path)
135
268
  else:
@@ -142,11 +275,10 @@ def _persist_flows_diff(
142
275
  data_path: Path,
143
276
  ) -> None:
144
277
  """Creates a new flows file from the diff of original and studio data."""
145
- diff_eng = DataDiffGenerator(
146
- original_flows=data_original.get_flows().underlying_flows,
147
- studio_flows=data_from_studio.get_flows().underlying_flows,
278
+ new_flows_data = data_handler.create_new_flows_from_diff(
279
+ data_from_studio.get_flows().underlying_flows,
280
+ data_original.get_flows().underlying_flows,
148
281
  )
149
- new_flows_data = diff_eng.create_new_flows_from_diff()
150
282
  if new_flows_data:
151
283
  YamlFlowsWriter.dump(new_flows_data, data_path)
152
284
  else:
@@ -183,6 +315,7 @@ def _handle_download_with_overwrite(
183
315
  mapper = RasaPrimitiveStorageMapper(
184
316
  domain_path=domain_path, training_data_paths=data_paths
185
317
  )
318
+
186
319
  if domain_path.is_file():
187
320
  domain_merged = data_from_studio.get_domain().merge(data_original.get_domain())
188
321
  domain_merged.persist(domain_path)