rasa-pro 3.9.18__py3-none-any.whl → 3.10.4__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 +26 -57
- rasa/__init__.py +1 -2
- rasa/__main__.py +5 -0
- rasa/anonymization/anonymization_rule_executor.py +2 -2
- rasa/api.py +26 -22
- rasa/cli/arguments/data.py +27 -2
- rasa/cli/arguments/default_arguments.py +25 -3
- rasa/cli/arguments/run.py +9 -9
- rasa/cli/arguments/train.py +2 -0
- rasa/cli/data.py +70 -8
- rasa/cli/e2e_test.py +108 -433
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +395 -0
- rasa/cli/project_templates/calm/endpoints.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +14 -13
- rasa/cli/scaffold.py +10 -8
- rasa/cli/train.py +8 -7
- rasa/cli/utils.py +15 -0
- rasa/constants.py +7 -1
- rasa/core/actions/action.py +98 -49
- rasa/core/actions/action_run_slot_rejections.py +4 -1
- rasa/core/actions/custom_action_executor.py +9 -6
- rasa/core/actions/direct_custom_actions_executor.py +80 -0
- rasa/core/actions/e2e_stub_custom_action_executor.py +68 -0
- rasa/core/actions/grpc_custom_action_executor.py +2 -2
- rasa/core/actions/http_custom_action_executor.py +6 -5
- rasa/core/agent.py +21 -17
- rasa/core/channels/__init__.py +2 -0
- rasa/core/channels/audiocodes.py +1 -16
- rasa/core/channels/inspector/dist/index.html +0 -2
- rasa/core/channels/inspector/index.html +0 -2
- rasa/core/channels/voice_aware/__init__.py +0 -0
- rasa/core/channels/voice_aware/jambonz.py +103 -0
- rasa/core/channels/voice_aware/jambonz_protocol.py +344 -0
- rasa/core/channels/voice_aware/utils.py +20 -0
- rasa/core/channels/voice_native/__init__.py +0 -0
- rasa/core/constants.py +6 -1
- rasa/core/featurizers/single_state_featurizer.py +1 -22
- rasa/core/featurizers/tracker_featurizers.py +18 -115
- rasa/core/information_retrieval/faiss.py +7 -4
- rasa/core/information_retrieval/information_retrieval.py +8 -0
- rasa/core/information_retrieval/milvus.py +9 -2
- rasa/core/information_retrieval/qdrant.py +1 -1
- rasa/core/nlg/contextual_response_rephraser.py +32 -10
- rasa/core/nlg/summarize.py +4 -3
- rasa/core/policies/enterprise_search_policy.py +100 -44
- rasa/core/policies/flows/flow_executor.py +130 -94
- rasa/core/policies/intentless_policy.py +52 -28
- rasa/core/policies/ted_policy.py +33 -58
- rasa/core/policies/unexpected_intent_policy.py +7 -15
- rasa/core/processor.py +20 -53
- rasa/core/run.py +5 -4
- rasa/core/tracker_store.py +8 -4
- rasa/core/utils.py +45 -56
- rasa/dialogue_understanding/coexistence/llm_based_router.py +45 -12
- rasa/dialogue_understanding/commands/__init__.py +4 -0
- rasa/dialogue_understanding/commands/change_flow_command.py +0 -6
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/set_slot_command.py +1 -5
- rasa/dialogue_understanding/commands/utils.py +38 -0
- rasa/dialogue_understanding/generator/constants.py +10 -3
- rasa/dialogue_understanding/generator/flow_retrieval.py +14 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +12 -2
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +106 -87
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +28 -6
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +90 -37
- rasa/dialogue_understanding/patterns/default_flows_for_patterns.yml +15 -15
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +13 -14
- rasa/e2e_test/aggregate_test_stats_calculator.py +124 -0
- rasa/e2e_test/assertions.py +1181 -0
- rasa/e2e_test/assertions_schema.yml +106 -0
- rasa/e2e_test/constants.py +20 -0
- rasa/e2e_test/e2e_config.py +220 -0
- rasa/e2e_test/e2e_config_schema.yml +26 -0
- rasa/e2e_test/e2e_test_case.py +131 -8
- rasa/e2e_test/e2e_test_converter.py +363 -0
- rasa/e2e_test/e2e_test_converter_prompt.jinja2 +70 -0
- rasa/e2e_test/e2e_test_coverage_report.py +364 -0
- rasa/e2e_test/e2e_test_result.py +26 -6
- rasa/e2e_test/e2e_test_runner.py +491 -72
- rasa/e2e_test/e2e_test_schema.yml +96 -0
- rasa/e2e_test/pykwalify_extensions.py +39 -0
- rasa/e2e_test/stub_custom_action.py +70 -0
- rasa/e2e_test/utils/__init__.py +0 -0
- rasa/e2e_test/utils/e2e_yaml_utils.py +55 -0
- rasa/e2e_test/utils/io.py +596 -0
- rasa/e2e_test/utils/validation.py +80 -0
- rasa/engine/recipes/default_components.py +0 -2
- rasa/engine/storage/local_model_storage.py +0 -1
- rasa/env.py +9 -0
- rasa/keys +1 -0
- rasa/llm_fine_tuning/__init__.py +0 -0
- rasa/llm_fine_tuning/annotation_module.py +241 -0
- rasa/llm_fine_tuning/conversations.py +144 -0
- rasa/llm_fine_tuning/llm_data_preparation_module.py +178 -0
- rasa/llm_fine_tuning/notebooks/unsloth_finetuning.ipynb +407 -0
- rasa/llm_fine_tuning/paraphrasing/__init__.py +0 -0
- rasa/llm_fine_tuning/paraphrasing/conversation_rephraser.py +281 -0
- rasa/llm_fine_tuning/paraphrasing/default_rephrase_prompt_template.jina2 +44 -0
- rasa/llm_fine_tuning/paraphrasing/rephrase_validator.py +121 -0
- rasa/llm_fine_tuning/paraphrasing/rephrased_user_message.py +10 -0
- rasa/llm_fine_tuning/paraphrasing_module.py +128 -0
- rasa/llm_fine_tuning/storage.py +174 -0
- rasa/llm_fine_tuning/train_test_split_module.py +441 -0
- rasa/model_training.py +48 -16
- rasa/nlu/classifiers/diet_classifier.py +25 -38
- rasa/nlu/classifiers/logistic_regression_classifier.py +9 -44
- rasa/nlu/classifiers/sklearn_intent_classifier.py +16 -37
- rasa/nlu/extractors/crf_entity_extractor.py +50 -93
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -78
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +17 -52
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +3 -5
- rasa/nlu/persistor.py +129 -32
- rasa/server.py +45 -10
- rasa/shared/constants.py +63 -15
- rasa/shared/core/domain.py +15 -12
- rasa/shared/core/events.py +28 -2
- rasa/shared/core/flows/flow.py +208 -13
- rasa/shared/core/flows/flow_path.py +84 -0
- rasa/shared/core/flows/flows_list.py +28 -10
- rasa/shared/core/flows/flows_yaml_schema.json +269 -193
- rasa/shared/core/flows/validation.py +112 -25
- rasa/shared/core/flows/yaml_flows_io.py +149 -10
- rasa/shared/core/trackers.py +6 -0
- rasa/shared/core/training_data/visualization.html +2 -2
- rasa/shared/exceptions.py +4 -0
- rasa/shared/importers/importer.py +60 -11
- rasa/shared/importers/remote_importer.py +196 -0
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/nlu/training_data/features.py +2 -120
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +181 -0
- rasa/shared/providers/_configs/client_config.py +57 -0
- rasa/shared/providers/_configs/default_litellm_client_config.py +130 -0
- rasa/shared/providers/_configs/huggingface_local_embedding_client_config.py +234 -0
- rasa/shared/providers/_configs/openai_client_config.py +175 -0
- rasa/shared/providers/_configs/self_hosted_llm_client_config.py +171 -0
- rasa/shared/providers/_configs/utils.py +101 -0
- rasa/shared/providers/_ssl_verification_utils.py +124 -0
- rasa/shared/providers/embedding/__init__.py +0 -0
- rasa/shared/providers/embedding/_base_litellm_embedding_client.py +254 -0
- rasa/shared/providers/embedding/_langchain_embedding_client_adapter.py +74 -0
- rasa/shared/providers/embedding/azure_openai_embedding_client.py +277 -0
- rasa/shared/providers/embedding/default_litellm_embedding_client.py +102 -0
- rasa/shared/providers/embedding/embedding_client.py +90 -0
- rasa/shared/providers/embedding/embedding_response.py +41 -0
- rasa/shared/providers/embedding/huggingface_local_embedding_client.py +191 -0
- rasa/shared/providers/embedding/openai_embedding_client.py +172 -0
- rasa/shared/providers/llm/__init__.py +0 -0
- rasa/shared/providers/llm/_base_litellm_client.py +227 -0
- rasa/shared/providers/llm/azure_openai_llm_client.py +338 -0
- rasa/shared/providers/llm/default_litellm_llm_client.py +84 -0
- rasa/shared/providers/llm/llm_client.py +76 -0
- rasa/shared/providers/llm/llm_response.py +50 -0
- rasa/shared/providers/llm/openai_llm_client.py +155 -0
- rasa/shared/providers/llm/self_hosted_llm_client.py +169 -0
- rasa/shared/providers/mappings.py +75 -0
- rasa/shared/utils/cli.py +30 -0
- rasa/shared/utils/io.py +65 -3
- rasa/shared/utils/llm.py +223 -200
- rasa/shared/utils/yaml.py +122 -7
- rasa/studio/download.py +19 -13
- rasa/studio/train.py +2 -3
- rasa/studio/upload.py +2 -3
- rasa/telemetry.py +113 -58
- rasa/tracing/config.py +2 -3
- rasa/tracing/instrumentation/attribute_extractors.py +29 -17
- rasa/tracing/instrumentation/instrumentation.py +4 -47
- rasa/utils/common.py +18 -19
- rasa/utils/endpoints.py +7 -4
- rasa/utils/io.py +66 -0
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +9 -1
- rasa/utils/ml_utils.py +4 -2
- rasa/utils/tensorflow/model_data.py +193 -2
- rasa/validator.py +196 -1
- rasa/version.py +1 -1
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/METADATA +47 -72
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/RECORD +186 -121
- rasa/nlu/classifiers/llm_intent_classifier.py +0 -519
- rasa/shared/providers/openai/clients.py +0 -43
- rasa/shared/providers/openai/session_handler.py +0 -110
- rasa/utils/tensorflow/feature_array.py +0 -366
- /rasa/{shared/providers/openai → cli/project_templates/tutorial/actions}/__init__.py +0 -0
- /rasa/cli/project_templates/tutorial/{actions.py → actions/actions.py} +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/NOTICE +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/WHEEL +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.4.dist-info}/entry_points.txt +0 -0
rasa/shared/core/flows/flow.py
CHANGED
|
@@ -1,33 +1,40 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import copy
|
|
3
4
|
from dataclasses import dataclass, field
|
|
4
5
|
from functools import cached_property
|
|
5
|
-
from
|
|
6
|
-
from
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Text, Optional, Dict, Any, List, Set, Union
|
|
8
|
+
|
|
7
9
|
import structlog
|
|
10
|
+
from pypred import Predicate
|
|
8
11
|
|
|
9
12
|
import rasa.shared.utils.io
|
|
10
13
|
from rasa.shared.constants import RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
14
|
+
from rasa.shared.core.flows.flow_path import PathNode, FlowPath, FlowPathsList
|
|
11
15
|
from rasa.shared.core.flows.flow_step import FlowStep
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
CONTINUE_STEP_PREFIX,
|
|
18
|
-
START_STEP,
|
|
19
|
-
END_STEP,
|
|
16
|
+
from rasa.shared.core.flows.flow_step_links import (
|
|
17
|
+
FlowStepLink,
|
|
18
|
+
StaticFlowStepLink,
|
|
19
|
+
IfFlowStepLink,
|
|
20
|
+
ElseFlowStepLink,
|
|
20
21
|
)
|
|
22
|
+
from rasa.shared.core.flows.flow_step_sequence import FlowStepSequence
|
|
23
|
+
from rasa.shared.core.flows.nlu_trigger import NLUTriggers
|
|
21
24
|
from rasa.shared.core.flows.steps import (
|
|
22
25
|
CollectInformationFlowStep,
|
|
23
26
|
EndFlowStep,
|
|
24
27
|
StartFlowStep,
|
|
25
28
|
ActionFlowStep,
|
|
26
29
|
)
|
|
27
|
-
from rasa.shared.core.flows.
|
|
30
|
+
from rasa.shared.core.flows.steps.constants import (
|
|
31
|
+
CONTINUE_STEP_PREFIX,
|
|
32
|
+
START_STEP,
|
|
33
|
+
END_STEP,
|
|
34
|
+
)
|
|
35
|
+
from rasa.shared.core.flows.steps.continuation import ContinueFlowStep
|
|
28
36
|
from rasa.shared.core.slots import Slot
|
|
29
37
|
|
|
30
|
-
|
|
31
38
|
structlogger = structlog.get_logger()
|
|
32
39
|
|
|
33
40
|
|
|
@@ -51,14 +58,21 @@ class Flow:
|
|
|
51
58
|
"""
|
|
52
59
|
A flag that checks whether the flow should always be included in the prompt or not.
|
|
53
60
|
"""
|
|
61
|
+
file_path: Optional[str] = None
|
|
62
|
+
"""The path to the file where the flow is stored."""
|
|
54
63
|
|
|
55
64
|
@staticmethod
|
|
56
|
-
def from_json(
|
|
65
|
+
def from_json(
|
|
66
|
+
flow_id: Text,
|
|
67
|
+
data: Dict[Text, Any],
|
|
68
|
+
file_path: Optional[Union[str, Path]] = None,
|
|
69
|
+
) -> Flow:
|
|
57
70
|
"""Create a Flow object from serialized data.
|
|
58
71
|
|
|
59
72
|
Args:
|
|
60
73
|
flow_id: id of the flow
|
|
61
74
|
data: data for a Flow object in a serialized format.
|
|
75
|
+
file_path: the file path of the flow
|
|
62
76
|
|
|
63
77
|
Returns:
|
|
64
78
|
A Flow object.
|
|
@@ -66,6 +80,9 @@ class Flow:
|
|
|
66
80
|
step_sequence = FlowStepSequence.from_json(data.get("steps"))
|
|
67
81
|
nlu_triggers = NLUTriggers.from_json(data.get("nlu_trigger"))
|
|
68
82
|
|
|
83
|
+
if file_path and isinstance(file_path, Path):
|
|
84
|
+
file_path = str(file_path)
|
|
85
|
+
|
|
69
86
|
return Flow(
|
|
70
87
|
id=flow_id,
|
|
71
88
|
custom_name=data.get("name"),
|
|
@@ -75,8 +92,16 @@ class Flow:
|
|
|
75
92
|
guard_condition=str(data["if"]) if "if" in data else None,
|
|
76
93
|
step_sequence=Flow.resolve_default_ids(step_sequence),
|
|
77
94
|
nlu_triggers=nlu_triggers,
|
|
95
|
+
# If we are reading the flows in after training the file_path is part of
|
|
96
|
+
# data. When the model is trained, take the provided file_path.
|
|
97
|
+
file_path=data.get("file_path") if "file_path" in data else file_path,
|
|
78
98
|
)
|
|
79
99
|
|
|
100
|
+
def get_full_name(self) -> str:
|
|
101
|
+
if self.file_path:
|
|
102
|
+
return f"{self.file_path}::{self.name}"
|
|
103
|
+
return self.name
|
|
104
|
+
|
|
80
105
|
@staticmethod
|
|
81
106
|
def create_default_name(flow_id: str) -> str:
|
|
82
107
|
"""Create a default flow name for when it is missing."""
|
|
@@ -140,6 +165,8 @@ class Flow:
|
|
|
140
165
|
data["always_include_in_prompt"] = self.always_include_in_prompt
|
|
141
166
|
if self.nlu_triggers:
|
|
142
167
|
data["nlu_trigger"] = self.nlu_triggers.as_json()
|
|
168
|
+
if self.file_path:
|
|
169
|
+
data["file_path"] = self.file_path
|
|
143
170
|
|
|
144
171
|
return data
|
|
145
172
|
|
|
@@ -360,3 +387,171 @@ class Flow:
|
|
|
360
387
|
return True
|
|
361
388
|
|
|
362
389
|
return False
|
|
390
|
+
|
|
391
|
+
def extract_all_paths(self) -> FlowPathsList:
|
|
392
|
+
"""Extracts all possible flow paths.
|
|
393
|
+
|
|
394
|
+
Extracts all possible flow paths from a given flow structure by
|
|
395
|
+
recursively exploring each step.
|
|
396
|
+
This function initializes an empty list to collect paths, an empty path list,
|
|
397
|
+
and a set of visited step IDs to prevent revisiting steps.
|
|
398
|
+
It calls `go_over_steps` to recursively explore and fill the paths list.
|
|
399
|
+
"""
|
|
400
|
+
flow_paths_list = FlowPathsList(self.id, paths=[])
|
|
401
|
+
steps: List[FlowStep] = self.steps
|
|
402
|
+
current_path: FlowPath = FlowPath(flow=self.id, nodes=[])
|
|
403
|
+
step_ids_visited: Set[str] = set()
|
|
404
|
+
|
|
405
|
+
self._go_over_steps(steps, current_path, flow_paths_list, step_ids_visited)
|
|
406
|
+
|
|
407
|
+
if not flow_paths_list.is_path_part_of_list(current_path):
|
|
408
|
+
flow_paths_list.paths.append(copy.deepcopy(current_path))
|
|
409
|
+
|
|
410
|
+
structlogger.debug(
|
|
411
|
+
"shared.core.flows.flow.extract_all_paths",
|
|
412
|
+
comment="Extraction complete",
|
|
413
|
+
number_of_paths=len(flow_paths_list.paths),
|
|
414
|
+
flow_name=self.name,
|
|
415
|
+
)
|
|
416
|
+
return flow_paths_list
|
|
417
|
+
|
|
418
|
+
def _go_over_steps(
|
|
419
|
+
self,
|
|
420
|
+
steps_to_go: Union[str, List[FlowStep]],
|
|
421
|
+
current_path: FlowPath,
|
|
422
|
+
completed_paths: FlowPathsList,
|
|
423
|
+
step_ids_visited: Set[str],
|
|
424
|
+
) -> None:
|
|
425
|
+
"""Processes the flow steps recursively.
|
|
426
|
+
|
|
427
|
+
Either following direct step IDs or handling conditions, and adds complete
|
|
428
|
+
paths to the collected_paths.
|
|
429
|
+
|
|
430
|
+
Args:
|
|
431
|
+
steps_to_go: Either a direct step ID or a list of steps to process.
|
|
432
|
+
current_path: The current path being constructed.
|
|
433
|
+
completed_paths: The list where completed paths are added.
|
|
434
|
+
step_ids_visited: A set of step IDs that have been visited to avoid cycles.
|
|
435
|
+
|
|
436
|
+
Returns:
|
|
437
|
+
None: This function modifies collected_paths in place by appending new paths
|
|
438
|
+
as they are found.
|
|
439
|
+
"""
|
|
440
|
+
# Case 1: If the steps_to_go is a custom_id string
|
|
441
|
+
# This happens when a "next" of, for example, a IfFlowStepLink is targeting
|
|
442
|
+
# a specific step by id
|
|
443
|
+
if isinstance(steps_to_go, str):
|
|
444
|
+
for i, step in enumerate(self.steps):
|
|
445
|
+
# We don't need to check for 'id' as a link can only happen to a
|
|
446
|
+
# custom id.
|
|
447
|
+
if step.custom_id == steps_to_go:
|
|
448
|
+
self._go_over_steps(
|
|
449
|
+
self.steps[i:], current_path, completed_paths, step_ids_visited
|
|
450
|
+
)
|
|
451
|
+
|
|
452
|
+
# Case 2: If steps_to_go is a list of steps
|
|
453
|
+
else:
|
|
454
|
+
for i, step in enumerate(steps_to_go):
|
|
455
|
+
# 1. Check if the step is relevant for testable_paths extraction.
|
|
456
|
+
# We only create new path nodes for ActionFlowStep and
|
|
457
|
+
# CollectInformationFlowStep because these are externally visible
|
|
458
|
+
# changes in the assistant's behaviour (trackable in the e2e tests).
|
|
459
|
+
# For other flow steps, we only follow their links.
|
|
460
|
+
# We decided to ignore calls to other flows in our coverage analysis.
|
|
461
|
+
if not isinstance(step, (CollectInformationFlowStep, ActionFlowStep)):
|
|
462
|
+
self._handle_links(
|
|
463
|
+
step.next.links,
|
|
464
|
+
current_path,
|
|
465
|
+
completed_paths,
|
|
466
|
+
step_ids_visited,
|
|
467
|
+
)
|
|
468
|
+
continue
|
|
469
|
+
|
|
470
|
+
# 2. Check if already visited this custom step id
|
|
471
|
+
# in order to keep track of loops
|
|
472
|
+
if step.custom_id is not None and step.custom_id in step_ids_visited:
|
|
473
|
+
if not completed_paths.is_path_part_of_list(current_path):
|
|
474
|
+
completed_paths.paths.append(copy.deepcopy(current_path))
|
|
475
|
+
return # Stop traversing this path if we've revisited a step
|
|
476
|
+
elif step.custom_id is not None:
|
|
477
|
+
step_ids_visited.add(step.custom_id)
|
|
478
|
+
|
|
479
|
+
# 3. Append step info to the path
|
|
480
|
+
current_path.nodes.append(
|
|
481
|
+
PathNode(
|
|
482
|
+
flow=current_path.flow,
|
|
483
|
+
step_id=step.id,
|
|
484
|
+
lines=step.metadata["line_numbers"],
|
|
485
|
+
)
|
|
486
|
+
)
|
|
487
|
+
|
|
488
|
+
# 4. Check if 'END' branch
|
|
489
|
+
if (
|
|
490
|
+
len(step.next.links) == 1
|
|
491
|
+
and isinstance(step.next.links[0], StaticFlowStepLink)
|
|
492
|
+
and step.next.links[0].target == END_STEP
|
|
493
|
+
):
|
|
494
|
+
if not completed_paths.is_path_part_of_list(current_path):
|
|
495
|
+
completed_paths.paths.append(copy.deepcopy(current_path))
|
|
496
|
+
return
|
|
497
|
+
else:
|
|
498
|
+
self._handle_links(
|
|
499
|
+
step.next.links,
|
|
500
|
+
current_path,
|
|
501
|
+
completed_paths,
|
|
502
|
+
step_ids_visited,
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
def _handle_links(
|
|
506
|
+
self,
|
|
507
|
+
links: List[FlowStepLink],
|
|
508
|
+
path: FlowPath,
|
|
509
|
+
collected_paths: FlowPathsList,
|
|
510
|
+
step_ids_visited: set,
|
|
511
|
+
) -> None:
|
|
512
|
+
"""Processes the next step in a flow.
|
|
513
|
+
|
|
514
|
+
Potentially recursively calling itself to handle conditional paths and
|
|
515
|
+
branching.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
links: Links listed in the "next" attribute.
|
|
519
|
+
path: The current path taken in the flow.
|
|
520
|
+
collected_paths: A list of paths collected so far.
|
|
521
|
+
step_ids_visited: A set of step IDs that have already been visited
|
|
522
|
+
to avoid loops.
|
|
523
|
+
|
|
524
|
+
Returns:
|
|
525
|
+
None: Modifies collected_paths in place by appending new paths
|
|
526
|
+
as they are completed.
|
|
527
|
+
"""
|
|
528
|
+
steps = self.steps
|
|
529
|
+
|
|
530
|
+
for link in links:
|
|
531
|
+
# Direct step id reference
|
|
532
|
+
if isinstance(link, StaticFlowStepLink):
|
|
533
|
+
# Find this id in the flow steps and restart from there
|
|
534
|
+
for i, step in enumerate(steps):
|
|
535
|
+
if step.id == link.target_step_id:
|
|
536
|
+
self._go_over_steps(
|
|
537
|
+
steps[i:],
|
|
538
|
+
copy.deepcopy(path),
|
|
539
|
+
collected_paths,
|
|
540
|
+
copy.deepcopy(step_ids_visited),
|
|
541
|
+
)
|
|
542
|
+
|
|
543
|
+
# If conditions
|
|
544
|
+
elif isinstance(link, (IfFlowStepLink, ElseFlowStepLink)):
|
|
545
|
+
# Handling conditional paths
|
|
546
|
+
target_steps: Union[str, List[FlowStep]]
|
|
547
|
+
if isinstance(link.target_reference, FlowStepSequence):
|
|
548
|
+
target_steps = link.target_reference.child_steps
|
|
549
|
+
else:
|
|
550
|
+
target_steps = link.target_reference
|
|
551
|
+
|
|
552
|
+
self._go_over_steps(
|
|
553
|
+
target_steps,
|
|
554
|
+
copy.deepcopy(path),
|
|
555
|
+
collected_paths,
|
|
556
|
+
copy.deepcopy(step_ids_visited),
|
|
557
|
+
)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import List, Optional, Set
|
|
3
|
+
|
|
4
|
+
import structlog
|
|
5
|
+
|
|
6
|
+
NODE_KEY_SEPARATOR = " | "
|
|
7
|
+
|
|
8
|
+
structlogger = structlog.get_logger()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class PathNode:
|
|
13
|
+
"""Representation of a path step."""
|
|
14
|
+
|
|
15
|
+
step_id: str
|
|
16
|
+
"""Step ID"""
|
|
17
|
+
|
|
18
|
+
flow: str
|
|
19
|
+
"""Flow name"""
|
|
20
|
+
|
|
21
|
+
lines: Optional[str] = None
|
|
22
|
+
"""Line numbers range from the original flow .yaml file"""
|
|
23
|
+
|
|
24
|
+
def __eq__(self, other: object) -> bool:
|
|
25
|
+
if not isinstance(other, PathNode):
|
|
26
|
+
return False
|
|
27
|
+
|
|
28
|
+
return self.flow == other.flow and self.step_id == other.step_id
|
|
29
|
+
|
|
30
|
+
def __hash__(self) -> int:
|
|
31
|
+
return hash((self.flow, self.step_id))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class FlowPath:
|
|
36
|
+
"""Representation of a path through a flow.
|
|
37
|
+
|
|
38
|
+
Attributes:
|
|
39
|
+
flow (str): The name of the flow.
|
|
40
|
+
nodes (List[PathNode]): A list of nodes that constitute the path.
|
|
41
|
+
test_name (str): Name of the test from which it was extracted.
|
|
42
|
+
test_passing (bool): Test status: True if 'passed'.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
flow: str
|
|
46
|
+
nodes: List[PathNode] = field(default_factory=list)
|
|
47
|
+
|
|
48
|
+
def are_paths_matching(self, other_path: "FlowPath") -> bool:
|
|
49
|
+
"""Compares this FlowPath to another to determine if they are identical."""
|
|
50
|
+
if len(self.nodes) != len(other_path.nodes):
|
|
51
|
+
return False
|
|
52
|
+
return all(
|
|
53
|
+
node == other_node for node, other_node in zip(self.nodes, other_path.nodes)
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class FlowPathsList:
|
|
59
|
+
"""Representing a list of all available paths through a flow.
|
|
60
|
+
|
|
61
|
+
Attributes:
|
|
62
|
+
flow (str): The name of the flow.
|
|
63
|
+
paths (List[FlowPath]): All paths of that flow.
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
flow: str
|
|
67
|
+
paths: List[FlowPath] = field(default=list)
|
|
68
|
+
|
|
69
|
+
def get_unique_nodes(self) -> Set[PathNode]:
|
|
70
|
+
"""Returns the unique nodes of all flow paths."""
|
|
71
|
+
nodes = set()
|
|
72
|
+
|
|
73
|
+
for path in self.paths:
|
|
74
|
+
for node in path.nodes:
|
|
75
|
+
nodes.add(node)
|
|
76
|
+
|
|
77
|
+
return nodes
|
|
78
|
+
|
|
79
|
+
def get_number_of_unique_nodes(self) -> int:
|
|
80
|
+
return len(self.get_unique_nodes())
|
|
81
|
+
|
|
82
|
+
def is_path_part_of_list(self, flow_path: FlowPath) -> bool:
|
|
83
|
+
"""Checks if the FlowPath exists in a list of FlowPaths."""
|
|
84
|
+
return any(flow_path.are_paths_matching(path) for path in self.paths)
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from dataclasses import dataclass
|
|
3
|
-
from
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Generator, Any, Optional, Dict, Text, Set, Union
|
|
4
6
|
|
|
5
7
|
import rasa.shared.utils.io
|
|
6
8
|
from rasa.shared.core.flows import Flow
|
|
9
|
+
from rasa.shared.core.flows.flow_path import FlowPathsList
|
|
7
10
|
from rasa.shared.core.flows.validation import (
|
|
8
11
|
validate_flow,
|
|
9
12
|
validate_link_in_call_restriction,
|
|
@@ -64,11 +67,16 @@ class FlowsList:
|
|
|
64
67
|
return FlowsList(list(merged_flows.values()))
|
|
65
68
|
|
|
66
69
|
@classmethod
|
|
67
|
-
def from_json(
|
|
68
|
-
|
|
70
|
+
def from_json(
|
|
71
|
+
cls,
|
|
72
|
+
data: Optional[Dict[Text, Dict[Text, Any]]],
|
|
73
|
+
file_path: Optional[Union[str, Path]] = None,
|
|
74
|
+
) -> FlowsList:
|
|
75
|
+
"""Create a FlowsList object from serialized data.
|
|
69
76
|
|
|
70
77
|
Args:
|
|
71
78
|
data: data for a FlowsList in a serialized format
|
|
79
|
+
file_path: the file path of the flows
|
|
72
80
|
|
|
73
81
|
Returns:
|
|
74
82
|
A FlowsList object.
|
|
@@ -78,7 +86,7 @@ class FlowsList:
|
|
|
78
86
|
|
|
79
87
|
return cls(
|
|
80
88
|
underlying_flows=[
|
|
81
|
-
Flow.from_json(flow_id, flow_config)
|
|
89
|
+
Flow.from_json(flow_id, flow_config, file_path)
|
|
82
90
|
for flow_id, flow_config in data.items()
|
|
83
91
|
]
|
|
84
92
|
)
|
|
@@ -139,7 +147,8 @@ class FlowsList:
|
|
|
139
147
|
"""Get all ids of flows that can be started by a user.
|
|
140
148
|
|
|
141
149
|
Returns:
|
|
142
|
-
|
|
150
|
+
The ids of all flows that can be started by a user.
|
|
151
|
+
"""
|
|
143
152
|
return {f.id for f in self.user_flows}
|
|
144
153
|
|
|
145
154
|
@property
|
|
@@ -147,7 +156,8 @@ class FlowsList:
|
|
|
147
156
|
"""Get all ids of flows.
|
|
148
157
|
|
|
149
158
|
Returns:
|
|
150
|
-
|
|
159
|
+
The ids of all flows.
|
|
160
|
+
"""
|
|
151
161
|
return {f.id for f in self.underlying_flows}
|
|
152
162
|
|
|
153
163
|
@property
|
|
@@ -155,7 +165,8 @@ class FlowsList:
|
|
|
155
165
|
"""Get all flows that can be started by a user.
|
|
156
166
|
|
|
157
167
|
Returns:
|
|
158
|
-
|
|
168
|
+
All flows that can be started by a user.
|
|
169
|
+
"""
|
|
159
170
|
return FlowsList(
|
|
160
171
|
[f for f in self.underlying_flows if not f.is_rasa_default_flow]
|
|
161
172
|
)
|
|
@@ -179,14 +190,14 @@ class FlowsList:
|
|
|
179
190
|
slots: The slots to evaluate the starting conditions against.
|
|
180
191
|
|
|
181
192
|
Returns:
|
|
182
|
-
|
|
193
|
+
All flows for which the starting conditions are met.
|
|
194
|
+
"""
|
|
183
195
|
return FlowsList(
|
|
184
196
|
[f for f in self.underlying_flows if f.is_startable(context, slots)]
|
|
185
197
|
)
|
|
186
198
|
|
|
187
199
|
def get_flows_always_included_in_prompt(self) -> FlowsList:
|
|
188
|
-
"""
|
|
189
|
-
Gets all flows based on their inclusion status in prompts.
|
|
200
|
+
"""Gets all flows based on their inclusion status in prompts.
|
|
190
201
|
|
|
191
202
|
Args:
|
|
192
203
|
always_included: Inclusion status.
|
|
@@ -221,3 +232,10 @@ class FlowsList:
|
|
|
221
232
|
def available_custom_actions(self) -> Set[str]:
|
|
222
233
|
"""Get all custom actions collected by flows."""
|
|
223
234
|
return set().union(*[flow.custom_actions for flow in self.underlying_flows])
|
|
235
|
+
|
|
236
|
+
def extract_flow_paths(self) -> Dict[str, FlowPathsList]:
|
|
237
|
+
paths = {}
|
|
238
|
+
for flow in self.user_flows.underlying_flows:
|
|
239
|
+
paths[flow.id] = flow.extract_all_paths()
|
|
240
|
+
|
|
241
|
+
return paths
|