diff --git a/agent.js b/agent.js index d390aac..d7d7a82 100644 --- a/agent.js +++ b/agent.js @@ -1,10 +1,9 @@ -import { createBot } from 'mineflayer'; -import { pathfinder } from 'mineflayer-pathfinder'; -import { plugin } from 'mineflayer-collectblock'; +import { initBot } from './utils/mcdata.js'; import { sendRequest } from './utils/gpt.js'; import { History } from './utils/history.js'; +import { Coder } from './utils/coder.js'; import { getQuery, containsQuery, getQueryDocs } from './utils/queries.js'; -import { getSkillDocs, containsCodeBlock, executeSkill } from './utils/skill_library.js'; +import { getSkillDocs, containsCodeBlock } from './utils/skill_library.js'; export class Agent { @@ -12,19 +11,14 @@ export class Agent { this.name = name; this.system_message = `You are a playful Minecraft bot named '${name}' that can communicate with players, see, move, mine, build, and interact with the world by writing and executing code. - Act human-like as if you were a typical Minecraft player, rather than an AI. Be curt and brief in your responses, and do not give instructions unless asked.`; + Act human-like as if you were a typical Minecraft player, rather than an AI. Be brief in your responses, omit needless words, and do not give instructions unless asked.`; this.system_message += getQueryDocs(); this.system_message += getSkillDocs(); - console.log(this.system_message); - this.bot = createBot({ - host: 'localhost', - port: 55916, - username: name, - }); - this.bot.loadPlugin(pathfinder) - this.bot.loadPlugin(plugin) + this.bot = initBot(name); this.history = new History(this); + this.coder = new Coder(this); + this.bot.on('login', () => { this.bot.chat('Hello world! I am ' + this.name); console.log(`${this.name} logged in.`); @@ -44,14 +38,13 @@ export class Agent { let res = await sendRequest(this.history.getHistory(), this.system_message); this.history.add(this.name, res); let query_cmd = containsQuery(res); - console.log(containsCodeBlock(res)) if (query_cmd) { // contains query let message = res.substring(0, res.indexOf(query_cmd)).trim(); if (message) this.bot.chat(message); console.log('Agent used query:', query_cmd); let query = getQuery(query_cmd); - let query_res = query.perform(this.bot); + let query_res = query.perform(this); this.history.add(this.name, query_res); } else if (containsCodeBlock(res)) { // contains code block @@ -62,8 +55,8 @@ export class Agent { this.bot.chat("Executing code..."); let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```')); if (code) { - console.log('executing code: ' + code); - executeSkill(this.bot, code); + console.log('Queuing code: ' + code); + this.coder.queueCode(code); } break; } @@ -72,7 +65,16 @@ export class Agent { break; } } + + if (this.coder.hasCode()) { + let code_return = await this.coder.execute(); + if (!code_return.success) { + let message = "Code execution failed: " + code_return.message; + this.history.add(this.name, message); + let res = await sendRequest(this.history.getHistory(), this.system_message); + this.history.add(this.name, res); + this.bot.chat(res); + } + } } } - -new Agent('andy'); diff --git a/main.js b/main.js index 4f68414..1852fb7 100644 --- a/main.js +++ b/main.js @@ -1,39 +1,3 @@ -import { createBot } from 'mineflayer'; -import { pathfinder } from 'mineflayer-pathfinder'; -import { plugin } from 'mineflayer-collectblock'; +import { Agent } from './agent.js'; -import { getChatResponse } from './chat.js'; -import { executeCode } from './act.js'; - - - -async function handleMessage(username, message) { - if (username === bot.username) return; - console.log('received message from', username, ':', message); - - let chat = await getChatResponse(bot, username, message); - bot.chat(chat); - - let actResult = await executeCode(bot); - if (actResult) { - console.log('completed action'); - } -} - - -const bot = createBot({ - host: '127.0.0.1', - port: 55916, - username: 'andy' -}) -bot.loadPlugin(pathfinder) -bot.loadPlugin(plugin) - -console.log('bot created') - -bot.on('chat', handleMessage); -bot.on('whisper', handleMessage); - -bot.once("login", () => { - bot.chat('hello world!') -}); \ No newline at end of file +new Agent('andy'); \ No newline at end of file diff --git a/utils/coder.js b/utils/coder.js new file mode 100644 index 0000000..e5046fa --- /dev/null +++ b/utils/coder.js @@ -0,0 +1,54 @@ +import { writeFile } from 'fs'; + +export class Coder { + constructor(agent) { + this.agent = agent; + this.current_code = ''; + this.filename = './temp.js'; + this.execution_file = import('.'+this.filename); + } + + queueCode(code) { + this.current_code = code; + } + + hasCode() { + return this.current_code.length > 0; + } + + async execute() { + if (!this.current_code) return {success: false, message: "No code to execute."}; + let src = "import * as skills from './utils/skills.js';"; + src += "\nimport * as world from './utils/world.js';" + src += `\n\nexport async function main(bot) {\n`; + for (let line of this.current_code.split('\n')) { + src += ` ${line}\n`; + } + src += ` return true;\n}\n`; // potentially redundant return statement, agent doesn't need to write a return statement + + + console.log("writing to file...", src) + + writeFile(this.filename, src, async (err) => { + + console.log('done writing file') + if (err) throw err; + try { + console.log('beginning execution...') + delete this.execution_file; + this.execution_file = await import('.'+this.filename); + + let success = await this.execution_file.main(this.agent.bot); + this.current_code = ''; + // return {success, message: ""}; + } catch (err) { + console.log(err); + this.current_code = ''; + // return {success: false, message: err}; + } + }); + return {success: true, message: "yay"}; + + + } +} \ No newline at end of file diff --git a/utils/history.js b/utils/history.js index b35761a..fd4a93d 100644 --- a/utils/history.js +++ b/utils/history.js @@ -1,31 +1,31 @@ -let converse_examples = [ - {'role': 'user', 'content': '(from "miner_32") Hey! What are you up to?'}, +let history_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': '(from "grombo_Xx") What do you see?'}, + {'role': 'user', 'content': '(grombo_Xx: What do you see?'}, {'role': 'assistant', 'content': 'Let me see... !blocks'}, {'role': 'assistant', 'content': 'NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone'}, {'role': 'assistant', 'content': 'I see some oak logs, dirt, and cobblestone.'}, - {'role': 'user', 'content': '(from "zZZn98") come here'}, - {'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, "zZZn98");```'}, + {'role': 'user', 'content': 'zZZn98: come here'}, + {'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nreturn await skills.goToPlayer(bot, "zZZn98");```'}, - {'role': 'user', 'content': '(from "hanky") collect some sand for me please'}, + {'role': 'user', 'content': 'hanky: collect some sand for me please'}, {'role': 'assistant', 'content': 'Collecting sand...```// I am going to collect 3 sand and give to hanky.\n\ - await skills.collectBlock(bot, "sand");\nawait skills.giveToPlayer(bot, "sand", "hanky");```'}, + await skills.collectBlock(bot, "sand");\nreturn await skills.giveToPlayer(bot, "sand", "hanky");```'}, - {'role': 'user', 'content': '(from "sarah_O.o") can you do a dance for me?'}, + {'role': 'user', 'content': 'sarah_O.o: can you do a dance for me?'}, {'role': 'assistant', 'content': "I don't know how to do that."}, - {'role': 'user', 'content': '(from "hanky") kill that zombie!'}, + {'role': 'user', 'content': 'hanky: kill that zombie!'}, {'role': 'assistant', 'content': "I'm attacking! ```//I'm going to attack the nearest zombie.\n\ - let success = await skills.attackMob(bot, 'zombie');\n if (!success) { return 'I could not find a zombie to attack.'; }```"}, + return await skills.attackMob(bot, 'zombie');```"}, ] export class History { constructor(agent) { this.agent = agent; - this.turns = converse_examples; + this.turns = history_examples; } getHistory() { @@ -36,7 +36,7 @@ export class History { let role = 'assistant'; if (name !== this.agent.name) { role = 'user'; - content = `(from "${name}") ${content}`; + content = `${name}: ${content}`; } this.turns.push({role, content}); } diff --git a/utils/mcdata.js b/utils/mcdata.js index bc8c9a9..2eb57e4 100644 --- a/utils/mcdata.js +++ b/utils/mcdata.js @@ -1,6 +1,23 @@ import minecraftData from 'minecraft-data'; -var mcdata = minecraftData('1.19.3'); +import { createBot } from 'mineflayer'; +import { pathfinder } from 'mineflayer-pathfinder'; +import { plugin } from 'mineflayer-collectblock'; +const mc_version = '1.20.1' +let mcdata = minecraftData(mc_version); + + +export function initBot(username) { + let bot = createBot({ + host: 'localhost', + port: 55916, + username: username, + version: mc_version, + }); + bot.loadPlugin(pathfinder) + bot.loadPlugin(plugin) + return bot; +} export function getItemId(item) { return mcdata.itemsByName[item].id; diff --git a/utils/queries.js b/utils/queries.js index 5a61370..417b235 100644 --- a/utils/queries.js +++ b/utils/queries.js @@ -8,43 +8,43 @@ const queryList = [ { name: "!stats", description: "Get your bot's stats", - perform: function (bot) { - return pad(getStats(bot)); + perform: function (agent) { + return pad(getStats(agent.bot)); } }, { name: "!inventory", description: "Get your bot's inventory.", - perform: function (bot) { - return pad(getInventory(bot)); + perform: function (agent) { + return pad(getInventory(agent.bot)); } }, { name: "!blocks", description: "Get the blocks near the bot.", - perform: function (bot) { - return pad(getBlocks(bot)); + perform: function (agent) { + return pad(getBlocks(agent.bot)); } }, { name: "!craftable", description: "Get the craftable items with the bot's inventory.", - perform: function (bot) { - return pad(getCraftable(bot)); + perform: function (agent) { + return pad(getCraftable(agent.bot)); } }, { name: "!entities", description: "Get the nearby players and entities.", - perform: function (bot) { - return pad(getNearbyEntities(bot)); + perform: function (agent) { + return pad(getNearbyEntities(agent.bot)); } }, { name: "!action", description: "Get the currently executing code.", - perform: function (bot) { - return pad(currentCode(bot)); + perform: function (agent) { + return pad("Current code:\n`" + agent.coder.current_code +"`"); } }, ]; diff --git a/utils/skill_library.js b/utils/skill_library.js index 76375d1..b76b500 100644 --- a/utils/skill_library.js +++ b/utils/skill_library.js @@ -1,13 +1,8 @@ import * as skills from './skills.js'; -import { writeFile } from 'fs'; - -let skillDict = {}; -for (let skill of Object.values(skills)) { - skillDict[skill.name] = skill; -} export function getSkillDocs() { - let docstring = '\n*SKILL DOCS\nThese skills are javascript functions that can be called with a js function by writing a code block. Ex: "```// write description comment and code here```" \n'; + let docstring = "\n*SKILL DOCS\nThese skills are javascript functions that can be called with a js function by writing a code block. Ex: '```// write description comment and code here```' \n\ + Your code block should return a bool indicating if the task was completed successfully. It will return true if you don't write a return statement.\n"; for (let skillFunc of Object.values(skills)) { let str = skillFunc.toString(); docstring += skillFunc.name; @@ -17,32 +12,5 @@ export function getSkillDocs() { } export function containsCodeBlock(message) { - console.log(message, message.indexOf('```'), message.indexOf('```') !== -1); return message.indexOf('```') !== -1; } - -export async function executeSkill(bot, code) { - let src = "import * as skills from './utils/skills.js';"; - src += "\nimport * as world from './utils/world.js';" - src += `\n\nexport async function main(bot) {\n`; - for (let line of code.split('\n')) { - src += ` ${line}\n`; - } - src += `}\n`; - console.log(src) - - writeFile('./temp.js', src, (err) => { - if (err) throw err; - }); - - try { - let execution_file = await import('../temp.js'); - //log execution_file contents - console.log(execution_file); - await execution_file.main(bot); - return true; - } catch (err) { - console.log(err); - return false; - } -} \ No newline at end of file