rasa-pro 3.8.16__py3-none-any.whl → 3.8.18__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 +37 -1
- 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/patterns/default_flows_for_patterns.yml +15 -15
- 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/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 +370 -0
- rasa/utils/tensorflow/model_data.py +2 -193
- rasa/version.py +1 -1
- {rasa_pro-3.8.16.dist-info → rasa_pro-3.8.18.dist-info}/METADATA +40 -4
- {rasa_pro-3.8.16.dist-info → rasa_pro-3.8.18.dist-info}/RECORD +25 -24
- {rasa_pro-3.8.16.dist-info → rasa_pro-3.8.18.dist-info}/NOTICE +0 -0
- {rasa_pro-3.8.16.dist-info → rasa_pro-3.8.18.dist-info}/WHEEL +0 -0
- {rasa_pro-3.8.16.dist-info → rasa_pro-3.8.18.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
from typing import Dict, Any, List, Tuple, Optional, Union
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
import scipy.sparse
|
|
5
|
+
from safetensors.numpy import load_file
|
|
6
|
+
from safetensors.numpy import save_file
|
|
7
|
+
|
|
8
|
+
import rasa.shared.utils.io
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _recursive_serialize(
|
|
12
|
+
array: Any, prefix: str, data_dict: Dict[str, Any], metadata: List[Dict[str, Any]]
|
|
13
|
+
) -> None:
|
|
14
|
+
"""Recursively serialize arrays and matrices for high dimensional data."""
|
|
15
|
+
if isinstance(array, np.ndarray) and array.ndim <= 2:
|
|
16
|
+
data_key = f"{prefix}_array"
|
|
17
|
+
data_dict[data_key] = array
|
|
18
|
+
metadata.append({"type": "dense", "key": data_key, "shape": array.shape})
|
|
19
|
+
|
|
20
|
+
elif isinstance(array, list) and all([isinstance(v, float) for v in array]):
|
|
21
|
+
data_key = f"{prefix}_list"
|
|
22
|
+
data_dict[data_key] = np.array(array, dtype=np.float32)
|
|
23
|
+
metadata.append({"type": "list", "key": data_key})
|
|
24
|
+
|
|
25
|
+
elif isinstance(array, list) and all([isinstance(v, int) for v in array]):
|
|
26
|
+
data_key = f"{prefix}_list"
|
|
27
|
+
data_dict[data_key] = np.array(array, dtype=np.int64)
|
|
28
|
+
metadata.append({"type": "list", "key": data_key})
|
|
29
|
+
|
|
30
|
+
elif isinstance(array, scipy.sparse.spmatrix):
|
|
31
|
+
data_key_data = f"{prefix}_data"
|
|
32
|
+
data_key_row = f"{prefix}_row"
|
|
33
|
+
data_key_col = f"{prefix}_col"
|
|
34
|
+
array = array.tocoo()
|
|
35
|
+
data_dict.update(
|
|
36
|
+
{
|
|
37
|
+
data_key_data: array.data,
|
|
38
|
+
data_key_row: array.row,
|
|
39
|
+
data_key_col: array.col,
|
|
40
|
+
}
|
|
41
|
+
)
|
|
42
|
+
metadata.append({"type": "sparse", "key": prefix, "shape": array.shape})
|
|
43
|
+
|
|
44
|
+
elif isinstance(array, list) or isinstance(array, np.ndarray):
|
|
45
|
+
group_metadata = {"type": "group", "subcomponents": []}
|
|
46
|
+
for idx, item in enumerate(array):
|
|
47
|
+
new_prefix = f"{prefix}_{idx}"
|
|
48
|
+
_recursive_serialize(
|
|
49
|
+
item, new_prefix, data_dict, group_metadata["subcomponents"]
|
|
50
|
+
)
|
|
51
|
+
metadata.append(group_metadata)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _serialize_nested_data(
|
|
55
|
+
nested_data: Dict[str, Dict[str, List["FeatureArray"]]],
|
|
56
|
+
prefix: str,
|
|
57
|
+
data_dict: Dict[str, np.ndarray],
|
|
58
|
+
metadata: List[Dict[str, Union[str, List]]],
|
|
59
|
+
) -> None:
|
|
60
|
+
"""Handle serialization across dictionary and list levels."""
|
|
61
|
+
for outer_key, inner_dict in nested_data.items():
|
|
62
|
+
inner_metadata = {"key": outer_key, "components": []}
|
|
63
|
+
|
|
64
|
+
for inner_key, feature_arrays in inner_dict.items():
|
|
65
|
+
array_metadata = {
|
|
66
|
+
"key": inner_key,
|
|
67
|
+
"number_of_dimensions": feature_arrays[0].number_of_dimensions,
|
|
68
|
+
"features": [],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
for idx, feature_array in enumerate(feature_arrays):
|
|
72
|
+
feature_prefix = f"{prefix}_{outer_key}_{inner_key}_{idx}"
|
|
73
|
+
_recursive_serialize(
|
|
74
|
+
feature_array.tolist(),
|
|
75
|
+
feature_prefix,
|
|
76
|
+
data_dict,
|
|
77
|
+
array_metadata["features"],
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
inner_metadata["components"].append( # type:ignore[attr-defined]
|
|
81
|
+
array_metadata
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
metadata.append(inner_metadata)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def serialize_nested_feature_arrays(
|
|
88
|
+
nested_feature_array: Dict[str, Dict[str, List["FeatureArray"]]],
|
|
89
|
+
data_filename: str,
|
|
90
|
+
metadata_filename: str,
|
|
91
|
+
) -> None:
|
|
92
|
+
data_dict: Dict[str, np.ndarray] = {}
|
|
93
|
+
metadata: List[Dict[str, Union[str, List]]] = []
|
|
94
|
+
|
|
95
|
+
_serialize_nested_data(nested_feature_array, "component", data_dict, metadata)
|
|
96
|
+
|
|
97
|
+
# Save serialized data and metadata
|
|
98
|
+
save_file(data_dict, data_filename)
|
|
99
|
+
rasa.shared.utils.io.dump_obj_as_json_to_file(metadata_filename, metadata)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _recursive_deserialize(
|
|
103
|
+
metadata: List[Dict[str, Any]], data: Dict[str, Any]
|
|
104
|
+
) -> List[Any]:
|
|
105
|
+
"""Recursively deserialize arrays and matrices for high dimensional data."""
|
|
106
|
+
result = []
|
|
107
|
+
|
|
108
|
+
for item in metadata:
|
|
109
|
+
if item["type"] == "dense":
|
|
110
|
+
key = item["key"]
|
|
111
|
+
array = np.asarray(data[key]).reshape(item["shape"])
|
|
112
|
+
result.append(array)
|
|
113
|
+
|
|
114
|
+
elif item["type"] == "list":
|
|
115
|
+
key = item["key"]
|
|
116
|
+
result.append(list(data[key]))
|
|
117
|
+
|
|
118
|
+
elif item["type"] == "sparse":
|
|
119
|
+
data_vals = data[f"{item['key']}_data"]
|
|
120
|
+
row_vals = data[f"{item['key']}_row"]
|
|
121
|
+
col_vals = data[f"{item['key']}_col"]
|
|
122
|
+
sparse_matrix = scipy.sparse.coo_matrix(
|
|
123
|
+
(data_vals, (row_vals, col_vals)), shape=item["shape"]
|
|
124
|
+
)
|
|
125
|
+
result.append(sparse_matrix)
|
|
126
|
+
elif item["type"] == "group":
|
|
127
|
+
sublist = _recursive_deserialize(item["subcomponents"], data)
|
|
128
|
+
result.append(sublist)
|
|
129
|
+
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _deserialize_nested_data(
|
|
134
|
+
metadata: List[Dict[str, Any]], data_dict: Dict[str, Any]
|
|
135
|
+
) -> Dict[str, Dict[str, List["FeatureArray"]]]:
|
|
136
|
+
"""Handle deserialization across all dictionary and list levels."""
|
|
137
|
+
result: Dict[str, Dict[str, List["FeatureArray"]]] = {}
|
|
138
|
+
|
|
139
|
+
for outer_item in metadata:
|
|
140
|
+
outer_key = outer_item["key"]
|
|
141
|
+
result[outer_key] = {}
|
|
142
|
+
|
|
143
|
+
for inner_item in outer_item["components"]:
|
|
144
|
+
inner_key = inner_item["key"]
|
|
145
|
+
feature_arrays = []
|
|
146
|
+
|
|
147
|
+
# Reconstruct the list of FeatureArrays
|
|
148
|
+
for feature_item in inner_item["features"]:
|
|
149
|
+
# Reconstruct the list of FeatureArrays
|
|
150
|
+
feature_array_data = _recursive_deserialize([feature_item], data_dict)
|
|
151
|
+
# Prepare the input for the FeatureArray;
|
|
152
|
+
# ensure it is np.ndarray compatible
|
|
153
|
+
input_array = np.array(feature_array_data[0], dtype=object)
|
|
154
|
+
feature_array = FeatureArray(
|
|
155
|
+
input_array, inner_item["number_of_dimensions"]
|
|
156
|
+
)
|
|
157
|
+
feature_arrays.append(feature_array)
|
|
158
|
+
|
|
159
|
+
result[outer_key][inner_key] = feature_arrays
|
|
160
|
+
|
|
161
|
+
return result
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def deserialize_nested_feature_arrays(
|
|
165
|
+
data_filename: str, metadata_filename: str
|
|
166
|
+
) -> Dict[str, Dict[str, List["FeatureArray"]]]:
|
|
167
|
+
metadata = rasa.shared.utils.io.read_json_file(metadata_filename)
|
|
168
|
+
data_dict = load_file(data_filename)
|
|
169
|
+
|
|
170
|
+
return _deserialize_nested_data(metadata, data_dict)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class FeatureArray(np.ndarray):
|
|
174
|
+
"""Stores any kind of features ready to be used by a RasaModel.
|
|
175
|
+
|
|
176
|
+
Next to the input numpy array of features, it also received the number of
|
|
177
|
+
dimensions of the features.
|
|
178
|
+
As our features can have 1 to 4 dimensions we might have different number of numpy
|
|
179
|
+
arrays stacked. The number of dimensions helps us to figure out how to handle this
|
|
180
|
+
particular feature array. Also, it is automatically determined whether the feature
|
|
181
|
+
array is sparse or not and the number of units is determined as well.
|
|
182
|
+
|
|
183
|
+
Subclassing np.array: https://numpy.org/doc/stable/user/basics.subclassing.html
|
|
184
|
+
"""
|
|
185
|
+
|
|
186
|
+
def __new__(
|
|
187
|
+
cls, input_array: np.ndarray, number_of_dimensions: int
|
|
188
|
+
) -> "FeatureArray":
|
|
189
|
+
"""Create and return a new object. See help(type) for accurate signature."""
|
|
190
|
+
FeatureArray._validate_number_of_dimensions(number_of_dimensions, input_array)
|
|
191
|
+
|
|
192
|
+
feature_array = np.asarray(input_array).view(cls)
|
|
193
|
+
|
|
194
|
+
if number_of_dimensions <= 2:
|
|
195
|
+
feature_array.units = input_array.shape[-1]
|
|
196
|
+
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
197
|
+
elif number_of_dimensions == 3:
|
|
198
|
+
feature_array.units = input_array[0].shape[-1]
|
|
199
|
+
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
200
|
+
elif number_of_dimensions == 4:
|
|
201
|
+
feature_array.units = input_array[0][0].shape[-1]
|
|
202
|
+
feature_array.is_sparse = isinstance(
|
|
203
|
+
input_array[0][0], scipy.sparse.spmatrix
|
|
204
|
+
)
|
|
205
|
+
else:
|
|
206
|
+
raise ValueError(
|
|
207
|
+
f"Number of dimensions '{number_of_dimensions}' currently not "
|
|
208
|
+
f"supported."
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
feature_array.number_of_dimensions = number_of_dimensions
|
|
212
|
+
|
|
213
|
+
return feature_array
|
|
214
|
+
|
|
215
|
+
def __init__(
|
|
216
|
+
self, input_array: Any, number_of_dimensions: int, **kwargs: Any
|
|
217
|
+
) -> None:
|
|
218
|
+
"""Initialize. FeatureArray.
|
|
219
|
+
|
|
220
|
+
Needed in order to avoid 'Invalid keyword argument number_of_dimensions
|
|
221
|
+
to function FeatureArray.__init__ '
|
|
222
|
+
Args:
|
|
223
|
+
input_array: the array that contains features
|
|
224
|
+
number_of_dimensions: number of dimensions in input_array
|
|
225
|
+
"""
|
|
226
|
+
super().__init__(**kwargs)
|
|
227
|
+
self.number_of_dimensions = number_of_dimensions
|
|
228
|
+
|
|
229
|
+
def __array_finalize__(self, obj: Optional[np.ndarray]) -> None:
|
|
230
|
+
"""This method is called when the system allocates a new array from obj.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
obj: A subclass (subtype) of ndarray.
|
|
234
|
+
"""
|
|
235
|
+
if obj is None:
|
|
236
|
+
return
|
|
237
|
+
|
|
238
|
+
self.units = getattr(obj, "units", None)
|
|
239
|
+
self.number_of_dimensions = getattr( # type:ignore[assignment]
|
|
240
|
+
obj, "number_of_dimensions", None
|
|
241
|
+
)
|
|
242
|
+
self.is_sparse = getattr(obj, "is_sparse", None)
|
|
243
|
+
|
|
244
|
+
default_attributes = {
|
|
245
|
+
"units": self.units,
|
|
246
|
+
"number_of_dimensions": self.number_of_dimensions,
|
|
247
|
+
"is_spare": self.is_sparse,
|
|
248
|
+
}
|
|
249
|
+
self.__dict__.update(default_attributes)
|
|
250
|
+
|
|
251
|
+
# pytype: disable=attribute-error
|
|
252
|
+
def __array_ufunc__(
|
|
253
|
+
self, ufunc: Any, method: str, *inputs: Any, **kwargs: Any
|
|
254
|
+
) -> Any:
|
|
255
|
+
"""Overwrite this method as we are subclassing numpy array.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
ufunc: The ufunc object that was called.
|
|
259
|
+
method: A string indicating which Ufunc method was called
|
|
260
|
+
(one of "__call__", "reduce", "reduceat", "accumulate", "outer",
|
|
261
|
+
"inner").
|
|
262
|
+
*inputs: A tuple of the input arguments to the ufunc.
|
|
263
|
+
**kwargs: Any additional arguments
|
|
264
|
+
|
|
265
|
+
Returns:
|
|
266
|
+
The result of the operation.
|
|
267
|
+
"""
|
|
268
|
+
f = {
|
|
269
|
+
"reduce": ufunc.reduce,
|
|
270
|
+
"accumulate": ufunc.accumulate,
|
|
271
|
+
"reduceat": ufunc.reduceat,
|
|
272
|
+
"outer": ufunc.outer,
|
|
273
|
+
"at": ufunc.at,
|
|
274
|
+
"__call__": ufunc,
|
|
275
|
+
}
|
|
276
|
+
# convert the inputs to np.ndarray to prevent recursion, call the function,
|
|
277
|
+
# then cast it back as FeatureArray
|
|
278
|
+
output = FeatureArray(
|
|
279
|
+
f[method](*(i.view(np.ndarray) for i in inputs), **kwargs),
|
|
280
|
+
number_of_dimensions=kwargs["number_of_dimensions"],
|
|
281
|
+
)
|
|
282
|
+
output.__dict__ = self.__dict__ # carry forward attributes
|
|
283
|
+
return output
|
|
284
|
+
|
|
285
|
+
def __reduce__(self) -> Tuple[Any, Any, Any]:
|
|
286
|
+
"""Needed in order to pickle this object.
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
A tuple.
|
|
290
|
+
"""
|
|
291
|
+
pickled_state = super(FeatureArray, self).__reduce__()
|
|
292
|
+
if isinstance(pickled_state, str):
|
|
293
|
+
raise TypeError("np array __reduce__ returned string instead of tuple.")
|
|
294
|
+
new_state = pickled_state[2] + (
|
|
295
|
+
self.number_of_dimensions,
|
|
296
|
+
self.is_sparse,
|
|
297
|
+
self.units,
|
|
298
|
+
)
|
|
299
|
+
return pickled_state[0], pickled_state[1], new_state
|
|
300
|
+
|
|
301
|
+
def __setstate__(self, state: Any, **kwargs: Any) -> None:
|
|
302
|
+
"""Sets the state.
|
|
303
|
+
|
|
304
|
+
Args:
|
|
305
|
+
state: The state argument must be a sequence that contains the following
|
|
306
|
+
elements version, shape, dtype, isFortan, rawdata.
|
|
307
|
+
**kwargs: Any additional parameter
|
|
308
|
+
"""
|
|
309
|
+
# Needed in order to load the object
|
|
310
|
+
self.number_of_dimensions = state[-3]
|
|
311
|
+
self.is_sparse = state[-2]
|
|
312
|
+
self.units = state[-1]
|
|
313
|
+
super(FeatureArray, self).__setstate__(state[0:-3], **kwargs)
|
|
314
|
+
|
|
315
|
+
# pytype: enable=attribute-error
|
|
316
|
+
|
|
317
|
+
@staticmethod
|
|
318
|
+
def _validate_number_of_dimensions(
|
|
319
|
+
number_of_dimensions: int, input_array: np.ndarray
|
|
320
|
+
) -> None:
|
|
321
|
+
"""Validates if the input array has given number of dimensions.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
number_of_dimensions: number of dimensions
|
|
325
|
+
input_array: input array
|
|
326
|
+
|
|
327
|
+
Raises: ValueError in case the dimensions do not match
|
|
328
|
+
"""
|
|
329
|
+
# when loading the feature arrays from disk, the shape represents
|
|
330
|
+
# the correct number of dimensions
|
|
331
|
+
if len(input_array.shape) == number_of_dimensions:
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
_sub_array = input_array
|
|
335
|
+
dim = 0
|
|
336
|
+
# Go number_of_dimensions into the given input_array
|
|
337
|
+
for i in range(1, number_of_dimensions + 1):
|
|
338
|
+
_sub_array = _sub_array[0]
|
|
339
|
+
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
340
|
+
dim = i
|
|
341
|
+
break
|
|
342
|
+
if isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
343
|
+
# sequence dimension is 0, we are dealing with "fake" features
|
|
344
|
+
dim = i
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
# If the resulting sub_array is sparse, the remaining number of dimensions
|
|
348
|
+
# should be at least 2
|
|
349
|
+
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
350
|
+
if dim > 2:
|
|
351
|
+
raise ValueError(
|
|
352
|
+
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
353
|
+
f"match dimensions of given input array: {input_array}."
|
|
354
|
+
)
|
|
355
|
+
elif isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
356
|
+
# sequence dimension is 0, we are dealing with "fake" features,
|
|
357
|
+
# but they should be of dim 2
|
|
358
|
+
if dim > 2:
|
|
359
|
+
raise ValueError(
|
|
360
|
+
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
361
|
+
f"match dimensions of given input array: {input_array}."
|
|
362
|
+
)
|
|
363
|
+
# If the resulting sub_array is dense, the sub_array should be a single number
|
|
364
|
+
elif not np.issubdtype(type(_sub_array), np.integer) and not isinstance(
|
|
365
|
+
_sub_array, (np.float32, np.float64)
|
|
366
|
+
):
|
|
367
|
+
raise ValueError(
|
|
368
|
+
f"Given number of dimensions '{number_of_dimensions}' does not match "
|
|
369
|
+
f"dimensions of given input array: {input_array}."
|
|
370
|
+
)
|
|
@@ -20,6 +20,8 @@ import numpy as np
|
|
|
20
20
|
import scipy.sparse
|
|
21
21
|
from sklearn.model_selection import train_test_split
|
|
22
22
|
|
|
23
|
+
from rasa.utils.tensorflow.feature_array import FeatureArray
|
|
24
|
+
|
|
23
25
|
logger = logging.getLogger(__name__)
|
|
24
26
|
|
|
25
27
|
|
|
@@ -37,199 +39,6 @@ def ragged_array_to_ndarray(ragged_array: Iterable[np.ndarray]) -> np.ndarray:
|
|
|
37
39
|
return np.array(ragged_array, dtype=object)
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
class FeatureArray(np.ndarray):
|
|
41
|
-
"""Stores any kind of features ready to be used by a RasaModel.
|
|
42
|
-
|
|
43
|
-
Next to the input numpy array of features, it also received the number of
|
|
44
|
-
dimensions of the features.
|
|
45
|
-
As our features can have 1 to 4 dimensions we might have different number of numpy
|
|
46
|
-
arrays stacked. The number of dimensions helps us to figure out how to handle this
|
|
47
|
-
particular feature array. Also, it is automatically determined whether the feature
|
|
48
|
-
array is sparse or not and the number of units is determined as well.
|
|
49
|
-
|
|
50
|
-
Subclassing np.array: https://numpy.org/doc/stable/user/basics.subclassing.html
|
|
51
|
-
"""
|
|
52
|
-
|
|
53
|
-
def __new__(
|
|
54
|
-
cls, input_array: np.ndarray, number_of_dimensions: int
|
|
55
|
-
) -> "FeatureArray":
|
|
56
|
-
"""Create and return a new object. See help(type) for accurate signature."""
|
|
57
|
-
FeatureArray._validate_number_of_dimensions(number_of_dimensions, input_array)
|
|
58
|
-
|
|
59
|
-
feature_array = np.asarray(input_array).view(cls)
|
|
60
|
-
|
|
61
|
-
if number_of_dimensions <= 2:
|
|
62
|
-
feature_array.units = input_array.shape[-1]
|
|
63
|
-
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
64
|
-
elif number_of_dimensions == 3:
|
|
65
|
-
feature_array.units = input_array[0].shape[-1]
|
|
66
|
-
feature_array.is_sparse = isinstance(input_array[0], scipy.sparse.spmatrix)
|
|
67
|
-
elif number_of_dimensions == 4:
|
|
68
|
-
feature_array.units = input_array[0][0].shape[-1]
|
|
69
|
-
feature_array.is_sparse = isinstance(
|
|
70
|
-
input_array[0][0], scipy.sparse.spmatrix
|
|
71
|
-
)
|
|
72
|
-
else:
|
|
73
|
-
raise ValueError(
|
|
74
|
-
f"Number of dimensions '{number_of_dimensions}' currently not "
|
|
75
|
-
f"supported."
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
feature_array.number_of_dimensions = number_of_dimensions
|
|
79
|
-
|
|
80
|
-
return feature_array
|
|
81
|
-
|
|
82
|
-
def __init__(
|
|
83
|
-
self, input_array: Any, number_of_dimensions: int, **kwargs: Any
|
|
84
|
-
) -> None:
|
|
85
|
-
"""Initialize. FeatureArray.
|
|
86
|
-
|
|
87
|
-
Needed in order to avoid 'Invalid keyword argument number_of_dimensions
|
|
88
|
-
to function FeatureArray.__init__ '
|
|
89
|
-
Args:
|
|
90
|
-
input_array: the array that contains features
|
|
91
|
-
number_of_dimensions: number of dimensions in input_array
|
|
92
|
-
"""
|
|
93
|
-
super().__init__(**kwargs)
|
|
94
|
-
self.number_of_dimensions = number_of_dimensions
|
|
95
|
-
|
|
96
|
-
def __array_finalize__(self, obj: Optional[np.ndarray]) -> None:
|
|
97
|
-
"""This method is called when the system allocates a new array from obj.
|
|
98
|
-
|
|
99
|
-
Args:
|
|
100
|
-
obj: A subclass (subtype) of ndarray.
|
|
101
|
-
"""
|
|
102
|
-
if obj is None:
|
|
103
|
-
return
|
|
104
|
-
|
|
105
|
-
self.units = getattr(obj, "units", None)
|
|
106
|
-
self.number_of_dimensions = getattr(obj, "number_of_dimensions", None) # type: ignore[assignment] # noqa:E501
|
|
107
|
-
self.is_sparse = getattr(obj, "is_sparse", None)
|
|
108
|
-
|
|
109
|
-
default_attributes = {
|
|
110
|
-
"units": self.units,
|
|
111
|
-
"number_of_dimensions": self.number_of_dimensions,
|
|
112
|
-
"is_spare": self.is_sparse,
|
|
113
|
-
}
|
|
114
|
-
self.__dict__.update(default_attributes)
|
|
115
|
-
|
|
116
|
-
# pytype: disable=attribute-error
|
|
117
|
-
def __array_ufunc__(
|
|
118
|
-
self, ufunc: Any, method: Text, *inputs: Any, **kwargs: Any
|
|
119
|
-
) -> Any:
|
|
120
|
-
"""Overwrite this method as we are subclassing numpy array.
|
|
121
|
-
|
|
122
|
-
Args:
|
|
123
|
-
ufunc: The ufunc object that was called.
|
|
124
|
-
method: A string indicating which Ufunc method was called
|
|
125
|
-
(one of "__call__", "reduce", "reduceat", "accumulate", "outer",
|
|
126
|
-
"inner").
|
|
127
|
-
*inputs: A tuple of the input arguments to the ufunc.
|
|
128
|
-
**kwargs: Any additional arguments
|
|
129
|
-
|
|
130
|
-
Returns:
|
|
131
|
-
The result of the operation.
|
|
132
|
-
"""
|
|
133
|
-
f = {
|
|
134
|
-
"reduce": ufunc.reduce,
|
|
135
|
-
"accumulate": ufunc.accumulate,
|
|
136
|
-
"reduceat": ufunc.reduceat,
|
|
137
|
-
"outer": ufunc.outer,
|
|
138
|
-
"at": ufunc.at,
|
|
139
|
-
"__call__": ufunc,
|
|
140
|
-
}
|
|
141
|
-
# convert the inputs to np.ndarray to prevent recursion, call the function,
|
|
142
|
-
# then cast it back as FeatureArray
|
|
143
|
-
output = FeatureArray(
|
|
144
|
-
f[method](*(i.view(np.ndarray) for i in inputs), **kwargs),
|
|
145
|
-
number_of_dimensions=kwargs["number_of_dimensions"],
|
|
146
|
-
)
|
|
147
|
-
output.__dict__ = self.__dict__ # carry forward attributes
|
|
148
|
-
return output
|
|
149
|
-
|
|
150
|
-
def __reduce__(self) -> Tuple[Any, Any, Any]:
|
|
151
|
-
"""Needed in order to pickle this object.
|
|
152
|
-
|
|
153
|
-
Returns:
|
|
154
|
-
A tuple.
|
|
155
|
-
"""
|
|
156
|
-
pickled_state = super(FeatureArray, self).__reduce__()
|
|
157
|
-
if isinstance(pickled_state, str):
|
|
158
|
-
raise TypeError("np array __reduce__ returned string instead of tuple.")
|
|
159
|
-
new_state = pickled_state[2] + (
|
|
160
|
-
self.number_of_dimensions,
|
|
161
|
-
self.is_sparse,
|
|
162
|
-
self.units,
|
|
163
|
-
)
|
|
164
|
-
return pickled_state[0], pickled_state[1], new_state
|
|
165
|
-
|
|
166
|
-
def __setstate__(self, state: Any, **kwargs: Any) -> None:
|
|
167
|
-
"""Sets the state.
|
|
168
|
-
|
|
169
|
-
Args:
|
|
170
|
-
state: The state argument must be a sequence that contains the following
|
|
171
|
-
elements version, shape, dtype, isFortan, rawdata.
|
|
172
|
-
**kwargs: Any additional parameter
|
|
173
|
-
"""
|
|
174
|
-
# Needed in order to load the object
|
|
175
|
-
self.number_of_dimensions = state[-3]
|
|
176
|
-
self.is_sparse = state[-2]
|
|
177
|
-
self.units = state[-1]
|
|
178
|
-
super(FeatureArray, self).__setstate__(state[0:-3], **kwargs)
|
|
179
|
-
|
|
180
|
-
# pytype: enable=attribute-error
|
|
181
|
-
|
|
182
|
-
@staticmethod
|
|
183
|
-
def _validate_number_of_dimensions(
|
|
184
|
-
number_of_dimensions: int, input_array: np.ndarray
|
|
185
|
-
) -> None:
|
|
186
|
-
"""Validates if the the input array has given number of dimensions.
|
|
187
|
-
|
|
188
|
-
Args:
|
|
189
|
-
number_of_dimensions: number of dimensions
|
|
190
|
-
input_array: input array
|
|
191
|
-
|
|
192
|
-
Raises: ValueError in case the dimensions do not match
|
|
193
|
-
"""
|
|
194
|
-
_sub_array = input_array
|
|
195
|
-
dim = 0
|
|
196
|
-
# Go number_of_dimensions into the given input_array
|
|
197
|
-
for i in range(1, number_of_dimensions + 1):
|
|
198
|
-
_sub_array = _sub_array[0]
|
|
199
|
-
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
200
|
-
dim = i
|
|
201
|
-
break
|
|
202
|
-
if isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
203
|
-
# sequence dimension is 0, we are dealing with "fake" features
|
|
204
|
-
dim = i
|
|
205
|
-
break
|
|
206
|
-
|
|
207
|
-
# If the resulting sub_array is sparse, the remaining number of dimensions
|
|
208
|
-
# should be at least 2
|
|
209
|
-
if isinstance(_sub_array, scipy.sparse.spmatrix):
|
|
210
|
-
if dim > 2:
|
|
211
|
-
raise ValueError(
|
|
212
|
-
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
213
|
-
f"match dimensions of given input array: {input_array}."
|
|
214
|
-
)
|
|
215
|
-
elif isinstance(_sub_array, np.ndarray) and _sub_array.shape[0] == 0:
|
|
216
|
-
# sequence dimension is 0, we are dealing with "fake" features,
|
|
217
|
-
# but they should be of dim 2
|
|
218
|
-
if dim > 2:
|
|
219
|
-
raise ValueError(
|
|
220
|
-
f"Given number of dimensions '{number_of_dimensions}' does not "
|
|
221
|
-
f"match dimensions of given input array: {input_array}."
|
|
222
|
-
)
|
|
223
|
-
# If the resulting sub_array is dense, the sub_array should be a single number
|
|
224
|
-
elif not np.issubdtype(type(_sub_array), np.integer) and not isinstance(
|
|
225
|
-
_sub_array, (np.float32, np.float64)
|
|
226
|
-
):
|
|
227
|
-
raise ValueError(
|
|
228
|
-
f"Given number of dimensions '{number_of_dimensions}' does not match "
|
|
229
|
-
f"dimensions of given input array: {input_array}."
|
|
230
|
-
)
|
|
231
|
-
|
|
232
|
-
|
|
233
42
|
class FeatureSignature(NamedTuple):
|
|
234
43
|
"""Signature of feature arrays.
|
|
235
44
|
|
rasa/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: rasa-pro
|
|
3
|
-
Version: 3.8.
|
|
3
|
+
Version: 3.8.18
|
|
4
4
|
Summary: State-of-the-art open-core Conversational AI framework for Enterprises that natively leverages generative AI for effortless assistant development.
|
|
5
5
|
Home-page: https://rasa.com
|
|
6
6
|
Keywords: nlp,machine-learning,machine-learning-library,bot,bots,botkit,rasa conversational-agents,conversational-ai,chatbot,chatbot-framework,bot-framework
|
|
@@ -33,7 +33,6 @@ Requires-Dist: attrs (>=23.1,<23.2)
|
|
|
33
33
|
Requires-Dist: azure-storage-blob (>=12.16.0,<12.17.0)
|
|
34
34
|
Requires-Dist: boto3 (>=1.27.1,<2.0.0)
|
|
35
35
|
Requires-Dist: certifi (>=2023.7.22)
|
|
36
|
-
Requires-Dist: cloudpickle (>=2.2.1,<3.1)
|
|
37
36
|
Requires-Dist: colorama (>=0.4.6,<0.5.0) ; sys_platform == "win32"
|
|
38
37
|
Requires-Dist: colorclass (>=2.2,<2.3)
|
|
39
38
|
Requires-Dist: coloredlogs (>=15,<16)
|
|
@@ -54,7 +53,6 @@ Requires-Dist: importlib-metadata (>=6.8.0,<7.0.0)
|
|
|
54
53
|
Requires-Dist: importlib-resources (>=6.1.1,<7.0.0)
|
|
55
54
|
Requires-Dist: jieba (>=0.42.1,<0.43) ; extra == "jieba" or extra == "full"
|
|
56
55
|
Requires-Dist: jinja2 (>=3.1.4,<4.0.0)
|
|
57
|
-
Requires-Dist: joblib (>=1.2.0,<1.3.0)
|
|
58
56
|
Requires-Dist: jsonpatch (>=1.33,<2.0)
|
|
59
57
|
Requires-Dist: jsonpickle (>=3.0,<3.1)
|
|
60
58
|
Requires-Dist: jsonschema (>=4.20,<4.21)
|
|
@@ -103,6 +101,7 @@ Requires-Dist: requests (>=2.31.0,<2.32.0)
|
|
|
103
101
|
Requires-Dist: rich (>=13.4.2,<14.0.0)
|
|
104
102
|
Requires-Dist: rocketchat_API (>=1.30.0,<1.31.0)
|
|
105
103
|
Requires-Dist: ruamel.yaml (>=0.17.21,<0.17.22)
|
|
104
|
+
Requires-Dist: safetensors (>=0.4.5,<0.5.0)
|
|
106
105
|
Requires-Dist: sanic (>=21.12.2,<21.13.0)
|
|
107
106
|
Requires-Dist: sanic-cors (>=2.0.1,<2.1.0)
|
|
108
107
|
Requires-Dist: sanic-jwt (>=1.8.0,<2.0.0)
|
|
@@ -113,6 +112,7 @@ Requires-Dist: sentencepiece[sentencepiece] (>=0.1.99,<0.2.0) ; extra == "transf
|
|
|
113
112
|
Requires-Dist: sentry-sdk (>=1.14.0,<1.15.0)
|
|
114
113
|
Requires-Dist: setuptools (>=70.0.0,<70.1.0)
|
|
115
114
|
Requires-Dist: sklearn-crfsuite (>=0.3.6,<0.4.0)
|
|
115
|
+
Requires-Dist: skops (>=0.10.0,<0.11.0)
|
|
116
116
|
Requires-Dist: slack-sdk (>=3.21.3,<4.0.0)
|
|
117
117
|
Requires-Dist: spacy (>=3.5.4,<4.0.0) ; extra == "spacy" or extra == "full"
|
|
118
118
|
Requires-Dist: structlog (>=23.1.0,<23.2.0)
|
|
@@ -383,6 +383,39 @@ To check the types execute
|
|
|
383
383
|
make types
|
|
384
384
|
```
|
|
385
385
|
|
|
386
|
+
### Backporting
|
|
387
|
+
|
|
388
|
+
In order to port changes to `main` and across release branches, we use the `backport` workflow located at
|
|
389
|
+
the `.github/workflows/backport.yml` path.
|
|
390
|
+
This workflow is triggered by the `backport-to-<release-branch>` label applied to a PR, for example `backport-to-3.8.x`.
|
|
391
|
+
Current available target branches are `main` and maintained release branches.
|
|
392
|
+
|
|
393
|
+
When a PR gets labelled `backport-to-<release-branch>`, a PR is opened by the `backport-github-action` as soon as the
|
|
394
|
+
source PR gets closed (by merging). If you want to close the PR without merging changes, make sure to remove the `backport-to-<release-branch>` label.
|
|
395
|
+
|
|
396
|
+
The PR author which the action assigns to the backporting PR has to resolve any conflicts before approving and merging.
|
|
397
|
+
Release PRs should also be labelled with `backport-to-main` to backport the `CHANGELOG.md` updates to `main`.
|
|
398
|
+
Backporting version updates should be accepted to the `main` branch from the latest release branch only.
|
|
399
|
+
|
|
400
|
+
Here are some guidelines to follow when backporting changes and resolving conflicts:
|
|
401
|
+
|
|
402
|
+
a) for conflicts in `version.py`: accept only the version from the latest release branch. Do not merge version changes
|
|
403
|
+
from earlier release branches into `main` because this could cause issues when trying to make the next minor release.
|
|
404
|
+
|
|
405
|
+
b) for conflicts in `pyproject.toml`: if related to the `rasa-pro` version, accept only the latest release branch;
|
|
406
|
+
if related to other dependencies, accept `main` or whichever is the higher upgrade (main usually has the updated
|
|
407
|
+
dependencies because we only do housekeeping on `main`, apart from vulnerability updates). Be mindful of dependencies that
|
|
408
|
+
are removed from `main` but still exist in former release branches (for example `langchain`).
|
|
409
|
+
|
|
410
|
+
c) for conflicts in `poetry.lock`: accept changes which were already present on the target branch, then run
|
|
411
|
+
`poetry lock --no-update` so that the lock file contains your changes from `pyproject.toml` too.
|
|
412
|
+
|
|
413
|
+
d) for conflicts in `CHANGELOG.md`: Manually place the changelog in their allocated section (e.g. 3.8.10 will go under the
|
|
414
|
+
3.8 section with the other releases, rather than go at the top of the file)
|
|
415
|
+
|
|
416
|
+
If the backporting workflow fails, you are encouraged to cherry-pick the commits manually and create a PR to
|
|
417
|
+
the target branch. Alternatively, you can install the backporting CLI tool as described [here](https://github.com/sorenlouv/backport?tab=readme-ov-file#install).
|
|
418
|
+
|
|
386
419
|
## Releases
|
|
387
420
|
Rasa has implemented robust policies governing version naming, as well as release pace for major, minor, and patch releases.
|
|
388
421
|
|
|
@@ -465,9 +498,12 @@ Releasing a new version is quite simple, as the packages are build and distribut
|
|
|
465
498
|
9. If however an error occurs in the build, then we should see a failure message automatically posted in the company's Slack (`dev-tribe` channel) like this [one](https://rasa-hq.slack.com/archives/C01M5TAHDHA/p1701444735622919)
|
|
466
499
|
(In this case do the following checks):
|
|
467
500
|
- Check the workflows in [Github Actions](https://github.com/RasaHQ/rasa-private/actions) and make sure that the merged PR of the current release is completed successfully. To easily find your PR you can use the filters `event: push` and `branch: <version number>` (example on release 2.4 you can see [here](https://github.com/RasaHQ/rasa/actions/runs/643344876))
|
|
468
|
-
- If the workflow is not completed, then try to re
|
|
501
|
+
- If the workflow is not completed, then try to re-run the workflow in case that solves the problem
|
|
469
502
|
- If the problem persists, check also the log files and try to find the root cause of the issue
|
|
470
503
|
- If you still cannot resolve the error, contact the infrastructure team by providing any helpful information from your investigation
|
|
504
|
+
10. If the release is successful, add the newly created release branch to the backporting configuration in the `.backportrc.json` file to
|
|
505
|
+
the `targetBranchesChoices` list. This is necessary for the backporting workflow to work correctly with new release branches.
|
|
506
|
+
|
|
471
507
|
|
|
472
508
|
### Cutting a Patch release
|
|
473
509
|
|