예제 #1
0
class PutMemoryTestCase(BaseCraftassistTestCase):
    def setUp(self):
        super().setUp()
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent,
            TTAD_MODELS_DIR + "/ttad.pth",
            TTAD_MODELS_DIR + "/ttad_ft_embeds.pth",
            TTAD_MODELS_DIR + "/dialogue_grammar.json",
        )

        self.cube_right = self.add_object(shapes.cube(bid=(42, 0)), (9, 63, 4))
        self.cube_left = self.add_object(shapes.cube(), (9, 63, 10))
        self.set_looking_at(list(self.cube_right.blocks.keys())[0])

    def test_come_here(self):
        chat = "come here"
        self.add_incoming_chat(chat)
        self.dialogue_manager.step((self.speaker, chat))
        self.flush()

        self.assertLessEqual(
            euclid_dist(self.agent.pos, self.get_speaker_pos()), 1)

    def test_stop(self):
        chat = "stop"
        self.add_incoming_chat(chat)
        self.dialogue_manager.step((self.speaker, chat))
        self.flush()
예제 #2
0
    def setUp(self):
        super().setUp()
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent, None, TTAD_MODEL_DIR, TTAD_BERT_DATA_DIR, None, None)

        self.cube_right = self.add_object(shapes.cube(bid=(42, 0)), (9, 63, 4))
        self.cube_left = self.add_object(shapes.cube(), (9, 63, 10))
        self.set_looking_at(list(self.cube_right.blocks.keys())[0])
예제 #3
0
    def setUp(self):
        super().setUp()
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent,
            TTAD_MODELS_DIR + "/ttad.pth",
            TTAD_MODELS_DIR + "/ttad_ft_embeds.pth",
            TTAD_MODELS_DIR + "/dialogue_grammar.json",
        )

        self.cube_right = self.add_object(shapes.cube(bid=(42, 0)), (9, 63, 4))
        self.cube_left = self.add_object(shapes.cube(), (9, 63, 10))
        self.set_looking_at(list(self.cube_right.blocks.keys())[0])
예제 #4
0
    def setUp(self):
        self.memory = AgentMemory(
            load_minecraft_specs=False)  # don't load specs, it's slow
        self.agent = FakeAgent(self.memory)
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent, None, None, None, no_ground_truth_actions=True)

        # More helpful error message to encourage test writers to use self.set_looking_at()
        self.agent.get_player_line_of_sight = Mock(
            side_effect=NotImplementedError(
                "Cannot call into C++ function in this unit test. " +
                "Call self.set_looking_at() to set the return value"))

        # Add a speaker at position (5, 63, 5) looking in the +x direction
        self.memory.update(self.agent)
        self.speaker = list(self.memory.other_players.values())[0].name
