From 1047d698ae05dac9c6267d5cca4f6d3ae1624230 Mon Sep 17 00:00:00 2001 From: yuymf <1352948945@qq.com> Date: Tue, 3 Oct 2023 05:37:38 +0800 Subject: [PATCH] Fix some bugs, a runnable version remain some TODO --- .../minecraft/control_primitives/__init__.py | 24 ++++----- .../actions/minecraft/design_curriculumn.py | 19 +++---- metagpt/actions/minecraft/manage_skills.py | 45 +++++++--------- metagpt/actions/minecraft/review_task.py | 2 +- metagpt/minecraft_team.py | 51 ++++++++++++++----- metagpt/roles/minecraft/action_developer.py | 4 +- metagpt/roles/minecraft/skill_manager.py | 29 ++++++----- 7 files changed, 95 insertions(+), 79 deletions(-) diff --git a/metagpt/actions/minecraft/control_primitives/__init__.py b/metagpt/actions/minecraft/control_primitives/__init__.py index 2446d087c..f7ba48f03 100644 --- a/metagpt/actions/minecraft/control_primitives/__init__.py +++ b/metagpt/actions/minecraft/control_primitives/__init__.py @@ -1,18 +1,16 @@ -import pkg_resources import os -import voyager.utils as U +import metagpt.utils.minecraft as utils +from metagpt.logs import logger -def load_control_primitives(primitive_names=None): - package_path = pkg_resources.resource_filename("metagpt", "") - if primitive_names is None: - primitive_names = [ - primitives[:-3] - for primitives in os.listdir(f"{package_path}/actions/minecraft/control_primitives") - if primitives.endswith(".js") +def load_skills_code(skill_names=None): + skills_dir = os.path.dirname(os.path.abspath(__file__)) + if skill_names is None: + skill_names = [ + skill[:-3] for skill in os.listdir(f"{skills_dir}") if skill.endswith(".js") ] - primitives = [ - U.load_text(f"{package_path}/actions/minecraft/control_primitives/{primitive_name}.js") - for primitive_name in primitive_names + skills = [ + utils.load_text(os.path.join(skills_dir, f"{skill_name}.js")) + for skill_name in skill_names ] - return primitives + return skills diff --git a/metagpt/actions/minecraft/design_curriculumn.py b/metagpt/actions/minecraft/design_curriculumn.py index ffe3dbcc0..28299c620 100644 --- a/metagpt/actions/minecraft/design_curriculumn.py +++ b/metagpt/actions/minecraft/design_curriculumn.py @@ -100,20 +100,21 @@ class DesignCurriculum(Action): ) # TODO: change to FaissStore # self.qa_cache_questions_vectordb = FaissStore( {CKPT_DIR}/ 'curriculum/vectordb') + # TODO: + # assert self.qa_cache_questions_vectordb._collection.count() == len( + # self.qa_cache + # ), ( + # f"Curriculum Agent's qa cache question vectordb is not synced with qa_cache.json.\n" + # f"There are {self.qa_cache_questions_vectordb._collection.count()} questions in vectordb " + # f"but {len(self.qa_cache)} questions in qa_cache.json.\n" + # f"Did you set resume=False when initializing the agent?\n" + # f"You may need to manually delete the qa cache question vectordb directory for running from scratch.\n" + # ) @classmethod def set_qa_cache(cls, qa_cache): cls.qa_cache = qa_cache # Check if qa_cache right using - assert cls.qa_cache_questions_vectordb._collection.count() == len( - cls.qa_cache - ), ( - f"Curriculum Agent's qa cache question vectordb is not synced with qa_cache.json.\n" - f"There are {cls.qa_cache_questions_vectordb._collection.count()} questions in vectordb " - f"but {len(cls.qa_cache)} questions in qa_cache.json.\n" - f"Did you set resume=False when initializing the agent?\n" - f"You may need to manually delete the qa cache question vectordb directory for running from scratch.\n" - ) @classmethod def generate_qa(cls, events, chest_observation): diff --git a/metagpt/actions/minecraft/manage_skills.py b/metagpt/actions/minecraft/manage_skills.py index fcc47724c..bee726f15 100644 --- a/metagpt/actions/minecraft/manage_skills.py +++ b/metagpt/actions/minecraft/manage_skills.py @@ -23,25 +23,21 @@ class RetrieveSkills(Action): super().__init__(name, context, llm) # TODO: mv to PlayerAction self.retrieval_top_k = 5 - self.skills = {} self.vectordb = Chroma( collection_name="skill_vectordb", embedding_function=OpenAIEmbeddings(), persist_directory=f"{CKPT_DIR}/skill/vectordb", ) - - @classmethod - def set_skills(cls, skills): - cls.skills = skills # Check if skills right using - assert cls.vectordb._collection.count() == len(cls.skills), ( - f"Skill Manager's vectordb is not synced with skills.json.\n" - f"There are {cls.vectordb._collection.count()} skills in vectordb but {len(cls.skills)} skills in skills.json.\n" - f"Did you set resume=False when initializing the manager?\n" - f"You may need to manually delete the vectordb directory for running from scratch." - ) + # TODO: + # assert self.vectordb._collection.count() == len(self.skills), ( + # f"Skill Manager's vectordb is not synced with skills.json.\n" + # f"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\n" + # f"Did you set resume=False when initializing the manager?\n" + # f"You may need to manually delete the vectordb directory for running from scratch." + # ) - async def run(self, query, *args, **kwargs): + async def run(self, query, skills, *args, **kwargs): # Implement the logic for retrieving skills here. k = min(self.vectordb._collection.count(), self.retrieval_top_k) if k == 0: @@ -52,10 +48,10 @@ class RetrieveSkills(Action): f"Skill Manager retrieved skills: " f"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}" ) - skills = [] + retrieve_skills = [] for doc, _ in docs_and_scores: - skills.append(self.skills[doc.metadata["name"]]["code"]) - return skills + retrieve_skills.append(skills[doc.metadata["name"]]["code"]) + return retrieve_skills class AddNewSkills(Action): @@ -74,26 +70,23 @@ class AddNewSkills(Action): ) # TODO: change to FaissStore # self.qa_cache_questions_vectordb = FaissStore( {CKPT_DIR}/ 'skill/vectordb') - - @classmethod - def set_skills(cls, skills): - cls.skills = skills + # TODO: # Check if skills right using - assert cls.vectordb._collection.count() == len(cls.skills), ( - f"Skill Manager's vectordb is not synced with skills.json.\n" - f"There are {cls.vectordb._collection.count()} skills in vectordb but {len(cls.skills)} skills in skills.json.\n" - f"Did you set resume=False when initializing the manager?\n" - f"You may need to manually delete the vectordb directory for running from scratch." - ) + # assert self.vectordb._collection.count() == len(self.skills), ( + # f"Skill Manager's vectordb is not synced with skills.json.\n" + # f"There are {self.vectordb._collection.count()} skills in vectordb but {len(self.skills)} skills in skills.json.\n" + # f"Did you set resume=False when initializing the manager?\n" + # f"You may need to manually delete the vectordb directory for running from scratch." + # ) async def run( self, task, program_name, program_code, skills, skill_desp, *args, **kwargs ): # Implement the logic for adding new skills here. + # TODO: Fix this if task.startswith("Deposit useless items into the chest at"): # No need to reuse the deposit skill return {} - # TODO: Fix this logger.info( f"Skill Manager generated description for {program_name}:\n{skill_desp}\033[0m" ) diff --git a/metagpt/actions/minecraft/review_task.py b/metagpt/actions/minecraft/review_task.py index 6ae1b7c11..b532fb370 100644 --- a/metagpt/actions/minecraft/review_task.py +++ b/metagpt/actions/minecraft/review_task.py @@ -41,6 +41,6 @@ class VerifyTask(Action): return response["success"], response["critique"] except Exception as e: logger.error(f"Error verifying the task: {str(e)}") - return self.run(human_msg, system_msg, max_retries=max_retries-1) + return await self.run(human_msg, system_msg, max_retries=max_retries-1) diff --git a/metagpt/minecraft_team.py b/metagpt/minecraft_team.py index 4233d7fdc..5ead788ce 100644 --- a/metagpt/minecraft_team.py +++ b/metagpt/minecraft_team.py @@ -18,7 +18,7 @@ from metagpt.roles.minecraft.minecraft_base import Minecraft from metagpt.environment import Environment from metagpt.mineflayer_environment import MineflayerEnv from metagpt.const import CKPT_DIR -from metagpt.actions.minecraft.control_primitives_context import load_skills_code_context +from metagpt.actions.minecraft.control_primitives import load_skills_code class GameEnvironment(BaseModel, arbitrary_types_allowed=True): @@ -32,10 +32,11 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): context: str = Field( default="You can mine one of oak, birch, spruce, jungle, acacia, dark oak, or mangrove logs." ) - code: str = Field(default=None) + code: str = Field(default="") program_name: str = Field(default="") - critique: str = Field(default=None) - skills: dict = Field(default_factory=dict) + critique: str = Field(default="") + skills: dict = Field(default_factory=dict) # for skills.json + retrieve_skills: list[str] = Field(default_factory=list) event_summary: str = Field(default="") qa_cache: dict[str, str] = Field(default_factory=dict) @@ -59,11 +60,13 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): @property def programs(self): programs = "" + if self.code == "": + return programs # TODO: maybe fix 10054 now, a better way is isolating env.step() like voyager for skill_name, entry in self.skills.items(): programs += f"{entry['code']}\n\n" - for primitives in load_skills_code_context(): + for primitives in load_skills_code(): programs += f"{primitives}\n\n" - return programs + return programs @property def warm_up(self): @@ -123,6 +126,9 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): def append_skill(self, skill: dict): self.skills[self.program_name] = skill # skill_manager.retrieve_skills to HERE + def update_retrieve_skills(self, retrieve_skills: list): + self.retrieve_skills = retrieve_skills + def update_skill_desp(self, skill_desp: str): self.skill_desp = skill_desp @@ -208,19 +214,36 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): return if success: logger.info(f"Completed task {task}.") - self.game_memory.completed_tasks.append(task) + self.completed_tasks.append(task) else: logger.info(f"Failed to complete task {task}. Skipping to next task.") - self.game_memory.failed_tasks.append(task) + self.failed_tasks.append(task) + # TODO: when not success, transform code below to update event!(isolate step soon!) + # if self.reset_placed_if_failed and not success: + # # revert all the placing event in the last step + # blocks = [] + # positions = [] + # for event_type, event in events: + # if event_type == "onSave" and event["onSave"].endswith("_placed"): + # block = event["onSave"].split("_placed")[0] + # position = event["status"]["position"] + # blocks.append(block) + # positions.append(position) + # new_events = self.env.step( + # f"await givePlacedItemBack(bot, {U.json_dumps(blocks)}, {U.json_dumps(positions)})", + # programs=self.skill_manager.programs, + # ) + # events[-1][1]["inventory"] = new_events[-1][1]["inventory"] + # events[-1][1]["voxels"] = new_events[-1][1]["voxels"] self.save_sorted_tasks() def save_sorted_tasks(self): updated_completed_tasks = [] # record repeated failed tasks - updated_failed_tasks = self.game_memory.failed_tasks + updated_failed_tasks = self.failed_tasks # dedup but keep order - for task in self.game_memory.completed_tasks: + for task in self.completed_tasks: if task not in updated_completed_tasks: updated_completed_tasks.append(task) @@ -229,14 +252,14 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): while task in updated_failed_tasks: updated_failed_tasks.remove(task) - self.game_memory.completed_tasks = updated_completed_tasks - self.game_memory.failed_tasks = updated_failed_tasks + self.completed_tasks = updated_completed_tasks + self.failed_tasks = updated_failed_tasks # dump to json with open(f"{CKPT_DIR}/curriculum/completed_tasks.json", "w") as f: - json.dump(self.game_memory.completed_tasks, f) + json.dump(self.completed_tasks, f) with open(f"{CKPT_DIR}/curriculum/failed_tasks.json", "w") as f: - json.dump(self.game_memory.failed_tasks, f) + json.dump(self.failed_tasks, f) async def on_event(self, *args): """ diff --git a/metagpt/roles/minecraft/action_developer.py b/metagpt/roles/minecraft/action_developer.py index 5c2a28438..9171e455b 100644 --- a/metagpt/roles/minecraft/action_developer.py +++ b/metagpt/roles/minecraft/action_developer.py @@ -210,7 +210,7 @@ class ActionDeveloper(Base): task = self.game_memory.current_task code = self.game_memory.code critique = self.game_memory.critique - skills = self.game_memory.skills + retrieve_skills = self.game_memory.retrieve_skills message = self.encapsule_message( events=events, @@ -218,7 +218,7 @@ class ActionDeveloper(Base): task=task, context=context, critique=critique, - skills=skills, + skills=retrieve_skills, ) logger.info(todo) handler_map = { diff --git a/metagpt/roles/minecraft/skill_manager.py b/metagpt/roles/minecraft/skill_manager.py index 044193f86..4dddf0ab1 100644 --- a/metagpt/roles/minecraft/skill_manager.py +++ b/metagpt/roles/minecraft/skill_manager.py @@ -42,10 +42,11 @@ class SkillManager(Base): ) return {"system_msg": [system_msg.content], "human_msg": human_msg.content} - async def retrieve_skills(self, query, *args, **kwargs): - skills = await RetrieveSkills().run(query) - logger.info(f"Render Action Agent system message with {len(skills)} skills") - return Message(content=f"{skills}", instruct_content="retrieve_skills", + async def retrieve_skills(self, query, skills, *args, **kwargs): + retrieve_skills = await RetrieveSkills().run(query, skills) + logger.info(f"Render Action Agent system message with {len(retrieve_skills)} skills") + self.perform_game_info_callback(retrieve_skills, self.game_memory.update_retrieve_skills) + return Message(content=f"{retrieve_skills}", instruct_content="retrieve_skills", role=self.profile, send_to=agent_registry.entries["action_developer"]()._setting.name) # return Message( # content=f"{skills}", instruct_content="retrieve_skills", role=self.profile @@ -84,19 +85,19 @@ class SkillManager(Base): task = self.game_memory.current_task event_summary = self.game_memory.event_summary code = self.game_memory.code - program_code = code["program_code"] + try: + program_code = code["program_code"] # TODO: Handle code is None, cuz first round DesignCurriculum(code is None) trigger this + except (KeyError, TypeError): + program_code = "" + program_name = self.game_memory.program_name skills = self.game_memory.skills - # TODO: mv to PlayerAction - RetrieveSkills.set_skills(skills) - AddNewSkills.set_skills(skills) - # msg = self._rc.memory.get(k=1)[0] - retrieve_skills_message_step1 = {"query": context} + retrieve_skills_message_step1 = {"query": context, "skills": skills} - retrieve_skills_message_step2 = {"query": context + "\n\n" + event_summary} + retrieve_skills_message_step2 = {"query": context + "\n\n" + event_summary, "skills": skills} generate_skill_message = self.encapsule_message(program_code, program_name) @@ -115,11 +116,11 @@ class SkillManager(Base): } handler = handler_map.get(type(todo)) if handler: - if type(todo) == "DesignCurriculum": + if type(todo) == DesignCurriculum: msg = await handler(**retrieve_skills_message_step1) - elif type(todo) == "RetrieveSkills": + elif type(todo) == RetrieveSkills: msg = await handler(**retrieve_skills_message_step2) - elif type(todo) == "GenerateSkillDescription": + elif type(todo) == GenerateSkillDescription: msg = await handler(**generate_skill_message) else: msg = await handler(**add_new_skills_message)