rasa-pro 3.8.17__py3-none-any.whl → 3.9.14__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of rasa-pro might be problematic. Click here for more details.

Files changed (272) hide show
  1. README.md +5 -5
  2. rasa/__main__.py +14 -9
  3. rasa/anonymization/anonymization_pipeline.py +0 -1
  4. rasa/anonymization/anonymization_rule_executor.py +3 -3
  5. rasa/anonymization/utils.py +4 -3
  6. rasa/api.py +2 -2
  7. rasa/cli/arguments/default_arguments.py +1 -1
  8. rasa/cli/arguments/run.py +2 -2
  9. rasa/cli/arguments/test.py +1 -1
  10. rasa/cli/arguments/train.py +10 -10
  11. rasa/cli/e2e_test.py +27 -7
  12. rasa/cli/export.py +0 -1
  13. rasa/cli/license.py +3 -3
  14. rasa/cli/project_templates/calm/actions/action_template.py +1 -1
  15. rasa/cli/project_templates/calm/config.yml +1 -1
  16. rasa/cli/project_templates/calm/credentials.yml +1 -1
  17. rasa/cli/project_templates/calm/data/flows/add_contact.yml +1 -1
  18. rasa/cli/project_templates/calm/data/flows/remove_contact.yml +1 -1
  19. rasa/cli/project_templates/calm/domain/add_contact.yml +8 -2
  20. rasa/cli/project_templates/calm/domain/list_contacts.yml +3 -0
  21. rasa/cli/project_templates/calm/domain/remove_contact.yml +9 -2
  22. rasa/cli/project_templates/calm/domain/shared.yml +5 -0
  23. rasa/cli/project_templates/calm/endpoints.yml +4 -4
  24. rasa/cli/project_templates/default/actions/actions.py +1 -1
  25. rasa/cli/project_templates/default/config.yml +5 -5
  26. rasa/cli/project_templates/default/credentials.yml +1 -1
  27. rasa/cli/project_templates/default/endpoints.yml +4 -4
  28. rasa/cli/project_templates/default/tests/test_stories.yml +1 -1
  29. rasa/cli/project_templates/tutorial/config.yml +1 -1
  30. rasa/cli/project_templates/tutorial/credentials.yml +1 -1
  31. rasa/cli/project_templates/tutorial/data/patterns.yml +6 -0
  32. rasa/cli/project_templates/tutorial/domain.yml +4 -0
  33. rasa/cli/project_templates/tutorial/endpoints.yml +6 -6
  34. rasa/cli/run.py +0 -1
  35. rasa/cli/scaffold.py +3 -2
  36. rasa/cli/studio/download.py +11 -0
  37. rasa/cli/studio/studio.py +180 -24
  38. rasa/cli/studio/upload.py +0 -8
  39. rasa/cli/telemetry.py +18 -6
  40. rasa/cli/utils.py +21 -10
  41. rasa/cli/x.py +3 -2
  42. rasa/core/actions/action.py +90 -315
  43. rasa/core/actions/action_exceptions.py +24 -0
  44. rasa/core/actions/constants.py +3 -0
  45. rasa/core/actions/custom_action_executor.py +188 -0
  46. rasa/core/actions/forms.py +11 -7
  47. rasa/core/actions/grpc_custom_action_executor.py +251 -0
  48. rasa/core/actions/http_custom_action_executor.py +140 -0
  49. rasa/core/actions/loops.py +3 -0
  50. rasa/core/actions/two_stage_fallback.py +1 -1
  51. rasa/core/agent.py +2 -4
  52. rasa/core/brokers/pika.py +1 -2
  53. rasa/core/channels/audiocodes.py +1 -1
  54. rasa/core/channels/botframework.py +0 -1
  55. rasa/core/channels/callback.py +0 -1
  56. rasa/core/channels/console.py +6 -8
  57. rasa/core/channels/development_inspector.py +1 -1
  58. rasa/core/channels/facebook.py +0 -3
  59. rasa/core/channels/hangouts.py +0 -6
  60. rasa/core/channels/inspector/dist/assets/{arc-5623b6dc.js → arc-b6e548fe.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-685c106a.js → c4Diagram-d0fbc5ce-fa03ac9e.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-8cbed007.js → classDiagram-936ed81e-ee67392a.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-5889cf12.js → classDiagram-v2-c3cb15f1-9b283fae.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{createText-62fc7601-24c249d7.js → createText-62fc7601-8b6fcc2a.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-7dd06a75.js → edges-f2ad444c-22e77f4f.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-62c1e54c.js → erDiagram-9d236eb7-60ffc87f.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-ce49b86f.js → flowDb-1972c806-9dd802e4.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4067e48f.js → flowDiagram-7ea5b25a-5fa1912f.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +1 -0
  70. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-59fe4051.js → flowchart-elk-definition-abe16c3d-622a1fd2.js} +1 -1
  71. rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-47e3a43b.js → ganttDiagram-9b5ea136-e285a63a.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-5a2ac0d9.js → gitGraphDiagram-99d0ae7c-f237bdca.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-dfb8efc4.js → index-2c4b9a3b-4b03d70e.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/{index-268a75c0.js → index-a5d3e69d.js} +4 -4
  75. rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-b0c470f2.js → infoDiagram-736b4530-72a0fa5f.js} +1 -1
  76. rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-2edb829a.js → journeyDiagram-df861f2b-82218c41.js} +1 -1
  77. rasa/core/channels/inspector/dist/assets/{layout-b6873d69.js → layout-78cff630.js} +1 -1
  78. rasa/core/channels/inspector/dist/assets/{line-1efc5781.js → line-5038b469.js} +1 -1
  79. rasa/core/channels/inspector/dist/assets/{linear-661e9b94.js → linear-c4fc4098.js} +1 -1
  80. rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2d2e727f.js → mindmap-definition-beec6740-c33c8ea6.js} +1 -1
  81. rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-9d3ea93d.js → pieDiagram-dbbf0591-a8d03059.js} +1 -1
  82. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-06a178a2.js → quadrantDiagram-4d7f4fd6-6a0e56b2.js} +1 -1
  83. rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-0bfedffc.js → requirementDiagram-6fc4c22a-2dc7c7bd.js} +1 -1
  84. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-d76d0a04.js → sankeyDiagram-8f13d901-2360fe39.js} +1 -1
  85. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-37bb4341.js → sequenceDiagram-b655622a-41b9f9ad.js} +1 -1
  86. rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-f52f7f57.js → stateDiagram-59f0c015-0aad326f.js} +1 -1
  87. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-4a986a20.js → stateDiagram-v2-2b26beab-9847d984.js} +1 -1
  88. rasa/core/channels/inspector/dist/assets/{styles-080da4f6-7dd9ae12.js → styles-080da4f6-564d890e.js} +1 -1
  89. rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-46e1ca14.js → styles-3dcbcfbf-38957613.js} +1 -1
  90. rasa/core/channels/inspector/dist/assets/{styles-9c745c82-4a97439a.js → styles-9c745c82-f0fc6921.js} +1 -1
  91. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-823917a3.js → svgDrawCommon-4835440b-ef3c5a77.js} +1 -1
  92. rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-9ea72896.js → timeline-definition-5b62e21b-bf3e91c1.js} +1 -1
  93. rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-b631a8b6.js → xychartDiagram-2b33534f-4d4026c0.js} +1 -1
  94. rasa/core/channels/inspector/dist/index.html +1 -1
  95. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +10 -0
  96. rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -7
  97. rasa/core/channels/inspector/src/helpers/formatters.ts +3 -2
  98. rasa/core/channels/rest.py +36 -21
  99. rasa/core/channels/rocketchat.py +0 -1
  100. rasa/core/channels/socketio.py +1 -1
  101. rasa/core/channels/telegram.py +3 -3
  102. rasa/core/channels/webexteams.py +0 -1
  103. rasa/core/concurrent_lock_store.py +1 -1
  104. rasa/core/evaluation/marker_base.py +1 -3
  105. rasa/core/evaluation/marker_stats.py +1 -2
  106. rasa/core/featurizers/single_state_featurizer.py +2 -4
  107. rasa/core/featurizers/tracker_featurizers.py +0 -7
  108. rasa/core/information_retrieval/__init__.py +7 -0
  109. rasa/core/information_retrieval/faiss.py +9 -4
  110. rasa/core/information_retrieval/information_retrieval.py +64 -7
  111. rasa/core/information_retrieval/milvus.py +7 -14
  112. rasa/core/information_retrieval/qdrant.py +8 -15
  113. rasa/core/lock_store.py +0 -1
  114. rasa/core/migrate.py +1 -2
  115. rasa/core/nlg/callback.py +3 -4
  116. rasa/core/policies/enterprise_search_policy.py +86 -22
  117. rasa/core/policies/enterprise_search_prompt_template.jinja2 +4 -41
  118. rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +60 -0
  119. rasa/core/policies/flows/flow_executor.py +104 -2
  120. rasa/core/policies/intentless_policy.py +7 -9
  121. rasa/core/policies/memoization.py +3 -3
  122. rasa/core/policies/policy.py +18 -9
  123. rasa/core/policies/rule_policy.py +8 -11
  124. rasa/core/policies/ted_policy.py +28 -30
  125. rasa/core/policies/unexpected_intent_policy.py +1 -2
  126. rasa/core/processor.py +136 -47
  127. rasa/core/run.py +41 -25
  128. rasa/core/secrets_manager/endpoints.py +2 -2
  129. rasa/core/secrets_manager/vault.py +6 -8
  130. rasa/core/test.py +3 -5
  131. rasa/core/tracker_store.py +49 -14
  132. rasa/core/train.py +1 -3
  133. rasa/core/training/interactive.py +9 -6
  134. rasa/core/utils.py +5 -10
  135. rasa/dialogue_understanding/coexistence/intent_based_router.py +11 -4
  136. rasa/dialogue_understanding/coexistence/llm_based_router.py +2 -3
  137. rasa/dialogue_understanding/commands/__init__.py +4 -0
  138. rasa/dialogue_understanding/commands/can_not_handle_command.py +9 -0
  139. rasa/dialogue_understanding/commands/cancel_flow_command.py +9 -0
  140. rasa/dialogue_understanding/commands/change_flow_command.py +38 -0
  141. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +9 -0
  142. rasa/dialogue_understanding/commands/clarify_command.py +9 -0
  143. rasa/dialogue_understanding/commands/correct_slots_command.py +9 -0
  144. rasa/dialogue_understanding/commands/error_command.py +12 -0
  145. rasa/dialogue_understanding/commands/handle_code_change_command.py +9 -0
  146. rasa/dialogue_understanding/commands/human_handoff_command.py +9 -0
  147. rasa/dialogue_understanding/commands/knowledge_answer_command.py +9 -0
  148. rasa/dialogue_understanding/commands/noop_command.py +9 -0
  149. rasa/dialogue_understanding/commands/set_slot_command.py +34 -3
  150. rasa/dialogue_understanding/commands/skip_question_command.py +9 -0
  151. rasa/dialogue_understanding/commands/start_flow_command.py +9 -0
  152. rasa/dialogue_understanding/generator/__init__.py +16 -1
  153. rasa/dialogue_understanding/generator/command_generator.py +92 -6
  154. rasa/dialogue_understanding/generator/constants.py +18 -0
  155. rasa/dialogue_understanding/generator/flow_retrieval.py +7 -5
  156. rasa/dialogue_understanding/generator/llm_based_command_generator.py +467 -0
  157. rasa/dialogue_understanding/generator/llm_command_generator.py +39 -609
  158. rasa/dialogue_understanding/generator/multi_step/__init__.py +0 -0
  159. rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2 +62 -0
  160. rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2 +38 -0
  161. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +827 -0
  162. rasa/dialogue_understanding/generator/nlu_command_adapter.py +69 -8
  163. rasa/dialogue_understanding/generator/single_step/__init__.py +0 -0
  164. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +345 -0
  165. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +44 -39
  166. rasa/dialogue_understanding/processor/command_processor.py +111 -3
  167. rasa/e2e_test/constants.py +1 -0
  168. rasa/e2e_test/e2e_test_case.py +44 -0
  169. rasa/e2e_test/e2e_test_runner.py +114 -11
  170. rasa/e2e_test/e2e_test_schema.yml +18 -0
  171. rasa/engine/caching.py +0 -1
  172. rasa/engine/graph.py +18 -6
  173. rasa/engine/recipes/config_files/default_config.yml +3 -3
  174. rasa/engine/recipes/default_components.py +1 -1
  175. rasa/engine/recipes/default_recipe.py +4 -5
  176. rasa/engine/recipes/recipe.py +1 -1
  177. rasa/engine/runner/dask.py +3 -9
  178. rasa/engine/storage/local_model_storage.py +0 -2
  179. rasa/engine/validation.py +179 -145
  180. rasa/exceptions.py +2 -2
  181. rasa/graph_components/validators/default_recipe_validator.py +3 -5
  182. rasa/hooks.py +0 -1
  183. rasa/model.py +1 -1
  184. rasa/model_training.py +1 -0
  185. rasa/nlu/classifiers/diet_classifier.py +8 -14
  186. rasa/nlu/extractors/crf_entity_extractor.py +4 -4
  187. rasa/nlu/extractors/duckling_entity_extractor.py +1 -1
  188. rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +1 -5
  189. rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +0 -4
  190. rasa/nlu/featurizers/featurizer.py +1 -1
  191. rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +2 -4
  192. rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +9 -12
  193. rasa/nlu/persistor.py +68 -26
  194. rasa/nlu/selectors/response_selector.py +7 -10
  195. rasa/nlu/test.py +0 -3
  196. rasa/nlu/utils/hugging_face/registry.py +1 -1
  197. rasa/nlu/utils/spacy_utils.py +1 -3
  198. rasa/server.py +22 -7
  199. rasa/shared/constants.py +12 -1
  200. rasa/shared/core/command_payload_reader.py +109 -0
  201. rasa/shared/core/constants.py +4 -5
  202. rasa/shared/core/domain.py +57 -56
  203. rasa/shared/core/events.py +4 -7
  204. rasa/shared/core/flows/flow.py +9 -0
  205. rasa/shared/core/flows/flows_list.py +12 -0
  206. rasa/shared/core/flows/steps/action.py +7 -2
  207. rasa/shared/core/generator.py +12 -11
  208. rasa/shared/core/slot_mappings.py +315 -24
  209. rasa/shared/core/slots.py +4 -2
  210. rasa/shared/core/trackers.py +32 -14
  211. rasa/shared/core/training_data/loading.py +0 -1
  212. rasa/shared/core/training_data/story_reader/story_reader.py +3 -3
  213. rasa/shared/core/training_data/story_reader/yaml_story_reader.py +11 -11
  214. rasa/shared/core/training_data/story_writer/yaml_story_writer.py +5 -3
  215. rasa/shared/core/training_data/structures.py +1 -1
  216. rasa/shared/core/training_data/visualization.py +1 -1
  217. rasa/shared/data.py +58 -1
  218. rasa/shared/exceptions.py +36 -2
  219. rasa/shared/importers/importer.py +1 -2
  220. rasa/shared/importers/rasa.py +0 -1
  221. rasa/shared/nlu/constants.py +2 -0
  222. rasa/shared/nlu/training_data/entities_parser.py +1 -2
  223. rasa/shared/nlu/training_data/formats/dialogflow.py +3 -2
  224. rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -5
  225. rasa/shared/nlu/training_data/formats/readerwriter.py +0 -1
  226. rasa/shared/nlu/training_data/message.py +13 -0
  227. rasa/shared/nlu/training_data/training_data.py +0 -2
  228. rasa/shared/providers/openai/session_handler.py +2 -2
  229. rasa/shared/utils/constants.py +3 -0
  230. rasa/shared/utils/io.py +11 -0
  231. rasa/shared/utils/llm.py +1 -2
  232. rasa/shared/utils/pykwalify_extensions.py +1 -0
  233. rasa/shared/utils/schemas/domain.yml +3 -0
  234. rasa/shared/utils/yaml.py +44 -35
  235. rasa/studio/auth.py +26 -10
  236. rasa/studio/constants.py +2 -0
  237. rasa/studio/data_handler.py +114 -107
  238. rasa/studio/download.py +160 -27
  239. rasa/studio/results_logger.py +137 -0
  240. rasa/studio/train.py +6 -7
  241. rasa/studio/upload.py +159 -134
  242. rasa/telemetry.py +188 -34
  243. rasa/tracing/config.py +18 -3
  244. rasa/tracing/constants.py +26 -2
  245. rasa/tracing/instrumentation/attribute_extractors.py +50 -41
  246. rasa/tracing/instrumentation/instrumentation.py +290 -44
  247. rasa/tracing/instrumentation/intentless_policy_instrumentation.py +7 -5
  248. rasa/tracing/instrumentation/metrics.py +109 -21
  249. rasa/tracing/metric_instrument_provider.py +83 -3
  250. rasa/utils/cli.py +2 -1
  251. rasa/utils/common.py +1 -1
  252. rasa/utils/endpoints.py +1 -2
  253. rasa/utils/io.py +6 -6
  254. rasa/utils/licensing.py +246 -31
  255. rasa/utils/ml_utils.py +1 -1
  256. rasa/utils/tensorflow/data_generator.py +1 -1
  257. rasa/utils/tensorflow/environment.py +1 -1
  258. rasa/utils/tensorflow/model_data.py +9 -11
  259. rasa/utils/tensorflow/model_data_utils.py +499 -500
  260. rasa/utils/tensorflow/models.py +5 -6
  261. rasa/utils/tensorflow/rasa_layers.py +15 -15
  262. rasa/utils/train_utils.py +1 -1
  263. rasa/utils/url_tools.py +53 -0
  264. rasa/validator.py +305 -3
  265. rasa/version.py +1 -1
  266. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/METADATA +22 -22
  267. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/RECORD +271 -253
  268. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-85583a23.js +0 -1
  269. /rasa/dialogue_understanding/generator/{command_prompt_template.jinja2 → single_step/command_prompt_template.jinja2} +0 -0
  270. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/NOTICE +0 -0
  271. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/WHEEL +0 -0
  272. {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/entry_points.txt +0 -0
rasa/utils/licensing.py CHANGED
@@ -1,14 +1,27 @@
1
+ from asyncio import AbstractEventLoop
1
2
  import hashlib
2
- import logging
3
3
  import os
4
+ import random
4
5
  import re
5
6
  import time
7
+ import typing
6
8
  import uuid
7
9
  from datetime import datetime, timezone
8
- from typing import Any, Callable, Dict, Optional, Set, Text
10
+ from typing import Any, Callable, Dict, Optional, Set, Text, TypeVar
9
11
 
10
12
  import jwt
11
13
  from dotenv import dotenv_values
14
+ from sanic import Sanic
15
+ import structlog
16
+ from rasa import telemetry
17
+
18
+ from rasa.core import jobs
19
+ from rasa.shared.utils.cli import print_error_and_exit
20
+
21
+
22
+ if typing.TYPE_CHECKING:
23
+ from rasa.core.tracker_store import TrackerStore
24
+
12
25
 
13
26
  LICENSE_ENV_VAR = "RASA_PRO_LICENSE"
14
27
  ALGORITHM = "RS256"
@@ -32,8 +45,11 @@ JTI_BLOCKLIST: Set[Text] = set([])
32
45
  SCOPE_DELIMITER = ":"
33
46
  PRODUCT_AREA = "rasa:pro:plus"
34
47
  VOICE_SCOPE = "rasa:voice"
35
-
36
- logger = logging.getLogger(__name__)
48
+ CHAMPION_SERVER_LIMITED_SCOPE = "rasa:pro:champion-server-limited"
49
+ CHAMPION_SERVER_INTERNAL_SCOPE = "rasa:pro:champion-server-internal"
50
+ # defines multiple of the soft limit that triggers the hard limit
51
+ HARD_LIMIT_FACTOR = 10
52
+ structlogger = structlog.get_logger()
37
53
 
38
54
 
39
55
  class LicenseValidationException(Exception):
@@ -64,6 +80,10 @@ class LicenseNotYetValidException(LicenseValidationException):
64
80
  """Exception raised when a license is not valid yet (nbf)."""
65
81
 
66
82
 
83
+ class LicenseNotFoundException(LicenseValidationException):
84
+ """Exception raised when a license is not available."""
85
+
86
+
67
87
  class License:
68
88
  """Represents a Rasa Pro license.
69
89
 
@@ -80,6 +100,37 @@ class License:
80
100
  requires access to the private key to then encode) or for testing
81
101
  purposes.
82
102
 
103
+ The scopes of the license define what it unlocks. The scopes are
104
+ hierarchical, meaning that a license with a scope `rasa:pro` also
105
+ unlocks the scopes `rasa:pro:plus`.
106
+
107
+ Scopes can be used to enable or disable features in Rasa, the following
108
+ scopes are used:
109
+
110
+ - `rasa:pro` - Rasa Pro features
111
+ - `rasa:pro:plus` - Rasa Pro Plus features
112
+ - `rasa:voice` - Voice features
113
+
114
+ In addition to scopes focused on feature flagging, there are also scopes
115
+ that are used to limited by the license agreement they are issued under:
116
+
117
+ - `rasa:pro:champion` - Champion license, can only be used for local
118
+ development
119
+
120
+ - `rasa:pro:champion-server-internal` - Champion license, can be used
121
+ to deploy an assistant on a server for an internal use case. The license
122
+ limits the number of conversations per month to 100.
123
+
124
+ - `rasa:pro:champion-server-limited` - Champion license, can be used
125
+ to deploy an assistant on a server for a limited external use case.
126
+ The license allows for a maximum of 1000 conversations per month.
127
+
128
+ The champion scopes on their own do not unlock any features, they are
129
+ used to limit the use of the license to the agreed upon number of
130
+ conversations. The champion scopes can be combined with the feature
131
+ scopes to enable the features for the champion license, e.g.
132
+ `rasa:pro:champion rasa:pro` would enable the champion license for
133
+ local development and the Rasa Pro features.
83
134
  """
84
135
 
85
136
  __slots__ = ["jti", "iat", "nbf", "scope", "exp", "email", "company"]
@@ -215,39 +266,44 @@ class License:
215
266
  return jwt.encode(self.as_dict(), key=private_key, algorithm=ALGORITHM)
216
267
 
217
268
 
218
- def date_as_unix_timestamp(utc_date: Text) -> int:
219
- """Returns a date represented as a UNIX timestamp.
220
-
221
- Args:
222
- utc_date: Date as text (YYYY-MM-DD), UTC timezone.
223
-
224
- Returns:
225
- Date as UNIX timestamp.
226
- """
227
- dt = datetime.strptime(utc_date, "%Y-%m-%d")
228
- return int(dt.replace(tzinfo=timezone.utc).timestamp())
229
-
230
-
231
269
  def retrieve_license_from_env() -> Text:
232
270
  """Return the license found in the env var."""
233
271
  stored_env_values = dotenv_values(".env")
234
272
  license_from_env = os.environ.get(LICENSE_ENV_VAR)
235
273
  license = license_from_env or stored_env_values.get(LICENSE_ENV_VAR)
236
274
  if not license:
237
- raise SystemExit(
238
- f"A Rasa Pro license is required. "
239
- f"Please set the environmental variable "
240
- f"`{LICENSE_ENV_VAR}` to a valid license string. "
241
- )
275
+ raise LicenseNotFoundException()
242
276
  return license
243
277
 
244
278
 
279
+ def is_license_expiring_soon(license: License) -> bool:
280
+ """Check if the license is about to expire in less than 30 days."""
281
+ return license.exp - time.time() < 30 * 24 * 60 * 60
282
+
283
+
245
284
  def validate_license_from_env(product_area: Text = PRODUCT_AREA) -> None:
246
- license = retrieve_license_from_env()
247
285
  try:
248
- License.decode(license, product_area=product_area)
286
+ license_text = retrieve_license_from_env()
287
+ license = License.decode(license_text, product_area=product_area)
288
+
289
+ if is_license_expiring_soon(license):
290
+ structlogger.warning(
291
+ "license.expiration.warning",
292
+ event_info=(
293
+ "Your license is about to expire. "
294
+ "Please contact Rasa for a renewal."
295
+ ),
296
+ expiration_date=datetime.utcfromtimestamp(license.exp).isoformat(),
297
+ )
298
+ except LicenseNotFoundException:
299
+ structlogger.error("license.not_found.error")
300
+ raise SystemExit(
301
+ f"A Rasa Pro license is required. "
302
+ f"Please set the environmental variable "
303
+ f"`{LICENSE_ENV_VAR}` to a valid license string. "
304
+ )
249
305
  except LicenseValidationException as e:
250
- logger.error(e)
306
+ structlogger.error("license.validation.error", error=e)
251
307
  raise SystemExit(
252
308
  f"Failed to validate Rasa Pro license "
253
309
  f"which was read from environmental variable `{LICENSE_ENV_VAR}`. "
@@ -295,21 +351,25 @@ def derive_scope_hierarchy(scope: Text) -> Set[Text]:
295
351
  return set(required_scopes)
296
352
 
297
353
 
298
- def property_of_active_license(prop: Callable[[License], str]) -> Optional[str]:
354
+ T = TypeVar("T")
355
+
356
+
357
+ def property_of_active_license(prop: Callable[[License], T]) -> Optional[T]:
299
358
  """Return a property for this installation based on license.
300
359
 
301
360
  Returns:
302
361
  The property of the license if it exists, otherwise None.
303
362
  """
304
- retrieved_license = os.environ.get(LICENSE_ENV_VAR)
305
- if not retrieved_license:
306
- return None
307
-
308
363
  try:
364
+ retrieved_license = retrieve_license_from_env()
365
+ if not retrieved_license:
366
+ return None
309
367
  decoded = License.decode(retrieved_license)
310
368
  return prop(decoded)
369
+ except LicenseNotFoundException:
370
+ return None
311
371
  except LicenseValidationException as e:
312
- logger.warning("The provided license is invalid.", exc_info=e)
372
+ structlogger.warn("licensing.active_license.invalid", error=e)
313
373
  return None
314
374
 
315
375
 
@@ -317,3 +377,158 @@ def get_license_hash() -> Optional[Text]:
317
377
  """Return the hash of the current active license."""
318
378
  license_value = retrieve_license_from_env()
319
379
  return hashlib.sha256(license_value.encode("utf-8")).hexdigest()
380
+
381
+
382
+ def is_champion_server_license() -> bool:
383
+ """Return whether the current license is a developer license."""
384
+
385
+ def has_developer_license_scope(license: License) -> bool:
386
+ if CHAMPION_SERVER_LIMITED_SCOPE in license.scope:
387
+ return True
388
+ if CHAMPION_SERVER_INTERNAL_SCOPE in license.scope:
389
+ return True
390
+ return False
391
+
392
+ # or False is needed to handle the case where the license is not set
393
+ # this is to please the type checker
394
+ return property_of_active_license(has_developer_license_scope) or False
395
+
396
+
397
+ def conversation_limit_for_license() -> Optional[int]:
398
+ def conversations_limit(license: License) -> Optional[int]:
399
+ """Return maximum number of conversations per month for this license."""
400
+ scope = license.scope
401
+
402
+ if CHAMPION_SERVER_LIMITED_SCOPE in scope:
403
+ return 1000
404
+ if CHAMPION_SERVER_INTERNAL_SCOPE in scope:
405
+ return 100
406
+ return None
407
+
408
+ return property_of_active_license(conversations_limit)
409
+
410
+
411
+ async def validate_limited_server_license(app: Sanic, loop: AbstractEventLoop) -> None:
412
+ """Validate a limited server license and schedule conversation counting job."""
413
+ max_number_conversations = conversation_limit_for_license()
414
+
415
+ if app.ctx.agent:
416
+ if max_number_conversations is None:
417
+ structlogger.debug("licensing.server_limit.unlimited")
418
+ else:
419
+ store = app.ctx.agent.tracker_store
420
+ await run_conversation_counting(store, max_number_conversations)
421
+ await _schedule_conversation_counting(app, store, max_number_conversations)
422
+ else:
423
+ structlogger.warn("licensing.validate_limited_server_license.no_agent")
424
+
425
+
426
+ async def handle_soft_limit_reached(
427
+ conversation_count: int, max_number_conversations: int, month: datetime
428
+ ) -> None:
429
+ """Log a warning when the number of conversations exceeds the soft limit."""
430
+ structlogger.error(
431
+ "licensing.conversation_count.exceeded",
432
+ event_info=(
433
+ "The number of conversations has exceeded the limit granted "
434
+ "by your license. Please contact us to upgrade your license."
435
+ ),
436
+ conversation_count=conversation_count,
437
+ max_number_conversations=max_number_conversations,
438
+ )
439
+ telemetry.track_conversation_count_soft_limit(conversation_count, month)
440
+
441
+
442
+ async def handle_hard_limit_reached(
443
+ conversation_count: int, max_number_conversations: int, month: datetime
444
+ ) -> None:
445
+ """Log an error when the number of conversations exceeds the hard limit."""
446
+ structlogger.error(
447
+ "licensing.conversation_count.exceeded",
448
+ event_info=(
449
+ "The number of conversations has exceeded the limit granted "
450
+ "by your license. Please contact us to upgrade your license."
451
+ ),
452
+ conversation_count=conversation_count,
453
+ max_number_conversations=max_number_conversations,
454
+ )
455
+ telemetry.track_conversation_count_hard_limit(conversation_count, month)
456
+ print_error_and_exit(
457
+ "The number of conversations has exceeded the limit granted by your "
458
+ "license. Please contact us to upgrade your license."
459
+ )
460
+
461
+
462
+ def current_utc_month() -> datetime:
463
+ """Return the current month in UTC timezone."""
464
+ return datetime.now(timezone.utc).replace(
465
+ day=1, hour=0, minute=0, second=0, microsecond=0
466
+ )
467
+
468
+
469
+ async def run_conversation_counting(
470
+ tracker_store: Optional["TrackerStore"], max_number_conversations: int
471
+ ) -> None:
472
+ """Count the number of conversations started in the current month and log it."""
473
+ start_of_month_utc = current_utc_month()
474
+ start_of_month_timestamp = start_of_month_utc.timestamp()
475
+
476
+ conversation_count = await _count_conversations_after(
477
+ tracker_store, start_of_month_timestamp
478
+ )
479
+
480
+ if conversation_count >= max_number_conversations * HARD_LIMIT_FACTOR:
481
+ # user is above the hard limit
482
+ await handle_hard_limit_reached(
483
+ conversation_count, max_number_conversations, start_of_month_utc
484
+ )
485
+ elif conversation_count > max_number_conversations:
486
+ await handle_soft_limit_reached(
487
+ conversation_count, max_number_conversations, start_of_month_utc
488
+ )
489
+
490
+ structlogger.debug(
491
+ "licensing.conversation_count",
492
+ conversation_count=conversation_count,
493
+ max_number_conversations=max_number_conversations,
494
+ )
495
+ telemetry.track_conversation_count(conversation_count, start_of_month_utc)
496
+
497
+
498
+ async def _schedule_conversation_counting(
499
+ app: Sanic, tracker_store: Optional["TrackerStore"], max_number_conversations: int
500
+ ) -> None:
501
+ """Schedule a job counting the number of conversations in the current month."""
502
+
503
+ async def conversation_counting_job(
504
+ app: Sanic,
505
+ tracker_store: Optional["TrackerStore"],
506
+ max_number_conversations: int,
507
+ ) -> None:
508
+ try:
509
+ await run_conversation_counting(tracker_store, max_number_conversations)
510
+ except SystemExit:
511
+ # we've reached the conversation limit
512
+ app.stop()
513
+
514
+ (await jobs.scheduler()).add_job(
515
+ conversation_counting_job,
516
+ "interval",
517
+ # every 24 hours with a random offset of max 1 hour
518
+ # clusters tend to get started at the same time, this avoids them
519
+ # doing the "conversation counting work" at exactly the same time
520
+ seconds=24 * 60 * 60 + random.randint(0, 60 * 60),
521
+ args=[app, tracker_store, max_number_conversations],
522
+ id="count-conversations",
523
+ replace_existing=True,
524
+ )
525
+
526
+
527
+ async def _count_conversations_after(
528
+ tracker_store: Optional["TrackerStore"], after_timestamp: float
529
+ ) -> int:
530
+ """Count the number of conversations started in the current month."""
531
+ if tracker_store is None:
532
+ return 0
533
+
534
+ return await tracker_store.count_conversations(after_timestamp=after_timestamp)
rasa/utils/ml_utils.py CHANGED
@@ -64,7 +64,7 @@ def extract_participant_messages_from_transcript(
64
64
 
65
65
 
66
66
  def extract_ai_response_examples(
67
- responses: Dict[Text, List[Dict[Text, Any]]]
67
+ responses: Dict[Text, List[Dict[Text, Any]]],
68
68
  ) -> List[str]:
69
69
  """Extracts the responses from the domain.
70
70
 
@@ -217,7 +217,7 @@ class RasaDataGenerator(Sequence):
217
217
  # we need to make sure that the matrices are coo_matrices otherwise the
218
218
  # transformation does not work (e.g. you cannot access x.row, x.col)
219
219
  if not isinstance(array_of_sparse[0], scipy.sparse.coo_matrix):
220
- array_of_sparse = [x.tocoo() for x in array_of_sparse] # type: ignore[assignment] # noqa: E501
220
+ array_of_sparse = [x.tocoo() for x in array_of_sparse] # type: ignore[assignment]
221
221
 
222
222
  max_seq_len = max([x.shape[0] for x in array_of_sparse])
223
223
 
@@ -98,7 +98,7 @@ def _parse_gpu_config(gpu_memory_config: Text) -> Dict[int, int]:
98
98
  # Helper explanation of where the error comes from
99
99
  raise ValueError(
100
100
  f"Error parsing GPU configuration. Please cross-check the format of "
101
- f"'{ENV_GPU_CONFIG}' at https://rasa.com/docs/rasa/tuning-your-model"
101
+ f"'{ENV_GPU_CONFIG}' at https://rasa.com/docs/rasa-pro/nlu-based-assistants/tuning-your-model"
102
102
  f"#restricting-absolute-gpu-memory-available ."
103
103
  )
104
104
 
@@ -103,7 +103,7 @@ class FeatureArray(np.ndarray):
103
103
  return
104
104
 
105
105
  self.units = getattr(obj, "units", None)
106
- self.number_of_dimensions = getattr(obj, "number_of_dimensions", None) # type: ignore[assignment] # noqa:E501
106
+ self.number_of_dimensions = getattr(obj, "number_of_dimensions", None) # type: ignore[assignment]
107
107
  self.is_sparse = getattr(obj, "is_sparse", None)
108
108
 
109
109
  default_attributes = {
@@ -285,12 +285,10 @@ class RasaModelData:
285
285
  self.sparse_feature_sizes: Dict[Text, Dict[Text, List[int]]] = {}
286
286
 
287
287
  @overload
288
- def get(self, key: Text, sub_key: Text) -> List[FeatureArray]:
289
- ...
288
+ def get(self, key: Text, sub_key: Text) -> List[FeatureArray]: ...
290
289
 
291
290
  @overload
292
- def get(self, key: Text, sub_key: None = ...) -> Dict[Text, List[FeatureArray]]:
293
- ...
291
+ def get(self, key: Text, sub_key: None = ...) -> Dict[Text, List[FeatureArray]]: ...
294
292
 
295
293
  def get(
296
294
  self, key: Text, sub_key: Optional[Text] = None
@@ -739,9 +737,9 @@ class RasaModelData:
739
737
  # if a label was skipped in current batch
740
738
  skipped = [False] * num_label_ids
741
739
 
742
- new_data: DefaultDict[
743
- Text, DefaultDict[Text, List[List[FeatureArray]]]
744
- ] = defaultdict(lambda: defaultdict(list))
740
+ new_data: DefaultDict[Text, DefaultDict[Text, List[List[FeatureArray]]]] = (
741
+ defaultdict(lambda: defaultdict(list))
742
+ )
745
743
 
746
744
  while min(num_data_cycles) == 0:
747
745
  if shuffle:
@@ -892,9 +890,9 @@ class RasaModelData:
892
890
  Returns:
893
891
  The test and train RasaModelData
894
892
  """
895
- data_train: DefaultDict[
896
- Text, DefaultDict[Text, List[FeatureArray]]
897
- ] = defaultdict(lambda: defaultdict(list))
893
+ data_train: DefaultDict[Text, DefaultDict[Text, List[FeatureArray]]] = (
894
+ defaultdict(lambda: defaultdict(list))
895
+ )
898
896
  data_val: DefaultDict[Text, DefaultDict[Text, List[Any]]] = defaultdict(
899
897
  lambda: defaultdict(list)
900
898
  )