예제 #5
0
class BaseCraftassistTestCase(unittest.TestCase):
    def setUp(self):
        self.memory = AgentMemory(
            load_minecraft_specs=False)  # don't load specs, it's slow
        self.agent = FakeAgent(self.memory)
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent, None, None, None, no_ground_truth_actions=True)

        # More helpful error message to encourage test writers to use self.set_looking_at()
        self.agent.get_player_line_of_sight = Mock(
            side_effect=NotImplementedError(
                "Cannot call into C++ function in this unit test. " +
                "Call self.set_looking_at() to set the return value"))

        # Add a speaker at position (5, 63, 5) looking in the +x direction
        self.memory.update(self.agent)
        self.speaker = list(self.memory.other_players.values())[0].name

    def handle_action_dict(self,
                           d,
                           answer: str = None,
                           stop_on_chat=False,
                           max_steps=10000) -> Dict[XYZ, IDM]:
        """Handle an action dict and call self.flush()

        If "answer" is specified and a question is asked by the agent, respond
        with this string.

        If "stop_on_chat" is specified, stop iterating if the agent says anything
        """
        self.add_incoming_chat("TEST {}".format(d))
        obj = self.dialogue_manager.handle_action_dict(self.speaker, d)
        if obj is not None:
            self.dialogue_manager.dialogue_stack.append(obj)
        changes = self.flush(max_steps, stop_on_chat=stop_on_chat)
        if len(self.dialogue_manager.dialogue_stack
               ) != 0 and answer is not None:
            self.add_incoming_chat(answer)
            changes.update(self.flush(max_steps, stop_on_chat=stop_on_chat))
        return changes

    def flush(self, max_steps=10000, stop_on_chat=False) -> Dict[XYZ, IDM]:
        """Update memory and step the dialogue and task stacks until they are empty

        If "stop_on_chat" is specified, stop iterating if the agent says anything

        Return the set of blocks that were changed.
        """
        if stop_on_chat:
            self.agent.clear_outgoing_chats()

        world_before = self.agent._world.copy()

        for _ in range(max_steps):
            if (len(self.dialogue_manager.dialogue_stack) == 0
                    and not self.memory.task_stack_peek()):
                break
            self.memory.update(self.agent)
            self.dialogue_manager.dialogue_stack.step()
            self.agent.task_step()
            if (isinstance(self.dialogue_manager.dialogue_stack.peek(),
                           AwaitResponse) and
                    not self.dialogue_manager.dialogue_stack.peek().finished
                ) or (stop_on_chat and self.agent.get_last_outgoing_chat()):
                break
        self.memory.update(self.agent)

        # get changes
        world_after = self.agent._world.copy()
        changes = dict(set(world_after.items()) - set(world_before.items()))
        changes.update({
            k: (0, 0)
            for k in set(world_before.keys()) - set(world_after.keys())
        })
        return changes

    def set_looking_at(self, xyz: XYZ):
        """Set the return value for C++ call to get_player_line_of_sight"""
        self.agent.get_player_line_of_sight = Mock(return_value=Pos(*xyz))

    def set_blocks(self, xyzbms: List[Block], origin: XYZ = (0, 0, 0)):
        """Change the state of the world, block by block"""
        for xyz, idm in xyzbms:
            abs_xyz = tuple(np.array(xyz) + origin)
            self.memory.on_block_changed(abs_xyz, idm)
            self.agent._world[abs_xyz] = idm

    def add_object(self, xyzbms: List[Block],
                   origin: XYZ = (0, 0, 0)) -> ObjectNode:
        """Add an object to memory as if it was placed block by block

        Args:
        - xyzbms: a list of relative (xyz, idm)
        - origin: (x, y, z) of the corner

        Returns an ObjectNode
        """
        self.set_blocks(xyzbms, origin)
        abs_xyz = tuple(np.array(xyzbms[0][0]) + origin)
        memid = self.memory.get_block_object_ids_by_xyz(abs_xyz)[0]
        return self.memory.get_object_by_id(memid)

    def get_blocks(self, xyzs: Sequence[XYZ]) -> Dict[XYZ, IDM]:
        """Return the ground truth block state"""
        d = {}
        for (x, y, z) in xyzs:
            B = self.agent.get_blocks(x, x, y, y, z, z)
            d[(x, y, z)] = tuple(B[0, 0, 0, :])
        return d

    def add_incoming_chat(self, chat: str):
        """Add a chat to memory as if it was just spoken by SPEAKER"""
        self.memory.add_chat(
            self.memory.get_player_by_name(self.speaker).memid, chat)

    def assert_schematics_equal(self, a, b):
        """Check equality between two list[(xyz, idm)] schematics

        N.B. this compares the shapes and idms, but ignores absolute position offsets.
        """
        a, _ = to_relative_pos(a)
        b, _ = to_relative_pos(b)
        self.assertEqual(set(a), set(b))

    def last_outgoing_chat(self) -> str:
        return self.agent.get_last_outgoing_chat()

    def get_speaker_pos(self) -> XYZ:
        return tuple(
            pos_to_np(self.memory.get_player_struct_by_name(self.speaker).pos))
