rasa-pro 3.8.17__py3-none-any.whl → 3.9.14__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of rasa-pro might be problematic. Click here for more details.
- README.md +5 -5
- rasa/__main__.py +14 -9
- rasa/anonymization/anonymization_pipeline.py +0 -1
- rasa/anonymization/anonymization_rule_executor.py +3 -3
- rasa/anonymization/utils.py +4 -3
- rasa/api.py +2 -2
- rasa/cli/arguments/default_arguments.py +1 -1
- rasa/cli/arguments/run.py +2 -2
- rasa/cli/arguments/test.py +1 -1
- rasa/cli/arguments/train.py +10 -10
- rasa/cli/e2e_test.py +27 -7
- rasa/cli/export.py +0 -1
- rasa/cli/license.py +3 -3
- rasa/cli/project_templates/calm/actions/action_template.py +1 -1
- rasa/cli/project_templates/calm/config.yml +1 -1
- rasa/cli/project_templates/calm/credentials.yml +1 -1
- rasa/cli/project_templates/calm/data/flows/add_contact.yml +1 -1
- rasa/cli/project_templates/calm/data/flows/remove_contact.yml +1 -1
- rasa/cli/project_templates/calm/domain/add_contact.yml +8 -2
- rasa/cli/project_templates/calm/domain/list_contacts.yml +3 -0
- rasa/cli/project_templates/calm/domain/remove_contact.yml +9 -2
- rasa/cli/project_templates/calm/domain/shared.yml +5 -0
- rasa/cli/project_templates/calm/endpoints.yml +4 -4
- rasa/cli/project_templates/default/actions/actions.py +1 -1
- rasa/cli/project_templates/default/config.yml +5 -5
- rasa/cli/project_templates/default/credentials.yml +1 -1
- rasa/cli/project_templates/default/endpoints.yml +4 -4
- rasa/cli/project_templates/default/tests/test_stories.yml +1 -1
- rasa/cli/project_templates/tutorial/config.yml +1 -1
- rasa/cli/project_templates/tutorial/credentials.yml +1 -1
- rasa/cli/project_templates/tutorial/data/patterns.yml +6 -0
- rasa/cli/project_templates/tutorial/domain.yml +4 -0
- rasa/cli/project_templates/tutorial/endpoints.yml +6 -6
- rasa/cli/run.py +0 -1
- rasa/cli/scaffold.py +3 -2
- rasa/cli/studio/download.py +11 -0
- rasa/cli/studio/studio.py +180 -24
- rasa/cli/studio/upload.py +0 -8
- rasa/cli/telemetry.py +18 -6
- rasa/cli/utils.py +21 -10
- rasa/cli/x.py +3 -2
- rasa/core/actions/action.py +90 -315
- rasa/core/actions/action_exceptions.py +24 -0
- rasa/core/actions/constants.py +3 -0
- rasa/core/actions/custom_action_executor.py +188 -0
- rasa/core/actions/forms.py +11 -7
- rasa/core/actions/grpc_custom_action_executor.py +251 -0
- rasa/core/actions/http_custom_action_executor.py +140 -0
- rasa/core/actions/loops.py +3 -0
- rasa/core/actions/two_stage_fallback.py +1 -1
- rasa/core/agent.py +2 -4
- rasa/core/brokers/pika.py +1 -2
- rasa/core/channels/audiocodes.py +1 -1
- rasa/core/channels/botframework.py +0 -1
- rasa/core/channels/callback.py +0 -1
- rasa/core/channels/console.py +6 -8
- rasa/core/channels/development_inspector.py +1 -1
- rasa/core/channels/facebook.py +0 -3
- rasa/core/channels/hangouts.py +0 -6
- rasa/core/channels/inspector/dist/assets/{arc-5623b6dc.js → arc-b6e548fe.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{c4Diagram-d0fbc5ce-685c106a.js → c4Diagram-d0fbc5ce-fa03ac9e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-936ed81e-8cbed007.js → classDiagram-936ed81e-ee67392a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{classDiagram-v2-c3cb15f1-5889cf12.js → classDiagram-v2-c3cb15f1-9b283fae.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{createText-62fc7601-24c249d7.js → createText-62fc7601-8b6fcc2a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{edges-f2ad444c-7dd06a75.js → edges-f2ad444c-22e77f4f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{erDiagram-9d236eb7-62c1e54c.js → erDiagram-9d236eb7-60ffc87f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDb-1972c806-ce49b86f.js → flowDb-1972c806-9dd802e4.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{flowDiagram-7ea5b25a-4067e48f.js → flowDiagram-7ea5b25a-5fa1912f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-1844e5a5.js +1 -0
- rasa/core/channels/inspector/dist/assets/{flowchart-elk-definition-abe16c3d-59fe4051.js → flowchart-elk-definition-abe16c3d-622a1fd2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{ganttDiagram-9b5ea136-47e3a43b.js → ganttDiagram-9b5ea136-e285a63a.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{gitGraphDiagram-99d0ae7c-5a2ac0d9.js → gitGraphDiagram-99d0ae7c-f237bdca.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-2c4b9a3b-dfb8efc4.js → index-2c4b9a3b-4b03d70e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{index-268a75c0.js → index-a5d3e69d.js} +4 -4
- rasa/core/channels/inspector/dist/assets/{infoDiagram-736b4530-b0c470f2.js → infoDiagram-736b4530-72a0fa5f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{journeyDiagram-df861f2b-2edb829a.js → journeyDiagram-df861f2b-82218c41.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{layout-b6873d69.js → layout-78cff630.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{line-1efc5781.js → line-5038b469.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{linear-661e9b94.js → linear-c4fc4098.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{mindmap-definition-beec6740-2d2e727f.js → mindmap-definition-beec6740-c33c8ea6.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{pieDiagram-dbbf0591-9d3ea93d.js → pieDiagram-dbbf0591-a8d03059.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{quadrantDiagram-4d7f4fd6-06a178a2.js → quadrantDiagram-4d7f4fd6-6a0e56b2.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{requirementDiagram-6fc4c22a-0bfedffc.js → requirementDiagram-6fc4c22a-2dc7c7bd.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sankeyDiagram-8f13d901-d76d0a04.js → sankeyDiagram-8f13d901-2360fe39.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{sequenceDiagram-b655622a-37bb4341.js → sequenceDiagram-b655622a-41b9f9ad.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-59f0c015-f52f7f57.js → stateDiagram-59f0c015-0aad326f.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{stateDiagram-v2-2b26beab-4a986a20.js → stateDiagram-v2-2b26beab-9847d984.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-080da4f6-7dd9ae12.js → styles-080da4f6-564d890e.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-3dcbcfbf-46e1ca14.js → styles-3dcbcfbf-38957613.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{styles-9c745c82-4a97439a.js → styles-9c745c82-f0fc6921.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{svgDrawCommon-4835440b-823917a3.js → svgDrawCommon-4835440b-ef3c5a77.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{timeline-definition-5b62e21b-9ea72896.js → timeline-definition-5b62e21b-bf3e91c1.js} +1 -1
- rasa/core/channels/inspector/dist/assets/{xychartDiagram-2b33534f-b631a8b6.js → xychartDiagram-2b33534f-4d4026c0.js} +1 -1
- rasa/core/channels/inspector/dist/index.html +1 -1
- rasa/core/channels/inspector/src/components/DiagramFlow.tsx +10 -0
- rasa/core/channels/inspector/src/helpers/formatters.test.ts +4 -7
- rasa/core/channels/inspector/src/helpers/formatters.ts +3 -2
- rasa/core/channels/rest.py +36 -21
- rasa/core/channels/rocketchat.py +0 -1
- rasa/core/channels/socketio.py +1 -1
- rasa/core/channels/telegram.py +3 -3
- rasa/core/channels/webexteams.py +0 -1
- rasa/core/concurrent_lock_store.py +1 -1
- rasa/core/evaluation/marker_base.py +1 -3
- rasa/core/evaluation/marker_stats.py +1 -2
- rasa/core/featurizers/single_state_featurizer.py +2 -4
- rasa/core/featurizers/tracker_featurizers.py +0 -7
- rasa/core/information_retrieval/__init__.py +7 -0
- rasa/core/information_retrieval/faiss.py +9 -4
- rasa/core/information_retrieval/information_retrieval.py +64 -7
- rasa/core/information_retrieval/milvus.py +7 -14
- rasa/core/information_retrieval/qdrant.py +8 -15
- rasa/core/lock_store.py +0 -1
- rasa/core/migrate.py +1 -2
- rasa/core/nlg/callback.py +3 -4
- rasa/core/policies/enterprise_search_policy.py +86 -22
- rasa/core/policies/enterprise_search_prompt_template.jinja2 +4 -41
- rasa/core/policies/enterprise_search_prompt_with_citation_template.jinja2 +60 -0
- rasa/core/policies/flows/flow_executor.py +104 -2
- rasa/core/policies/intentless_policy.py +7 -9
- rasa/core/policies/memoization.py +3 -3
- rasa/core/policies/policy.py +18 -9
- rasa/core/policies/rule_policy.py +8 -11
- rasa/core/policies/ted_policy.py +28 -30
- rasa/core/policies/unexpected_intent_policy.py +1 -2
- rasa/core/processor.py +136 -47
- rasa/core/run.py +41 -25
- rasa/core/secrets_manager/endpoints.py +2 -2
- rasa/core/secrets_manager/vault.py +6 -8
- rasa/core/test.py +3 -5
- rasa/core/tracker_store.py +49 -14
- rasa/core/train.py +1 -3
- rasa/core/training/interactive.py +9 -6
- rasa/core/utils.py +5 -10
- rasa/dialogue_understanding/coexistence/intent_based_router.py +11 -4
- rasa/dialogue_understanding/coexistence/llm_based_router.py +2 -3
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/can_not_handle_command.py +9 -0
- rasa/dialogue_understanding/commands/cancel_flow_command.py +9 -0
- rasa/dialogue_understanding/commands/change_flow_command.py +38 -0
- rasa/dialogue_understanding/commands/chit_chat_answer_command.py +9 -0
- rasa/dialogue_understanding/commands/clarify_command.py +9 -0
- rasa/dialogue_understanding/commands/correct_slots_command.py +9 -0
- rasa/dialogue_understanding/commands/error_command.py +12 -0
- rasa/dialogue_understanding/commands/handle_code_change_command.py +9 -0
- rasa/dialogue_understanding/commands/human_handoff_command.py +9 -0
- rasa/dialogue_understanding/commands/knowledge_answer_command.py +9 -0
- rasa/dialogue_understanding/commands/noop_command.py +9 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +34 -3
- rasa/dialogue_understanding/commands/skip_question_command.py +9 -0
- rasa/dialogue_understanding/commands/start_flow_command.py +9 -0
- rasa/dialogue_understanding/generator/__init__.py +16 -1
- rasa/dialogue_understanding/generator/command_generator.py +92 -6
- rasa/dialogue_understanding/generator/constants.py +18 -0
- rasa/dialogue_understanding/generator/flow_retrieval.py +7 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +467 -0
- rasa/dialogue_understanding/generator/llm_command_generator.py +39 -609
- rasa/dialogue_understanding/generator/multi_step/__init__.py +0 -0
- rasa/dialogue_understanding/generator/multi_step/fill_slots_prompt.jinja2 +62 -0
- rasa/dialogue_understanding/generator/multi_step/handle_flows_prompt.jinja2 +38 -0
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +827 -0
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +69 -8
- rasa/dialogue_understanding/generator/single_step/__init__.py +0 -0
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +345 -0
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +44 -39
- rasa/dialogue_understanding/processor/command_processor.py +111 -3
- rasa/e2e_test/constants.py +1 -0
- rasa/e2e_test/e2e_test_case.py +44 -0
- rasa/e2e_test/e2e_test_runner.py +114 -11
- rasa/e2e_test/e2e_test_schema.yml +18 -0
- rasa/engine/caching.py +0 -1
- rasa/engine/graph.py +18 -6
- rasa/engine/recipes/config_files/default_config.yml +3 -3
- rasa/engine/recipes/default_components.py +1 -1
- rasa/engine/recipes/default_recipe.py +4 -5
- rasa/engine/recipes/recipe.py +1 -1
- rasa/engine/runner/dask.py +3 -9
- rasa/engine/storage/local_model_storage.py +0 -2
- rasa/engine/validation.py +179 -145
- rasa/exceptions.py +2 -2
- rasa/graph_components/validators/default_recipe_validator.py +3 -5
- rasa/hooks.py +0 -1
- rasa/model.py +1 -1
- rasa/model_training.py +1 -0
- rasa/nlu/classifiers/diet_classifier.py +8 -14
- rasa/nlu/extractors/crf_entity_extractor.py +4 -4
- rasa/nlu/extractors/duckling_entity_extractor.py +1 -1
- rasa/nlu/featurizers/dense_featurizer/convert_featurizer.py +1 -5
- rasa/nlu/featurizers/dense_featurizer/lm_featurizer.py +0 -4
- rasa/nlu/featurizers/featurizer.py +1 -1
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +2 -4
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +9 -12
- rasa/nlu/persistor.py +68 -26
- rasa/nlu/selectors/response_selector.py +7 -10
- rasa/nlu/test.py +0 -3
- rasa/nlu/utils/hugging_face/registry.py +1 -1
- rasa/nlu/utils/spacy_utils.py +1 -3
- rasa/server.py +22 -7
- rasa/shared/constants.py +12 -1
- rasa/shared/core/command_payload_reader.py +109 -0
- rasa/shared/core/constants.py +4 -5
- rasa/shared/core/domain.py +57 -56
- rasa/shared/core/events.py +4 -7
- rasa/shared/core/flows/flow.py +9 -0
- rasa/shared/core/flows/flows_list.py +12 -0
- rasa/shared/core/flows/steps/action.py +7 -2
- rasa/shared/core/generator.py +12 -11
- rasa/shared/core/slot_mappings.py +315 -24
- rasa/shared/core/slots.py +4 -2
- rasa/shared/core/trackers.py +32 -14
- rasa/shared/core/training_data/loading.py +0 -1
- rasa/shared/core/training_data/story_reader/story_reader.py +3 -3
- rasa/shared/core/training_data/story_reader/yaml_story_reader.py +11 -11
- rasa/shared/core/training_data/story_writer/yaml_story_writer.py +5 -3
- rasa/shared/core/training_data/structures.py +1 -1
- rasa/shared/core/training_data/visualization.py +1 -1
- rasa/shared/data.py +58 -1
- rasa/shared/exceptions.py +36 -2
- rasa/shared/importers/importer.py +1 -2
- rasa/shared/importers/rasa.py +0 -1
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/nlu/training_data/entities_parser.py +1 -2
- rasa/shared/nlu/training_data/formats/dialogflow.py +3 -2
- rasa/shared/nlu/training_data/formats/rasa_yaml.py +3 -5
- rasa/shared/nlu/training_data/formats/readerwriter.py +0 -1
- rasa/shared/nlu/training_data/message.py +13 -0
- rasa/shared/nlu/training_data/training_data.py +0 -2
- rasa/shared/providers/openai/session_handler.py +2 -2
- rasa/shared/utils/constants.py +3 -0
- rasa/shared/utils/io.py +11 -0
- rasa/shared/utils/llm.py +1 -2
- rasa/shared/utils/pykwalify_extensions.py +1 -0
- rasa/shared/utils/schemas/domain.yml +3 -0
- rasa/shared/utils/yaml.py +44 -35
- rasa/studio/auth.py +26 -10
- rasa/studio/constants.py +2 -0
- rasa/studio/data_handler.py +114 -107
- rasa/studio/download.py +160 -27
- rasa/studio/results_logger.py +137 -0
- rasa/studio/train.py +6 -7
- rasa/studio/upload.py +159 -134
- rasa/telemetry.py +188 -34
- rasa/tracing/config.py +18 -3
- rasa/tracing/constants.py +26 -2
- rasa/tracing/instrumentation/attribute_extractors.py +50 -41
- rasa/tracing/instrumentation/instrumentation.py +290 -44
- rasa/tracing/instrumentation/intentless_policy_instrumentation.py +7 -5
- rasa/tracing/instrumentation/metrics.py +109 -21
- rasa/tracing/metric_instrument_provider.py +83 -3
- rasa/utils/cli.py +2 -1
- rasa/utils/common.py +1 -1
- rasa/utils/endpoints.py +1 -2
- rasa/utils/io.py +6 -6
- rasa/utils/licensing.py +246 -31
- rasa/utils/ml_utils.py +1 -1
- rasa/utils/tensorflow/data_generator.py +1 -1
- rasa/utils/tensorflow/environment.py +1 -1
- rasa/utils/tensorflow/model_data.py +9 -11
- rasa/utils/tensorflow/model_data_utils.py +499 -500
- rasa/utils/tensorflow/models.py +5 -6
- rasa/utils/tensorflow/rasa_layers.py +15 -15
- rasa/utils/train_utils.py +1 -1
- rasa/utils/url_tools.py +53 -0
- rasa/validator.py +305 -3
- rasa/version.py +1 -1
- {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/METADATA +22 -22
- {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/RECORD +271 -253
- rasa/core/channels/inspector/dist/assets/flowDiagram-v2-855bc5b3-85583a23.js +0 -1
- /rasa/dialogue_understanding/generator/{command_prompt_template.jinja2 → single_step/command_prompt_template.jinja2} +0 -0
- {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/NOTICE +0 -0
- {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/WHEEL +0 -0
- {rasa_pro-3.8.17.dist-info → rasa_pro-3.9.14.dist-info}/entry_points.txt +0 -0
rasa/studio/data_handler.py
CHANGED
|
@@ -50,7 +50,7 @@ class StudioDataHandler:
|
|
|
50
50
|
"query ExportAsEncodedYaml($input: ExportAsEncodedYamlInput!) "
|
|
51
51
|
"{ exportAsEncodedYaml(input: $input) "
|
|
52
52
|
"{ ... on ExportModernAsEncodedYamlOutput "
|
|
53
|
-
"{ nlu flows domain } "
|
|
53
|
+
"{ nlu flows domain endpoints config } "
|
|
54
54
|
"... on ExportClassicAsEncodedYamlOutput "
|
|
55
55
|
"{ nlu domain }}}"
|
|
56
56
|
),
|
|
@@ -148,6 +148,12 @@ class StudioDataHandler:
|
|
|
148
148
|
response = self._make_request(GQL_req)
|
|
149
149
|
self._extract_data(response)
|
|
150
150
|
|
|
151
|
+
def get_config(self) -> Optional[str]:
|
|
152
|
+
return self.config
|
|
153
|
+
|
|
154
|
+
def get_endpoints(self) -> Optional[str]:
|
|
155
|
+
return self.endpoints
|
|
156
|
+
|
|
151
157
|
def _validate_response(self, response: dict) -> bool:
|
|
152
158
|
"""Validates the response from Rasa Studio.
|
|
153
159
|
|
|
@@ -184,6 +190,8 @@ class StudioDataHandler:
|
|
|
184
190
|
self.nlu = self._decode_response(return_data.get("nlu"))
|
|
185
191
|
self.domain = self._decode_response(return_data.get("domain"))
|
|
186
192
|
self.flows = self._decode_response(return_data.get("flows"))
|
|
193
|
+
self.config = self._decode_response(return_data.get("config"))
|
|
194
|
+
self.endpoints = self._decode_response(return_data.get("endpoints"))
|
|
187
195
|
|
|
188
196
|
if not self.has_nlu() and not self.has_flows():
|
|
189
197
|
raise RasaException("No nlu or flows data in Studio response.")
|
|
@@ -195,114 +203,113 @@ class StudioDataHandler:
|
|
|
195
203
|
return base64.b64decode(data).decode("utf-8")
|
|
196
204
|
|
|
197
205
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
def create_new_domain_from_diff(self) -> Dict:
|
|
218
|
-
"""Create a new domain file from the diff."""
|
|
219
|
-
if self.studio_domain is None or self.original_domain is None:
|
|
220
|
-
return {}
|
|
221
|
-
return self._domain_from_diff_rec(self.studio_domain, self.original_domain)
|
|
222
|
-
|
|
223
|
-
@staticmethod
|
|
224
|
-
def _domain_from_diff_rec(studio: Dict, original: Dict) -> Dict:
|
|
225
|
-
ret_dict = {}
|
|
226
|
-
for key in studio:
|
|
227
|
-
if key not in original:
|
|
228
|
-
ret_dict[key] = studio[key]
|
|
229
|
-
elif isinstance(studio[key], dict):
|
|
230
|
-
ret_dict[key] = DataDiffGenerator._domain_from_diff_rec(
|
|
231
|
-
studio[key], original[key]
|
|
232
|
-
)
|
|
233
|
-
# remove empty diffs
|
|
234
|
-
if not ret_dict[key]:
|
|
235
|
-
del ret_dict[key]
|
|
236
|
-
elif key not in [KEY_SLOTS, KEY_RESPONSES]:
|
|
237
|
-
# copy over the whole key not just the diff in case of items
|
|
238
|
-
# to get complete (valid) data not just the diff
|
|
239
|
-
ret_dict[key] = studio[key]
|
|
240
|
-
elif isinstance(studio[key], list):
|
|
241
|
-
ret_dict[key] = []
|
|
242
|
-
for item in studio[key]:
|
|
243
|
-
if item not in original[key]:
|
|
244
|
-
ret_dict[key].append(item)
|
|
245
|
-
|
|
246
|
-
# if list is empty, remove it
|
|
247
|
-
if not ret_dict[key]:
|
|
248
|
-
del ret_dict[key]
|
|
249
|
-
|
|
250
|
-
return ret_dict
|
|
251
|
-
|
|
252
|
-
def create_new_nlu_from_diff(self) -> Dict:
|
|
253
|
-
"""Create a new nlu file from the diff."""
|
|
254
|
-
if self.studio_nlu is None or self.original_nlu is None:
|
|
255
|
-
return {"nlu": []}
|
|
256
|
-
|
|
257
|
-
nlu_diff = []
|
|
258
|
-
nlu_new_examples = [
|
|
259
|
-
new for new in self.studio_nlu["nlu"] if new not in self.original_nlu["nlu"]
|
|
260
|
-
]
|
|
261
|
-
if nlu_new_examples:
|
|
262
|
-
for new_example in nlu_new_examples:
|
|
263
|
-
nlu_diff.append(new_example)
|
|
264
|
-
intent = new_example.get("intent")
|
|
265
|
-
if intent:
|
|
266
|
-
self._diff_nlu_examples(new_example, nlu_diff, "intent", intent)
|
|
267
|
-
synonym = new_example.get("synonym")
|
|
268
|
-
if synonym:
|
|
269
|
-
self._diff_nlu_examples(new_example, nlu_diff, "synonym", synonym)
|
|
270
|
-
|
|
271
|
-
return {"nlu": nlu_diff}
|
|
272
|
-
|
|
273
|
-
def create_new_flows_from_diff(self) -> List[Flow]:
|
|
274
|
-
"""Create a new flows file from the diff."""
|
|
275
|
-
if self.studio_flows is None or self.original_flows is None:
|
|
276
|
-
return []
|
|
277
|
-
|
|
278
|
-
flows_new = [new for new in self.studio_flows if new not in self.original_flows]
|
|
279
|
-
return flows_new
|
|
280
|
-
|
|
281
|
-
def _diff_nlu_examples(
|
|
282
|
-
self, new_example: Dict, nlu_diff: List, match_key: str, match_value: str
|
|
283
|
-
) -> None:
|
|
284
|
-
"""Creates diff of nlu data examples.
|
|
285
|
-
|
|
286
|
-
Args:
|
|
287
|
-
new_example (Dict): intent or synonym with examples
|
|
288
|
-
nlu_diff (List): list of diff examples
|
|
289
|
-
match_key (str): intent or synonym name
|
|
290
|
-
match_value (str): intent or synonym value
|
|
291
|
-
"""
|
|
292
|
-
orig = list(
|
|
293
|
-
filter(
|
|
294
|
-
lambda x: x.get(match_key) == match_value,
|
|
295
|
-
self.original_nlu["nlu"], # type: ignore[index]
|
|
206
|
+
def combine_domains(
|
|
207
|
+
studio_domain: Dict[str, Any], original_domain: Dict[str, Any]
|
|
208
|
+
) -> Dict:
|
|
209
|
+
"""Create a new domain file from the diff."""
|
|
210
|
+
if studio_domain is None or original_domain is None:
|
|
211
|
+
return {}
|
|
212
|
+
return _combine_domain_keys(studio_domain, original_domain)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _combine_domain_keys(
|
|
216
|
+
first_domain: Dict[str, Any], second_domain: Dict[str, Any]
|
|
217
|
+
) -> Dict[str, Any]:
|
|
218
|
+
combined_keys = {}
|
|
219
|
+
for key in first_domain:
|
|
220
|
+
if key not in second_domain:
|
|
221
|
+
combined_keys[key] = first_domain[key]
|
|
222
|
+
elif isinstance(first_domain[key], dict):
|
|
223
|
+
combined_keys[key] = _combine_domain_keys(
|
|
224
|
+
first_domain[key], second_domain[key]
|
|
296
225
|
)
|
|
226
|
+
# remove empty diffs
|
|
227
|
+
if not combined_keys[key]:
|
|
228
|
+
del combined_keys[key]
|
|
229
|
+
elif key not in [KEY_SLOTS, KEY_RESPONSES]:
|
|
230
|
+
# for all keys except slots and responses, we want to keep the
|
|
231
|
+
# keys from the first domain
|
|
232
|
+
combined_keys[key] = first_domain[key]
|
|
233
|
+
elif isinstance(first_domain[key], list):
|
|
234
|
+
combined_keys[key] = []
|
|
235
|
+
for item in first_domain[key]:
|
|
236
|
+
if item not in second_domain[key]:
|
|
237
|
+
combined_keys[key].append(item)
|
|
238
|
+
|
|
239
|
+
# if list is empty, remove it
|
|
240
|
+
if not combined_keys[key]:
|
|
241
|
+
del combined_keys[key]
|
|
242
|
+
|
|
243
|
+
return combined_keys
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _diff_nlu_examples(
|
|
247
|
+
new_example: Dict,
|
|
248
|
+
nlu_diff: List,
|
|
249
|
+
match_key: str,
|
|
250
|
+
match_value: str,
|
|
251
|
+
original_nlu_examples: List,
|
|
252
|
+
) -> None:
|
|
253
|
+
"""Creates diff of nlu data examples.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
new_example (Dict): intent or synonym with examples
|
|
257
|
+
nlu_diff (List): list of diff examples
|
|
258
|
+
match_key (str): intent or synonym name
|
|
259
|
+
match_value (str): intent or synonym value
|
|
260
|
+
original_nlu_examples (List): original nlu examples
|
|
261
|
+
"""
|
|
262
|
+
orig = list(
|
|
263
|
+
filter(
|
|
264
|
+
lambda x: x.get(match_key) == match_value,
|
|
265
|
+
original_nlu_examples,
|
|
297
266
|
)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
267
|
+
)
|
|
268
|
+
if len(orig) == 1:
|
|
269
|
+
orig_ex = orig[0]["examples"].split("\n")
|
|
270
|
+
new_ex = new_example["examples"].split("\n")
|
|
271
|
+
new_example["examples"] = "\n".join(list(set(new_ex).difference(set(orig_ex))))
|
|
272
|
+
if not new_example["examples"]:
|
|
273
|
+
nlu_diff.remove(new_example)
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def create_new_nlu_from_diff(
|
|
277
|
+
studio_nlu: Dict[str, Any], original_nlu: Dict[str, Any]
|
|
278
|
+
) -> Dict:
|
|
279
|
+
"""Create a new nlu file from the diff."""
|
|
280
|
+
# `or []` handles the case where the data contains the property as an empty
|
|
281
|
+
# key, example yaml:
|
|
282
|
+
# ```
|
|
283
|
+
# nlu:
|
|
284
|
+
# ```
|
|
285
|
+
# in this case, the yaml parser will return an empty dict (because it
|
|
286
|
+
# can't know that it is supposed to be a list, so we need to convert it
|
|
287
|
+
# to a list
|
|
288
|
+
studio_nlu_data = studio_nlu.get("nlu", []) or []
|
|
289
|
+
original_nlu_data = original_nlu.get("nlu", []) or []
|
|
290
|
+
|
|
291
|
+
nlu_diff = []
|
|
292
|
+
for new in studio_nlu_data:
|
|
293
|
+
if new in original_nlu_data:
|
|
294
|
+
continue
|
|
295
|
+
|
|
296
|
+
nlu_diff.append(new)
|
|
297
|
+
intent = new.get("intent")
|
|
298
|
+
if intent:
|
|
299
|
+
_diff_nlu_examples(new, nlu_diff, "intent", intent, original_nlu_data)
|
|
300
|
+
synonym = new.get("synonym")
|
|
301
|
+
if synonym:
|
|
302
|
+
_diff_nlu_examples(new, nlu_diff, "synonym", synonym, original_nlu_data)
|
|
303
|
+
|
|
304
|
+
return {"nlu": nlu_diff}
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def create_new_flows_from_diff(
|
|
308
|
+
studio_flows: List[Flow], original_flows: List[Flow]
|
|
309
|
+
) -> List[Flow]:
|
|
310
|
+
"""Create a new flows file from the diff."""
|
|
311
|
+
flows_new = [new for new in studio_flows if new not in original_flows]
|
|
312
|
+
return flows_new
|
|
306
313
|
|
|
307
314
|
|
|
308
315
|
def import_data_from_studio(
|
rasa/studio/download.py
CHANGED
|
@@ -1,19 +1,24 @@
|
|
|
1
1
|
import argparse
|
|
2
2
|
import logging
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Any, Dict, List, Optional, Set
|
|
4
|
+
from typing import Any, Dict, List, Optional, Set, Tuple
|
|
5
|
+
|
|
6
|
+
import questionary
|
|
7
|
+
import structlog
|
|
5
8
|
|
|
6
9
|
import rasa.cli.utils
|
|
7
10
|
import rasa.shared.utils.cli
|
|
8
11
|
from rasa.shared.constants import (
|
|
9
12
|
DEFAULT_DATA_PATH,
|
|
10
|
-
|
|
13
|
+
DEFAULT_DOMAIN_PATH,
|
|
14
|
+
DEFAULT_CONFIG_PATH,
|
|
15
|
+
DEFAULT_ENDPOINTS_PATH,
|
|
11
16
|
)
|
|
12
17
|
from rasa.shared.core.domain import Domain
|
|
13
18
|
from rasa.shared.core.flows.yaml_flows_io import YamlFlowsWriter
|
|
14
19
|
from rasa.shared.importers.importer import TrainingDataImporter
|
|
15
20
|
from rasa.shared.utils.yaml import read_yaml
|
|
16
|
-
|
|
21
|
+
from rasa.studio import data_handler
|
|
17
22
|
from rasa.studio.config import StudioConfig
|
|
18
23
|
from rasa.studio.constants import (
|
|
19
24
|
STUDIO_DOMAIN_FILENAME,
|
|
@@ -21,13 +26,119 @@ from rasa.studio.constants import (
|
|
|
21
26
|
STUDIO_NLU_FILENAME,
|
|
22
27
|
)
|
|
23
28
|
from rasa.studio.data_handler import (
|
|
24
|
-
DataDiffGenerator,
|
|
25
29
|
StudioDataHandler,
|
|
26
30
|
import_data_from_studio,
|
|
27
31
|
)
|
|
28
32
|
from rasa.utils.mapper import RasaPrimitiveStorageMapper
|
|
29
33
|
|
|
30
34
|
logger = logging.getLogger(__name__)
|
|
35
|
+
structlogger = structlog.getLogger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _handle_file_overwrite(
|
|
39
|
+
file_path: Optional[str], default_path: str, file_type: str
|
|
40
|
+
) -> Tuple[Optional[Path], bool]:
|
|
41
|
+
"""Handles the logic for determining whether to
|
|
42
|
+
overwrite an existing file or create a new one.
|
|
43
|
+
Works for config and endpoints at this moment
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
file_path (Optional[str]): The path to the file
|
|
47
|
+
provided by the user. Can be None.
|
|
48
|
+
default_path (str): The default path to use if `file_path`
|
|
49
|
+
is None or invalid. Must be a file path.
|
|
50
|
+
file_type (str): The type of the file (e.g., "config",
|
|
51
|
+
"endpoints") for logging and messaging purposes.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
tuple[Optional[Path], bool]: A tuple containing the
|
|
55
|
+
resolved file path and a boolean
|
|
56
|
+
indicating whether to write the file.
|
|
57
|
+
"""
|
|
58
|
+
file_already_exists = rasa.cli.utils.get_validated_path(
|
|
59
|
+
file_path, file_type, default_path, none_is_valid=True
|
|
60
|
+
)
|
|
61
|
+
write_file = False
|
|
62
|
+
path = None
|
|
63
|
+
file_or_default_path = file_path or default_path
|
|
64
|
+
|
|
65
|
+
if file_already_exists is None:
|
|
66
|
+
path = Path(file_or_default_path)
|
|
67
|
+
if path.is_dir():
|
|
68
|
+
path = path / default_path
|
|
69
|
+
return path, True
|
|
70
|
+
|
|
71
|
+
if questionary.confirm(
|
|
72
|
+
f"{file_type.capitalize()} file '{file_or_default_path}' "
|
|
73
|
+
f"already exists. Do you want to overwrite it?"
|
|
74
|
+
).ask():
|
|
75
|
+
write_file = True
|
|
76
|
+
path = Path(file_or_default_path)
|
|
77
|
+
return path, write_file
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _prepare_data_and_domain_paths(args: argparse.Namespace) -> Tuple[Path, List[Path]]:
|
|
81
|
+
"""Handles the logic for preparing the domain and data paths
|
|
82
|
+
based on the provided arguments.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
args (argparse.Namespace): The parsed arguments.
|
|
86
|
+
|
|
87
|
+
Returns:
|
|
88
|
+
tuple[Path, list[Path]]: A tuple containing the domain path
|
|
89
|
+
and a list of data paths.
|
|
90
|
+
"""
|
|
91
|
+
# prepare domain
|
|
92
|
+
domain_path = rasa.cli.utils.get_validated_path(
|
|
93
|
+
args.domain, "domain", DEFAULT_DOMAIN_PATH, none_is_valid=True
|
|
94
|
+
)
|
|
95
|
+
domain_or_default_path = args.domain or DEFAULT_DOMAIN_PATH
|
|
96
|
+
|
|
97
|
+
if domain_path is None:
|
|
98
|
+
# If the path is None, use the provided domain path
|
|
99
|
+
domain_path = Path(domain_or_default_path)
|
|
100
|
+
domain_path.touch()
|
|
101
|
+
|
|
102
|
+
if isinstance(domain_path, str):
|
|
103
|
+
domain_path = Path(domain_path)
|
|
104
|
+
|
|
105
|
+
if domain_path.is_file():
|
|
106
|
+
if not args.overwrite:
|
|
107
|
+
domain_path.unlink()
|
|
108
|
+
domain_path.touch()
|
|
109
|
+
|
|
110
|
+
if domain_path.is_dir():
|
|
111
|
+
if not args.overwrite:
|
|
112
|
+
domain_path = domain_path / STUDIO_DOMAIN_FILENAME
|
|
113
|
+
domain_path.touch()
|
|
114
|
+
|
|
115
|
+
# prepare data
|
|
116
|
+
data_paths = []
|
|
117
|
+
|
|
118
|
+
for f in args.data:
|
|
119
|
+
data_path = rasa.cli.utils.get_validated_path(
|
|
120
|
+
f, "data", DEFAULT_DATA_PATH, none_is_valid=True
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if data_path is None:
|
|
124
|
+
# If the path is None, use the default data path
|
|
125
|
+
data_path = Path(f)
|
|
126
|
+
data_path.mkdir(parents=True, exist_ok=True)
|
|
127
|
+
else:
|
|
128
|
+
data_path = Path(data_path)
|
|
129
|
+
|
|
130
|
+
if data_path.is_file() or data_path.is_dir():
|
|
131
|
+
# If it's a file, add it directly
|
|
132
|
+
data_paths.append(data_path)
|
|
133
|
+
else:
|
|
134
|
+
# If it doesn't exist, create the directory
|
|
135
|
+
data_path.mkdir(parents=True, exist_ok=True)
|
|
136
|
+
data_paths.append(data_path)
|
|
137
|
+
|
|
138
|
+
# Remove duplicates while preserving order
|
|
139
|
+
data_paths = list(dict.fromkeys(data_paths))
|
|
140
|
+
|
|
141
|
+
return domain_path, data_paths
|
|
31
142
|
|
|
32
143
|
|
|
33
144
|
def handle_download(args: argparse.Namespace) -> None:
|
|
@@ -35,19 +146,43 @@ def handle_download(args: argparse.Namespace) -> None:
|
|
|
35
146
|
studio_config=StudioConfig.read_config(), assistant_name=args.assistant_name[0]
|
|
36
147
|
)
|
|
37
148
|
handler.request_all_data()
|
|
38
|
-
|
|
39
|
-
|
|
149
|
+
|
|
150
|
+
domain_path, data_paths = _prepare_data_and_domain_paths(args)
|
|
151
|
+
|
|
152
|
+
# handle config and endpoints
|
|
153
|
+
config_path, write_config = _handle_file_overwrite(
|
|
154
|
+
args.config, DEFAULT_CONFIG_PATH, "config"
|
|
155
|
+
)
|
|
156
|
+
endpoints_path, write_endpoints = _handle_file_overwrite(
|
|
157
|
+
args.endpoints, DEFAULT_ENDPOINTS_PATH, "endpoints"
|
|
40
158
|
)
|
|
41
|
-
domain_path = Path(domain_path)
|
|
42
159
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
160
|
+
# generate log message if we write the config or endpoints
|
|
161
|
+
message_parts = []
|
|
162
|
+
|
|
163
|
+
config_path = config_path if write_config else None
|
|
164
|
+
endpoints_path = endpoints_path if write_endpoints else None
|
|
165
|
+
|
|
166
|
+
if config_path:
|
|
167
|
+
config_data = handler.get_config()
|
|
168
|
+
if not config_data:
|
|
169
|
+
rasa.shared.utils.cli.print_error_and_exit("No config data found.")
|
|
170
|
+
with open(config_path, "w") as f:
|
|
171
|
+
f.write(config_data)
|
|
172
|
+
message_parts.append(f"config to '{config_path}'")
|
|
173
|
+
|
|
174
|
+
if endpoints_path:
|
|
175
|
+
endpoints_data = handler.get_endpoints()
|
|
176
|
+
if not endpoints_data:
|
|
177
|
+
raise ValueError("No endpoints data found.")
|
|
178
|
+
|
|
179
|
+
with open(endpoints_path, "w") as f:
|
|
180
|
+
f.write(endpoints_data)
|
|
181
|
+
message_parts.append(f"endpoints to '{endpoints_path}'")
|
|
182
|
+
|
|
183
|
+
if message_parts:
|
|
184
|
+
message = "Downloaded " + " and ".join(message_parts)
|
|
185
|
+
structlogger.info("studio.download.config_endpoints", event_info=message)
|
|
51
186
|
|
|
52
187
|
if not args.overwrite:
|
|
53
188
|
_handle_download_no_overwrite(
|
|
@@ -74,11 +209,10 @@ def _handle_download_no_overwrite(
|
|
|
74
209
|
|
|
75
210
|
if domain_path.is_dir():
|
|
76
211
|
studio_domain_path = domain_path / STUDIO_DOMAIN_FILENAME
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
212
|
+
new_domain_data = data_handler.combine_domains(
|
|
213
|
+
data_from_studio.get_domain().as_dict(),
|
|
214
|
+
data_original.get_domain().as_dict(),
|
|
80
215
|
)
|
|
81
|
-
new_domain_data = diff_eng.create_new_domain_from_diff()
|
|
82
216
|
studio_domain = Domain.from_dict(new_domain_data)
|
|
83
217
|
if not studio_domain.is_empty():
|
|
84
218
|
studio_domain.persist(studio_domain_path)
|
|
@@ -125,11 +259,10 @@ def _persist_nlu_diff(
|
|
|
125
259
|
data_path: Path,
|
|
126
260
|
) -> None:
|
|
127
261
|
"""Creates a new nlu file from the diff of original and studio data."""
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
262
|
+
new_nlu_data = data_handler.create_new_nlu_from_diff(
|
|
263
|
+
read_yaml(data_from_studio.get_nlu_data().nlu_as_yaml()),
|
|
264
|
+
read_yaml(data_original.get_nlu_data().nlu_as_yaml()),
|
|
131
265
|
)
|
|
132
|
-
new_nlu_data = diff_eng.create_new_nlu_from_diff()
|
|
133
266
|
if new_nlu_data["nlu"]:
|
|
134
267
|
pretty_write_nlu_yaml(new_nlu_data, data_path)
|
|
135
268
|
else:
|
|
@@ -142,11 +275,10 @@ def _persist_flows_diff(
|
|
|
142
275
|
data_path: Path,
|
|
143
276
|
) -> None:
|
|
144
277
|
"""Creates a new flows file from the diff of original and studio data."""
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
278
|
+
new_flows_data = data_handler.create_new_flows_from_diff(
|
|
279
|
+
data_from_studio.get_flows().underlying_flows,
|
|
280
|
+
data_original.get_flows().underlying_flows,
|
|
148
281
|
)
|
|
149
|
-
new_flows_data = diff_eng.create_new_flows_from_diff()
|
|
150
282
|
if new_flows_data:
|
|
151
283
|
YamlFlowsWriter.dump(new_flows_data, data_path)
|
|
152
284
|
else:
|
|
@@ -183,6 +315,7 @@ def _handle_download_with_overwrite(
|
|
|
183
315
|
mapper = RasaPrimitiveStorageMapper(
|
|
184
316
|
domain_path=domain_path, training_data_paths=data_paths
|
|
185
317
|
)
|
|
318
|
+
|
|
186
319
|
if domain_path.is_file():
|
|
187
320
|
domain_merged = data_from_studio.get_domain().merge(data_original.get_domain())
|
|
188
321
|
domain_merged.persist(domain_path)
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from typing import Callable, Any, Dict
|
|
4
|
+
|
|
5
|
+
import structlog
|
|
6
|
+
from keycloak.exceptions import KeycloakError
|
|
7
|
+
from requests.exceptions import RequestException, Timeout, ConnectionError
|
|
8
|
+
|
|
9
|
+
from rasa.shared.exceptions import RasaException
|
|
10
|
+
from rasa.shared.utils.cli import print_success, print_error
|
|
11
|
+
from rasa.studio.config import StudioConfig
|
|
12
|
+
|
|
13
|
+
structlogger = structlog.get_logger()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class StudioResult:
|
|
18
|
+
message: str
|
|
19
|
+
was_successful: bool
|
|
20
|
+
|
|
21
|
+
@staticmethod
|
|
22
|
+
def success(message: str) -> "StudioResult":
|
|
23
|
+
return StudioResult(message, was_successful=True)
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def error(response: Dict[str, Any]) -> "StudioResult":
|
|
27
|
+
"""Create a StudioResult from a GraphQL error response.
|
|
28
|
+
|
|
29
|
+
Factory will evaluate the response and return a StudioResult with the
|
|
30
|
+
appropriate message and success status.
|
|
31
|
+
"""
|
|
32
|
+
if isinstance(response.get("errors"), list):
|
|
33
|
+
error_details = "; ".join(
|
|
34
|
+
[error.get("message", "Unknown error") for error in response["errors"]]
|
|
35
|
+
)
|
|
36
|
+
else:
|
|
37
|
+
error_details = "No detailed error information available."
|
|
38
|
+
|
|
39
|
+
structlogger.warn(
|
|
40
|
+
"studio.graphql_error", event_info=error_details, response=response
|
|
41
|
+
)
|
|
42
|
+
return StudioResult(error_details, was_successful=False)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def with_studio_error_handler(
|
|
46
|
+
func: Callable[..., StudioResult],
|
|
47
|
+
) -> Callable[..., StudioResult]:
|
|
48
|
+
@wraps(func)
|
|
49
|
+
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
50
|
+
try:
|
|
51
|
+
result = func(*args, **kwargs)
|
|
52
|
+
if result.was_successful:
|
|
53
|
+
print_success(result.message)
|
|
54
|
+
else:
|
|
55
|
+
print_error(result.message)
|
|
56
|
+
return result
|
|
57
|
+
except RasaException as e:
|
|
58
|
+
return _handle_rasa_exception(e)
|
|
59
|
+
except KeycloakError as e:
|
|
60
|
+
return _handle_keycloak_error(e)
|
|
61
|
+
except ConnectionError as e:
|
|
62
|
+
return _handle_connection_error(e)
|
|
63
|
+
except Timeout as e:
|
|
64
|
+
return _handle_timeout_error(e)
|
|
65
|
+
except RequestException as e:
|
|
66
|
+
return _handle_request_exception(e)
|
|
67
|
+
except Exception as e:
|
|
68
|
+
return _handle_unexpected_error(e)
|
|
69
|
+
|
|
70
|
+
return wrapper
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def response_has_errors(response: Dict) -> bool:
|
|
74
|
+
return (
|
|
75
|
+
"errors" in response
|
|
76
|
+
and isinstance(response["errors"], list)
|
|
77
|
+
and len(response["errors"]) > 0
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _handle_rasa_exception(e: RasaException) -> StudioResult:
|
|
82
|
+
error_msg = "Rasa internal exception was raised while interacting with Studio."
|
|
83
|
+
structlogger.error("studio.rasa_error", event_info=error_msg, exception=str(e))
|
|
84
|
+
return StudioResult(message=str(e), was_successful=False)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _handle_keycloak_error(e: KeycloakError) -> StudioResult:
|
|
88
|
+
error_msg = (
|
|
89
|
+
f"Unable to authenticate with Keycloak at "
|
|
90
|
+
f"{StudioConfig.read_config().authentication_server_url} "
|
|
91
|
+
)
|
|
92
|
+
if e.response_code == 401:
|
|
93
|
+
error_msg += "Please check if the credentials are correct."
|
|
94
|
+
elif e.error_message.startswith("Can't connect to server"):
|
|
95
|
+
error_msg += (
|
|
96
|
+
"Please check if the server is running "
|
|
97
|
+
"and the configured URL is correct. \n"
|
|
98
|
+
"You may need to reconfigure Rasa Studio "
|
|
99
|
+
"using 'rasa studio config'."
|
|
100
|
+
)
|
|
101
|
+
else:
|
|
102
|
+
error_msg += f"Error message: {e.error_message}"
|
|
103
|
+
structlogger.error("studio.keycloak_error", event_info=error_msg, exception=str(e))
|
|
104
|
+
return StudioResult(error_msg, was_successful=False)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _handle_connection_error(e: ConnectionError) -> StudioResult:
|
|
108
|
+
studio_url = StudioConfig.read_config().studio_url
|
|
109
|
+
error_msg = (
|
|
110
|
+
f"Unable to reach Rasa Studio API at {studio_url} \n"
|
|
111
|
+
"Please check if Studio is running and the configured URL is correct. \n"
|
|
112
|
+
"You may need to reconfigure Rasa Studio using 'rasa studio config'."
|
|
113
|
+
)
|
|
114
|
+
structlogger.error("studio.graphql_error", event_info=error_msg, exception=str(e))
|
|
115
|
+
return StudioResult(error_msg, was_successful=False)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _handle_timeout_error(e: Timeout) -> StudioResult:
|
|
119
|
+
error_msg = "The request timed out. Please try again later."
|
|
120
|
+
structlogger.error("studio.graphql_timeout", event_info=error_msg, exception=str(e))
|
|
121
|
+
return StudioResult(error_msg, was_successful=False)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _handle_request_exception(e: RequestException) -> StudioResult:
|
|
125
|
+
error_msg = f"An error occurred while communicating with the server: {e!s}"
|
|
126
|
+
structlogger.error(
|
|
127
|
+
"studio.graphql.request_exception", event_info=error_msg, exception=str(e)
|
|
128
|
+
)
|
|
129
|
+
return StudioResult(error_msg, was_successful=False)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _handle_unexpected_error(e: Exception) -> StudioResult:
|
|
133
|
+
error_msg = f"An unexpected error occurred: {e!s}"
|
|
134
|
+
structlogger.exception(
|
|
135
|
+
"studio.unexpected_error", event_info=error_msg, exception=str(e)
|
|
136
|
+
)
|
|
137
|
+
return StudioResult(error_msg, was_successful=False)
|
rasa/studio/train.py
CHANGED
|
@@ -19,11 +19,11 @@ from rasa.shared.constants import (
|
|
|
19
19
|
from rasa.shared.core.flows.yaml_flows_io import YamlFlowsWriter
|
|
20
20
|
from rasa.shared.importers.importer import TrainingDataImporter
|
|
21
21
|
from rasa.shared.utils.yaml import read_yaml, write_yaml
|
|
22
|
+
from rasa.studio import data_handler
|
|
22
23
|
from rasa.utils.common import get_temp_dir_name
|
|
23
24
|
|
|
24
25
|
from rasa.studio.config import StudioConfig
|
|
25
26
|
from rasa.studio.data_handler import (
|
|
26
|
-
DataDiffGenerator,
|
|
27
27
|
StudioDataHandler,
|
|
28
28
|
import_data_from_studio,
|
|
29
29
|
)
|
|
@@ -50,9 +50,9 @@ def handle_train(args: argparse.Namespace) -> Optional[str]:
|
|
|
50
50
|
handler, domain, args.data
|
|
51
51
|
)
|
|
52
52
|
|
|
53
|
-
domain = data_original.get_domain().merge(data_form_studio.get_domain()) # type: ignore[assignment]
|
|
53
|
+
domain = data_original.get_domain().merge(data_form_studio.get_domain()) # type: ignore[assignment]
|
|
54
54
|
|
|
55
|
-
domain_file = _create_temp_file(read_yaml(domain.as_yaml()), "domain.yml") # type: ignore[union-attr]
|
|
55
|
+
domain_file = _create_temp_file(read_yaml(domain.as_yaml()), "domain.yml") # type: ignore[union-attr]
|
|
56
56
|
|
|
57
57
|
studio_training_files = make_training_files(
|
|
58
58
|
handler, data_form_studio, data_original
|
|
@@ -116,11 +116,10 @@ def make_training_files(
|
|
|
116
116
|
training_files.append(training_file)
|
|
117
117
|
|
|
118
118
|
if handler.has_flows():
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
diff_flows = data_handler.create_new_flows_from_diff(
|
|
120
|
+
data_form_studio.get_flows().underlying_flows,
|
|
121
|
+
data_original.get_flows().underlying_flows,
|
|
122
122
|
)
|
|
123
|
-
diff_flows = diff.create_new_flows_from_diff()
|
|
124
123
|
tmp_dir = get_temp_dir_name()
|
|
125
124
|
training_file = Path(tmp_dir, "flows.yml")
|
|
126
125
|
YamlFlowsWriter.dump(diff_flows, training_file)
|