Merge pull request #372 from yuymf/minecraft_dev

Minecraft game add action_developer
This commit is contained in:
Sirui Hong 2023-09-30 16:59:40 +08:00 committed by GitHub
commit 89bdd19a51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 633 additions and 126 deletions

1
.gitignore vendored
View file

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

10
Temp.md
View file

@ -31,3 +31,13 @@ ### 0926 环境信息获取和更新 on_event()实际内容
<img src="docs/resources/workspace/minecraft_tests/on_event.jpeg" style="zoom:67%;" />
### 0927Action_developer 更新
对应需实现 GenerateActionCode ,完成对应的和 GameEnvironment 的交
互和 Environment 的信息传递
测试结果
![action_developer](docs/resources/workspace/minecraft_tests/action_developer.png)

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

3
mc_requirements.txt Normal file
View file

@ -0,0 +1,3 @@
javascript
requests
psutil

View file

@ -1,18 +1,20 @@
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_context(primitive_names=None):
package_path = pkg_resources.resource_filename("metagpt", "")
if primitive_names is None:
primitive_names = [
primitive[:-3]
for primitive in os.listdir(f"{package_path}/actions/minecraft/control_primitives_context")
if primitive.endswith(".js")
def load_skills_code_context(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_context/{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
if __name__ == "__main__":
logger.info(load_skills_code_context(["craftItem", "exploreUntil"]))

View file

@ -4,6 +4,7 @@
# @Desc :
from metagpt.logs import logger
from metagpt.actions import Action
from metagpt.utils.minecraft import parse_action_response
class GenerateActionCode(Action):
@ -11,23 +12,33 @@ class GenerateActionCode(Action):
Action class for generating action code.
Refer to the code in the voyager/agents/action.py for implementation details.
"""
def __init__(self, name="", context=None, llm=None):
super().__init__(name, context, llm)
async def generate_code(self):
async def generate_code(self, human_msg, system_msg=[]):
"""
Generate action code logic.
Implement the logic for generating action code here.
"""
return ""
async def run(self, human_msg, system_msg=[], *args, **kwargs):
rsp = await self._aask(prompt=human_msg, system_msgs=system_msg)
parsed_result = parse_action_response(rsp)
# logger.info(f"parsed_result is HERE: {parsed_result}")
try:
return parsed_result["program_code"] + "\n" + parsed_result["exec_code"]
except:
logger.error(f"Failed to parse response: {parsed_result}")
return None
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()
generated_code = await self.generate_code(
human_msg=human_msg, system_msg=system_msg
)
# Return the generated code.
return generated_code
@ -37,10 +48,10 @@ class SummarizeLog(Action):
Action class for parsing and summarizing logs.
Refer to the code in the voyager/agents/action.py for implementation details.
"""
def __init__(self, name="", context=None, llm=None):
super().__init__(name, context, llm)
async def summarize_logs(self):
"""
Summarize chatlogs.
@ -48,10 +59,10 @@ class SummarizeLog(Action):
Implement the logic for summarizing chatlogs here.
"""
return ""
async def run(self, *args, **kwargs):
# Summarize chatlogs.
summary = await self.summarize_logs()
# Return the summary.
return summary

View file

@ -15,44 +15,87 @@ from metagpt.software_company import SoftwareCompany
from metagpt.actions.minecraft.player_action import PlayerActions
from metagpt.roles.minecraft.minecraft_base import Minecraft
from metagpt.environment import Environment
from .mineflayer_environment import MineflayerEnv
from metagpt.mineflayer_environment import MineflayerEnv
class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
"""
游戏环境的记忆用于多个agent进行信息的共享和缓存而不需要重复在自己的角色内维护缓存
"""
event: dict[str, Any] = Field(default_factory=dict)
current_task: str = Field(default="Craft 4 wooden planks")
task_execution_time: float = Field(default=float)
context: str = Field(default="")
code: str = Field(default="")
code: str = Field(default=None)
programs: str = Field(default="")
critique: str = Field(default="")
skills: list[str] = Field(default_factory=list)
mf_instance : MineflayerEnv = Field(default_factory=MineflayerEnv)
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
def update_context(self, context: str):
self.context = context
def update_code(self, code: str):
self.code = code
self.code = code # action_developer.gen_action_code to HERE
def update_programs(self, programs: str):
self.programs = programs
def update_critique(self, critique: str):
self.critique = critique # critic_agent.check_task_success to HERE
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.
@ -70,10 +113,12 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
if not self.mf_instance.has_reset:
# TODO Modify
logger.info("Environment has not been reset yet, is resetting")
self.mf_instance.reset(options={
"mode": "soft",
"wait_ticks": 20,
})
self.mf_instance.reset(
options={
"mode": "soft",
"wait_ticks": 20,
}
)
# raise {}
self.mf_instance.check_process()
self.mf_instance.unpause()
@ -82,7 +127,9 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
"programs": self.programs,
}
res = requests.post(
f"{self.mf_instance.server}/step", json=data, timeout=self.mf_instance.request_timeout
f"{self.mf_instance.server}/step",
json=data,
timeout=self.mf_instance.request_timeout,
)
if res.status_code != 200:
logger.error("Failed to step Minecraft server")
@ -96,33 +143,40 @@ class GameEnvironment(BaseModel, arbitrary_types_allowed=True):
logger.error(f"Failed to retrieve Minecraft events: {str(e)}")
raise {}
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 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)
def start(self, task):
"""Start a project from publishing boss requirement."""
self.task = task
self.environment.publish_message(Message(role="Player", content=task, cause_by=PlayerActions))
self.environment.publish_message(
Message(role="Player", content=task, cause_by=PlayerActions)
)
logger.info(self.game_info)
def _save(self):
logger.info(self.json())
async def run(self, n_round=3):
"""Run company until target round or no money"""
while n_round > 0:
@ -131,13 +185,5 @@ class MinecraftPlayer(SoftwareCompany):
logger.debug(f"{n_round=}")
self._check_balance()
await self.environment.run()
return self.environment.history
if "__name__" == "__main__":
test_code = "bot.chat(`/time set ${getNextTime()}`);"
mc_port = 1960
ge = GameEnvironment()
ge.set_mc_port(mc_port)
ge.update_code(test_code)
logger.info(ge.on_event())
return self.environment.history

View file

@ -11,6 +11,7 @@ from metagpt.logs import logger
import metagpt.utils.minecraft as U
from metagpt.utils.minecraft.process_monitor import SubprocessMonitor
class MineflayerEnv:
def __init__(
self,
@ -28,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
@ -66,18 +70,21 @@ class MineflayerEnv:
)
if res.status_code != 200:
self.mineflayer.stop()
logger.error(
f"Minecraft server reply with code {res.status_code}"
)
logger.error(f"Minecraft server reply with code {res.status_code}")
raise {}
return res.json()
def reset(self, *, seed=None, options=None, ):
def reset(
self,
*,
seed=None,
options=None,
):
if options is None:
options = {}
if options.get("inventory", {}) and options.get("mode", "hard") != "hard":
logger.error("inventory can only be set when options is hard")
raise{}
raise {}
self.reset_options = {
"port": self.mc_port,
@ -100,7 +107,7 @@ class MineflayerEnv:
self.reset_options["reset"] = "soft"
self.pause()
return json.loads(returned_data)
def close(self):
self.unpause()
if self.connected:

View file

@ -12,4 +12,4 @@ Code:
async function yourMainFunctionName(bot) {
// ...
}
```
```

View file

@ -124,4 +124,4 @@ RESPONSE
"reasoning": "You have 28 items in your inventory after depositing, which is more than 20. You need to deposit more items from your inventory to the chest.",
"success": false,
"critique": "Deposit more useless items such as copper_block, diorite, granite, cobbled_deepslate, feather, and leather to meet the requirement of having only 20 occupied slots in your inventory."
}
}