예제 #6
0
    def setUp(self):
        self.memory = AgentMemory(load_minecraft_specs=False)  # don't load specs, it's slow
        self.agent = FakeAgent(self.memory)
        self.dialogue_manager = TtadModelDialogueManager(
            self.agent, None, None, None, None, None, no_ground_truth_actions=True
        )

        # More helpful error message to encourage test writers to use self.set_looking_at()
        self.agent.get_player_line_of_sight = Mock(
            side_effect=NotImplementedError(
                "Cannot call into C++ function in this unit test. "
                + "Call self.set_looking_at() to set the return value"
            )
        )

        # Add a speaker at position (5, 63, 5) looking in the +x direction
        self.memory.update(self.agent)
        self.speaker = list(self.memory.other_players.values())[0].name

        # Combinable actions to be used in test cases
        self.possible_actions = {
            "destroy_speaker_look": {
                "action_type": "DESTROY",
                "reference_object": {"location": {"location_type": "SPEAKER_LOOK"}},
            },
            "copy_speaker_look_to_agent_pos": {
                "action_type": "BUILD",
                "reference_object": {"location": {"location_type": "SPEAKER_LOOK"}},
                "location": {"location_type": "AGENT_POS"},
            },
            "build_small_sphere": {
                "action_type": "BUILD",
                "schematic": {"has_name": "sphere", "has_size": "small"},
            },
            "build_1x1x1_cube": {
                "action_type": "BUILD",
                "schematic": {"has_name": "cube", "has_size": "1 x 1 x 1"},
            },
            "move_speaker_pos": {
                "action_type": "MOVE",
                "location": {"location_type": "SPEAKER_POS"},
            },
            "build_diamond": {"action_type": "BUILD", "schematic": {"has_name": "diamond"}},
            "build_gold_cube": {
                "action_type": "BUILD",
                "schematic": {"has_block_type": "gold", "has_name": "cube"},
            },
            "fill_all_holes_speaker_look": {
                "action_type": "FILL",
                "location": {"location_type": "SPEAKER_LOOK"},
                "repeat": {"repeat_key": "ALL"},
            },
            "go_to_tree": {
                "action_type": "MOVE",
                "location": {
                    "location_type": "REFERENCE_OBJECT",
                    "reference_object": {"has_name": "tree"},
                },
            },
            "build_square_height_1": {
                "action_type": "BUILD",
                "schematic": {"has_name": "square", "has_height": "1"},
            },
            "stop": {"action_type": "STOP"},
            "fill_speaker_look": {
                "action_type": "FILL",
                "location": {"location_type": "SPEAKER_LOOK"},
            },
            "fill_speaker_look_gold": {
                "action_type": "FILL",
                "has_block_type": "gold",
                "location": {"location_type": "SPEAKER_LOOK"},
            },
        }
예제 #7
0
    def __init__(
        self,
        host="localhost",
        port=DEFAULT_PORT,
        name=None,
        ttad_prev_model_path=None,
        ttad_model_dir=None,
        ttad_bert_data_dir=None,
        ttad_embeddings_path=None,
        ttad_grammar_path=None,
        semseg_model_path=None,
        voxel_model_gpu_id=-1,
        get_perception_interval=20,
        draw_fn=None,
        no_default_behavior=False,
        geoscorer_model_path=None,
    ):
        logging.info("CraftAssistAgent.__init__ started")
        self.name = name or default_agent_name()
        self.no_default_behavior = no_default_behavior

        # files needed to set up ttad model
        if ttad_prev_model_path is None:
            ttad_prev_model_path = os.path.join(os.path.dirname(__file__),
                                                "models/ttad/ttad.pth")
        if ttad_model_dir is None:
            ttad_model_dir = os.path.join(os.path.dirname(__file__),
                                          "models/ttad_bert/model/")
        if ttad_bert_data_dir is None:
            ttad_bert_data_dir = os.path.join(
                os.path.dirname(__file__), "models/ttad_bert/annotated_data/")
        if ttad_embeddings_path is None:
            ttad_embeddings_path = os.path.join(
                os.path.dirname(__file__), "models/ttad/ttad_ft_embeds.pth")
        if ttad_grammar_path is None:
            ttad_grammar_path = os.path.join(
                os.path.dirname(__file__), "models/ttad/dialogue_grammar.json")

        # set up the SubComponentClassifier model
        if semseg_model_path is not None:
            self.subcomponent_classifier = SubComponentClassifier(
                voxel_model_path=semseg_model_path)
        else:
            self.subcomponent_classifier = None

        # set up the Geoscorer model
        if geoscorer_model_path is not None:
            self.geoscorer = Geoscorer(merger_model_path=geoscorer_model_path)
        else:
            self.geoscorer = None

        self.memory = memory.AgentMemory(
            db_file=os.environ.get("DB_FILE", ":memory:"),
            db_log_path="agent_memory.{}.log".format(self.name),
        )
        logging.info("Initialized AgentMemory")

        self.dialogue_manager = TtadModelDialogueManager(
            self,
            ttad_prev_model_path,
            ttad_model_dir,
            ttad_bert_data_dir,
            ttad_embeddings_path,
            ttad_grammar_path,
        )
        logging.info("Initialized DialogueManager")

        # Log to file
        fh = logging.FileHandler("agent.{}.log".format(self.name))
        fh.setFormatter(log_formatter)
        fh.setLevel(logging.DEBUG)
        logging.getLogger().addHandler(fh)

        # Login to server
        logging.info("Attempting to connect to port {}".format(port))
        super().__init__(host, port, self.name)
        logging.info("Logged in to server")

        # Wrap C++ agent methods
        self._cpp_send_chat = self.send_chat
        self.send_chat = self._send_chat
        self.last_chat_time = 0

        self.get_perception_interval = get_perception_interval
        self.uncaught_error_count = 0
        self.last_task_memid = None
        self.point_targets = []
