Action developer add chest_memory & .run test pass

This commit is contained in:
yuymf 2023-09-28 21:09:13 +08:00
parent 3a705a0e89
commit 73cd368828
8 changed files with 84 additions and 49 deletions

1
.gitignore vendored
View file

@ -114,6 +114,7 @@ venv/
ENV/
env.bak/
venv.bak/
*/ckpt
# Spyder project settings
.spyderproject

View file

@ -32,11 +32,11 @@ class GenerateActionCode(Action):
logger.error(f"Failed to parse response: {parsed_result}")
return None
async def run(self, msg, *args, **kwargs):
async def run(self, human_msg, system_msg, *args, **kwargs):
logger.info(f"run {self.__repr__()}")
# Generate action code.
generated_code = await self.generate_code(
human_msg=msg['human_msg'], system_msg=msg['system_msg']
human_msg=human_msg, system_msg=system_msg
)
# Return the generated code.

View file

@ -6,7 +6,6 @@ from typing import Iterable, Dict, Any
from pydantic import BaseModel, Field
import requests
import json
import asyncio
from metagpt.logs import logger
from metagpt.roles import Role
@ -33,17 +32,31 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
critique: str = Field(default="")
skills: list[str] = Field(default_factory=list)
chest_memory: dict[str, Any] = Field(default_factory=dict)
mf_instance: MineflayerEnv = Field(default_factory=MineflayerEnv)
def set_mc_port(self, mc_port):
self.mf_instance.set_mc_port(mc_port)
def set_mc_resume(self, resume: bool = False):
if resume:
logger.info(
f"Loading Action Developer from {self.mf_instance.ckpt_dir}/action"
)
with open(
f"{self.mf_instance.ckpt_dir}/action/chest_memory.json", "r"
) as f:
self.chest_memory = json.load(f)
# TODO: add skills resume
def register_roles(self, roles: Iterable[Minecraft]):
for role in roles:
role.set_memory(self)
def update_event(self, event: Dict):
self.event = event
self.update_chest_memory(event)
def update_task(self, task: str):
self.current_task = task
@ -52,7 +65,7 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
self.context = context
def update_code(self, code: str):
self.code = code # action_developer.gen to HERE
self.code = code # action_developer.gen_action_code to HERE
def update_programs(self, programs: str):
self.programs = programs
@ -63,6 +76,26 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
def update_skills(self, skills: list):
self.skills = skills # skill_manager.retrieve_skills to HERE
def update_chest_memory(self, events: Dict):
"""
Input: events: Dict
Result: self.chest_memory update & save to json
"""
nearbyChests = events[-1][1]["nearbyChests"]
for position, chest in nearbyChests.items():
if position in self.chest_memory:
if isinstance(chest, dict):
self.chest_memory[position] = chest
if chest == "Invalid":
logger.info(f"Action Developer removing chest {position}: {chest}")
self.chest_memory.pop(position)
else:
if chest != "Invalid":
logger.info(f"Action Developer saving chest {position}: {chest}")
self.chest_memory[position] = chest
with open(f"{self.mf_instance.ckpt_dir}/action/chest_memory.json", "w") as f:
json.dump(self.chest_memory, f)
async def on_event(self, *args):
"""
Retrieve Minecraft events.
@ -126,6 +159,9 @@ class MinecraftPlayer(SoftwareCompany):
def set_port(self, mc_port):
self.game_memory.set_mc_port(mc_port)
def set_resume(self, resume: bool = False):
self.game_memory.set_mc_resume(resume=resume)
def hire(self, roles: list[Role]):
self.environment.add_roles(roles)
self.game_memory.register_roles(roles)

View file

@ -29,6 +29,9 @@ class MineflayerEnv:
self.reset_options = None
self.connected = False
self.server_paused = False
self.ckpt_dir = "metagpt/ckpt"
os.makedirs(f"{self.ckpt_dir}/action", exist_ok=True)
def set_mc_port(self, mc_port):
self.mc_port = mc_port

View file

@ -7,7 +7,6 @@ from metagpt.roles.minecraft.minecraft_base import Minecraft as Base
from metagpt.schema import Message, HumanMessage, SystemMessage
from metagpt.roles.minecraft.minecraft_base import agent_registry
from metagpt.actions.minecraft.generate_actions import GenerateActionCode
from metagpt.actions.minecraft.design_curriculumn import DesignCurriculum
from metagpt.actions.minecraft.manage_skills import (
GenerateSkillDescription,
RetrieveSkills,
@ -42,6 +41,30 @@ class ActionDeveloper(Base):
# 需要根据events进行自己chest_observation的更新
self._watch([RetrieveSkills])
def render_chest_observation(self):
"""
Render game_memory.chest_memory to prompt text.
Refer to @ https://github.com/MineDojo/Voyager/blob/main/voyager/agents/action.py
"""
chests = []
for chest_position, chest in self.game_memory.chest_memory.items():
if isinstance(chest, dict) and len(chest) > 0:
chests.append(f"{chest_position}: {chest}")
for chest_position, chest in self.game_memory.chest_memory.items():
if isinstance(chest, dict) and len(chest) == 0:
chests.append(f"{chest_position}: Empty")
for chest_position, chest in self.game_memory.chest_memory.items():
if isinstance(chest, str):
assert chest == "Unknown"
chests.append(f"{chest_position}: Unknown items inside")
assert len(chests) == len(self.game_memory.chest_memory)
if chests:
chests = "\n".join(chests)
return f"Chests:\n{chests}\n\n"
else:
return f"Chests: None\n\n"
def render_system_message(self, skills=[], *args, **kwargs):
"""
According to basic skills context files to genenarate js skill codes.
@ -140,12 +163,12 @@ class ActionDeveloper(Base):
observation += f"Equipment: {equipment}\n\n"
observation += f"Inventory ({inventory_used}/36): {'Empty' if not inventory else ', '.join(inventory)}\n\n"
if not (
task == "Place and deposit useless items into a chest"
or task.startswith("Deposit useless items into the chest at")
):
# TODO: observation += self.render_chest_observation()
logger.warning("chest_observation will add later")
# TODO: if task update, uncomment this
# if not (
# task == "Place and deposit useless items into a chest"
# or task.startswith("Deposit useless items into the chest at")
# ):
observation += self.render_chest_observation()
observation += f"Task: {task}\n\n"
observation += f"Context: {context or 'None'}\n\n"
@ -183,8 +206,8 @@ class ActionDeveloper(Base):
logger.info(len(self._rc.news))
return len(self._rc.news)
async def generate_action_code(self, msg, *args, **kwargs):
code = await GenerateActionCode().run(msg, *args, **kwargs)
async def generate_action_code(self, human_msg, system_msg, *args, **kwargs):
code = await GenerateActionCode().run(human_msg, system_msg, *args, **kwargs)
# logger.warning(type(code))
# logger.info(f"Code is Here:{code}")
self.perform_game_info_callback(code, self.game_memory.update_code)
@ -202,6 +225,7 @@ class ActionDeveloper(Base):
# 获取最新的游戏周边信息
events = await self._obtain_events()
self.perform_game_info_callback(events, self.game_memory.update_event)
context = self.game_memory.context
task = self.game_memory.current_task
code = self.game_memory.code

