rasa-pro 3.13.0.dev20250612__py3-none-any.whl → 3.13.0rc1__py3-none-any.whl

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

Potentially problematic release.


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

Files changed (252) hide show
  1. rasa/__main__.py +0 -3
  2. rasa/api.py +1 -1
  3. rasa/cli/dialogue_understanding_test.py +1 -1
  4. rasa/cli/e2e_test.py +1 -8
  5. rasa/cli/evaluate.py +1 -1
  6. rasa/cli/export.py +3 -1
  7. rasa/cli/llm_fine_tuning.py +12 -11
  8. rasa/cli/project_templates/defaults.py +133 -0
  9. rasa/cli/project_templates/tutorial/config.yml +1 -1
  10. rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
  11. rasa/cli/run.py +1 -1
  12. rasa/cli/studio/download.py +1 -23
  13. rasa/cli/studio/link.py +52 -0
  14. rasa/cli/studio/pull.py +79 -0
  15. rasa/cli/studio/push.py +78 -0
  16. rasa/cli/studio/studio.py +12 -0
  17. rasa/cli/studio/train.py +0 -1
  18. rasa/cli/studio/upload.py +8 -0
  19. rasa/cli/train.py +1 -1
  20. rasa/cli/utils.py +1 -1
  21. rasa/cli/x.py +1 -1
  22. rasa/constants.py +2 -0
  23. rasa/core/__init__.py +0 -16
  24. rasa/core/actions/action.py +5 -1
  25. rasa/core/actions/action_repeat_bot_messages.py +18 -22
  26. rasa/core/actions/action_run_slot_rejections.py +0 -1
  27. rasa/core/agent.py +16 -1
  28. rasa/core/available_endpoints.py +146 -0
  29. rasa/core/brokers/pika.py +1 -2
  30. rasa/core/channels/__init__.py +2 -0
  31. rasa/core/channels/botframework.py +2 -2
  32. rasa/core/channels/channel.py +2 -2
  33. rasa/core/channels/development_inspector.py +1 -1
  34. rasa/core/channels/facebook.py +1 -4
  35. rasa/core/channels/hangouts.py +8 -5
  36. rasa/core/channels/inspector/README.md +3 -3
  37. rasa/core/channels/inspector/dist/assets/{arc-c4b064fc.js → arc-371401b1.js} +1 -1
  38. rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-215b5026.js → blockDiagram-38ab4fdb-3f126156.js} +1 -1
  39. rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-2b54a0a3.js → c4Diagram-3d4e48cf-12f22eb7.js} +1 -1
  40. rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +1 -0
  41. rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-daacea5f.js → classDiagram-70f12bd4-03b1d386.js} +1 -1
  42. rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-930d4dc2.js → classDiagram-v2-f2320105-84f69d63.js} +1 -1
  43. rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +1 -0
  44. rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-83c206ba.js → createText-2e5e7dd3-ca47fd38.js} +1 -1
  45. rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b0eb01d0.js → edges-e0da2a9e-f837ca8a.js} +1 -1
  46. rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-17586500.js → erDiagram-9861fffd-8717ac54.js} +1 -1
  47. rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-be2a1776.js → flowDb-956e92f1-94f38b83.js} +1 -1
  48. rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-c2120ebd.js → flowDiagram-66a62f08-b616f9fb.js} +1 -1
  49. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +1 -0
  50. rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-a6ab5c48.js → flowchart-elk-definition-4a651766-f5d24bb8.js} +1 -1
  51. rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-ef613457.js → ganttDiagram-c361ad54-b43ba8d9.js} +1 -1
  52. rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-d59185b3.js → gitGraphDiagram-72cf32ee-c3aafaa5.js} +1 -1
  53. rasa/core/channels/inspector/dist/assets/{graph-0f155405.js → graph-0d0a2c10.js} +1 -1
  54. rasa/core/channels/inspector/dist/assets/{index-3862675e-d5f1d1b7.js → index-3862675e-58ea0305.js} +1 -1
  55. rasa/core/channels/inspector/dist/assets/{index-47737d3a.js → index-cce6f8a1.js} +3 -3
  56. rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b07d141f.js → infoDiagram-f8f76790-b8f60461.js} +1 -1
  57. rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-1936d429.js → journeyDiagram-49397b02-95be5545.js} +1 -1
  58. rasa/core/channels/inspector/dist/assets/{layout-dde8d0f3.js → layout-da885b9b.js} +1 -1
  59. rasa/core/channels/inspector/dist/assets/{line-0c2c7ee0.js → line-f1c817d3.js} +1 -1
  60. rasa/core/channels/inspector/dist/assets/{linear-35dd89a4.js → linear-d42801e6.js} +1 -1
  61. rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-56192851.js → mindmap-definition-fc14e90a-a38923a6.js} +1 -1
  62. rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-fc21ed78.js → pieDiagram-8a3498a8-ca6e71e9.js} +1 -1
  63. rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-25e98518.js → quadrantDiagram-120e2f19-b290dae9.js} +1 -1
  64. rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-546ff1f5.js → requirementDiagram-deff3bca-03f02ceb.js} +1 -1
  65. rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-02d8b82d.js → sankeyDiagram-04a897e0-c49eee40.js} +1 -1
  66. rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3ca5a92e.js → sequenceDiagram-704730f1-b2cd6a3d.js} +1 -1
  67. rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-128ea07c.js → stateDiagram-587899a1-e53a2028.js} +1 -1
  68. rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-95f290af.js → stateDiagram-v2-d93cdb3a-e1982a03.js} +1 -1
  69. rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-4984898a.js → styles-6aaf32cf-d0226ca5.js} +1 -1
  70. rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1bf266ba.js → styles-9a916d00-0e21dc00.js} +1 -1
  71. rasa/core/channels/inspector/dist/assets/{styles-c10674c1-60521c63.js → styles-c10674c1-9588494e.js} +1 -1
  72. rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-a25b6e12.js → svgDrawCommon-08f97a94-be478d4f.js} +1 -1
  73. rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-0fc086bf.js → timeline-definition-85554ec2-74631749.js} +1 -1
  74. rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-44ee592e.js → xychartDiagram-e933f94c-a043552f.js} +1 -1
  75. rasa/core/channels/inspector/dist/index.html +1 -1
  76. rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +1 -1
  77. rasa/core/channels/mattermost.py +1 -1
  78. rasa/core/channels/rasa_chat.py +2 -4
  79. rasa/core/channels/rest.py +5 -4
  80. rasa/core/channels/socketio.py +56 -41
  81. rasa/core/channels/studio_chat.py +314 -10
  82. rasa/core/channels/vier_cvg.py +1 -2
  83. rasa/core/channels/voice_ready/audiocodes.py +2 -9
  84. rasa/core/channels/voice_stream/asr/azure.py +9 -0
  85. rasa/core/channels/voice_stream/audiocodes.py +8 -5
  86. rasa/core/channels/voice_stream/browser_audio.py +1 -1
  87. rasa/core/channels/voice_stream/genesys.py +2 -2
  88. rasa/core/channels/voice_stream/jambonz.py +166 -0
  89. rasa/core/channels/voice_stream/tts/__init__.py +8 -0
  90. rasa/core/channels/voice_stream/twilio_media_streams.py +17 -5
  91. rasa/core/channels/voice_stream/voice_channel.py +44 -24
  92. rasa/core/exporter.py +36 -0
  93. rasa/core/http_interpreter.py +3 -7
  94. rasa/core/information_retrieval/faiss.py +18 -11
  95. rasa/core/information_retrieval/ingestion/faq_parser.py +158 -0
  96. rasa/core/jobs.py +2 -1
  97. rasa/core/nlg/contextual_response_rephraser.py +48 -12
  98. rasa/core/nlg/generator.py +0 -1
  99. rasa/core/nlg/interpolator.py +2 -3
  100. rasa/core/nlg/summarize.py +39 -5
  101. rasa/core/policies/enterprise_search_policy.py +298 -184
  102. rasa/core/policies/enterprise_search_policy_config.py +241 -0
  103. rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2 +64 -0
  104. rasa/core/policies/flow_policy.py +1 -1
  105. rasa/core/policies/flows/flow_executor.py +96 -17
  106. rasa/core/policies/intentless_policy.py +71 -26
  107. rasa/core/processor.py +104 -51
  108. rasa/core/run.py +33 -11
  109. rasa/core/tracker_stores/tracker_store.py +1 -1
  110. rasa/core/training/interactive.py +1 -1
  111. rasa/core/utils.py +35 -99
  112. rasa/dialogue_understanding/coexistence/intent_based_router.py +2 -1
  113. rasa/dialogue_understanding/coexistence/llm_based_router.py +13 -17
  114. rasa/dialogue_understanding/commands/__init__.py +4 -0
  115. rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -0
  116. rasa/dialogue_understanding/commands/cancel_flow_command.py +6 -2
  117. rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -0
  118. rasa/dialogue_understanding/commands/clarify_command.py +7 -3
  119. rasa/dialogue_understanding/commands/command_syntax_manager.py +1 -0
  120. rasa/dialogue_understanding/commands/correct_slots_command.py +5 -6
  121. rasa/dialogue_understanding/commands/error_command.py +1 -1
  122. rasa/dialogue_understanding/commands/human_handoff_command.py +3 -3
  123. rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -0
  124. rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -0
  125. rasa/dialogue_understanding/commands/set_slot_command.py +15 -5
  126. rasa/dialogue_understanding/commands/skip_question_command.py +3 -3
  127. rasa/dialogue_understanding/commands/start_flow_command.py +7 -3
  128. rasa/dialogue_understanding/commands/utils.py +26 -2
  129. rasa/dialogue_understanding/generator/__init__.py +7 -1
  130. rasa/dialogue_understanding/generator/command_generator.py +15 -3
  131. rasa/dialogue_understanding/generator/command_parser.py +2 -2
  132. rasa/dialogue_understanding/generator/command_parser_validator.py +63 -0
  133. rasa/dialogue_understanding/generator/constants.py +2 -2
  134. rasa/dialogue_understanding/generator/nlu_command_adapter.py +2 -2
  135. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +0 -2
  136. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -0
  137. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +1 -0
  138. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +79 -0
  139. rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +79 -0
  140. rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +28 -463
  141. rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +147 -0
  142. rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +461 -0
  143. rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -64
  144. rasa/dialogue_understanding/patterns/cancel.py +1 -2
  145. rasa/dialogue_understanding/patterns/clarify.py +1 -1
  146. rasa/dialogue_understanding/patterns/correction.py +2 -2
  147. rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +37 -25
  148. rasa/dialogue_understanding/patterns/domain_for_patterns.py +190 -0
  149. rasa/dialogue_understanding/processor/command_processor.py +11 -12
  150. rasa/dialogue_understanding/processor/command_processor_component.py +3 -3
  151. rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +17 -4
  152. rasa/dialogue_understanding/stack/utils.py +3 -1
  153. rasa/dialogue_understanding/utils.py +68 -12
  154. rasa/dialogue_understanding_test/du_test_case.py +1 -1
  155. rasa/dialogue_understanding_test/du_test_runner.py +4 -22
  156. rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +2 -6
  157. rasa/e2e_test/e2e_test_coverage_report.py +1 -1
  158. rasa/e2e_test/e2e_test_runner.py +1 -1
  159. rasa/engine/constants.py +1 -1
  160. rasa/engine/graph.py +2 -2
  161. rasa/engine/recipes/default_recipe.py +26 -2
  162. rasa/engine/validation.py +3 -2
  163. rasa/hooks.py +0 -28
  164. rasa/llm_fine_tuning/annotation_module.py +39 -9
  165. rasa/llm_fine_tuning/conversations.py +3 -0
  166. rasa/llm_fine_tuning/llm_data_preparation_module.py +66 -49
  167. rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +5 -7
  168. rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +52 -44
  169. rasa/llm_fine_tuning/paraphrasing_module.py +10 -12
  170. rasa/llm_fine_tuning/storage.py +4 -4
  171. rasa/llm_fine_tuning/utils.py +63 -1
  172. rasa/model_manager/model_api.py +88 -0
  173. rasa/model_manager/trainer_service.py +4 -4
  174. rasa/plugin.py +1 -11
  175. rasa/privacy/__init__.py +0 -0
  176. rasa/privacy/constants.py +83 -0
  177. rasa/privacy/event_broker_utils.py +77 -0
  178. rasa/privacy/privacy_config.py +281 -0
  179. rasa/privacy/privacy_config_schema.json +86 -0
  180. rasa/privacy/privacy_filter.py +340 -0
  181. rasa/privacy/privacy_manager.py +576 -0
  182. rasa/server.py +23 -2
  183. rasa/shared/constants.py +18 -0
  184. rasa/shared/core/command_payload_reader.py +1 -5
  185. rasa/shared/core/constants.py +4 -3
  186. rasa/shared/core/domain.py +7 -0
  187. rasa/shared/core/events.py +38 -10
  188. rasa/shared/core/flows/constants.py +2 -0
  189. rasa/shared/core/flows/flow.py +127 -14
  190. rasa/shared/core/flows/flows_list.py +18 -1
  191. rasa/shared/core/flows/flows_yaml_schema.json +3 -0
  192. rasa/shared/core/flows/steps/collect.py +46 -2
  193. rasa/shared/core/flows/steps/link.py +7 -2
  194. rasa/shared/core/flows/validation.py +25 -5
  195. rasa/shared/core/slots.py +28 -0
  196. rasa/shared/core/training_data/story_reader/yaml_story_reader.py +1 -4
  197. rasa/shared/exceptions.py +4 -0
  198. rasa/shared/providers/_configs/azure_openai_client_config.py +6 -2
  199. rasa/shared/providers/_configs/default_litellm_client_config.py +1 -1
  200. rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +1 -1
  201. rasa/shared/providers/_configs/openai_client_config.py +5 -1
  202. rasa/shared/providers/_configs/rasa_llm_client_config.py +1 -1
  203. rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -1
  204. rasa/shared/providers/_configs/utils.py +0 -99
  205. rasa/shared/providers/embedding/_base_litellm_embedding_client.py +3 -0
  206. rasa/shared/providers/llm/_base_litellm_client.py +5 -2
  207. rasa/shared/utils/common.py +1 -1
  208. rasa/shared/utils/configs.py +110 -0
  209. rasa/shared/utils/constants.py +0 -3
  210. rasa/shared/utils/llm.py +195 -9
  211. rasa/shared/utils/pykwalify_extensions.py +0 -9
  212. rasa/shared/utils/yaml.py +32 -0
  213. rasa/studio/constants.py +1 -0
  214. rasa/studio/data_handler.py +11 -4
  215. rasa/studio/download.py +167 -0
  216. rasa/studio/link.py +200 -0
  217. rasa/studio/prompts.py +223 -0
  218. rasa/studio/pull/__init__.py +0 -0
  219. rasa/studio/{download/flows.py → pull/data.py} +23 -160
  220. rasa/studio/{download → pull}/domains.py +1 -1
  221. rasa/studio/pull/pull.py +235 -0
  222. rasa/studio/push.py +136 -0
  223. rasa/studio/train.py +1 -1
  224. rasa/studio/upload.py +117 -67
  225. rasa/telemetry.py +82 -25
  226. rasa/tracing/config.py +3 -4
  227. rasa/tracing/constants.py +19 -1
  228. rasa/tracing/instrumentation/attribute_extractors.py +30 -8
  229. rasa/tracing/instrumentation/instrumentation.py +53 -2
  230. rasa/tracing/instrumentation/metrics.py +98 -15
  231. rasa/tracing/metric_instrument_provider.py +75 -3
  232. rasa/utils/common.py +7 -22
  233. rasa/utils/log_utils.py +1 -45
  234. rasa/validator.py +2 -8
  235. rasa/version.py +1 -1
  236. {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/METADATA +8 -9
  237. {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/RECORD +241 -220
  238. rasa/anonymization/__init__.py +0 -2
  239. rasa/anonymization/anonymisation_rule_yaml_reader.py +0 -91
  240. rasa/anonymization/anonymization_pipeline.py +0 -286
  241. rasa/anonymization/anonymization_rule_executor.py +0 -266
  242. rasa/anonymization/anonymization_rule_orchestrator.py +0 -119
  243. rasa/anonymization/schemas/config.yml +0 -47
  244. rasa/anonymization/utils.py +0 -118
  245. rasa/core/channels/inspector/dist/assets/channel-3730f5fd.js +0 -1
  246. rasa/core/channels/inspector/dist/assets/clone-e847561e.js +0 -1
  247. rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-efbbfe00.js +0 -1
  248. rasa/studio/download/download.py +0 -439
  249. /rasa/{studio/download → core/information_retrieval/ingestion}/__init__.py +0 -0
  250. {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/NOTICE +0 -0
  251. {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/WHEEL +0 -0
  252. {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/entry_points.txt +0 -0
rasa/studio/link.py ADDED
@@ -0,0 +1,200 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import datetime
5
+ import sys
6
+ from pathlib import Path
7
+ from typing import Any, Dict, List, Optional, Text, Union
8
+
9
+ import questionary
10
+ import structlog
11
+ from pydantic import BaseModel, Field
12
+
13
+ import rasa.shared.utils.cli
14
+ from rasa.constants import RASA_DIR_NAME
15
+ from rasa.shared.utils.yaml import read_yaml_file, write_yaml
16
+ from rasa.studio.config import StudioConfig
17
+ from rasa.studio.upload import (
18
+ check_if_assistant_already_exists,
19
+ handle_upload,
20
+ is_auth_working,
21
+ )
22
+
23
+ structlogger = structlog.get_logger(__name__)
24
+
25
+ _LINK_FILE_NAME: Text = "studio.yml"
26
+
27
+
28
+ class AssistantLinkPayload(BaseModel):
29
+ assistant_name: Text
30
+ studio_url: Text
31
+ linked_at: Text = Field(
32
+ default_factory=lambda: datetime.datetime.utcnow().isoformat() + "Z"
33
+ )
34
+
35
+
36
+ def _link_file(project_root: Path) -> Path:
37
+ """Return `<project-root>/.rasa/studio.yml`.
38
+
39
+ Args:
40
+ project_root: The path to the project root.
41
+
42
+ Returns:
43
+ The path to the link file.
44
+ """
45
+ return project_root / RASA_DIR_NAME / _LINK_FILE_NAME
46
+
47
+
48
+ def _write_link_file(
49
+ project_root: Path, assistant_name: Text, studio_url: Text
50
+ ) -> None:
51
+ """Persist assistant information inside the project.
52
+
53
+ Args:
54
+ project_root: The path to the project root.
55
+ assistant_name: The name of the assistant.
56
+ studio_url: The URL of the Rasa Studio instance.
57
+ """
58
+ file_path = _link_file(project_root)
59
+ file_path.parent.mkdir(exist_ok=True, parents=True)
60
+
61
+ payload = AssistantLinkPayload(
62
+ assistant_name=assistant_name,
63
+ studio_url=studio_url,
64
+ )
65
+ write_yaml(payload.model_dump(), file_path)
66
+
67
+
68
+ def _read_link_file(
69
+ project_root: Path = Path.cwd(),
70
+ ) -> Optional[Union[List[Any], Dict[Text, Any]]]:
71
+ """Reads the link configuration file.
72
+
73
+ Args:
74
+ project_root: The path to the project root.
75
+
76
+ Returns:
77
+ The assistant information if the file exists, otherwise None.
78
+ """
79
+ file_path = _link_file(project_root)
80
+ if not file_path.is_file():
81
+ return None
82
+
83
+ return read_yaml_file(file_path)
84
+
85
+
86
+ def read_assistant_name(project_root: Path = Path.cwd()) -> Optional[Text]:
87
+ """Reads the assistant_name from the linked configuration file.
88
+
89
+ Args:
90
+ project_root: The path to the project root.
91
+
92
+ Returns:
93
+ The assistant name if the file exists, otherwise None.
94
+ """
95
+ linked = _read_link_file(project_root)
96
+ assistant_name = (
97
+ linked.get("assistant_name") if linked and isinstance(linked, dict) else None
98
+ )
99
+
100
+ if not assistant_name:
101
+ rasa.shared.utils.cli.print_error_and_exit(
102
+ "This project is not linked to any Rasa Studio assistant.\n"
103
+ "Run `rasa studio link <assistant-name>` first."
104
+ )
105
+
106
+ return assistant_name
107
+
108
+
109
+ def get_studio_config() -> StudioConfig:
110
+ """Get the StudioConfig object or exit with an error message.
111
+
112
+ Returns:
113
+ A valid StudioConfig object.
114
+ """
115
+ config = StudioConfig.read_config()
116
+ if not config.is_valid():
117
+ rasa.shared.utils.cli.print_error_and_exit(
118
+ "Rasa Studio is not configured correctly. Run `rasa studio config` first."
119
+ )
120
+ if not is_auth_working(config.studio_url, not config.disable_verify):
121
+ rasa.shared.utils.cli.print_error_and_exit(
122
+ "Authentication invalid or expired. Please run `rasa studio login`."
123
+ )
124
+ return config
125
+
126
+
127
+ def _ensure_assistant_exists(
128
+ assistant_name: Text,
129
+ studio_cfg: StudioConfig,
130
+ args: argparse.Namespace,
131
+ ) -> bool:
132
+ """Create the assistant on Studio if it does not yet exist.
133
+
134
+ Args:
135
+ assistant_name: The name the user provided on the CLI.
136
+ studio_cfg: The validated Studio configuration.
137
+ args: The original CLI args (needed for `handle_upload`).
138
+
139
+ Returns:
140
+ True if the assistant already exists or was created, False otherwise.
141
+ """
142
+ verify_ssl = not studio_cfg.disable_verify
143
+ assistant_already_exists = check_if_assistant_already_exists(
144
+ assistant_name, studio_cfg.studio_url, verify_ssl
145
+ )
146
+ if not assistant_already_exists:
147
+ should_create_assistant = questionary.confirm(
148
+ f"Assistant '{assistant_name}' was not found on Rasa Studio. "
149
+ f"Do you want to create it?"
150
+ ).ask()
151
+ if should_create_assistant:
152
+ # `handle_upload` expects the name to live in `args.assistant_name`
153
+ args.assistant_name = assistant_name
154
+ handle_upload(args)
155
+
156
+ rasa.shared.utils.cli.print_info(
157
+ f"Assistant {assistant_name} successfully created."
158
+ )
159
+ return should_create_assistant
160
+
161
+ return assistant_already_exists
162
+
163
+
164
+ def handle_link(args: argparse.Namespace) -> None:
165
+ """Implementation of `rasa studio link <assistant-name>` CLI command.
166
+
167
+ Args:
168
+ args: The command line arguments.
169
+ """
170
+ assistant_name: Text = args.assistant_name
171
+ studio_cfg = get_studio_config()
172
+ assistant_exists = _ensure_assistant_exists(assistant_name, studio_cfg, args)
173
+ if not assistant_exists:
174
+ rasa.shared.utils.cli.print_error_and_exit(
175
+ "Project has not been linked with Studio assistant."
176
+ )
177
+
178
+ project_root = Path.cwd()
179
+ link_file = _link_file(project_root)
180
+
181
+ if link_file.exists():
182
+ overwrite = questionary.confirm(
183
+ f"This project is already linked " f"(link file '{link_file}').\nOverwrite?"
184
+ ).ask()
185
+ if not overwrite:
186
+ rasa.shared.utils.cli.print_info(
187
+ "Existing link kept – nothing was changed."
188
+ )
189
+ sys.exit(0)
190
+
191
+ _write_link_file(project_root, assistant_name, studio_cfg.studio_url)
192
+
193
+ structlogger.info(
194
+ "studio.link.success",
195
+ event_info=f"Project linked to Studio assistant '{assistant_name}'.",
196
+ assistant_name=assistant_name,
197
+ )
198
+ rasa.shared.utils.cli.print_success(
199
+ f"Project successfully linked to assistant '{assistant_name}'."
200
+ )
rasa/studio/prompts.py ADDED
@@ -0,0 +1,223 @@
1
+ from pathlib import Path
2
+ from typing import Dict, List, Optional, Text
3
+
4
+ import structlog
5
+
6
+ from rasa.core.policies.enterprise_search_policy import EnterpriseSearchPolicy
7
+ from rasa.dialogue_understanding.generator.llm_based_command_generator import (
8
+ LLMBasedCommandGenerator,
9
+ )
10
+ from rasa.shared.constants import (
11
+ CONFIG_NAME_KEY,
12
+ CONFIG_PIPELINE_KEY,
13
+ CONFIG_POLICIES_KEY,
14
+ DEFAULT_CONFIG_PATH,
15
+ DEFAULT_ENDPOINTS_PATH,
16
+ DEFAULT_PROMPTS_PATH,
17
+ PROMPT_CONFIG_KEY,
18
+ PROMPT_TEMPLATE_CONFIG_KEY,
19
+ )
20
+ from rasa.shared.utils.common import all_subclasses
21
+ from rasa.shared.utils.llm import get_system_default_prompts
22
+ from rasa.shared.utils.yaml import read_yaml, write_yaml
23
+ from rasa.studio.data_handler import StudioDataHandler
24
+
25
+ structlogger = structlog.get_logger()
26
+
27
+ CONTEXTUAL_RESPONSE_REPHRASER_NAME = "contextual_response_rephraser"
28
+ COMMAND_GENERATOR_NAME = "command_generator"
29
+ ENTERPRISE_SEARCH_NAME = "enterprise_search"
30
+
31
+
32
+ def handle_prompts(handler: StudioDataHandler, root: Path) -> None:
33
+ """Handle prompts for the assistant.
34
+
35
+ Args:
36
+ handler: The data handler to retrieve prompts from.
37
+ root: The root directory where the prompts should be saved.
38
+ """
39
+ prompts = handler.get_prompts()
40
+ if not prompts:
41
+ return
42
+
43
+ config_path = root / DEFAULT_CONFIG_PATH
44
+ endpoints_path = root / DEFAULT_ENDPOINTS_PATH
45
+ config: Dict = read_yaml(config_path)
46
+ endpoints: Dict = read_yaml(endpoints_path)
47
+
48
+ system_prompts = get_system_default_prompts(config, endpoints)
49
+
50
+ _handle_contextual_response_rephraser(
51
+ root,
52
+ prompts.get(CONTEXTUAL_RESPONSE_REPHRASER_NAME),
53
+ system_prompts.contextual_response_rephraser,
54
+ endpoints,
55
+ )
56
+ _handle_command_generator(
57
+ root,
58
+ prompts.get(COMMAND_GENERATOR_NAME),
59
+ system_prompts.command_generator,
60
+ config,
61
+ )
62
+ _handle_enterprise_search(
63
+ root,
64
+ prompts.get(ENTERPRISE_SEARCH_NAME),
65
+ system_prompts.enterprise_search,
66
+ config,
67
+ )
68
+
69
+
70
+ def _handle_contextual_response_rephraser(
71
+ root: Path,
72
+ prompt_content: Optional[Text],
73
+ system_prompt: Optional[Text],
74
+ endpoints: Dict,
75
+ ) -> None:
76
+ """Handles the contextual response rephraser prompt.
77
+
78
+ Args:
79
+ root: The root directory where the prompt file will be saved.
80
+ prompt_content: The content of the contextual response rephraser prompt.
81
+ system_prompt: The system prompt for comparison.
82
+ endpoints: The endpoints configuration to update with the prompt path.
83
+ """
84
+ if not _is_custom_prompt(prompt_content, system_prompt):
85
+ return
86
+
87
+ prompt_path = _save_prompt_file(
88
+ root, f"{CONTEXTUAL_RESPONSE_REPHRASER_NAME}.jinja", prompt_content
89
+ )
90
+
91
+ endpoints["nlg"] = endpoints.get("nlg") or {}
92
+ endpoints["nlg"]["prompt"] = str(prompt_path)
93
+
94
+ endpoints_path = root / DEFAULT_ENDPOINTS_PATH
95
+ write_yaml(data=endpoints, target=endpoints_path, should_preserve_key_order=True)
96
+
97
+
98
+ def _handle_command_generator(
99
+ root: Path,
100
+ prompt_content: Optional[Text],
101
+ system_prompt: Optional[Text],
102
+ config: Dict,
103
+ ) -> None:
104
+ """Handles the command generator prompt.
105
+
106
+ Args:
107
+ root: The root directory where the prompt file will be saved.
108
+ prompt_content: The content of the command generator prompt.
109
+ system_prompt: The system prompt for comparison.
110
+ config: The configuration dictionary to update with the prompt path.
111
+ """
112
+ if not _is_custom_prompt(prompt_content, system_prompt):
113
+ return
114
+
115
+ prompt_path = _save_prompt_file(
116
+ root, f"{COMMAND_GENERATOR_NAME}.jinja", prompt_content
117
+ )
118
+
119
+ command_generator_names: List[str] = [
120
+ cls.__name__ for cls in all_subclasses(LLMBasedCommandGenerator)
121
+ ]
122
+ _add_prompt_to_config(
123
+ config=config,
124
+ section_key=CONFIG_PIPELINE_KEY,
125
+ component_names=command_generator_names,
126
+ prompt_key=PROMPT_TEMPLATE_CONFIG_KEY,
127
+ prompt_path=str(prompt_path),
128
+ )
129
+
130
+ config_path = root / DEFAULT_CONFIG_PATH
131
+ write_yaml(data=config, target=config_path, should_preserve_key_order=True)
132
+
133
+
134
+ def _handle_enterprise_search(
135
+ root: Path,
136
+ prompt_content: Optional[Text],
137
+ system_prompt: Optional[Text],
138
+ config: Dict,
139
+ ) -> None:
140
+ """Handles the enterprise search prompt.
141
+
142
+ Args:
143
+ root: The root directory where the prompt file will be saved.
144
+ prompt_content: The content of the enterprise search prompt.
145
+ system_prompt: The system prompt for comparison.
146
+ config: The configuration dictionary to update with the prompt path.
147
+ """
148
+ if not _is_custom_prompt(prompt_content, system_prompt):
149
+ return
150
+
151
+ prompt_path = _save_prompt_file(
152
+ root, f"{ENTERPRISE_SEARCH_NAME}.jinja", prompt_content
153
+ )
154
+
155
+ _add_prompt_to_config(
156
+ config=config,
157
+ section_key=CONFIG_POLICIES_KEY,
158
+ component_names=[EnterpriseSearchPolicy.__name__],
159
+ prompt_key=PROMPT_CONFIG_KEY,
160
+ prompt_path=str(prompt_path),
161
+ )
162
+
163
+ config_path = root / DEFAULT_CONFIG_PATH
164
+ write_yaml(data=config, target=config_path, should_preserve_key_order=True)
165
+
166
+
167
+ def _is_custom_prompt(
168
+ studio_prompt: Optional[Text], system_prompt: Optional[Text]
169
+ ) -> bool:
170
+ """Check if the prompt has been customized in Studio.
171
+
172
+ Args:
173
+ studio_prompt: The prompt content from the Studio.
174
+ system_prompt: The default system prompt content.
175
+ """
176
+ return bool(studio_prompt and studio_prompt != system_prompt)
177
+
178
+
179
+ def _save_prompt_file(root: Path, filename: str, content: str) -> Path:
180
+ """Save a prompt file to the specified root directory.
181
+
182
+ Args:
183
+ root: The root directory where the prompt file will be saved.
184
+ filename: The name of the prompt file.
185
+ content: The content of the prompt.
186
+ """
187
+ prompts_dir = root / DEFAULT_PROMPTS_PATH
188
+ prompts_dir.mkdir(parents=True, exist_ok=True)
189
+
190
+ file_path = prompts_dir / filename
191
+ file_path.write_text(content, encoding="utf-8")
192
+
193
+ return file_path.relative_to(root)
194
+
195
+
196
+ def _add_prompt_to_config(
197
+ *,
198
+ config: Dict,
199
+ section_key: str,
200
+ component_names: List[str],
201
+ prompt_key: str,
202
+ prompt_path: str,
203
+ ) -> None:
204
+ """Add a prompt path to the specified section of the configuration."""
205
+ matches = [
206
+ component
207
+ for component in config.get(section_key, [])
208
+ if component.get(CONFIG_NAME_KEY) in component_names
209
+ ]
210
+
211
+ if not matches:
212
+ return
213
+
214
+ # Update the first occurrence of the component.
215
+ matches[0][prompt_key] = prompt_path
216
+
217
+ if len(matches) > 1:
218
+ structlogger.warning(
219
+ "rasa.studio.prompts.add_prompt_to_config.multiple_components",
220
+ event_info=(
221
+ "Multiple components found in the configuration for the same prompt."
222
+ ),
223
+ )
File without changes
@@ -3,106 +3,16 @@ from pathlib import Path
3
3
  from typing import Any, Dict, List, Set, Text
4
4
 
5
5
  from rasa.shared.core.flows import Flow
6
- from rasa.shared.core.flows.flow_step_links import StaticFlowStepLink
7
6
  from rasa.shared.core.flows.flows_list import FlowsList
8
7
  from rasa.shared.core.flows.yaml_flows_io import YAMLFlowsReader, YamlFlowsWriter
9
8
  from rasa.shared.importers.importer import TrainingDataImporter
10
9
  from rasa.shared.utils.yaml import read_yaml
11
10
  from rasa.studio.constants import STUDIO_NLU_FILENAME
12
- from rasa.studio.data_handler import StudioDataHandler
13
11
  from rasa.utils.mapper import RasaPrimitiveStorageMapper
14
12
 
15
13
  logger = logging.getLogger(__name__)
16
14
 
17
- STUDIO_FLOWS_DIR_NAME = "studio_flows"
18
-
19
-
20
- def merge_flows_with_overwrite(
21
- data_paths: List[Path],
22
- handler: Any,
23
- data_from_studio: TrainingDataImporter,
24
- data_local: TrainingDataImporter,
25
- mapper: RasaPrimitiveStorageMapper,
26
- ) -> None:
27
- """
28
- Merges flows data from a file or directory when overwrite is enabled.
29
-
30
- Args:
31
- data_paths: List of paths to the training data.
32
- handler: The StudioDataHandler instance.
33
- data_from_studio: The TrainingDataImporter instance for Studio data.
34
- data_local: The TrainingDataImporter instance for local data.
35
- mapper: The RasaPrimitiveStorageMapper instance for mapping.
36
- """
37
- if len(data_paths) != 1:
38
- # TODO: Handle multiple data paths.
39
- raise NotImplementedError("Multiple data paths are not supported yet.")
40
-
41
- data_path = data_paths[0]
42
- if data_path.is_file():
43
- merge_training_data_file(handler, data_from_studio, data_local, data_path)
44
- elif data_path.is_dir():
45
- merge_training_data_dir(
46
- handler, data_from_studio, data_local, data_path, mapper
47
- )
48
- else:
49
- raise ValueError("Provided data path is neither a file nor a directory.")
50
-
51
-
52
- def merge_training_data_file(
53
- handler: StudioDataHandler,
54
- data_from_studio: TrainingDataImporter,
55
- data_local: TrainingDataImporter,
56
- file_path: Path,
57
- ) -> None:
58
- """
59
- Merges NLU and flows data when training data is stored in a single file.
60
-
61
- Args:
62
- handler: The StudioDataHandler instance.
63
- data_from_studio: The TrainingDataImporter instance for Studio data.
64
- data_local: The TrainingDataImporter instance for local data.
65
- file_path: The path to the training data file.
66
- """
67
- if handler.has_nlu():
68
- nlu_data_merged = data_from_studio.get_nlu_data().merge(
69
- data_local.get_nlu_data()
70
- )
71
- nlu_data_merged.persist_nlu(file_path)
72
-
73
- if handler.has_flows():
74
- flows_data_merged = data_from_studio.get_user_flows().merge(
75
- data_local.get_user_flows()
76
- )
77
- YamlFlowsWriter.dump(
78
- flows=flows_data_merged.underlying_flows,
79
- filename=file_path,
80
- should_clean_json=True,
81
- )
82
-
83
-
84
- def merge_training_data_dir(
85
- handler: StudioDataHandler,
86
- data_from_studio: TrainingDataImporter,
87
- data_local: TrainingDataImporter,
88
- data_path: Path,
89
- mapper: RasaPrimitiveStorageMapper,
90
- ) -> None:
91
- """
92
- Merges NLU and flows data when training data is stored in a directory.
93
-
94
- Args:
95
- handler: The StudioDataHandler instance.
96
- data_from_studio: The TrainingDataImporter instance for Studio data.
97
- data_local: The TrainingDataImporter instance for local data.
98
- data_path: The path to the training data directory.
99
- mapper: The RasaPrimitiveStorageMapper instance for mapping.
100
- """
101
- if handler.has_nlu():
102
- merge_nlu_in_directory(data_from_studio, data_local, data_path, mapper)
103
-
104
- if handler.has_flows():
105
- merge_flows_in_directory(data_from_studio, data_path, mapper)
15
+ STUDIO_FLOWS_DIR_NAME = "flows"
106
16
 
107
17
 
108
18
  def merge_nlu_in_directory(
@@ -121,7 +31,7 @@ def merge_nlu_in_directory(
121
31
  data_path: The path to the training data directory.
122
32
  mapper: The RasaPrimitiveStorageMapper instance for mapping.
123
33
  """
124
- from rasa.studio.download.download import pretty_write_nlu_yaml
34
+ from rasa.studio.download import pretty_write_nlu_yaml
125
35
 
126
36
  nlu_data = data_from_studio.get_nlu_data()
127
37
  nlu_file_path = get_nlu_path(data_path, data_local, mapper)
@@ -132,7 +42,8 @@ def merge_nlu_in_directory(
132
42
  )
133
43
  nlu_data = nlu_data.merge(local_nlu.get_nlu_data())
134
44
 
135
- pretty_write_nlu_yaml(read_yaml(nlu_data.nlu_as_yaml()), nlu_file_path)
45
+ if nlu_yaml := nlu_data.nlu_as_yaml():
46
+ pretty_write_nlu_yaml(read_yaml(nlu_yaml), nlu_file_path)
136
47
 
137
48
 
138
49
  def get_nlu_path(
@@ -158,29 +69,6 @@ def get_nlu_path(
158
69
  return _select_path(nlu_paths, "nlu", base_path, STUDIO_NLU_FILENAME)
159
70
 
160
71
 
161
- def get_flows_path(
162
- base_path: Path,
163
- data_local: TrainingDataImporter,
164
- mapper: RasaPrimitiveStorageMapper,
165
- ) -> Path:
166
- """Determines where flows data should be stored.
167
-
168
- Args:
169
- base_path: The base path for the training data.
170
- data_local: The TrainingDataImporter instance for local data.
171
- mapper: The RasaPrimitiveStorageMapper instance for mapping.
172
-
173
- Returns:
174
- The path where flows data should be stored.
175
- """
176
- flow_paths = set()
177
- for flow in data_local.get_user_flows().underlying_flows:
178
- for p in mapper.get_file(flow.id, "flows").get("training", []):
179
- flow_paths.add(p)
180
-
181
- return _select_path(flow_paths, "flows", base_path, "flows.yml")
182
-
183
-
184
72
  def merge_flows_in_directory(
185
73
  data_from_studio: TrainingDataImporter,
186
74
  data_path: Path,
@@ -211,14 +99,16 @@ def merge_flows_in_directory(
211
99
  local_flow_paths: Set[Path] = _get_local_flow_paths(local_flows, mapper)
212
100
 
213
101
  # Track updated flows and update local files with Studio flow data.
214
- all_updated_flows: List[Flow] = []
102
+ all_updated_flows_ids: List[Text] = []
215
103
  for flow_file_path in local_flow_paths:
216
- updated_file_flows = _update_flow_file(flow_file_path, studio_flow_map)
217
- all_updated_flows.extend(updated_file_flows)
104
+ updated_flows_ids = _update_flow_file(flow_file_path, studio_flow_map)
105
+ all_updated_flows_ids.extend(updated_flows_ids)
218
106
 
219
107
  # Identify new Studio flows and save them as separate files in the directory.
220
108
  new_flows = [
221
- flow for flow in studio_flow_map.values() if flow not in all_updated_flows
109
+ flow
110
+ for flow_id, flow in studio_flow_map.items()
111
+ if flow_id not in all_updated_flows_ids
222
112
  ]
223
113
  _dump_flows_as_separate_files(new_flows, data_path)
224
114
 
@@ -243,7 +133,7 @@ def _get_local_flow_paths(
243
133
 
244
134
  def _update_flow_file(
245
135
  flow_file_path: Path, studio_flows_map: Dict[Text, Any]
246
- ) -> List[Flow]:
136
+ ) -> List[Text]:
247
137
  """
248
138
  Reads a flow file, updates outdated flows, and replaces them with studio versions.
249
139
 
@@ -252,31 +142,25 @@ def _update_flow_file(
252
142
  studio_flows_map: A dictionary mapping flow IDs to their updated versions.
253
143
 
254
144
  Returns:
255
- A list of flows from the updated flow file.
145
+ A list of Flows IDs from the updated flow file.
256
146
  """
257
147
  file_flows = YAMLFlowsReader.read_from_file(flow_file_path, False)
258
- updated_list: List[Any] = []
259
- has_changes = False
260
-
261
- for flow in file_flows.underlying_flows:
262
- studio_flow = studio_flows_map.get(flow.id)
263
- if studio_flow is not None and studio_flow != flow:
264
- updated_list.append(studio_flow)
265
- has_changes = True
266
- else:
267
- updated_list.append(flow)
268
-
269
- if has_changes:
270
- new_flows_list = FlowsList(underlying_flows=updated_list)
271
- new_flows_list = strip_default_next_references(new_flows_list)
148
+
149
+ # Build a list of flows, replacing any outdated flow with its studio version
150
+ updated_flows = [
151
+ studio_flows_map.get(flow.id, flow) or flow
152
+ for flow in file_flows.underlying_flows
153
+ ]
154
+
155
+ # If the updated flows differ from the original file flows, write them back
156
+ if updated_flows != file_flows.underlying_flows:
272
157
  YamlFlowsWriter.dump(
273
- flows=new_flows_list.underlying_flows,
158
+ flows=updated_flows,
274
159
  filename=flow_file_path,
275
160
  should_clean_json=True,
276
161
  )
277
- return new_flows_list.underlying_flows
278
162
 
279
- return file_flows.underlying_flows
163
+ return [flow.id for flow in updated_flows]
280
164
 
281
165
 
282
166
  def _dump_flows_as_separate_files(flows: List[Any], data_path: Path) -> None:
@@ -306,27 +190,6 @@ def _dump_flows_as_separate_files(flows: List[Any], data_path: Path) -> None:
306
190
  )
307
191
 
308
192
 
309
- def strip_default_next_references(flows: FlowsList) -> FlowsList:
310
- """Strips default next references from flows.
311
-
312
- Args:
313
- flows: The FlowsList instance containing the flows.
314
-
315
- Returns:
316
- An updated FlowsList instance with default next references removed.
317
- """
318
- default_step_ids = [step.default_id for flow in flows for step in flow.steps]
319
- for flow in flows:
320
- for step in flow.steps:
321
- if (
322
- step.next.links
323
- and isinstance(step.next.links[0], StaticFlowStepLink)
324
- and step.next.links[0].target in default_step_ids
325
- ):
326
- step.next.links = []
327
- return flows
328
-
329
-
330
193
  def _select_path(
331
194
  paths: Set[Path], primitive_type: str, default_path: Path, default: str
332
195
  ) -> Path:
@@ -10,7 +10,7 @@ from rasa.studio.constants import STUDIO_DOMAIN_FILENAME
10
10
  logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
- def merge_domain_with_overwrite(
13
+ def merge_domain(
14
14
  data_from_studio: TrainingDataImporter,
15
15
  data_local: TrainingDataImporter,
16
16
  domain_path: Path,