Add getCraftingPlan tool for making agents faster and resource efficient

This commit is contained in:
Ayush Maniar 2025-01-05 19:27:44 -08:00
parent 43cdcc6879
commit a14d4af65f
6 changed files with 209 additions and 5 deletions

View file

@ -200,6 +200,18 @@
[ [
{"role": "system", "content": "You are self-prompting with the goal: 'Build a house'. Respond:"}, {"role": "system", "content": "You are self-prompting with the goal: 'Build a house'. Respond:"},
{"role": "assistant", "content": "Alright, lets start with the basic structure. !newAction(\"Build an 8x8 base for the house.\")"} {"role": "assistant", "content": "Alright, lets start with the basic structure. !newAction(\"Build an 8x8 base for the house.\")"}
],
[
{"role": "user", "content": "trex56: Lets make a wooden pickaxe together"},
{"role": "assistant", "content": "Sure, let me check how to make a wooden pickaxe !getCraftingPlan(\"wooden_pickaxe\",1)"},
{"role": "system", "content": "You are missing the following items:\n- 2 oak_log\n\nOnce you have these items, here's your crafting plan:\n\nCraft 1 oak_log -> 4 oak_planks\nCraft 1 oak_log -> 4 oak_planks\nCraft 2 oak_planks -> 4 stick\nCraft 3 oak_planks + 2 stick -> 1 wooden_pickaxe"},
{"role": "assistant", "content": "I need 2 oak logs to make a wooden pickaxe. Do you have any?"}
],
[
{"role": "user", "content": "terrance: (FROM OTHER BOT) I want you to craft 5 chains"},
{"role": "assistant", "content": "Sure, first let me check how to build a chain, !getCraftingPlan(\"chain\",5)"},
{"role": "system", "content": "You are missing the following items:\n- 7 iron_ingot\n\nOnce you have these items, here's your crafting plan:\n\nCraft 2 iron_ingot -> 18 iron_nugget\nCraft 10 iron_nugget + 5 iron_ingot -> 5 chain"},
{"role": "assistant", "content": "Seems like I need 7 iron_ingots for 5 chains. Do you have any?"}
] ]
], ],

View file

@ -406,7 +406,7 @@ export const actionsList = [
convoManager.endConversation(player_name); convoManager.endConversation(player_name);
return `Converstaion with ${player_name} ended.`; return `Converstaion with ${player_name} ended.`;
} }
} },
// { // commented for now, causes confusion with goal command // { // commented for now, causes confusion with goal command
// name: '!npcGoal', // name: '!npcGoal',
// description: 'Set a simple goal for an item or building to automatically work towards. Do not use for complex goals.', // description: 'Set a simple goal for an item or building to automatically work towards. Do not use for complex goals.',

View file

@ -176,5 +176,42 @@ export const queryList = [
perform: async function (agent) { perform: async function (agent) {
return "Saved place names: " + agent.memory_bank.getKeys(); return "Saved place names: " + agent.memory_bank.getKeys();
} }
} },
{
name: '!getCraftingPlan',
description: "Provides a comprehensive crafting plan for a specified item. This includes a breakdown of required ingredients, the exact quantities needed, and an analysis of missing ingredients or extra items needed based on the bot's current inventory.",
params: {
targetItem: {
type: 'string',
description: 'The item that we are trying to craft'
},
quantity: {
type: 'int',
description: 'The quantity of the item that we are trying to craft',
optional: true,
domain: [1, Infinity, '[)'], // Quantity must be at least 1,
default: 1
}
},
perform: function (agent, targetItem, quantity = 1) {
let bot = agent.bot;
// Fetch the bot's inventory
const curr_inventory = world.getInventoryCounts(bot);
const target_item = targetItem;
let existingCount = curr_inventory[target_item] || 0;
var prefixMessage = '';
if (existingCount > 0) {
curr_inventory[target_item] -= existingCount;
prefixMessage = `You already have ${existingCount} ${target_item} in your inventory. If you need to craft more,\n`;
}
// Generate crafting plan
var craftingPlan = mc.getDetailedCraftingPlan(target_item, quantity, curr_inventory);
craftingPlan = prefixMessage + craftingPlan;
console.log('\n\n\n\n\n\n\n\n\n\n\n');
console.log(craftingPlan);
return pad(craftingPlan);
}
},
]; ];

View file

