From 7d35ef1bfc0561baab2ba04ebd9bc0dfcb373425 Mon Sep 17 00:00:00 2001 From: MaxRobinsonTheGreat Date: Tue, 9 Jan 2024 22:50:22 -0600 Subject: [PATCH] combine queries+commands, allow user commands --- src/agent/agent.js | 26 +++++--- src/agent/commands.js | 99 ++--------------------------- src/agent/commands/actions.js | 94 +++++++++++++++++++++++++++ src/agent/{ => commands}/queries.js | 38 ++--------- src/agent/history.js | 2 - 5 files changed, 122 insertions(+), 137 deletions(-) create mode 100644 src/agent/commands/actions.js rename src/agent/{ => commands}/queries.js (78%) diff --git a/src/agent/agent.js b/src/agent/agent.js index 4dc2992..3a438de 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -2,7 +2,6 @@ import { initBot } from '../utils/mcdata.js'; import { sendRequest } from '../utils/gpt.js'; import { History } from './history.js'; import { Coder } from './coder.js'; -import { getQuery, containsQuery } from './queries.js'; import { getCommand, containsCommand } from './commands.js'; import { Events } from './events.js'; @@ -54,25 +53,32 @@ export class Agent { if (!!source && !!message) await this.history.add(source, message); + const user_command_name = containsCommand(message); + if (user_command_name) { + this.bot.chat(`*${source} used ${user_command_name.substring(1)}*`); + let execute_res = await getCommand(user_command_name).perform(this); + if (execute_res) + this.bot.chat(execute_res); + else + this.bot.chat('Finished command.'); + return; + } + for (let i=0; i<5; i++) { let res = await sendRequest(this.history.getHistory(), this.history.getSystemMessage()); this.history.add(this.name, res); - let query_name = containsQuery(res); let command_name = containsCommand(res); - if (query_name || command_name) { // contains query or command + if (command_name) { // contains query or command console.log('Query/Command response:', res); - let execute_name = query_name ? query_name : command_name; - let message = res.substring(0, res.indexOf(execute_name)).trim(); - if (message) - this.bot.chat(message); + let message = res.substring(0, res.indexOf(command_name)).trim(); - let execute_func = query_name ? getQuery(query_name) : getCommand(command_name); - let execute_res = await execute_func.perform(this); + this.bot.chat(`${message} *used ${command_name.substring(1)}*`); + let execute_res = await getCommand(command_name).perform(this); - console.log('Agent executed:', execute_name, 'and got:', execute_res); + console.log('Agent executed:', command_name, 'and got:', execute_res); if (execute_res) this.history.add('system', execute_res); diff --git a/src/agent/commands.js b/src/agent/commands.js index c6e45e0..8015fae 100644 --- a/src/agent/commands.js +++ b/src/agent/commands.js @@ -1,97 +1,8 @@ -import * as skills from './skills.js'; -import * as world from './world.js'; +import { actionsList } from './commands/actions.js'; +import { queryList } from './commands/queries.js'; -function wrapExecution(func) { - return async function (agent) { - agent.bot.output = ''; - agent.coder.executing = true; - let res = await func(agent); - if (res) - agent.bot.output += '\n' + res; - agent.coder.executing = false; - return '\n' + agent.bot.output + '\n'; - } -} - -const commandList = [ - { - name: '!execute_action', - description: 'Write and execute code to perform custom behaviors not available as a command.', - perform: async function (agent) { - let res = await agent.coder.generateCode(agent.history); - if (res) - return '\n' + res + '\n'; - } - }, - { - name: '!abort', - description: 'Force stop all actions and commands that are currently executing.', - perform: async function (agent) { - await agent.coder.stop(); - } - }, - // { - // name: '!gotoplayer', - // description: 'Go to the nearest player.', - // perform: wrapExecution(async (agent) => { - // let player_name = world.getNearbyPlayerNames(agent.bot); - // if (player_name.length == 0) - // return 'No players nearby.'; - // await skills.goToPlayer(agent.bot, player_name[0]); - // }) - // }, - // { - // name: '!followplayer', - // description: 'Follow the nearest player.', - // perform: wrapExecution(async (agent) => { - // let player_name = world.getNearbyPlayerNames(agent.bot); - // if (player_name.length == 0) - // return 'No players nearby.'; - // await skills.followPlayer(agent.bot, player_name[0]); - // }) - // }, - // { - // name: '!collectwood', - // description: 'Collect 3 wood logs of any type.', - // perform: wrapExecution(async (agent) => { - // let blocks = world.getNearbyBlockTypes(agent.bot); - // for (let block of blocks) { - // if (block.includes('log')) { - // await skills.collectBlock(agent.bot, block, 3); - // return; - // } - // } - // return 'No wood nearby.'; - // }) - // }, - // { - // name: '!collectstone', - // description: 'Collect 3 cobblestone blocks.', - // perform: wrapExecution(async (agent) => { - // let inventory = world.getInventoryCounts(agent.bot); - // for (const item in inventory) { - // if (inventory[item] && inventory[item] > 0 && item.includes('pickaxe')) { - // if (await skills.equip(agent.bot, 'pickaxe')) - // await skills.collectBlock(agent.bot, 'stone', 3); - // return; - // } - // } - // return 'No pickaxe in inventory.'; - // }) - // }, - // { - // name: '!fightmob', - // description: 'Fight the nearest mob.', - // perform: wrapExecution(async (agent) => { - // let mobs = world.getNearbyMobTypes(agent.bot); - // if (mobs.length == 0) - // return 'No mobs nearby.'; - // await skills.attackMob(agent.bot, mobs[0], true); - // }) - // } -]; - +const commandList = queryList.concat(actionsList); const commandMap = {}; for (let command of commandList) { commandMap[command.name] = command; @@ -111,7 +22,9 @@ export function containsCommand(message) { } export function getCommandDocs() { - let docs = `\n*COMMAND DOCS\n You can use the following commands to execute actions in the world. Use the command name in your response and the results of the command will be included in the next input. Do not use commands not listed below. If trying to perform an action outside of the scope the listed commands, use the !custom command to write custom code.\n`; + let docs = `\n*Command DOCS\n You can use the following commands to perform actions and get information about the world. + Use the commands with the syntax: !commandName \n + Don't use codeblocks. Only use one command in each response, trailing commands will be ignored. Use these commands frequently in your responses!\n`; for (let command of commandList) { docs += command.name + ': ' + command.description + '\n'; } diff --git a/src/agent/commands/actions.js b/src/agent/commands/actions.js new file mode 100644 index 0000000..1ff0419 --- /dev/null +++ b/src/agent/commands/actions.js @@ -0,0 +1,94 @@ +import * as skills from '../skills.js'; +import * as world from '../world.js'; + +function wrapExecution(func) { + return async function (agent) { + agent.bot.output = ''; + agent.coder.executing = true; + let res = await func(agent); + if (res) + agent.bot.output += '\n' + res; + agent.coder.executing = false; + return '\n' + agent.bot.output + '\n'; + } +} + +// const actionsList = [ +export const actionsList = [ + { + name: '!execute_action', + description: 'Write and execute code to perform custom behaviors not available as a command.', + perform: async function (agent) { + let res = await agent.coder.generateCode(agent.history); + if (res) + return '\n' + res + '\n'; + } + }, + { + name: '!stop', + description: 'Force stop all actions and commands that are currently executing.', + perform: async function (agent) { + await agent.coder.stop(); + return 'Agent stopped.'; + } + }, + // { + // name: '!gotoplayer', + // description: 'Go to the nearest player.', + // perform: wrapExecution(async (agent) => { + // let player_name = world.getNearbyPlayerNames(agent.bot); + // if (player_name.length == 0) + // return 'No players nearby.'; + // await skills.goToPlayer(agent.bot, player_name[0]); + // }) + // }, + // { + // name: '!followplayer', + // description: 'Follow the nearest player.', + // perform: wrapExecution(async (agent) => { + // let player_name = world.getNearbyPlayerNames(agent.bot); + // if (player_name.length == 0) + // return 'No players nearby.'; + // await skills.followPlayer(agent.bot, player_name[0]); + // }) + // }, + // { + // name: '!collectwood', + // description: 'Collect 3 wood logs of any type.', + // perform: wrapExecution(async (agent) => { + // let blocks = world.getNearbyBlockTypes(agent.bot); + // for (let block of blocks) { + // if (block.includes('log')) { + // await skills.collectBlock(agent.bot, block, 3); + // return; + // } + // } + // return 'No wood nearby.'; + // }) + // }, + // { + // name: '!collectstone', + // description: 'Collect 3 cobblestone blocks.', + // perform: wrapExecution(async (agent) => { + // let inventory = world.getInventoryCounts(agent.bot); + // for (const item in inventory) { + // if (inventory[item] && inventory[item] > 0 && item.includes('pickaxe')) { + // if (await skills.equip(agent.bot, 'pickaxe')) + // await skills.collectBlock(agent.bot, 'stone', 3); + // return; + // } + // } + // return 'No pickaxe in inventory.'; + // }) + // }, + // { + // name: '!fightmob', + // description: 'Fight the nearest mob.', + // perform: wrapExecution(async (agent) => { + // let mobs = world.getNearbyMobTypes(agent.bot); + // if (mobs.length == 0) + // return 'No mobs nearby.'; + // await skills.attackMob(agent.bot, mobs[0], true); + // }) + // } +]; diff --git a/src/agent/queries.js b/src/agent/commands/queries.js similarity index 78% rename from src/agent/queries.js rename to src/agent/commands/queries.js index d1408b9..ae98164 100644 --- a/src/agent/queries.js +++ b/src/agent/commands/queries.js @@ -1,15 +1,15 @@ -import { getNearestBlock, getNearbyMobTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from './world.js'; -import { getAllItems } from '../utils/mcdata.js'; - +import { getNearestBlock, getNearbyMobTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from '../world.js'; +import { getAllItems } from '../../utils/mcdata.js'; const pad = (str) => { return '\n' + str + '\n'; } -const queryList = [ +// queries are commands that just return strings and don't affect anything in the world +export const queryList = [ { name: "!stats", - description: "Get your bot's stats", + description: "Get your bot's location, health, and time of day.", perform: function (agent) { let bot = agent.bot; let res = 'STATS'; @@ -102,30 +102,4 @@ const queryList = [ return pad("Current code:\n`" + agent.coder.current_code +"`"); } } -]; - -const queryMap = {}; -for (let query of queryList) { - queryMap[query.name] = query; -} - -export function getQuery(name) { - return queryMap[name]; -} - -export function containsQuery(message) { - for (let query of queryList) { - if (message.includes(query.name)) { - return query.name; - } - } - return null; -} - -export function getQueryDocs() { - let docs = `\n*QUERY DOCS\n You can use the following commands to query for information about the world. Use the query name in your response and the next input will have the requested information.\n`; - for (let query of queryList) { - docs += query.name + ': ' + query.description + '\n'; - } - return docs + '*\n'; -} \ No newline at end of file +]; \ No newline at end of file diff --git a/src/agent/history.js b/src/agent/history.js index 9dcb6b7..1ba13ea 100644 --- a/src/agent/history.js +++ b/src/agent/history.js @@ -1,5 +1,4 @@ import { writeFileSync, readFileSync, mkdirSync } from 'fs'; -import { getQueryDocs } from './queries.js'; import { getCommandDocs } from './commands.js'; import { sendRequest, embed, cosineSimilarity } from '../utils/gpt.js'; import { stringifyTurns } from '../utils/text.js'; @@ -39,7 +38,6 @@ 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 writing and executing code. Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, use actions often, and do not give instructions unless asked.`; - system_message += getQueryDocs(); system_message += getCommandDocs(); if (this.bio != '') system_message += '\n\nBio:\n' + this.bio;