From b55a080c833993003672708a7a13b09bdfa30712 Mon Sep 17 00:00:00 2001 From: stellahsr Date: Wed, 11 Oct 2023 22:20:27 +0800 Subject: [PATCH 1/3] update code --- .../actions/minecraft/design_curriculumn.py | 25 +-- metagpt/actions/minecraft/manage_skills.py | 33 ++-- metagpt/actions/minecraft/player_action.py | 54 ------ metagpt/minecraft_team.py | 173 ++++++++++++------ metagpt/roles/minecraft/curriculum_agent.py | 3 +- metagpt/roles/minecraft/skill_manager.py | 61 +++--- 6 files changed, 182 insertions(+), 167 deletions(-) diff --git a/metagpt/actions/minecraft/design_curriculumn.py b/metagpt/actions/minecraft/design_curriculumn.py index 3e2f33705..62555f54a 100644 --- a/metagpt/actions/minecraft/design_curriculumn.py +++ b/metagpt/actions/minecraft/design_curriculumn.py @@ -92,7 +92,7 @@ class DesignCurriculum(Action): def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) - async def generate_qa(self, events, qa_cache, human_msg, system_msg): + async def generate_qa(self, events, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg): """ Generate qa for DesignTask's HumanMessage """ @@ -104,9 +104,9 @@ class DesignCurriculum(Action): questions = [] answers = [] for question in questions_new: - if self.qa_cache_questions_vectordb._collection.count() > 0: + if qa_cache_questions_vectordb._collection.count() > 0: docs_and_scores = ( - self.qa_cache_questions_vectordb.similarity_search_with_score( + qa_cache_questions_vectordb.similarity_search_with_score( question, k=1 ) ) @@ -120,12 +120,12 @@ class DesignCurriculum(Action): answer = await self.generate_qa_step2(question=question) assert question not in qa_cache qa_cache[question] = answer - self.qa_cache_questions_vectordb.add_texts( + qa_cache_questions_vectordb.add_texts( texts=[question], ) with open(f"{CKPT_DIR}/curriculum/qa_cache.json", "w") as f: json.dump(qa_cache, f) - self.qa_cache_questions_vectordb.persist() + qa_cache_questions_vectordb.persist() questions.append(question) answers.append(answer) assert len(questions_new) == len(questions) == len(answers) @@ -170,7 +170,7 @@ class DesignCurriculum(Action): # logger.info(f"Curriculum Agent generate_qa_step2 answer: {answer}") return answer - async def get_context_from_task(self, task, qa_cache): + async def get_context_from_task(self, task, qa_cache, qa_cache_questions_vectordb): """ Args: task Returns: context: "Question: {question}\n{answer}" @@ -186,16 +186,16 @@ class DesignCurriculum(Action): else: answer = await self.generate_qa_step2(question=question) qa_cache[question] = answer - self.qa_cache_questions_vectordb.add_texts( + qa_cache_questions_vectordb.add_texts( texts=[question], ) with open(f"{CKPT_DIR}/curriculum/qa_cache.json", "w") as f: json.dump(qa_cache, f) - self.qa_cache_questions_vectordb.persist() + qa_cache_questions_vectordb.persist() context = f"Question: {question}\n{answer}" return context - async def generate_context(self, task, qa_cache, max_retries=5): + async def generate_context(self, task, qa_cache, qa_cache_questions_vectordb, max_retries=5): """ Refer to the code in the voyager/agents/curriculum.py propose_next_ai_task() for implementation details. Returns: context @@ -206,7 +206,7 @@ class DesignCurriculum(Action): raise RuntimeError("Max retries reached, failed to propose context.") try: context = await self.get_context_from_task( - task=task, qa_cache=qa_cache + task=task, qa_cache=qa_cache, qa_cache_questions_vectordb=qa_cache_questions_vectordb ) # Curriculum Agent Question: How to craft 4 wooden planks in Minecraft? & Curriculum Agent Answer: ... return context except Exception as e: @@ -214,14 +214,15 @@ class DesignCurriculum(Action): return await self.generate_context( task=task, qa_cache=qa_cache, + qa_cache_questions_vectordb=qa_cache_questions_vectordb, max_retries=max_retries - 1, ) - async def run(self, task, qa_cache, human_msg, system_msg, *args, **kwargs): + async def run(self, task, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg, *args, **kwargs): logger.info(f"run {self.__repr__()}") # Generate curriculum-related questions and answers. # curriculum_qustion = await self.generate_qa_step1(events, human_msg, system_msg) - curriculum_context = await self.generate_context(task, qa_cache) + curriculum_context = await self.generate_context(task, qa_cache, qa_cache_questions_vectordb) # Return the generated questions and answers. return curriculum_context diff --git a/metagpt/actions/minecraft/manage_skills.py b/metagpt/actions/minecraft/manage_skills.py index 208bb9360..b5c51e270 100644 --- a/metagpt/actions/minecraft/manage_skills.py +++ b/metagpt/actions/minecraft/manage_skills.py @@ -5,8 +5,6 @@ import os import json -from traits.trait_types import self - from metagpt.logs import logger from metagpt.actions.minecraft.player_action import PlayerActions as Action from metagpt.const import CKPT_DIR @@ -17,18 +15,18 @@ class RetrieveSkills(Action): Action class for retrieving skills. Refer to the code in the voyager/agents/skill.py for implementation details. """ - + def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) self.llm.model = "gpt-3.5-turbo" - - async def run(self, query, skills, *args, **kwargs): + + async def run(self, query, skills, vectordb, *args, **kwargs): # Implement the logic for retrieving skills here. - k = min(self.vectordb._collection.count(), self.retrieval_top_k) + k = min(vectordb._collection.count(), self.retrieval_top_k) if k == 0: return [] logger.info(f"Skill Manager retrieving for {k} skills") - docs_and_scores = self.vectordb.similarity_search_with_score(query, k=k) + docs_and_scores = vectordb.similarity_search_with_score(query, k=k) logger.info( f"Skill Manager retrieved skills: " f"{', '.join([doc.metadata['name'] for doc, _ in docs_and_scores])}" @@ -44,13 +42,13 @@ class AddNewSkills(Action): Action class for adding new skills. Refer to the code in the voyager/agents/skill.py for implementation details. """ - + def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) self.llm.model = "gpt-3.5-turbo" - + async def run( - self, task, program_name, program_code, skills, skill_desp, *args, **kwargs + self, task, program_name, program_code, skills, skill_desp, vectordb, *args, **kwargs ): # Implement the logic for adding new skills here. # TODO: Fix this @@ -65,26 +63,27 @@ class AddNewSkills(Action): if program_name in skills: logger.info(f"Skill {program_name} already exists. Rewriting!") - self.vectordb._collection.delete(ids=[program_name]) + vectordb._collection.delete(ids=[program_name]) i = 2 while f"{program_name}V{i}.js" in os.listdir(f"{CKPT_DIR}/skill/code"): i += 1 dumped_program_name = f"{program_name}V{i}" else: dumped_program_name = program_name - self.vectordb.add_texts( + vectordb.add_texts( texts=[skill_desp], ids=[program_name], metadatas=[{"name": program_name}], ) - + logger.debug(f"ADD_CHECK: There are {vectordb._collection.count()} skills in vectordb") + with open(f"{CKPT_DIR}/skill/code/{dumped_program_name}.js", "w") as f: f.write(program_code) with open(f"{CKPT_DIR}/skill/description/{dumped_program_name}.txt", "w") as f: f.write(skill_desp) with open(f"{CKPT_DIR}/skill/skills.json", "w") as f: json.dump(skills, f) - self.vectordb.persist() + vectordb.persist() return { "code": program_code, "description": skill_desp, @@ -96,13 +95,13 @@ class GenerateSkillDescription(Action): Action class for generating skill descriptions. Refer to the code in the voyager/agents/skill.py for implementation details. """ - + def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) self.llm.model = "gpt-3.5-turbo" - + async def run(self, program_name, human_message, system_message, *args, **kwargs): # Implement the logic for generating skill descriptions here. rsp = await self._aask(prompt=human_message, system_msgs=system_message) - skill_description = f" // {rsp}" + skill_description = f" // { rsp}" return f"async function {program_name}(bot) {{\n{skill_description}\n}}" diff --git a/metagpt/actions/minecraft/player_action.py b/metagpt/actions/minecraft/player_action.py index 58f121878..677887d51 100644 --- a/metagpt/actions/minecraft/player_action.py +++ b/metagpt/actions/minecraft/player_action.py @@ -2,68 +2,14 @@ # @Date : 2023/9/23 17:06 # @Author : stellahong (stellahong@fuzhi.ai) # @Desc : -import json - from metagpt.actions import Action -from langchain.vectorstores import Chroma -from langchain.embeddings.openai import OpenAIEmbeddings -from metagpt.const import CKPT_DIR -from metagpt.config import CONFIG class PlayerActions(Action): def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) - self.skills_check = {} # for skills.json - self.qa_cache_check = {} - self.check_init = True - - if ( - CONFIG.resume - ): # TODO: now for assert only, no update, cuz program using in step() - with open(f"{CKPT_DIR}/skill/skills.json", "r") as f: - self.skills_check = json.load(f) - - with open(f"{CKPT_DIR}/curriculum/qa_cache.json", "r") as f: - self.qa_cache_check = json.load(f) - self.retrieval_top_k = 5 - self.vectordb = Chroma( - collection_name="skill_vectordb", - embedding_function=OpenAIEmbeddings(), - persist_directory=f"{CKPT_DIR}/skill/vectordb", - ) - - self.qa_cache_questions_vectordb = Chroma( - collection_name="qa_cache_questions_vectordb", - embedding_function=OpenAIEmbeddings(), - persist_directory=f"{CKPT_DIR}/curriculum/vectordb", - ) - - # FIXME - if self.check_init: - # Check if Skill Manager's vectordb right using - assert self.vectordb._collection.count() >= len(self.skills_check), ( - 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_check)} 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." - ) - # Check if Skill Manager's vectordb right using - assert self.qa_cache_questions_vectordb._collection.count() >= len( - self.qa_cache_check - ), ( - 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_check)} 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" - ) - self.check_init = False - # TODO: change to FaissStore - # self.qa_cache_questions_vectordb = FaissStore( {CKPT_DIR}/ 'curriculum/vectordb' - """Minecraft player info without any implementation details""" async def run(self, *args, **kwargs): diff --git a/metagpt/minecraft_team.py b/metagpt/minecraft_team.py index e86f6dfe8..0aac8c1cb 100644 --- a/metagpt/minecraft_team.py +++ b/metagpt/minecraft_team.py @@ -31,7 +31,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): """ 游戏环境的记忆,用于多个agent进行信息的共享和缓存,而不需要重复在自己的角色内维护缓存 """ - + event: dict[str, Any] = Field(default_factory=dict) current_task: str = Field(default="Mine 1 wood log") task_execution_time: float = Field(default=float) @@ -39,32 +39,32 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): default="You can mine one of oak, birch, spruce, jungle, acacia, dark oak, or mangrove logs." ) code: str = Field(default="") - program_code: str = Field(default="") # write in skill/code/*.js + program_code: str = Field(default="") # write in skill/code/*.js program_name: str = Field(default="") 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) completed_tasks: list[str] = Field(default_factory=list) # Critique things failed_tasks: list[str] = Field(default_factory=list) - + skill_desp: str = Field(default="") - + chest_memory: dict[str, Any] = Field( default_factory=dict ) # eg: {'(1344, 64, 1381)': 'Unknown'} chest_observation: str = Field(default="") # eg: "Chests: None\n\n" - + mf_instance: MineflayerEnv = Field(default_factory=MineflayerEnv) runtime_status: bool = False # equal to action execution status: success or failed - + @property def progress(self): # return len(self.completed_tasks) + 10 # Test only return len(self.completed_tasks) - + @property def programs(self): programs = "" @@ -75,25 +75,41 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): for primitives in load_skills_code(): programs += f"{primitives}\n\n" return programs - + @property def warm_up(self): return self.mf_instance.warm_up - + @property def core_inv_items_regex(self): return self.mf_instance.core_inv_items_regex + @property + def qa_cache_questions_vectordb(self): + return Chroma( + collection_name="qa_cache_questions_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{CKPT_DIR}/curriculum/vectordb", + ) + + @property + def vectordb(self): + return Chroma( + collection_name="skill_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{CKPT_DIR}/skill/vectordb", + ) + def set_mc_port(self, mc_port): self.mf_instance.set_mc_port(mc_port) self.set_mc_resume() - + def set_mc_resume(self): if CONFIG.resume: logger.info(f"Loading Action Developer from {CKPT_DIR}/action") with open(f"{CKPT_DIR}/action/chest_memory.json", "r") as f: self.chest_memory = json.load(f) - + logger.info(f"Loading Curriculum Agent from {CKPT_DIR}/curriculum") with open(f"{CKPT_DIR}/curriculum/completed_tasks.json", "r") as f: self.completed_tasks = json.load(f) @@ -103,25 +119,72 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): logger.info(f"Loading Skill Manager from {CKPT_DIR}/skill\033[0m") with open(f"{CKPT_DIR}/skill/skills.json", "r") as f: self.skills = json.load(f) - + logger.info(f"Loading Qa Cache from {CKPT_DIR}/curriculum\033[0m") with open(f"{CKPT_DIR}/curriculum/qa_cache.json", "r") as f: - self.qa_cache = json.load(f) - + self.qa_cache = json.load(f) + + if self.vectordb._collection.count() == 0: + # Set vdvs for skills & qa_cache + skill_desps = [ + skill["description"] for program_name, skill in self.skills.items() + ] + program_names = [ + program_name for program_name, skill in self.skills.items() + ] + metadatas = [{"name": program_name} for program_name in program_names] + # add vectordb from file + ids = self.vectordb.add_texts( + texts=skill_desps, + ids=program_names, + metadatas=metadatas, + ) + self.vectordb.persist() + + if self.qa_cache_questions_vectordb._collection.count() == 0: + questions = [question for question, answer in self.qa_cache.items()] + self.qa_cache_questions_vectordb.add_texts(texts=questions) + + self.qa_cache_questions_vectordb.persist() + + logger.info( + f"INIT_CHECK: There are {self.vectordb._collection.count()} skills in vectordb and {len(self.skills)} skills in skills.json." + ) + # Check if Skill Manager's vectordb right using + 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." + ) + + logger.info( + f"INIT_CHECK: There are {self.qa_cache_questions_vectordb._collection.count()} qa_cache in vectordb and {len(self.qa_cache)} questions in qa_cache.json." + ) + 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" + ) + def register_roles(self, roles: Iterable[Minecraft]): for role in roles: role.set_memory(self) - + def update_event(self, event: Dict): if self.event == event: return self.event = event # self.update_chest_memory(event) # self.event_summary = self.summarize_chatlog(event) - + def update_task(self, task: str): self.current_task = task - + def update_context(self, context: str): self.context = context @@ -130,22 +193,22 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): def update_code(self, code: str): self.code = code # action_developer.gen_action_code to HERE - + def update_program_name(self, program_name: str): self.program_name = program_name - + def update_critique(self, critique: str): self.critique = critique # critic_agent.check_task_success to HERE - + 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 - + def update_chest_memory(self, events: Dict): """ Input: events: Dict @@ -165,13 +228,13 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): self.chest_memory[position] = chest with open(f"{CKPT_DIR}/action/chest_memory.json", "w") as f: json.dump(self.chest_memory, f) - + def update_chest_observation(self): """ update chest_memory to chest_observation. Refer to @ https://github.com/MineDojo/Voyager/blob/main/voyager/agents/action.py """ - + chests = [] for chest_position, chest in self.chest_memory.items(): if isinstance(chest, dict) and len(chest) > 0: @@ -189,7 +252,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): self.chest_observation = f"Chests:\n{chests}\n\n" else: self.chest_observation = f"Chests: None\n\n" - + def summarize_chatlog(self, events): def filter_item(message: str): craft_pattern = r"I cannot make \w+ because I need: (.*)" @@ -198,25 +261,28 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): ) mine_pattern = r"I need at least a (.*) to mine \w+!" if re.match(craft_pattern, message): - self.event_summary = re.match(craft_pattern, message).groups()[0] + self.event_summary = re.match(craft_pattern, message).groups()[0] elif re.match(craft_pattern2, message): self.event_summary = "a nearby crafting table" elif re.match(mine_pattern, message): self.event_summary = re.match(mine_pattern, message).groups()[0] else: self.event_summary = "" + chatlog = set() for event_type, event in events: if event_type == "onChat": item = filter_item(event["onChat"]) if item: chatlog.add(item) - self.event_summary = "I also need " + ", ".join(chatlog) + "." if chatlog else "" - + self.event_summary = ( + "I also need " + ", ".join(chatlog) + "." if chatlog else "" + ) + def reset_block_info(self): # revert all the placing event in the last step pass - + def update_exploration_progress(self, success: bool): """ Split task into completed_tasks or failed_tasks @@ -252,9 +318,9 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): ) self.event[-1][1]["inventory"] = new_events[-1][1]["inventory"] self.event[-1][1]["voxels"] = new_events[-1][1]["voxels"] - + self.save_sorted_tasks() - + def save_sorted_tasks(self): updated_completed_tasks = [] # record repeated failed tasks @@ -263,21 +329,21 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): for task in self.completed_tasks: if task not in updated_completed_tasks: updated_completed_tasks.append(task) - + # remove completed tasks from failed tasks for task in updated_completed_tasks: while task in updated_failed_tasks: updated_failed_tasks.remove(task) - + 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.completed_tasks, f) with open(f"{CKPT_DIR}/curriculum/failed_tasks.json", "w") as f: json.dump(self.failed_tasks, f) - + async def on_event_retrieve(self, *args): """ Retrieve Minecraft events. @@ -295,9 +361,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): "wait_ticks": 20, } ) - difficulty = ( - "easy" if len(self.completed_tasks) > 15 else "peaceful" - ) + difficulty = "easy" if len(self.completed_tasks) > 15 else "peaceful" events = self.mf_instance.step( "bot.chat(`/time set ${getNextTime()}`);\n" + f"bot.chat('/difficulty {difficulty}');" @@ -318,7 +382,8 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): ) self.update_event(events) logger.error(f"Failed to retrieve Minecraft events: {str(e)}") - + return events + async def on_event_execute(self, *args): """ Execute Minecraft events. @@ -334,7 +399,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): """ try: events = self.mf_instance.step( - code = self.code, + code=self.code, programs=self.programs, ) self.update_event(events) @@ -353,6 +418,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): ) self.update_event(events) logger.error(f"Failed to execute Minecraft events: {str(e)}") + return events class MinecraftPlayer(SoftwareCompany): @@ -360,16 +426,16 @@ class MinecraftPlayer(SoftwareCompany): Software Company: Possesses a team, SOP (Standard Operating Procedures), and a platform for instant messaging, dedicated to writing executable code. """ - + environment: Environment = Field(default_factory=Environment) game_memory: GameEnvironment = Field(default_factory=GameEnvironment) investment: float = Field(default=50.0) task: str = Field(default="") game_info: dict = Field(default={}) - + def set_port(self, mc_port): self.game_memory.set_mc_port(mc_port) - + def check_complete_round(self): complete_round = [] for role in self.environment.roles.values(): @@ -381,7 +447,7 @@ class MinecraftPlayer(SoftwareCompany): complete_round_tag = all(complete_round) logger.info(f"complete_round {complete_round}") return complete_round_tag - + def update_round(self): for role in self.environment.roles.values(): role.finish_step = False @@ -389,11 +455,11 @@ class MinecraftPlayer(SoftwareCompany): role._rc.todo = None role.finish_state = len(role._actions) logger.info(f"round_id:{role.round_id}") - + def hire(self, roles: list[Role]): self.environment.add_roles(roles) self.game_memory.register_roles(roles) - + def start(self, task, round=0): """Start a project from publishing boss requirement.""" self.task = task @@ -401,14 +467,14 @@ class MinecraftPlayer(SoftwareCompany): Message(role="Player", content=task, cause_by=PlayerActions, round_id=round) ) logger.info(self.game_info) - + def _save(self): logger.info(self.json()) - + def _reset(self): for role_profile, role in self.environment.roles.items(): role.reset_state() - + async def run(self, n_round=3): """Run company until target round or no money""" round_id = 0 @@ -434,18 +500,17 @@ class MinecraftPlayer(SoftwareCompany): programs="", ) self.game_memory.update_event(events) - + while n_round > 0: # self._save() if self.check_complete_round(): - n_round -= 1 self.update_round() round_id += 1 # add new task into env and continue # fixme: update self.task self.start(task=self.task, round=round_id) - + logger.info(f"{n_round=}") self._check_balance() await self.environment.run() diff --git a/metagpt/roles/minecraft/curriculum_agent.py b/metagpt/roles/minecraft/curriculum_agent.py index 831d54999..8b414aa7a 100644 --- a/metagpt/roles/minecraft/curriculum_agent.py +++ b/metagpt/roles/minecraft/curriculum_agent.py @@ -300,6 +300,7 @@ class CurriculumDesigner(Base): inventoryUsed = events[-1][1]["status"]["inventoryUsed"] task = self.game_memory.current_task qa_cache = self.game_memory.qa_cache + qa_cache_questions_vectordb = self.game_memory.qa_cache_questions_vectordb if self.game_memory.progress == 0: context = self.game_memory.context @@ -309,7 +310,7 @@ class CurriculumDesigner(Base): ) else: context = await DesignCurriculum().run( - task, qa_cache, human_msg, system_msg, *args, **kwargs + task, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg, *args, **kwargs ) self.perform_game_info_callback(context, self.game_memory.update_context) return Message( diff --git a/metagpt/roles/minecraft/skill_manager.py b/metagpt/roles/minecraft/skill_manager.py index aec802d15..f70511c1c 100644 --- a/metagpt/roles/minecraft/skill_manager.py +++ b/metagpt/roles/minecraft/skill_manager.py @@ -19,40 +19,42 @@ from metagpt.utils.minecraft import load_prompt @agent_registry.register("skill_manager") class SkillManager(Base): def __init__( - self, - name: str = "John", - profile: str = "Skills Management Specialist", - goal: str = "To oversee and optimize the acquisition, development, and utilization of skills within the organization, ensuring workforce competence and efficiency.", - constraints: str = "Resource allocation, training budgets, and alignment with organizational goals.", + self, + name: str = "John", + profile: str = "Skills Management Specialist", + goal: str = "To oversee and optimize the acquisition, development, and utilization of skills within the organization, ensuring workforce competence and efficiency.", + constraints: str = "Resource allocation, training budgets, and alignment with organizational goals.", ) -> None: super().__init__(name, profile, goal, constraints) - + # Initialize actions specific to the SkillManager role self._init_actions([RetrieveSkills, GenerateSkillDescription, AddNewSkills]) - + # Set events or actions the SkillManager should watch or be aware of self._watch( [DesignCurriculum, GenerateActionCode, RetrieveSkills, GenerateSkillDescription] ) + self.finish_state = len(self._actions) - + def encapsule_message(self, program_code, program_name, *args, **kwargs): system_msg = self.render_system_message(load_prompt("skill")) human_msg = self.render_human_message( program_code + "\n\n" + f"The main function is `{program_name}`." ) return {"system_msg": [system_msg.content], "human_msg": human_msg.content} - + async def retrieve_skills(self, query, skills, *args, **kwargs): - retrieve_skills = await RetrieveSkills().run(query, skills) + vectordb = self.game_memory.vectordb + retrieve_skills = await RetrieveSkills().run(query, skills, vectordb) 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", + 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 # ) # Unit test only - + async def generate_skill_descp(self, human_msg, system_msg, *args, **kwargs): program_name = self.game_memory.program_name desp = await GenerateSkillDescription().run(program_name, human_msg, system_msg) @@ -62,20 +64,21 @@ class SkillManager(Base): instruct_content="generate_skill_descp", role=self.profile, ) - + async def handle_add_new_skills( - self, task, program_name, program_code, skills, *args, **kwargs + self, task, program_name, program_code, skills, *args, **kwargs ): if not self.game_memory.runtime_status: return Message( - content="", - instruct_content="handle_add_new_skills", - role=self.profile, - ) + content="", + instruct_content="handle_add_new_skills", + role=self.profile, + ) skill_desp = self.game_memory.skill_desp + vectordb = self.game_memory.vectordb new_skills_info = await AddNewSkills().run( - task, program_name, program_code, skills, skill_desp + task, program_name, program_code, skills, skill_desp, vectordb ) self.perform_game_info_callback(new_skills_info, self.game_memory.append_skill) return Message( @@ -83,13 +86,13 @@ class SkillManager(Base): instruct_content="handle_add_new_skills", role=self.profile, ) - + async def _act(self) -> Message: todo = self._rc.todo logger.debug(f"Todo is {todo}") self.maintain_actions(todo) + # 获取最新的游戏周边信息 - context = self.game_memory.context task = self.game_memory.current_task @@ -97,27 +100,27 @@ class SkillManager(Base): self.perform_game_info_callback(self.game_memory.event, self.game_memory.summarize_chatlog) event_summary = self.game_memory.event_summary program_code = self.game_memory.program_code - + program_name = self.game_memory.program_name skills = self.game_memory.skills - + # msg = self._rc.memory.get(k=1)[0] - + retrieve_skills_message_step1 = {"query": context, "skills": skills} logger.info(f"check query {context}") logger.info(f"check event summary {event_summary}") retrieve_skills_message_step2 = {"query": context + "\n\n" + event_summary, "skills": skills} - + generate_skill_message = self.encapsule_message(program_code, program_name) - + add_new_skills_message = { "task": task, "program_name": program_name, "program_code": program_code, "skills": skills, } - + handler_map = { DesignCurriculum: self.retrieve_skills, RetrieveSkills: self.retrieve_skills, @@ -136,10 +139,10 @@ class SkillManager(Base): msg = await handler(**generate_skill_message) else: msg = await handler(**add_new_skills_message) - + msg.cause_by = type(todo) msg.round_id = self.round_id self._publish_message(msg) return msg - + raise ValueError(f"Unknown todo type: {type(todo)}") From bb5f3f8ecf043b870a3f43b556861bf78eb60acb Mon Sep 17 00:00:00 2001 From: stellahsr Date: Thu, 12 Oct 2023 11:57:43 +0800 Subject: [PATCH 2/3] =?UTF-8?q?update=20code:=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E6=96=AD=E5=90=8E=E6=81=A2=E5=A4=8D=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?vdb=E7=9A=84=E6=9B=B4=E6=96=B0=E5=92=8C=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../actions/minecraft/design_curriculumn.py | 25 +++-- metagpt/actions/minecraft/manage_skills.py | 6 + metagpt/minecraft_team.py | 104 ++++++++++++------ metagpt/roles/minecraft/action_developer.py | 2 +- metagpt/roles/minecraft/curriculum_agent.py | 46 ++++---- 5 files changed, 120 insertions(+), 63 deletions(-) diff --git a/metagpt/actions/minecraft/design_curriculumn.py b/metagpt/actions/minecraft/design_curriculumn.py index 62555f54a..40abb7ca4 100644 --- a/metagpt/actions/minecraft/design_curriculumn.py +++ b/metagpt/actions/minecraft/design_curriculumn.py @@ -91,8 +91,9 @@ class DesignCurriculum(Action): def __init__(self, name="", context=None, llm=None): super().__init__(name, context, llm) + self.llm.model = "gpt-3.5-turbo" - async def generate_qa(self, events, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg): + async def generate_qa(self, events, qa_cache, qa_cache_questions_vectordb, game_memory, human_msg, system_msg): """ Generate qa for DesignTask's HumanMessage """ @@ -100,7 +101,7 @@ class DesignCurriculum(Action): events=events, human_msg=human_msg, system_msg=system_msg ) logger.debug(f"Generate_qa_step1 result list is HERE: {questions_new}") - + questions = [] answers = [] for question in questions_new: @@ -128,6 +129,10 @@ class DesignCurriculum(Action): qa_cache_questions_vectordb.persist() questions.append(question) answers.append(answer) + + game_memory.qa_cache = qa_cache + + assert len(questions_new) == len(questions) == len(answers) logger.info(f"Curriculum Agent generate_qa Questions: {questions}") logger.info(f"Curriculum Agent generate_qa Answers: {answers}") @@ -170,7 +175,7 @@ class DesignCurriculum(Action): # logger.info(f"Curriculum Agent generate_qa_step2 answer: {answer}") return answer - async def get_context_from_task(self, task, qa_cache, qa_cache_questions_vectordb): + async def get_context_from_task(self, task, qa_cache, qa_cache_questions_vectordb, game_memory): """ Args: task Returns: context: "Question: {question}\n{answer}" @@ -192,10 +197,12 @@ class DesignCurriculum(Action): with open(f"{CKPT_DIR}/curriculum/qa_cache.json", "w") as f: json.dump(qa_cache, f) qa_cache_questions_vectordb.persist() + + game_memory.qa_cache = qa_cache context = f"Question: {question}\n{answer}" return context - async def generate_context(self, task, qa_cache, qa_cache_questions_vectordb, max_retries=5): + async def generate_context(self, task, qa_cache, qa_cache_questions_vectordb, game_memory, max_retries=5): """ Refer to the code in the voyager/agents/curriculum.py propose_next_ai_task() for implementation details. Returns: context @@ -206,7 +213,8 @@ class DesignCurriculum(Action): raise RuntimeError("Max retries reached, failed to propose context.") try: context = await self.get_context_from_task( - task=task, qa_cache=qa_cache, qa_cache_questions_vectordb=qa_cache_questions_vectordb + task=task, qa_cache=qa_cache, qa_cache_questions_vectordb=qa_cache_questions_vectordb, + game_memory=game_memory, ) # Curriculum Agent Question: How to craft 4 wooden planks in Minecraft? & Curriculum Agent Answer: ... return context except Exception as e: @@ -215,14 +223,17 @@ class DesignCurriculum(Action): task=task, qa_cache=qa_cache, qa_cache_questions_vectordb=qa_cache_questions_vectordb, + game_memory=game_memory, max_retries=max_retries - 1, ) - async def run(self, task, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg, *args, **kwargs): + async def run(self, task, qa_cache, qa_cache_questions_vectordb, game_memory, human_msg, system_msg, *args, + **kwargs): logger.info(f"run {self.__repr__()}") # Generate curriculum-related questions and answers. # curriculum_qustion = await self.generate_qa_step1(events, human_msg, system_msg) - curriculum_context = await self.generate_context(task, qa_cache, qa_cache_questions_vectordb) + curriculum_context = await self.generate_context(task, qa_cache, qa_cache_questions_vectordb, + game_memory=game_memory) # Return the generated questions and answers. return curriculum_context diff --git a/metagpt/actions/minecraft/manage_skills.py b/metagpt/actions/minecraft/manage_skills.py index b5c51e270..ad0ed0cdb 100644 --- a/metagpt/actions/minecraft/manage_skills.py +++ b/metagpt/actions/minecraft/manage_skills.py @@ -75,6 +75,12 @@ class AddNewSkills(Action): ids=[program_name], metadatas=[{"name": program_name}], ) + + skills[program_name] = { + "code": program_code, + "description": skill_desp, + } + logger.debug(f"ADD_CHECK: There are {vectordb._collection.count()} skills in vectordb") with open(f"{CKPT_DIR}/skill/code/{dumped_program_name}.js", "w") as f: diff --git a/metagpt/minecraft_team.py b/metagpt/minecraft_team.py index 0aac8c1cb..86f69adcc 100644 --- a/metagpt/minecraft_team.py +++ b/metagpt/minecraft_team.py @@ -60,6 +60,10 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): mf_instance: MineflayerEnv = Field(default_factory=MineflayerEnv) runtime_status: bool = False # equal to action execution status: success or failed + vectordb: Chroma = Field(default_factory=Chroma) + + qa_cache_questions_vectordb: Chroma = Field(default_factory=Chroma) + @property def progress(self): # return len(self.completed_tasks) + 10 # Test only @@ -84,27 +88,40 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): def core_inv_items_regex(self): return self.mf_instance.core_inv_items_regex - @property - def qa_cache_questions_vectordb(self): - return Chroma( - collection_name="qa_cache_questions_vectordb", - embedding_function=OpenAIEmbeddings(), - persist_directory=f"{CKPT_DIR}/curriculum/vectordb", - ) + # @property + # def qa_cache_questions_vectordb(self): + # return Chroma( + # collection_name="qa_cache_questions_vectordb", + # embedding_function=OpenAIEmbeddings(), + # persist_directory=f"{CKPT_DIR}/curriculum/vectordb", + # ) - @property - def vectordb(self): - return Chroma( - collection_name="skill_vectordb", - embedding_function=OpenAIEmbeddings(), - persist_directory=f"{CKPT_DIR}/skill/vectordb", - ) + # @property + # def vectordb(self): + # return Chroma( + # collection_name="skill_vectordb", + # embedding_function=OpenAIEmbeddings(), + # persist_directory=f"{CKPT_DIR}/skill/vectordb", + # ) def set_mc_port(self, mc_port): self.mf_instance.set_mc_port(mc_port) self.set_mc_resume() def set_mc_resume(self): + self.qa_cache_questions_vectordb = Chroma( + collection_name="qa_cache_questions_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{CKPT_DIR}/curriculum/vectordb", + ) + + self.vectordb = Chroma( + collection_name="skill_vectordb", + embedding_function=OpenAIEmbeddings(), + persist_directory=f"{CKPT_DIR}/skill/vectordb", + ) + + if CONFIG.resume: logger.info(f"Loading Action Developer from {CKPT_DIR}/action") with open(f"{CKPT_DIR}/action/chest_memory.json", "r") as f: @@ -125,6 +142,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): self.qa_cache = json.load(f) if self.vectordb._collection.count() == 0: + logger.info(self.vectordb._collection.count()) # Set vdvs for skills & qa_cache skill_desps = [ skill["description"] for program_name, skill in self.skills.items() @@ -140,36 +158,41 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): metadatas=metadatas, ) self.vectordb.persist() + + logger.info(self.qa_cache_questions_vectordb._collection.count()) if self.qa_cache_questions_vectordb._collection.count() == 0: + questions = [question for question, answer in self.qa_cache.items()] + self.qa_cache_questions_vectordb.add_texts(texts=questions) self.qa_cache_questions_vectordb.persist() - logger.info( - f"INIT_CHECK: There are {self.vectordb._collection.count()} skills in vectordb and {len(self.skills)} skills in skills.json." - ) - # Check if Skill Manager's vectordb right using - 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." - ) + logger.info( + f"INIT_CHECK: There are {self.vectordb._collection.count()} skills in vectordb and {len(self.skills)} skills in skills.json." + ) + # Check if Skill Manager's vectordb right using + 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." + ) + + logger.info( + f"INIT_CHECK: There are {self.qa_cache_questions_vectordb._collection.count()} qa_cache in vectordb and {len(self.qa_cache)} questions in qa_cache.json." + ) + 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" + ) - logger.info( - f"INIT_CHECK: There are {self.qa_cache_questions_vectordb._collection.count()} qa_cache in vectordb and {len(self.qa_cache)} questions in qa_cache.json." - ) - 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" - ) def register_roles(self, roles: Iterable[Minecraft]): for role in roles: @@ -209,6 +232,9 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): def update_skill_desp(self, skill_desp: str): self.skill_desp = skill_desp + async def update_qa_cache(self, qa_cache: dict): + self.qa_cache = qa_cache + def update_chest_memory(self, events: Dict): """ Input: events: Dict @@ -517,3 +543,9 @@ class MinecraftPlayer(SoftwareCompany): # self.environment.memory.clear() # self._reset() return self.environment.history + + +if __name__ == "__main__": + A = GameEnvironment() + a = A.qa_cache_questions_vectordb + print(a._persist_directory) diff --git a/metagpt/roles/minecraft/action_developer.py b/metagpt/roles/minecraft/action_developer.py index 07136a82c..244523022 100644 --- a/metagpt/roles/minecraft/action_developer.py +++ b/metagpt/roles/minecraft/action_developer.py @@ -216,7 +216,7 @@ class ActionDeveloper(Base): self.perform_game_info_callback(new_skills_info, self.game_memory.append_skill) async def retrieve_skills(self, query, skills, *args, **kwargs): - retrieve_skills = await RetrieveSkills().run(query, skills) + retrieve_skills = await RetrieveSkills().run(query, skills, vectordb=self.game_memory.vectordb) 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", diff --git a/metagpt/roles/minecraft/curriculum_agent.py b/metagpt/roles/minecraft/curriculum_agent.py index 8b414aa7a..9bbf9ccdd 100644 --- a/metagpt/roles/minecraft/curriculum_agent.py +++ b/metagpt/roles/minecraft/curriculum_agent.py @@ -20,11 +20,11 @@ class CurriculumDesigner(Base): """ def __init__( - self, - name: str = "David", - profile: str = "Expertise in minecraft task design and curriculum development.", - goal: str = " Collect and integrate learner feedback to improve and refine educational content and pathways", - constraints: str = "Limited budget and resources for the development of educational content and technology tools.", + self, + name: str = "David", + profile: str = "Expertise in minecraft task design and curriculum development.", + goal: str = " Collect and integrate learner feedback to improve and refine educational content and pathways", + constraints: str = "Limited budget and resources for the development of educational content and technology tools.", ) -> None: super().__init__(name, profile, goal, constraints) # Initialize actions specific to the Action role @@ -56,12 +56,12 @@ class CurriculumDesigner(Base): inventory = event["inventory"] if not any( - "dirt" in block - or "log" in block - or "grass" in block - or "sand" in block - or "snow" in block - for block in voxels + "dirt" in block + or "log" in block + or "grass" in block + or "sand" in block + or "snow" in block + for block in voxels ): biome = "underground" @@ -92,8 +92,8 @@ class CurriculumDesigner(Base): # filter out optional inventory items if required if ( - self.game_memory.progress - < self.game_memory.warm_up["optional_inventory_items"] + self.game_memory.progress + < self.game_memory.warm_up["optional_inventory_items"] ): inventory = { k: v @@ -121,7 +121,7 @@ class CurriculumDesigner(Base): # --------------------------------Design Task Prepare--------------------------------------- async def render_design_task_human_message( - self, events, chest_observation, *args, **kwargs + self, events, chest_observation, *args, **kwargs ): """ Returns: observation for curriculum @@ -135,15 +135,19 @@ class CurriculumDesigner(Base): events=events, chest_observation=chest_observation ) if self.game_memory.progress >= warm_up["context"]: - # if self.game_memory.progress >= 0: # TEST ONLY + # if self.game_memory.progress >= 0: # TEST ONLY human_msg = self.render_design_curriculum_human_message( events=events, chest_observation=chest_observation ).content system_msg = [self.render_design_curriculum_system_message().content] questions, answers = await DesignCurriculum().generate_qa( - events=events, qa_cache=qa_cache, human_msg=human_msg, system_msg=system_msg + events=events, qa_cache=qa_cache, + qa_cache_questions_vectordb=self.game_memory.qa_cache_questions_vectordb, + game_memory=self.game_memory, + human_msg=human_msg, system_msg=system_msg ) logger.debug(f"Generate_qa result is HERE: Ques: {questions}, Ans: {answers}") + i = 1 for question, answer in zip(questions, answers): if "Answer: Unknown" in answer or "language model" in answer: @@ -202,7 +206,7 @@ class CurriculumDesigner(Base): return SystemMessage(content=load_prompt("curriculum_qa_step1_ask_questions")) def render_design_curriculum_human_message( - self, events, chest_observation, *args, **kwargs + self, events, chest_observation, *args, **kwargs ): observation = self.render_curriculum_observation( events=events, chest_observation=chest_observation @@ -213,7 +217,7 @@ class CurriculumDesigner(Base): return HumanMessage(content=content) def encapsule_design_curriculum_message( - self, events, chest_observation, *args, **kwargs + self, events, chest_observation, *args, **kwargs ): human_msg = self.render_design_curriculum_human_message( events=events, chest_observation=chest_observation, *args, **kwargs @@ -309,8 +313,12 @@ class CurriculumDesigner(Base): self, events=events, chest_observation=chest_observation ) else: + logger.info(self.game_memory.qa_cache_questions_vectordb._collection.count()) + logger.info(self.game_memory.vectordb._collection.count()) context = await DesignCurriculum().run( - task, qa_cache, qa_cache_questions_vectordb, human_msg, system_msg, *args, **kwargs + task, qa_cache, qa_cache_questions_vectordb, game_memory=self.game_memory, + human_msg=human_msg, + system_msg=system_msg, *args, **kwargs ) self.perform_game_info_callback(context, self.game_memory.update_context) return Message( From 8a7d96d507e7cc6a5db8d9bba51f8ef5e257fc4f Mon Sep 17 00:00:00 2001 From: stellahsr Date: Thu, 12 Oct 2023 12:10:51 +0800 Subject: [PATCH 3/3] remove useless code --- metagpt/minecraft_team.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/metagpt/minecraft_team.py b/metagpt/minecraft_team.py index 86f69adcc..829db84e4 100644 --- a/metagpt/minecraft_team.py +++ b/metagpt/minecraft_team.py @@ -88,21 +88,6 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True): def core_inv_items_regex(self): return self.mf_instance.core_inv_items_regex - # @property - # def qa_cache_questions_vectordb(self): - # return Chroma( - # collection_name="qa_cache_questions_vectordb", - # embedding_function=OpenAIEmbeddings(), - # persist_directory=f"{CKPT_DIR}/curriculum/vectordb", - # ) - - # @property - # def vectordb(self): - # return Chroma( - # collection_name="skill_vectordb", - # embedding_function=OpenAIEmbeddings(), - # persist_directory=f"{CKPT_DIR}/skill/vectordb", - # ) def set_mc_port(self, mc_port): self.mf_instance.set_mc_port(mc_port)