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.
- rasa/__main__.py +0 -3
- rasa/api.py +1 -1
- rasa/cli/dialogue_understanding_test.py +1 -1
- rasa/cli/e2e_test.py +1 -8
- rasa/cli/evaluate.py +1 -1
- rasa/cli/export.py +3 -1
- rasa/cli/llm_fine_tuning.py +12 -11
- rasa/cli/project_templates/defaults.py +133 -0
- rasa/cli/project_templates/tutorial/config.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +1 -1
- rasa/cli/studio/download.py +1 -23
- rasa/cli/studio/link.py +52 -0
- rasa/cli/studio/pull.py +79 -0
- rasa/cli/studio/push.py +78 -0
- rasa/cli/studio/studio.py +12 -0
- rasa/cli/studio/train.py +0 -1
- rasa/cli/studio/upload.py +8 -0
- rasa/cli/train.py +1 -1
- rasa/cli/utils.py +1 -1
- rasa/cli/x.py +1 -1
- rasa/constants.py +2 -0
- rasa/core/__init__.py +0 -16
- rasa/core/actions/action.py +5 -1
- rasa/core/actions/action_repeat_bot_messages.py +18 -22
- rasa/core/actions/action_run_slot_rejections.py +0 -1
- rasa/core/agent.py +16 -1
- rasa/core/available_endpoints.py +146 -0
- rasa/core/brokers/pika.py +1 -2
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/botframework.py +2 -2
- rasa/core/channels/channel.py +2 -2
- rasa/core/channels/development_inspector.py +1 -1
- rasa/core/channels/facebook.py +1 -4
- rasa/core/channels/hangouts.py +8 -5
- rasa/core/channels/inspector/README.md +3 -3
- rasa/core/channels/inspector/dist/assets/{arc-c4b064fc.js → arc-371401b1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{blockDiagram-38ab4fdb-215b5026.js → blockDiagram-38ab4fdb-3f126156.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-3d4e48cf-2b54a0a3.js → c4Diagram-3d4e48cf-12f22eb7.js} +1 -1
- rasa/core/channels/inspector/dist/assets/channel-f1efda17.js +1 -0
- rasa/core/channels/inspector/dist/assets/{classDiagram-70f12bd4-daacea5f.js → classDiagram-70f12bd4-03b1d386.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-f2320105-930d4dc2.js → classDiagram-v2-f2320105-84f69d63.js} +1 -1
- rasa/core/channels/inspector/dist/assets/clone-fdf164e2.js +1 -0
- rasa/core/channels/inspector/dist/assets/{createText-2e5e7dd3-83c206ba.js → createText-2e5e7dd3-ca47fd38.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-e0da2a9e-b0eb01d0.js → edges-e0da2a9e-f837ca8a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9861fffd-17586500.js → erDiagram-9861fffd-8717ac54.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-956e92f1-be2a1776.js → flowDb-956e92f1-94f38b83.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-66a62f08-c2120ebd.js → flowDiagram-66a62f08-b616f9fb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-7d7a1629.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-4a651766-a6ab5c48.js → flowchart-elk-definition-4a651766-f5d24bb8.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-c361ad54-ef613457.js → ganttDiagram-c361ad54-b43ba8d9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-72cf32ee-d59185b3.js → gitGraphDiagram-72cf32ee-c3aafaa5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{graph-0f155405.js → graph-0d0a2c10.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-3862675e-d5f1d1b7.js → index-3862675e-58ea0305.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-47737d3a.js → index-cce6f8a1.js} +3 -3
- rasa/core/channels/inspector/dist/assets/{infoDiagram-f8f76790-b07d141f.js → infoDiagram-f8f76790-b8f60461.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-49397b02-1936d429.js → journeyDiagram-49397b02-95be5545.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-dde8d0f3.js → layout-da885b9b.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-0c2c7ee0.js → line-f1c817d3.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-35dd89a4.js → linear-d42801e6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-fc14e90a-56192851.js → mindmap-definition-fc14e90a-a38923a6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-8a3498a8-fc21ed78.js → pieDiagram-8a3498a8-ca6e71e9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-120e2f19-25e98518.js → quadrantDiagram-120e2f19-b290dae9.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-deff3bca-546ff1f5.js → requirementDiagram-deff3bca-03f02ceb.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-04a897e0-02d8b82d.js → sankeyDiagram-04a897e0-c49eee40.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-704730f1-3ca5a92e.js → sequenceDiagram-704730f1-b2cd6a3d.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-587899a1-128ea07c.js → stateDiagram-587899a1-e53a2028.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-d93cdb3a-95f290af.js → stateDiagram-v2-d93cdb3a-e1982a03.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-6aaf32cf-4984898a.js → styles-6aaf32cf-d0226ca5.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9a916d00-1bf266ba.js → styles-9a916d00-0e21dc00.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-c10674c1-60521c63.js → styles-c10674c1-9588494e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-08f97a94-a25b6e12.js → svgDrawCommon-08f97a94-be478d4f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-85554ec2-0fc086bf.js → timeline-definition-85554ec2-74631749.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-e933f94c-44ee592e.js → xychartDiagram-e933f94c-a043552f.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/components/RecruitmentPanel.tsx +1 -1
- rasa/core/channels/mattermost.py +1 -1
- rasa/core/channels/rasa_chat.py +2 -4
- rasa/core/channels/rest.py +5 -4
- rasa/core/channels/socketio.py +56 -41
- rasa/core/channels/studio_chat.py +314 -10
- rasa/core/channels/vier_cvg.py +1 -2
- rasa/core/channels/voice_ready/audiocodes.py +2 -9
- rasa/core/channels/voice_stream/asr/azure.py +9 -0
- rasa/core/channels/voice_stream/audiocodes.py +8 -5
- rasa/core/channels/voice_stream/browser_audio.py +1 -1
- rasa/core/channels/voice_stream/genesys.py +2 -2
- rasa/core/channels/voice_stream/jambonz.py +166 -0
- rasa/core/channels/voice_stream/tts/__init__.py +8 -0
- rasa/core/channels/voice_stream/twilio_media_streams.py +17 -5
- rasa/core/channels/voice_stream/voice_channel.py +44 -24
- rasa/core/exporter.py +36 -0
- rasa/core/http_interpreter.py +3 -7
- rasa/core/information_retrieval/faiss.py +18 -11
- rasa/core/information_retrieval/ingestion/faq_parser.py +158 -0
- rasa/core/jobs.py +2 -1
- rasa/core/nlg/contextual_response_rephraser.py +48 -12
- rasa/core/nlg/generator.py +0 -1
- rasa/core/nlg/interpolator.py +2 -3
- rasa/core/nlg/summarize.py +39 -5
- rasa/core/policies/enterprise_search_policy.py +298 -184
- rasa/core/policies/enterprise_search_policy_config.py +241 -0
- rasa/core/policies/enterprise_search_prompt_with_relevancy_check_and_citation_template.jinja2 +64 -0
- rasa/core/policies/flow_policy.py +1 -1
- rasa/core/policies/flows/flow_executor.py +96 -17
- rasa/core/policies/intentless_policy.py +71 -26
- rasa/core/processor.py +104 -51
- rasa/core/run.py +33 -11
- rasa/core/tracker_stores/tracker_store.py +1 -1
- rasa/core/training/interactive.py +1 -1
- rasa/core/utils.py +35 -99
- rasa/dialogue_understanding/coexistence/intent_based_router.py +2 -1
- rasa/dialogue_understanding/coexistence/llm_based_router.py +13 -17
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/can_not_handle_command.py +2 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +6 -2
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +2 -0
- rasa/dialogue_understanding/commands/clarify_command.py +7 -3
- rasa/dialogue_understanding/commands/command_syntax_manager.py +1 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +5 -6
- rasa/dialogue_understanding/commands/error_command.py +1 -1
- rasa/dialogue_understanding/commands/human_handoff_command.py +3 -3
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +2 -0
- rasa/dialogue_understanding/commands/repeat_bot_messages_command.py +2 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +15 -5
- rasa/dialogue_understanding/commands/skip_question_command.py +3 -3
- rasa/dialogue_understanding/commands/start_flow_command.py +7 -3
- rasa/dialogue_understanding/commands/utils.py +26 -2
- rasa/dialogue_understanding/generator/__init__.py +7 -1
- rasa/dialogue_understanding/generator/command_generator.py +15 -3
- rasa/dialogue_understanding/generator/command_parser.py +2 -2
- rasa/dialogue_understanding/generator/command_parser_validator.py +63 -0
- rasa/dialogue_understanding/generator/constants.py +2 -2
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +2 -2
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_template.jinja2 +0 -2
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_claude_3_5_sonnet_20240620_template.jinja2 +1 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v2_gpt_4o_2024_11_20_template.jinja2 +1 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_claude_3_5_sonnet_20240620_template.jinja2 +79 -0
- rasa/dialogue_understanding/generator/prompt_templates/command_prompt_v3_gpt_4o_2024_11_20_template.jinja2 +79 -0
- rasa/dialogue_understanding/generator/single_step/compact_llm_command_generator.py +28 -463
- rasa/dialogue_understanding/generator/single_step/search_ready_llm_command_generator.py +147 -0
- rasa/dialogue_understanding/generator/single_step/single_step_based_llm_command_generator.py +461 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +11 -64
- rasa/dialogue_understanding/patterns/cancel.py +1 -2
- rasa/dialogue_understanding/patterns/clarify.py +1 -1
- rasa/dialogue_understanding/patterns/correction.py +2 -2
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +37 -25
- rasa/dialogue_understanding/patterns/domain_for_patterns.py +190 -0
- rasa/dialogue_understanding/processor/command_processor.py +11 -12
- rasa/dialogue_understanding/processor/command_processor_component.py +3 -3
- rasa/dialogue_understanding/stack/frames/flow_stack_frame.py +17 -4
- rasa/dialogue_understanding/stack/utils.py +3 -1
- rasa/dialogue_understanding/utils.py +68 -12
- rasa/dialogue_understanding_test/du_test_case.py +1 -1
- rasa/dialogue_understanding_test/du_test_runner.py +4 -22
- rasa/dialogue_understanding_test/test_case_simulation/test_case_tracker_simulator.py +2 -6
- rasa/e2e_test/e2e_test_coverage_report.py +1 -1
- rasa/e2e_test/e2e_test_runner.py +1 -1
- rasa/engine/constants.py +1 -1
- rasa/engine/graph.py +2 -2
- rasa/engine/recipes/default_recipe.py +26 -2
- rasa/engine/validation.py +3 -2
- rasa/hooks.py +0 -28
- rasa/llm_fine_tuning/annotation_module.py +39 -9
- rasa/llm_fine_tuning/conversations.py +3 -0
- rasa/llm_fine_tuning/llm_data_preparation_module.py +66 -49
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +5 -7
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +52 -44
- rasa/llm_fine_tuning/paraphrasing_module.py +10 -12
- rasa/llm_fine_tuning/storage.py +4 -4
- rasa/llm_fine_tuning/utils.py +63 -1
- rasa/model_manager/model_api.py +88 -0
- rasa/model_manager/trainer_service.py +4 -4
- rasa/plugin.py +1 -11
- rasa/privacy/__init__.py +0 -0
- rasa/privacy/constants.py +83 -0
- rasa/privacy/event_broker_utils.py +77 -0
- rasa/privacy/privacy_config.py +281 -0
- rasa/privacy/privacy_config_schema.json +86 -0
- rasa/privacy/privacy_filter.py +340 -0
- rasa/privacy/privacy_manager.py +576 -0
- rasa/server.py +23 -2
- rasa/shared/constants.py +18 -0
- rasa/shared/core/command_payload_reader.py +1 -5
- rasa/shared/core/constants.py +4 -3
- rasa/shared/core/domain.py +7 -0
- rasa/shared/core/events.py +38 -10
- rasa/shared/core/flows/constants.py +2 -0
- rasa/shared/core/flows/flow.py +127 -14
- rasa/shared/core/flows/flows_list.py +18 -1
- rasa/shared/core/flows/flows_yaml_schema.json +3 -0
- rasa/shared/core/flows/steps/collect.py +46 -2
- rasa/shared/core/flows/steps/link.py +7 -2
- rasa/shared/core/flows/validation.py +25 -5
- rasa/shared/core/slots.py +28 -0
- rasa/shared/core/training_data/story_reader/yaml_story_reader.py +1 -4
- rasa/shared/exceptions.py +4 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +6 -2
- rasa/shared/providers/_configs/default_litellm_client_config.py +1 -1
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +1 -1
- rasa/shared/providers/_configs/openai_client_config.py +5 -1
- rasa/shared/providers/_configs/rasa_llm_client_config.py +1 -1
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +1 -1
- rasa/shared/providers/_configs/utils.py +0 -99
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +3 -0
- rasa/shared/providers/llm/_base_litellm_client.py +5 -2
- rasa/shared/utils/common.py +1 -1
- rasa/shared/utils/configs.py +110 -0
- rasa/shared/utils/constants.py +0 -3
- rasa/shared/utils/llm.py +195 -9
- rasa/shared/utils/pykwalify_extensions.py +0 -9
- rasa/shared/utils/yaml.py +32 -0
- rasa/studio/constants.py +1 -0
- rasa/studio/data_handler.py +11 -4
- rasa/studio/download.py +167 -0
- rasa/studio/link.py +200 -0
- rasa/studio/prompts.py +223 -0
- rasa/studio/pull/__init__.py +0 -0
- rasa/studio/{download/flows.py → pull/data.py} +23 -160
- rasa/studio/{download → pull}/domains.py +1 -1
- rasa/studio/pull/pull.py +235 -0
- rasa/studio/push.py +136 -0
- rasa/studio/train.py +1 -1
- rasa/studio/upload.py +117 -67
- rasa/telemetry.py +82 -25
- rasa/tracing/config.py +3 -4
- rasa/tracing/constants.py +19 -1
- rasa/tracing/instrumentation/attribute_extractors.py +30 -8
- rasa/tracing/instrumentation/instrumentation.py +53 -2
- rasa/tracing/instrumentation/metrics.py +98 -15
- rasa/tracing/metric_instrument_provider.py +75 -3
- rasa/utils/common.py +7 -22
- rasa/utils/log_utils.py +1 -45
- rasa/validator.py +2 -8
- rasa/version.py +1 -1
- {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/METADATA +8 -9
- {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/RECORD +241 -220
- rasa/anonymization/__init__.py +0 -2
- rasa/anonymization/anonymisation_rule_yaml_reader.py +0 -91
- rasa/anonymization/anonymization_pipeline.py +0 -286
- rasa/anonymization/anonymization_rule_executor.py +0 -266
- rasa/anonymization/anonymization_rule_orchestrator.py +0 -119
- rasa/anonymization/schemas/config.yml +0 -47
- rasa/anonymization/utils.py +0 -118
- rasa/core/channels/inspector/dist/assets/channel-3730f5fd.js +0 -1
- rasa/core/channels/inspector/dist/assets/clone-e847561e.js +0 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-96b9c2cf-efbbfe00.js +0 -1
- rasa/studio/download/download.py +0 -439
- /rasa/{studio/download → core/information_retrieval/ingestion}/__init__.py +0 -0
- {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/NOTICE +0 -0
- {rasa_pro-3.13.0.dev20250612.dist-info → rasa_pro-3.13.0rc1.dist-info}/WHEEL +0 -0
- {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 = "
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
102
|
+
all_updated_flows_ids: List[Text] = []
|
|
215
103
|
for flow_file_path in local_flow_paths:
|
|
216
|
-
|
|
217
|
-
|
|
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
|
|
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[
|
|
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
|
|
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
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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=
|
|
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
|
|
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
|
|
13
|
+
def merge_domain(
|
|
14
14
|
data_from_studio: TrainingDataImporter,
|
|
15
15
|
data_local: TrainingDataImporter,
|
|
16
16
|
domain_path: Path,
|