"""Intent recognition and handling.""" from dataclasses import asdict, dataclass, field from typing import Any, Dict, List, Optional from .event import Event, Eventable DOMAIN = "intent" _RECOGNIZE_TYPE = "recognize" _INTENT_TYPE = "intent" _NOT_RECOGNIZED_TYPE = "not-recognized" _INTENTS_START_TYPE = "intents-start" _INTENTS_STOP_TYPE = "intents-stop" @dataclass class Entity: """Named entity with a value.""" name: str value: Optional[Any] = None @dataclass class Recognize(Eventable): """Request to recognize an event from text.""" text: str """Text with intent in natural language.""" context: Optional[Dict[str, Any]] = None """Context from previous interactions.""" @staticmethod def is_type(event_type: str) -> bool: return event_type == _RECOGNIZE_TYPE def event(self) -> Event: data: Dict[str, Any] = {"text": self.text} if self.context is not None: data["context"] = self.context return Event(type=_RECOGNIZE_TYPE, data=data) @staticmethod def from_event(event: Event) -> "Recognize": return Recognize(text=event.data["text"], context=event.data.get("context")) @dataclass class Intent(Eventable): """Result of successful intent recognition.""" name: str """Name of intent.""" entities: List[Entity] = field(default_factory=list) """Named entities with values.""" text: Optional[str] = None """Human-readable response.""" context: Optional[Dict[str, Any]] = None """Context for next interaction.""" @staticmethod def is_type(event_type: str) -> bool: return event_type == _INTENT_TYPE def event(self) -> Event: data: Dict[str, Any] = {"name": self.name} if self.entities: data["entities"] = [asdict(entity) for entity in self.entities] if self.text is not None: data["text"] = self.text if self.context is not None: data["context"] = self.context return Event(type=_INTENT_TYPE, data=data) @staticmethod def from_dict(data: Dict[str, Any]) -> "Intent": entity_dicts = data.get("entities") if entity_dicts: entities: List[Entity] = [ Entity(**entity_dict) for entity_dict in entity_dicts ] else: entities = [] return Intent( name=data["name"], entities=entities, text=data.get("text"), context=data.get("context"), ) @staticmethod def from_event(event: Event) -> "Intent": return Intent.from_dict(event.data) def to_rhasspy(self) -> Dict[str, Any]: return { "intent": { "name": self.name, }, "entities": [ {"entity": entity.name, "value": entity.value} for entity in self.entities ], "slots": {entity.name: entity.value for entity in self.entities}, } @dataclass class NotRecognized(Eventable): """Result of intent recognition failure.""" text: Optional[str] = None """Human-readable response.""" context: Optional[Dict[str, Any]] = None """Context for next interaction.""" @staticmethod def is_type(event_type: str) -> bool: return event_type == _NOT_RECOGNIZED_TYPE def event(self) -> Event: data: Dict[str, Any] = {} if self.text is not None: data["text"] = self.text if self.context is not None: data["context"] = self.context return Event(type=_NOT_RECOGNIZED_TYPE, data=data) @staticmethod def from_event(event: Event) -> "NotRecognized": return NotRecognized( text=event.data.get("text"), context=event.data.get("context") ) @dataclass class IntentsStart(Eventable): """Start of one or more intents. Event flow: intents-start intent [intent]... intents-stop """ context: Optional[Dict[str, Any]] = None """Context for next interaction.""" @staticmethod def is_type(event_type: str) -> bool: return event_type == _INTENTS_START_TYPE def event(self) -> Event: data: Dict[str, Any] = {} if self.context is not None: data["context"] = self.context return Event(type=_INTENTS_START_TYPE, data=data) @staticmethod def from_event(event: Event) -> "IntentsStart": if not event.data: return IntentsStart() return IntentsStart(context=event.data.get("context")) @dataclass class IntentsStop(Eventable): """End of intent series. Event flow: intents-start intent [intent]... intents-stop """ context: Optional[Dict[str, Any]] = None """Context for next interaction.""" @staticmethod def is_type(event_type: str) -> bool: return event_type == _INTENTS_STOP_TYPE def event(self) -> Event: data: Dict[str, Any] = {} if self.context is not None: data["context"] = self.context return Event(type=_INTENTS_STOP_TYPE, data=data) @staticmethod def from_event(event: Event) -> "IntentsStop": if not event.data: return IntentsStop() return IntentsStop(context=event.data.get("context"))