rasa-pro 3.13.0.dev2__py3-none-any.whl → 3.13.0.dev5__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 (173) hide show
  1. rasa/__main__.py +3 -1
  2. rasa/cli/inspect.py +8 -4
  3. rasa/cli/project_templates/default/config.yml +5 -32
  4. rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_cancels_during_a_correction.yml +1 -1
  5. rasa/cli/project_templates/{calm → default}/e2e_tests/cancelations/user_changes_mind_on_a_whim.yml +1 -1
  6. rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_handle.yml +1 -1
  7. rasa/cli/project_templates/{calm → default}/e2e_tests/corrections/user_corrects_contact_name.yml +1 -1
  8. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_adds_contact_to_their_list.yml +1 -1
  9. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_lists_contacts.yml +1 -1
  10. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact.yml +1 -1
  11. rasa/cli/project_templates/{calm → default}/e2e_tests/happy_paths/user_removes_contact_from_list.yml +1 -1
  12. rasa/cli/project_templates/default/endpoints.yml +18 -2
  13. rasa/cli/run.py +10 -6
  14. rasa/cli/scaffold.py +3 -4
  15. rasa/cli/studio/download.py +1 -1
  16. rasa/cli/studio/upload.py +0 -6
  17. rasa/cli/utils.py +7 -0
  18. rasa/core/channels/channel.py +93 -0
  19. rasa/core/channels/inspector/dist/assets/{arc-c7691751.js → arc-9f75cc3b.js} +1 -1
  20. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-ab99dff7.js → blockDiagram-38ab4fdb-7f34db23.js} +1 -1
  21. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-08c35a6b.js → c4Diagram-3d4e48cf-948bab2c.js} +1 -1
  22. rasa/core/channels/inspector/dist/assets/channel-dfa68278.js +1 -0
  23. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-9e9c71c9.js → classDiagram-70f12bd4-53b0dd0e.js} +1 -1
  24. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-15e7e2bf.js → classDiagram-v2-f2320105-fdf789e7.js} +1 -1
  25. rasa/core/channels/inspector/dist/assets/clone-edb7f119.js +1 -0
  26. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-9c105cb1.js → createText-2e5e7dd3-87c4ece5.js} +1 -1
  27. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-77e89e48.js → edges-e0da2a9e-5a8b0749.js} +1 -1
  28. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-7a011646.js → erDiagram-9861fffd-66da90e2.js} +1 -1
  29. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-b6f105ac.js → flowDb-956e92f1-10044f05.js} +1 -1
  30. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-ce4f18c2.js → flowDiagram-66a62f08-f338f66a.js} +1 -1
  31. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-65e7c670.js +1 -0
  32. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-cb5f6da4.js → flowchart-elk-definition-4a651766-b13140aa.js} +1 -1
  33. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-e4d19e28.js → ganttDiagram-c361ad54-f2b4a55a.js} +1 -1
  34. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-727b1c33.js → gitGraphDiagram-72cf32ee-dedc298d.js} +1 -1
  35. rasa/core/channels/inspector/dist/assets/{graph-6e2ab9a7.js → graph-4ede11ff.js} +1 -1
  36. rasa/core/channels/inspector/dist/assets/{index-3862675e-84ec700f.js → index-3862675e-65549d37.js} +1 -1
  37. rasa/core/channels/inspector/dist/assets/{index-098a1a24.js → index-3a23e736.js} +142 -129
  38. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-78dda442.js → infoDiagram-f8f76790-65439671.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-f1cc6dd1.js → journeyDiagram-49397b02-56d03d98.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/{layout-d98dcd0c.js → layout-dd48f7f4.js} +1 -1
  41. rasa/core/channels/inspector/dist/assets/{line-838e3d82.js → line-1569ad2c.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{linear-eae72406.js → linear-48bf4935.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-c96fd84b.js → mindmap-definition-fc14e90a-688504c1.js} +1 -1
  44. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-c936d4e2.js → pieDiagram-8a3498a8-78b6d7e6.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-b338eb8f.js → quadrantDiagram-120e2f19-048b84b3.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-c6b6c0d5.js → requirementDiagram-deff3bca-dd67f107.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-b9372e19.js → sankeyDiagram-04a897e0-8128436e.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-479e0a3f.js → sequenceDiagram-704730f1-1a0d1461.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-fd26eebc.js → stateDiagram-587899a1-46d388ed.js} +1 -1
  50. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-3233e0ae.js → stateDiagram-v2-d93cdb3a-ea42951a.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-1fdd392b.js → styles-6aaf32cf-7427ed0c.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-6d7bfa1b.js → styles-9a916d00-ff5e5a16.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-f86aab11.js → styles-c10674c1-7b3680cf.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-e3e49d7a.js → svgDrawCommon-08f97a94-f860f2ad.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-6fe08b4d.js → timeline-definition-85554ec2-2eebf0c8.js} +1 -1
  56. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-c2e06fd6.js → xychartDiagram-e933f94c-5d7f4e96.js} +1 -1
  57. rasa/core/channels/inspector/dist/index.html +1 -1
  58. rasa/core/channels/inspector/src/App.tsx +3 -2
  59. rasa/core/channels/inspector/src/components/Chat.tsx +23 -2
  60. rasa/core/channels/inspector/src/components/DiagramFlow.tsx +2 -5
  61. rasa/core/channels/inspector/src/helpers/conversation.ts +16 -0
  62. rasa/core/channels/inspector/src/types.ts +1 -1
  63. rasa/core/channels/voice_ready/audiocodes.py +41 -15
  64. rasa/core/channels/voice_ready/jambonz.py +25 -5
  65. rasa/core/channels/voice_ready/jambonz_protocol.py +4 -0
  66. rasa/core/channels/voice_ready/twilio_voice.py +48 -1
  67. rasa/core/channels/voice_stream/tts/azure.py +11 -2
  68. rasa/core/channels/voice_stream/twilio_media_streams.py +101 -26
  69. rasa/core/channels/voice_stream/voice_channel.py +28 -2
  70. rasa/core/concurrent_lock_store.py +24 -10
  71. rasa/core/information_retrieval/faiss.py +7 -68
  72. rasa/core/information_retrieval/information_retrieval.py +2 -40
  73. rasa/core/information_retrieval/milvus.py +2 -7
  74. rasa/core/information_retrieval/qdrant.py +2 -7
  75. rasa/core/lock_store.py +151 -60
  76. rasa/core/nlg/contextual_response_rephraser.py +3 -0
  77. rasa/core/policies/enterprise_search_policy.py +310 -61
  78. rasa/core/policies/intentless_policy.py +3 -0
  79. rasa/dialogue_understanding/coexistence/llm_based_router.py +8 -0
  80. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -2
  81. rasa/dialogue_understanding/generator/command_parser.py +1 -1
  82. rasa/dialogue_understanding/generator/flow_retrieval.py +1 -4
  83. rasa/dialogue_understanding/generator/llm_based_command_generator.py +1 -2
  84. rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +13 -0
  85. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +1 -1
  86. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -1
  87. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +2 -24
  88. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +22 -17
  89. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +27 -12
  90. rasa/dialogue_understanding_test/du_test_case.py +16 -8
  91. rasa/dialogue_understanding_test/io.py +8 -13
  92. rasa/e2e_test/utils/validation.py +3 -3
  93. rasa/engine/recipes/default_components.py +0 -2
  94. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +3 -0
  95. rasa/plugin.py +0 -3
  96. rasa/shared/constants.py +1 -0
  97. rasa/shared/core/domain.py +165 -11
  98. rasa/shared/core/flows/flow.py +155 -131
  99. rasa/shared/core/flows/flow_step.py +19 -3
  100. rasa/shared/core/flows/flow_step_links.py +15 -0
  101. rasa/shared/core/flows/flow_step_sequence.py +6 -0
  102. rasa/shared/core/flows/nlu_trigger.py +13 -0
  103. rasa/shared/core/flows/steps/action.py +7 -4
  104. rasa/shared/core/flows/steps/call.py +11 -4
  105. rasa/shared/core/flows/steps/collect.py +27 -6
  106. rasa/shared/core/flows/steps/internal.py +6 -1
  107. rasa/shared/core/flows/steps/link.py +7 -4
  108. rasa/shared/core/flows/steps/no_operation.py +7 -4
  109. rasa/shared/core/flows/steps/set_slots.py +8 -4
  110. rasa/shared/core/flows/yaml_flows_io.py +106 -5
  111. rasa/shared/importers/importer.py +8 -0
  112. rasa/shared/providers/_utils.py +83 -0
  113. rasa/shared/providers/llm/_base_litellm_client.py +6 -3
  114. rasa/shared/providers/llm/azure_openai_llm_client.py +6 -68
  115. rasa/shared/providers/router/_base_litellm_router_client.py +53 -1
  116. rasa/shared/utils/common.py +42 -0
  117. rasa/shared/utils/constants.py +3 -0
  118. rasa/shared/utils/llm.py +70 -24
  119. rasa/studio/download/domains.py +49 -0
  120. rasa/studio/download/download.py +439 -0
  121. rasa/studio/download/flows.py +359 -0
  122. rasa/studio/results_logger.py +6 -1
  123. rasa/studio/upload.py +69 -5
  124. rasa/tracing/instrumentation/attribute_extractors.py +7 -10
  125. rasa/tracing/instrumentation/instrumentation.py +12 -12
  126. rasa/utils/common.py +36 -0
  127. rasa/utils/endpoints.py +22 -1
  128. rasa/utils/licensing.py +1 -1
  129. rasa/validator.py +1 -2
  130. rasa/version.py +1 -1
  131. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev5.dist-info}/METADATA +7 -7
  132. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev5.dist-info}/RECORD +149 -166
  133. rasa/cli/project_templates/calm/config.yml +0 -10
  134. rasa/cli/project_templates/calm/credentials.yml +0 -33
  135. rasa/cli/project_templates/calm/endpoints.yml +0 -58
  136. rasa/cli/project_templates/default/actions/actions.py +0 -27
  137. rasa/cli/project_templates/default/data/nlu.yml +0 -91
  138. rasa/cli/project_templates/default/data/rules.yml +0 -13
  139. rasa/cli/project_templates/default/data/stories.yml +0 -30
  140. rasa/cli/project_templates/default/domain.yml +0 -34
  141. rasa/cli/project_templates/default/tests/test_stories.yml +0 -91
  142. rasa/core/channels/inspector/dist/assets/channel-11268142.js +0 -1
  143. rasa/core/channels/inspector/dist/assets/clone-ff7f2ce7.js +0 -1
  144. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-cba7ae20.js +0 -1
  145. rasa/document_retrieval/__init__.py +0 -0
  146. rasa/document_retrieval/constants.py +0 -32
  147. rasa/document_retrieval/document_post_processor.py +0 -351
  148. rasa/document_retrieval/document_post_processor_prompt_template.jinja2 +0 -0
  149. rasa/document_retrieval/document_retriever.py +0 -333
  150. rasa/document_retrieval/knowledge_base_connectors/__init__.py +0 -0
  151. rasa/document_retrieval/knowledge_base_connectors/api_connector.py +0 -39
  152. rasa/document_retrieval/knowledge_base_connectors/knowledge_base_connector.py +0 -34
  153. rasa/document_retrieval/knowledge_base_connectors/vector_store_connector.py +0 -226
  154. rasa/document_retrieval/query_rewriter.py +0 -234
  155. rasa/document_retrieval/query_rewriter_prompt_template.jinja2 +0 -8
  156. rasa/studio/download.py +0 -489
  157. /rasa/cli/project_templates/{calm → default}/actions/action_template.py +0 -0
  158. /rasa/cli/project_templates/{calm → default}/actions/add_contact.py +0 -0
  159. /rasa/cli/project_templates/{calm → default}/actions/db.py +0 -0
  160. /rasa/cli/project_templates/{calm → default}/actions/list_contacts.py +0 -0
  161. /rasa/cli/project_templates/{calm → default}/actions/remove_contact.py +0 -0
  162. /rasa/cli/project_templates/{calm → default}/data/flows/add_contact.yml +0 -0
  163. /rasa/cli/project_templates/{calm → default}/data/flows/list_contacts.yml +0 -0
  164. /rasa/cli/project_templates/{calm → default}/data/flows/remove_contact.yml +0 -0
  165. /rasa/cli/project_templates/{calm → default}/db/contacts.json +0 -0
  166. /rasa/cli/project_templates/{calm → default}/domain/add_contact.yml +0 -0
  167. /rasa/cli/project_templates/{calm → default}/domain/list_contacts.yml +0 -0
  168. /rasa/cli/project_templates/{calm → default}/domain/remove_contact.yml +0 -0
  169. /rasa/cli/project_templates/{calm → default}/domain/shared.yml +0 -0
  170. /rasa/{cli/project_templates/calm/actions → studio/download}/__init__.py +0 -0
  171. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev5.dist-info}/NOTICE +0 -0
  172. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev5.dist-info}/WHEEL +0 -0
  173. {rasa_pro-3.13.0.dev2.dist-info → rasa_pro-3.13.0.dev5.dist-info}/entry_points.txt +0 -0