View file

@ -6,38 +6,6 @@
import json
import re
from typing import Any, Dict, Union
from .file_utils import f_join
def json_load(*file_path, **kwargs):
file_path = f_join(file_path)
with open(file_path, "r") as fp:
return json.load(fp, **kwargs)
def json_loads(string, **kwargs):
return json.loads(string, **kwargs)
def json_dump(data, *file_path, **kwargs):
file_path = f_join(file_path)
with open(file_path, "w") as fp:
json.dump(data, fp, **kwargs)
def json_dumps(data, **kwargs):
"""
Returns: string
"""
return json.dumps(data, **kwargs)
# ---------------- Aliases -----------------
# add aliases where verb goes first, json_load -> load_json
load_json = json_load
loads_json = json_loads
dump_json = json_dump
dumps_json = json_dumps
def extract_char_position(error_message: str) -> int:

View file

@ -13,7 +13,8 @@ from metagpt.minecraft_team import MinecraftPlayer
async def learn(task="Start", investment: float = 50.0, n_round: int = 3):
mc_player = MinecraftPlayer()
mc_player.set_port(2253) # Modify this to your LAN port
mc_player.set_port(1077) # Modify this to your Minecraft LAN port
# mc_player.set_resume(True) # If load json from ckpt dir(include chest_memory, skills, ...)
mc_player.hire(
[
CurriculumDesigner(),

View file

@ -37,11 +37,12 @@ async def main():
"elapsedTime": 41,
},
"inventory": {},
"nearbyChests": {},
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
"blockRecords": ["grass_block", "dirt", "grass"],
},
]
]
code = """
async function collectBamboo(bot) {
// Equip the iron sword
@ -78,11 +79,12 @@ async def main():
"""
ad = ActionDeveloper()
ge = GameEnvironment()
ge.update_event(events)
ad.set_memory(shared_memory=ge)
msg = ad.encapsule_message(events=events, code=code)
msg = ad.encapsule_message(events=ge.event, code=code)
logger.info(f"Encapsuled_message: {msg}")
parsed_result = await ad.generate_action_code(msg)
parsed_result = await ad.generate_action_code(**msg)
logger.info(f"Parsed_code_updating: {parsed_result}")