@ -79,7 +79,7 @@ export async function craftRecipe(bot, itemName, num=1) {
} }
} }
if (!recipes || recipes.length === 0) { if (!recipes || recipes.length === 0) {
log(bot, `You do not have the resources to craft a ${itemName}. It requires: ${Object.entries(mc.getItemCraftingRecipes(itemName)[0]).map(([key, value]) => `${key}: ${value}`).join(', ')}.`); log(bot, `You do not have the resources to craft a ${itemName}. It requires: ${Object.entries(mc.getItemCraftingRecipes(itemName)[0][0]).map(([key, value]) => `${key}: ${value}`).join(', ')}.`);
if (placedTable) { if (placedTable) {
await collectBlock(bot, 'crafting_table', 1); await collectBlock(bot, 'crafting_table', 1);
} }

View file

@ -204,7 +204,7 @@ class ItemWrapper {
} }
createChildren() { createChildren() {
let recipes = mc.getItemCraftingRecipes(this.name); let recipes = mc.getItemCraftingRecipes(this.name).map(([recipe, craftedCount]) => recipe);
if (recipes) { if (recipes) {
for (let recipe of recipes) { for (let recipe of recipes) {
let includes_blacklisted = false; let includes_blacklisted = false;

View file

@ -190,7 +190,10 @@ export function getItemCraftingRecipes(itemName) {
recipe[ingredientName] = 0; recipe[ingredientName] = 0;
recipe[ingredientName]++; recipe[ingredientName]++;
} }
recipes.push(recipe); recipes.push([
recipe,
{craftedCount : r.result.count}
]);
} }
return recipes; return recipes;
@ -327,4 +330,156 @@ export function calculateLimitingResource(availableItems, requiredItems, discret
} }
if(discrete) num = Math.floor(num); if(discrete) num = Math.floor(num);
return {num, limitingResource} return {num, limitingResource}
}
let loopingItems = new Set();
export function initializeLoopingItems() {
loopingItems = new Set(['coal',
'wheat',
'diamond',
'emerald',
'raw_iron',
'raw_gold',
'redstone',
'blue_wool',
'packed_mud',
'raw_copper',
'iron_ingot',
'dried_kelp',
'gold_ingot',
'slime_ball',
'black_wool',
'quartz_slab',
'copper_ingot',
'lapis_lazuli',
'honey_bottle',
'rib_armor_trim_smithing_template',
'eye_armor_trim_smithing_template',
'vex_armor_trim_smithing_template',
'dune_armor_trim_smithing_template',
'host_armor_trim_smithing_template',
'tide_armor_trim_smithing_template',
'wild_armor_trim_smithing_template',
'ward_armor_trim_smithing_template',
'coast_armor_trim_smithing_template',
'spire_armor_trim_smithing_template',
'snout_armor_trim_smithing_template',
'shaper_armor_trim_smithing_template',
'netherite_upgrade_smithing_template',
'raiser_armor_trim_smithing_template',
'sentry_armor_trim_smithing_template',
'silence_armor_trim_smithing_template',
'wayfinder_armor_trim_smithing_template']);
}
/**
* Gets a detailed plan for crafting an item considering current inventory
*/
export function getDetailedCraftingPlan(targetItem, count = 1, current_inventory = {}) {
initializeLoopingItems();
if (!targetItem || count <= 0 || !getItemId(targetItem)) {
return "Invalid input. Please provide a valid item name and positive count.";
}
if (isBaseItem(targetItem)) {
const available = current_inventory[targetItem] || 0;
if (available >= count) return "You have all required items already in your inventory!";
return `${targetItem} is a base item, you need to find ${count - available} more in the world`;
}
const inventory = { ...current_inventory };
const leftovers = {};
const plan = craftItem(targetItem, count, inventory, leftovers);
return formatPlan(plan);
}
function isBaseItem(item) {
return loopingItems.has(item) || getItemCraftingRecipes(item) === null;
}
function craftItem(item, count, inventory, leftovers, crafted = { required: {}, steps: [], leftovers: {} }) {
// Check available inventory and leftovers first
const availableInv = inventory[item] || 0;
const availableLeft = leftovers[item] || 0;
const totalAvailable = availableInv + availableLeft;
if (totalAvailable >= count) {
// Use leftovers first, then inventory
const useFromLeft = Math.min(availableLeft, count);
leftovers[item] = availableLeft - useFromLeft;
const remainingNeeded = count - useFromLeft;
if (remainingNeeded > 0) {
inventory[item] = availableInv - remainingNeeded;
}
return crafted;
}
// Use whatever is available
const stillNeeded = count - totalAvailable;
if (availableLeft > 0) leftovers[item] = 0;
if (availableInv > 0) inventory[item] = 0;
if (isBaseItem(item)) {
crafted.required[item] = (crafted.required[item] || 0) + stillNeeded;
return crafted;
}
const recipe = getItemCraftingRecipes(item)?.[0];
if (!recipe) {
crafted.required[item] = stillNeeded;
return crafted;
}
const [ingredients, result] = recipe;
const craftedPerRecipe = result.craftedCount;
const batchCount = Math.ceil(stillNeeded / craftedPerRecipe);
const totalProduced = batchCount * craftedPerRecipe;
// Add excess to leftovers
if (totalProduced > stillNeeded) {
leftovers[item] = (leftovers[item] || 0) + (totalProduced - stillNeeded);
}
// Process each ingredient
for (const [ingredientName, ingredientCount] of Object.entries(ingredients)) {
const totalIngredientNeeded = ingredientCount * batchCount;
craftItem(ingredientName, totalIngredientNeeded, inventory, leftovers, crafted);
}
// Add crafting step
const stepIngredients = Object.entries(ingredients)
.map(([name, amount]) => `${amount * batchCount} ${name}`)
.join(' + ');
crafted.steps.push(`Craft ${stepIngredients} -> ${totalProduced} ${item}`);
return crafted;
}
function formatPlan({ required, steps, leftovers }) {
const lines = [];
if (Object.keys(required).length > 0) {
lines.push('You are missing the following items:');
Object.entries(required).forEach(([item, count]) =>
lines.push(`- ${count} ${item}`));
lines.push('\nOnce you have these items, here\'s your crafting plan:');
} else {
lines.push('You have all items required to craft this item!');
lines.push('Here\'s your crafting plan:');
}
lines.push('');
lines.push(...steps);
if (Object.keys(leftovers).length > 0) {
lines.push('\nYou will have leftover:');
Object.entries(leftovers).forEach(([item, count]) =>
lines.push(`- ${count} ${item}`));
}
return lines.join('\n');
} }