示例#1
0
    def handle_destroy(self, speaker, d) -> Tuple[Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and perception and handles a 'destroy' command by either pushing a dialogue object
        or pushing a Destroy task to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        default_ref_d = {"filters": {"location": SPEAKERLOOK}}
        ref_d = d.get("reference_object", default_ref_d)
        objs = self.subinterpret["reference_objects"](
            self, speaker, ref_d, extra_tags=["_destructible"]
        )
        if len(objs) == 0:
            raise ErrorWithResponse("I don't understand what you want me to destroy.")

        # don't kill mobs
        if all(isinstance(obj, MobNode) for obj in objs):
            raise ErrorWithResponse("I don't kill animals, sorry!")
        if all(isinstance(obj, PlayerNode) for obj in objs):
            raise ErrorWithResponse("I don't kill players, sorry!")
        objs = [obj for obj in objs if not isinstance(obj, MobNode)]
        num_destroy_tasks = 0
        for obj in objs:
            if hasattr(obj, "blocks"):
                schematic = list(obj.blocks.items())
                task_data = {"schematic": schematic, "action_dict": d}
                self.append_new_task(self.task_objects["destroy"], task_data)
                num_destroy_tasks += 1
        logging.info("Added {} Destroy tasks to stack".format(num_destroy_tasks))
        self.finished = True
        return None, None
示例#2
0
    def handle_drop(self, speaker, d) -> Tuple[Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and perception and handles a 'drop' command by either pushing a dialogue object
        or pushing a Drop task to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        ref_d = d.get("reference_object", None)
        if not ref_d:
            raise ErrorWithResponse("I don't understand what you want me to drop.")

        objs = self.subinterpret["reference_objects"](
            self, speaker, ref_d, extra_tags=["_in_inventory"]
        )
        if len(objs) == 0:
            raise ErrorWithResponse("I don't understand what you want me to drop.")

        obj = [obj for obj in objs if isinstance(obj, ItemStackNode)][0]
        item_stack = self.agent.get_item_stack(obj.eid)
        idm = (item_stack.item.id, item_stack.item.meta)
        task_data = {"eid": obj.eid, "idm": idm, "memid": obj.memid}
        self.append_new_task(self.task_objects["drop"], task_data)

        self.finished = True
        return None, None
示例#3
0
    def handle_spawn(self, speaker, d) -> Tuple[Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and handles a 'spawn' command by either replying back or 
        pushing a Spawn task to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        # FIXME! use filters appropriately, don't search by hand
        spawn_triples = d.get("reference_object", {}).get("filters", {}).get("triples", [])
        if not spawn_triples:
            raise ErrorWithResponse("I don't understand what you want me to spawn.")
        names = [t.get("obj_text") for t in spawn_triples if t.get("pred_text", "") == "has_name"]
        if not any(names):
            raise ErrorWithResponse("I don't understand what you want me to spawn.")
        # if multiple possible has_name triples, just pick the first:
        object_name = names[0]
        schematic = self.memory.get_mob_schematic_by_name(object_name)
        if not schematic:
            raise ErrorWithResponse("I don't know how to spawn: %r." % (object_name))

        object_idm = list(schematic.blocks.values())[0]
        location_d = d.get("location", SPEAKERLOOK)
        mems = self.subinterpret["reference_locations"](self, speaker, location_d)
        steps, reldir = interpret_relative_direction(self, location_d)
        pos, _ = self.subinterpret["specify_locations"](self, speaker, mems, steps, reldir)
        repeat_times = get_repeat_num(d)
        for i in range(repeat_times):
            task_data = {"object_idm": object_idm, "pos": pos, "action_dict": d}
            self.append_new_task(self.task_objects["spawn"], task_data)
        self.finished = True
        return None, None
示例#4
0
    def handle_get(self, speaker, d) -> Tuple[Any, Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and perception and handles a 'get' command by either pushing a dialogue object
        or pushing a Get task to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        ref_d = d.get("reference_object", None)
        if not ref_d:
            raise ErrorWithResponse(
                "I don't understand what you want me to get.")

        objs = self.subinterpret["reference_objects"](
            self, speaker, ref_d, extra_tags=["_on_ground"])
        if len(objs) == 0:
            raise ErrorWithResponse(
                "I don't understand what you want me to get.")
        obj = [obj for obj in objs if isinstance(obj, ItemStackNode)][0]
        item_stack = self.agent.get_item_stack(obj.eid)
        idm = (item_stack.item.id, item_stack.item.meta)
        task_data = {
            "idm": idm,
            "pos": obj.pos,
            "eid": obj.eid,
            "memid": obj.memid
        }
        return self.task_objects["get"](self.agent, task_data), None, None
示例#5
0
    def handle_modify(self, speaker, d) -> Tuple[Any, Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and handles a 'modify' command by either replying back or pushing 
        appropriate tasks to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        default_ref_d = {"filters": {"location": SPEAKERLOOK}}
        ref_d = d.get("reference_object", default_ref_d)
        # only modify blockobjects...
        objs = self.subinterpret["reference_objects"](
            self,
            speaker,
            ref_d,
            extra_tags=["_physical_object", "VOXEL_OBJECT"])
        if len(objs) == 0:
            raise ErrorWithResponse(
                "I don't understand what you want me to modify.")

        m_d = d.get("modify_dict")
        if not m_d:
            raise ErrorWithResponse(
                "I think you want me to modify an object but am not sure what to do"
            )
        tasks = []
        for obj in objs:
            if m_d["modify_type"] == "THINNER" or m_d[
                    "modify_type"] == "THICKER":
                destroy_task_data, build_task_data = handle_thicken(
                    self, speaker, m_d, obj)
            elif m_d["modify_type"] == "REPLACE":
                destroy_task_data, build_task_data = handle_replace(
                    self, speaker, m_d, obj)
            elif m_d["modify_type"] == "SCALE":
                destroy_task_data, build_task_data = handle_scale(
                    self, speaker, m_d, obj)
            elif m_d["modify_type"] == "RIGIDMOTION":
                destroy_task_data, build_task_data = handle_rigidmotion(
                    self, speaker, m_d, obj)
            elif m_d["modify_type"] == "FILL" or m_d["modify_type"] == "HOLLOW":
                destroy_task_data, build_task_data = handle_fill(
                    self, speaker, m_d, obj)
            else:
                raise ErrorWithResponse(
                    "I think you want me to modify an object but am not sure what to do (parse error)"
                )

            if build_task_data:
                tasks.append(self.task_objects["build"](self.agent,
                                                        build_task_data))

            if destroy_task_data:
                tasks.append(self.task_objects["build"](self.agent,
                                                        destroy_task_data))

        return maybe_task_list_to_control_block(tasks, self.agent), None, None
示例#6
0
def interpret_named_schematic(
    interpreter, speaker, d
) -> Tuple[List[Block], Optional[str], List[Tuple[str, str]]]:
    """Return a tuple of 3 values:
    - the schematic blocks, list[(xyz, idm)]
    - a SchematicNode memid, or None
    - a list of (pred, val) tags

    warning:  if multiple possibilities are given for the same tag, current
    heursitic just picks one.  e.g. if the lf is 
        "triples" : [{"pred_text": "has_colour", "obj_text": "red"}, 
                     {"pred_text": "has_colour", "obj_text": "blue"}]
    will currently just pick red.   Same for other properties encoded in triples
    """
    # FIXME! this is not compositional, and is not using full FILTERS handlers
    filters_d = d.get("filters", {})
    triples = filters_d.get("triples", [])
    names = get_properties_from_triples(triples, "has_name")
    if not any(names):
        raise ErrorWithResponse("I don't know what you want me to build.")
    name = names[0]
    stemmed_name = name.strip("s")  # why aren't we using stemmer anymore?
    shapename = SPECIAL_SHAPES_CANONICALIZE.get(name) or SPECIAL_SHAPES_CANONICALIZE.get(
        stemmed_name
    )
    if shapename:
        shape_blocks, tags = interpret_shape_schematic(
            interpreter, speaker, d, shapename=shapename
        )
        return shape_blocks, None, tags

    schematic = interpreter.memory.get_schematic_by_name(name)
    if schematic is None:
        schematic = interpreter.memory.get_schematic_by_name(stemmed_name)
        if schematic is None:
            raise ErrorWithResponse("I don't know what you want me to build.")
    tags = [(p, v) for (_, p, v) in interpreter.memory.get_triples(subj=schematic.memid)]
    blocks = schematic.blocks
    # TODO generalize to more general block properties
    # Longer term: remove and put a call to the modify model here
    colours = get_properties_from_triples(triples, "has_colour")
    if any(colours):
        colour = colours[0]
        old_idm = most_common_idm(blocks.values())
        c = block_data.COLOR_BID_MAP.get(colour)
        if c is not None:
            new_idm = random.choice(c)
            for l in blocks:
                if blocks[l] == old_idm:
                    blocks[l] = new_idm
    return list(blocks.items()), schematic.memid, tags
示例#7
0
def get_special_reference_object(interpreter,
                                 speaker,
                                 S,
                                 agent_memory=None,
                                 eid=None):
    """ subinterpret a special reference object.  
    args:
    interpreter:  the root interpreter
    speaker (str): The name of the player/human/agent who uttered 
        the chat resulting in this interpreter
    S:  the special reference object logical form from
    """

    # TODO/FIXME! add things to workspace memory
    agent_memory = agent_memory or interpreter.agent.memory
    if not eid:
        eid = get_eid_from_special(agent_memory, S, speaker=speaker)
    sd = special_reference_search_data(None,
                                       speaker,
                                       S,
                                       entity_id=eid,
                                       agent_memory=agent_memory)
    mems = agent_memory.basic_search(sd)
    if not mems:
        # need a better interface for this, don't need to run full perception
        # just to force speakerlook in memory
        # TODO force if look is stale, not just if it doesn't exist
        # this branch shouldn't occur
        # interpreter.agent.perceive(force=True)
        raise ErrorWithResponse(
            "I think you are pointing at something but I don't know what it is"
        )
    return mems[0]
示例#8
0
 def __call__(self, interpreter, speaker, d):
     # get these from memory, not player struct!!!!! FIXME!!!
     current_pitch = interpreter.agent.get_player().look.pitch
     current_yaw = interpreter.agent.get_player().look.yaw
     if d.get("yaw_pitch"):
         span = d["yaw_pitch"]
         # for now assumed in (yaw, pitch) or yaw, pitch or yaw pitch formats
         yp = span.replace("(", "").replace(")", "").split()
         return {"head_yaw_pitch": (int(yp[0]), int(yp[1]))}
     elif d.get("yaw"):
         # for now assumed span is yaw as word or number
         w = d["yaw"].strip(" degrees").strip(" degree")
         return {"head_yaw_pitch": (word_to_num(w), current_pitch)}
     elif d.get("pitch"):
         # for now assumed span is pitch as word or number
         w = d["pitch"].strip(" degrees").strip(" degree")
         return {"head_yaw_pitch": (current_yaw, word_to_num(w))}
     elif d.get("relative_yaw"):
         # TODO in the task use turn angle
         if "left" in d["relative_yaw"] or "right" in d["relative_yaw"]:
             left = "left" in span or "leave" in span  # lemmatizer :)
             degrees = number_from_span(span) or 90
             if degrees > 0 and left:
                 return {"relative_yaw": -degrees}
             else:
                 return {"relative_yaw": degrees}
         else:
             try:
                 deg = int(d["relative_yaw"])
                 return {"relative_yaw": deg}
             except:
                 pass
     elif d.get("relative_pitch"):
         if "down" in d["relative_pitch"] or "up" in d["relative_pitch"]:
             down = "down" in d["relative_pitch"]
             degrees = number_from_span(d["relative_pitch"]) or 90
             if degrees > 0 and down:
                 return {"relative_pitch": -degrees}
             else:
                 return {"relative_pitch": degrees}
         else:
             # TODO in the task make this relative!
             try:
                 deg = int(d["relative_pitch"]["angle"])
                 return {"relative_pitch": deg}
             except:
                 pass
     elif d.get("location"):
         mems = interpreter.subinterpret["reference_locations"](
             interpreter, speaker, d["location"])
         steps, reldir = interpret_relative_direction(
             interpreter, d["location"])
         loc, _ = interpreter.subinterpret["specify_locations"](interpreter,
                                                                speaker,
                                                                mems, steps,
                                                                reldir)
         return {"head_xyz": loc}
     else:
         raise ErrorWithResponse("I am not sure where you want me to turn")
示例#9
0
 def handle_task_refobj_string(self, task, refobj_attr):
     if refobj_attr == "name":
         for pred, val in task.task.target:
             if pred == "has_name":
                 return "I am going to the " + prepend_a_an(val), None
     elif refobj_attr == "location":
         target = tuple(task.task.target)
         return "I am going to {}".format(target), None
     else:
         raise ErrorWithResponse(
             "trying get attribute {} from action".format(refobj_attr))
示例#10
0
 def __call__(self, interpreter, speaker, d) -> POINT_AT_TARGET:
     if d.get("location") is None:
         # TODO other facings
         raise ErrorWithResponse("I am not sure where you want me to point")
     # TODO: We might want to specifically check for BETWEEN/INSIDE, I'm not sure
     mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                            speaker,
                                                            d["location"])
     steps, reldir = interpret_relative_direction(interpreter.agent, d)
     loc, _ = interpreter.subinterpret["specify_locations"](interpreter,
                                                            speaker, mems,
                                                            steps, reldir)
     return self.point_to_region(loc)
示例#11
0
 def handle_task_refobj_string(self, task, refobj_attr):
     if refobj_attr == "name":
         assert isinstance(task.task, Build), task.task
         for pred, val in task.task.schematic_tags:
             if pred == "has_name":
                 return "I am building " + prepend_a_an(val), None
             return "I am building something that is {}".format(val), None
     elif refobj_attr == "location":
         assert task.action_name == "Move", task.action_name
         target = tuple(task.task.target)
         return "I am going to {}".format(target), None
     else:
         raise ErrorWithResponse("trying get attribute {} from action".format(refobj_attr))
     return None, None
示例#12
0
    def handle_get(self, speaker, d) -> Tuple[Optional[str], Any]:
        default_ref_d = {"filters": {"location": AGENTPOS}}
        ref_d = d.get("reference_object", default_ref_d)
        objs = self.subinterpret["reference_objects"](
            self, speaker, ref_d, extra_tags=["_physical_object"])
        if len(objs) == 0:
            raise ErrorWithResponse("I don't know what you want me to get.")

        if all(isinstance(obj, PlayerNode) for obj in objs):
            raise ErrorWithResponse("I can't get a person, sorry!")
        objs = [obj for obj in objs if not isinstance(obj, PlayerNode)]

        if d.get("receiver") is None:
            receiver_d = None
        else:
            receiver_d = d.get("receiver").get("reference_object")
        receiver = None
        if receiver_d:
            receiver = self.subinterpret["reference_objects"](self, speaker,
                                                              receiver_d)
            if len(receiver) == 0:
                raise ErrorWithResponse(
                    "I don't know where you want me to take it")
            receiver = receiver[0].memid

        num_get_tasks = 0
        for obj in objs:
            task_data = {
                "get_target": obj.memid,
                "give_target": receiver,
                "action_dict": d
            }
            self.append_new_task(self.task_objects["get"], task_data)
            num_get_tasks += 1
        #        logging.info("Added {} Get tasks to stack".format(num_get_tasks))
        self.finished = True
        return None, None
示例#13
0
    def step(self) -> Tuple[Optional[str], Any]:
        assert self.action_dict["dialogue_type"] == "HUMAN_GIVE_COMMAND"
        try:
            actions = []
            if "action" in self.action_dict:
                actions.append(self.action_dict["action"])
            elif "action_sequence" in self.action_dict:
                actions = self.action_dict["action_sequence"]

            if len(actions) == 0:
                # The action dict is in an unexpected state
                raise ErrorWithResponse(
                    "I thought you wanted me to do something, but now I don't know what"
                )
            tasks_to_push = []
            for action_def in actions:
                action_type = action_def["action_type"]
                r = self.action_handlers[action_type](self.speaker, action_def)
                if len(r) == 3:
                    task, response, dialogue_data = r
                else:
                    # FIXME don't use this branch, uniformize the signatures
                    task = None
                    response, dialogue_data = r
                if task:
                    tasks_to_push.append(task)
            task_mem = None
            if tasks_to_push:
                T = maybe_task_list_to_control_block(tasks_to_push, self.agent)
                task_mem = TaskNode(self.agent.memory, tasks_to_push[0].memid)
            if task_mem:
                chat = self.agent.memory.get_most_recent_incoming_chat()
                TripleNode.create(
                    self.agent.memory,
                    subj=chat.memid,
                    pred_text="chat_effect_",
                    obj=task_mem.memid,
                )
            self.finished = True
            return response, dialogue_data
        except NextDialogueStep:
            return None, None
        except ErrorWithResponse as err:
            self.finished = True
            return err.chat, None
示例#14
0
 def new_tasks():
     # TODO if we do this better will be able to handle "stay between the x"
     default_loc = getattr(self, "default_loc", SPEAKERLOOK)
     location_d = d.get("location", default_loc)
     if self.loop_data and hasattr(self.loop_data, "get_pos"):
         mems = [self.loop_data]
     else:
         mems = self.subinterpret["reference_locations"](self, speaker, location_d)
     # FIXME this should go in the ref_location subinterpret:
     steps, reldir = interpret_relative_direction(self, location_d)
     pos, _ = self.subinterpret["specify_locations"](self, speaker, mems, steps, reldir)
     # TODO: can this actually happen?
     if pos is None:
         raise ErrorWithResponse("I don't understand where you want me to move.")
     pos = self.post_process_loc(pos, self)
     task_data = {"target": pos, "action_dict": d}
     task = Move(self.agent, task_data)
     return [task]
示例#15
0
    def handle_triple(self) -> Tuple[Optional[str], Any]:
        """Writes a triple of type : (subject, predicate_text, object_text)
        to the memory and returns a confirmation.

        Returns:
            output_chat: An optional string for when the agent wants to send a chat
            step_data: Any other data that this step would like to send to the task
        """
        ref_obj_d = {"filters": self.action_dict["filters"]}
        r = self.subinterpret["reference_objects"](
            self, self.speaker_name, ref_obj_d, extra_tags=["_physical_object"]
        )
        if len(r) == 0:
            raise ErrorWithResponse("I don't know what you're referring to")
        mem = r[0]

        name = "it"
        triples = self.memory.get_triples(subj=mem.memid, pred_text="has_tag")
        if len(triples) > 0:
            name = triples[0][2].strip("_")

        schematic_memid = (
            self.memory.convert_block_object_to_schematic(mem.memid).memid
            if isinstance(mem, VoxelObjectNode)
            else None
        )

        for t in self.action_dict["upsert"]["memory_data"].get("triples", []):
            if t.get("pred_text") and t.get("obj_text"):
                logging.info("Tagging {} {} {}".format(mem.memid, t["pred_text"], t["obj_text"]))
                self.memory.add_triple(
                    subj=mem.memid, pred_text=t["pred_text"], obj_text=t["obj_text"]
                )
                if schematic_memid:
                    self.memory.add_triple(
                        subj=schematic_memid, pred_text=t["pred_text"], obj_text=t["obj_text"]
                    )
        point_at_target = mem.get_point_at_target()
        self.agent.send_chat(
            "OK I'm tagging this %r as %r %r " % (name, t["pred_text"], t["obj_text"])
        )
        self.agent.point_at(list(point_at_target))

        return "Done!", None
示例#16
0
def handle_thicken(interpreter, speaker, modify_dict, obj):
    old_blocks = list(obj.blocks.items())
    bounds = obj.get_bounds()
    mx, my, mz = (bounds[0], bounds[2], bounds[4])
    origin = [mx, my, mz]
    if modify_dict.get("modify_type") == "THICKER":
        num_blocks = modify_dict.get("num_blocks", 1)
        new_blocks = thicker(old_blocks, delta=num_blocks)
    else:
        raise ErrorWithResponse("I don't know how thin out blocks yet")

    destroy_task_data = {"schematic": old_blocks}
    # FIXME deal with tags!!!
    build_task_data = {
        "blocks_list": new_blocks,
        "origin": origin,
        #        "schematic_tags": tags,
    }
    return destroy_task_data, build_task_data
示例#17
0
    def __call__(self, interpreter, speaker, filters_d, get_all=False):
        """
        This is a subinterpreter to handle FILTERS dictionaries 

        Args:
        interpreter:  root interpreter.
        speaker (str): The name of the player/human/agent who uttered 
            the chat resulting in this interpreter
        filters_d: FILTERS logical form from semantic parser
        get_all (bool): if True, output attributes are set with get_all=True

        Outputs a (chain) of MemoryFilter objects
        """
        val_map = get_val_map(interpreter, speaker, filters_d, get_all=get_all)
        # NB (kavyasrinet) output can be string and have value "memory" too here

        # is this a specific memory?
        # ... then return
        specific_mem = maybe_handle_specific_mem(interpreter, speaker,
                                                 filters_d, val_map)
        if specific_mem is not None:
            return specific_mem

        memtype = filters_d.get("memory_type", "REFERENCE_OBJECT")
        # FIXME/TODO: these share lots of code, refactor
        if memtype == "REFERENCE_OBJECT":
            F = interpret_ref_obj_filter(interpreter, speaker, filters_d)
        elif memtype == "TASKS":
            F = interpret_task_filter(interpreter, speaker, filters_d)
        else:
            memtype_key = memtype.lower() + "_filters"
            try:
                F = interpreter.subinterpret[memtype_key](interpreter, speaker,
                                                          filters_d)
            except:
                raise ErrorWithResponse(
                    "failed at interpreting filters of type {}".format(
                        memtype))
        F = maybe_apply_selector(interpreter, speaker, filters_d, F)
        return maybe_append_left(F, to_append=val_map)
示例#18
0
    def do_answer(self, mems: Sequence[Any],
                  vals: Sequence[Any]) -> Tuple[Optional[str], Any]:
        """This function uses the action dictionary and memory state to return an answer. 

        Args:
            mems: Sequence of memories 
            vals: Sequence of values

        Returns:
            output_chat: An optional string for when the agent wants to send a chat
            step_data: Any other data that this step would like to send to the task
        """
        self.finished = True  # noqa
        output_type = self.action_dict_orig["filters"].get("output")
        try:
            if type(output_type) is str and output_type.lower() == "count":
                # FIXME will multiple count if getting tags
                if not any(vals):
                    return "none", None
                return str(vals[0]), None
            elif type(output_type) is dict and output_type.get("attribute"):
                attrib = output_type["attribute"]
                if type(attrib) is str and attrib.lower() == "location":
                    # add a Point task if attribute is a location
                    target = self.subinterpret["point_target"].point_to_region(
                        vals[0])
                    t = self.task_objects["point"](self.agent, {
                        "target": target
                    })
                    self.append_new_task(t)
                return str(vals[0]), None
            elif type(output_type) is str and output_type.lower() == "memory":
                return self.handle_exists(mems)
            else:
                raise ValueError("Bad answer_type={}".format(output_type))
        except IndexError:  # index error indicates no answer available
            logging.error("No answer available from do_answer")
            raise ErrorWithResponse("I don't understand what you're asking")
        except Exception as e:
            logging.exception(e)
示例#19
0
    def handle_undo(self, speaker, d) -> Tuple[Optional[str], Any]:
        Undo = self.task_objects["undo"]
        task_name = d.get("undo_action")
        if task_name:
            task_name = task_name.split("_")[0].strip()
        old_task = self.memory.get_last_finished_root_task(task_name)
        if old_task is None:
            raise ErrorWithResponse("Nothing to be undone ...")
        undo_tasks = [Undo(self.agent, {"memid": old_task.memid})]

        #        undo_tasks = [
        #            tasks.Undo(self.agent, {"memid": task.memid})
        #            for task in old_task.all_descendent_tasks(include_root=True)
        #        ]
        undo_command = old_task.get_chat().chat_text

        logging.debug("Pushing ConfirmTask tasks={}".format(undo_tasks))
        self.dialogue_stack.append_new(
            ConfirmTask,
            'Do you want me to undo the command: "{}" ?'.format(undo_command),
            undo_tasks,
        )
        self.finished = True
        return None, None
示例#20
0
    def step(self) -> Tuple[Optional[str], Any]:
        assert self.action_dict["dialogue_type"] == "HUMAN_GIVE_COMMAND"
        try:
            actions = []
            if "action" in self.action_dict:
                actions.append(self.action_dict["action"])
            elif "action_sequence" in self.action_dict:
                actions = self.action_dict["action_sequence"]
                actions.reverse()

            if len(actions) == 0:
                # The action dict is in an unexpected state
                raise ErrorWithResponse(
                    "I thought you wanted me to do something, but now I don't know what"
                )
            for action_def in actions:
                action_type = action_def["action_type"]
                response = self.action_handlers[action_type](self.speaker, action_def)
            return response
        except NextDialogueStep:
            return None, None
        except ErrorWithResponse as err:
            self.finished = True
            return err.chat, None
示例#21
0
def special_reference_search_data(interpreter,
                                  speaker,
                                  S,
                                  entity_id=None,
                                  agent_memory=None):
    """ make a search dictionary for a BasicMemorySearcher to return the special ReferenceObject"""
    # TODO/FIXME! add things to workspace memory
    agent_memory = agent_memory or interpreter.agent.memory
    if type(S) is dict:
        coord_span = S["coordinates_span"]
        loc = cast(
            XYZ,
            tuple(int(float(w)) for w in re.findall("[-0-9.]+", coord_span)))
        if len(loc) != 3:
            logging.error("Bad coordinates: {}".format(coord_span))
            raise ErrorWithResponse(
                "I don't understand what location you're referring to")
        memid = agent_memory.add_location(
            (int(loc[0]), int(loc[1]), int(loc[2])))
        mem = agent_memory.get_location_by_id(memid)
        f = {"special": {"DUMMY": mem}}
    else:
        f = {"special": {S: entity_id}}
    return f
示例#22
0
        def new_tasks():
            repeat = get_repeat_num(d)
            tasks_to_do = []
            # only go around the x has "around"; FIXME allow other kinds of dances
            location_d = d.get("location")
            if location_d is not None:
                rd = location_d.get("relative_direction")
                if rd is not None and (rd == "AROUND" or rd == "CLOCKWISE"
                                       or rd == "ANTICLOCKWISE"):
                    ref_obj = None
                    location_reference_object = location_d.get(
                        "reference_object")
                    if location_reference_object:
                        objmems = self.subinterpret["reference_objects"](
                            self, speaker, location_reference_object)
                        if len(objmems) == 0:
                            raise ErrorWithResponse(
                                "I don't understand where you want me to go.")
                        ref_obj = objmems[0]
                    for i in range(repeat):
                        refmove = dance.RefObjMovement(
                            self.agent,
                            ref_object=ref_obj,
                            relative_direction=location_d[
                                "relative_direction"],
                        )
                        t = self.task_objects["dance"](self.agent, {
                            "movement": refmove
                        })
                        tasks_to_do.append(t)
                    return list(reversed(tasks_to_do))

            dance_type = d.get("dance_type", {"dance_type_name": "dance"})
            if dance_type.get("point"):
                target = self.subinterpret["point_target"](self, speaker,
                                                           dance_type["point"])
                for i in range(repeat):
                    t = self.task_objects["point"](self.agent, {
                        "target": target
                    })
                    tasks_to_do.append(t)
            elif dance_type.get("look_turn") or dance_type.get("body_turn"):
                lt = dance_type.get("look_turn")
                if lt:
                    f = self.subinterpret["facing"](self,
                                                    speaker,
                                                    lt,
                                                    head_or_body="head")
                    T = self.task_objects["look"]
                else:
                    bt = dance_type.get("body_turn")
                    f = self.subinterpret["facing"](self,
                                                    speaker,
                                                    bt,
                                                    head_or_body="body")
                    T = self.task_objects["turn"]
                for i in range(repeat):
                    tasks_to_do.append(T(self.agent, f))
            elif dance_type["dance_type_name"] == "wave":
                new_task = self.task_objects["dance"](self.agent, {
                    "movement_type": "wave"
                })
                tasks_to_do.append(new_task)
            else:
                # FIXME ! merge dances, refactor.  search by name in sql
                raise ErrorWithResponse(
                    "I don't know how to do that dance yet!")
            return list(reversed(tasks_to_do))
示例#23
0
def handle_scale(interpreter, speaker, modify_dict, obj):
    old_blocks = list(obj.blocks.items())
    bounds = obj.get_bounds()
    mx, my, mz = (bounds[0], bounds[2], bounds[4])
    csf = modify_dict.get("categorical_scale_factor")
    origin = [mx, my, mz]
    if not csf:
        if modify_dict.get("numerical_scale_factor"):
            raise ErrorWithResponse("I don't know how to handle numerical_scale_factor yet")
        else:
            raise ErrorWithResponse(
                "I think I am supposed to scale something but I don't know which dimensions to scale"
            )
    destroy_task_data = {"schematic": old_blocks}
    if csf == "WIDER":
        if bounds[1] - bounds[0] > bounds[5] - bounds[4]:
            lam = (2.0, 1.0, 1.0)
        else:
            lam = (1.0, 1.0, 2.0)
        new_blocks = maybe_convert_to_list(scale(old_blocks, lam))
        destroy_task_data = None
    elif csf == "NARROWER":
        if bounds[1] - bounds[0] > bounds[5] - bounds[4]:
            lam = (0.5, 1.0, 1.0)
        else:
            lam = (1.0, 1.0, 0.5)
        new_blocks = maybe_convert_to_list(shrink_sample(old_blocks, lam))
    elif csf == "TALLER":
        lam = (1.0, 2.0, 1.0)
        new_blocks = maybe_convert_to_list(scale(old_blocks, lam))
        destroy_task_data = None
    elif csf == "SHORTER":
        lam = (1.0, 0.5, 1.0)
        new_blocks = maybe_convert_to_list(shrink_sample(old_blocks, lam))
    elif csf == "SKINNIER":
        lam = (0.5, 1.0, 0.5)
        new_blocks = maybe_convert_to_list(shrink_sample(old_blocks, lam))
    elif csf == "FATTER":
        lam = (2.0, 1.0, 2.0)
        new_blocks = maybe_convert_to_list(scale(old_blocks, lam))
        destroy_task_data = None
    elif csf == "BIGGER":
        lam = (2.0, 2.0, 2.0)
        destroy_task_data = None
        new_blocks = maybe_convert_to_list(scale(old_blocks, lam))
    elif csf == "SMALLER":
        lam = (0.5, 0.5, 0.5)
        new_blocks = maybe_convert_to_list(shrink_sample(old_blocks, lam))

    M_new = np.max([l for l, idm in new_blocks], axis=0)
    m_new = np.min([l for l, idm in new_blocks], axis=0)
    new_extent = (M_new[0] - m_new[0], M_new[1] - m_new[1], M_new[2] - m_new[2])
    old_extent = (bounds[1] - bounds[0], bounds[3] - bounds[2], bounds[5] - bounds[4])
    origin = (
        mx - (new_extent[0] - old_extent[0]) // 2,
        my,
        mz - (new_extent[2] - old_extent[2]) // 2,
    )

    # FIXME deal with tags!!!
    build_task_data = {
        "blocks_list": new_blocks,
        "origin": origin,
        #        "schematic_tags": tags,
    }
    return destroy_task_data, build_task_data
示例#24
0
        def new_tasks():
            repeat = get_repeat_num(d)
            tasks_to_do = []
            # only go around the x has "around"; FIXME allow other kinds of dances
            location_d = d.get("location")
            if location_d is not None:
                rd = location_d.get("relative_direction")
                if rd is not None and (rd == "AROUND" or rd == "CLOCKWISE"
                                       or rd == "ANTICLOCKWISE"):
                    ref_obj = None
                    location_reference_object = location_d.get(
                        "reference_object")
                    if location_reference_object:
                        objmems = self.subinterpret["reference_objects"](
                            self, speaker, location_reference_object)
                        if len(objmems) == 0:
                            raise ErrorWithResponse(
                                "I don't understand where you want me to go.")
                        ref_obj = objmems[0]
                    for i in range(repeat):
                        refmove = dance.RefObjMovement(
                            self.agent,
                            ref_object=ref_obj,
                            relative_direction=location_d[
                                "relative_direction"],
                        )
                        t = self.task_objects["dance"](self.agent, {
                            "movement": refmove
                        })
                        tasks_to_do.append(t)
                    return maybe_task_list_to_control_block(
                        tasks_to_do, self.agent)

            dance_type = d.get("dance_type", {})
            if dance_type.get("point"):
                target = self.subinterpret["point_target"](self, speaker,
                                                           dance_type["point"])
                for i in range(repeat):
                    t = self.task_objects["point"](self.agent, {
                        "target": target
                    })
                    tasks_to_do.append(t)
            # MC bot does not control body turn separate from head
            elif dance_type.get("look_turn") or dance_type.get("body_turn"):
                lt = dance_type.get("look_turn") or dance_type.get("body_turn")
                f = self.subinterpret["facing"](self, speaker, lt)
                for i in range(repeat):
                    t = self.task_objects["dancemove"](self.agent, f)
                    tasks_to_do.append(t)
            else:
                if location_d is None:
                    dance_location = None
                else:
                    mems = self.subinterpret["reference_locations"](self,
                                                                    speaker,
                                                                    location_d)
                    steps, reldir = interpret_relative_direction(
                        self, location_d)
                    dance_location, _ = self.subinterpret["specify_locations"](
                        self, speaker, mems, steps, reldir)
                filters_d = dance_type.get("filters", {})
                filters_d["memory_type"] = "DANCES"
                F = self.subinterpret["filters"](self, speaker,
                                                 dance_type.get("filters", {}))
                dance_memids, _ = F()
                # TODO correct selector in filters
                dance_memid = random.choice(dance_memids)
                dance_mem = self.memory.get_mem_by_id(dance_memid)
                for i in range(repeat):
                    dance_obj = dance.Movement(agent=self.agent,
                                               move_fn=dance_mem.dance_fn,
                                               dance_location=dance_location)
                    t = self.task_objects["dance"](self.agent, {
                        "movement": dance_obj
                    })
                    tasks_to_do.append(t)
            return maybe_task_list_to_control_block(tasks_to_do, self.agent)
示例#25
0
def filter_by_sublocation(
    interpreter,
    speaker,
    candidates: List[Tuple[XYZ, T]],
    d: Dict,
    limit=1,
    all_proximity=10,
    loose=False,
) -> List[Tuple[XYZ, T]]:
    """Select from a list of candidate (xyz, object) tuples given a sublocation

    If limit == 'ALL', return all matching candidates

    Returns a list of (xyz, mem) tuples
    """
    F = d.get("filters")
    assert F is not None, "no filters: {}".format(d)
    default_loc = getattr(interpreter, "default_loc", SPEAKERLOOK)
    location = F.get("location", default_loc)
    #    if limit == 1:
    #        limit = get_repeat_num(d)

    reldir = location.get("relative_direction")
    if reldir:
        if reldir == "INSIDE":
            # FIXME formalize this better, make extensible
            if location.get("reference_object"):
                # should probably return from interpret_reference_location...
                ref_mems = interpret_reference_object(
                    interpreter, speaker, location["reference_object"])
                for l, candidate_mem in candidates:
                    I = interpreter.agent.on_demand_perception.get[
                        "check_inside"]
                    if I:
                        if I([candidate_mem, ref_mems[0]]):
                            return [(l, candidate_mem)]
                    else:
                        raise ErrorWithResponse(
                            "I don't know how to check inside")
            raise ErrorWithResponse("I can't find something inside that")
        elif reldir == "AWAY":
            raise ErrorWithResponse("I don't know which object you mean")
        elif reldir == "NEAR":
            pass  # fall back to no reference direction
        elif reldir == "BETWEEN":
            mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                                   speaker,
                                                                   location)
            steps, reldir = interpret_relative_direction(interpreter, d)
            ref_loc, _ = interpreter.subinterpret["specify_locations"](
                interpreter, speaker, mems, steps, reldir)
            candidates.sort(key=lambda c: euclid_dist(c[0], ref_loc))
            return candidates[:limit]
        else:
            # FIXME need some tests here
            # reference object location, i.e. the "X" in "left of X"
            mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                                   speaker,
                                                                   location)

            # FIXME!!! handle frame better, might want agent's frame instead
            eid = interpreter.agent.memory.get_player_by_name(speaker).eid
            self_mem = interpreter.agent.memory.get_mem_by_id(
                interpreter.agent.memory.self_memid)
            L = LinearExtentAttribute(interpreter.agent, {
                "frame": eid,
                "relative_direction": reldir
            },
                                      mem=self_mem)
            proj = L([c[1] for c in candidates])

            # filter by relative dir, e.g. "left of Y"
            proj_cands = [(p, c) for (p, c) in zip(proj, candidates) if p > 0]

            # "the X left of Y" = the right-most X that is left of Y
            if limit == "ALL":
                limit = len(proj_cands)
            return [c for (_, c) in sorted(proj_cands, key=lambda p: p[0])
                    ][:limit]
    else:
        # no reference direction: choose the closest
        mems = interpreter.subinterpret["reference_locations"](interpreter,
                                                               speaker,
                                                               location)
        steps, reldir = interpret_relative_direction(interpreter, d)
        ref_loc, _ = interpreter.subinterpret["specify_locations"](interpreter,
                                                                   speaker,
                                                                   mems, steps,
                                                                   reldir)
        if limit == "ALL":
            return list(
                filter(lambda c: euclid_dist(c[0], ref_loc) <= all_proximity,
                       candidates))
        else:
            candidates.sort(key=lambda c: euclid_dist(c[0], ref_loc))
            return candidates[:limit]
    return []  # this fixes flake but seems awful?
示例#26
0
def safe_call(f, *args):
    try:
        return f(*args)
    except Pyro4.errors.ConnectionClosedError as e:
        msg = "{} - {}".format(f._RemoteMethod__name, e)
        raise ErrorWithResponse(msg)
示例#27
0
        def new_tasks():
            repeat = get_repeat_num(d)
            tasks_to_do = []
            # only go around the x has "around"; FIXME allow other kinds of dances
            location_d = d.get("location")
            if location_d is not None:
                rd = location_d.get("relative_direction")
                if rd is not None and (
                    rd == "AROUND" or rd == "CLOCKWISE" or rd == "ANTICLOCKWISE"
                ):
                    ref_obj = None
                    location_reference_object = location_d.get("reference_object")
                    if location_reference_object:
                        objmems = self.subinterpret["reference_objects"](
                            self, speaker, location_reference_object
                        )
                        if len(objmems) == 0:
                            raise ErrorWithResponse("I don't understand where you want me to go.")
                        ref_obj = objmems[0]
                    for i in range(repeat):
                        refmove = dance.RefObjMovement(
                            self.agent,
                            ref_object=ref_obj,
                            relative_direction=location_d["relative_direction"],
                        )
                        t = self.task_objects["dance"](self.agent, {"movement": refmove})
                        tasks_to_do.append(t)
                    return list(reversed(tasks_to_do))

            dance_type = d.get("dance_type", {"dance_type_name": "dance"})
            # FIXME holdover from old dict format
            if type(dance_type) is str:
                dance_type = dance_type = {"dance_type_name": "dance"}
            if dance_type.get("point"):
                target = self.subinterpret["point_target"](self, speaker, dance_type["point"])
                for i in range(repeat):
                    t = self.task_objects["point"](self.agent, {"target": target})
                    tasks_to_do.append(t)
            # MC bot does not control body turn separate from head
            elif dance_type.get("look_turn") or dance_type.get("body_turn"):
                lt = dance_type.get("look_turn") or dance_type.get("body_turn")
                f = self.subinterpret["facing"](self, speaker, lt)
                for i in range(repeat):
                    t = self.task_objects["dancemove"](self.agent, f)
                    tasks_to_do.append(t)
            else:
                if location_d is None:
                    dance_location = None
                else:
                    mems = self.subinterpret["reference_locations"](self, speaker, location_d)
                    steps, reldir = interpret_relative_direction(self, location_d)
                    dance_location, _ = self.subinterpret["specify_locations"](
                        self, speaker, mems, steps, reldir
                    )
                # TODO use name!
                if dance_type.get("dance_type_span") is not None:
                    dance_name = dance_type["dance_type_span"]
                    if dance_name == "dance":
                        dance_name = "ornamental_dance"
                    dance_memids = self.memory._db_read(
                        "SELECT DISTINCT(Dances.uuid) FROM Dances INNER JOIN Triples on Dances.uuid=Triples.subj WHERE Triples.obj_text=?",
                        dance_name,
                    )
                else:
                    dance_memids = self.memory._db_read(
                        "SELECT DISTINCT(Dances.uuid) FROM Dances INNER JOIN Triples on Dances.uuid=Triples.subj WHERE Triples.obj_text=?",
                        "ornamental_dance",
                    )
                dance_memid = random.choice(dance_memids)[0]
                dance_fn = self.memory.dances[dance_memid]
                for i in range(repeat):
                    dance_obj = dance.Movement(
                        agent=self.agent, move_fn=dance_fn, dance_location=dance_location
                    )
                    t = self.task_objects["dance"](self.agent, {"movement": dance_obj})
                    tasks_to_do.append(t)
            return list(reversed(tasks_to_do))
示例#28
0
    def __call__(self, interpreter, speaker, d, head_or_body="head"):
        current_pitch = interpreter.agent.pitch
        if head_or_body == "head":
            current_yaw = interpreter.agent.pan
        else:
            current_yaw = interpreter.agent.base_yaw

        if d.get("yaw_pitch"):
            # make everything relative:
            span = d["yaw_pitch"]
            # for now assumed in (yaw, pitch) or yaw, pitch or yaw pitch formats
            yp = span.replace("(", "").replace(")", "").split()
            # negated in look_at in locobot_mover
            rel_yaw = current_yaw - float(yp[0])
            rel_pitch = current_pitch - float(yp[1])
            return {"yaw": rel_yaw, "pitch": rel_pitch}
        elif d.get("yaw"):
            # make everything relative:
            # for now assumed span is yaw as word or number
            w = float(word_to_num(d["yaw"].strip(" degrees").strip(" degree")))
            return {"yaw": current_yaw - w}
        elif d.get("pitch"):
            # make everything relative:
            # for now assumed span is pitch as word or number
            w = float(
                word_to_num(d["pitch"].strip(" degrees").strip(" degree")))
            return {"yaw": current_pitch - w}
        elif d.get("relative_yaw"):
            if d["relative_yaw"].get("angle"):
                return {"yaw": float(d["relative_yaw"]["angle"])}
            elif d["relative_yaw"].get("yaw_span"):
                span = d["relative_yaw"].get("yaw_span")
                left = "left" in span or "leave" in span  # lemmatizer :)
                degrees = number_from_span(span) or 90
                if degrees > 0 and left:
                    print(degrees)
                    return {"yaw": degrees}
                else:
                    print(-degrees)
                    return {"yaw": -degrees}
            else:
                pass
        elif d.get("relative_pitch"):
            if d["relative_pitch"].get("angle"):
                # TODO in the task make this relative!
                return {"pitch": int(d["relative_pitch"]["angle"])}
            elif d["relative_pitch"].get("pitch_span"):
                span = d["relative_pitch"].get("pitch_span")
                down = "down" in span
                degrees = number_from_span(span) or 90
                if degrees > 0 and down:
                    return {"pitch": -degrees}
                else:
                    return {"pitch": degrees}
            else:
                pass
        elif d.get("location"):
            loc_mems = interpreter.subinterpret["reference_locations"](
                interpreter, speaker, d["location"])
            loc = loc_mems[0].get_pos()
            return {"target": loc}
        else:
            raise ErrorWithResponse("I am not sure where you want me to turn")
示例#29
0
    def handle_build(self, speaker, d) -> Tuple[Optional[str], Any]:
        """This function reads the dictionary, resolves the missing details using memory
        and perception and handles a 'build' command by either pushing a dialogue object
        or pushing a Build task to the task stack. 

        Args:
            speaker: speaker_id or name.
            d: the complete action dictionary
        """
        # Get the segment to build
        if "reference_object" in d:
            # handle copy
            repeat = get_repeat_num(d)
            objs = self.subinterpret["reference_objects"](
                self,
                speaker,
                d["reference_object"],
                limit=repeat,
                extra_tags=["_voxel_object"],
                loose_speakerlook=True,
            )
            if len(objs) == 0:
                raise ErrorWithResponse("I don't understand what you want me to build")
            tagss = [
                [(p, v) for (_, p, v) in self.memory.get_triples(subj=obj.memid)] for obj in objs
            ]
            interprets = [
                [list(obj.blocks.items()), obj.memid, tags] for (obj, tags) in zip(objs, tagss)
            ]
        else:  # a schematic
            if d.get("repeat") is not None:
                repeat_dict = d
            else:
                repeat_dict = None
            interprets = interpret_schematic(
                self, speaker, d.get("schematic", {}), repeat_dict=repeat_dict
            )

        # Get the locations to build
        location_d = d.get("location", SPEAKERLOOK)
        mems = self.subinterpret["reference_locations"](self, speaker, location_d)
        steps, reldir = interpret_relative_direction(self, location_d)
        origin, offsets = self.subinterpret["specify_locations"](
            self,
            speaker,
            mems,
            steps,
            reldir,
            repeat_dir=get_repeat_dir(location_d),
            objects=interprets,
            enable_geoscorer=True,
        )
        interprets_with_offsets = [
            (blocks, mem, tags, off) for (blocks, mem, tags), off in zip(interprets, offsets)
        ]

        tasks_todo = []
        for schematic, schematic_memid, tags, offset in interprets_with_offsets:
            og = np.array(origin) + offset
            task_data = {
                "blocks_list": schematic,
                "origin": og,
                "schematic_memid": schematic_memid,
                "schematic_tags": tags,
                "action_dict": d,
            }

            tasks_todo.append(task_data)

        for task_data in reversed(tasks_todo):
            self.append_new_task(self.task_objects["build"], task_data)
        logging.info("Added {} Build tasks to stack".format(len(tasks_todo)))
        self.finished = True
        return None, None
示例#30
0
def interpret_reference_object(
    interpreter,
    speaker,
    d,
    extra_tags=[],
    limit=1,
    loose_speakerlook=False,
    allow_clarification=True,
) -> List[ReferenceObjectNode]:
    """this tries to find a ref obj memory matching the criteria from the
    ref_obj_dict

    args:
    interpreter:  root interpreter.
    speaker (str): The name of the player/human/agent who uttered 
        the chat resulting in this interpreter
    d: logical form from semantic parser

    extra_tags (list of strings): tags added by parent to narrow the search
    limit (natural number): maximum number of reference objects to return
    allow_clarification (bool): should a Clarification object be put on the DialogueStack    
    """
    F = d.get("filters")
    special = d.get("special_reference")
    # F can be empty...
    assert (
        F is not None
    ) or special, "no filters or special_reference sub-dicts {}".format(d)
    if special:
        mem = get_special_reference_object(interpreter, speaker, special)
        return [mem]

    if F.get("contains_coreference", "NULL") != "NULL":
        mem = F["contains_coreference"]
        if isinstance(mem, ReferenceObjectNode):
            return [mem]
        elif mem == "resolved":
            pass
        else:
            logging.error("bad coref_resolve -> {}".format(mem))

    if len(interpreter.progeny_data) == 0:
        if any(extra_tags):
            if not F.get("triples"):
                F["triples"] = []
            for tag in extra_tags:
                F["triples"].append({"pred_text": "has_tag", "obj_text": tag})
        # TODO Add ignore_player maybe?
        candidate_mems = apply_memory_filters(interpreter, speaker, F)
        if len(candidate_mems) > 0:
            # FIXME?
            candidates = [(c.get_pos(), c) for c in candidate_mems]
            r = filter_by_sublocation(interpreter,
                                      speaker,
                                      candidates,
                                      d,
                                      limit=limit,
                                      loose=loose_speakerlook)
            return [mem for _, mem in r]
        elif allow_clarification:
            # no candidates found; ask Clarification
            # TODO: move ttad call to dialogue manager and remove this logic
            interpreter.action_dict_frozen = True
            confirm_candidates = apply_memory_filters(interpreter, speaker, F)
            objects = object_looked_at(interpreter.agent,
                                       confirm_candidates,
                                       speaker=speaker)
            if len(objects) == 0:
                raise ErrorWithResponse(
                    "I don't know what you're referring to")
            _, mem = objects[0]
            interpreter.provisional["object_mem"] = mem
            interpreter.provisional["F"] = F
            interpreter.dialogue_stack.append_new(ConfirmReferenceObject, mem)
            raise NextDialogueStep()
        else:
            raise ErrorWithResponse("I don't know what you're referring to")

    else:
        # clarification answered
        r = interpreter.progeny_data[-1].get("response")
        if r == "yes":
            # TODO: learn from the tag!  put it in memory!
            return [interpreter.provisional.get("object_mem")] * limit
        else:
            raise ErrorWithResponse("I don't know what you're referring to")