rasa-pro 3.9.18__py3-none-any.whl → 3.10.16__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 +0 -374
- rasa/__init__.py +1 -2
- rasa/__main__.py +5 -0
- rasa/anonymization/anonymization_rule_executor.py +2 -2
- rasa/api.py +27 -23
- 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 +11 -3
- rasa/cli/data.py +70 -8
- rasa/cli/e2e_test.py +104 -431
- rasa/cli/evaluate.py +1 -1
- rasa/cli/interactive.py +1 -0
- rasa/cli/llm_fine_tuning.py +398 -0
- rasa/cli/project_templates/calm/endpoints.yml +1 -1
- rasa/cli/project_templates/tutorial/endpoints.yml +1 -1
- rasa/cli/run.py +15 -14
- rasa/cli/scaffold.py +10 -8
- rasa/cli/studio/studio.py +35 -5
- rasa/cli/train.py +56 -8
- rasa/cli/utils.py +22 -5
- rasa/cli/x.py +1 -1
- 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/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/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 +113 -45
- rasa/core/policies/flows/flow_executor.py +122 -76
- rasa/core/policies/intentless_policy.py +83 -29
- rasa/core/processor.py +72 -54
- rasa/core/run.py +5 -4
- rasa/core/tracker_store.py +8 -4
- rasa/core/training/interactive.py +1 -1
- rasa/core/utils.py +56 -57
- rasa/dialogue_understanding/coexistence/llm_based_router.py +53 -13
- rasa/dialogue_understanding/commands/__init__.py +6 -0
- rasa/dialogue_understanding/commands/restart_command.py +58 -0
- rasa/dialogue_understanding/commands/session_start_command.py +59 -0
- rasa/dialogue_understanding/commands/utils.py +40 -0
- rasa/dialogue_understanding/generator/constants.py +10 -3
- rasa/dialogue_understanding/generator/flow_retrieval.py +21 -5
- rasa/dialogue_understanding/generator/llm_based_command_generator.py +13 -3
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +134 -90
- rasa/dialogue_understanding/generator/nlu_command_adapter.py +47 -7
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +127 -41
- rasa/dialogue_understanding/patterns/restart.py +37 -0
- rasa/dialogue_understanding/patterns/session_start.py +37 -0
- rasa/dialogue_understanding/processor/command_processor.py +16 -3
- rasa/dialogue_understanding/processor/command_processor_component.py +6 -2
- rasa/e2e_test/aggregate_test_stats_calculator.py +134 -0
- rasa/e2e_test/assertions.py +1223 -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 +493 -71
- 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 +598 -0
- rasa/e2e_test/utils/validation.py +80 -0
- rasa/engine/graph.py +9 -3
- rasa/engine/recipes/default_components.py +0 -2
- rasa/engine/recipes/default_recipe.py +10 -2
- rasa/engine/storage/local_model_storage.py +40 -12
- rasa/engine/validation.py +78 -1
- rasa/env.py +9 -0
- rasa/graph_components/providers/story_graph_provider.py +59 -6
- 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 +56 -16
- rasa/nlu/persistor.py +157 -36
- rasa/server.py +45 -10
- rasa/shared/constants.py +76 -16
- rasa/shared/core/domain.py +27 -19
- 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 +33 -11
- 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/structures.py +20 -0
- rasa/shared/core/training_data/visualization.html +2 -2
- rasa/shared/exceptions.py +4 -0
- rasa/shared/importers/importer.py +64 -16
- rasa/shared/nlu/constants.py +2 -0
- rasa/shared/providers/_configs/__init__.py +0 -0
- rasa/shared/providers/_configs/azure_openai_client_config.py +183 -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 +176 -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 +259 -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 +251 -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 +293 -0
- rasa/shared/providers/mappings.py +75 -0
- rasa/shared/utils/cli.py +30 -0
- rasa/shared/utils/io.py +65 -2
- rasa/shared/utils/llm.py +246 -200
- rasa/shared/utils/yaml.py +121 -15
- rasa/studio/auth.py +6 -4
- rasa/studio/config.py +13 -4
- rasa/studio/constants.py +1 -0
- rasa/studio/data_handler.py +10 -3
- rasa/studio/download.py +19 -13
- rasa/studio/train.py +2 -3
- rasa/studio/upload.py +19 -11
- rasa/telemetry.py +113 -58
- rasa/tracing/instrumentation/attribute_extractors.py +32 -17
- rasa/utils/common.py +18 -19
- rasa/utils/endpoints.py +7 -4
- rasa/utils/json_utils.py +60 -0
- rasa/utils/licensing.py +9 -1
- rasa/utils/ml_utils.py +4 -2
- rasa/validator.py +213 -3
- rasa/version.py +1 -1
- rasa_pro-3.10.16.dist-info/METADATA +196 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/RECORD +179 -113
- 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_pro-3.9.18.dist-info/METADATA +0 -563
- /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.16.dist-info}/NOTICE +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/WHEEL +0 -0
- {rasa_pro-3.9.18.dist-info → rasa_pro-3.10.16.dist-info}/entry_points.txt +0 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections import defaultdict
|
|
4
3
|
import re
|
|
5
|
-
from typing import Optional, Set, Text, List
|
|
6
4
|
import typing
|
|
7
|
-
from
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from typing import Optional, Set, Text, List
|
|
8
7
|
|
|
8
|
+
from rasa.shared.constants import (
|
|
9
|
+
RASA_DEFAULT_FLOW_PATTERN_PREFIX,
|
|
10
|
+
RASA_PATTERN_HUMAN_HANDOFF,
|
|
11
|
+
)
|
|
12
|
+
from rasa.shared.constants import (
|
|
13
|
+
RASA_PATTERN_INTERNAL_ERROR,
|
|
14
|
+
)
|
|
15
|
+
from rasa.shared.core.flows.flow import Flow
|
|
9
16
|
from rasa.shared.core.flows.flow_step import (
|
|
10
17
|
FlowStep,
|
|
11
18
|
)
|
|
@@ -15,17 +22,15 @@ from rasa.shared.core.flows.flow_step_links import (
|
|
|
15
22
|
ElseFlowStepLink,
|
|
16
23
|
)
|
|
17
24
|
from rasa.shared.core.flows.flow_step_sequence import FlowStepSequence
|
|
18
|
-
from rasa.shared.core.flows.steps.constants import CONTINUE_STEP_PREFIX, DEFAULT_STEPS
|
|
19
25
|
from rasa.shared.core.flows.steps.call import CallFlowStep
|
|
20
|
-
from rasa.shared.core.flows.steps.link import LinkFlowStep
|
|
21
26
|
from rasa.shared.core.flows.steps.collect import CollectInformationFlowStep
|
|
22
|
-
from rasa.shared.core.flows.
|
|
27
|
+
from rasa.shared.core.flows.steps.constants import CONTINUE_STEP_PREFIX, DEFAULT_STEPS
|
|
28
|
+
from rasa.shared.core.flows.steps.link import LinkFlowStep
|
|
23
29
|
from rasa.shared.exceptions import RasaException
|
|
24
30
|
|
|
25
31
|
if typing.TYPE_CHECKING:
|
|
26
32
|
from rasa.shared.core.flows.flows_list import FlowsList
|
|
27
33
|
|
|
28
|
-
|
|
29
34
|
FLOW_ID_REGEX = r"""^[a-zA-Z0-9_][a-zA-Z0-9_-]*?$"""
|
|
30
35
|
|
|
31
36
|
|
|
@@ -132,36 +137,75 @@ class NoNextAllowedForLinkException(RasaException):
|
|
|
132
137
|
class ReferenceToPatternException(RasaException):
|
|
133
138
|
"""Raised when a flow step is referencing a pattern, which is not allowed."""
|
|
134
139
|
|
|
135
|
-
def __init__(
|
|
140
|
+
def __init__(
|
|
141
|
+
self, referenced_pattern: str, flow_id: str, step_id: str, call_step: bool
|
|
142
|
+
) -> None:
|
|
136
143
|
"""Initializes the exception."""
|
|
137
144
|
self.step_id = step_id
|
|
138
145
|
self.flow_id = flow_id
|
|
139
146
|
self.referenced_pattern = referenced_pattern
|
|
147
|
+
self.call_step = call_step
|
|
140
148
|
|
|
141
149
|
def __str__(self) -> str:
|
|
142
150
|
"""Return a string representation of the exception."""
|
|
143
|
-
|
|
144
|
-
f"Step '{self.step_id}' in flow '{self.flow_id}' is referencing a
|
|
145
|
-
f"'{self.referenced_pattern}', which is not allowed. "
|
|
146
|
-
|
|
151
|
+
message = (
|
|
152
|
+
f"Step '{self.step_id}' in flow '{self.flow_id}' is referencing a "
|
|
153
|
+
f"pattern '{self.referenced_pattern}', which is not allowed. "
|
|
154
|
+
)
|
|
155
|
+
if self.call_step:
|
|
156
|
+
return message + "Patterns can not be used as a target for a call step."
|
|
157
|
+
else:
|
|
158
|
+
return message + (
|
|
159
|
+
"All patterns, except for 'pattern_human_handoff', can "
|
|
160
|
+
"not be used as a target for a link step."
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class PatternReferencedPatternException(RasaException):
|
|
165
|
+
"""Raised when a pattern is referencing a pattern, which is not allowed."""
|
|
166
|
+
|
|
167
|
+
def __init__(self, flow_id: str, step_id: str, call_step: bool) -> None:
|
|
168
|
+
"""Initializes the exception."""
|
|
169
|
+
self.step_id = step_id
|
|
170
|
+
self.flow_id = flow_id
|
|
171
|
+
self.call_step = call_step
|
|
172
|
+
|
|
173
|
+
def __str__(self) -> str:
|
|
174
|
+
"""Return a string representation of the exception."""
|
|
175
|
+
message = (
|
|
176
|
+
f"Step '{self.step_id}' in pattern '{self.flow_id}' is referencing a "
|
|
177
|
+
f"pattern which is not allowed. "
|
|
147
178
|
)
|
|
179
|
+
if self.call_step:
|
|
180
|
+
return message + "Patterns can not use call steps to other patterns."
|
|
181
|
+
else:
|
|
182
|
+
return message + (
|
|
183
|
+
"Patterns can not use link steps to other patterns. "
|
|
184
|
+
"Exception: patterns can link to 'pattern_human_handoff'."
|
|
185
|
+
)
|
|
148
186
|
|
|
149
187
|
|
|
150
188
|
class PatternReferencedFlowException(RasaException):
|
|
151
189
|
"""Raised when a pattern is referencing a flow, which is not allowed."""
|
|
152
190
|
|
|
153
|
-
def __init__(self, flow_id: str, step_id: str) -> None:
|
|
191
|
+
def __init__(self, flow_id: str, step_id: str, call_step: bool) -> None:
|
|
154
192
|
"""Initializes the exception."""
|
|
155
193
|
self.step_id = step_id
|
|
156
194
|
self.flow_id = flow_id
|
|
195
|
+
self.call_step = call_step
|
|
157
196
|
|
|
158
197
|
def __str__(self) -> str:
|
|
159
198
|
"""Return a string representation of the exception."""
|
|
160
|
-
|
|
161
|
-
f"Step '{self.step_id}' in
|
|
199
|
+
message = (
|
|
200
|
+
f"Step '{self.step_id}' in pattern '{self.flow_id}' is referencing a flow "
|
|
162
201
|
f"which is not allowed. "
|
|
163
|
-
f"Patterns can not use link or call steps."
|
|
164
202
|
)
|
|
203
|
+
if self.call_step:
|
|
204
|
+
return message + "Patterns can not use call steps."
|
|
205
|
+
else:
|
|
206
|
+
return message + (
|
|
207
|
+
"'pattern_internal_error' can not use link steps to user flows."
|
|
208
|
+
)
|
|
165
209
|
|
|
166
210
|
|
|
167
211
|
class NoLinkAllowedInCalledFlowException(RasaException):
|
|
@@ -459,35 +503,76 @@ def validate_linked_flows_exists(flows: "FlowsList") -> None:
|
|
|
459
503
|
if not isinstance(step, LinkFlowStep):
|
|
460
504
|
continue
|
|
461
505
|
|
|
462
|
-
|
|
506
|
+
# It might be that the flows do not contain the default rasa patterns, but
|
|
507
|
+
# only the user flows. Manually check for `pattern_human_handoff` as this
|
|
508
|
+
# pattern can be linked to and it is part of the default patterns of rasa.
|
|
509
|
+
if (
|
|
510
|
+
flows.flow_by_id(step.link) is None
|
|
511
|
+
and step.link != RASA_PATTERN_HUMAN_HANDOFF
|
|
512
|
+
):
|
|
463
513
|
raise UnresolvedFlowException(step.link, flow.id, step.id)
|
|
464
514
|
|
|
465
515
|
|
|
466
516
|
def validate_patterns_are_not_called_or_linked(flows: "FlowsList") -> None:
|
|
467
|
-
"""Validates that patterns are never called or linked.
|
|
517
|
+
"""Validates that patterns are never called or linked.
|
|
518
|
+
|
|
519
|
+
Exception: pattern_human_handoff can be linked.
|
|
520
|
+
"""
|
|
468
521
|
for flow in flows.underlying_flows:
|
|
469
522
|
for step in flow.steps:
|
|
470
|
-
if
|
|
471
|
-
|
|
523
|
+
if (
|
|
524
|
+
isinstance(step, LinkFlowStep)
|
|
525
|
+
and step.link.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX)
|
|
526
|
+
and step.link != RASA_PATTERN_HUMAN_HANDOFF
|
|
472
527
|
):
|
|
473
|
-
raise ReferenceToPatternException(
|
|
528
|
+
raise ReferenceToPatternException(
|
|
529
|
+
step.link, flow.id, step.id, call_step=False
|
|
530
|
+
)
|
|
474
531
|
|
|
475
532
|
if isinstance(step, CallFlowStep) and step.call.startswith(
|
|
476
533
|
RASA_DEFAULT_FLOW_PATTERN_PREFIX
|
|
477
534
|
):
|
|
478
|
-
raise ReferenceToPatternException(
|
|
535
|
+
raise ReferenceToPatternException(
|
|
536
|
+
step.call, flow.id, step.id, call_step=True
|
|
537
|
+
)
|
|
479
538
|
|
|
480
539
|
|
|
481
540
|
def validate_patterns_are_not_calling_or_linking_other_flows(
|
|
482
541
|
flows: "FlowsList",
|
|
483
542
|
) -> None:
|
|
484
|
-
"""Validates that patterns do not contain call or link steps.
|
|
543
|
+
"""Validates that patterns do not contain call or link steps.
|
|
544
|
+
|
|
545
|
+
Link steps to user flows are allowed for all patterns but 'pattern_internal_error'.
|
|
546
|
+
Link steps to other patterns, except for 'pattern_human_handoff', are forbidden.
|
|
547
|
+
"""
|
|
485
548
|
for flow in flows.underlying_flows:
|
|
486
549
|
if not flow.is_rasa_default_flow:
|
|
487
550
|
continue
|
|
488
551
|
for step in flow.steps:
|
|
489
|
-
if isinstance(step,
|
|
490
|
-
|
|
552
|
+
if isinstance(step, LinkFlowStep):
|
|
553
|
+
if step.link == RASA_PATTERN_HUMAN_HANDOFF:
|
|
554
|
+
# links to 'pattern_human_handoff' are allowed
|
|
555
|
+
continue
|
|
556
|
+
if step.link.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX):
|
|
557
|
+
# all other patterns are allowed to link to user flows, but not
|
|
558
|
+
# to other patterns
|
|
559
|
+
raise PatternReferencedPatternException(
|
|
560
|
+
flow.id, step.id, call_step=False
|
|
561
|
+
)
|
|
562
|
+
if flow.id == RASA_PATTERN_INTERNAL_ERROR:
|
|
563
|
+
# 'pattern_internal_error' is not allowed to link at all
|
|
564
|
+
raise PatternReferencedFlowException(
|
|
565
|
+
flow.id, step.id, call_step=False
|
|
566
|
+
)
|
|
567
|
+
if isinstance(step, CallFlowStep):
|
|
568
|
+
if step.call.startswith(RASA_DEFAULT_FLOW_PATTERN_PREFIX):
|
|
569
|
+
raise PatternReferencedPatternException(
|
|
570
|
+
flow.id, step.id, call_step=True
|
|
571
|
+
)
|
|
572
|
+
else:
|
|
573
|
+
raise PatternReferencedFlowException(
|
|
574
|
+
flow.id, step.id, call_step=True
|
|
575
|
+
)
|
|
491
576
|
|
|
492
577
|
|
|
493
578
|
def validate_step_ids_are_unique(flows: "FlowsList") -> None:
|
|
@@ -517,8 +602,10 @@ def validate_flow_id(flow: Flow) -> None:
|
|
|
517
602
|
"""Validates if the flow id comply with a specified regex.
|
|
518
603
|
Flow IDs can start with an alphanumeric character or an underscore.
|
|
519
604
|
Followed by zero or more alphanumeric characters, hyphens, or underscores.
|
|
605
|
+
|
|
520
606
|
Args:
|
|
521
607
|
flow: The flow to validate.
|
|
608
|
+
|
|
522
609
|
Raises:
|
|
523
610
|
FlowIdNamingException: If the flow id does not comply with the regex.
|
|
524
611
|
"""
|
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import textwrap
|
|
2
2
|
from pathlib import Path
|
|
3
|
-
from typing import Any, Dict, List, Text, Union
|
|
3
|
+
from typing import Any, Dict, List, Text, Union, Optional
|
|
4
4
|
|
|
5
5
|
import jsonschema
|
|
6
|
+
import ruamel.yaml.nodes as yaml_nodes
|
|
7
|
+
from ruamel import yaml as yaml
|
|
6
8
|
|
|
7
9
|
import rasa.shared
|
|
8
10
|
import rasa.shared.data
|
|
9
|
-
from rasa.shared.importers.importer import FlowSyncImporter
|
|
10
11
|
import rasa.shared.utils.io
|
|
11
|
-
from rasa.shared.exceptions import RasaException, YamlException
|
|
12
|
-
|
|
13
12
|
from rasa.shared.core.flows.flow import Flow
|
|
14
13
|
from rasa.shared.core.flows.flows_list import FlowsList
|
|
14
|
+
from rasa.shared.exceptions import RasaException, YamlException
|
|
15
|
+
from rasa.shared.importers.importer import FlowSyncImporter
|
|
15
16
|
from rasa.shared.utils.yaml import (
|
|
16
17
|
validate_yaml_with_jsonschema,
|
|
17
18
|
read_yaml,
|
|
@@ -27,11 +28,14 @@ class YAMLFlowsReader:
|
|
|
27
28
|
"""Class that reads flows information in YAML format."""
|
|
28
29
|
|
|
29
30
|
@classmethod
|
|
30
|
-
def read_from_file(
|
|
31
|
+
def read_from_file(
|
|
32
|
+
cls, filename: Union[Text, Path], add_line_numbers: bool = True
|
|
33
|
+
) -> FlowsList:
|
|
31
34
|
"""Read flows from file.
|
|
32
35
|
|
|
33
36
|
Args:
|
|
34
37
|
filename: Path to the flows file.
|
|
38
|
+
add_line_numbers: Flag whether to add line numbers to yaml
|
|
35
39
|
|
|
36
40
|
Returns:
|
|
37
41
|
`Flow`s read from `filename`.
|
|
@@ -41,6 +45,8 @@ class YAMLFlowsReader:
|
|
|
41
45
|
rasa.shared.utils.io.read_file(
|
|
42
46
|
filename, rasa.shared.utils.io.DEFAULT_ENCODING
|
|
43
47
|
),
|
|
48
|
+
add_line_numbers=add_line_numbers,
|
|
49
|
+
file_path=filename,
|
|
44
50
|
)
|
|
45
51
|
except YamlException as e:
|
|
46
52
|
e.filename = str(filename)
|
|
@@ -195,11 +201,19 @@ class YAMLFlowsReader:
|
|
|
195
201
|
)
|
|
196
202
|
|
|
197
203
|
@classmethod
|
|
198
|
-
def read_from_string(
|
|
204
|
+
def read_from_string(
|
|
205
|
+
cls,
|
|
206
|
+
string: Text,
|
|
207
|
+
add_line_numbers: bool = True,
|
|
208
|
+
file_path: Optional[Union[str, Path]] = None,
|
|
209
|
+
) -> FlowsList:
|
|
199
210
|
"""Read flows from a string.
|
|
200
211
|
|
|
201
212
|
Args:
|
|
202
213
|
string: Unprocessed YAML file content.
|
|
214
|
+
add_line_numbers: If true, a custom constructor is added to add line
|
|
215
|
+
numbers to each node.
|
|
216
|
+
file_path: File path of the flow.
|
|
203
217
|
|
|
204
218
|
Returns:
|
|
205
219
|
`Flow`s read from `string`.
|
|
@@ -207,10 +221,14 @@ class YAMLFlowsReader:
|
|
|
207
221
|
validate_yaml_with_jsonschema(
|
|
208
222
|
string, FLOWS_SCHEMA_FILE, humanize_error=cls.humanize_flow_error
|
|
209
223
|
)
|
|
224
|
+
if add_line_numbers:
|
|
225
|
+
yaml_content = read_yaml(string, custom_constructor=line_number_constructor)
|
|
226
|
+
yaml_content = process_yaml_content(yaml_content)
|
|
210
227
|
|
|
211
|
-
|
|
228
|
+
else:
|
|
229
|
+
yaml_content = read_yaml(string)
|
|
212
230
|
|
|
213
|
-
return FlowsList.from_json(yaml_content.get(KEY_FLOWS, {}))
|
|
231
|
+
return FlowsList.from_json(yaml_content.get(KEY_FLOWS, {}), file_path=file_path)
|
|
214
232
|
|
|
215
233
|
|
|
216
234
|
class YamlFlowsWriter:
|
|
@@ -246,14 +264,18 @@ class YamlFlowsWriter:
|
|
|
246
264
|
|
|
247
265
|
def flows_from_str(yaml_str: str) -> FlowsList:
|
|
248
266
|
"""Reads flows from a YAML string."""
|
|
249
|
-
flows = YAMLFlowsReader.read_from_string(
|
|
267
|
+
flows = YAMLFlowsReader.read_from_string(
|
|
268
|
+
textwrap.dedent(yaml_str), add_line_numbers=False
|
|
269
|
+
)
|
|
250
270
|
flows.validate()
|
|
251
271
|
return flows
|
|
252
272
|
|
|
253
273
|
|
|
254
274
|
def flows_from_str_including_defaults(yaml_str: str) -> FlowsList:
|
|
255
275
|
"""Reads flows from a YAML string and combine them with default flows."""
|
|
256
|
-
flows = YAMLFlowsReader.read_from_string(
|
|
276
|
+
flows = YAMLFlowsReader.read_from_string(
|
|
277
|
+
textwrap.dedent(yaml_str), add_line_numbers=False
|
|
278
|
+
)
|
|
257
279
|
all_flows = FlowSyncImporter.merge_with_default_flows(flows)
|
|
258
280
|
all_flows.validate()
|
|
259
281
|
return all_flows
|
|
@@ -276,3 +298,120 @@ def is_flows_file(file_path: Union[Text, Path]) -> bool:
|
|
|
276
298
|
return rasa.shared.data.is_likely_yaml_file(file_path) and is_key_in_yaml(
|
|
277
299
|
file_path, KEY_FLOWS
|
|
278
300
|
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def line_number_constructor(loader: yaml.Loader, node: yaml_nodes.Node) -> Any:
|
|
304
|
+
"""A custom YAML constructor adding line numbers to nodes.
|
|
305
|
+
|
|
306
|
+
Args:
|
|
307
|
+
loader (yaml.Loader): The YAML loader.
|
|
308
|
+
node (yaml.nodes.Node): The YAML node.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
Any: The constructed Python object with added line numbers in metadata.
|
|
312
|
+
"""
|
|
313
|
+
if isinstance(node, yaml_nodes.MappingNode):
|
|
314
|
+
mapping = loader.construct_mapping(node, deep=True)
|
|
315
|
+
if "metadata" not in mapping:
|
|
316
|
+
# We add the line information to the metadata of a flow step
|
|
317
|
+
# Lines are 0-based index; adding 1 to start from
|
|
318
|
+
# line 1 for human readability
|
|
319
|
+
mapping["metadata"] = {
|
|
320
|
+
"line_numbers": f"{node.start_mark.line + 1}-{node.end_mark.line}"
|
|
321
|
+
}
|
|
322
|
+
return mapping
|
|
323
|
+
elif isinstance(node, yaml_nodes.SequenceNode):
|
|
324
|
+
sequence = loader.construct_sequence(node, deep=True)
|
|
325
|
+
for item in node.value:
|
|
326
|
+
if isinstance(item, yaml_nodes.MappingNode):
|
|
327
|
+
start_line = item.start_mark.line + 1
|
|
328
|
+
end_line = item.end_mark.line
|
|
329
|
+
# Only add line numbers to dictionary items within the sequence
|
|
330
|
+
index = node.value.index(item)
|
|
331
|
+
if isinstance(sequence[index], dict):
|
|
332
|
+
if "metadata" not in sequence[index]:
|
|
333
|
+
sequence[index]["metadata"] = {}
|
|
334
|
+
if "line_numbers" not in sequence[index]["metadata"]:
|
|
335
|
+
sequence[index]["metadata"]["line_numbers"] = (
|
|
336
|
+
f"{start_line}-{end_line}"
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
return sequence
|
|
340
|
+
return loader.construct_object(node, deep=True)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def _remove_keys_recursively(
|
|
344
|
+
data: Union[Dict, List], keys_to_delete: List[str]
|
|
345
|
+
) -> None:
|
|
346
|
+
"""Recursively removes all specified keys in the given data.
|
|
347
|
+
|
|
348
|
+
Special handling for 'metadata'.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
data: The data structure (dictionary or list) to clean.
|
|
352
|
+
keys_to_delete: A list of keys to remove from the dictionaries.
|
|
353
|
+
"""
|
|
354
|
+
if isinstance(data, dict):
|
|
355
|
+
keys = list(data.keys())
|
|
356
|
+
for key in keys:
|
|
357
|
+
if key in keys_to_delete:
|
|
358
|
+
# Special case for 'metadata': only delete if it
|
|
359
|
+
# only contains 'line_numbers'
|
|
360
|
+
if key == "metadata" and isinstance(data[key], dict):
|
|
361
|
+
if len(data[key]) == 1 and "line_numbers" in data[key]:
|
|
362
|
+
del data[key]
|
|
363
|
+
else:
|
|
364
|
+
del data[key]
|
|
365
|
+
else:
|
|
366
|
+
_remove_keys_recursively(data[key], keys_to_delete)
|
|
367
|
+
elif isinstance(data, list):
|
|
368
|
+
for item in data:
|
|
369
|
+
_remove_keys_recursively(item, keys_to_delete)
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _process_keys_recursively(
|
|
373
|
+
data: Union[Dict, List], keys_to_check: List[str]
|
|
374
|
+
) -> None:
|
|
375
|
+
"""Recursively iterates over YAML content and applies remove_keys_recursively."""
|
|
376
|
+
if isinstance(data, dict):
|
|
377
|
+
keys = list(
|
|
378
|
+
data.keys()
|
|
379
|
+
) # Make a list of keys to avoid changing the dictionary size during iteration
|
|
380
|
+
for key in keys:
|
|
381
|
+
if key in keys_to_check:
|
|
382
|
+
_remove_keys_recursively(data[key], ["metadata"])
|
|
383
|
+
else:
|
|
384
|
+
_process_keys_recursively(data[key], keys_to_check)
|
|
385
|
+
elif isinstance(data, list):
|
|
386
|
+
for item in data:
|
|
387
|
+
_process_keys_recursively(item, keys_to_check)
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def process_yaml_content(yaml_content: Dict[str, Any]) -> Dict[str, Any]:
|
|
391
|
+
"""Processes parsed YAML content to remove "metadata"."""
|
|
392
|
+
# Remove metadata on the top level
|
|
393
|
+
if "metadata" in yaml_content and (
|
|
394
|
+
len(yaml_content["metadata"]) == 1
|
|
395
|
+
and "line_numbers" in yaml_content["metadata"]
|
|
396
|
+
):
|
|
397
|
+
del yaml_content["metadata"]
|
|
398
|
+
|
|
399
|
+
# We expect metadata only under "flows" key...
|
|
400
|
+
keys_to_delete_metadata = [key for key in yaml_content if key != "flows"]
|
|
401
|
+
for key in keys_to_delete_metadata:
|
|
402
|
+
_remove_keys_recursively(yaml_content[key], ["metadata"])
|
|
403
|
+
|
|
404
|
+
# ...but "metadata" is also not a key of "flows"
|
|
405
|
+
if "flows" in yaml_content and "metadata" in yaml_content["flows"]:
|
|
406
|
+
if (
|
|
407
|
+
len(yaml_content["flows"]["metadata"]) == 1
|
|
408
|
+
and "line_numbers" in yaml_content["flows"]["metadata"]
|
|
409
|
+
):
|
|
410
|
+
del yaml_content["flows"]["metadata"]
|
|
411
|
+
|
|
412
|
+
# Under the "flows" key certain keys cannot have metadata
|
|
413
|
+
_process_keys_recursively(
|
|
414
|
+
yaml_content["flows"], ["nlu_trigger", "set_slots", "metadata"]
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
return yaml_content
|
rasa/shared/core/trackers.py
CHANGED
|
@@ -449,6 +449,12 @@ class DialogueStateTracker:
|
|
|
449
449
|
current_context = self.stack.current_context()
|
|
450
450
|
return current_context.get("flow_id")
|
|
451
451
|
|
|
452
|
+
@property
|
|
453
|
+
def current_step_id(self) -> Optional[str]:
|
|
454
|
+
"""Returns the id of the current step id."""
|
|
455
|
+
current_context = self.stack.current_context()
|
|
456
|
+
return current_context.get("step_id")
|
|
457
|
+
|
|
452
458
|
@property
|
|
453
459
|
def has_coexistence_routing_slot(self) -> bool:
|
|
454
460
|
"""Returns whether the coexistence routing slot is present."""
|
|
@@ -804,6 +804,26 @@ class StoryGraph:
|
|
|
804
804
|
"""Returns text representation of object."""
|
|
805
805
|
return f"{self.__class__.__name__}: {len(self.story_steps)} story steps"
|
|
806
806
|
|
|
807
|
+
def has_e2e_stories(self) -> bool:
|
|
808
|
+
"""
|
|
809
|
+
Checks if there are end-to-end (E2E) stories present in the story steps.
|
|
810
|
+
|
|
811
|
+
An E2E story is determined by checking if any `UserUttered` event has
|
|
812
|
+
associated text within the story steps.
|
|
813
|
+
|
|
814
|
+
Returns:
|
|
815
|
+
bool: True if any E2E story (i.e., a `UserUttered` event with text)
|
|
816
|
+
is found, False otherwise.
|
|
817
|
+
"""
|
|
818
|
+
if not self.story_steps:
|
|
819
|
+
return False
|
|
820
|
+
for story_step in self.story_steps:
|
|
821
|
+
for event in story_step.events:
|
|
822
|
+
if isinstance(event, UserUttered):
|
|
823
|
+
if event.text:
|
|
824
|
+
return True
|
|
825
|
+
return False
|
|
826
|
+
|
|
807
827
|
|
|
808
828
|
def generate_id(prefix: Text = "", max_chars: Optional[int] = None) -> Text:
|
|
809
829
|
"""Generate a random UUID.
|
|
@@ -87,13 +87,13 @@
|
|
|
87
87
|
// trigger a refresh by fetching an updated graph
|
|
88
88
|
setInterval(function () {
|
|
89
89
|
fetch(url).then(r => r.text()).then(dot => {
|
|
90
|
-
document.getElementById('errormsg').
|
|
90
|
+
document.getElementById('errormsg').innerText = '';
|
|
91
91
|
if (oldInputGraphValue === dot) return;
|
|
92
92
|
|
|
93
93
|
oldInputGraphValue = dot;
|
|
94
94
|
drawGraph(dot);
|
|
95
95
|
}).catch(err => {
|
|
96
|
-
document.getElementById('errormsg').
|
|
96
|
+
document.getElementById('errormsg').innerText=
|
|
97
97
|
'Failed to update plot. (' + err.message + ')';
|
|
98
98
|
});
|
|
99
99
|
}, refreshInterval);
|
rasa/shared/exceptions.py
CHANGED