import logging
from rasa_core import utils
logger = logging.getLogger(__name__)
[docs]class Slot(object):
type_name = None
def __init__(self, name,
initial_value=None,
value_reset_delay=None,
auto_fill=True):
self.name = name
self.value = initial_value
self.initial_value = initial_value
self._value_reset_delay = value_reset_delay
self.auto_fill = auto_fill
[docs] def feature_dimensionality(self):
"""How many features this single slot creates.
The dimensionality of the array returned by `as_feature` needs
to correspond to this value."""
return 1
def has_features(self):
"""Indicate if the slot creates any features."""
return self.feature_dimensionality() != 0
def value_reset_delay(self):
"""After how many turns the slot should be reset to the initial_value.
If the delay is set to `None`, the slot will keep its value forever."""
# TODO: FUTURE this needs to be implemented - slots are not reset yet
return self._value_reset_delay
[docs] def as_feature(self):
raise NotImplementedError("Each slot type needs to specify how its "
"value can be converted to a feature. Slot "
"'{}' is a generic slot that can not be used "
"for predictions. Make sure you add this "
"slot to your domain definition, specifying "
"the type of the slot. If you implemented "
"a custom slot type class, make sure to "
"implement `.as_feature()`."
"".format(self.name))
def reset(self):
self.value = self.initial_value
def __str__(self):
return "{}({}: {})".format(self.__class__.__name__,
self.name,
self.value)
def __repr__(self):
return "<{}({}: {})>".format(self.__class__.__name__,
self.name,
self.value)
@staticmethod
def resolve_by_type(type_name):
"""Returns a slots class by its type name."""
for cls in utils.all_subclasses(Slot):
if cls.type_name == type_name:
return cls
try:
return utils.class_from_module_path(type_name)
except(ImportError, AttributeError):
raise ValueError(
"Failed to find slot type, '{}' is neither a known type nor "
"user-defined. If you are creating your own slot type, make "
"sure its module path is correct.".format(type_name))
def persistence_info(self):
return {"type": utils.module_path_from_instance(self),
"initial_value": self.initial_value,
"auto_fill": self.auto_fill}
class FloatSlot(Slot):
type_name = "float"
def __init__(self, name,
initial_value=None,
value_reset_delay=None,
auto_fill=True,
max_value=1.0,
min_value=0.0):
super(FloatSlot, self).__init__(name,
initial_value,
value_reset_delay,
auto_fill)
self.max_value = max_value
self.min_value = min_value
if min_value >= max_value:
raise ValueError(
"Float slot ('{}') created with an invalid range "
"using min ({}) and max ({}) values. Make sure "
"min is smaller than max."
"".format(self.name, self.min_value, self.max_value))
if (initial_value is not None and
not (min_value <= initial_value <= max_value)):
logger.warning("Float slot ('{}') created with an initial value {}"
"outside of configured min ({}) and max ({}) values."
"".format(self.name, self.value, self.min_value,
self.max_value))
def as_feature(self):
try:
capped_value = max(self.min_value,
min(self.max_value, float(self.value)))
if abs(self.max_value - self.min_value) > 0:
covered_range = abs(self.max_value - self.min_value)
else:
covered_range = 1
return [(capped_value - self.min_value) / covered_range]
except (TypeError, ValueError):
return [0.0]
def persistence_info(self):
d = super(FloatSlot, self).persistence_info()
d["max_value"] = self.max_value
d["min_value"] = self.min_value
return d
class BooleanSlot(Slot):
type_name = "bool"
def as_feature(self):
try:
if self.value is not None:
return [1.0, float(float(self.value) != 0.0)]
else:
return [0.0, 0.0]
except (TypeError, ValueError):
# we couldn't convert the value to float - using default value
return [0.0, 0.0]
def feature_dimensionality(self):
return len(self.as_feature())
class TextSlot(Slot):
type_name = "text"
def as_feature(self):
return [1.0 if self.value is not None else 0.0]
class ListSlot(Slot):
type_name = "list"
def as_feature(self):
try:
if self.value is not None and len(self.value) > 0:
return [1.0]
else:
return [0.0]
except (TypeError, ValueError):
# we couldn't convert the value to a list - using default value
return [0.0]
class UnfeaturizedSlot(Slot):
type_name = "unfeaturized"
def as_feature(self):
return []
def feature_dimensionality(self):
return 0
class CategoricalSlot(Slot):
type_name = "categorical"
def __init__(self, name,
values=None,
initial_value=None,
value_reset_delay=None,
auto_fill=True):
super(CategoricalSlot, self).__init__(name,
initial_value,
value_reset_delay,
auto_fill)
self.values = [str(v).lower() for v in values] if values else []
def persistence_info(self):
d = super(CategoricalSlot, self).persistence_info()
d["values"] = self.values
return d
def as_feature(self):
r = [0.0] * self.feature_dimensionality()
try:
for i, v in enumerate(self.values):
if v == str(self.value).lower():
r[i] = 1.0
break
else:
if self.value is not None:
logger.warning(
"Categorical slot '{}' is set to a value ('{}') "
"that is not specified in the domain. "
"Value will be ignored and the slot will "
"behave as if no value is set. "
"Make sure to add all values a categorical "
"slot should store to the domain."
"".format(self.name, self.value))
except (TypeError, ValueError):
logger.exception("Failed to featurize categorical slot.")
return r
return r
def feature_dimensionality(self):
return len(self.values)
class DataSlot(Slot):
def __init__(self, name,
initial_value=None,
value_reset_delay=1,
auto_fill=True):
super(DataSlot, self).__init__(name,
initial_value,
value_reset_delay,
auto_fill)
def as_feature(self):
raise NotImplementedError("Each slot type needs to specify how its "
"value can be converted to a feature.")