View file

@ -39,4 +39,4 @@ Task: The next task.
Here's an example response:
Reasoning: The inventory is empty now, chop down a tree to get some wood.
Task: Obtain a wood log.
Task: Obtain a wood log.

View file

@ -5,4 +5,4 @@ Question: ...
You will answer the question based on the context (only if available and helpful) and your own knowledge of Minecraft.
1) Start your answer with "Answer: ".
2) Answer "Answer: Unknown" if you don't know the answer.
2) Answer "Answer: Unknown" if you don't know the answer.

View file

@ -9,4 +9,4 @@ You must follow the following criteria:
You should only respond in JSON format as described below:
["subgoal1", "subgoal2", "subgoal3", ...]
Ensure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc.
Ensure the response can be parsed by Python `json.loads`, e.g.: no trailing commas, no single quotes, etc.

View file

@ -48,4 +48,4 @@ The main function is `mineCobblestone`.
Then you would write:
The function is about mining 8 cobblestones using a wooden pickaxe. First check if a wooden pickaxe is in the inventory. If not, craft one. If the wooden pickaxe is available, equip the wooden pickaxe in the hand. Next, explore the environment until finding a stone block. Once a stone block is found, mine a total of 8 cobblestone blocks using the wooden pickaxe.
The function is about mining 8 cobblestones using a wooden pickaxe. First check if a wooden pickaxe is in the inventory. If not, craft one. If the wooden pickaxe is available, equip the wooden pickaxe in the hand. Next, explore the environment until finding a stone block. Once a stone block is found, mine a total of 8 cobblestone blocks using the wooden pickaxe.

