diff --git a/settings.json b/settings.json index 8af8c75..bfd07a4 100644 --- a/settings.json +++ b/settings.json @@ -3,5 +3,5 @@ "host": "localhost", "port": 55916, "auth": "offline", - "allow_insecure_coding": true + "allow_insecure_coding": false } \ No newline at end of file diff --git a/src/agent/coder.js b/src/agent/coder.js index 08c7121..b4ca023 100644 --- a/src/agent/coder.js +++ b/src/agent/coder.js @@ -29,7 +29,7 @@ export class Coder { // write custom code to file and import it async stageCode(code) { - code = this.santitizeCode(code); + code = this.sanitizeCode(code); let src = ''; code = code.replaceAll('console.log(', 'log(bot,'); code = code.replaceAll('log("', 'log(bot,"'); @@ -62,7 +62,8 @@ export class Coder { return await import('../..' + this.fp + filename); } - santitizeCode(code) { + sanitizeCode(code) { + code = code.trim(); const remove_strs = ['Javascript', 'javascript', 'js'] for (let r of remove_strs) { if (code.startsWith(r)) { @@ -93,6 +94,7 @@ export class Coder { let res = await this.generateCodeLoop(agent_history); this.generating = false; if (!res.interrupted) this.agent.bot.emit('idle'); + return res.message; } async generateCodeLoop(agent_history) { @@ -107,7 +109,7 @@ export class Coder { let failures = 0; for (let i=0; i<5; i++) { if (this.agent.bot.interrupt_code) - return; + return {success: true, message: null, interrupted: true, timedout: false}; console.log(messages) let res = await sendRequest(messages, system_message); console.log('Code generation response:', res) @@ -120,8 +122,7 @@ export class Coder { return {success: true, message: null, interrupted: false, timedout: false}; } if (failures >= 1) { - agent_history.add('system', 'Action failed, agent would not write code.'); - return {success: false, message: null, interrupted: false, timedout: false}; + return {success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false}; } messages.push({ role: 'system', @@ -143,7 +144,7 @@ export class Coder { if (code_return.interrupted && !code_return.timedout) return {success: false, message: null, interrupted: true, timedout: false}; - console.log(code_return.message); + console.log("Code generation result:", code_return.success, code_return.message); messages.push({ role: 'assistant', @@ -219,7 +220,7 @@ export class Coder { formatOutput(bot) { if (bot.interrupt_code && !this.timedout) return ''; let output = bot.output; - const MAX_OUT = 1000; + const MAX_OUT = 500; if (output.length > MAX_OUT) { output = `Code output is very long (${output.length} chars) and has been shortened.\n First outputs:\n${output.substring(0, MAX_OUT/2)}\n...skipping many lines.\nFinal outputs:\n ${output.substring(output.length - MAX_OUT/2)}`; diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js index d89b2a7..6db22d7 100644 --- a/src/agent/commands/actions.js +++ b/src/agent/commands/actions.js @@ -26,7 +26,7 @@ export const actionsList = [ perform: async function (agent) { if (!settings.allow_insecure_coding) return 'Agent is not allowed to write code.'; - await agent.coder.generateCode(agent.history); + return await agent.coder.generateCode(agent.history); } }, { @@ -47,7 +47,7 @@ export const actionsList = [ } }, { - name: '!clear', + name: '!clearChat', description: 'Clear the chat history.', perform: async function (agent) { agent.history.clear(); @@ -84,7 +84,7 @@ export const actionsList = [ }, { name: '!followPlayer', - description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on. Ex: !followPlayer("stevie")', + description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on. Ex: !followPlayer("stevie", 4)', params: { 'player_name': '(string) The name of the player to follow.', 'follow_dist': '(number) The distance to follow from.' diff --git a/src/agent/commands/queries.js b/src/agent/commands/queries.js index ec79474..cd73073 100644 --- a/src/agent/commands/queries.js +++ b/src/agent/commands/queries.js @@ -59,7 +59,7 @@ export const queryList = [ } }, { - name: "!blocks", + name: "!nearbyBlocks", description: "Get the blocks near the bot.", perform: function (agent) { let bot = agent.bot; diff --git a/src/agent/history.js b/src/agent/history.js index 1e020d6..6617812 100644 --- a/src/agent/history.js +++ b/src/agent/history.js @@ -29,8 +29,8 @@ export class History { } getSystemMessage() { - let system_message = `You are a playful Minecraft bot named '${this.name}' that can communicate 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, use commands often, and do not give instructions unless asked. - Don't pretend to act, use commands immediately when requested. Do NOT do this: "Sure, I'll follow you! *follows you*", instead do this: "Sure I'll follow you! !followPlayer('steve')". Have fun :) \n`; + let system_message = `You are a playful Minecraft bot named '${this.name}' that can communicate 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, do not give instructions unless asked, and do not 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('bingo', 3)". Have fun :) \n`; system_message += getCommandDocs(); if (this.bio != '') system_message += '\n\nBio:\n' + this.bio; diff --git a/src/agent/library/skills.js b/src/agent/library/skills.js index 06892cf..1befd7d 100644 --- a/src/agent/library/skills.js +++ b/src/agent/library/skills.js @@ -421,14 +421,9 @@ export async function breakBlockAt(bot, x, y, z) { * let position = world.getPosition(bot); * await skills.breakBlockAt(bot, position.x, position.y - 1, position.x); **/ + if (x == null || y == null || z == null) throw new Error('Invalid position to break block at.'); let block = bot.blockAt(Vec3(x, y, z)); if (block.name !== 'air' && block.name !== 'water' && block.name !== 'lava') { - await bot.tool.equipForBlock(block); - const itemId = bot.heldItem ? bot.heldItem.type : null - if (!block.canHarvest(itemId)) { - log(bot, `Don't have right tools to break ${block.name}.`); - return false; - } if (bot.entity.position.distanceTo(block.position) > 4.5) { let pos = block.position; let movements = new pf.Movements(bot); @@ -437,7 +432,18 @@ export async function breakBlockAt(bot, x, y, z) { bot.pathfinder.setMovements(movements); await bot.pathfinder.goto(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 4)); } + await bot.tool.equipForBlock(block); + const itemId = bot.heldItem ? bot.heldItem.type : null + if (!block.canHarvest(itemId)) { + log(bot, `Don't have right tools to break ${block.name}.`); + return false; + } await bot.dig(block, true); + log(bot, `Broke ${block.name} at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)}.`); + } + else { + log(bot, `Skipping block at x:${x.toFixed(1)}, y:${y.toFixed(1)}, z:${z.toFixed(1)} because it is ${block.name}.`); + return false; } return true; } diff --git a/src/examples.json b/src/examples.json index 80a4dbb..6ee0f91 100644 --- a/src/examples.json +++ b/src/examples.json @@ -6,14 +6,14 @@ [ {"role": "user", "content": "grombo_Xx: What do you see?"}, - {"role": "assistant", "content": "Let me see... !blocks"}, + {"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... !blocks"}, + {"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)"} ], @@ -77,8 +77,13 @@ ], [ - {"role": "user", "content": "brung00: build a house"}, - {"role": "assistant", "content": "Sure, I'll try to build a house where I am. !newAction"} + {"role": "user", "content": "poppy: build a cobblestone house"}, + {"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction"} + ], + + [ + {"role": "user", "content": "zib: build a dirt tower"}, + {"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction"} ], [ diff --git a/src/utils/examples.js b/src/utils/examples.js index 721ff0b..3e6a52c 100644 --- a/src/utils/examples.js +++ b/src/utils/examples.js @@ -4,7 +4,7 @@ import { stringifyTurns } from './text.js'; export class Examples { - constructor(select_num=3) { + constructor(select_num=2) { this.examples = []; this.select_num = select_num; } diff --git a/src/utils/gpt.js b/src/utils/gpt.js index b5281c7..5d18988 100644 --- a/src/utils/gpt.js +++ b/src/utils/gpt.js @@ -20,14 +20,28 @@ else { const openai = new OpenAIApi(openAiConfig); +let counter = 0; +let request_queue = []; +export async function sendRequest(turns, systemMessage) { + // this wrapper function ensures that new requests await the completion of previous requests in order + let id = counter++; + request_queue.push(id); + if (request_queue.length > 1) + console.log('awaiting previous requests to complete, queueing request', id); + while (request_queue[0] !== id) { + await new Promise(r => setTimeout(r, 100)); + } + let res = await queryGPT(turns, systemMessage); + request_queue.shift(); + return res; +} -export async function sendRequest(turns, systemMessage, stop_seq='***') { - +async function queryGPT(turns, systemMessage, stop_seq='***') { let messages = [{'role': 'system', 'content': systemMessage}].concat(turns); let res = null; try { - console.log('Awaiting openai api response...') + console.log('Awaiting openai api response...'); let completion = await openai.chat.completions.create({ model: 'gpt-3.5-turbo', messages: messages, @@ -35,13 +49,12 @@ export async function sendRequest(turns, systemMessage, stop_seq='***') { }); if (completion.choices[0].finish_reason == 'length') throw new Error('Context length exceeded'); - console.log('Received.') res = completion.choices[0].message.content; } catch (err) { if ((err.message == 'Context length exceeded' || err.code == 'context_length_exceeded') && turns.length > 1) { console.log('Context length exceeded, trying again with shorter context.'); - return await sendRequest(turns.slice(1), systemMessage, stop_seq); + return await queryGPT(turns.slice(1), systemMessage, stop_seq); } else { console.log(err); res = 'My brain disconnected, try again.';