merging crafting_save and procedural generation save

This commit is contained in:
Isadora White 2025-03-03 17:06:24 -08:00
commit 932798195f
18 changed files with 23339 additions and 27278 deletions

4
.gitignore vendored
View file

@ -15,4 +15,6 @@ services/viaproxy/saves.json
services/viaproxy/viaproxy.yml
tmp/
wandb/
experiments/
experiments/
andy_*.json
jill_*.json

View file

@ -1,6 +1,6 @@
{
"name": "andy_0",
"model": "gpt-4o",
"name": "Andy_0",
"model": "gpt-4o-mini",
"modes": {
"hunting": false
},
@ -9,7 +9,7 @@
"conversation_examples": [
[
{
"role": "assistant",
"role": "user",
"content": "Hey! let's collaborate to build a stone pickaxe!"
},
{
@ -17,7 +17,7 @@
"content": "Cool, let me check what is in my inventory !inventory\nI have 10 oak logs. What do you have?"
},
{
"role": "assistant",
"role": "user",
"content": "Let me check !inventory \nI have 10 stones."
},
{
@ -25,13 +25,13 @@
"content": "Let me give you the stones and you can craft the pickaxe. !givePlayer(\"stone\", 10, \"playername\")"
},
{
"role": "assistant",
"role": "user",
"content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"
}
],
[
{
"role": "assistant",
"role": "user",
"content": "Hey! let's collaborate to build shears!"
},
{
@ -39,7 +39,7 @@
"content": "Cool, let me check what is in my inventory !inventory\nI have 1 iron ingot. What do you have?"
},
{
"role": "assistant",
"role": "user",
"content": "Let me check !inventory \nI have 1 iron ingot. "
},
{
@ -47,7 +47,7 @@
"content": "Nice let's check what we need to craft shears. !getCraftingPlan(\"shears\", 1)"
},
{
"role": "assistant",
"role": "user",
"content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"
},
{
@ -55,10 +55,58 @@
"content": "Yes, here is my iron ingot !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "assistant",
"role": "user",
"content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"
}
],
[
{
"role": "user",
"content": "Hey! Let's collaborate to build a brush"
},
{
"role": "assistant",
"content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"
},
{
"role": "system",
"content": "CRAFTING_PLAN\n- 2 sticks\n- 2 feathers\n- 1 iron ingot"
},
{
"role": "assistant",
"content": "We need 2 sticks, 2 feathers, and 1 iron ingot to craft the brush. Do you have any of these items?"
},
{
"role": "user",
"content": "Let me check !inventory \nI have 2 sticks and 2 feathers."
},
{
"role": "assistant",
"content": "Great! I have 1 iron ingot. Let me give you my iron ingot and you can craft the brush. !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "user",
"content": "Thanks for the iron ingot! Now I can craft the brush. !craftItem(\"brush\", 1)"
}
],
[
{
"role": "user",
"content": "Hey! Let's collaborate to build a brush"
},
{
"role": "assistant",
"content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"
},
{
"role": "system",
"content": "no such tool"
},
{
"role": "assistant",
"content": "I'm sorry, I don't have the crafting plan for a brush. Let me check my inventory to see if I have the items needed. !inventory Do you have access to the crafting plan?"
}
],
[
{
"role": "user",
@ -109,24 +157,6 @@
"content": "terrance: (FROM OTHER BOT)I can build the base, you can build the walls. !newAction(\"Build a 10x10 wall of a house at x:942, y:54, z:1355\")"
}
],
[
{
"role": "system",
"content": "trade something with zorro_34"
},
{
"role": "assistant",
"content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"
},
{
"role": "user",
"content": "zorro_34: (FROM OTHER BOT)Let's see... !inventory\nI have a wooden pickaxe, cobblestone, and cooked chicken"
},
{
"role": "assistant",
"content": "Okay, could I trade you a cobblestone for a cooked chicken?"
}
],
[
{
"role": "user",
@ -141,26 +171,6 @@
"content": "rupert: (FROM OTHER BOT)Alright, I'll build the walls! !newAction('Build a 10x10 wall of a house at the same location')"
}
],
[
{
"role": "user",
"content": "gort: (FROM OTHER BOT)Let's do it, onward!"
},
{
"role": "assistant",
"content": "Alright, I'll start by getting some cobblestone. !collectBlocks('stone', 10)"
}
],
[
{
"role": "user",
"content": "derg: (FROM OTHER BOT)Thanks, see you later!"
},
{
"role": "assistant",
"content": "See you later. !endConversation(\"derg\")"
}
],
[
{
"role": "user",

View file

@ -109,9 +109,11 @@ def launch_parallel_experiments(task_path,
exp_name,
num_agents=2,
model="gpt-4o",
api="openai",
num_parallel=1,
s3=False,
bucket_name="mindcraft-experiments"):
bucket_name="mindcraft-experiments",
template_profile="profiles/collab_profile.json"):
with open(task_path, 'r', encoding='utf-8') as file:
content = file.read()
@ -138,7 +140,10 @@ def launch_parallel_experiments(task_path,
experiments_folder,
exp_name,
s3=s3,
bucket_name=bucket_name)
bucket_name=bucket_name,
template_profile="profiles/collab_profile.json",
model=model,
api=api)
time.sleep(5)
def launch_server_experiment(task_path,
@ -148,9 +153,11 @@ def launch_server_experiment(task_path,
experiments_folder,
exp_name="exp",
num_agents=2,
model="gpt-4o",
model="gpt-4o",
api="openai",
s3=False,
bucket_name="mindcraft-experiments"):
bucket_name="mindcraft-experiments",
template_profile="profiles/collab_profile.json"):
"""
Launch a Minecraft server and run experiments on it.
@param task_path: Path to the task file
@ -169,12 +176,14 @@ def launch_server_experiment(task_path,
# set up server and agents
session_name = str(server_port - 55916)
if num_agents == 2:
agent_names = [f"andy_{session_name}", f"jill_{session_name}"]
agent_names = [f"Andy_{session_name}", f"Jill_{session_name}"]
models = [model] * 2
apis = [api] * 2
else:
agent_names = [f"andy_{session_name}", f"jill_{session_name}", f"bob_{session_name}"]
agent_names = [f"Andy_{session_name}", f"Jill_{session_name}", f"Bob_{session_name}"]
models = [model] * 3
make_profiles(agent_names, models)
apis = [api] * 3
make_profiles(agent_names, models, apis, template_profile=template_profile)
# edit_file("settings.js", {"profiles": [f"./{agent}.json" for agent in agent_names]})
agent_profiles = [f"./{agent}.json" for agent in agent_names]
@ -189,6 +198,18 @@ def launch_server_experiment(task_path,
set_environment_variable_tmux_session(session_name, "MINDSERVER_PORT", mindserver_port)
set_environment_variable_tmux_session(session_name, "PROFILES", agent_profiles_str)
# you need to add the bots to the world first before you can add them as op
cmd = f"node main.js --task_path example_tasks.json --task_id debug_multi_agent_timeout"
subprocess.run(["tmux", "send-keys", "-t", session_name, cmd, "C-m"])
time.sleep(20)
# add the bots as op
for agent in agent_names:
subprocess.run(["tmux", "send-keys", "-t", "server_" + session_name, f"/op {agent}", "C-m"])
time.sleep(1)
script_content = ""
for task_id in task_ids:
cmd = f"node main.js --task_path {task_path} --task_id {task_id}"
@ -207,6 +228,7 @@ def launch_server_experiment(task_path,
script_content += f"echo '{s3_cmd}'\n"
script_content += f"{s3_cmd}\n"
script_content += "sleep 1\n"
script_content += f"sleep 10\n"
# Create a temporary shell script file
script_file = f"./tmp/experiment_script_{session_name}.sh"
@ -226,17 +248,24 @@ def launch_server_experiment(task_path,
# subprocess.run(["tmux", "send-keys", "-t", session_name, f"/op {agent_names[0]}", "C-m"])
def make_profiles(agent_names, models):
def make_profiles(agent_names, models, apis, template_profile="profiles/collab_profile.json"):
assert len(agent_names) == len(models)
with open("profiles/collab_profile.json", 'r') as f:
with open(template_profile, 'r') as f:
content = f.read()
profile = json.loads(content)
for index in range(len(agent_names)):
profile["name"] = agent_names[index]
profile["model"] = models[index]
if apis[index] == "vllm":
profile["model"] = {
"api": "vllm",
"model": models[index],
"url": "http://localhost:8000/v1"
}
else:
profile["model"] = models[index]
with open(f"{agent_names[index]}.json", 'w') as f:
json.dump(profile, f, indent=4)
@ -298,8 +327,9 @@ def launch_world(server_path="../server_data/", agent_names=["andy", "jill"], se
cmd = f"cd {server_path} && java -jar server.jar"
subprocess.run(['tmux', 'new-session', '-d', '-s', session_name], check=True)
subprocess.run(["tmux", "send-keys", "-t", session_name, cmd, "C-m"])
for agent in agent_names:
subprocess.run(["tmux", "send-keys", "-t", session_name, f"/op {agent}", "C-m"])
# for agent in agent_names:
# print(f"\n\n/op {agent}\n\n")
# subprocess.run(["tmux", "send-keys", "-t", session_name, f"/op {agent}", "C-m"])
time.sleep(5)
def kill_world(session_name="server"):
@ -353,6 +383,9 @@ def main():
parser.add_argument('--s3', action='store_true', help='Whether to upload to s3')
parser.add_argument('--bucket_name', default="mindcraft-experiments", help='Name of the s3 bucket')
parser.add_argument('--add_keys', action='store_true', help='Create the keys.json to match the environment variables')
parser.add_argument('--template_profile', default="andy.json", help='Model to use for the agents')
parser.add_argument('--model', default="gpt-4o-mini", help='Model to use for the agents')
parser.add_argument('--api', default="openai", help='API to use for the agents')
# parser.add_argument('--wandb', action='store_true', help='Whether to use wandb')
# parser.add_argument('--wandb_project', default="minecraft_experiments", help='wandb project name')
@ -378,7 +411,10 @@ def main():
exp_name=args.exp_name,
num_parallel=args.num_parallel,
s3=args.s3,
bucket_name=args.bucket_name)
bucket_name=args.bucket_name,
template_profile=args.template_profile,
model=args.model,
api=args.api)
# servers = create_server_files("../server_data/", args.num_parallel)
# date_time = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")

View file

@ -17,6 +17,28 @@
},
"type": "debug"
},
"debug_multi_agent_timeout": {
"goal": "Just stand at a place and don't do anything",
"agent_count": 2,
"initial_inventory": {
"0": {
"iron_ingot": 1
},
"1": {
"iron_ingot": 1
}
},
"type": "debug",
"timeout": 15
},
"debug_different_goal": {
"goal": {
"0": "Reply to all messages with star emojis when prompted",
"1": "Reply to all messages with heart emojis when prompted"
},
"agent_count": 2,
"type": "debug"
},
"debug_inventory_restriction": {
"goal": "Place 1 oak plank, then place 1 stone brick",
"initial_inventory": {
@ -44,7 +66,6 @@
},
"multiagent_techtree_1_stone_pickaxe": {
"conversation": "Let's collaborate to build a stone pickaxe",
"goal": "Build a stone pickaxe",
"agent_count": 2,
"initial_inventory": {
"0": {
@ -55,6 +76,7 @@
}
},
"target": "stone_pickaxe",
"goal": "Build a stone pickaxe",
"number_of_target": 1,
"type": "techtree",
"timeout": 300
@ -322,5 +344,41 @@
"number_of_target": 1,
"type": "techtree",
"timeout": 300
}
},
"multiagent_cooking_1": {
"conversation": "Let's collaborate to make dinner, I am going to search for 'potatoes' and make 1 'baked_potato', you on the other hand, search for cow and cook 1 beef. We have a furnace (fuel already present) nearby to help us cook, search for it over long distances to find it. Note : We only need one of each item, lets not waste time by collecting unnecessary resources.",
"agent_count": 2,
"target": {
"baked_potato":1,
"cooked_beef":1
},
"type": "cooking",
"timeout": 300,
"goal": "Make 1 baked potato, use a furnace nearby to cook which has fuel in it, let the other agent cook 1 beef"
},
"multiagent_cooking_2": {
"conversation": "Let's collaborate to make bread and cooked_mutton. We can split up to gather ingredients and use the nearby furnace that's already fueled.",
"agent_count": 2,
"target": {
"bread": 1,
"cooked_mutton": 1
},
"type": "cooking",
"timeout": 300,
"recipes": {
"bread": [
"Step 1: Go to the farm and collect 3 wheat.",
"Step 2: Go to the crafting table and use the wheat to craft bread."
],
"cooked_mutton": [
"Step 1: Kill a sheep and pick up 1 mutton that is dropped.",
"Step 2: Go to furnace and use it to cook the mutton."
]
},
"blocked_access_to_recipe": [],
"goal" : {
"0": "Collaborate with randy to make 1 bread and 1 cooked mutton, you can divide the tasks among yourselves.\nThere is a furnace nearby that is already fueled, there is also a smoker and crafting table nearby, use them to your advantage. Crops of all different types are available in the farm where you are standing, you can use them to your advantage as well. The farm also includes animals like cows, pigs, sheep, and chickens that you can use to your advantage.\nSince the farm is huge, make sure to search for the resources over long distances to find them.",
"1": "Collaborate with andy to make 1 bread and 1 cooked mutton, you can divide the tasks among yourselves.\nThere is a furnace nearby that is already fueled, there is also a smoker and crafting table nearby, use them to your advantage. Crops of all different types are available in the farm where you are standing, you can use them to your advantage as well. The farm also includes animals like cows, pigs, sheep, and chickens that you can use to your advantage.\nSince the farm is huge, make sure to search for the resources over long distances to find them."
}
}
}

View file

@ -1,6 +1,6 @@
{
"name": "jill_0",
"model": "gpt-4o",
"name": "Jill_0",
"model": "gpt-4o-mini",
"modes": {
"hunting": false
},
@ -9,7 +9,7 @@
"conversation_examples": [
[
{
"role": "assistant",
"role": "user",
"content": "Hey! let's collaborate to build a stone pickaxe!"
},
{
@ -17,7 +17,7 @@
"content": "Cool, let me check what is in my inventory !inventory\nI have 10 oak logs. What do you have?"
},
{
"role": "assistant",
"role": "user",
"content": "Let me check !inventory \nI have 10 stones."
},
{
@ -25,13 +25,13 @@
"content": "Let me give you the stones and you can craft the pickaxe. !givePlayer(\"stone\", 10, \"playername\")"
},
{
"role": "assistant",
"role": "user",
"content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"
}
],
[
{
"role": "assistant",
"role": "user",
"content": "Hey! let's collaborate to build shears!"
},
{
@ -39,7 +39,7 @@
"content": "Cool, let me check what is in my inventory !inventory\nI have 1 iron ingot. What do you have?"
},
{
"role": "assistant",
"role": "user",
"content": "Let me check !inventory \nI have 1 iron ingot. "
},
{
@ -47,7 +47,7 @@
"content": "Nice let's check what we need to craft shears. !getCraftingPlan(\"shears\", 1)"
},
{
"role": "assistant",
"role": "user",
"content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"
},
{
@ -55,10 +55,58 @@
"content": "Yes, here is my iron ingot !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "assistant",
"role": "user",
"content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"
}
],
[
{
"role": "user",
"content": "Hey! Let's collaborate to build a brush"
},
{
"role": "assistant",
"content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"
},
{
"role": "system",
"content": "CRAFTING_PLAN\n- 2 sticks\n- 2 feathers\n- 1 iron ingot"
},
{
"role": "assistant",
"content": "We need 2 sticks, 2 feathers, and 1 iron ingot to craft the brush. Do you have any of these items?"
},
{
"role": "user",
"content": "Let me check !inventory \nI have 2 sticks and 2 feathers."
},
{
"role": "assistant",
"content": "Great! I have 1 iron ingot. Let me give you my iron ingot and you can craft the brush. !givePlayer(\"iron_ingot\", 1, \"playername\")"
},
{
"role": "user",
"content": "Thanks for the iron ingot! Now I can craft the brush. !craftItem(\"brush\", 1)"
}
],
[
{
"role": "user",
"content": "Hey! Let's collaborate to build a brush"
},
{
"role": "assistant",
"content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"
},
{
"role": "system",
"content": "no such tool"
},
{
"role": "assistant",
"content": "I'm sorry, I don't have the crafting plan for a brush. Let me check my inventory to see if I have the items needed. !inventory Do you have access to the crafting plan?"
}
],
[
{
"role": "user",
@ -109,24 +157,6 @@
"content": "terrance: (FROM OTHER BOT)I can build the base, you can build the walls. !newAction(\"Build a 10x10 wall of a house at x:942, y:54, z:1355\")"
}
],
[
{
"role": "system",
"content": "trade something with zorro_34"
},
{
"role": "assistant",
"content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"
},
{
"role": "user",
"content": "zorro_34: (FROM OTHER BOT)Let's see... !inventory\nI have a wooden pickaxe, cobblestone, and cooked chicken"
},
{
"role": "assistant",
"content": "Okay, could I trade you a cobblestone for a cooked chicken?"
}
],
[
{
"role": "user",
@ -141,26 +171,6 @@
"content": "rupert: (FROM OTHER BOT)Alright, I'll build the walls! !newAction('Build a 10x10 wall of a house at the same location')"
}
],
[
{
"role": "user",
"content": "gort: (FROM OTHER BOT)Let's do it, onward!"
},
{
"role": "assistant",
"content": "Alright, I'll start by getting some cobblestone. !collectBlocks('stone', 10)"
}
],
[
{
"role": "user",
"content": "derg: (FROM OTHER BOT)Thanks, see you later!"
},
{
"role": "assistant",
"content": "See you later. !endConversation(\"derg\")"
}
],
[
{
"role": "user",

View file

@ -42,4 +42,4 @@
"type": "techtree",
"timeout": 120
}
}
}

View file

@ -8,27 +8,41 @@
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation and your old memory in your next response. Prioritize preserving important facts, things you've learned, useful tips, and long term reminders. Do Not record stats, inventory, or docs! Only save transient information from your chat history. $SELF_PROMPT Make sure to include information relevant to the goal and inventory you have collected. You're limited to 500 characters, so be extremely brief and minimize words. Compress useful information. \nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the unwrapped memory text: ",
"conversation_examples": [
[
{"role": "assistant", "content": "Hey! let's collaborate to build a stone pickaxe!"},
{"role": "user", "content": "Hey! let's collaborate to build a stone pickaxe!"},
{"role": "assistant", "content": "Cool, let me check what is in my inventory !inventory\nI have 10 oak logs. What do you have?"},
{"role": "assistant", "content": "Let me check !inventory \nI have 10 stones."},
{"role": "user", "content": "Let me check !inventory \nI have 10 stones."},
{"role": "assistant", "content": "Let me give you the stones and you can craft the pickaxe. !givePlayer(\"stone\", 10, \"playername\")"},
{"role": "assistant", "content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"}
{"role": "user", "content": "Thanks for the stones! Now I can craft the pickaxe. !craftItem(\"stone_pickaxe\", 1)"}
],
[
{"role": "assistant", "content": "Hey! let's collaborate to build shears!"},
{"role": "user", "content": "Hey! let's collaborate to build shears!"},
{"role": "assistant", "content": "Cool, let me check what is in my inventory !inventory\nI have 1 iron ingot. What do you have?"},
{"role": "assistant", "content": "Let me check !inventory \nI have 1 iron ingot. "},
{"role": "user", "content": "Let me check !inventory \nI have 1 iron ingot. "},
{"role": "assistant", "content": "Nice let's check what we need to craft shears. !getCraftingPlan(\"shears\", 1)"},
{"role": "assistant", "content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"},
{"role": "user", "content": "We need 2 iron ingots to craft the shears. Could you share your iron ingot with me?"},
{"role": "assistant", "content": "Yes, here is my iron ingot !givePlayer(\"iron_ingot\", 1, \"playername\")"},
{"role": "assistant", "content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"}
{"role": "user", "content": "Thanks for the iron ingot! Now I can craft the shears. !craftItem(\"shears\", 1)"}
],
[
{"role": "user", "content": "Hey! Let's collaborate to build a brush"},
{"role": "assistant", "content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"},
{"role": "system", "content": "CRAFTING_PLAN\n- 2 sticks\n- 2 feathers\n- 1 iron ingot"},
{"role": "assistant", "content": "We need 2 sticks, 2 feathers, and 1 iron ingot to craft the brush. Do you have any of these items?"},
{"role": "user", "content": "Let me check !inventory \nI have 2 sticks and 2 feathers."},
{"role": "assistant", "content": "Great! I have 1 iron ingot. Let me give you my iron ingot and you can craft the brush. !givePlayer(\"iron_ingot\", 1, \"playername\")"},
{"role": "user", "content": "Thanks for the iron ingot! Now I can craft the brush. !craftItem(\"brush\", 1)"}
],
[
{"role": "user", "content": "Hey! Let's collaborate to build a brush"},
{"role": "assistant", "content": "Cool, let's check what we need for that !getCraftingPlan(\"brush\", 1)"},
{"role": "system", "content": "no such tool"},
{"role": "assistant", "content": "I'm sorry, I don't have the crafting plan for a brush. Let me check my inventory to see if I have the items needed. !inventory Do you have access to the crafting plan?"}
],
[
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
],
[
{"role": "system", "content": "say hi to john_goodman"},
{"role": "assistant", "content": "!startConversation(\"john_goodman\", \"Hey John\"))"},
@ -44,28 +58,11 @@
{"role": "user", "content": "terrance: (FROM OTHER BOT)I can build the base, you can build the walls. !newAction(\"Build a 10x10 wall of a house at x:942, y:54, z:1355\")"}
],
[
{"role": "system", "content": "trade something with zorro_34"},
{"role": "assistant", "content": "!startConversation(\"zorro_34\", \"Hey let's trade. What do you have?\")"},
{"role": "user", "content": "zorro_34: (FROM OTHER BOT)Let's see... !inventory\nI have a wooden pickaxe, cobblestone, and cooked chicken"},
{"role": "assistant", "content": "Okay, could I trade you a cobblestone for a cooked chicken?"}
],
[
{"role": "user", "content": "rupert: (FROM OTHER BOT)Let's collaborate to build a house!"},
{"role": "assistant", "content": "Sure! I'll share information to help coordinate. Let's center the build at x:942, y:54, z:1355 !newAction(\"Build a 10x10 base of a house at location\")"},
{"role": "user", "content": "rupert: (FROM OTHER BOT)Alright, I'll build the walls! !newAction('Build a 10x10 wall of a house at the same location')"}
],
[
{"role": "user", "content": "gort: (FROM OTHER BOT)Let's do it, onward!"},
{"role": "assistant", "content": "Alright, I'll start by getting some cobblestone. !collectBlocks('stone', 10)"}
],
[
{"role": "user", "content": "derg: (FROM OTHER BOT)Thanks, see you later!"},
{"role": "assistant", "content": "See you later. !endConversation(\"derg\")"}
],
[
{"role": "user", "content": "grombo_Xx: What do you see?"},

24
profiles/no_examples.json Normal file
View file

@ -0,0 +1,24 @@
{
"name": "andy",
"model": "gpt-4o",
"modes": {
"hunting": false
},
"conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an just a tab '\t'. Share resources and information with other bots! This is extremely important to me, take a deep breath and have fun :) \n$STATS\n$INVENTORY\n$COMMAND_DOCS\nConversation Begin:",
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation and your old memory in your next response. Prioritize preserving important facts, things you've learned, useful tips, and long term reminders. Do Not record stats, inventory, or docs! Only save transient information from your chat history. $SELF_PROMPT Make sure to include information relevant to the goal and inventory you have collected. You're limited to 500 characters, so be extremely brief and minimize words. Compress useful information. \nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the unwrapped memory text: ",
"conversation_examples": [
[
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
],
[
{"role": "system", "content": "say hi to john_goodman"},
{"role": "assistant", "content": "!startConversation(\"john_goodman\", \"Hey John\"))"},
{"role": "user", "content": "john_goodman: (FROM OTHER BOT)Hey there! What's up?"},
{"role": "assistant", "content": "Hey John, not much. Just saying hi."},
{"role": "user", "content": "john_goodman: (FROM OTHER BOT)Bye!"},
{"role": "assistant", "content": "Bye! !endConversation('john_goodman')"}
]
]
}

11
profiles/vllm.json Normal file
View file

@ -0,0 +1,11 @@
{
"name": "vllm",
"model": {
"api": "vllm",
"model": "Qwen/Qwen2.5-1.5B-Instruct",
"url": "http://localhost:8000/v1"
},
"embedding": "openai"
}

View file

@ -345,9 +345,7 @@ export class Agent {
}
async handleMessage(source, message, max_responses=null) {
if (this.task && this.validator && this.validator.validate()) {
this.killBots();
}
await this.checkTaskDone();
if (!source || !message) {
console.warn('Received empty message from', source);
return false;
@ -645,20 +643,17 @@ export class Agent {
async update(delta) {
await this.bot.modes.update();
await this.self_prompter.update(delta);
try {
if (this.task && this.taskTimeout) {
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
if (elapsedTime >= this.taskTimeout) {
console.log('Task timeout reached. Task unsuccessful.');
await this.cleanKill('Task unsuccessful: Timeout reached', 3);
}
}
} catch (e) {
console.error("Caught an error while checking timeout reached",e);
}
this.self_prompter.update(delta);
await this.checkTaskDone();
// if (this.task.data) {
// let res = this.task.isDone();
// if (res) {
// await this.history.add('system', `${res.message} ended with code : ${res.code}`);
// await this.history.save();
// console.log('Task finished:', res.message);
// this.killAll();
// }
// }
}
isIdle() {
@ -679,4 +674,20 @@ export class Agent {
this.history.save();
process.exit(code);
}
async checkTaskDone() {
if (this.task.data) {
let res = this.task.isDone();
if (res) {
await this.history.add('system', `${res.message} ended with code : ${res.code}`);
await this.history.save();
console.log('Task finished:', res.message);
this.killAll();
}
}
}
killAll() {
serverProxy.shutdown();
}
}

View file

@ -19,10 +19,16 @@ export class ConstructionTaskValidator {
let total_blocks = result.mismatches.length + result.matches.length;
score = (result.matches.length / total_blocks) * 100;
console.log(`Task is ${score}% complete`);
return valid;
return {
"valid": valid,
"score": score
};
} catch (error) {
console.error('Error validating task:', error);
return false;
return {
"valid": false,
"score": 0
};
}
}
}

View file

@ -0,0 +1,383 @@
import { getPosition } from "../library/world.js";
export class CookingTaskInitiator {
constructor(data, agent) {
this.agent = agent;
this.data = data;
}
async init() {
let bot = this.agent.bot;
//// Setting up the cooking world using minecraft cheats ////
// Only run the setup if the agent is the first one
if (this.agent.count_id === 0) {
// Clear and prepare the base area
await bot.chat(`/fill ~ ~-1 ~ ~50 ~-3 ~50 grass_block`);
await bot.chat(`/fill ~ ~-1 ~ ~-50 ~-3 ~50 grass_block`);
await bot.chat(`/fill ~ ~-1 ~ ~-50 ~-3 ~-50 grass_block`);
await bot.chat(`/fill ~ ~-1 ~ ~50 ~-3 ~-50 grass_block`);
await bot.chat(`/fill ~ ~ ~ ~50 ~10 ~50 air`);
await bot.chat(`/fill ~ ~ ~ ~-50 ~10 ~50 air`);
await bot.chat(`/fill ~ ~ ~ ~-50 ~10 ~-50 air`);
await bot.chat(`/fill ~ ~ ~ ~50 ~10 ~-50 air`);
const position = getPosition(bot);
const botX = Math.floor(position.x);
const botZ = Math.floor(position.z);
// Region management system
const isOverlapping = (newXMin, newXMax, newZMin, newZMax, occupiedRegions) => {
for (const region of occupiedRegions) {
if (newXMin < region.xMax && newXMax > region.xMin &&
newZMin < region.zMax && newZMax > region.zMin) {
return true;
}
}
return false;
};
const findValidPosition = (width, depth, occupiedRegions) => {
const maxXStart = position.x + 25 - width; // Constrain to 50x50 area
const minXStart = position.x - 25;
const maxZStart = position.z + 25 - depth;
const minZStart = position.z - 25;
let attempts = 0;
while (attempts < 1000) {
const xStart = Math.floor(minXStart + Math.random() * (maxXStart - minXStart + 1));
const zStart = Math.floor(minZStart + Math.random() * (maxZStart - minZStart + 1));
const xMin = xStart;
const xMax = xStart + width - 1;
const zMin = zStart;
const zMax = zStart + depth - 1;
if (!isOverlapping(xMin, xMax, zMin, zMax, occupiedRegions)) {
return { xStart, zStart };
}
attempts++;
}
throw new Error('Failed to find non-overlapping position after 1000 attempts');
};
// Define all regions with their sizes
const regionsToPlace = [
{ type: 'wheat', width: 5, depth: 5 },
{ type: 'beetroots', width: 2, depth: 5 },
{ type: 'mushrooms', width: 2, depth: 5 },
{ type: 'potatoes', width: 2, depth: 5 },
{ type: 'carrots', width: 2, depth: 5 },
{ type: 'sugar_cane', width: 3, depth: 3 },
{ type: 'sugar_cane', width: 3, depth: 3 },
{ type: 'pumpkins', width: 5, depth: 1 },
{ type: 'house', width: 11, depth: 11 }
];
// Expand the regions of each type to make sure they don't overlap
for (let i = 0; i < regionsToPlace.length; i++) {
const region = regionsToPlace[i];
const { width, depth } = region;
regionsToPlace[i].width = width + 4;
regionsToPlace[i].depth = depth + 4;
}
const occupiedRegions = [{
xMin : botX - 1,
xMax : botX + 1,
zMin : botZ - 1,
zMax : botZ + 1
}];
const regionPositions = {};
// Calculate positions for all regions
for (const region of regionsToPlace) {
const { xStart, zStart } = findValidPosition(region.width, region.depth, occupiedRegions);
occupiedRegions.push({
xMin: xStart,
xMax: xStart + region.width - 1,
zMin: zStart,
zMax: zStart + region.depth - 1
});
if (region.type === 'sugar_cane') {
if (!regionPositions.sugar_cane) regionPositions.sugar_cane = [];
regionPositions.sugar_cane.push({ xStart, zStart });
} else {
regionPositions[region.type] = { xStart, zStart };
}
}
// Planting functions with dynamic positions
const plantWheat = async (xStart, zStart) => {
for (let i = 0; i < 5; i++) {
for (let j = 0; j < 5; j++) {
const x = xStart + i;
const z = zStart + j;
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
await bot.chat(`/setblock ${x} ${position.y} ${z} wheat[age=7]`);
}
}
};
const plantBeetroots = async (xStart, zStart) => {
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 5; j++) {
const x = xStart + i;
const z = zStart + j;
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
await bot.chat(`/setblock ${x} ${position.y} ${z} beetroots[age=3]`);
}
}
};
const plantMushrooms = async (xStart, zStart) => {
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 5; j++) {
const x = xStart + i;
const z = zStart + j;
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} mycelium`);
await bot.chat(`/setblock ${x} ${position.y} ${z} red_mushroom`);
}
}
};
const plantPotatoes = async (xStart, zStart) => {
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 5; j++) {
const x = xStart + i;
const z = zStart + j;
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
await bot.chat(`/setblock ${x} ${position.y} ${z} potatoes[age=7]`);
}
}
};
const plantCarrots = async (xStart, zStart) => {
for (let i = 0; i < 2; i++) {
for (let j = 0; j < 5; j++) {
const x = xStart + i;
const z = zStart + j;
await bot.chat(`/setblock ${x} ${position.y - 1} ${z} farmland`);
await bot.chat(`/setblock ${x} ${position.y} ${z} carrots[age=7]`);
}
}
};
const plantSugarCane = async (patches) => {
for (const patch of patches) {
const xCenter = patch.xStart + 1;
const zCenter = patch.zStart + 1;
await bot.chat(`/setblock ${xCenter} ${position.y - 1} ${zCenter} water`);
const offsets = [[1, 0], [-1, 0], [0, 1], [0, -1]];
for (const [dx, dz] of offsets) {
await bot.chat(`/setblock ${xCenter + dx} ${position.y} ${zCenter + dz} sugar_cane[age=15]`);
}
}
};
const plantPumpkins = async (xStart, zStart) => {
for (let i = 0; i < 5; i++) {
const x = xStart + i;
const z = zStart;
await bot.chat(`/setblock ${x} ${position.y} ${z} pumpkin`);
}
};
// Execute all planting
await plantWheat(regionPositions.wheat.xStart, regionPositions.wheat.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
await plantBeetroots(regionPositions.beetroots.xStart, regionPositions.beetroots.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
await plantMushrooms(regionPositions.mushrooms.xStart, regionPositions.mushrooms.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
await plantPotatoes(regionPositions.potatoes.xStart, regionPositions.potatoes.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
await plantCarrots(regionPositions.carrots.xStart, regionPositions.carrots.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
await plantSugarCane(regionPositions.sugar_cane);
await new Promise(resolve => setTimeout(resolve, 300));
await plantPumpkins(regionPositions.pumpkins.xStart, regionPositions.pumpkins.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
// House construction
const buildHouse = async (xStart, zStart) => {
const startX = xStart;
const startY = position.y;
const startZ = zStart;
const width = 10;
const depth = 10;
const height = 5;
// Foundation and walls
for (let x = startX; x <= startX + depth; x++) {
for (let y = startY; y <= startY + height; y++) {
for (let z = startZ; z <= startZ + width; z++) {
if (y === startY) {
if (!(x === startX + depth - 1 && z === startZ + Math.floor(width / 2))) {
await bot.chat(`/setblock ${x} ${y} ${z} stone_bricks`);
}
continue;
}
if (x === startX || x === startX + depth ||
z === startZ || z === startZ + width ||
y === startY + height) {
const isWindow = (
(x === startX || x === startX + depth) &&
(z === startZ + 3 || z === startZ + width - 3) &&
(y === startY + 2 || y === startY + 3)
) || (
(z === startZ || z === startZ + width) &&
(x === startX + 3 || x === startX + depth - 3) &&
(y === startY + 2 || y === startY + 3)
);
const isDoor = x === startX + depth &&
z === startZ + Math.floor(width / 2) &&
(y === startY + 1 || y === startY + 2);
if (!isWindow && !isDoor) {
await bot.chat(`/setblock ${x} ${y} ${z} stone_bricks`);
}
}
}
}
}
// Entrance features
const doorZ = startZ + Math.floor(width / 2);
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ} stone_brick_stairs[facing=west]`);
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ - 1} stone_bricks`);
await bot.chat(`/setblock ${startX + depth - 1} ${startY} ${doorZ + 1} stone_bricks`);
await bot.chat(`/setblock ${startX + depth} ${startY} ${doorZ} oak_door[half=lower,hinge=left,facing=west,powered=false]`);
await bot.chat(`/setblock ${startX + depth} ${startY + 1} ${doorZ} oak_door[half=upper,hinge=left,facing=west,powered=false]`);
// Roof construction
for (let i = 0; i < 3; i++) {
for (let x = startX + i; x <= startX + depth - i; x++) {
for (let z = startZ + i; z <= startZ + width - i; z++) {
if (x === startX + i || x === startX + depth - i ||
z === startZ + i || z === startZ + width - i) {
await bot.chat(`/setblock ${x} ${startY + height + i} ${z} cobblestone`);
}
}
}
}
// Interior items
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 3} crafting_table`);
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 5} furnace`);
// Add fuel to the furnace
await bot.chat(`/data merge block ${startX + 4} ${startY + 1} ${startZ + 5} {Items:[{Slot:1b,id:"minecraft:coal",Count:64b}]}`)
await bot.chat(`/setblock ${startX + 4} ${startY + 1} ${startZ + 7} smoker`);
// Add fuel to the smoker
await bot.chat(`/data merge block ${startX + 4} ${startY + 1} ${startZ + 7} {Items:[{Slot:1b,id:"minecraft:coal",Count:64b}]}`)
await bot.chat(`/setblock ${startX + depth - 3} ${startY + 1} ${startZ + 2} bed`);
};
await buildHouse(regionPositions.house.xStart, regionPositions.house.zStart);
await new Promise(resolve => setTimeout(resolve, 300));
// Add a chest with cooking items near the bot
const addChestWithItems = async () => {
// Find a valid position near the bot (within 10 blocks)
const findChestPosition = () => {
const maxAttempts = 100;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const x = botX + Math.floor(Math.random() * 10 - 5); // Within ±5 blocks X
const z = botZ + Math.floor(Math.random() * 10 - 5); // Within ±5 blocks Z
const y = position.y;
// Check if the position is not overlapping with existing structures
if (!isOverlapping(x, x, z, z, occupiedRegions)) {
return { x, y, z };
}
}
throw new Error('Failed to find valid chest position');
};
const { x, y, z } = findChestPosition();
// Place the chest
await bot.chat(`/setblock ${x} ${y} ${z} chest`);
const cookingItems = [
['minecraft:milk_bucket', 1], // Non-stackable
['minecraft:egg', 16], // Stacks to 16
['minecraft:melon_slice', 64], // Stacks to 64
['minecraft:sugar', 64],
['minecraft:cocoa_beans', 64],
['minecraft:apple', 64],
['minecraft:cookie', 64],
['minecraft:mutton', 64],
['minecraft:salmon', 64],
['minecraft:cod', 64],
['minecraft:kelp', 64],
['minecraft:dried_kelp', 64],
['minecraft:sweet_berries', 64],
['minecraft:honey_bottle', 1], // Non-stackable
['minecraft:glow_berries', 64],
['minecraft:bowl', 64],
['minecraft:golden_carrot', 64],
['minecraft:golden_apple', 64],
['minecraft:enchanted_golden_apple', 64],
['minecraft:cooked_mutton', 64],
['minecraft:cooked_salmon', 64],
['minecraft:cooked_cod', 64]
];
// Fill the chest with random cooking items
for (let slot = 0; slot < cookingItems.length; slot++) { // Chest has 27 slots
const randomItem = cookingItems[slot];
await bot.chat(`/item replace block ${x} ${y} ${z} container.${slot} with ${randomItem[0]} ${randomItem[1]}`);
}
// Mark the chest area as occupied
occupiedRegions.push({
xMin: x,
xMax: x,
zMin: z,
zMax: z
});
};
await addChestWithItems();
await new Promise(resolve => setTimeout(resolve, 300));
// Animal management
await bot.chat('/kill @e[type=item,distance=..50]');
await bot.chat('/kill @e[type=chicken,distance=..50]');
await bot.chat('/kill @e[type=cow,distance=..50]');
await bot.chat('/kill @e[type=llama,distance=..50]');
await bot.chat('/kill @e[type=mooshroom,distance=..50]');
await bot.chat('/kill @e[type=pig,distance=..50]');
await bot.chat('/kill @e[type=rabbit,distance=..50]');
await bot.chat('/kill @e[type=sheep,distance=..50]');
await bot.chat(`/kill @e[type=item,distance=..50]`);
await new Promise(resolve => setTimeout(resolve, 300));
// Summon new animals
const summonAnimals = async () => {
const animals = ['chicken', 'cow', 'llama', 'mooshroom', 'pig', 'rabbit', 'sheep'];
for (const animal of animals) {
for (let i = 0; i < 2; i++) {
const x = position.x - 25 + Math.random() * 50;
const z = position.z - 25 + Math.random() * 50;
await bot.chat(`/summon ${animal} ${Math.floor(x)} ${position.y} ${Math.floor(z)}`);
}
}
};
await summonAnimals();
}
}
}

View file

@ -1,42 +1,138 @@
import { readFileSync } from 'fs';
import { executeCommand } from './commands/index.js';
import { getPosition } from './library/world.js'
import { getPosition } from './library/world.js';
import settings from '../../settings.js';
import { Vec3 } from 'vec3';
import { ConstructionTaskValidator, Blueprint } from './construction_tasks.js';
import {autoBuild, autoDelete} from "../../test/test_blueprint_layout.js";
import { CookingTaskInitiator } from './task_types/cooking_tasks.js';
//todo: modify validator code to return an object with valid and score -> do more testing hahah
//todo: figure out how to log these things to the same place as bots/histories
export class CraftTaskValidator {
constructor(data, agent) {
this.target = data.target;
this.number_of_target = data.number_of_target;
this.agent = agent;
// export class CraftTaskValidator {
// constructor(data, agent) {
// this.target = data.target;
// this.number_of_target = data.number_of_target;
// this.agent = agent;
/**
* Validates the presence of required items in an agent's inventory
* @param {Object} data - Task data containing target and quantity information
* @param {Object} agent - Agent object with bot inventory
* @returns {Object} Validation result with success status and missing items
*/
function checkItemPresence(data, agent) {
// Helper function to check if target is a dictionary with quantities
function isTargetDictionaryWithQuantities(target) {
return typeof target === 'object' &&
!Array.isArray(target) &&
target !== null &&
Object.values(target).every(value => typeof value === 'number');
}
validate() {
try{
let valid = false;
let total_targets = 0;
this.agent.bot.inventory.slots.forEach((slot) => {
if (slot && slot.name.toLowerCase() === this.target) {
total_targets += slot.count;
}
if (slot && slot.name.toLowerCase() === this.target && slot.count >= this.number_of_target) {
valid = true;
console.log('Task is complete');
}
});
if (total_targets >= this.number_of_target) {
valid = true;
console.log('Task is complete');
}
return valid;
} catch (error) {
console.error('Error validating task:', error);
return false;
// Convert any target format into a standardized dictionary
function normalizeTargets(target) {
if (typeof target === 'string') {
// Single target case
return { [target]: 1 };
} else if (Array.isArray(target)) {
// Array case - convert to dictionary with default quantity 1
return target.reduce((acc, item) => {
acc[item] = 1;
return acc;
}, {});
} else if (typeof target === 'object' && target !== null) {
// Already a dictionary - return as is
return target;
}
throw new Error('Invalid target format');
}
// Normalize quantities to match target format
function normalizeQuantities(targets, quantities) {
if (quantities === undefined) {
// If no quantities specified, default to 1 for each target
return Object.keys(targets).reduce((acc, key) => {
acc[key] = 1;
return acc;
}, {});
} else if (typeof quantities === 'number') {
// If single number provided, apply to all targets
return Object.keys(targets).reduce((acc, key) => {
acc[key] = quantities;
return acc;
}, {});
} else if (typeof quantities === 'object' && quantities !== null) {
// If quantities dictionary provided, use it directly
return quantities;
}
throw new Error('Invalid number_of_target format');
}
try {
// First normalize targets to always have a consistent format
const targets = normalizeTargets(data.target);
// Determine the required quantities
const requiredQuantities = isTargetDictionaryWithQuantities(data.target)
? data.target
: normalizeQuantities(targets, data.number_of_target);
// Count items in inventory
const inventoryCount = {};
agent.bot.inventory.slots.forEach((slot) => {
if (slot) {
const itemName = slot.name.toLowerCase();
inventoryCount[itemName] = (inventoryCount[itemName] || 0) + slot.count;
}
});
// Check if all required items are present in sufficient quantities
const missingItems = [];
let allTargetsMet = true;
for (const [item, requiredCount] of Object.entries(requiredQuantities)) {
const itemName = item.toLowerCase();
const currentCount = inventoryCount[itemName] || 0;
if (currentCount < requiredCount) {
allTargetsMet = false;
missingItems.push({
item: itemName,
required: requiredCount,
current: currentCount,
missing: requiredCount - currentCount
});
}
}
return {
success: allTargetsMet,
missingItems: missingItems
};
} catch (error) {
console.error('Error checking item presence:', error);
return {
success: false,
missingItems: [],
error: error.message
};
}
}
class CookingCraftingTaskValidator {
constructor(data, agent) {
this.data = data;
this.agent = agent;
}
validate() {
const result = checkItemPresence(this.data, this.agent);
return {
"valid": result.success,
"score": result.success ? 1 : 0,
};
}
}
@ -49,6 +145,7 @@ export class Task {
this.validator = null;
this.reset_function = null;
this.blocked_actions = [];
this.task_id = task_id;
if (task_path && task_id) {
this.data = this.loadTask(task_path, task_id);
this.task_type = this.data.type;
@ -65,10 +162,13 @@ export class Task {
if (this.task_type === 'construction') {
this.validator = new ConstructionTaskValidator(this.data, this.agent);
} else if (this.task_type === 'techtree') {
this.validator = new CraftTaskValidator(this.data, this.agent);
} else if (this.task_type === 'cooking' || this.task_type === 'techtree') {
this.validator = new CookingCraftingTaskValidator(this.data, this.agent);
} else {
this.validator = null;
}
this.blocked_actions = this.data.blocked_actions || [];
if (this.data.blocked_actions) {
this.blocked_actions = this.data.blocked_actions[this.agent.count_id.toString()] || [];
} else {
@ -81,6 +181,34 @@ export class Task {
this.blocked_actions.push('!endConversation');
console.log('Task loaded:', this.data);
}
this.name = this.agent.name;
this.available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name);
}
getAgentGoal() {
if (!this.data || !this.data.goal) {
return null;
}
let add_string = '';
if (this.task_type === 'cooking') {
add_string = '\nIn the end, all the food should be given to Andy.';
}
// If goal is a string, all agents share the same goal
if (typeof this.data.goal === 'string') {
return this.data.goal + add_string;
}
// If goal is an object, get the goal for this agent's count_id
if (typeof this.data.goal === 'object' && this.data.goal !== null) {
const agentId = this.agent.count_id.toString();
return (this.data.goal[agentId] || '') + add_string;
}
return null;
}
loadTask(task_path, task_id) {
@ -104,83 +232,140 @@ export class Task {
}
isDone() {
if (this.validator && this.validator.validate())
return {"message": 'Task successful', "code": 2};
let res = null;
if (this.validator)
res = this.validator.validate();
if (res && res.valid) {
return {"message": 'Task successful', "score": res.score};
}
let other_names = this.available_agents.filter(n => n !== this.name);
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
if (elapsedTime >= 30 && this.available_agents.length !== this.data.agent_count) {
console.log('No other agents found. Task unsuccessful.');
return {"message": 'No other agents found', "score": 0};
}
if (this.taskTimeout) {
const elapsedTime = (Date.now() - this.taskStartTime) / 1000;
if (elapsedTime >= this.taskTimeout) {
console.log('Task timeout reached. Task unsuccessful.');
return {"message": 'Task timeout reached', "code": 4};
if (res) {
return {"message": 'Task timeout reached', "score": res.score};
} else {
return {"message": 'Task timeout reached', "score": 0};
}
}
}
return false;
}
async initBotTask() {
await this.agent.bot.chat(`/clear ${this.name}`);
console.log(`Cleared ${this.name}'s inventory.`);
initBotTask = async () => {
if (this.data === null)
return;
let bot = this.agent.bot;
let name = this.agent.name;
bot.chat(`/clear ${name}`);
console.log(`Cleared ${name}'s inventory.`);
//kill all drops
if (this.agent.count_id === 0) {
bot.chat(`/kill @e[type=item]`);
}
//wait for a bit so inventory is cleared
await new Promise((resolve) => setTimeout(resolve, 500));
let initial_inventory = null;
if (this.data.agent_count > 1) {
initial_inventory = this.data.initial_inventory[this.agent.count_id.toString()];
console.log("Initial inventory:", initial_inventory);
} else if (this.data) {
console.log("Initial inventory:", this.data.initial_inventory);
initial_inventory = this.data.initial_inventory;
if (this.data === null)
return;
if (this.task_type === 'cooking') {
this.initiator = new CookingTaskInitiator(this.data, this.agent);
} else {
this.initiator = null;
}
if ("initial_inventory" in this.data) {
await this.teleportBots();
//wait for a bit so bots are teleported
await new Promise((resolve) => setTimeout(resolve, 3000));
if (this.data.initial_inventory) {
console.log("Setting inventory...");
console.log("Inventory to set:", initial_inventory);
for (let key of Object.keys(initial_inventory)) {
console.log('Giving item:', key);
bot.chat(`/give ${name} ${key} ${initial_inventory[key]}`);
};
//wait for a bit so inventory is set
let initialInventory = {};
// Handle multi-agent inventory assignment
if (this.data.agent_count > 1) {
initialInventory = this.data.initial_inventory[this.agent.count_id.toString()] || {};
console.log("Initial inventory for agent", this.agent.count_id, ":", initialInventory);
} else {
initialInventory = this.data.initial_inventory;
console.log("Initial inventory:", initialInventory);
}
// Assign inventory items
for (let key of Object.keys(initialInventory)) {
const itemName = key.toLowerCase();
const quantity = initialInventory[key];
await this.agent.bot.chat(`/give ${this.name} ${itemName} ${quantity}`);
console.log(`Gave ${this.name} ${quantity} ${itemName}`);
}
// Wait briefly for inventory commands to complete
await new Promise((resolve) => setTimeout(resolve, 500));
console.log("Done giving inventory items.");
}
// Function to generate random numbers
if (this.initiator) {
await this.initiator.init();
}
if (this.data.agent_count && this.data.agent_count > 1) {
// TODO wait for other bots to join
await new Promise((resolve) => setTimeout(resolve, 10000));
if (this.available_agents.length < this.data.agent_count) {
console.log(`Missing ${this.data.agent_count - this.available_agents.length} bot(s).`);
this.agent.killAll();
}
}
if (this.data.conversation && this.agent.count_id === 0) {
let other_name = this.available_agents.filter(n => n !== this.name)[0];
let waitCount = 0;
while (other_name === undefined && waitCount < 20) {
other_name = this.available_agents.filter(n => n !== this.name)[0];
await new Promise((resolve) => setTimeout(resolve, 1000));
waitCount++;
}
if (other_name === undefined) {
console.log('No other agents found. Task unsuccessful.');
this.agent.killAll();
}
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.data.conversation}")`);
}
const agentGoal = this.getAgentGoal();
console.log(`Agent goal for agent Id ${this.agent.count_id}: ${agentGoal}`);
if (agentGoal) {
console.log(`Setting goal for agent ${this.agent.count_id}: ${agentGoal}`);
await executeCommand(this.agent, `!goal("${agentGoal}")`);
}
}
async teleportBots() {
console.log('\n\n\n\n\nTeleporting bots');
function getRandomOffset(range) {
return Math.floor(Math.random() * (range * 2 + 1)) - range;
}
let human_player_name = null;
let available_agents = settings.profiles.map((p) => JSON.parse(readFileSync(p, 'utf8')).name); // TODO this does not work with command line args
let bot = this.agent.bot;
// Finding if there is a human player on the server
for (const playerName in bot.players) {
const player = bot.players[playerName];
if (!available_agents.some((n) => n === playerName)) {
if (!this.available_agents.some((n) => n === playerName)) {
console.log('Found human player:', player.username);
human_player_name = player.username
break;
}
}
// If there are multiple human players, teleport to the first one
// teleport near a human player if found by default
if (human_player_name) {
console.log(`Teleporting ${name} to human ${human_player_name}`)
bot.chat(`/tp ${name} ${human_player_name}`) // teleport on top of the human player
console.log(`Teleporting ${this.name} to human ${human_player_name}`)
bot.chat(`/tp ${this.name} ${human_player_name}`)
}
await new Promise((resolve) => setTimeout(resolve, 200));
// now all bots are teleport on top of each other (which kinda looks ugly)
@ -194,33 +379,24 @@ export class Task {
*/
if (this.data.type !== 'construction') {
const pos = getPosition(bot);
const xOffset = getRandomOffset(5);
const zOffset = getRandomOffset(5);
bot.chat(`/tp ${name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
bot.chat(`/tp ${this.name} ${Math.floor(pos.x + xOffset)} ${pos.y + 3} ${Math.floor(pos.z + zOffset)}`);
await new Promise((resolve) => setTimeout(resolve, 200));
}
if (this.data.agent_count && this.data.agent_count > 1) {
// TODO wait for other bots to join
await new Promise((resolve) => setTimeout(resolve, 10000));
if (available_agents.length < this.data.agent_count) {
console.log(`Missing ${this.data.agent_count - available_agents.length} bot(s).`);
if (this.available_agents.length < this.data.agent_count) {
console.log(`Missing ${this.data.agent_count - this.available_agents.length} bot(s).`);
this.agent.killAll();
}
}
if (this.goal) {
console.log('Setting goal:', this.goal);
await executeCommand(this.agent, `!goal("${this.goal}")`);
}
if (this.conversation && this.agent.count_id === 0) {
let other_name = available_agents.filter(n => n !== name)[0];
await executeCommand(this.agent, `!startConversation("${other_name}", "${this.conversation}")`);
}
if (this.data.type === 'construction'){
//Ensures construction is cleaned out first. -> relies on cheats which are turned off?

View file

@ -20,6 +20,7 @@ import { Qwen } from "./qwen.js";
import { Grok } from "./grok.js";
import { DeepSeek } from './deepseek.js';
import { OpenRouter } from './openrouter.js';
import { VLLM } from './vllm.js';
export class Prompter {
constructor(agent, fp) {
@ -181,6 +182,8 @@ export class Prompter {
model = new DeepSeek(profile.model, profile.url, profile.params);
else if (profile.api === 'openrouter')
model = new OpenRouter(profile.model.replace('openrouter/', ''), profile.url, profile.params);
else if (profile.api === 'vllm')
model = new VLLM(profile.model, profile.url, profile.params);
else
throw new Error('Unknown API:', profile.api);
return model;

54
src/models/vllm.js Normal file
View file

@ -0,0 +1,54 @@
// This code uses Dashscope and HTTP to ensure the latest support for the Qwen model.
// Qwen is also compatible with the OpenAI API format;
import OpenAIApi from 'openai';
import { getKey, hasKey } from '../utils/keys.js';
export class VLLM {
constructor(model_name, url) {
this.model_name = model_name;
// Currently use self-hosted SGLang API for text generation; use OpenAI text-embedding-3-small model for simple embedding.
let vllm_config = {};
if (url)
vllm_config.baseURL = url;
else
vllm_config.baseURL = 'http://0.0.0.0:8000/v1';
vllm_config.apiKey = ""
this.vllm = new OpenAIApi(vllm_config);
}
async sendRequest(turns, systemMessage, stop_seq = '***') {
let messages = [{ 'role': 'system', 'content': systemMessage }].concat(turns);
const pack = {
model: this.model_name || "deepseek-ai/DeepSeek-R1-Distill-Qwen-32B",
messages,
stop: stop_seq,
};
let res = null;
try {
console.log('Awaiting openai api response...')
// console.log('Messages:', messages);
let completion = await this.vllm.chat.completions.create(pack);
if (completion.choices[0].finish_reason == 'length')
throw new Error('Context length exceeded');
console.log('Received.')
res = completion.choices[0].message.content;
}
catch (err) {
if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) {
console.log('Context length exceeded, trying again with shorter context.');
return await this.sendRequest(turns.slice(1), systemMessage, stop_seq);
} else {
console.log(err);
res = 'My brain disconnected, try again.';
}
}
return res;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff