rasa-pro 3.10.10__py3-none-any.whl → 3.10.12__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 +17 -396
- rasa/cli/arguments/train.py +9 -3
- rasa/cli/train.py +40 -2
- rasa/cli/utils.py +7 -5
- rasa/constants.py +1 -1
- rasa/core/featurizers/single_state_featurizer.py +22 -1
- rasa/core/featurizers/tracker_featurizers.py +115 -18
- rasa/core/policies/ted_policy.py +58 -33
- rasa/core/policies/unexpected_intent_policy.py +15 -7
- rasa/dialogue_understanding/commands/change_flow_command.py +6 -0
- rasa/dialogue_understanding/generator/multi_step/multi_step_llm_command_generator.py +20 -3
- rasa/dialogue_understanding/generator/single_step/single_step_llm_command_generator.py +29 -4
- rasa/e2e_test/e2e_test_runner.py +2 -2
- rasa/engine/storage/local_model_storage.py +41 -12
- rasa/model_training.py +10 -3
- rasa/nlu/classifiers/diet_classifier.py +38 -25
- rasa/nlu/classifiers/logistic_regression_classifier.py +22 -9
- rasa/nlu/classifiers/sklearn_intent_classifier.py +37 -16
- rasa/nlu/extractors/crf_entity_extractor.py +93 -50
- rasa/nlu/featurizers/sparse_featurizer/count_vectors_featurizer.py +45 -16
- rasa/nlu/featurizers/sparse_featurizer/lexical_syntactic_featurizer.py +52 -17
- rasa/nlu/featurizers/sparse_featurizer/regex_featurizer.py +5 -3
- rasa/nlu/persistor.py +37 -15
- rasa/shared/constants.py +4 -1
- rasa/shared/importers/importer.py +7 -8
- rasa/shared/nlu/training_data/features.py +120 -2
- rasa/shared/utils/io.py +1 -0
- rasa/utils/io.py +0 -66
- rasa/utils/tensorflow/feature_array.py +366 -0
- rasa/utils/tensorflow/model_data.py +2 -193
- rasa/version.py +1 -1
- rasa_pro-3.10.12.dist-info/METADATA +196 -0
- {rasa_pro-3.10.10.dist-info → rasa_pro-3.10.12.dist-info}/RECORD +36 -36
- rasa/shared/importers/remote_importer.py +0 -196
- rasa_pro-3.10.10.dist-info/METADATA +0 -575
- {rasa_pro-3.10.10.dist-info → rasa_pro-3.10.12.dist-info}/NOTICE +0 -0
- {rasa_pro-3.10.10.dist-info → rasa_pro-3.10.12.dist-info}/WHEEL +0 -0
- {rasa_pro-3.10.10.dist-info → rasa_pro-3.10.12.dist-info}/entry_points.txt +0 -0
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
from collections import defaultdict
|
|
4
|
-
from abc import abstractmethod
|
|
5
|
-
import jsonpickle
|
|
6
|
-
import logging
|
|
7
2
|
|
|
8
|
-
|
|
3
|
+
import logging
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from pathlib import Path
|
|
9
7
|
from typing import (
|
|
10
8
|
Tuple,
|
|
11
9
|
List,
|
|
@@ -18,25 +16,30 @@ from typing import (
|
|
|
18
16
|
Set,
|
|
19
17
|
DefaultDict,
|
|
20
18
|
cast,
|
|
19
|
+
Type,
|
|
20
|
+
Callable,
|
|
21
|
+
ClassVar,
|
|
21
22
|
)
|
|
23
|
+
|
|
22
24
|
import numpy as np
|
|
25
|
+
from tqdm import tqdm
|
|
23
26
|
|
|
24
|
-
from rasa.core.featurizers.single_state_featurizer import SingleStateFeaturizer
|
|
25
|
-
from rasa.core.featurizers.precomputation import MessageContainerForCoreFeaturization
|
|
26
|
-
from rasa.core.exceptions import InvalidTrackerFeaturizerUsageError
|
|
27
27
|
import rasa.shared.core.trackers
|
|
28
28
|
import rasa.shared.utils.io
|
|
29
|
-
from rasa.
|
|
30
|
-
from rasa.
|
|
31
|
-
from rasa.
|
|
32
|
-
from rasa.shared.core.domain import State, Domain
|
|
33
|
-
from rasa.shared.core.events import Event, ActionExecuted, UserUttered
|
|
29
|
+
from rasa.core.exceptions import InvalidTrackerFeaturizerUsageError
|
|
30
|
+
from rasa.core.featurizers.precomputation import MessageContainerForCoreFeaturization
|
|
31
|
+
from rasa.core.featurizers.single_state_featurizer import SingleStateFeaturizer
|
|
34
32
|
from rasa.shared.core.constants import (
|
|
35
33
|
USER,
|
|
36
34
|
ACTION_UNLIKELY_INTENT_NAME,
|
|
37
35
|
PREVIOUS_ACTION,
|
|
38
36
|
)
|
|
37
|
+
from rasa.shared.core.domain import State, Domain
|
|
38
|
+
from rasa.shared.core.events import Event, ActionExecuted, UserUttered
|
|
39
|
+
from rasa.shared.core.trackers import DialogueStateTracker
|
|
39
40
|
from rasa.shared.exceptions import RasaException
|
|
41
|
+
from rasa.shared.nlu.constants import TEXT, INTENT, ENTITIES, ACTION_NAME
|
|
42
|
+
from rasa.shared.nlu.training_data.features import Features
|
|
40
43
|
from rasa.utils.tensorflow.constants import LABEL_PAD_ID
|
|
41
44
|
from rasa.utils.tensorflow.model_data import ragged_array_to_ndarray
|
|
42
45
|
|
|
@@ -64,6 +67,10 @@ class InvalidStory(RasaException):
|
|
|
64
67
|
class TrackerFeaturizer:
|
|
65
68
|
"""Base class for actual tracker featurizers."""
|
|
66
69
|
|
|
70
|
+
# Class registry to store all subclasses
|
|
71
|
+
_registry: ClassVar[Dict[str, Type["TrackerFeaturizer"]]] = {}
|
|
72
|
+
_featurizer_type: str = "TrackerFeaturizer"
|
|
73
|
+
|
|
67
74
|
def __init__(
|
|
68
75
|
self, state_featurizer: Optional[SingleStateFeaturizer] = None
|
|
69
76
|
) -> None:
|
|
@@ -74,6 +81,36 @@ class TrackerFeaturizer:
|
|
|
74
81
|
"""
|
|
75
82
|
self.state_featurizer = state_featurizer
|
|
76
83
|
|
|
84
|
+
@classmethod
|
|
85
|
+
def register(cls, featurizer_type: str) -> Callable:
|
|
86
|
+
"""Decorator to register featurizer subclasses."""
|
|
87
|
+
|
|
88
|
+
def wrapper(subclass: Type["TrackerFeaturizer"]) -> Type["TrackerFeaturizer"]:
|
|
89
|
+
cls._registry[featurizer_type] = subclass
|
|
90
|
+
# Store the type identifier in the class for serialization
|
|
91
|
+
subclass._featurizer_type = featurizer_type
|
|
92
|
+
return subclass
|
|
93
|
+
|
|
94
|
+
return wrapper
|
|
95
|
+
|
|
96
|
+
@classmethod
|
|
97
|
+
def from_dict(cls, data: Dict[str, Any]) -> "TrackerFeaturizer":
|
|
98
|
+
"""Create featurizer instance from dictionary."""
|
|
99
|
+
featurizer_type = data.pop("type")
|
|
100
|
+
|
|
101
|
+
if featurizer_type not in cls._registry:
|
|
102
|
+
raise ValueError(f"Unknown featurizer type: {featurizer_type}")
|
|
103
|
+
|
|
104
|
+
# Get the correct subclass and instantiate it
|
|
105
|
+
subclass = cls._registry[featurizer_type]
|
|
106
|
+
return subclass.create_from_dict(data)
|
|
107
|
+
|
|
108
|
+
@classmethod
|
|
109
|
+
@abstractmethod
|
|
110
|
+
def create_from_dict(cls, data: Dict[str, Any]) -> "TrackerFeaturizer":
|
|
111
|
+
"""Each subclass must implement its own creation from dict method."""
|
|
112
|
+
pass
|
|
113
|
+
|
|
77
114
|
@staticmethod
|
|
78
115
|
def _create_states(
|
|
79
116
|
tracker: DialogueStateTracker,
|
|
@@ -465,9 +502,7 @@ class TrackerFeaturizer:
|
|
|
465
502
|
self.state_featurizer.entity_tag_specs = []
|
|
466
503
|
|
|
467
504
|
# noinspection PyTypeChecker
|
|
468
|
-
rasa.shared.utils.io.
|
|
469
|
-
str(jsonpickle.encode(self)), featurizer_file
|
|
470
|
-
)
|
|
505
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(featurizer_file, self.to_dict())
|
|
471
506
|
|
|
472
507
|
@staticmethod
|
|
473
508
|
def load(path: Union[Text, Path]) -> Optional[TrackerFeaturizer]:
|
|
@@ -481,7 +516,17 @@ class TrackerFeaturizer:
|
|
|
481
516
|
"""
|
|
482
517
|
featurizer_file = Path(path) / FEATURIZER_FILE
|
|
483
518
|
if featurizer_file.is_file():
|
|
484
|
-
|
|
519
|
+
data = rasa.shared.utils.io.read_json_file(featurizer_file)
|
|
520
|
+
|
|
521
|
+
if "type" not in data:
|
|
522
|
+
logger.error(
|
|
523
|
+
f"Couldn't load featurizer for policy. "
|
|
524
|
+
f"File '{featurizer_file}' does not contain all "
|
|
525
|
+
f"necessary information. 'type' is missing."
|
|
526
|
+
)
|
|
527
|
+
return None
|
|
528
|
+
|
|
529
|
+
return TrackerFeaturizer.from_dict(data)
|
|
485
530
|
|
|
486
531
|
logger.error(
|
|
487
532
|
f"Couldn't load featurizer for policy. "
|
|
@@ -508,7 +553,16 @@ class TrackerFeaturizer:
|
|
|
508
553
|
)
|
|
509
554
|
]
|
|
510
555
|
|
|
556
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
557
|
+
return {
|
|
558
|
+
"type": self.__class__._featurizer_type,
|
|
559
|
+
"state_featurizer": (
|
|
560
|
+
self.state_featurizer.to_dict() if self.state_featurizer else None
|
|
561
|
+
),
|
|
562
|
+
}
|
|
563
|
+
|
|
511
564
|
|
|
565
|
+
@TrackerFeaturizer.register("FullDialogueTrackerFeaturizer")
|
|
512
566
|
class FullDialogueTrackerFeaturizer(TrackerFeaturizer):
|
|
513
567
|
"""Creates full dialogue training data for time distributed architectures.
|
|
514
568
|
|
|
@@ -646,7 +700,20 @@ class FullDialogueTrackerFeaturizer(TrackerFeaturizer):
|
|
|
646
700
|
|
|
647
701
|
return trackers_as_states
|
|
648
702
|
|
|
703
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
704
|
+
return super().to_dict()
|
|
649
705
|
|
|
706
|
+
@classmethod
|
|
707
|
+
def create_from_dict(cls, data: Dict[str, Any]) -> "FullDialogueTrackerFeaturizer":
|
|
708
|
+
state_featurizer = SingleStateFeaturizer.create_from_dict(
|
|
709
|
+
data["state_featurizer"]
|
|
710
|
+
)
|
|
711
|
+
return cls(
|
|
712
|
+
state_featurizer,
|
|
713
|
+
)
|
|
714
|
+
|
|
715
|
+
|
|
716
|
+
@TrackerFeaturizer.register("MaxHistoryTrackerFeaturizer")
|
|
650
717
|
class MaxHistoryTrackerFeaturizer(TrackerFeaturizer):
|
|
651
718
|
"""Truncates the tracker history into `max_history` long sequences.
|
|
652
719
|
|
|
@@ -884,7 +951,25 @@ class MaxHistoryTrackerFeaturizer(TrackerFeaturizer):
|
|
|
884
951
|
|
|
885
952
|
return trackers_as_states
|
|
886
953
|
|
|
954
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
955
|
+
data = super().to_dict()
|
|
956
|
+
data.update(
|
|
957
|
+
{
|
|
958
|
+
"remove_duplicates": self.remove_duplicates,
|
|
959
|
+
"max_history": self.max_history,
|
|
960
|
+
}
|
|
961
|
+
)
|
|
962
|
+
return data
|
|
963
|
+
|
|
964
|
+
@classmethod
|
|
965
|
+
def create_from_dict(cls, data: Dict[str, Any]) -> "MaxHistoryTrackerFeaturizer":
|
|
966
|
+
state_featurizer = SingleStateFeaturizer.create_from_dict(
|
|
967
|
+
data["state_featurizer"]
|
|
968
|
+
)
|
|
969
|
+
return cls(state_featurizer, data["max_history"], data["remove_duplicates"])
|
|
887
970
|
|
|
971
|
+
|
|
972
|
+
@TrackerFeaturizer.register("IntentMaxHistoryTrackerFeaturizer")
|
|
888
973
|
class IntentMaxHistoryTrackerFeaturizer(MaxHistoryTrackerFeaturizer):
|
|
889
974
|
"""Truncates the tracker history into `max_history` long sequences.
|
|
890
975
|
|
|
@@ -1159,6 +1244,18 @@ class IntentMaxHistoryTrackerFeaturizer(MaxHistoryTrackerFeaturizer):
|
|
|
1159
1244
|
|
|
1160
1245
|
return trackers_as_states
|
|
1161
1246
|
|
|
1247
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
1248
|
+
return super().to_dict()
|
|
1249
|
+
|
|
1250
|
+
@classmethod
|
|
1251
|
+
def create_from_dict(
|
|
1252
|
+
cls, data: Dict[str, Any]
|
|
1253
|
+
) -> "IntentMaxHistoryTrackerFeaturizer":
|
|
1254
|
+
state_featurizer = SingleStateFeaturizer.create_from_dict(
|
|
1255
|
+
data["state_featurizer"]
|
|
1256
|
+
)
|
|
1257
|
+
return cls(state_featurizer, data["max_history"], data["remove_duplicates"])
|
|
1258
|
+
|
|
1162
1259
|
|
|
1163
1260
|
def _is_prev_action_unlikely_intent_in_state(state: State) -> bool:
|
|
1164
1261
|
prev_action_name = state.get(PREVIOUS_ACTION, {}).get(ACTION_NAME)
|
rasa/core/policies/ted_policy.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
|
-
import logging
|
|
3
2
|
|
|
4
|
-
|
|
3
|
+
import logging
|
|
5
4
|
from pathlib import Path
|
|
6
5
|
from collections import defaultdict
|
|
7
6
|
import contextlib
|
|
7
|
+
from typing import Any, List, Optional, Text, Dict, Tuple, Union, Type
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
10
|
import tensorflow as tf
|
|
11
|
-
from typing import Any, List, Optional, Text, Dict, Tuple, Union, Type
|
|
12
11
|
|
|
12
|
+
from rasa.engine.recipes.default_recipe import DefaultV1Recipe
|
|
13
13
|
from rasa.engine.graph import ExecutionContext
|
|
14
14
|
from rasa.engine.storage.resource import Resource
|
|
15
15
|
from rasa.engine.storage.storage import ModelStorage
|
|
@@ -49,18 +49,22 @@ from rasa.shared.core.generator import TrackerWithCachedStates
|
|
|
49
49
|
from rasa.shared.core.events import EntitiesAdded, Event
|
|
50
50
|
from rasa.shared.core.domain import Domain
|
|
51
51
|
from rasa.shared.nlu.training_data.message import Message
|
|
52
|
-
from rasa.shared.nlu.training_data.features import
|
|
52
|
+
from rasa.shared.nlu.training_data.features import (
|
|
53
|
+
Features,
|
|
54
|
+
save_features,
|
|
55
|
+
load_features,
|
|
56
|
+
)
|
|
53
57
|
import rasa.shared.utils.io
|
|
54
58
|
import rasa.utils.io
|
|
55
59
|
from rasa.utils import train_utils
|
|
56
|
-
from rasa.utils.tensorflow.
|
|
57
|
-
from rasa.utils.tensorflow import rasa_layers
|
|
58
|
-
from rasa.utils.tensorflow.model_data import (
|
|
59
|
-
RasaModelData,
|
|
60
|
-
FeatureSignature,
|
|
60
|
+
from rasa.utils.tensorflow.feature_array import (
|
|
61
61
|
FeatureArray,
|
|
62
|
-
|
|
62
|
+
serialize_nested_feature_arrays,
|
|
63
|
+
deserialize_nested_feature_arrays,
|
|
63
64
|
)
|
|
65
|
+
from rasa.utils.tensorflow.models import RasaModel, TransformerRasaModel
|
|
66
|
+
from rasa.utils.tensorflow import rasa_layers
|
|
67
|
+
from rasa.utils.tensorflow.model_data import RasaModelData, FeatureSignature, Data
|
|
64
68
|
from rasa.utils.tensorflow.model_data_utils import convert_to_data_format
|
|
65
69
|
from rasa.utils.tensorflow.constants import (
|
|
66
70
|
LABEL,
|
|
@@ -961,22 +965,32 @@ class TEDPolicy(Policy):
|
|
|
961
965
|
model_path: Path where model is to be persisted
|
|
962
966
|
"""
|
|
963
967
|
model_filename = self._metadata_filename()
|
|
964
|
-
rasa.utils.io.
|
|
965
|
-
model_path / f"{model_filename}.priority.
|
|
966
|
-
)
|
|
967
|
-
rasa.utils.io.pickle_dump(
|
|
968
|
-
model_path / f"{model_filename}.meta.pkl", self.config
|
|
968
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(
|
|
969
|
+
model_path / f"{model_filename}.priority.json", self.priority
|
|
969
970
|
)
|
|
970
|
-
rasa.utils.io.
|
|
971
|
-
model_path / f"{model_filename}.
|
|
971
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(
|
|
972
|
+
model_path / f"{model_filename}.meta.json", self.config
|
|
972
973
|
)
|
|
973
|
-
|
|
974
|
-
|
|
974
|
+
# save data example
|
|
975
|
+
serialize_nested_feature_arrays(
|
|
976
|
+
self.data_example,
|
|
977
|
+
str(model_path / f"{model_filename}.data_example.st"),
|
|
978
|
+
str(model_path / f"{model_filename}.data_example_metadata.json"),
|
|
975
979
|
)
|
|
976
|
-
|
|
977
|
-
|
|
980
|
+
# save label data
|
|
981
|
+
serialize_nested_feature_arrays(
|
|
978
982
|
dict(self._label_data.data) if self._label_data is not None else {},
|
|
983
|
+
str(model_path / f"{model_filename}.label_data.st"),
|
|
984
|
+
str(model_path / f"{model_filename}.label_data_metadata.json"),
|
|
985
|
+
)
|
|
986
|
+
# save fake features
|
|
987
|
+
metadata = save_features(
|
|
988
|
+
self.fake_features, str(model_path / f"{model_filename}.fake_features.st")
|
|
989
|
+
)
|
|
990
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(
|
|
991
|
+
model_path / f"{model_filename}.fake_features_metadata.json", metadata
|
|
979
992
|
)
|
|
993
|
+
|
|
980
994
|
entity_tag_specs = (
|
|
981
995
|
[tag_spec._asdict() for tag_spec in self._entity_tag_specs]
|
|
982
996
|
if self._entity_tag_specs
|
|
@@ -994,18 +1008,29 @@ class TEDPolicy(Policy):
|
|
|
994
1008
|
model_path: Path where model is to be persisted.
|
|
995
1009
|
"""
|
|
996
1010
|
tf_model_file = model_path / f"{cls._metadata_filename()}.tf_model"
|
|
997
|
-
|
|
998
|
-
|
|
1011
|
+
|
|
1012
|
+
# load data example
|
|
1013
|
+
loaded_data = deserialize_nested_feature_arrays(
|
|
1014
|
+
str(model_path / f"{cls._metadata_filename()}.data_example.st"),
|
|
1015
|
+
str(model_path / f"{cls._metadata_filename()}.data_example_metadata.json"),
|
|
999
1016
|
)
|
|
1000
|
-
|
|
1001
|
-
|
|
1017
|
+
# load label data
|
|
1018
|
+
loaded_label_data = deserialize_nested_feature_arrays(
|
|
1019
|
+
str(model_path / f"{cls._metadata_filename()}.label_data.st"),
|
|
1020
|
+
str(model_path / f"{cls._metadata_filename()}.label_data_metadata.json"),
|
|
1002
1021
|
)
|
|
1003
|
-
|
|
1004
|
-
|
|
1022
|
+
label_data = RasaModelData(data=loaded_label_data)
|
|
1023
|
+
|
|
1024
|
+
# load fake features
|
|
1025
|
+
metadata = rasa.shared.utils.io.read_json_file(
|
|
1026
|
+
model_path / f"{cls._metadata_filename()}.fake_features_metadata.json"
|
|
1005
1027
|
)
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1028
|
+
fake_features = load_features(
|
|
1029
|
+
str(model_path / f"{cls._metadata_filename()}.fake_features.st"), metadata
|
|
1030
|
+
)
|
|
1031
|
+
|
|
1032
|
+
priority = rasa.shared.utils.io.read_json_file(
|
|
1033
|
+
model_path / f"{cls._metadata_filename()}.priority.json"
|
|
1009
1034
|
)
|
|
1010
1035
|
entity_tag_specs = rasa.shared.utils.io.read_json_file(
|
|
1011
1036
|
model_path / f"{cls._metadata_filename()}.entity_tag_specs.json"
|
|
@@ -1023,8 +1048,8 @@ class TEDPolicy(Policy):
|
|
|
1023
1048
|
)
|
|
1024
1049
|
for tag_spec in entity_tag_specs
|
|
1025
1050
|
]
|
|
1026
|
-
model_config = rasa.utils.io.
|
|
1027
|
-
model_path / f"{cls._metadata_filename()}.meta.
|
|
1051
|
+
model_config = rasa.shared.utils.io.read_json_file(
|
|
1052
|
+
model_path / f"{cls._metadata_filename()}.meta.json"
|
|
1028
1053
|
)
|
|
1029
1054
|
|
|
1030
1055
|
return {
|
|
@@ -1070,7 +1095,7 @@ class TEDPolicy(Policy):
|
|
|
1070
1095
|
) -> TEDPolicy:
|
|
1071
1096
|
featurizer = TrackerFeaturizer.load(model_path)
|
|
1072
1097
|
|
|
1073
|
-
if not (model_path / f"{cls._metadata_filename()}.data_example.
|
|
1098
|
+
if not (model_path / f"{cls._metadata_filename()}.data_example.st").is_file():
|
|
1074
1099
|
return cls(
|
|
1075
1100
|
config,
|
|
1076
1101
|
model_storage,
|
|
@@ -5,6 +5,7 @@ from typing import Any, List, Optional, Text, Dict, Type, Union
|
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
import tensorflow as tf
|
|
8
|
+
|
|
8
9
|
import rasa.utils.common
|
|
9
10
|
from rasa.engine.graph import ExecutionContext
|
|
10
11
|
from rasa.engine.recipes.default_recipe import DefaultV1Recipe
|
|
@@ -16,6 +17,7 @@ from rasa.shared.core.domain import Domain
|
|
|
16
17
|
from rasa.shared.core.trackers import DialogueStateTracker
|
|
17
18
|
from rasa.shared.core.constants import SLOTS, ACTIVE_LOOP, ACTION_UNLIKELY_INTENT_NAME
|
|
18
19
|
from rasa.shared.core.events import UserUttered, ActionExecuted
|
|
20
|
+
import rasa.shared.utils.io
|
|
19
21
|
from rasa.shared.nlu.constants import (
|
|
20
22
|
INTENT,
|
|
21
23
|
TEXT,
|
|
@@ -103,8 +105,6 @@ from rasa.utils.tensorflow.constants import (
|
|
|
103
105
|
)
|
|
104
106
|
from rasa.utils.tensorflow import layers
|
|
105
107
|
from rasa.utils.tensorflow.model_data import RasaModelData, FeatureArray, Data
|
|
106
|
-
|
|
107
|
-
import rasa.utils.io as io_utils
|
|
108
108
|
from rasa.core.exceptions import RasaCoreException
|
|
109
109
|
from rasa.shared.utils import common
|
|
110
110
|
|
|
@@ -881,9 +881,12 @@ class UnexpecTEDIntentPolicy(TEDPolicy):
|
|
|
881
881
|
model_path: Path where model is to be persisted
|
|
882
882
|
"""
|
|
883
883
|
super().persist_model_utilities(model_path)
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
884
|
+
|
|
885
|
+
from safetensors.numpy import save_file
|
|
886
|
+
|
|
887
|
+
save_file(
|
|
888
|
+
{str(k): np.array(v) for k, v in self.label_quantiles.items()},
|
|
889
|
+
model_path / f"{self._metadata_filename()}.label_quantiles.st",
|
|
887
890
|
)
|
|
888
891
|
|
|
889
892
|
@classmethod
|
|
@@ -894,9 +897,14 @@ class UnexpecTEDIntentPolicy(TEDPolicy):
|
|
|
894
897
|
model_path: Path where model is to be persisted.
|
|
895
898
|
"""
|
|
896
899
|
model_utilties = super()._load_model_utilities(model_path)
|
|
897
|
-
|
|
898
|
-
|
|
900
|
+
|
|
901
|
+
from safetensors.numpy import load_file
|
|
902
|
+
|
|
903
|
+
loaded_label_quantiles = load_file(
|
|
904
|
+
model_path / f"{cls._metadata_filename()}.label_quantiles.st"
|
|
899
905
|
)
|
|
906
|
+
label_quantiles = {int(k): list(v) for k, v in loaded_label_quantiles.items()}
|
|
907
|
+
|
|
900
908
|
model_utilties.update({"label_quantiles": label_quantiles})
|
|
901
909
|
return model_utilties
|
|
902
910
|
|
|
@@ -36,3 +36,9 @@ class ChangeFlowCommand(Command):
|
|
|
36
36
|
# the change flow command is not actually pushing anything to the tracker,
|
|
37
37
|
# but it is predicted by the MultiStepLLMCommandGenerator and used internally
|
|
38
38
|
return []
|
|
39
|
+
|
|
40
|
+
def __eq__(self, other: Any) -> bool:
|
|
41
|
+
return isinstance(other, ChangeFlowCommand)
|
|
42
|
+
|
|
43
|
+
def __hash__(self) -> int:
|
|
44
|
+
return hash(self.command())
|
|
@@ -229,9 +229,9 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
229
229
|
commands: List[Command] = []
|
|
230
230
|
|
|
231
231
|
slot_set_re = re.compile(
|
|
232
|
-
r"""SetSlot\(
|
|
232
|
+
r"""SetSlot\(['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]?, ?['"]?(.*)['"]?\)"""
|
|
233
233
|
)
|
|
234
|
-
start_flow_re = re.compile(r"StartFlow\(([a-zA-Z0-9_-]
|
|
234
|
+
start_flow_re = re.compile(r"StartFlow\(['\"]?([a-zA-Z0-9_-]+)['\"]?\)")
|
|
235
235
|
change_flow_re = re.compile(r"ChangeFlow\(\)")
|
|
236
236
|
cancel_flow_re = re.compile(r"CancelFlow\(\)")
|
|
237
237
|
chitchat_re = re.compile(r"ChitChat\(\)")
|
|
@@ -280,9 +280,19 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
280
280
|
commands.append(HumanHandoffCommand())
|
|
281
281
|
elif match := clarify_re.search(action):
|
|
282
282
|
options = sorted([opt.strip() for opt in match.group(1).split(",")])
|
|
283
|
+
# Remove surrounding quotes if present
|
|
284
|
+
cleaned_options = []
|
|
285
|
+
for flow in options:
|
|
286
|
+
if (flow.startswith('"') and flow.endswith('"')) or (
|
|
287
|
+
flow.startswith("'") and flow.endswith("'")
|
|
288
|
+
):
|
|
289
|
+
cleaned_options.append(flow[1:-1])
|
|
290
|
+
else:
|
|
291
|
+
cleaned_options.append(flow)
|
|
292
|
+
# check if flow is valid
|
|
283
293
|
valid_options = [
|
|
284
294
|
flow
|
|
285
|
-
for flow in
|
|
295
|
+
for flow in cleaned_options
|
|
286
296
|
if flow in flows.user_flow_ids
|
|
287
297
|
and flow not in user_flows_on_the_stack(tracker.stack)
|
|
288
298
|
]
|
|
@@ -293,6 +303,13 @@ class MultiStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
293
303
|
elif change_flow_re.search(action):
|
|
294
304
|
commands.append(ChangeFlowCommand())
|
|
295
305
|
|
|
306
|
+
if not commands:
|
|
307
|
+
structlogger.debug(
|
|
308
|
+
"multi_step_llm_command_generator.parse_commands",
|
|
309
|
+
message="No commands were parsed from the LLM actions.",
|
|
310
|
+
actions=actions,
|
|
311
|
+
)
|
|
312
|
+
|
|
296
313
|
return commands
|
|
297
314
|
|
|
298
315
|
### Helper methods
|
|
@@ -185,6 +185,12 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
185
185
|
|
|
186
186
|
if not commands:
|
|
187
187
|
# no commands are parsed or there's an invalid command
|
|
188
|
+
structlogger.warning(
|
|
189
|
+
"single_step_llm_command_generator.predict_commands",
|
|
190
|
+
message="No commands were predicted as the LLM response could "
|
|
191
|
+
"not be parsed or the LLM responded with an invalid command."
|
|
192
|
+
"Returning a CannotHandleCommand instead.",
|
|
193
|
+
)
|
|
188
194
|
commands = [CannotHandleCommand()]
|
|
189
195
|
|
|
190
196
|
if tracker.has_coexistence_routing_slot:
|
|
@@ -285,14 +291,16 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
285
291
|
|
|
286
292
|
commands: List[Command] = []
|
|
287
293
|
|
|
288
|
-
slot_set_re = re.compile(
|
|
289
|
-
|
|
294
|
+
slot_set_re = re.compile(
|
|
295
|
+
r"""SetSlot\(['"]?([a-zA-Z_][a-zA-Z0-9_-]*)['"]?, ?['"]?(.*)['"]?\)"""
|
|
296
|
+
)
|
|
297
|
+
start_flow_re = re.compile(r"StartFlow\(['\"]?([a-zA-Z0-9_-]+)['\"]?\)")
|
|
290
298
|
cancel_flow_re = re.compile(r"CancelFlow\(\)")
|
|
291
299
|
chitchat_re = re.compile(r"ChitChat\(\)")
|
|
292
300
|
skip_question_re = re.compile(r"SkipQuestion\(\)")
|
|
293
301
|
knowledge_re = re.compile(r"SearchAndReply\(\)")
|
|
294
302
|
humand_handoff_re = re.compile(r"HumanHandoff\(\)")
|
|
295
|
-
clarify_re = re.compile(r"Clarify\(([a-zA-Z0-9_, ]+)\)")
|
|
303
|
+
clarify_re = re.compile(r"Clarify\(([\"\'a-zA-Z0-9_, ]+)\)")
|
|
296
304
|
|
|
297
305
|
for action in actions.strip().splitlines():
|
|
298
306
|
if match := slot_set_re.search(action):
|
|
@@ -321,14 +329,31 @@ class SingleStepLLMCommandGenerator(LLMBasedCommandGenerator):
|
|
|
321
329
|
commands.append(HumanHandoffCommand())
|
|
322
330
|
elif match := clarify_re.search(action):
|
|
323
331
|
options = sorted([opt.strip() for opt in match.group(1).split(",")])
|
|
332
|
+
# Remove surrounding quotes if present
|
|
333
|
+
cleaned_options = []
|
|
334
|
+
for flow in options:
|
|
335
|
+
if (flow.startswith('"') and flow.endswith('"')) or (
|
|
336
|
+
flow.startswith("'") and flow.endswith("'")
|
|
337
|
+
):
|
|
338
|
+
cleaned_options.append(flow[1:-1])
|
|
339
|
+
else:
|
|
340
|
+
cleaned_options.append(flow)
|
|
341
|
+
# check if flow is valid
|
|
324
342
|
valid_options = [
|
|
325
|
-
flow for flow in
|
|
343
|
+
flow for flow in cleaned_options if flow in flows.user_flow_ids
|
|
326
344
|
]
|
|
327
345
|
if len(set(valid_options)) == 1:
|
|
328
346
|
commands.extend(cls.start_flow_by_name(valid_options[0], flows))
|
|
329
347
|
elif len(valid_options) > 1:
|
|
330
348
|
commands.append(ClarifyCommand(valid_options))
|
|
331
349
|
|
|
350
|
+
if not commands:
|
|
351
|
+
structlogger.debug(
|
|
352
|
+
"single_step_llm_command_generator.parse_commands",
|
|
353
|
+
message="No commands were parsed from the LLM actions.",
|
|
354
|
+
actions=actions,
|
|
355
|
+
)
|
|
356
|
+
|
|
332
357
|
return commands
|
|
333
358
|
|
|
334
359
|
@classmethod
|
rasa/e2e_test/e2e_test_runner.py
CHANGED
|
@@ -190,11 +190,11 @@ class E2ETestRunner:
|
|
|
190
190
|
error=f"Message handling timed out for user message '{step.text}'.",
|
|
191
191
|
exc_info=True,
|
|
192
192
|
)
|
|
193
|
-
except Exception:
|
|
193
|
+
except Exception as exc:
|
|
194
194
|
structlogger.error(
|
|
195
195
|
"e2e_test_runner.run_prediction_loop",
|
|
196
196
|
error=f"An exception occurred while handling "
|
|
197
|
-
f"user message '{step.text}'.",
|
|
197
|
+
f"user message '{step.text}'. Error: {exc}",
|
|
198
198
|
)
|
|
199
199
|
tracker = await self.agent.tracker_store.retrieve(sender_id) # type: ignore[assignment]
|
|
200
200
|
turns[position], event_cursor = self.get_actual_step_output(
|