mirror of
https://github.com/FoundationAgents/MetaGPT.git
synced 2026-06-26 15:49:42 +02:00
Communicate with mineflayer & update on_event()
This commit is contained in:
parent
1bb2450949
commit
7225b70e25
32 changed files with 3274 additions and 20 deletions
|
|
@ -2,3 +2,6 @@
|
|||
# @Date : 2023/9/24 0:32
|
||||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
from .load_prompts import load_prompt
|
||||
from .json_utils import *
|
||||
from .file_utils import *
|
||||
569
metagpt/utils/minecraft/file_utils.py
Normal file
569
metagpt/utils/minecraft/file_utils.py
Normal file
|
|
@ -0,0 +1,569 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/09/25 16:13
|
||||
# @Author : yuymf
|
||||
# @Desc : Temp Using :File system utils.@ https://github.com/MineDojo/Voyager/blob/main/voyager/utils/file_utils.py
|
||||
import collections
|
||||
import os
|
||||
import pickle
|
||||
import sys
|
||||
import errno
|
||||
import shutil
|
||||
import glob
|
||||
|
||||
# import pwd
|
||||
import codecs
|
||||
import hashlib
|
||||
import tarfile
|
||||
import fnmatch
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from socket import gethostname
|
||||
import logging
|
||||
|
||||
|
||||
f_ext = os.path.splitext
|
||||
|
||||
f_size = os.path.getsize
|
||||
|
||||
is_file = os.path.isfile
|
||||
|
||||
is_dir = os.path.isdir
|
||||
|
||||
get_dir = os.path.dirname
|
||||
|
||||
|
||||
def host_name():
|
||||
"Get host name, alias with ``socket.gethostname()``"
|
||||
return gethostname()
|
||||
|
||||
|
||||
def host_id():
|
||||
"""
|
||||
Returns: first part of hostname up to '.'
|
||||
"""
|
||||
return host_name().split(".")[0]
|
||||
|
||||
|
||||
def utf_open(fname, mode):
|
||||
"""
|
||||
Wrapper for codecs.open
|
||||
"""
|
||||
return codecs.open(fname, mode=mode, encoding="utf-8")
|
||||
|
||||
|
||||
def is_sequence(obj):
|
||||
"""
|
||||
Returns:
|
||||
True if the sequence is a collections.Sequence and not a string.
|
||||
"""
|
||||
return isinstance(obj, collections.abc.Sequence) and not isinstance(obj, str)
|
||||
|
||||
|
||||
def pack_varargs(args):
|
||||
"""
|
||||
Pack *args or a single list arg as list
|
||||
|
||||
def f(*args):
|
||||
arg_list = pack_varargs(args)
|
||||
# arg_list is now packed as a list
|
||||
"""
|
||||
assert isinstance(args, tuple), "please input the tuple `args` as in *args"
|
||||
if len(args) == 1 and is_sequence(args[0]):
|
||||
return args[0]
|
||||
else:
|
||||
return args
|
||||
|
||||
|
||||
def f_not_empty(*fpaths):
|
||||
"""
|
||||
Returns:
|
||||
True if and only if the file exists and file size > 0
|
||||
if fpath is a dir, if and only if dir exists and has at least 1 file
|
||||
"""
|
||||
fpath = f_join(*fpaths)
|
||||
if not os.path.exists(fpath):
|
||||
return False
|
||||
|
||||
if os.path.isdir(fpath):
|
||||
return len(os.listdir(fpath)) > 0
|
||||
else:
|
||||
return os.path.getsize(fpath) > 0
|
||||
|
||||
|
||||
def f_expand(fpath):
|
||||
return os.path.expandvars(os.path.expanduser(fpath))
|
||||
|
||||
|
||||
def f_exists(*fpaths):
|
||||
return os.path.exists(f_join(*fpaths))
|
||||
|
||||
|
||||
def f_join(*fpaths):
|
||||
"""
|
||||
join file paths and expand special symbols like `~` for home dir
|
||||
"""
|
||||
fpaths = pack_varargs(fpaths)
|
||||
fpath = f_expand(os.path.join(*fpaths))
|
||||
if isinstance(fpath, str):
|
||||
fpath = fpath.strip()
|
||||
return fpath
|
||||
|
||||
|
||||
def f_listdir(
|
||||
*fpaths,
|
||||
filter_ext=None,
|
||||
filter=None,
|
||||
sort=True,
|
||||
full_path=False,
|
||||
nonexist_ok=True,
|
||||
recursive=False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
full_path: True to return full paths to the dir contents
|
||||
filter: function that takes in file name and returns True to include
|
||||
nonexist_ok: True to return [] if the dir is non-existent, False to raise
|
||||
sort: sort the file names by alphabetical
|
||||
recursive: True to use os.walk to recursively list files. Note that `filter`
|
||||
will be applied to the relative path string to the root dir.
|
||||
e.g. filter will take "a/data1.txt" and "a/b/data3.txt" as input, instead of
|
||||
just the base file names "data1.txt" and "data3.txt".
|
||||
if False, will simply call os.listdir()
|
||||
"""
|
||||
assert not (filter_ext and filter), "filter_ext and filter are mutually exclusive"
|
||||
dir_path = f_join(*fpaths)
|
||||
if not os.path.exists(dir_path) and nonexist_ok:
|
||||
return []
|
||||
if recursive:
|
||||
files = [
|
||||
os.path.join(os.path.relpath(root, dir_path), file)
|
||||
for root, _, files in os.walk(dir_path)
|
||||
for file in files
|
||||
]
|
||||
else:
|
||||
files = os.listdir(dir_path)
|
||||
if filter is not None:
|
||||
files = [f for f in files if filter(f)]
|
||||
elif filter_ext is not None:
|
||||
files = [f for f in files if f.endswith(filter_ext)]
|
||||
if sort:
|
||||
files.sort()
|
||||
if full_path:
|
||||
return [os.path.join(dir_path, f) for f in files]
|
||||
else:
|
||||
return files
|
||||
|
||||
|
||||
def f_mkdir(*fpaths):
|
||||
"""
|
||||
Recursively creates all the subdirs
|
||||
If exist, do nothing.
|
||||
"""
|
||||
fpath = f_join(*fpaths)
|
||||
os.makedirs(fpath, exist_ok=True)
|
||||
return fpath
|
||||
|
||||
|
||||
def f_mkdir_in_path(*fpaths):
|
||||
"""
|
||||
fpath is a file,
|
||||
recursively creates all the parent dirs that lead to the file
|
||||
If exist, do nothing.
|
||||
"""
|
||||
os.makedirs(get_dir(f_join(*fpaths)), exist_ok=True)
|
||||
|
||||
|
||||
def last_part_in_path(fpath):
|
||||
"""
|
||||
https://stackoverflow.com/questions/3925096/how-to-get-only-the-last-part-of-a-path-in-python
|
||||
"""
|
||||
return os.path.basename(os.path.normpath(f_expand(fpath)))
|
||||
|
||||
|
||||
def is_abs_path(*fpath):
|
||||
return os.path.isabs(f_join(*fpath))
|
||||
|
||||
|
||||
def is_relative_path(*fpath):
|
||||
return not is_abs_path(f_join(*fpath))
|
||||
|
||||
|
||||
def f_time(*fpath):
|
||||
"File modification time"
|
||||
return str(os.path.getctime(f_join(*fpath)))
|
||||
|
||||
|
||||
def f_append_before_ext(fpath, suffix):
|
||||
"""
|
||||
Append a suffix to file name and retain its extension
|
||||
"""
|
||||
name, ext = f_ext(fpath)
|
||||
return name + suffix + ext
|
||||
|
||||
|
||||
def f_add_ext(fpath, ext):
|
||||
"""
|
||||
Append an extension if not already there
|
||||
Args:
|
||||
ext: will add a preceding `.` if doesn't exist
|
||||
"""
|
||||
if not ext.startswith("."):
|
||||
ext = "." + ext
|
||||
if fpath.endswith(ext):
|
||||
return fpath
|
||||
else:
|
||||
return fpath + ext
|
||||
|
||||
|
||||
def f_has_ext(fpath, ext):
|
||||
"Test if file path is a text file"
|
||||
_, actual_ext = f_ext(fpath)
|
||||
return actual_ext == "." + ext.lstrip(".")
|
||||
|
||||
|
||||
def f_glob(*fpath):
|
||||
return glob.glob(f_join(*fpath), recursive=True)
|
||||
|
||||
|
||||
def f_remove(*fpath, verbose=False, dry_run=False):
|
||||
"""
|
||||
If exist, remove. Supports both dir and file. Supports glob wildcard.
|
||||
"""
|
||||
assert isinstance(verbose, bool)
|
||||
fpath = f_join(fpath)
|
||||
if dry_run:
|
||||
print("Dry run, delete:", fpath)
|
||||
return
|
||||
for f in glob.glob(fpath):
|
||||
try:
|
||||
shutil.rmtree(f)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOTDIR:
|
||||
try:
|
||||
os.remove(f)
|
||||
except: # final resort safeguard
|
||||
pass
|
||||
if verbose:
|
||||
print(f'Deleted "{fpath}"')
|
||||
|
||||
|
||||
def f_copy(fsrc, fdst, ignore=None, include=None, exists_ok=True, verbose=False):
|
||||
"""
|
||||
Supports both dir and file. Supports glob wildcard.
|
||||
"""
|
||||
fsrc, fdst = f_expand(fsrc), f_expand(fdst)
|
||||
for f in glob.glob(fsrc):
|
||||
try:
|
||||
f_copytree(f, fdst, ignore=ignore, include=include, exist_ok=exists_ok)
|
||||
except OSError as e:
|
||||
if e.errno == errno.ENOTDIR:
|
||||
shutil.copy(f, fdst)
|
||||
else:
|
||||
raise
|
||||
if verbose:
|
||||
print(f'Copied "{fsrc}" to "{fdst}"')
|
||||
|
||||
|
||||
def _f_copytree(
|
||||
src,
|
||||
dst,
|
||||
symlinks=False,
|
||||
ignore=None,
|
||||
exist_ok=True,
|
||||
copy_function=shutil.copy2,
|
||||
ignore_dangling_symlinks=False,
|
||||
):
|
||||
"""Copied from python standard lib shutil.copytree
|
||||
except that we allow exist_ok
|
||||
Use f_copytree as entry
|
||||
"""
|
||||
names = os.listdir(src)
|
||||
if ignore is not None:
|
||||
ignored_names = ignore(src, names)
|
||||
else:
|
||||
ignored_names = set()
|
||||
|
||||
os.makedirs(dst, exist_ok=exist_ok)
|
||||
errors = []
|
||||
for name in names:
|
||||
if name in ignored_names:
|
||||
continue
|
||||
srcname = os.path.join(src, name)
|
||||
dstname = os.path.join(dst, name)
|
||||
try:
|
||||
if os.path.islink(srcname):
|
||||
linkto = os.readlink(srcname)
|
||||
if symlinks:
|
||||
# We can't just leave it to `copy_function` because legacy
|
||||
# code with a custom `copy_function` may rely on copytree
|
||||
# doing the right thing.
|
||||
os.symlink(linkto, dstname)
|
||||
shutil.copystat(srcname, dstname, follow_symlinks=not symlinks)
|
||||
else:
|
||||
# ignore dangling symlink if the flag is on
|
||||
if not os.path.exists(linkto) and ignore_dangling_symlinks:
|
||||
continue
|
||||
# otherwise let the copy occurs. copy2 will raise an error
|
||||
if os.path.isdir(srcname):
|
||||
_f_copytree(
|
||||
srcname, dstname, symlinks, ignore, exist_ok, copy_function
|
||||
)
|
||||
else:
|
||||
copy_function(srcname, dstname)
|
||||
elif os.path.isdir(srcname):
|
||||
_f_copytree(srcname, dstname, symlinks, ignore, exist_ok, copy_function)
|
||||
else:
|
||||
# Will raise a SpecialFileError for unsupported file types
|
||||
copy_function(srcname, dstname)
|
||||
# catch the Error from the recursive copytree so that we can
|
||||
# continue with other files
|
||||
except shutil.Error as err:
|
||||
errors.extend(err.args[0])
|
||||
except OSError as why:
|
||||
errors.append((srcname, dstname, str(why)))
|
||||
try:
|
||||
shutil.copystat(src, dst)
|
||||
except OSError as why:
|
||||
# Copying file access times may fail on Windows
|
||||
if getattr(why, "winerror", None) is None:
|
||||
errors.append((src, dst, str(why)))
|
||||
if errors:
|
||||
raise shutil.Error(errors)
|
||||
return dst
|
||||
|
||||
|
||||
def _include_patterns(*patterns):
|
||||
"""Factory function that can be used with copytree() ignore parameter.
|
||||
|
||||
Arguments define a sequence of glob-style patterns
|
||||
that are used to specify what files to NOT ignore.
|
||||
Creates and returns a function that determines this for each directory
|
||||
in the file hierarchy rooted at the source directory when used with
|
||||
shutil.copytree().
|
||||
"""
|
||||
|
||||
def _ignore_patterns(path, names):
|
||||
keep = set(
|
||||
name for pattern in patterns for name in fnmatch.filter(names, pattern)
|
||||
)
|
||||
ignore = set(
|
||||
name
|
||||
for name in names
|
||||
if name not in keep and not os.path.isdir(os.path.join(path, name))
|
||||
)
|
||||
return ignore
|
||||
|
||||
return _ignore_patterns
|
||||
|
||||
|
||||
def f_copytree(fsrc, fdst, symlinks=False, ignore=None, include=None, exist_ok=True):
|
||||
fsrc, fdst = f_expand(fsrc), f_expand(fdst)
|
||||
assert (ignore is None) or (
|
||||
include is None
|
||||
), "ignore= and include= are mutually exclusive"
|
||||
if ignore:
|
||||
ignore = shutil.ignore_patterns(*ignore)
|
||||
elif include:
|
||||
ignore = _include_patterns(*include)
|
||||
_f_copytree(fsrc, fdst, ignore=ignore, symlinks=symlinks, exist_ok=exist_ok)
|
||||
|
||||
|
||||
def f_move(fsrc, fdst):
|
||||
fsrc, fdst = f_expand(fsrc), f_expand(fdst)
|
||||
for f in glob.glob(fsrc):
|
||||
shutil.move(f, fdst)
|
||||
|
||||
|
||||
def f_split_path(fpath, normpath=True):
|
||||
"""
|
||||
Splits path into a list of its component folders
|
||||
|
||||
Args:
|
||||
normpath: call os.path.normpath to remove redundant '/' and
|
||||
up-level references like ".."
|
||||
"""
|
||||
if normpath:
|
||||
fpath = os.path.normpath(fpath)
|
||||
allparts = []
|
||||
while 1:
|
||||
parts = os.path.split(fpath)
|
||||
if parts[0] == fpath: # sentinel for absolute paths
|
||||
allparts.insert(0, parts[0])
|
||||
break
|
||||
elif parts[1] == fpath: # sentinel for relative paths
|
||||
allparts.insert(0, parts[1])
|
||||
break
|
||||
else:
|
||||
fpath = parts[0]
|
||||
allparts.insert(0, parts[1])
|
||||
return allparts
|
||||
|
||||
|
||||
def get_script_dir():
|
||||
"""
|
||||
Returns: the dir of current script
|
||||
"""
|
||||
return os.path.dirname(os.path.realpath(sys.argv[0]))
|
||||
|
||||
|
||||
def get_script_file_name():
|
||||
"""
|
||||
Returns: the dir of current script
|
||||
"""
|
||||
return os.path.basename(sys.argv[0])
|
||||
|
||||
|
||||
def get_script_self_path():
|
||||
"""
|
||||
Returns: the dir of current script
|
||||
"""
|
||||
return os.path.realpath(sys.argv[0])
|
||||
|
||||
|
||||
def get_parent_dir(location, abspath=False):
|
||||
"""
|
||||
Args:
|
||||
location: current directory or file
|
||||
|
||||
Returns:
|
||||
parent directory absolute or relative path
|
||||
"""
|
||||
_path = os.path.abspath if abspath else os.path.relpath
|
||||
return _path(f_join(location, os.pardir))
|
||||
|
||||
|
||||
def md5_checksum(*fpath):
|
||||
"""
|
||||
File md5 signature
|
||||
"""
|
||||
hash_md5 = hashlib.md5()
|
||||
with open(f_join(*fpath), "rb") as f:
|
||||
for chunk in iter(lambda: f.read(65536), b""):
|
||||
hash_md5.update(chunk)
|
||||
return hash_md5.hexdigest()
|
||||
|
||||
|
||||
def create_tar(fsrc, output_tarball, include=None, ignore=None, compress_mode="gz"):
|
||||
"""
|
||||
Args:
|
||||
fsrc: source file or folder
|
||||
output_tarball: output tar file name
|
||||
compress_mode: "gz", "bz2", "xz" or "" (empty for uncompressed write)
|
||||
include: include pattern, will trigger copy to temp directory
|
||||
ignore: ignore pattern, will trigger copy to temp directory
|
||||
"""
|
||||
fsrc, output_tarball = f_expand(fsrc), f_expand(output_tarball)
|
||||
assert compress_mode in ["gz", "bz2", "xz", ""]
|
||||
src_base = os.path.basename(fsrc)
|
||||
|
||||
tempdir = None
|
||||
if include or ignore:
|
||||
tempdir = tempfile.mkdtemp()
|
||||
tempdest = f_join(tempdir, src_base)
|
||||
f_copy(fsrc, tempdest, include=include, ignore=ignore)
|
||||
fsrc = tempdest
|
||||
|
||||
with tarfile.open(output_tarball, "w:" + compress_mode) as tar:
|
||||
tar.add(fsrc, arcname=src_base)
|
||||
|
||||
if tempdir:
|
||||
f_remove(tempdir)
|
||||
|
||||
|
||||
def extract_tar(source_tarball, output_dir=".", members=None):
|
||||
"""
|
||||
Args:
|
||||
source_tarball: extract members from archive
|
||||
output_dir: default to current working dir
|
||||
members: must be a subset of the list returned by getmembers()
|
||||
"""
|
||||
source_tarball, output_dir = f_expand(source_tarball), f_expand(output_dir)
|
||||
with tarfile.open(source_tarball, "r:*") as tar:
|
||||
tar.extractall(output_dir, members=members)
|
||||
|
||||
|
||||
def move_with_backup(*fpath, suffix=".bak"):
|
||||
"""
|
||||
Ensures that a path is not occupied. If there is a file, rename it by
|
||||
adding @suffix. Resursively backs up everything.
|
||||
|
||||
Args:
|
||||
fpath: file path to clear
|
||||
suffix: Add to backed up files (default: {'.bak'})
|
||||
"""
|
||||
fpath = str(f_join(*fpath))
|
||||
if os.path.exists(fpath):
|
||||
move_with_backup(fpath + suffix)
|
||||
shutil.move(fpath, fpath + suffix)
|
||||
|
||||
|
||||
def insert_before_ext(name, insert):
|
||||
"""
|
||||
log.txt -> log.ep50.txt
|
||||
"""
|
||||
name, ext = os.path.splitext(name)
|
||||
return name + insert + ext
|
||||
|
||||
|
||||
def timestamp_file_name(fname):
|
||||
timestr = datetime.now().strftime("_%H-%M-%S_%m-%d-%y")
|
||||
return insert_before_ext(fname, timestr)
|
||||
|
||||
|
||||
def get_file_lock(*fpath, timeout: int = 15, logging_level="critical"):
|
||||
"""
|
||||
NFS-safe filesystem-backed lock. `pip install flufl.lock`
|
||||
https://flufllock.readthedocs.io/en/stable/apiref.html
|
||||
|
||||
Args:
|
||||
fpath: should be a path on NFS so that every process can see it
|
||||
timeout: seconds
|
||||
"""
|
||||
from flufl.lock import Lock
|
||||
|
||||
logging.getLogger("flufl.lock").setLevel(logging_level.upper())
|
||||
return Lock(f_join(*fpath), lifetime=timeout)
|
||||
|
||||
|
||||
def load_pickle(*fpaths):
|
||||
with open(f_join(*fpaths), "rb") as fp:
|
||||
return pickle.load(fp)
|
||||
|
||||
|
||||
def dump_pickle(data, *fpaths):
|
||||
with open(f_join(*fpaths), "wb") as fp:
|
||||
pickle.dump(data, fp)
|
||||
|
||||
|
||||
def load_text(*fpaths, by_lines=False):
|
||||
with open(f_join(*fpaths), "r") as fp:
|
||||
if by_lines:
|
||||
return fp.readlines()
|
||||
else:
|
||||
return fp.read()
|
||||
|
||||
|
||||
def load_text_lines(*fpaths):
|
||||
return load_text(*fpaths, by_lines=True)
|
||||
|
||||
|
||||
def dump_text(s, *fpaths):
|
||||
with open(f_join(*fpaths), "w") as fp:
|
||||
fp.write(s)
|
||||
|
||||
|
||||
def dump_text_lines(lines: list[str], *fpaths, add_newline=True):
|
||||
with open(f_join(*fpaths), "w") as fp:
|
||||
for line in lines:
|
||||
print(line, file=fp, end="\n" if add_newline else "")
|
||||
|
||||
|
||||
# aliases to be consistent with other load_* and dump_*
|
||||
pickle_load = load_pickle
|
||||
pickle_dump = dump_pickle
|
||||
text_load = load_text
|
||||
read_text = load_text
|
||||
read_text_lines = load_text_lines
|
||||
write_text = dump_text
|
||||
write_text_lines = dump_text_lines
|
||||
text_dump = dump_text
|
||||
231
metagpt/utils/minecraft/json_utils.py
Normal file
231
metagpt/utils/minecraft/json_utils.py
Normal file
|
|
@ -0,0 +1,231 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/09/25 19:27
|
||||
# @Author : yuymf
|
||||
# @Desc : Temp using @https://github.com/MineDojo/Voyager/blob/main/voyager/utils/json_utils.py
|
||||
|
||||
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:
|
||||
"""Extract the character position from the JSONDecodeError message.
|
||||
Args:
|
||||
error_message (str): The error message from the JSONDecodeError
|
||||
exception.
|
||||
Returns:
|
||||
int: The character position.
|
||||
"""
|
||||
import re
|
||||
|
||||
char_pattern = re.compile(r"\(char (\d+)\)")
|
||||
if match := char_pattern.search(error_message):
|
||||
return int(match[1])
|
||||
else:
|
||||
raise ValueError("Character position not found in the error message.")
|
||||
|
||||
|
||||
def add_quotes_to_property_names(json_string: str) -> str:
|
||||
"""
|
||||
Add quotes to property names in a JSON string.
|
||||
Args:
|
||||
json_string (str): The JSON string.
|
||||
Returns:
|
||||
str: The JSON string with quotes added to property names.
|
||||
"""
|
||||
|
||||
def replace_func(match):
|
||||
return f'"{match.group(1)}":'
|
||||
|
||||
property_name_pattern = re.compile(r"(\w+):")
|
||||
corrected_json_string = property_name_pattern.sub(replace_func, json_string)
|
||||
|
||||
try:
|
||||
json.loads(corrected_json_string)
|
||||
return corrected_json_string
|
||||
except json.JSONDecodeError as e:
|
||||
raise e
|
||||
|
||||
|
||||
def balance_braces(json_string: str) -> str:
|
||||
"""
|
||||
Balance the braces in a JSON string.
|
||||
Args:
|
||||
json_string (str): The JSON string.
|
||||
Returns:
|
||||
str: The JSON string with braces balanced.
|
||||
"""
|
||||
|
||||
open_braces_count = json_string.count("{")
|
||||
close_braces_count = json_string.count("}")
|
||||
|
||||
while open_braces_count > close_braces_count:
|
||||
json_string += "}"
|
||||
close_braces_count += 1
|
||||
|
||||
while close_braces_count > open_braces_count:
|
||||
json_string = json_string.rstrip("}")
|
||||
close_braces_count -= 1
|
||||
|
||||
try:
|
||||
json.loads(json_string)
|
||||
return json_string
|
||||
except json.JSONDecodeError as e:
|
||||
raise e
|
||||
|
||||
|
||||
def fix_invalid_escape(json_str: str, error_message: str) -> str:
|
||||
while error_message.startswith("Invalid \\escape"):
|
||||
bad_escape_location = extract_char_position(error_message)
|
||||
json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :]
|
||||
try:
|
||||
json.loads(json_str)
|
||||
return json_str
|
||||
except json.JSONDecodeError as e:
|
||||
error_message = str(e)
|
||||
return json_str
|
||||
|
||||
|
||||
def correct_json(json_str: str) -> str:
|
||||
"""
|
||||
Correct common JSON errors.
|
||||
Args:
|
||||
json_str (str): The JSON string.
|
||||
"""
|
||||
|
||||
try:
|
||||
json.loads(json_str)
|
||||
return json_str
|
||||
except json.JSONDecodeError as e:
|
||||
error_message = str(e)
|
||||
if error_message.startswith("Invalid \\escape"):
|
||||
json_str = fix_invalid_escape(json_str, error_message)
|
||||
if error_message.startswith(
|
||||
"Expecting property name enclosed in double quotes"
|
||||
):
|
||||
json_str = add_quotes_to_property_names(json_str)
|
||||
try:
|
||||
json.loads(json_str)
|
||||
return json_str
|
||||
except json.JSONDecodeError as e:
|
||||
error_message = str(e)
|
||||
if balanced_str := balance_braces(json_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]]:
|
||||
"""Fix and parse JSON string"""
|
||||
try:
|
||||
json_str = json_str.replace("\t", "")
|
||||
return json.loads(json_str)
|
||||
except json.JSONDecodeError as _: # noqa: F841
|
||||
json_str = correct_json(json_str)
|
||||
try:
|
||||
return json.loads(json_str)
|
||||
except json.JSONDecodeError as _: # noqa: F841
|
||||
pass
|
||||
# Let's do something manually:
|
||||
# sometimes GPT responds with something BEFORE the braces:
|
||||
# "I'm sorry, I don't understand. Please try again."
|
||||
# {"text": "I'm sorry, I don't understand. Please try again.",
|
||||
# "confidence": 0.0}
|
||||
# So let's try to find the first brace and then parse the rest
|
||||
# of the string
|
||||
try:
|
||||
brace_index = json_str.index("{")
|
||||
json_str = json_str[brace_index:]
|
||||
last_brace_index = json_str.rindex("}")
|
||||
json_str = json_str[: last_brace_index + 1]
|
||||
return json.loads(json_str)
|
||||
except json.JSONDecodeError as e: # noqa: F841
|
||||
# if try_to_fix_with_gpt:
|
||||
# print(
|
||||
# "Warning: Failed to parse AI output, attempting to fix."
|
||||
# "\n If you see this warning frequently, it's likely that"
|
||||
# " your prompt is confusing the AI. Try changing it up"
|
||||
# " slightly."
|
||||
# )
|
||||
# # Now try to fix this up using the ai_functions
|
||||
# ai_fixed_json = fix_json(json_str, JSON_SCHEMA)
|
||||
#
|
||||
# if ai_fixed_json != "failed":
|
||||
# return json.loads(ai_fixed_json)
|
||||
# else:
|
||||
# # This allows the AI to react to the error message,
|
||||
# # which usually results in it correcting its ways.
|
||||
# print("Failed to fix ai output, telling the AI.")
|
||||
# return json_str
|
||||
# else:
|
||||
raise e
|
||||
|
||||
|
||||
# def fix_json(json_str: str, schema: str) -> str:
|
||||
# """Fix the given JSON string to make it parseable and fully complient with the provided schema."""
|
||||
#
|
||||
# # Try to fix the JSON using gpt:
|
||||
# function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
|
||||
# args = [f"'''{json_str}'''", f"'''{schema}'''"]
|
||||
# description_string = (
|
||||
# "Fixes the provided JSON string to make it parseable"
|
||||
# " and fully complient with the provided schema.\n If an object or"
|
||||
# " field specified in the schema isn't contained within the correct"
|
||||
# " JSON, it is ommited.\n This function is brilliant at guessing"
|
||||
# " when the format is incorrect."
|
||||
# )
|
||||
#
|
||||
# # If it doesn't already start with a "`", add one:
|
||||
# if not json_str.startswith("`"):
|
||||
# json_str = "```json\n" + json_str + "\n```"
|
||||
# result_string = call_ai_function(
|
||||
# function_string, args, description_string, model=cfg.fast_llm_model
|
||||
# )
|
||||
# if cfg.debug:
|
||||
# print("------------ JSON FIX ATTEMPT ---------------")
|
||||
# print(f"Original JSON: {json_str}")
|
||||
# print("-----------")
|
||||
# print(f"Fixed JSON: {result_string}")
|
||||
# print("----------- END OF FIX ATTEMPT ----------------")
|
||||
#
|
||||
# try:
|
||||
# json.loads(result_string) # just check the validity
|
||||
# return result_string
|
||||
# except: # noqa: E722
|
||||
# # Get the call stack:
|
||||
# # import traceback
|
||||
# # call_stack = traceback.format_exc()
|
||||
# # print(f"Failed to fix JSON: '{json_str}' "+call_stack)
|
||||
# return "failed"
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
# @Author : stellahong (stellahong@fuzhi.ai)
|
||||
# @Desc :
|
||||
import pkg_resources
|
||||
|
||||
|
||||
from .file_utils import load_text
|
||||
|
||||
def load_prompt(prompt):
|
||||
pass
|
||||
package_path = pkg_resources.resource_filename("metagpt", "")
|
||||
return load_text(f"{package_path}/prompts/minecraft/{prompt}.txt")
|
||||
94
metagpt/utils/minecraft/process_monitor.py
Normal file
94
metagpt/utils/minecraft/process_monitor.py
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @Date : 2023/09/25 16:12
|
||||
# @Author : yuymf
|
||||
# @Desc : Temp using:@https://github.com/MineDojo/Voyager/blob/main/voyager/env/process_monitor.py
|
||||
import time
|
||||
import re
|
||||
import warnings
|
||||
from typing import List
|
||||
|
||||
import psutil
|
||||
import subprocess
|
||||
import logging
|
||||
import threading
|
||||
|
||||
import metagpt.utils.minecraft as U
|
||||
|
||||
|
||||
class SubprocessMonitor:
|
||||
def __init__(
|
||||
self,
|
||||
commands: List[str],
|
||||
name: str,
|
||||
ready_match: str = r".*",
|
||||
log_path: str = "logs",
|
||||
callback_match: str = r"^(?!x)x$", # regex that will never match
|
||||
callback: callable = None,
|
||||
finished_callback: callable = None,
|
||||
):
|
||||
self.commands = commands
|
||||
start_time = time.strftime("%Y%m%d_%H%M%S")
|
||||
self.name = name
|
||||
self.logger = logging.getLogger(name)
|
||||
handler = logging.FileHandler(U.f_join(log_path, f"{start_time}.log"))
|
||||
formatter = logging.Formatter(
|
||||
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
||||
)
|
||||
handler.setFormatter(formatter)
|
||||
self.logger.addHandler(handler)
|
||||
self.logger.setLevel(logging.INFO)
|
||||
self.process = None
|
||||
self.ready_match = ready_match
|
||||
self.ready_event = None
|
||||
self.ready_line = None
|
||||
self.callback_match = callback_match
|
||||
self.callback = callback
|
||||
self.finished_callback = finished_callback
|
||||
self.thread = None
|
||||
|
||||
def _start(self):
|
||||
self.logger.info(f"Starting subprocess with commands: {self.commands}")
|
||||
|
||||
self.process = psutil.Popen(
|
||||
self.commands,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.STDOUT,
|
||||
universal_newlines=True,
|
||||
)
|
||||
print(f"Subprocess {self.name} started with PID {self.process.pid}.")
|
||||
for line in iter(self.process.stdout.readline, ""):
|
||||
self.logger.info(line.strip())
|
||||
if re.search(self.ready_match, line):
|
||||
self.ready_line = line
|
||||
self.logger.info("Subprocess is ready.")
|
||||
self.ready_event.set()
|
||||
if re.search(self.callback_match, line):
|
||||
self.callback()
|
||||
if not self.ready_event.is_set():
|
||||
self.ready_event.set()
|
||||
warnings.warn(f"Subprocess {self.name} failed to start.")
|
||||
if self.finished_callback:
|
||||
self.finished_callback()
|
||||
|
||||
def run(self):
|
||||
self.ready_event = threading.Event()
|
||||
self.ready_line = None
|
||||
self.thread = threading.Thread(target=self._start)
|
||||
self.thread.start()
|
||||
self.ready_event.wait()
|
||||
|
||||
def stop(self):
|
||||
self.logger.info("Stopping subprocess.")
|
||||
if self.process and self.process.is_running():
|
||||
self.process.terminate()
|
||||
self.process.wait()
|
||||
|
||||
# def __del__(self):
|
||||
# if self.process.is_running():
|
||||
# self.stop()
|
||||
|
||||
@property
|
||||
def is_running(self):
|
||||
if self.process is None:
|
||||
return False
|
||||
return self.process.is_running()
|
||||
Loading…
Add table
Add a link
Reference in a new issue