예제 #8
0
class CraftAssistAgent(Agent):
    def __init__(
        self,
        host="localhost",
        port=DEFAULT_PORT,
        name=None,
        ttad_prev_model_path=None,
        ttad_model_dir=None,
        ttad_bert_data_dir=None,
        ttad_embeddings_path=None,
        ttad_grammar_path=None,
        semseg_model_path=None,
        voxel_model_gpu_id=-1,
        get_perception_interval=20,
        draw_fn=None,
        no_default_behavior=False,
        geoscorer_model_path=None,
    ):
        logging.info("CraftAssistAgent.__init__ started")
        self.name = name or default_agent_name()
        self.no_default_behavior = no_default_behavior

        # files needed to set up ttad model
        if ttad_prev_model_path is None:
            ttad_prev_model_path = os.path.join(os.path.dirname(__file__),
                                                "models/ttad/ttad.pth")
        if ttad_model_dir is None:
            ttad_model_dir = os.path.join(os.path.dirname(__file__),
                                          "models/ttad_bert/model/")
        if ttad_bert_data_dir is None:
            ttad_bert_data_dir = os.path.join(
                os.path.dirname(__file__), "models/ttad_bert/annotated_data/")
        if ttad_embeddings_path is None:
            ttad_embeddings_path = os.path.join(
                os.path.dirname(__file__), "models/ttad/ttad_ft_embeds.pth")
        if ttad_grammar_path is None:
            ttad_grammar_path = os.path.join(
                os.path.dirname(__file__), "models/ttad/dialogue_grammar.json")

        # set up the SubComponentClassifier model
        if semseg_model_path is not None:
            self.subcomponent_classifier = SubComponentClassifier(
                voxel_model_path=semseg_model_path)
        else:
            self.subcomponent_classifier = None

        # set up the Geoscorer model
        if geoscorer_model_path is not None:
            self.geoscorer = Geoscorer(merger_model_path=geoscorer_model_path)
        else:
            self.geoscorer = None

        self.memory = memory.AgentMemory(
            db_file=os.environ.get("DB_FILE", ":memory:"),
            db_log_path="agent_memory.{}.log".format(self.name),
        )
        logging.info("Initialized AgentMemory")

        self.dialogue_manager = TtadModelDialogueManager(
            self,
            ttad_prev_model_path,
            ttad_model_dir,
            ttad_bert_data_dir,
            ttad_embeddings_path,
            ttad_grammar_path,
        )
        logging.info("Initialized DialogueManager")

        # Log to file
        fh = logging.FileHandler("agent.{}.log".format(self.name))
        fh.setFormatter(log_formatter)
        fh.setLevel(logging.DEBUG)
        logging.getLogger().addHandler(fh)

        # Login to server
        logging.info("Attempting to connect to port {}".format(port))
        super().__init__(host, port, self.name)
        logging.info("Logged in to server")

        # Wrap C++ agent methods
        self._cpp_send_chat = self.send_chat
        self.send_chat = self._send_chat
        self.last_chat_time = 0

        self.get_perception_interval = get_perception_interval
        self.uncaught_error_count = 0
        self.last_task_memid = None
        self.point_targets = []

    def start(self):
        logging.info("CraftAssistAgent.start() called")
        # start the subcomponent classification model
        if self.subcomponent_classifier:
            self.subcomponent_classifier.start()
        for self.count in itertools.count():  # count forever
            try:
                if self.count == 0:
                    logging.info("First top-level step()")
                self.step()

            except Exception as e:
                logging.exception(
                    "Default handler caught exception, db_log_idx={}".format(
                        self.memory.get_db_log_idx()))
                self.send_chat(
                    "Oops! I got confused and wasn't able to complete my last task :("
                )
                sentry_sdk.capture_exception(e)
                self.memory.task_stack_clear()
                self.dialogue_manager.dialogue_stack.clear()
                self.uncaught_error_count += 1
                if self.uncaught_error_count >= 100:
                    sys.exit(1)

    def step(self):
        self.pos = to_block_pos(pos_to_np(self.get_player().pos))

        # remove old point targets
        self.point_targets = [
            pt for pt in self.point_targets if time.time() - pt[1] < 6
        ]

        # Update memory with current world state
        # Removed get_perception call due to very slow updates on non-flatworlds
        with TimingWarn(2):
            self.memory.update(self)

        # Process incoming chats
        self.dialogue_step()

        # Step topmost task on stack
        self.task_step()

    def task_step(self, sleep_time=0.25):
        # Clean finished tasks
        while (self.memory.task_stack_peek()
               and self.memory.task_stack_peek().task.check_finished()):
            self.memory.task_stack_pop()

        # Maybe add default task
        if not self.no_default_behavior:
            self.maybe_run_slow_defaults()

        # If nothing to do, wait a moment
        if self.memory.task_stack_peek() is None:
            time.sleep(sleep_time)
            return

        # If something to do, step the topmost task
        task_mem = self.memory.task_stack_peek()
        if task_mem.memid != self.last_task_memid:
            logging.info("Starting task {}".format(task_mem.task))
            self.last_task_memid = task_mem.memid
        task_mem.task.step(self)
        self.memory.task_stack_update_task(task_mem.memid, task_mem.task)

    def get_time(self):
        # round to 100th of second, return as
        # n hundreth of seconds since agent init
        return self.memory.get_time()

    def get_perception(self, force=False):
        """
        Get both block objects and component objects and put them
        in memory
        """
        if not force and (self.count % self.get_perception_interval != 0
                          or self.memory.task_stack_peek() is not None):
            return

        block_objs_for_vision = []
        for obj in perception.all_nearby_objects(self.get_blocks, self.pos):
            memory.BlockObjectNode.create(self.memory, obj)
            # If any xyz of obj is has not been labeled
            if any([(not self.memory.get_component_object_ids_by_xyz(xyz))
                    for xyz, _ in obj]):
                block_objs_for_vision.append(obj)

        # TODO formalize this, make a list of all perception calls to make, etc.
        # note this directly adds the memories
        perception.get_all_nearby_holes(self, self.pos, radius=15)
        perception.get_nearby_airtouching_blocks(self, self.pos, radius=15)

        if self.subcomponent_classifier is None:
            return

        for obj in block_objs_for_vision:
            self.subcomponent_classifier.block_objs_q.put(obj)

        # everytime we try to retrieve as many recognition results as possible
        while not self.subcomponent_classifier.loc2labels_q.empty():
            loc2labels, obj = self.subcomponent_classifier.loc2labels_q.get()
            loc2ids = dict(obj)
            label2blocks = {}

            def contaminated(blocks):
                """
                Check if blocks are still consistent with the current world
                """
                mx, Mx, my, My, mz, Mz = shapes.get_bounds(blocks)
                yzxb = self.get_blocks(mx, Mx, my, My, mz, Mz)
                for b, _ in blocks:
                    x, y, z = b
                    if loc2ids[b][0] != yzxb[y - my, z - mz, x - mx, 0]:
                        return True
                return False

            for loc, labels in loc2labels.items():
                b = (loc, loc2ids[loc])
                for l in labels:
                    if l in label2blocks:
                        label2blocks[l].append(b)
                    else:
                        label2blocks[l] = [b]
            for l, blocks in label2blocks.items():
                ## if the blocks are contaminated we just ignore
                if not contaminated(blocks):
                    memory.ComponentObjectNode.create(self.memory, blocks, [l])

    def maybe_run_slow_defaults(self):
        """Pick a default task task to run
        with a low probability"""
        if self.memory.task_stack_peek() or len(
                self.dialogue_manager.dialogue_stack) > 0:
            return

        # list of (prob, default function) pairs
        visible_defaults = [
            (0.001, default_behaviors.build_random_shape),
            (0.005, default_behaviors.come_to_player),
        ]
        # default behaviors of the agent not visible in the game
        invisible_defaults = []

        defaults = (visible_defaults + invisible_defaults if time.time() -
                    self.last_chat_time > DEFAULT_BEHAVIOUR_TIMEOUT else
                    invisible_defaults)

        defaults = [(p, f) for (p, f) in defaults
                    if f not in self.memory.banned_default_behaviors]

        def noop(*args):
            pass

        defaults.append(
            (1 - sum(p
                     for p, _ in defaults), noop))  # noop with remaining prob

        # weighted random choice of functions
        p, fns = zip(*defaults)
        fn = np.random.choice(fns, p=p)
        if fn != noop:
            logging.info("Default behavior: {}".format(fn))
        fn(self)

    def dialogue_step(self):
        """Process incoming chats and modify task stack"""
        raw_incoming_chats = self.get_incoming_chats()
        if raw_incoming_chats:
            # force to get objects
            self.get_perception(force=True)
            # logging.info("Incoming chats: {}".format(raw_incoming_chats))

        incoming_chats = []
        for raw_chat in raw_incoming_chats:
            match = re.search("^<([^>]+)> (.*)", raw_chat)
            if match is None:
                logging.info("Ignoring chat: {}".format(raw_chat))
                continue

            speaker, chat = match.group(1), match.group(2)
            speaker_hash = hash_user(speaker)
            logging.info("Incoming chat: ['{}' -> {}]".format(
                speaker_hash, chat))
            if chat.startswith("/"):
                continue
            incoming_chats.append((speaker, chat))
            self.memory.add_chat(
                self.memory.get_player_by_name(speaker).memid, chat)

        if len(incoming_chats) > 0:
            # change this to memory.get_time() format?
            self.last_chat_time = time.time()
            # for now just process the first incoming chat
            self.dialogue_manager.step(incoming_chats[0])
        else:
            self.dialogue_manager.step((None, ""))

    # TODO reset all blocks in point area to what they
    # were before the point action no matter what
    # so e.g. player construction in pointing area during point
    # is reverted
    def safe_get_changed_blocks(self):
        blocks = self.get_changed_blocks()
        safe_blocks = []
        if len(self.point_targets) > 0:
            for point_target in self.point_targets:
                pt = point_target[0]
                for b in blocks:
                    x, y, z = b[0]
                    xok = x < pt[0] or x > pt[3]
                    yok = y < pt[1] or y > pt[4]
                    zok = z < pt[2] or z > pt[5]
                    if xok and yok and zok:
                        safe_blocks.append(b)
        else:
            safe_blocks = blocks
        return safe_blocks

    def point_at(self, target, sleep=None):
        """Bot pointing.

        Args:
            target: list of x1 y1 z1 x2 y2 z2, where:
                    x1 <= x2,
                    y1 <= y2,
                    z1 <= z2.
        """
        assert len(target) == 6
        self.send_chat("/point {} {} {} {} {} {}".format(*target))
        self.point_targets.append((target, time.time()))
        # sleep before the bot can take any actions
        # otherwise there might be bugs since the object is flashing
        # deal with this in the task...
        if sleep:
            time.sleep(sleep)

    def relative_head_pitch(self, angle):
        # warning: pitch is flipped!
        new_pitch = self.get_player().look.pitch - angle
        self.set_look(self.get_player().look.yaw, new_pitch)

    def _send_chat(self, chat: str):
        logging.info("Sending chat: {}".format(chat))
        self.memory.add_chat(self.memory.self_memid, chat)
        return self._cpp_send_chat(chat)