View file

@ -7,8 +7,16 @@ 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, AddNewSkills
from metagpt.actions.minecraft.manage_skills import (
GenerateSkillDescription,
RetrieveSkills,
AddNewSkills,
)
import metagpt.utils.minecraft as utils
from metagpt.config import CONFIG
from metagpt.actions.minecraft.control_primitives_context import (
load_skills_code_context,
)
@agent_registry.register("action_developer")
@ -17,22 +25,177 @@ class ActionDeveloper(Base):
iterative prompting mechanism in paper.
generate action code based on environment observation and plan, as well as skills retrieval results
"""
def __init__(
self,
name: str = "Bob",
profile: str = "Generate code for specified tasks",
goal: str = "Produce accurate and efficient code solutions in Python and JavaScript",
constraints: str = "Adhere to coding best practices and style guidelines",
self,
name: str = "Bob",
profile: str = "Generate code for specified tasks",
goal: str = "Produce accurate and efficient code solutions in Python and JavaScript",
constraints: str = "Adhere to coding best practices and style guidelines",
) -> None:
super().__init__(name, profile, goal, constraints)
# Initialize actions specific to the Action role
self._init_actions([GenerateActionCode])
# Set events or actions the ActionAgent should watch or be aware of
# 需要根据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.
Refer to @ https://github.com/MineDojo/Voyager/blob/main/voyager/agents/action.py
"""
action_template = utils.load_prompt("action_template")
base_skills = [
"exploreUntil",
"mineBlock",
"craftItem",
"placeItem",
"smeltItem",
"killMob",
]
if not CONFIG.openai_api_model == "gpt-3.5-turbo":
base_skills += [
"useChest",
"mineflayer",
]
programs = "\n\n".join(load_skills_code_context(base_skills) + skills)
response_format = utils.load_prompt("action_response_format")
system_action_prompt = action_template.format(
programs=programs, response_format=response_format
)
system_action_message = SystemMessage(content=system_action_prompt)
assert isinstance(system_action_message, SystemMessage)
return system_action_message
def render_human_message(
self, events, code="", task="", context="", critique="", *args, **kwargs
):
"""
Integrate observation about the environment(especially events), add to HumanMessage.
Refer to @ https://github.com/MineDojo/Voyager/blob/main/voyager/agents/action.py
"""
# Deal with events info
chat_messages = []
error_messages = []
# damage_messages = [] # TODO: try to add damage_messages into prompt later
assert events[-1][0] == "observe", "Last event must be observe"
for i, (event_type, event) in enumerate(events):
if event_type == "onChat":
chat_messages.append(event["onChat"])
elif event_type == "onError":
error_messages.append(event["onError"])
elif event_type == "observe":
biome = event["status"]["biome"]
time_of_day = event["status"]["timeOfDay"]
voxels = event["voxels"]
entities = event["status"]["entities"]
health = event["status"]["health"]
hunger = event["status"]["food"]
position = event["status"]["position"]
equipment = event["status"]["equipment"]
inventory_used = event["status"]["inventoryUsed"]
inventory = event["inventory"]
assert i == len(events) - 1, "observe must be the last event"
# Collect all the environment information into a str: observation
observation = ""
observation = (
f"Code from the last round:\n{code or 'No code in the first round'}\n\n"
)
if error_messages:
error = "\n".join(error_messages)
observation += f"Execution error:\n{error}\n\n"
else:
observation += f"Execution error: No error\n\n"
if chat_messages:
chat_log = "\n".join(chat_messages)
observation += f"Chat log: {chat_log}\n\n"
else:
observation += f"Chat log: None\n\n"
observation += f"Biome: {biome}\n\n"
observation += f"Time: {time_of_day}\n\n"
observation += f"Nearby blocks: {', '.join(voxels) if voxels else 'None'}\n\n"
if entities:
nearby_entities = [
k for k, v in sorted(entities.items(), key=lambda x: x[1])
]
observation += f"Nearby entities (nearest to farthest): {', '.join(nearby_entities)}\n\n"
else:
observation += f"Nearby entities (nearest to farthest): None\n\n"
observation += f"Health: {health:.1f}/20\n\n"
observation += f"Hunger: {hunger:.1f}/20\n\n"
observation += f"Position: x={position['x']:.1f}, y={position['y']:.1f}, z={position['z']:.1f}\n\n"
observation += f"Equipment: {equipment}\n\n"
observation += f"Inventory ({inventory_used}/36): {'Empty' if not inventory else ', '.join(inventory)}\n\n"
# 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"
observation += f"Critique: {critique or 'None'}\n\n"
return HumanMessage(content=observation)
def encapsule_message(
self,
events,
code="",
task="",
context="",
critique="",
skills=[],
*args,
**kwargs,
):
system_message = self.render_system_message(skills=skills)
human_message = self.render_human_message(
events=events, code=code, task=task, context=context, critique=critique
)
return {
"system_msg": [system_message.content],
"human_msg": human_message.content,
}
async def _observe(self) -> int:
await super()._observe()
for msg in self._rc.news:
@ -42,26 +205,43 @@ class ActionDeveloper(Base):
] # only relevant msgs count as observed news
logger.info(len(self._rc.news))
return len(self._rc.news)
async def generate_action_code(self, human_msg, system_msg, *args, **kwargs):
code = await GenerateActionCode().run(human_msg)
logger.info(code)
msg = Message(content=f"test_action", instruct_content="generate_action_code", role=self.profile)
logger.info(msg)
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)
msg = Message(
content=f"{code}",
instruct_content="generate_action_code",
role=self.profile,
)
# logger.info(msg)
return msg
async def _act(self) -> Message:
todo = self._rc.todo
logger.debug(f"Todo is {todo}")
# 获取最新的游戏周边信息
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
critique = self.game_memory.critique
skills = self.game_memory.skills
message = self.encapsule_message(task, context)
message = self.encapsule_message(
events=events,
code=code,
task=task,
context=context,
critique=critique,
skills=skills,
)
logger.info(todo)
handler_map = {
GenerateActionCode: self.generate_action_code,
}
handler = handler_map.get(type(todo))
@ -69,10 +249,9 @@ class ActionDeveloper(Base):
if handler:
msg = await handler(**message)
logger.info(msg)
msg.cause_by = type(todo)
logger.info(msg.send_to)
self._publish_message(msg)
return msg
raise ValueError(f"Unknown todo type: {type(todo)}")
raise ValueError(f"Unknown todo type: {type(todo)}")

