def test_intent_relations__no_relations(): @dataclass class NoRelationsIntent(Intent): pass result = intent_relations(NoRelationsIntent) assert isinstance(result, IntentRelationMap) assert result.follow == []
def _build_need_context_set(agent_cls: type(Agent)) -> Set[Intent]: """ Return a list of intents that need to spawn a context, based on their relations: * If intent *B* follows intent *A*, then intent *A* needs to spawn a context """ result = set() for intent in agent_cls.intents: related = intent_relations(intent) for rel in related.follow: result.add(rel.target_cls) return result
def test_intent_relations__follow(): @dataclass class FollowedIntent(Intent): pass @dataclass class FollowingIntent(Intent): parent_followed_intent: FollowedIntent = follow() result = intent_relations(FollowingIntent) expected = [ FollowIntentRelation( relation_parameters=FollowRelationParameters(new_lifespan=None), field_name='parent_followed_intent', source_cls=FollowingIntent, target_cls=FollowedIntent) ] assert result.follow == expected assert result.follow[0].relation_type == RelationType.FOLLOW
def _df_body_to_intent( self, df_body: PredictionBody, build_related_cls: Type[Intent] = None, visited_intents: Set[Type[Intent]] = None) -> Intent: """ Convert a Dialogflow prediction response into an instance of :class:`Intent`. This method is recursive on intent relations. When an intent has a :meth:`~intents.model.relations.follow` field, that field must be filled with an instance of the followed intent; in this case :meth:`_df_body_to_intent` will call itself passing the parent intent class as `build_related_cls`, to force building that intent from the same `df_body`; contexts and parameters will be checked for consistency. Args: df_body: A Dialogflow Response build_related_cls: Force to build the related intent instead of the predicted one visited_intents: This is used internally to prevent recursion loops """ if not visited_intents: visited_intents = set() contexts, context_parameters = df_body.contexts() # Slot filling in progress # TODO: also check queryResult.cancelsSlotFilling # if "__system_counters__" in contexts: if not df_body.queryResult.allRequiredParamsPresent: logger.warning( "Prediction doesn't have values for all required parameters. " "Slot filling may be in progress, but this is not modeled yet: " "Intent object will be None") return None if build_related_cls: # TODO: adjust lifespan intent_cls = build_related_cls df_parameters = { p_name: p.value for p_name, p in context_parameters.items() if p_name in intent_cls.parameter_schema } else: intent_name = df_body.intent_name intent_cls: Intent = self.agent_cls._intents_by_name.get( intent_name) if not intent_cls: raise ValueError( f"Prediction returned intent '{intent_name}', " + "but this was not found in Agent definition. Make sure to restore a latest " + "Agent export from `services.dialogflow_es.export.export()`. If the problem " + "persists, please file a bug on the Intents repository.") df_parameters = df_body.intent_parameters visited_intents.add(intent_cls) parameter_dict = deserialize_intent_parameters(df_parameters, intent_cls, self.entity_mappings) related_intents_dict = {} for rel in intent_relations(intent_cls).follow: if rel.target_cls in visited_intents: raise ValueError( f"Loop detected: {rel.target_cls} was already visited. Make sure " "your Agent has no circular dependencies") related_intent = self._df_body_to_intent(df_body, rel.target_cls, visited_intents) related_intent.lifespan = df_body.context_lifespans.get( df_names.context_name(rel.target_cls), 0) related_intents_dict[rel.field_name] = related_intent result = intent_cls(**parameter_dict, **related_intents_dict) result.lifespan = df_body.context_lifespans.get( df_names.context_name(intent_cls), 0) return result