rasa/shared/utils/llm.py CHANGED
@@ -1,5 +1,6 @@
1
1
  import importlib.resources
2
2
  import json
3
+ import logging
3
4
  from copy import deepcopy
4
5
  from functools import wraps
5
6
  from typing import (
@@ -7,6 +8,7 @@ from typing import (
7
8
  Any,
8
9
  Callable,
9
10
  Dict,
11
+ Literal,
10
12
  Optional,
11
13
  Text,
12
14
  Type,
@@ -59,6 +61,7 @@ from rasa.shared.providers.mappings import (
59
61
  get_embedding_client_from_provider,
60
62
  get_llm_client_from_provider,
61
63
  )
64
+ from rasa.shared.utils.constants import LOG_COMPONENT_SOURCE_METHOD_INIT
62
65
 
63
66
  if TYPE_CHECKING:
64
67
  from rasa.shared.core.trackers import DialogueStateTracker
@@ -654,35 +657,59 @@ def embedder_client_factory(
654
657
 
655
658
 
656
659
  def get_prompt_template(
657
- jinja_file_path: Optional[Text], default_prompt_template: Text
660
+ jinja_file_path: Optional[Text],
661
+ default_prompt_template: Text,
662
+ *,
663
+ log_source_component: Optional[Text] = None,
664
+ log_source_method: Optional[Literal["init", "fingerprint_addon"]] = None,
658
665
  ) -> Text:
659
666
  """Returns the jinja template.
660
667
 
661
668
  Args:
662
- jinja_file_path: the path to the jinja file
663
- default_prompt_template: the default prompt template
669
+ jinja_file_path: The path to the jinja template file. If not provided, the
670
+ default template will be used.
671
+ default_prompt_template: The fallback prompt template to use if no file is
672
+ found or specified.
673
+ log_source_component: The name of the component emitting the log, used to
674
+ identify the source in structured logging.
675
+ log_source_method: The name of the method or function emitting the log for
676
+ better traceability.
664
677
 
665
678
  Returns:
666
679
  The prompt template.
667
680
  """
681
+
668
682
  try:
669
683
  if jinja_file_path is not None:
670
684
  prompt_template = rasa.shared.utils.io.read_file(jinja_file_path)
671
- structlogger.info(
672
- "utils.llm.get_prompt_template.custom_prompt_template_read_successfull",
685
+
686
+ log_level = (
687
+ logging.INFO
688
+ if log_source_method == LOG_COMPONENT_SOURCE_METHOD_INIT
689
+ else logging.DEBUG
690
+ )
691
+
692
+ structlogger.log(
693
+ log_level,
694
+ "utils.llm.get_prompt_template"
695
+ ".custom_prompt_template_read_successfully",
673
696
  event_info=(
674
697
  f"Custom prompt template read successfully from "
675
698
  f"`{jinja_file_path}`."
676
699
  ),
677
700
  prompt_file_path=jinja_file_path,
701
+ log_source_component=log_source_component,
702
+ log_source_method=log_source_method,
678
703
  )
679
704
  return prompt_template
680
705
  except (FileIOException, FileNotFoundException):
681
706
  structlogger.warning(
682
- "utils.llm.get_prompt_template.failed_to_read_custom_prompt_template",
707
+ "utils.llm.get_prompt_template" ".failed_to_read_custom_prompt_template",
683
708
  event_info=(
684
709
  "Failed to read custom prompt template. Using default template instead."
685
710
  ),
711
+ log_source_component=log_source_component,
712
+ log_source_method=log_source_method,
686
713
  )
687
714
  return default_prompt_template
688
715
 
@@ -692,50 +719,66 @@ def get_default_prompt_template_based_on_model(
692
719
  model_prompt_mapping: Dict[str, Any],
693
720
  default_prompt_path: str,
694
721
  fallback_prompt_path: str,
722
+ *,
723
+ log_source_component: Optional[Text] = None,
724
+ log_source_method: Optional[Literal["init", "fingerprint_addon"]] = None,
695
725
  ) -> Text:
696
726
  """Returns the default prompt template based on the model name.
697
727
 
698
728
  Args:
699
729
  llm_config: The model config.
700
- model_prompt_mapping: The mapping of model name to prompt template.
701
- default_prompt_path: The default prompt path of the component.
702
- fallback_prompt_path: The fallback prompt path for all other models
703
- that do not have a mapping in the model_prompt_mapping.
730
+ model_prompt_mapping: The model name -> prompt template mapping.
731
+ default_prompt_path: The path to the default prompt template for the component.
732
+ fallback_prompt_path: The fallback prompt path for all other models that do not
733
+ have a mapping in the model_prompt_mapping.
734
+ log_source_component: The name of the component emitting the log, used to
735
+ identify the source in structured logging.
736
+ log_source_method: The name of the method or function emitting the log for
737
+ better traceability.
704
738
 
705
739
  Returns:
706
740
  The default prompt template.
707
741
  """
742
+ # Extract the provider and model name information from the configuration
708
743
  _llm_config = deepcopy(llm_config)
709
744
  if MODELS_CONFIG_KEY in _llm_config:
710
745
  _llm_config = _llm_config[MODELS_CONFIG_KEY][0]
711
746
  provider = _llm_config.get(PROVIDER_CONFIG_KEY)
712
747
  model = _llm_config.get(MODEL_CONFIG_KEY)
748
+
749
+ # If the model is not defined, we default to the default prompt template.
713
750
  if not model:
714
- # If the model is not defined, we default to the default prompt template.
715
- structlogger.info(
716
- "utils.llm.get_default_prompt_template_based_on_model.using_default_prompt_template",
751
+ structlogger.debug(
752
+ "utils.llm.get_default_prompt_template_based_on_model"
753
+ ".using_default_prompt_template",
717
754
  event_info=(
718
755
  f"Model not defined in the config. Default prompt template read from"
719
756
  f" - `{default_prompt_path}`."
720
757
  ),
721
758
  default_prompt_path=default_prompt_path,
759
+ log_source_component=log_source_component,
760
+ log_source_method=log_source_method,
722
761
  )
723
762
  return importlib.resources.read_text(
724
763
  DEFAULT_PROMPT_PACKAGE_NAME, default_prompt_path
725
764
  )
726
765
 
727
- model_name = model if provider and provider in model else f"{provider}/{model}"
728
- if prompt_file_path := model_prompt_mapping.get(model_name):
729
- # If the model is found in the mapping, we use the model-specific prompt
730
- # template.
731
- structlogger.info(
732
- "utils.llm.get_default_prompt_template_based_on_model.using_model_specific_prompt_template",
766
+ full_model_name = model if provider and provider in model else f"{provider}/{model}"
767
+
768
+ # If the model is found in the mapping, we use the model-specific prompt
769
+ # template.
770
+ if prompt_file_path := model_prompt_mapping.get(full_model_name):
771
+ structlogger.debug(
772
+ "utils.llm.get_default_prompt_template_based_on_model"
773
+ ".using_model_specific_prompt_template",
733
774
  event_info=(
734
775
  f"Using model-specific default prompt template. Default prompt "
735
776
  f"template read from - `{prompt_file_path}`."
736
777
  ),
737
778
  default_prompt_path=prompt_file_path,
738
- model_name=model_name,
779
+ model_name=full_model_name,
780
+ log_source_component=log_source_component,
781
+ log_source_method=log_source_method,
739
782
  )
740
783
  return importlib.resources.read_text(
741
784
  DEFAULT_PROMPT_PACKAGE_NAME, prompt_file_path
@@ -743,14 +786,17 @@ def get_default_prompt_template_based_on_model(
743
786
 
744
787
  # If the model is not found in the mapping, we default to the fallback prompt
745
788
  # template.
746
- structlogger.info(
747
- "utils.llm.get_default_prompt_template_based_on_model.using_fallback_prompt_template",
789
+ structlogger.debug(
790
+ "utils.llm.get_default_prompt_template_based_on_model"
791
+ ".using_fallback_prompt_template",
748
792
  event_info=(
749
793
  f"Model not found in the model prompt mapping. Fallback prompt template "
750
794
  f"read from - `{fallback_prompt_path}`."
751
795
  ),
752
796
  fallback_prompt_path=fallback_prompt_path,
753
- model_name=model_name,
797
+ model_name=full_model_name,
798
+ log_source_component=log_source_component,
799
+ log_source_method=log_source_method,
754
800
  )
755
801
  return importlib.resources.read_text(
756
802
  DEFAULT_PROMPT_PACKAGE_NAME, fallback_prompt_path
@@ -762,7 +808,7 @@ def allowed_values_for_slot(slot: Slot) -> Union[str, None]:
762
808
  if isinstance(slot, BooleanSlot):
763
809
  return str([True, False])
764
810
  if isinstance(slot, CategoricalSlot):
765
- return str([v for v in slot.values if v != "__other__"] + ["other"])
811
+ return str([v for v in slot.values if v != "__other__"])
766
812
  else:
767
813
  return None
768
814
 
@@ -0,0 +1,49 @@
1
+ import logging
2
+ from pathlib import Path
3
+
4
+ from rasa.shared.core.domain import (
5
+ Domain,
6
+ )
7
+ from rasa.shared.importers.importer import TrainingDataImporter
8
+ from rasa.studio.constants import STUDIO_DOMAIN_FILENAME
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+
13
+ def merge_domain_with_overwrite(
14
+ data_from_studio: TrainingDataImporter,
15
+ data_local: TrainingDataImporter,
16
+ domain_path: Path,
17
+ ) -> None:
18
+ """
19
+ Merges the domain from Rasa Studio with the local domain.
20
+
21
+ Args:
22
+ data_from_studio: The training data importer for the Rasa Studio domain.
23
+ data_local: The training data importer for the local domain.
24
+ domain_path: The path to the domain file or directory.
25
+ """
26
+ if domain_path.is_file():
27
+ all_local_domain_files = [str(domain_path)]
28
+ domain_path = domain_path.parent
29
+ else:
30
+ all_local_domain_files = data_local.get_domain_files([str(domain_path)])
31
+
32
+ # leftover_domain represents the items in the studio
33
+ # domain that are not in the local domain
34
+ leftover_domain = data_from_studio.get_user_domain()
35
+ for file_path in all_local_domain_files:
36
+ # For each local domain file, we do a partial merge
37
+ local_domain = Domain.from_file(str(file_path))
38
+ updated_local_domain = local_domain.partial_merge(leftover_domain)
39
+
40
+ # If this partial merge made changes, persist them
41
+ if local_domain != updated_local_domain:
42
+ updated_local_domain.persist(file_path)
43
+
44
+ # Remove every item now present in updated_local_domain from leftover_domain
45
+ leftover_domain = leftover_domain.difference(updated_local_domain)
46
+
47
+ # If there are still items in leftover_domain, persist them
48
+ if not leftover_domain.is_empty():
49
+ leftover_domain.persist(domain_path / STUDIO_DOMAIN_FILENAME)
@@ -0,0 +1,439 @@
1
+ import argparse
2
+ from pathlib import Path
3
+ from typing import Dict, List, Optional, Tuple
4
+
5
+ import questionary
6
+ import structlog
7
+ from ruamel import yaml
8
+ from ruamel.yaml.scalarstring import LiteralScalarString
9
+
10
+ import rasa.cli.utils
11
+ import rasa.shared.utils.cli
12
+ from rasa.shared.constants import (
13
+ DEFAULT_CONFIG_PATH,
14
+ DEFAULT_DATA_PATH,
15
+ DEFAULT_DOMAIN_PATH,
16
+ DEFAULT_DOMAIN_PATHS,
17
+ DEFAULT_ENDPOINTS_PATH,
18
+ )
19
+ from rasa.shared.core.flows.yaml_flows_io import YamlFlowsWriter
20
+ from rasa.shared.importers.importer import TrainingDataImporter
21
+ from rasa.shared.utils.yaml import read_yaml
22
+ from rasa.studio import data_handler
23
+ from rasa.studio.config import StudioConfig
24
+ from rasa.studio.constants import (
25
+ STUDIO_DOMAIN_FILENAME,
26
+ STUDIO_FLOWS_FILENAME,
27
+ STUDIO_NLU_FILENAME,
28
+ )
29
+ from rasa.studio.data_handler import StudioDataHandler, import_data_from_studio
30
+ from rasa.studio.download.domains import merge_domain_with_overwrite
31
+ from rasa.studio.download.flows import merge_flows_with_overwrite
32
+ from rasa.utils.mapper import RasaPrimitiveStorageMapper
33
+
34
+ structlogger = structlog.getLogger(__name__)
35
+
36
+
37
+ def handle_download(args: argparse.Namespace) -> None:
38
+ """Main function to handle downloading and merging data.
39
+
40
+ Args:
41
+ args: The command line arguments.
42
+ """
43
+ handler = StudioDataHandler(
44
+ studio_config=StudioConfig.read_config(),
45
+ assistant_name=args.assistant_name[0],
46
+ )
47
+ handler.request_all_data()
48
+
49
+ domain_path, data_paths = _prepare_data_and_domain_paths(args)
50
+
51
+ # Handle config and endpoints.
52
+ config_path, write_config = _handle_file_overwrite(
53
+ args.config, DEFAULT_CONFIG_PATH, "config"
54
+ )
55
+ endpoints_path, write_endpoints = _handle_file_overwrite(
56
+ args.endpoints, DEFAULT_ENDPOINTS_PATH, "endpoints"
57
+ )
58
+ message_parts = []
59
+ config_path = config_path if write_config else None
60
+ endpoints_path = endpoints_path if write_endpoints else None
61
+
62
+ if config_path:
63
+ config_data = handler.get_config()
64
+ if not config_data:
65
+ rasa.shared.utils.cli.print_error_and_exit("No config data found.")
66
+ with open(config_path, "w") as f:
67
+ f.write(config_data)
68
+ message_parts.append(f"config to '{config_path}'")
69
+ if endpoints_path:
70
+ endpoints_data = handler.get_endpoints()
71
+ if not endpoints_data:
72
+ raise ValueError("No endpoints data found.")
73
+ with open(endpoints_path, "w") as f:
74
+ f.write(endpoints_data)
75
+ message_parts.append(f"endpoints to '{endpoints_path}'")
76
+ if message_parts:
77
+ message = "Downloaded " + " and ".join(message_parts)
78
+ structlogger.info("studio.download.config_endpoints", event_info=message)
79
+
80
+ if not args.overwrite:
81
+ _handle_download_no_overwrite(handler, domain_path, data_paths)
82
+ else:
83
+ _handle_download_with_overwrite(handler, domain_path, data_paths)
84
+
85
+
86
+ def _prepare_data_and_domain_paths(args: argparse.Namespace) -> Tuple[Path, List[Path]]:
87
+ """Prepars the domain and data paths based on the provided arguments.
88
+
89
+ Args:
90
+ args: The command line arguments.
91
+
92
+ Returns:
93
+ A tuple containing the domain path and a list of data paths.
94
+ """
95
+ # Prepare domain path.
96
+ domain_path = rasa.cli.utils.get_validated_path(
97
+ args.domain, "domain", DEFAULT_DOMAIN_PATHS, none_is_valid=True
98
+ )
99
+ domain_or_default_path = args.domain or DEFAULT_DOMAIN_PATH
100
+
101
+ if domain_path is None:
102
+ domain_path = Path(domain_or_default_path)
103
+ domain_path.touch()
104
+
105
+ if isinstance(domain_path, str):
106
+ domain_path = Path(domain_path)
107
+
108
+ if domain_path.is_file():
109
+ if not args.overwrite:
110
+ domain_path.unlink()
111
+ domain_path.touch()
112
+
113
+ if domain_path.is_dir():
114
+ if not args.overwrite:
115
+ domain_path = domain_path / STUDIO_DOMAIN_FILENAME
116
+ domain_path.touch()
117
+
118
+ # Prepare data paths.
119
+ data_paths: List[Path] = []
120
+ for f in args.data:
121
+ data_path = rasa.cli.utils.get_validated_path(
122
+ f, "data", DEFAULT_DATA_PATH, none_is_valid=True
123
+ )
124
+
125
+ if data_path is None:
126
+ data_path = Path(f)
127
+ data_path.mkdir(parents=True, exist_ok=True)
128
+ else:
129
+ data_path = Path(data_path)
130
+
131
+ if data_path.is_file() or data_path.is_dir():
132
+ data_paths.append(data_path)
133
+ else:
134
+ data_path.mkdir(parents=True, exist_ok=True)
135
+ data_paths.append(data_path)
136
+
137
+ # Remove duplicates while preserving order.
138
+ data_paths = list(dict.fromkeys(data_paths))
139
+ return domain_path, data_paths
140
+
141
+
142
+ def _handle_file_overwrite(
143
+ file_path: Optional[str], default_path: str, file_type: str
144
+ ) -> Tuple[Optional[Path], bool]:
145
+ """Determines whether to overwrite an existing file or create a new one.
146
+
147
+ Works for config and endpoints at this moment.
148
+
149
+ Args:
150
+ file_path: The path to the file.
151
+ default_path: The default path to use if no file path is provided.
152
+ file_type: The type of the file (e.g., config, endpoints).
153
+
154
+ Returns:
155
+ A tuple of the file path and a boolean indicating overwrite status.
156
+ """
157
+ file_already_exists = rasa.cli.utils.get_validated_path(
158
+ file_path, file_type, default_path, none_is_valid=True
159
+ )
160
+ write_file = False
161
+ path = None
162
+ file_or_default_path = file_path or default_path
163
+
164
+ if file_already_exists is None:
165
+ path = Path(file_or_default_path)
166
+ if path.is_dir():
167
+ path = path / default_path
168
+ return path, True
169
+
170
+ if questionary.confirm(
171
+ f"{file_type.capitalize()} file '{file_or_default_path}' already exists. "
172
+ f"Do you want to overwrite it?"
173
+ ).ask():
174
+ write_file = True
175
+ path = Path(file_or_default_path)
176
+ return path, write_file
177
+
178
+
179
+ def _handle_download_no_overwrite(
180
+ handler: StudioDataHandler, domain_path: Path, data_paths: List[Path]
181
+ ) -> None:
182
+ """Handles downloading without overwriting existing files.
183
+
184
+ Args:
185
+ handler: The StudioDataHandler instance.
186
+ domain_path: The path to the domain file or directory.
187
+ data_paths: The paths to the data files or directories.
188
+ """
189
+ data_from_studio, data_local = import_data_from_studio(
190
+ handler, domain_path, data_paths
191
+ )
192
+ _merge_domain_no_overwrite(domain_path, data_from_studio, data_local)
193
+ _merge_data_no_overwrite(data_paths, handler, data_from_studio, data_local)
194
+
195
+
196
+ def _merge_domain_no_overwrite(
197
+ domain_path: Path,
198
+ data_from_studio: TrainingDataImporter,
199
+ data_local: TrainingDataImporter,
200
+ ) -> None:
201
+ """
202
+ Merges the domain data without overwriting.
203
+
204
+ If the domain path is a directory, a new domain file is created under that folder.
205
+ If the domain path is a file, it merges both domains into that file.
206
+
207
+ Args:
208
+ domain_path: The path to the domain file or directory.
209
+ data_from_studio: The Studio data importer.
210
+ data_local: The local data importer.
211
+ """
212
+ if domain_path.is_dir():
213
+ _merge_directory_domain(domain_path, data_from_studio, data_local)
214
+ elif domain_path.is_file():
215
+ _merge_file_domain(domain_path, data_from_studio, data_local)
216
+
217
+
218
+ def _merge_directory_domain(
219
+ domain_path: Path,
220
+ data_from_studio: TrainingDataImporter,
221
+ data_local: TrainingDataImporter,
222
+ ) -> None:
223
+ """Merges domain data without overwriting when the domain is a directory.
224
+
225
+ Args:
226
+ domain_path: The path to the domain directory.
227
+ data_from_studio: The Studio data importer.
228
+ data_local: The local data importer.
229
+ """
230
+ from rasa.shared.core.domain import Domain
231
+
232
+ studio_domain_path = domain_path / STUDIO_NLU_FILENAME
233
+ new_domain_data = data_handler.combine_domains(
234
+ data_from_studio.get_user_domain().as_dict(),
235
+ data_local.get_user_domain().as_dict(),
236
+ )
237
+ studio_domain = Domain.from_dict(new_domain_data)
238
+
239
+ if not studio_domain.is_empty():
240
+ studio_domain.persist(studio_domain_path)
241
+ else:
242
+ structlogger.warning(
243
+ "studio.download.merge_directory_domain",
244
+ event_info="No additional domain data found in Studio assistant.",
245
+ )
246
+
247
+
248
+ def _merge_file_domain(
249
+ domain_path: Path,
250
+ data_from_studio: TrainingDataImporter,
251
+ data_local: TrainingDataImporter,
252
+ ) -> None:
253
+ """Merges domain data without overwriting when the domain is a file.
254
+
255
+ Args:
256
+ domain_path: The path to the domain file.
257
+ data_from_studio: The Studio data importer.
258
+ data_local: The local data importer.
259
+ """
260
+ domain_merged = data_local.get_user_domain().merge(
261
+ data_from_studio.get_user_domain()
262
+ )
263
+ domain_merged.persist(domain_path)
264
+
265
+
266
+ def _merge_data_no_overwrite(
267
+ data_paths: List[Path],
268
+ handler: StudioDataHandler,
269
+ data_from_studio: TrainingDataImporter,
270
+ data_local: TrainingDataImporter,
271
+ ) -> None:
272
+ """Merges NLU and flow data without overwriting existing data.
273
+
274
+ Args:
275
+ data_paths: The paths to the data files or directories.
276
+ handler: The StudioDataHandler instance.
277
+ data_from_studio: The Studio data importer.
278
+ data_local: The local data importer.
279
+ """
280
+ if not data_paths:
281
+ structlogger.warning(
282
+ "studio.download.merge_data_no_overwrite.no_path",
283
+ event_info="No data paths provided. Skipping data merge.",
284
+ )
285
+ return
286
+
287
+ if len(data_paths) == 1:
288
+ data_path = data_paths[0]
289
+ if data_path.is_file():
290
+ _merge_file_data_no_overwrite(
291
+ data_path, handler, data_from_studio, data_local
292
+ )
293
+ elif data_path.is_dir():
294
+ _merge_dir_data_no_overwrite(
295
+ data_path, handler, data_from_studio, data_local
296
+ )
297
+ else:
298
+ structlogger.warning(
299
+ "studio.download.merge_data_no_overwrite.invalid_path",
300
+ event_info=(
301
+ f"Provided path '{data_path}' is neither a file nor a directory."
302
+ ),
303
+ )
304
+ else:
305
+ # TODO: Handle multiple data paths.
306
+ raise NotImplementedError("Multiple data paths are not supported yet.")
307
+
308
+
309
+ def _merge_file_data_no_overwrite(
310
+ data_path: Path,
311
+ handler: StudioDataHandler,
312
+ data_from_studio: TrainingDataImporter,
313
+ data_local: TrainingDataImporter,
314
+ ) -> None:
315
+ """Merges NLU and flows data into a single file without overwriting.
316
+
317
+ Args:
318
+ data_path: Path to the single data file.
319
+ handler: The StudioDataHandler instance.
320
+ data_from_studio: The Studio data importer.
321
+ data_local: The local data importer.
322
+ """
323
+ if handler.has_nlu():
324
+ merged_nlu = data_local.get_nlu_data().merge(data_from_studio.get_nlu_data())
325
+ merged_nlu.persist_nlu(str(data_path))
326
+ if handler.has_flows():
327
+ merged_flows = data_local.get_user_flows().merge(
328
+ data_from_studio.get_user_flows()
329
+ )
330
+ YamlFlowsWriter.dump(merged_flows.underlying_flows, data_path)
331
+
332
+
333
+ def _merge_dir_data_no_overwrite(
334
+ dir_path: Path,
335
+ handler: StudioDataHandler,
336
+ data_from_studio: TrainingDataImporter,
337
+ data_local: TrainingDataImporter,
338
+ ) -> None:
339
+ """Merges NLU and flows data into a single directory without overwriting.
340
+
341
+ Args:
342
+ dir_path: Path to the data directory.
343
+ handler: The StudioDataHandler instance.
344
+ data_from_studio: The Studio data importer.
345
+ data_local: The local data importer.
346
+ """
347
+ if handler.has_nlu():
348
+ nlu_path = dir_path / Path(STUDIO_NLU_FILENAME)
349
+ _persist_nlu_diff(data_local, data_from_studio, nlu_path)
350
+ if handler.has_flows():
351
+ flows_path = dir_path / Path(STUDIO_FLOWS_FILENAME)
352
+ _persist_flows_diff(data_local, data_from_studio, flows_path)
353
+
354
+
355
+ def _handle_download_with_overwrite(
356
+ handler: StudioDataHandler, domain_path: Path, data_paths: List[Path]
357
+ ) -> None:
358
+ """Handles downloading and merging data when the user opts for overwrite.
359
+
360
+ Args:
361
+ handler: The StudioDataHandler instance.
362
+ domain_path: The path to the domain file or directory.
363
+ data_paths: The paths to the data files or directories.
364
+ """
365
+ data_from_studio, data_local = import_data_from_studio(
366
+ handler, domain_path, data_paths
367
+ )
368
+ mapper = RasaPrimitiveStorageMapper(
369
+ domain_path=domain_path, training_data_paths=data_paths
370
+ )
371
+ merge_domain_with_overwrite(data_from_studio, data_local, domain_path)
372
+ merge_flows_with_overwrite(
373
+ data_paths, handler, data_from_studio, data_local, mapper
374
+ )
375
+
376
+
377
+ def _persist_nlu_diff(
378
+ data_local: TrainingDataImporter,
379
+ data_from_studio: TrainingDataImporter,
380
+ data_path: Path,
381
+ ) -> None:
382
+ """Creates a new NLU file from the diff of local and studio data.
383
+
384
+ Args:
385
+ data_local: The local training data.
386
+ data_from_studio: The training data from Rasa Studio.
387
+ data_path: The path to the NLU file.
388
+ """
389
+ new_nlu_data = data_handler.create_new_nlu_from_diff(
390
+ read_yaml(data_from_studio.get_nlu_data().nlu_as_yaml()),
391
+ read_yaml(data_local.get_nlu_data().nlu_as_yaml()),
392
+ )
393
+ if new_nlu_data["nlu"]:
394
+ pretty_write_nlu_yaml(new_nlu_data, data_path)
395
+ else:
396
+ structlogger.warning(
397
+ "studio.download.persist_nlu_diff",
398
+ event_info="No additional nlu data found.",
399
+ )
400
+
401
+
402
+ def _persist_flows_diff(
403
+ data_local: TrainingDataImporter,
404
+ data_from_studio: TrainingDataImporter,
405
+ data_path: Path,
406
+ ) -> None:
407
+ """Creates a new flows file from the diff of local and studio data.
408
+
409
+ Args:
410
+ data_local: The local training data.
411
+ data_from_studio: The training data from Rasa Studio.
412
+ data_path: The path to the flows file.
413
+ """
414
+ new_flows_data = data_handler.create_new_flows_from_diff(
415
+ data_from_studio.get_user_flows().underlying_flows,
416
+ data_local.get_user_flows().underlying_flows,
417
+ )
418
+ if new_flows_data:
419
+ YamlFlowsWriter.dump(new_flows_data, data_path)
420
+ else:
421
+ structlogger.warning(
422
+ "studio.download.persist_flows_diff",
423
+ event_info="No additional flows data found.",
424
+ )
425
+
426
+
427
+ def pretty_write_nlu_yaml(data: Dict, file: Path) -> None:
428
+ """Writes the NLU YAML in a pretty way.
429
+
430
+ Args:
431
+ data: The data to write.
432
+ file: The file to write to.
433
+ """
434
+ dumper = yaml.YAML()
435
+ for item in data["nlu"]:
436
+ if item.get("examples"):
437
+ item["examples"] = LiteralScalarString(item["examples"])
438
+ with file.open("w", encoding="utf-8") as outfile:
439
+ dumper.dump(data, outfile)