View file

@ -8,7 +8,6 @@ import json
from metagpt.logs import logger
from metagpt.roles.role import Role
from metagpt.schema import HumanMessage, SystemMessage
from typing import Dict
from pydantic import BaseModel

View file

@ -4,4 +4,5 @@
# @Desc :
from .load_prompts import load_prompt
from .json_utils import *
from .file_utils import *
from .file_utils import *
from .action_rsp_parser import parse_js_code, parse_action_response

View file

@ -0,0 +1,91 @@
import re
import time
from javascript import require
def parse_js_code(msg: str):
'''
Extract and Parse JavaScript code blocks
'''
babel = require("@babel/core")
code_pattern = re.compile(r"```(?:javascript|js)(.*?)```", re.DOTALL)
code = "\n".join(code_pattern.findall(msg))
parsed = babel.parse(code)
return parsed
def parse_action_response(msg: str):
"""
Input:
'''
Explain: ...
Plan: ...
Code:
```javascript
...
```
'''
Return:
{
"program_code": program_code,
"program_name": main_function["name"],
"exec_code": exec_code,
} or
"{error}"
Refer to @ https://github.com/MineDojo/Voyager/blob/main/voyager/agents/action.py
"""
retry = 3
error = None # 3 times failed return error
babel_generator = require("@babel/generator").default
while retry > 0:
try:
parsed = parse_js_code(msg)
# Collect func list: check if func & async
functions = []
assert len(list(parsed.program.body)) > 0, "No functions found"
for i, node in enumerate(parsed.program.body):
if node.type != "FunctionDeclaration":
continue
node_type = (
"AsyncFunctionDeclaration"
if node["async"]
else "FunctionDeclaration"
)
functions.append(
{
"name": node.id.name,
"type": node_type,
"body": babel_generator(node).code,
"params": list(node["params"]),
}
)
# Ensure main_function is the last async function
main_function = None
for function in reversed(functions):
if function["type"] == "AsyncFunctionDeclaration":
main_function = function
break
assert (
main_function is not None
), "No async function found. Your main function must be async."
assert (
len(main_function["params"]) == 1
and main_function["params"][0].name == "bot"
), f"Main function {main_function['name']} must take a single argument named 'bot'"
# Split to program_code & exec_code for output
program_code = "\n\n".join(function["body"] for function in functions)
exec_code = f"await {main_function['name']}(bot);"
return {
"program_code": program_code,
"program_name": main_function["name"],
"exec_code": exec_code,
}
except Exception as e:
retry -= 1
error = e
time.sleep(1)
return f"Error parsing action response (before program execution): {error}"

