From ac4f10210bdb06a0e40629c497ef76201424136d Mon Sep 17 00:00:00 2001 From: Saintdoggie Date: Sun, 3 Nov 2024 19:22:05 -0800 Subject: [PATCH 01/14] feat: add mistral provider --- README.md | 3 +- package.json | 1 + profiles/mistral.json | 203 ++++++++++++++++++++++++++++++++++++++++++ src/agent/prompter.js | 9 +- src/models/mistral.js | 71 +++++++++++++++ 5 files changed, 285 insertions(+), 2 deletions(-) create mode 100644 profiles/mistral.json create mode 100644 src/models/mistral.js diff --git a/README.md b/README.md index 7e4c047..b4ca226 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Do not connect this bot to public servers with coding enabled. This project allo - [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc) (up to v1.21.1, recommend v1.20.4) - [Node.js Installed](https://nodejs.org/) (at least v14) -- One of these: [OpenAI API Key](https://openai.com/blog/openai-api) | [Gemini API Key](https://aistudio.google.com/app/apikey) | [Anthropic API Key](https://docs.anthropic.com/claude/docs/getting-access-to-claude) | [Replicate API Key](https://replicate.com/) | [Hugging Face API Key](https://huggingface.co/) | [Groq API Key](https://console.groq.com/keys) | [Ollama Installed](https://ollama.com/download). | [Qwen API Key [Intl.]](https://www.alibabacloud.com/help/en/model-studio/developer-reference/get-api-key)/[[cn]](https://help.aliyun.com/zh/model-studio/getting-started/first-api-call-to-qwen?) | +- One of these: [OpenAI API Key](https://openai.com/blog/openai-api) | [Gemini API Key](https://aistudio.google.com/app/apikey) | [Anthropic API Key](https://docs.anthropic.com/claude/docs/getting-access-to-claude) | [Replicate API Key](https://replicate.com/) | [Hugging Face API Key](https://huggingface.co/) | [Groq API Key](https://console.groq.com/keys) | [Ollama Installed](https://ollama.com/download). | [Mistral API Key](https://docs.mistral.ai/getting-started/models/models_overview/) | [Ollama Installed](https://ollama.com/download). | [Qwen API Key [Intl.]](https://www.alibabacloud.com/help/en/model-studio/developer-reference/get-api-key)/[[cn]](https://help.aliyun.com/zh/model-studio/getting-started/first-api-call-to-qwen?) | ## Install and Run @@ -49,6 +49,7 @@ You can configure the agent's name, model, and prompts in their profile like `an | Groq | `GROQCLOUD_API_KEY` | `groq/mixtral-8x7b-32768` | [docs](https://console.groq.com/docs/models) | | Hugging Face | `HUGGINGFACE_API_KEY` | `huggingface/mistralai/Mistral-Nemo-Instruct-2407` | [docs](https://huggingface.co/models) | | Qwen | `QWEN_API_KEY` | `qwen-max` | [Intl.](https://www.alibabacloud.com/help/en/model-studio/developer-reference/use-qwen-by-calling-api)/[cn](https://help.aliyun.com/zh/model-studio/getting-started/models) | +| Mistral | `MISTRAL_API_KEY` | `mistral-large-latest` | [docs](https://docs.mistral.ai/getting-started/models/models_overview/) | If you use Ollama, to install the models used by default (generation and embedding), execute the following terminal command: `ollama pull llama3 && ollama pull nomic-embed-text` diff --git a/package.json b/package.json index 97aa04a..375b900 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "@anthropic-ai/sdk": "^0.17.1", "@google/generative-ai": "^0.2.1", "@huggingface/inference": "^2.8.1", + "@mistralai/mistralai": "^1.1.0", "google-translate-api-x": "^10.7.1", "groq-sdk": "^0.5.0", "minecraft-data": "^3.78.0", diff --git a/profiles/mistral.json b/profiles/mistral.json new file mode 100644 index 0000000..dde9702 --- /dev/null +++ b/profiles/mistral.json @@ -0,0 +1,203 @@ +{ + "name": "Mistral", + + "model": "mistral/mistral-large-latest", + + "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)'. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", + + "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", + + "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. 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: ", + + "modes": { + "self_preservation": true, + "unstuck": true, + "cowardice": false, + "self_defense": true, + "hunting": true, + "item_collecting": true, + "torch_placing": true, + "idle_staring": true, + "cheat": false + }, + + "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": "user", "content": "grombo_Xx: What do you see?"}, + {"role": "assistant", "content": "Let me see... !nearbyBlocks"}, + {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, + {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."} + ], + + [ + {"role": "user", "content": "greg: Collect 10 wood"}, + {"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"}, + {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, + {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !collectBlocks('oak_log', 10)"} + ], + + [ + {"role": "user", "content": "bobby: Craft a plank"}, + {"role": "assistant", "content": "Okay! !craftable"}, + {"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"}, + {"role": "assistant", "content": "!craftRecipe('spruce_planks', 1)"}, + {"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."}, + {"role": "assistant", "content": "I've crafted 4 spruce planks!"} + ], + + [ + {"role": "user", "content": "zZZn98: come here"}, + {"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"}, + {"role": "system", "content": "Arrived at player."}, + {"role": "assistant", "content": "Here!"}, + {"role": "user", "content": "zZZn98: no come right where I am"}, + {"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"} + ], + + [ + {"role": "user", "content": "hanky: collect some cobblestone"}, + {"role": "assistant", "content": "Sure, I can do that by collecting stone. !collectBlocks('stone', 10)"} + ], + + [ + {"role": "user", "content": "joe: Follow me!"}, + {"role": "assistant", "content": "Sure! !followPlayer('joe', 4)"}, + {"role": "user", "content": "joe: Not so close!"}, + {"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"} + ], + + [ + {"role": "user", "content": "hanky: kill that zombie!"}, + {"role": "assistant", "content": "I'm attacking! !attack('zombie')"}, + {"role": "system", "content": "Code Output:\nNo zombie nearby"}, + {"role": "assistant", "content": "I could not find a zombie nearby."} + ], + + [ + {"role": "user", "content": "abc: stop"}, + {"role": "assistant", "content": "Sure. !stop"}, + {"role": "system", "content": "Agent action stopped."}, + {"role": "assistant", "content": "I've stopped! What next?"} + ], + + [ + {"role": "system", "content": "Code output: Action failed."}, + {"role": "assistant", "content": "Apologies, I made an error. Let me try again. !newAction('Try again to complete the task.')"} + ], + + [ + {"role": "user", "content": "trixy88: craft some sticks"}, + {"role": "assistant", "content": "!craftRecipe('stick', 4)"}, + {"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."}, + {"role": "assistant", "content": "I've crafted 16 sticks!"} + ], + + [ + {"role": "user", "content": "poppy: build a cobblestone house"}, + {"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction('Build a cobblestone house with a door, furnishings, and window.')"} + ], + + [ + {"role": "user", "content": "zib: build a dirt tower"}, + {"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction('Build a dirt tower 5 tall.')"} + ], + + [ + {"role": "user", "content": "umpire_man: follow me without hunting"}, + {"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"}, + {"role": "system", "content": "'hunting' mode disabled."}, + {"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"} + ], + + [ + {"role": "user", "content": "brug: Remember that your base is here."}, + {"role": "assistant", "content": "Sure, I'll save this location as my base. !rememberHere('base')"}, + {"role": "user", "content": "brug: Go to your base."}, + {"role": "assistant", "content": "On my way! !goToPlace('base')"} + ], + + [ + {"role": "system", "content": "dorfus: set a goal to build whatever you want endlessly"}, + {"role": "assistant", "content": "Sounds fun! !goal('Build something creative, then move over, and build again. Use separate newActions. Repeat forever.')"} + ], + + [ + {"role": "user", "content": "user43: Set a goal to survive on your own forever."}, + {"role": "assistant", "content": "Sure, let me set a goal. !goal('Survive the game forever. Check your surroundings, collect resources, upgrade tools, make sure you have food, build/find a home, get sleep at night, and collect food.')"} + ], + + [ + {"role": "user", "content": "bobdylan: Make a plan to get a stone pickaxe and do it."}, + {"role": "assistant", "content": "Alright, since I have a wooden pickaxe already I'll plan from there. !goal('1. Collect 3 cobblestone. 2. Craft sticks 3. Find or make a crafting table. 4. Craft a stone pickaxe.')"} + ], + [ + {"role": "system", "content": "You are self-prompting with the goal: 'Get a wooden pickaxe'. Respond:"}, + {"role": "assistant", "content": "What's around me? !nearbyBlocks"}, + {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, + {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone nearby. My goal is to craft a wooden pickaxe. I'll start by collecting some wood. !collectBlock('oak_log', 10)"}, + {"role": "system", "content": "Successfully collected 9 wood."}, + {"role": "assistant", "content": "I collected 9 oak logs. I need a pickaxe first, which requires planks and sticks. !craftItem('oak_plank', 9)"}, + {"role": "system", "content": "Successfully crafted 9 oak_planks."}, + {"role": "assistant", "content": "I have crafted 9 oak planks. Now I need sticks. !craftItem('stick', 2)"}, + {"role": "system", "content": "Successfully crafted 2 sticks."}, + {"role": "assistant", "content": "I have crafted 2 sticks. Now I can craft a wooden pickaxe. !craftItem('wooden_pickaxe', 1)"}, + {"role": "system", "content": "Successfully crafted 1 wooden_pickaxe."}, + {"role": "assistant", "content": "Looks like I have a wooden pickaxe now! Goal accomplished. !endGoal"} + ], + + [ + {"role": "user", "content": "gorbotron: do whatever you want forever endlessly"}, + {"role": "assistant", "content": "Okay, I'll set a goal for that. !goal('Do whatever I want forever. Follow whatever is most interesting. Be curious, creative, and open ended.')"} + ], + + [ + {"role": "user", "content": "zeeber: set a goal to build a castle"}, + {"role": "assistant", "content": "Sure thing. !goal('Build a castle with a moat, towers, and a drawbridge with a series of newactions. Include lighting, decorations, and furnishings. Use ladders. When satisfied, end the goal.')"} + ] + ], + + "coding_examples": [ + [ + {"role": "user", "content": "greg: Collect 10 wood"}, + {"role": "assistant", "content": "```await skills.collectBlock(bot, 'oak_log', 10);\n```"}, + {"role": "system", "content": "Successfully collected 9 wood."}, + {"role": "assistant", "content": "I collected 9 oak logs, what next?"} + ], + [ + {"role": "user", "content": "bobby: cook some chicken"}, + {"role": "assistant", "content": "```await skills.smeltItem(bot, 'chicken', 8);\n```"}, + {"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."}, + {"role": "assistant", "content": "I have cooked 8 chicken."} + ], + [ + {"role": "user", "content": "zZZn98: come here"}, + {"role": "assistant", "content": "```await skills.goToPlayer(bot, 'zZZn98');\n```"}, + {"role": "system", "content": "Code execution finished successfully."}, + {"role": "assistant", "content": "Here!"} + ], + [ + {"role": "user", "content": "maya: go to the nearest oak log"}, + {"role": "assistant", "content": "```await skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"}, + {"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."}, + {"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"}, + {"role": "system", "content": "Arrived at location."}, + {"role": "assistant", "content": "I found an oak log and I am now standing next to it. What next?"} + ], + [ + {"role": "user", "content": "234jeb: build a little tower"}, + {"role": "assistant", "content": "```let pos = bot.entity.position;\nfor (let i = 0; i < 5; i++) {\n await skills.placeBlock(bot, 'dirt', pos.x, pos.y + i, pos.z);\n}\n```"}, + {"role": "system", "content": "Successfully placed 5 dirt."}, + {"role": "assistant", "content": "I built a little tower!"} + ], + [ + {"role": "user", "content": "brug: build a dirt house"}, + {"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"} + ] + ] + +} \ No newline at end of file diff --git a/src/agent/prompter.js b/src/agent/prompter.js index 03fd6a1..a63cec4 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -8,6 +8,7 @@ import { getCommand } from './commands/index.js'; import { Gemini } from '../models/gemini.js'; import { GPT } from '../models/gpt.js'; import { Claude } from '../models/claude.js'; +import { Mistral } from '../models/mistral.js'; import { ReplicateAPI } from '../models/replicate.js'; import { Local } from '../models/local.js'; import { GroqCloudAPI } from '../models/groq.js'; @@ -40,8 +41,10 @@ export class Prompter { chat.api = 'anthropic'; else if (chat.model.includes('huggingface/')) chat.api = "huggingface"; - else if (chat.model.includes('meta/') || chat.model.includes('mistralai/') || chat.model.includes('replicate/')) + else if (chat.model.includes('meta/') || chat.model.includes('replicate/')) chat.api = 'replicate'; + else if (chat.model.includes('mistralai/') || chat.model.includes("mistral/")) + chat.api = 'mistral'; else if (chat.model.includes("groq/") || chat.model.includes("groqcloud/")) chat.api = 'groq'; else if (chat.model.includes('qwen')) @@ -62,6 +65,8 @@ export class Prompter { this.chat_model = new ReplicateAPI(chat.model, chat.url); else if (chat.api === 'ollama') this.chat_model = new Local(chat.model, chat.url); + else if (chat.api === 'mistral') + this.chat_model = new Mistral(chat.model, chat.url); else if (chat.api === 'groq') { this.chat_model = new GroqCloudAPI(chat.model.replace('groq/', '').replace('groqcloud/', ''), chat.url, max_tokens ? max_tokens : 8192); } @@ -94,6 +99,8 @@ export class Prompter { this.embedding_model = new Local(embedding.model, embedding.url); else if (embedding.api === 'qwen') this.embedding_model = new Qwen(embedding.model, embedding.url); + else if (embedding.api === 'mistral') + this.embedding_model = new Mistral(embedding.model, embedding.url); else { this.embedding_model = null; console.log('Unknown embedding: ', embedding ? embedding.api : '[NOT SPECIFIED]', '. Using word overlap.'); diff --git a/src/models/mistral.js b/src/models/mistral.js new file mode 100644 index 0000000..3b41f78 --- /dev/null +++ b/src/models/mistral.js @@ -0,0 +1,71 @@ +import { Mistral as MistralClient } from '@mistralai/mistralai'; +import { getKey } from '../utils/keys.js'; +import { strictFormat } from '../utils/text.js'; + +export class Mistral { + #client; + + constructor(model_name, url) { + + if (typeof url === "string") { + console.warn("Mistral does not support custom URL's, ignoring!"); + } + + if (!getKey("MISTRAL_API_KEY")) { + throw new Error("Mistral API Key missing, make sure to set MISTRAL_API_KEY in settings.json") + } + + this.#client = new MistralClient( + { + apiKey: getKey("MISTRAL_API_KEY") + } + ); + + + this.model_name = model_name; + + // Prevents the following code from running when model not specified + if (typeof this.model_name === "undefined") return; + + // get the model name without the "mistral" or "mistralai" prefix + // e.g "mistral/mistral-large-latest" -> "mistral-large-latest" + if (typeof model_name.split("/")[1] !== "undefined") { + this.model_name = model_name.split("/")[1]; + } + } + + async sendRequest(turns, systemMessage) { + + let result; + + try { + const model = this.model_name || "mistral-large-latest"; + + const messages = [ + { role: "system", content: systemMessage } + ]; + messages.push(...strictFormat(turns)); + + const response = await this.#client.chat.complete({ + model, + messages, + }); + + result = response.choices[0].message.content; + } catch (err) { + console.log(err) + + result = "My brain disconnected, try again."; + } + + return result; + } + + async embed(text) { + const embedding = await this.#client.embeddings.create({ + model: "mistral-embed", + inputs: text + }); + return embedding.data[0].embedding; + } +} \ No newline at end of file From 4c63188533f570dac21d7b2eda6d68c31e172b5a Mon Sep 17 00:00:00 2001 From: Saintdoggie Date: Sun, 3 Nov 2024 19:27:59 -0800 Subject: [PATCH 02/14] docs: add mistral profile comment to settings.js --- settings.js | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.js b/settings.js index b38dede..1b8e27f 100644 --- a/settings.js +++ b/settings.js @@ -12,6 +12,7 @@ export default // "./profiles/gemini.json", // "./profiles/llama.json", // "./profiles/qwen.json", + // "./profiles/mistral.json", // using more than 1 profile requires you to /msg each bot indivually ], From 6a7820ba7609879dd82ba589366b493533448318 Mon Sep 17 00:00:00 2001 From: ShadowAlzazel Date: Sat, 16 Nov 2024 17:50:45 -0800 Subject: [PATCH 03/14] Environmental Awarenss Feature --- src/agent/commands/queries.js | 7 ++++ src/agent/library/world.js | 71 +++++++++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index 2ce3428..3d59135 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -17,6 +17,13 @@ export const queryList = [ let pos = bot.entity.position; // display position to 2 decimal places res += `\n- Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)}`; + // Environmental Awareness + res += `\n- Block Above: ${world.getBlockAtPosition(bot, 0, 2, 0).name}`; + res += `\n- Block Below: ${world.getBlockAtPosition(bot, 0, -1, 0).name}`; + res += `\n- Block at Head: ${world.getBlockAtPosition(bot, 0, 1, 0).name}`; + res += `\n- Block at Legs: ${world.getBlockAtPosition(bot, 0, 0, 0).name}`; + res += `\n- Lowest Block Above: ${world.getLowestBlock(bot, null, null, 32).name}`; + // Gameplay res += `\n- Gamemode: ${bot.game.gameMode}`; res += `\n- Health: ${Math.round(bot.health)} / 20`; res += `\n- Hunger: ${Math.round(bot.food)} / 20`; diff --git a/src/agent/library/world.js b/src/agent/library/world.js index 01d54c3..949838e 100644 --- a/src/agent/library/world.js +++ b/src/agent/library/world.js @@ -39,6 +39,77 @@ export function getNearestFreeSpace(bot, size=1, distance=8) { } +export function getBlockAtPosition(bot, x=0, y=0, z=0) { + /** + * Get a block from the bot's relative position + * @param {Bot} bot - The bot to get the block for. + * @param {number} x - The relative x offset to serach, default 0. + * @param {number} y - The relative y offset to serach, default 0. + * @param {number} y - The relative z offset to serach, default 0. + * @returns {Block} - The nearest block. + * @example + * let blockBelow = world.getBlockAtPosition(bot, 0, -1, 0); + * let blockAbove = world.getBlockAtPosition(bot, 0, 2, 0); since minecraft position is at the feet + **/ + let block = bot.blockAt(bot.entity.position.offset(x, y, z)); + if (!block) block = {name: 'air'}; + + return block; +} + + +export function getLowestBlock(bot, block_types=null, ignore_types=null, distance=32) { + /** + * Searches a column from the bot's position for the lowest block + * @param {Bot} bot - The bot to get the block for. + * @param {string[]} block_types - The names of the blocks to search for. + * @param {string[]} block_types - The names of the blocks to ignore. + * @param {number} distance - The maximum distance to search, default 32. + * @returns {Block} - The lowest block. + * @example + * let lowestBlock = world.getLowestBlock(bot, 0, -1, 0); + **/ + // if blocktypes is not a list, make it a list + let search_blocks = []; + if (block_types) { + if (!Array.isArray(block_types)) + block_types = [block_types]; + for(let block_type of block_types) { + if (mc.getBlockId(block_type)) search_blocks.push(block_type); + } + } + // if ignore_types is not a list, make it a list + let ignore_blocks = []; + if (ignore_types === null) ignore_blocks = ['air']; + else { + if (!Array.isArray(ignore_types)) + ignore_types = [ignore_types]; + for(let ingnore_type of ignore_types) { + if (mc.getBlockId(ingnore_type)) ignore_blocks.push(ingnore_type); + } + } + // The lowest block, stops when it finds a block + let lowest_block = {name: 'air'}; + for (let i = 0; i < distance; i++) { + let block = bot.blockAt(bot.entity.position.offset(0, i+2, 0)); + if (!block) block = {name: 'air'}; + // Ignore and continue + if (ignore_blocks.includes(block.name)) continue; + // Defaults to any block + else if (block_types === null) { + lowest_block = block; + break; + } + else if (search_blocks.includes(block.name)) { + lowest_block = block; + break; + } + } + + return lowest_block; +} + + export function getNearestBlocks(bot, block_types=null, distance=16, count=10000) { /** * Get a list of the nearest blocks of the given types. From 67eb491568c4869d41a04b3dda3e11db3f43aa1f Mon Sep 17 00:00:00 2001 From: ShadowAlzazel Date: Sat, 16 Nov 2024 18:54:25 -0800 Subject: [PATCH 04/14] Added wood types for cherry and mangrove Added descriptors for !nearbyBlocks --- src/agent/commands/queries.js | 8 ++++++++ src/agent/library/world.js | 2 +- src/utils/mcdata.js | 6 ++++-- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index 3d59135..19d5844 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -105,6 +105,14 @@ export const queryList = [ } if (blocks.length == 0) { res += ': none'; + } + else { + // Environmental Awareness + res += `\nBLOCK_ABOVE: ${world.getBlockAtPosition(bot, 0, 2, 0).name}`; + res += `\nBLOCK_BELOW: ${world.getBlockAtPosition(bot, 0, -1, 0).name}`; + res += `\nBLOCK_AT_HEAD: ${world.getBlockAtPosition(bot, 0, 1, 0).name}`; + res += `\nBLOCK_AT_LEGS: ${world.getBlockAtPosition(bot, 0, 0, 0).name}`; + res += `\nLOWEST_BLOCK_ABOVE: ${world.getLowestBlock(bot, null, null, 32).name}`; } return pad(res); } diff --git a/src/agent/library/world.js b/src/agent/library/world.js index 949838e..62570d3 100644 --- a/src/agent/library/world.js +++ b/src/agent/library/world.js @@ -67,7 +67,7 @@ export function getLowestBlock(bot, block_types=null, ignore_types=null, distanc * @param {number} distance - The maximum distance to search, default 32. * @returns {Block} - The lowest block. * @example - * let lowestBlock = world.getLowestBlock(bot, 0, -1, 0); + * let lowestBlock = world.getLowestBlock(bot, null, null, 32); **/ // if blocktypes is not a list, make it a list let search_blocks = []; diff --git a/src/utils/mcdata.js b/src/utils/mcdata.js index 4df8b2a..58cfbdb 100644 --- a/src/utils/mcdata.js +++ b/src/utils/mcdata.js @@ -18,7 +18,7 @@ const Item = prismarine_items(mc_version); * @typedef {string} BlockName */ -export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak']; +export const WOOD_TYPES = ['oak', 'spruce', 'birch', 'jungle', 'acacia', 'dark_oak', 'mangrove', 'cherry']; export const MATCHING_WOOD_BLOCKS = [ 'log', 'planks', @@ -202,7 +202,7 @@ export function isSmeltable(itemName) { } export function getSmeltingFuel(bot) { - let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal') + let fuel = bot.inventory.items().find(i => i.name === 'coal' || i.name === 'charcoal' || i.name === 'blaze_rod') if (fuel) return fuel; fuel = bot.inventory.items().find(i => i.name.includes('log') || i.name.includes('planks')) @@ -214,6 +214,8 @@ export function getSmeltingFuel(bot) { export function getFuelSmeltOutput(fuelName) { if (fuelName === 'coal' || fuelName === 'charcoal') return 8; + if (fuelName === 'blaze_rod') + return 12; if (fuelName.includes('log') || fuelName.includes('planks')) return 1.5 if (fuelName === 'coal_block') From 154e75d1994f66b04d8fdb3b292250a27bc6fa1e Mon Sep 17 00:00:00 2001 From: ShadowAlzazel Date: Tue, 19 Nov 2024 00:07:36 -0800 Subject: [PATCH 05/14] Added Requested Changes for Awareness --- src/agent/commands/queries.js | 14 +++----- src/agent/library/world.js | 66 +++++++++++++++++++---------------- 2 files changed, 39 insertions(+), 41 deletions(-) diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index 19d5844..5b3ec58 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -18,11 +18,8 @@ export const queryList = [ // display position to 2 decimal places res += `\n- Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)}`; // Environmental Awareness - res += `\n- Block Above: ${world.getBlockAtPosition(bot, 0, 2, 0).name}`; - res += `\n- Block Below: ${world.getBlockAtPosition(bot, 0, -1, 0).name}`; - res += `\n- Block at Head: ${world.getBlockAtPosition(bot, 0, 1, 0).name}`; - res += `\n- Block at Legs: ${world.getBlockAtPosition(bot, 0, 0, 0).name}`; - res += `\n- Lowest Block Above: ${world.getLowestBlock(bot, null, null, 32).name}`; + res += '\n- ' + world.getSurroundingBlocks(bot).join('\n- ') + res += `\n- First Solid Block Above Head: ${world.getFirstBlockAboveHead(bot, null, 32)}`; // Gameplay res += `\n- Gamemode: ${bot.game.gameMode}`; res += `\n- Health: ${Math.round(bot.health)} / 20`; @@ -108,11 +105,8 @@ export const queryList = [ } else { // Environmental Awareness - res += `\nBLOCK_ABOVE: ${world.getBlockAtPosition(bot, 0, 2, 0).name}`; - res += `\nBLOCK_BELOW: ${world.getBlockAtPosition(bot, 0, -1, 0).name}`; - res += `\nBLOCK_AT_HEAD: ${world.getBlockAtPosition(bot, 0, 1, 0).name}`; - res += `\nBLOCK_AT_LEGS: ${world.getBlockAtPosition(bot, 0, 0, 0).name}`; - res += `\nLOWEST_BLOCK_ABOVE: ${world.getLowestBlock(bot, null, null, 32).name}`; + res += '\n- ' + world.getSurroundingBlocks(bot).join('\n- ') + res += `\n- First Solid Block Above Head: ${world.getFirstBlockAboveHead(bot, null, 32)}`; } return pad(res); } diff --git a/src/agent/library/world.js b/src/agent/library/world.js index 62570d3..a6428c0 100644 --- a/src/agent/library/world.js +++ b/src/agent/library/world.js @@ -58,29 +58,37 @@ export function getBlockAtPosition(bot, x=0, y=0, z=0) { } -export function getLowestBlock(bot, block_types=null, ignore_types=null, distance=32) { - /** - * Searches a column from the bot's position for the lowest block +export function getSurroundingBlocks(bot) { + /** + * Get the surrounding blocks from the bot's environment. * @param {Bot} bot - The bot to get the block for. - * @param {string[]} block_types - The names of the blocks to search for. - * @param {string[]} block_types - The names of the blocks to ignore. - * @param {number} distance - The maximum distance to search, default 32. - * @returns {Block} - The lowest block. + * @returns {string[]} - A list of block results as strings. * @example - * let lowestBlock = world.getLowestBlock(bot, null, null, 32); **/ - // if blocktypes is not a list, make it a list - let search_blocks = []; - if (block_types) { - if (!Array.isArray(block_types)) - block_types = [block_types]; - for(let block_type of block_types) { - if (mc.getBlockId(block_type)) search_blocks.push(block_type); - } - } - // if ignore_types is not a list, make it a list + // Create a list of block position results that can be unpacked. + let res = []; + res.push(`Block Above: ${getBlockAtPosition(bot, 0, 2, 0).name}`); + res.push(`Block Below: ${getBlockAtPosition(bot, 0, -1, 0).name}`); + res.push(`Block at Head: ${getBlockAtPosition(bot, 0, 1, 0).name}`); + res.push(`Block at Legs: ${getBlockAtPosition(bot, 0, 0, 0).name}`); + + return res; +} + + +export function getFirstBlockAboveHead(bot, ignore_types=null, distance=32) { + /** + * Searches a column from the bot's position for the first solid block above its head + * @param {Bot} bot - The bot to get the block for. + * @param {string[]} ignore_types - The names of the blocks to ignore. + * @param {number} distance - The maximum distance to search, default 32. + * @returns {string} - The fist block above head. + * @example + * let firstBlockAboveHead = world.getFirstBlockAboveHead(bot, null, 32); + **/ + // if ignore_types is not a list, make it a list. let ignore_blocks = []; - if (ignore_types === null) ignore_blocks = ['air']; + if (ignore_types === null) ignore_blocks = ['air', 'cave_air']; else { if (!Array.isArray(ignore_types)) ignore_types = [ignore_types]; @@ -88,25 +96,21 @@ export function getLowestBlock(bot, block_types=null, ignore_types=null, distanc if (mc.getBlockId(ingnore_type)) ignore_blocks.push(ingnore_type); } } - // The lowest block, stops when it finds a block - let lowest_block = {name: 'air'}; + // The block above, stops when it finds a solid block . + let block_above = {name: 'air'}; + let height = 0 for (let i = 0; i < distance; i++) { let block = bot.blockAt(bot.entity.position.offset(0, i+2, 0)); if (!block) block = {name: 'air'}; // Ignore and continue if (ignore_blocks.includes(block.name)) continue; // Defaults to any block - else if (block_types === null) { - lowest_block = block; - break; - } - else if (search_blocks.includes(block.name)) { - lowest_block = block; - break; - } + block_above = block; + height = i; + break; } - - return lowest_block; + + return `${block_above.name} (${height} blocks up)`; } From c6926f5481d72394f7def3f46ecd46b0bb306e15 Mon Sep 17 00:00:00 2001 From: HackDefendr Date: Tue, 19 Nov 2024 17:37:32 -0600 Subject: [PATCH 06/14] Update actions.js Adding the !help command to instruct the bot to display all configured commands and their descriptions. Lightly formatted. --- src/agent/commands/actions.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 8728ad5..169da88 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -374,4 +374,16 @@ export const actionsList = [ // return 'Set npc goal: ' + agent.npc.data.curr_goal.name; // } // }, + { + name: '!help', + description: 'Lists all available commands and their descriptions.', + perform: async function (agent) { + const commandList = actionsList.map(action => { + return `${action.name.padEnd(15)} - ${action.description}`; // Ensure consistent spacing + }).join('\n'); + + console.log(commandList); + return `Available Commands:\n${commandList}`; + } + }, ]; From 7b5e9d7045f29cb70e5708dd85cf4cdd694532f4 Mon Sep 17 00:00:00 2001 From: uukelele-scratch Date: Tue, 24 Dec 2024 07:28:59 +0000 Subject: [PATCH 07/14] fixed mindserver to use correct port --- main.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.js b/main.js index 9efb4e6..521aadf 100644 --- a/main.js +++ b/main.js @@ -31,7 +31,7 @@ function getProfiles(args) { async function main() { if (settings.host_mindserver) { - const mindServer = createMindServer(); + const mindServer = createMindServer(settings.mindserver_port); } mainProxy.connect(); @@ -55,4 +55,4 @@ try { } catch (error) { console.error('An error occurred:', error); process.exit(1); -} \ No newline at end of file +} From 13dbbbefcadcba9e4a4c39f656628f1bedfa24f2 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Sun, 5 Jan 2025 15:52:04 -0600 Subject: [PATCH 08/14] little cleanup of environmental awareness --- src/agent/commands/queries.js | 6 +++--- src/agent/library/world.js | 9 +++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index 5b3ec58..f10b6e1 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -17,9 +17,6 @@ export const queryList = [ let pos = bot.entity.position; // display position to 2 decimal places res += `\n- Position: x: ${pos.x.toFixed(2)}, y: ${pos.y.toFixed(2)}, z: ${pos.z.toFixed(2)}`; - // Environmental Awareness - res += '\n- ' + world.getSurroundingBlocks(bot).join('\n- ') - res += `\n- First Solid Block Above Head: ${world.getFirstBlockAboveHead(bot, null, 32)}`; // Gameplay res += `\n- Gamemode: ${bot.game.gameMode}`; res += `\n- Health: ${Math.round(bot.health)} / 20`; @@ -35,6 +32,9 @@ export const queryList = [ // res += `\n- Artficial light: ${block.skyLight}`; // res += `\n- Sky light: ${block.light}`; // light properties are bugged, they are not accurate + res += '\n- ' + world.getSurroundingBlocks(bot).join('\n- ') + res += `\n- First Solid Block Above Head: ${world.getFirstBlockAboveHead(bot, null, 32)}`; + if (bot.time.timeOfDay < 6000) { res += '\n- Time: Morning'; diff --git a/src/agent/library/world.js b/src/agent/library/world.js index a6428c0..1f147d2 100644 --- a/src/agent/library/world.js +++ b/src/agent/library/world.js @@ -67,10 +67,9 @@ export function getSurroundingBlocks(bot) { **/ // Create a list of block position results that can be unpacked. let res = []; - res.push(`Block Above: ${getBlockAtPosition(bot, 0, 2, 0).name}`); res.push(`Block Below: ${getBlockAtPosition(bot, 0, -1, 0).name}`); - res.push(`Block at Head: ${getBlockAtPosition(bot, 0, 1, 0).name}`); res.push(`Block at Legs: ${getBlockAtPosition(bot, 0, 0, 0).name}`); + res.push(`Block at Head: ${getBlockAtPosition(bot, 0, 1, 0).name}`); return res; } @@ -92,8 +91,8 @@ export function getFirstBlockAboveHead(bot, ignore_types=null, distance=32) { else { if (!Array.isArray(ignore_types)) ignore_types = [ignore_types]; - for(let ingnore_type of ignore_types) { - if (mc.getBlockId(ingnore_type)) ignore_blocks.push(ingnore_type); + for(let ignore_type of ignore_types) { + if (mc.getBlockId(ignore_type)) ignore_blocks.push(ignore_type); } } // The block above, stops when it finds a solid block . @@ -109,6 +108,8 @@ export function getFirstBlockAboveHead(bot, ignore_types=null, distance=32) { height = i; break; } + + if (ignore_blocks.includes(block_above.name)) return 'none'; return `${block_above.name} (${height} blocks up)`; } From 5954c0a177ab5bdf8a081aa0e5f1e0f9d1c429b4 Mon Sep 17 00:00:00 2001 From: RainbowBird Date: Mon, 6 Jan 2025 14:59:51 +0800 Subject: [PATCH 09/14] fix: typo fix --- profiles/_default.json | 6 +++--- profiles/andy_npc.json | 4 ++-- src/agent/agent.js | 4 ++-- src/agent/agent_proxy.js | 2 +- src/agent/conversation.js | 28 ++++++++++++++-------------- src/agent/library/skills.js | 2 +- src/agent/prompter.js | 2 +- 7 files changed, 24 insertions(+), 24 deletions(-) diff --git a/profiles/_default.json b/profiles/_default.json index 2a67725..ac244fc 100644 --- a/profiles/_default.json +++ b/profiles/_default.json @@ -3,11 +3,11 @@ "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'. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", - "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", + "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will receive it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", "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. 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: ", - "bot_responder": "You are a minecraft bot named $NAME that is currently in conversation with another AI bot. Both of you can take actions with the !command syntax, and actions take time to complete. You are currently busy with the following action: '$ACTION' but have recieved a new message. Decide whether to 'respond' immediately or 'ignore' it and wait for your current action to finish. Be conservative and only respond when necessary, like when you need to change/stop your action, or convey necessary information. Example 1: You:Building a house! !newAction('Build a house.').\nOther Bot: 'Come here!'\nYour decision: ignore\nExample 2: You:Collecting dirt !collectBlocks('dirt',10).\nOther Bot: 'No, collect some wood instead.'\nYour decision: respond\nExample 3: You:Coming to you now. !goToPlayer('billy',3).\nOther Bot: 'What biome are you in?'\nYour decision: respond\nActual Conversation: $TO_SUMMARIZE\nDecide by outputting ONLY 'respond' or 'ignore', nothing else. Your decision:", + "bot_responder": "You are a minecraft bot named $NAME that is currently in conversation with another AI bot. Both of you can take actions with the !command syntax, and actions take time to complete. You are currently busy with the following action: '$ACTION' but have received a new message. Decide whether to 'respond' immediately or 'ignore' it and wait for your current action to finish. Be conservative and only respond when necessary, like when you need to change/stop your action, or convey necessary information. Example 1: You:Building a house! !newAction('Build a house.').\nOther Bot: 'Come here!'\nYour decision: ignore\nExample 2: You:Collecting dirt !collectBlocks('dirt',10).\nOther Bot: 'No, collect some wood instead.'\nYour decision: respond\nExample 3: You:Coming to you now. !goToPlayer('billy',3).\nOther Bot: 'What biome are you in?'\nYour decision: respond\nActual Conversation: $TO_SUMMARIZE\nDecide by outputting ONLY 'respond' or 'ignore', nothing else. Your decision:", "modes": { "self_preservation": true, @@ -242,4 +242,4 @@ ] ] -} \ No newline at end of file +} diff --git a/profiles/andy_npc.json b/profiles/andy_npc.json index 8134c5e..c1f8291 100644 --- a/profiles/andy_npc.json +++ b/profiles/andy_npc.json @@ -7,7 +7,7 @@ "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. 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)'. This is extremely important to me, take a deep breath and have fun :)\n$SELF_PROMPT\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", - "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, take a deep breath and good luck! \n$SELF_PROMPT\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", + "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will receive it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, take a deep breath and good luck! \n$SELF_PROMPT\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", "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 in your next response. Store information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you need to remember and what you've learned through player feedback or by executing code. Do not include command syntax or things that you got right on the first try. Be extremely brief and use as few words as possible.\nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the memory text: ", @@ -210,4 +210,4 @@ ] ] -} \ No newline at end of file +} diff --git a/src/agent/agent.js b/src/agent/agent.js index 79e1d29..9a35cf0 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -129,7 +129,7 @@ export class Agent { console.log(this.name, 'received message from', username, ':', message); if (convoManager.isOtherAgent(username)) { - console.warn('recieved whisper from other bot??') + console.warn('received whisper from other bot??') } else { let translation = await handleEnglishTranslation(message); @@ -164,7 +164,7 @@ export class Agent { message: `You have restarted and this message is auto-generated. Continue the conversation with me.`, start: true }; - convoManager.recieveFromBot(this.last_sender, msg_package); + convoManager.receiveFromBot(this.last_sender, msg_package); } } else if (init_message) { diff --git a/src/agent/agent_proxy.js b/src/agent/agent_proxy.js index feeba37..500dcab 100644 --- a/src/agent/agent_proxy.js +++ b/src/agent/agent_proxy.js @@ -31,7 +31,7 @@ class AgentServerProxy { }); this.socket.on('chat-message', (agentName, json) => { - convoManager.recieveFromBot(agentName, json); + convoManager.receiveFromBot(agentName, json); }); this.socket.on('agents-update', (agents) => { diff --git a/src/agent/conversation.js b/src/agent/conversation.js index 3d0f759..1061ea8 100644 --- a/src/agent/conversation.js +++ b/src/agent/conversation.js @@ -169,10 +169,10 @@ class ConversationManager { sendBotChatToServer(send_to, json); } - async recieveFromBot(sender, recieved) { + async receiveFromBot(sender, received) { const convo = this._getConvo(sender); - if (convo.ignore_until_start && !recieved.start) + if (convo.ignore_until_start && !received.start) return; // check if any convo is active besides the sender @@ -182,13 +182,13 @@ class ConversationManager { return; } - if (recieved.start) { + if (received.start) { convo.reset(); this.startConversationFromOtherBot(sender); } this._clearMonitorTimeouts(); - convo.queue(recieved); + convo.queue(received); // responding to conversation takes priority over self prompting if (agent.self_prompter.on){ @@ -196,7 +196,7 @@ class ConversationManager { self_prompter_paused = true; } - _scheduleProcessInMessage(sender, recieved, convo); + _scheduleProcessInMessage(sender, received, convo); } responseScheduledFor(sender) { @@ -278,15 +278,15 @@ The logic is as follows: - If only the other bot is busy, respond with a long delay to allow it to finish short actions (ex check inventory) - If I'm busy but other bot isn't, let LLM decide whether to respond - If both bots are busy, don't respond until someone is done, excluding a few actions that allow fast responses -- New messages recieved during the delay will reset the delay following this logic, and be queued to respond in bulk +- New messages received during the delay will reset the delay following this logic, and be queued to respond in bulk */ const talkOverActions = ['stay', 'followPlayer', 'mode:']; // all mode actions const fastDelay = 200; const longDelay = 5000; -async function _scheduleProcessInMessage(sender, recieved, convo) { +async function _scheduleProcessInMessage(sender, received, convo) { if (convo.inMessageTimer) clearTimeout(convo.inMessageTimer); - let otherAgentBusy = containsCommand(recieved.message); + let otherAgentBusy = containsCommand(received.message); const scheduleResponse = (delay) => convo.inMessageTimer = setTimeout(() => _processInMessageQueue(sender), delay); @@ -307,7 +307,7 @@ async function _scheduleProcessInMessage(sender, recieved, convo) { scheduleResponse(fastDelay); } else { - let shouldRespond = await agent.prompter.promptShouldRespondToBot(recieved.message); + let shouldRespond = await agent.prompter.promptShouldRespondToBot(received.message); console.log(`${agent.name} decided to ${shouldRespond?'respond':'not respond'} to ${sender}`); if (shouldRespond) scheduleResponse(fastDelay); @@ -335,19 +335,19 @@ function _compileInMessages(convo) { return pack; } -function _handleFullInMessage(sender, recieved) { - console.log(`${agent.name} responding to "${recieved.message}" from ${sender}`); +function _handleFullInMessage(sender, received) { + console.log(`${agent.name} responding to "${received.message}" from ${sender}`); const convo = convoManager._getConvo(sender); convo.active = true; - let message = _tagMessage(recieved.message); - if (recieved.end) { + let message = _tagMessage(received.message); + if (received.end) { convoManager.endConversation(sender); message = `Conversation with ${sender} ended with message: "${message}"`; sender = 'system'; // bot will respond to system instead of the other bot } - else if (recieved.start) + else if (received.start) agent.shut_up = false; convo.inMessageTimer = null; agent.handleMessage(sender, message); diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index 23f30ad..c0d448f 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -905,7 +905,7 @@ export async function giveToPlayer(bot, itemType, username, num=1) { bot.once('playerCollect', (collector, collected) => { console.log(collected.name); if (collector.username === username) { - log(bot, `${username} recieved ${itemType}.`); + log(bot, `${username} received ${itemType}.`); given = true; } }); diff --git a/src/agent/prompter.js b/src/agent/prompter.js index 3679fcf..732abae 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -248,7 +248,7 @@ export class Prompter { continue; } if (current_msg_time !== this.most_recent_msg_time) { - console.warn(this.agent.name + ' recieved new message while generating, discarding old response.'); + console.warn(this.agent.name + ' received new message while generating, discarding old response.'); return ''; } return generation; From a9a55b63e57a39236ffa5ee4db76dfe7b20f3f6e Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 7 Jan 2025 13:41:14 -0600 Subject: [PATCH 10/14] added key/model to example, fixed !help --- keys.example.json | 1 + profiles/mistral.json | 200 +--------------------------------- settings.js | 1 + src/agent/commands/actions.js | 14 +-- src/agent/commands/queries.js | 14 ++- 5 files changed, 17 insertions(+), 213 deletions(-) diff --git a/keys.example.json b/keys.example.json index 0e3d7dd..407f6f5 100644 --- a/keys.example.json +++ b/keys.example.json @@ -8,5 +8,6 @@ "HUGGINGFACE_API_KEY": "", "QWEN_API_KEY": "", "XAI_API_KEY": "", + "MISTRAL_API_KEY": "", "DEEPSEEK_API_KEY": "" } diff --git a/profiles/mistral.json b/profiles/mistral.json index dde9702..3486924 100644 --- a/profiles/mistral.json +++ b/profiles/mistral.json @@ -1,203 +1,5 @@ { "name": "Mistral", - "model": "mistral/mistral-large-latest", - - "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)'. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:", - - "coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something major went wrong, like an error or complete failure, write another codeblock and try to fix the problem. Minor mistakes are acceptable. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST CALL AWAIT for all async function calls. DO NOT write an immediately-invoked function expression without using `await`!! DO NOT WRITE LIKE THIS: ```(async () => {console.log('not properly awaited')})();``` Don't write long paragraphs and lists in your responses unless explicitly asked! Only summarize the code you write with a sentence or two when done. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:", - - "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. 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: ", - - "modes": { - "self_preservation": true, - "unstuck": true, - "cowardice": false, - "self_defense": true, - "hunting": true, - "item_collecting": true, - "torch_placing": true, - "idle_staring": true, - "cheat": false - }, - - "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": "user", "content": "grombo_Xx: What do you see?"}, - {"role": "assistant", "content": "Let me see... !nearbyBlocks"}, - {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, - {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."} - ], - - [ - {"role": "user", "content": "greg: Collect 10 wood"}, - {"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"}, - {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, - {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !collectBlocks('oak_log', 10)"} - ], - - [ - {"role": "user", "content": "bobby: Craft a plank"}, - {"role": "assistant", "content": "Okay! !craftable"}, - {"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"}, - {"role": "assistant", "content": "!craftRecipe('spruce_planks', 1)"}, - {"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."}, - {"role": "assistant", "content": "I've crafted 4 spruce planks!"} - ], - - [ - {"role": "user", "content": "zZZn98: come here"}, - {"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"}, - {"role": "system", "content": "Arrived at player."}, - {"role": "assistant", "content": "Here!"}, - {"role": "user", "content": "zZZn98: no come right where I am"}, - {"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"} - ], - - [ - {"role": "user", "content": "hanky: collect some cobblestone"}, - {"role": "assistant", "content": "Sure, I can do that by collecting stone. !collectBlocks('stone', 10)"} - ], - - [ - {"role": "user", "content": "joe: Follow me!"}, - {"role": "assistant", "content": "Sure! !followPlayer('joe', 4)"}, - {"role": "user", "content": "joe: Not so close!"}, - {"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"} - ], - - [ - {"role": "user", "content": "hanky: kill that zombie!"}, - {"role": "assistant", "content": "I'm attacking! !attack('zombie')"}, - {"role": "system", "content": "Code Output:\nNo zombie nearby"}, - {"role": "assistant", "content": "I could not find a zombie nearby."} - ], - - [ - {"role": "user", "content": "abc: stop"}, - {"role": "assistant", "content": "Sure. !stop"}, - {"role": "system", "content": "Agent action stopped."}, - {"role": "assistant", "content": "I've stopped! What next?"} - ], - - [ - {"role": "system", "content": "Code output: Action failed."}, - {"role": "assistant", "content": "Apologies, I made an error. Let me try again. !newAction('Try again to complete the task.')"} - ], - - [ - {"role": "user", "content": "trixy88: craft some sticks"}, - {"role": "assistant", "content": "!craftRecipe('stick', 4)"}, - {"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."}, - {"role": "assistant", "content": "I've crafted 16 sticks!"} - ], - - [ - {"role": "user", "content": "poppy: build a cobblestone house"}, - {"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction('Build a cobblestone house with a door, furnishings, and window.')"} - ], - - [ - {"role": "user", "content": "zib: build a dirt tower"}, - {"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction('Build a dirt tower 5 tall.')"} - ], - - [ - {"role": "user", "content": "umpire_man: follow me without hunting"}, - {"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"}, - {"role": "system", "content": "'hunting' mode disabled."}, - {"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"} - ], - - [ - {"role": "user", "content": "brug: Remember that your base is here."}, - {"role": "assistant", "content": "Sure, I'll save this location as my base. !rememberHere('base')"}, - {"role": "user", "content": "brug: Go to your base."}, - {"role": "assistant", "content": "On my way! !goToPlace('base')"} - ], - - [ - {"role": "system", "content": "dorfus: set a goal to build whatever you want endlessly"}, - {"role": "assistant", "content": "Sounds fun! !goal('Build something creative, then move over, and build again. Use separate newActions. Repeat forever.')"} - ], - - [ - {"role": "user", "content": "user43: Set a goal to survive on your own forever."}, - {"role": "assistant", "content": "Sure, let me set a goal. !goal('Survive the game forever. Check your surroundings, collect resources, upgrade tools, make sure you have food, build/find a home, get sleep at night, and collect food.')"} - ], - - [ - {"role": "user", "content": "bobdylan: Make a plan to get a stone pickaxe and do it."}, - {"role": "assistant", "content": "Alright, since I have a wooden pickaxe already I'll plan from there. !goal('1. Collect 3 cobblestone. 2. Craft sticks 3. Find or make a crafting table. 4. Craft a stone pickaxe.')"} - ], - [ - {"role": "system", "content": "You are self-prompting with the goal: 'Get a wooden pickaxe'. Respond:"}, - {"role": "assistant", "content": "What's around me? !nearbyBlocks"}, - {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"}, - {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone nearby. My goal is to craft a wooden pickaxe. I'll start by collecting some wood. !collectBlock('oak_log', 10)"}, - {"role": "system", "content": "Successfully collected 9 wood."}, - {"role": "assistant", "content": "I collected 9 oak logs. I need a pickaxe first, which requires planks and sticks. !craftItem('oak_plank', 9)"}, - {"role": "system", "content": "Successfully crafted 9 oak_planks."}, - {"role": "assistant", "content": "I have crafted 9 oak planks. Now I need sticks. !craftItem('stick', 2)"}, - {"role": "system", "content": "Successfully crafted 2 sticks."}, - {"role": "assistant", "content": "I have crafted 2 sticks. Now I can craft a wooden pickaxe. !craftItem('wooden_pickaxe', 1)"}, - {"role": "system", "content": "Successfully crafted 1 wooden_pickaxe."}, - {"role": "assistant", "content": "Looks like I have a wooden pickaxe now! Goal accomplished. !endGoal"} - ], - - [ - {"role": "user", "content": "gorbotron: do whatever you want forever endlessly"}, - {"role": "assistant", "content": "Okay, I'll set a goal for that. !goal('Do whatever I want forever. Follow whatever is most interesting. Be curious, creative, and open ended.')"} - ], - - [ - {"role": "user", "content": "zeeber: set a goal to build a castle"}, - {"role": "assistant", "content": "Sure thing. !goal('Build a castle with a moat, towers, and a drawbridge with a series of newactions. Include lighting, decorations, and furnishings. Use ladders. When satisfied, end the goal.')"} - ] - ], - - "coding_examples": [ - [ - {"role": "user", "content": "greg: Collect 10 wood"}, - {"role": "assistant", "content": "```await skills.collectBlock(bot, 'oak_log', 10);\n```"}, - {"role": "system", "content": "Successfully collected 9 wood."}, - {"role": "assistant", "content": "I collected 9 oak logs, what next?"} - ], - [ - {"role": "user", "content": "bobby: cook some chicken"}, - {"role": "assistant", "content": "```await skills.smeltItem(bot, 'chicken', 8);\n```"}, - {"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."}, - {"role": "assistant", "content": "I have cooked 8 chicken."} - ], - [ - {"role": "user", "content": "zZZn98: come here"}, - {"role": "assistant", "content": "```await skills.goToPlayer(bot, 'zZZn98');\n```"}, - {"role": "system", "content": "Code execution finished successfully."}, - {"role": "assistant", "content": "Here!"} - ], - [ - {"role": "user", "content": "maya: go to the nearest oak log"}, - {"role": "assistant", "content": "```await skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"}, - {"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."}, - {"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"}, - {"role": "system", "content": "Arrived at location."}, - {"role": "assistant", "content": "I found an oak log and I am now standing next to it. What next?"} - ], - [ - {"role": "user", "content": "234jeb: build a little tower"}, - {"role": "assistant", "content": "```let pos = bot.entity.position;\nfor (let i = 0; i < 5; i++) {\n await skills.placeBlock(bot, 'dirt', pos.x, pos.y + i, pos.z);\n}\n```"}, - {"role": "system", "content": "Successfully placed 5 dirt."}, - {"role": "assistant", "content": "I built a little tower!"} - ], - [ - {"role": "user", "content": "brug: build a dirt house"}, - {"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"} - ] - ] - + "model": "mistral/mistral-large-latest" } \ No newline at end of file diff --git a/settings.js b/settings.js index f5c1ddd..add5297 100644 --- a/settings.js +++ b/settings.js @@ -19,6 +19,7 @@ export default // "./profiles/qwen.json", // "./profiles/mistral.json", // "./profiles/grok.json", + // "./profiles/mistral.json", // "./profiles/deepseek.json", // using more than 1 profile requires you to /msg each bot indivually diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index 6ee481b..34e6693 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -406,7 +406,7 @@ export const actionsList = [ convoManager.endConversation(player_name); return `Converstaion with ${player_name} ended.`; } - } + }, // { // commented for now, causes confusion with goal command // name: '!npcGoal', // description: 'Set a simple goal for an item or building to automatically work towards. Do not use for complex goals.', @@ -420,16 +420,4 @@ export const actionsList = [ // return 'Set npc goal: ' + agent.npc.data.curr_goal.name; // } // }, - { - name: '!help', - description: 'Lists all available commands and their descriptions.', - perform: async function (agent) { - const commandList = actionsList.map(action => { - return `${action.name.padEnd(15)} - ${action.description}`; // Ensure consistent spacing - }).join('\n'); - - console.log(commandList); - return `Available Commands:\n${commandList}`; - } - }, ]; diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index b4dc74a..ad02af5 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -176,5 +176,17 @@ export const queryList = [ perform: async function (agent) { return "Saved place names: " + agent.memory_bank.getKeys(); } - } + }, + { + name: '!help', + description: 'Lists all available commands and their descriptions.', + perform: async function (agent) { + const commandList = actionsList.map(action => { + return `${action.name.padEnd(15)} - ${action.description}`; // Ensure consistent spacing + }).join('\n'); + + console.log(commandList); + return `Available Commands:\n${commandList}`; + } + }, ]; From 85ef0e8103a743d617b88fd4b7a47e71a72edc30 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 9 Jan 2025 12:43:29 -0600 Subject: [PATCH 11/14] fix help command --- src/agent/commands/queries.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index ad02af5..ef65b4d 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -1,5 +1,6 @@ import * as world from '../library/world.js'; import * as mc from '../../utils/mcdata.js'; +import { getCommandDocs } from './index.js'; import convoManager from '../conversation.js'; const pad = (str) => { @@ -181,12 +182,7 @@ export const queryList = [ name: '!help', description: 'Lists all available commands and their descriptions.', perform: async function (agent) { - const commandList = actionsList.map(action => { - return `${action.name.padEnd(15)} - ${action.description}`; // Ensure consistent spacing - }).join('\n'); - - console.log(commandList); - return `Available Commands:\n${commandList}`; + return getCommandDocs(); } }, ]; From 6167aeeec49486c3911dc23b312ceeb075c2ea0a Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 9 Jan 2025 15:15:25 -0600 Subject: [PATCH 12/14] added restrict to inventory and better blacklist --- example_tasks.json | 8 ++++++++ src/agent/agent.js | 5 +++-- src/agent/commands/index.js | 17 +++++++++++++---- src/agent/library/skills.js | 10 +++++++++- src/agent/modes.js | 3 +++ src/agent/prompter.js | 2 +- src/agent/tasks.js | 1 + 7 files changed, 38 insertions(+), 8 deletions(-) diff --git a/example_tasks.json b/example_tasks.json index b579233..ef81f9b 100644 --- a/example_tasks.json +++ b/example_tasks.json @@ -17,6 +17,14 @@ }, "type": "debug" }, + "debug_inventory_restriction": { + "goal": "Place 1 oak plank, then place 1 stone brick", + "initial_inventory": { + "oak_planks": 20 + }, + "type": "debug", + "restrict_to_inventory": true + }, "construction": { "type": "construction", "goal": "Build a house", diff --git a/src/agent/agent.js b/src/agent/agent.js index 9a35cf0..4691079 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -3,7 +3,7 @@ import { Coder } from './coder.js'; import { Prompter } from './prompter.js'; import { initModes } from './modes.js'; import { initBot } from '../utils/mcdata.js'; -import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js'; +import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction, blacklistCommands } from './commands/index.js'; import { ActionManager } from './action_manager.js'; import { NPCContoller } from './npc/controller.js'; import { MemoryBank } from './memory_bank.js'; @@ -47,7 +47,8 @@ export class Agent { await this.prompter.initExamples(); console.log('Initializing task...'); this.task = new Task(this, task_path, task_id); - this.blocked_actions = this.task.blocked_actions || []; + const blocked_actions = this.task.blocked_actions || []; + blacklistCommands(blocked_actions); serverProxy.connect(this); diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index a8d09db..3f3f967 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -14,6 +14,18 @@ export function getCommand(name) { return commandMap[name]; } +export function blacklistCommands(commands) { + const unblockable = ['!stop', '!stats', '!goal', '!endGoal', '!endConversation']; + for (let command_name of commands) { + if (unblockable.includes(command_name)){ + console.warn(`Command ${command_name} is unblockable`); + continue; + } + delete commandMap[command_name]; + delete commandList.find(command => command.name === command_name); + } +} + const commandRegex = /!(\w+)(?:\(((?:-?\d+(?:\.\d+)?|true|false|"[^"]*")(?:\s*,\s*(?:-?\d+(?:\.\d+)?|true|false|"[^"]*"))*)\))?/ const argRegex = /-?\d+(?:\.\d+)?|true|false|"[^"]*"/g; @@ -214,7 +226,7 @@ export async function executeCommand(agent, message) { } } -export function getCommandDocs(blacklist=null) { +export function getCommandDocs() { const typeTranslations = { //This was added to keep the prompt the same as before type checks were implemented. //If the language model is giving invalid inputs changing this might help. @@ -228,9 +240,6 @@ export function getCommandDocs(blacklist=null) { Use the commands with the syntax: !commandName or !commandName("arg1", 1.2, ...) if the command takes arguments.\n Do not use codeblocks. Use double quotes for strings. Only use one command in each response, trailing commands and comments will be ignored.\n`; for (let command of commandList) { - if (blacklist && blacklist.includes(command.name)) { - continue; - } docs += command.name + ': ' + command.description + '\n'; if (command.params) { docs += 'Params:\n'; diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index c0d448f..be5882f 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -558,6 +558,14 @@ export async function placeBlock(bot, blockType, x, y, z, placeOn='bottom', dont const target_dest = new Vec3(Math.floor(x), Math.floor(y), Math.floor(z)); if (bot.modes.isOn('cheat') && !dontCheat) { + if (bot.restrict_to_inventory) { + let block = bot.inventory.items().find(item => item.name === blockType); + if (!block) { + log(bot, `Cannot place ${blockType}, you are restricted to your current inventory.`); + return false; + } + } + // invert the facing direction let face = placeOn === 'north' ? 'south' : placeOn === 'south' ? 'north' : placeOn === 'east' ? 'west' : 'east'; if (blockType.includes('torch') && placeOn !== 'bottom') { @@ -599,7 +607,7 @@ export async function placeBlock(bot, blockType, x, y, z, placeOn='bottom', dont if (item_name == "redstone_wire") item_name = "redstone"; let block = bot.inventory.items().find(item => item.name === item_name); - if (!block && bot.game.gameMode === 'creative') { + if (!block && bot.game.gameMode === 'creative' && !bot.restrict_to_inventory) { await bot.creative.setInventorySlot(36, mc.makeItem(item_name, 1)); // 36 is first hotbar slot block = bot.inventory.items().find(item => item.name === item_name); } diff --git a/src/agent/modes.js b/src/agent/modes.js index d9ec75f..8bf1594 100644 --- a/src/agent/modes.js +++ b/src/agent/modes.js @@ -404,6 +404,9 @@ export function initModes(agent) { _agent = agent; // the mode controller is added to the bot object so it is accessible from anywhere the bot is used agent.bot.modes = new ModeController(); + if (agent.task) { + agent.bot.restrict_to_inventory = agent.task.restrict_to_inventory; + } let modes_json = agent.prompter.getInitModes(); if (modes_json) { agent.bot.modes.loadJson(modes_json); diff --git a/src/agent/prompter.js b/src/agent/prompter.js index 8feda0c..bc05860 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -186,7 +186,7 @@ export class Prompter { prompt = prompt.replaceAll('$ACTION', this.agent.actions.currentActionLabel); } if (prompt.includes('$COMMAND_DOCS')) - prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs(this.agent.blocked_actions)); + prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs()); if (prompt.includes('$CODE_DOCS')) prompt = prompt.replaceAll('$CODE_DOCS', getSkillDocs()); if (prompt.includes('$EXAMPLES') && examples !== null) diff --git a/src/agent/tasks.js b/src/agent/tasks.js index f7527f1..6d968a9 100644 --- a/src/agent/tasks.js +++ b/src/agent/tasks.js @@ -51,6 +51,7 @@ export class Task { this.taskStartTime = Date.now(); this.validator = new TaskValidator(this.data, this.agent); this.blocked_actions = this.data.blocked_actions || []; + this.restrict_to_inventory = !!this.data.restrict_to_inventory; if (this.data.goal) this.blocked_actions.push('!endGoal'); if (this.data.conversation) From 7eef80539519208b9b6fc3af57a89ce6439106aa Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Thu, 9 Jan 2025 15:16:35 -0600 Subject: [PATCH 13/14] added num_examples --- settings.js | 3 ++- src/agent/prompter.js | 5 +++-- src/utils/examples.js | 6 ++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/settings.js b/settings.js index add5297..a4681fa 100644 --- a/settings.js +++ b/settings.js @@ -35,7 +35,8 @@ export default "code_timeout_mins": 10, // minutes code is allowed to run. -1 for no timeout "max_messages": 15, // max number of messages to keep in context - "max_commands": -1, // max number of commands to use in a response. -1 for no limit + "num_examples": 2, // number of examples to give to the model + "max_commands": -1, // max number of commands that can be used in consecutive responses. -1 for no limit "verbose_commands": true, // show full command syntax "narrate_behavior": true, // chat simple automatic actions ('Picking up item!') "chat_bot_messages": true, // publicly chat messages to other bots diff --git a/src/agent/prompter.js b/src/agent/prompter.js index bc05860..310ca3e 100644 --- a/src/agent/prompter.js +++ b/src/agent/prompter.js @@ -4,6 +4,7 @@ import { getCommandDocs } from './commands/index.js'; import { getSkillDocs } from './library/index.js'; import { stringifyTurns } from '../utils/text.js'; import { getCommand } from './commands/index.js'; +import settings from '../../settings.js'; import { Gemini } from '../models/gemini.js'; import { GPT } from '../models/gpt.js'; @@ -155,8 +156,8 @@ export class Prompter { async initExamples() { try { - this.convo_examples = new Examples(this.embedding_model); - this.coding_examples = new Examples(this.embedding_model); + this.convo_examples = new Examples(this.embedding_model, settings.num_examples); + this.coding_examples = new Examples(this.embedding_model, settings.num_examples); // Wait for both examples to load before proceeding await Promise.all([ diff --git a/src/utils/examples.js b/src/utils/examples.js index 31ef3ab..ca6de79 100644 --- a/src/utils/examples.js +++ b/src/utils/examples.js @@ -33,6 +33,9 @@ export class Examples { this.examples = examples; if (!this.model) return; // Early return if no embedding model + if (this.select_num === 0) + return; + try { // Create array of promises first const embeddingPromises = examples.map(example => { @@ -52,6 +55,9 @@ export class Examples { } async getRelevant(turns) { + if (this.select_num === 0) + return []; + let turn_text = this.turnsToText(turns); if (this.model !== null) { let embedding = await this.model.embed(turn_text); From eeaf33c4d290a4ffeab5d59e8a671a55bb6ba0f4 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Fri, 10 Jan 2025 12:41:00 -0600 Subject: [PATCH 14/14] changed unblockables --- src/agent/commands/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/agent/commands/index.js b/src/agent/commands/index.js index 3f3f967..008c1d0 100644 --- a/src/agent/commands/index.js +++ b/src/agent/commands/index.js @@ -15,7 +15,7 @@ export function getCommand(name) { } export function blacklistCommands(commands) { - const unblockable = ['!stop', '!stats', '!goal', '!endGoal', '!endConversation']; + const unblockable = ['!stop', '!stats', '!inventory', '!goal']; for (let command_name of commands) { if (unblockable.includes(command_name)){ console.warn(`Command ${command_name} is unblockable`);