View file

@ -15,6 +15,7 @@ is_dir = os.path.isdir
get_dir = os.path.dirname
def is_sequence(obj):
"""
Returns:
@ -78,7 +79,8 @@ def load_text(*fpaths, by_lines=False):
def load_text_lines(*fpaths):
return load_text(*fpaths, by_lines=True)
# aliases to be consistent with other load_* and dump_*
text_load = load_text
read_text = load_text
read_text_lines = load_text_lines
read_text_lines = load_text_lines

View file

@ -6,37 +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:
@ -144,6 +113,7 @@ def correct_json(json_str: str) -> str:
return balanced_str
return json_str
def fix_and_parse_json(
json_str: str, try_to_fix_with_gpt: bool = True
) -> Union[str, Dict[Any, Any]]:
@ -164,4 +134,4 @@ def fix_and_parse_json(
json_str = json_str[: last_brace_index + 1]
return json.loads(json_str)
except json.JSONDecodeError as e: # noqa: F841
raise e
raise e

View file

@ -4,7 +4,8 @@
# @Desc :
import pkg_resources
from .file_utils import load_text
def load_prompt(prompt):
package_path = pkg_resources.resource_filename("metagpt", "")
return load_text(f"{package_path}/prompts/minecraft/{prompt}.txt")
return load_text(f"{package_path}/prompts/minecraft/{prompt}.txt")

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

@ -0,0 +1,93 @@
import asyncio
from metagpt.minecraft_team import GameEnvironment
from metagpt.roles.minecraft.action_developer import ActionDeveloper
from metagpt.logs import logger
async def main():
events = [
[
"observe",
{
"voxels": ["grass_block", "dirt", "grass"],
"status": {
"health": 20,
"food": 20,
"saturation": 5,
"oxygen": 20,
"position": {"x": 0.5, "y": 84, "z": -207.5},
"velocity": {"x": 0, "y": -0.0784000015258789, "z": 0},
"yaw": 3.141592653589793,
"pitch": 0,
"onGround": True,
"equipment": [None, None, None, None, None, None],
"name": "bot",
"isInWater": False,
"isInLava": False,
"isCollidedHorizontally": False,
"isCollidedVertically": True,
"biome": "plains",
"entities": {
"chicken": 29.071822119730644,
"sheep": 20.361212992763768,
},
"timeOfDay": "day",
"inventoryUsed": 0,
"elapsedTime": 41,
},
"inventory": {},
"nearbyChests": {"(1344, 64, 1381)": "Unknown"},
"blockRecords": ["grass_block", "dirt", "grass"],
},
]
]
code = """
async function collectBamboo(bot) {
// Equip the iron sword
const ironSword = bot.inventory.findInventoryItem(mcData.itemsByName.iron_sword.id);
await bot.equip(ironSword, "hand");
// Find bamboo plants using the exploreUntil function
const bambooPlants = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {
const bambooPlants = bot.findBlocks({
matching: block => block.name === "bamboo",
maxDistance: 32,
count: 10
});
return bambooPlants.length >= 10 ? bambooPlants : null;
});
if (!bambooPlants) {
bot.chat("Could not find enough bamboo plants.");
return;
}
// Break 10 bamboo plants using the iron sword
for (const bambooPlant of bambooPlants) {
const block = bot.blockAt(bambooPlant);
await bot.dig(block);
}
bot.chat("Broke 10 bamboo plants.");
// Collect the dropped bamboo items
for (const bambooPlant of bambooPlants) {
await bot.pathfinder.goto(new GoalBlock(bambooPlant.x, bambooPlant.y, bambooPlant.z));
}
bot.chat("Collected 10 bamboo.");
}
"""
ad = ActionDeveloper()
ge = GameEnvironment()
ge.update_event(events)
ad.set_memory(shared_memory=ge)
msg = ad.encapsule_message(events=ge.event, code=code)
logger.info(f"Encapsuled_message: {msg}")
parsed_result = await ad.generate_action_code(**msg)
logger.info(f"Parsed_code_updating: {parsed_result}")
if __name__ == "__main__":
asyncio.run(main())

View file

@ -0,0 +1,21 @@
# -*- coding: utf-8 -*-
# @Date : 2023/09/28 00:03
# @Author : yuymf
# @Desc :
import asyncio
from metagpt.logs import logger
from metagpt.minecraft_team import GameEnvironment
async def main():
test_code = "bot.chat(`/time set ${getNextTime()}`);"
mc_port = 2745
ge = GameEnvironment()
ge.set_mc_port(mc_port)
ge.update_code(test_code)
result = await ge.on_event()
logger.info("On event test done")
if __name__ == "__main__":
asyncio.run(main())

View file

@ -0,0 +1,69 @@
# -*- coding: utf-8 -*-
# @Date : 2023/09/28 00:08
# @Author : yuymf
# @Desc :
from metagpt.utils.minecraft import parse_js_code, parse_action_response
from metagpt.logs import logger
from typing import Any
if __name__ == "__main__":
msg = '''
Explain: The code from the last round is a function called `collectBamboo` that is supposed to collect bamboo plants. It equips an iron sword, finds bamboo plants using the `exploreUntil` function, breaks 10 bamboo plants using the iron sword, and then collects the dropped bamboo items.
Plan:
1) Check if the bot has an iron sword in its inventory. If not, collect the necessary materials and craft an iron sword using the `craftItem` function.
2) Use the `exploreUntil` function to find at least 10 bamboo plants. If the function times out or cannot find enough bamboo plants, return and chat "Could not find enough bamboo plants."
3) Equip the iron sword.
4) Iterate over the found bamboo plants and break them using the iron sword.
5) Chat "Broke 10 bamboo plants."
6) Iterate over the found bamboo plants and collect the dropped bamboo items.
7) Chat "Collected 10 bamboo."
Code:
```javascript
async function collectBamboo(bot) {
// Check if the bot has an iron sword
const ironSword = bot.inventory.findInventoryItem(mcData.itemsByName.iron_sword.id);
if (!ironSword) {
// Collect the necessary materials to craft an iron sword
await mineBlock(bot, "iron_ore", 3);
await smeltItem(bot, "iron_ore", "oak_planks", 3);
await craftItem(bot, "iron_sword", 1);
}
// Find bamboo plants using the exploreUntil function
const bambooPlants = await exploreUntil(bot, new Vec3(1, 0, 1), 60, () => {
const bambooPlants = bot.findBlocks({
matching: block => block.name === "bamboo",
maxDistance: 32,
count: 10
});
return bambooPlants.length >= 10 ? bambooPlants : null;
});
if (!bambooPlants) {
bot.chat("Could not find enough bamboo plants.");
return;
}
// Equip the iron sword
await bot.equip(ironSword, "hand");
// Break 10 bamboo plants using the iron sword
for (const bambooPlant of bambooPlants) {
const block = bot.blockAt(bambooPlant);
await bot.dig(block);
}
bot.chat("Broke 10 bamboo plants.");
// Collect the dropped bamboo items
for (const bambooPlant of bambooPlants) {
await bot.pathfinder.goto(new GoalBlock(bambooPlant.x, bambooPlant.y, bambooPlant.z));
}
bot.chat("Collected 10 bamboo.");
}
```
'''
logger.info(f"Parse_js_code result is HERE: {parse_js_code(msg)}")
logger.info(f"Parse_action_response result is HERE: {parse_action_response(msg)}")