created agent, refactored queries/skills

This commit is contained in:
MaxRobinsonTheGreat 2023-11-07 09:44:56 -06:00
parent 1b09360172
commit a153316623
7 changed files with 347 additions and 154 deletions

1
.gitignore vendored
View file

@ -2,3 +2,4 @@
node_modules/
package-lock.json
temp.js
scratch.js

78
agent.js Normal file
View file

@ -0,0 +1,78 @@
import { createBot } from 'mineflayer';
import { pathfinder } from 'mineflayer-pathfinder';
import { plugin } from 'mineflayer-collectblock';
import { sendRequest } from './utils/gpt.js';
import { History } from './utils/history.js';
import { getQuery, containsQuery, getQueryDocs } from './utils/queries.js';
import { getSkillDocs, containsCodeBlock, executeSkill } from './utils/skill_library.js';
export class Agent {
constructor(name) {
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.`;
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.history = new History(this);
this.bot.on('login', () => {
this.bot.chat('Hello world! I am ' + this.name);
console.log(`${this.name} logged in.`);
});
this.bot.on('chat', (username, message) => {
if (username === this.name) return;
console.log('received message from', username, ':', message);
this.respond(username, message);
});
}
async respond(username, message) {
this.history.add(username, message);
for (let i=0; i<5; i++) {
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);
this.history.add(this.name, query_res);
}
else if (containsCodeBlock(res)) { // contains code block
let message = res.substring(0, res.indexOf('```')).trim();
if (message)
this.bot.chat(message);
else
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);
}
break;
}
else { // conversation response
this.bot.chat(res);
break;
}
}
}
}
new Agent('andy');

View file

@ -15,16 +15,9 @@ if (process.env.OPENAI_ORG_ID) {
const openai = new OpenAIApi(openAiConfig);
export async function sendRequest(turns, systemMessage, stop_seq) {
export async function sendRequest(turns, systemMessage, stop_seq='***') {
let messages = [{'role': 'system', 'content': systemMessage}];
for (let i = 0; i < turns.length; i++) {
if (i % 2 == 0) {
messages.push({'role': 'user', 'content': turns[i]});
} else {
messages.push({'role': 'assistant', 'content': turns[i]});
}
}
let messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
let res = null;
try {
@ -37,7 +30,7 @@ export async function sendRequest(turns, systemMessage, stop_seq) {
}
catch (err) {
console.log(err);
res = 'I am sorry, I do not know how to respond to that.';
res = 'My brain disconnected, try again.';
}
return res;
}

View file

@ -1,67 +1,43 @@
var messages = [
{'source': 'all', 'message': 'Hey! What are you up to?'},
{'source': 'bot', 'message': `!action
\`\`\`
await skills.ExploreToFind(bot, 'coal_ore');
await skills.EquipItem(bot, 'wooden_pickaxe');
await skills.CollectBlock(bot, 'coal_ore', 10);
\`\`\`
let converse_examples = [
{'role': 'user', 'content': '(from "miner_32") Hey! What are you up to?'},
{'role': 'assistant', 'content': 'Nothing much miner_32, what do you need?'},
I'm looking for coal. Have you seen any?`},
{'source': 'all', 'message': 'No, but can you help me collect wood?'},
{'source': 'bot', 'message': `!blocks
\`\`\`
NEARBY_BLOCKS
- oak_log
- dirt
- cobblestone
- birch_log
\`\`\`
{'role': 'user', 'content': '(from "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.'},
Sure, do you want oak or birch?`},
{'source': 'all', 'message': 'Thanks! Either is fine.'},
{'source': 'bot', 'message': `I'll do that now.
{'role': 'user', 'content': '(from "zZZn98") come here'},
{'role': 'assistant', 'content': '```// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, "zZZn98");```'},
!execute
\`\`\`
while (true) {
await skills.CollectBlock(bot, 'oak_log', 1);
await skills.goToPlayer(bot, 'username');
await skills.DropItem(bot, 'oak_log', 1);
}
\`\`\``},
{'source': 'all', 'message': 'come here'},
{'source': 'bot', 'message': `Sure! I'm on my way.
{'role': 'user', 'content': '(from "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");```'},
!execute
\`\`\`
await skills.goToPlayer(bot, 'user42');
\`\`\``},
];
{'role': 'user', 'content': '(from "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': '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.'; }```"},
]
export function addEvent(source, message) {
messages.push({source, message});
}
export function getHistory(source) {
let res = [];
let lastSource = null;
for (let i = 0; i < messages.length; i++) {
if (lastSource != source && (messages[i].source == source || messages[i].source == 'all')) {
res.push(messages[i].message);
lastSource = source;
} else if (lastSource == source && (messages[i].source == source || messages[i].source == 'all')) {
res[-1] += '\n\n' + messages[i].message;
} else if (lastSource == source && messages[i].source == 'bot') {
res.push(messages[i].message);
lastSource = 'bot';
} else if (lastSource == 'bot' && messages[i].source == 'bot') {
res[-1] += '\n\n' + messages[i].message;
} else {
lastSource = null;
}
export class History {
constructor(agent) {
this.agent = agent;
this.turns = converse_examples;
}
return res;
}
getHistory() {
return this.turns;
}
add(name, content) {
let role = 'assistant';
if (name !== this.agent.name) {
role = 'user';
content = `(from "${name}") ${content}`;
}
this.turns.push({role, content});
}
}

77
utils/queries.js Normal file
View file

@ -0,0 +1,77 @@
import { getStats, getInventory, getBlocks, getNearbyEntities, getCraftable } from './context.js';
const pad = (str) => {
return '\n' + str + '\n';
}
const queryList = [
{
name: "!stats",
description: "Get your bot's stats",
perform: function (bot) {
return pad(getStats(bot));
}
},
{
name: "!inventory",
description: "Get your bot's inventory.",
perform: function (bot) {
return pad(getInventory(bot));
}
},
{
name: "!blocks",
description: "Get the blocks near the bot.",
perform: function (bot) {
return pad(getBlocks(bot));
}
},
{
name: "!craftable",
description: "Get the craftable items with the bot's inventory.",
perform: function (bot) {
return pad(getCraftable(bot));
}
},
{
name: "!entities",
description: "Get the nearby players and entities.",
perform: function (bot) {
return pad(getNearbyEntities(bot));
}
},
{
name: "!action",
description: "Get the currently executing code.",
perform: function (bot) {
return pad(currentCode(bot));
}
},
];
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';
}

48
utils/skill_library.js Normal file
View file

@ -0,0 +1,48 @@
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';
for (let skillFunc of Object.values(skills)) {
let str = skillFunc.toString();
docstring += skillFunc.name;
docstring += str.substring(str.indexOf('/**')+3, str.indexOf('**/')) + '\n';
}
return docstring + '*\n';
}
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;
}
}

View file

@ -3,15 +3,15 @@ import { getCraftingTable, getInventoryCounts, getInventoryStacks, getNearbyMobs
import pf from 'mineflayer-pathfinder';
/**
* Attempt to craft the given item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} item_name, the item name to craft.
* @returns {Promise<boolean>} true if the item was crafted, false otherwise.
* @example
* await skills.craftItem(bot, "wooden_pickaxe");
**/
export async function craftItem(bot, itemName) {
/**
* Attempt to craft the given item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} item_name, the item name to craft.
* @returns {Promise<boolean>} true if the item was crafted, false otherwise.
* @example
* await skills.craftItem(bot, "wooden_pickaxe");
**/
const table = getCraftingTable(bot);
let recipes = bot.recipesFor(getItemId(itemName), null, 1, table);
await bot.craft(recipes[0], 1, null);
@ -19,15 +19,15 @@ export async function craftItem(bot, itemName) {
}
/**
* Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} mobType, the type of mob to attack.
* @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
* @example
* await skills.attackMob(bot, "zombie");
**/
export async function attackMob(bot, mobType) {
/**
* Attack mob of the given type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} mobType, the type of mob to attack.
* @returns {Promise<boolean>} true if the mob was attacked, false if the mob type was not found.
* @example
* await skills.attackMob(bot, "zombie");
**/
const mobs = getNearbyMobs(bot);
for (let i = 0; i < mobs.length; i++) {
if (mobs[i].mobType == mobType) {
@ -39,15 +39,15 @@ export async function attackMob(bot, mobType) {
}
/**
* Collect one of the given block type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to collect.
* @returns {Promise<boolean>} true if the block was collected, false if the block type was not found.
* @example
* await skills.collectBlock(bot, "oak_log");
**/
export async function collectBlock(bot, blockType) {
/**
* Collect one of the given block type.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to collect.
* @returns {Promise<boolean>} true if the block was collected, false if the block type was not found.
* @example
* await skills.collectBlock(bot, "oak_log");
**/
const blocks = getNearbyBlocks(bot);
for (let i = 0; i < blocks.length; i++) {
if (blocks[i].name == blockType) {
@ -59,18 +59,18 @@ export async function collectBlock(bot, blockType) {
}
/**
* Break the block at the given position. Will use the bot's equipped item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate of the block to break.
* @param {number} y, the y coordinate of the block to break.
* @param {number} z, the z coordinate of the block to break.
* @returns {Promise<boolean>} true if the block was broken, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.breakBlockAt(bot, position.x, position.y - 1, position.x);
**/
export async function breakBlockAt(bot, x, y, z) {
/**
* Break the block at the given position. Will use the bot's equipped item.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate of the block to break.
* @param {number} y, the y coordinate of the block to break.
* @param {number} z, the z coordinate of the block to break.
* @returns {Promise<boolean>} true if the block was broken, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.breakBlockAt(bot, position.x, position.y - 1, position.x);
**/
let current = bot.blockAt({ x: x, y: y, z: z });
if (current.name != 'air')
await bot.dig(current, true);
@ -78,20 +78,19 @@ export async function breakBlockAt(bot, x, y, z) {
}
/**
* Place the given block type at the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to place.
* @param {number} x, the x coordinate to place the block at.
* @param {number} y, the y coordinate to place the block at.
* @param {number} z, the z coordinate to place the block at.
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y, position.x);
**/
export async function placeBlock(bot, blockType, x, y, z) {
/**
* Place the given block type at the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} blockType, the type of block to place.
* @param {number} x, the x coordinate to place the block at.
* @param {number} y, the y coordinate to place the block at.
* @param {number} z, the z coordinate to place the block at.
* @returns {Promise<boolean>} true if the block was placed, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.placeBlock(bot, "oak_log", position.x + 1, position.y, position.x);
**/
let referenceBlock = null;
let refVec = null;
if (bot.blockAt({ x: x + 1, y: y, z: z }).name != "air") {
@ -133,15 +132,15 @@ export async function placeBlock(bot, blockType, x, y, z) {
}
/**
* Equip the given item or block.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemName, the item or block name to equip.
* @returns {Promise<boolean>} true if the item was equipped, false otherwise.
* @example
* await skills.equipItem(bot, "wooden_pickaxe");
**/
export async function equipItem(bot, itemName) {
/**
* Equip the given item or block.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemName, the item or block name to equip.
* @returns {Promise<boolean>} true if the item was equipped, false otherwise.
* @example
* await skills.equipItem(bot, "wooden_pickaxe");
**/
let item = null;
for (let stack of getInventoryStacks(bot)) {
if (stack.name == itemName) {
@ -156,18 +155,18 @@ export async function equipItem(bot, itemName) {
}
/**
* Navigate to the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate to navigate to. If null, the bot's current x coordinate will be used.
* @param {number} y, the y coordinate to navigate to. If null, the bot's current y coordinate will be used.
* @param {number} z, the z coordinate to navigate to. If null, the bot's current z coordinate will be used.
* @returns {Promise<boolean>} true if the position was reached, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.goToPosition(bot, position.x, position.y, position.x + 20);
**/
export async function goToPosition(bot, x, y, z) {
/**
* Navigate to the given position.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {number} x, the x coordinate to navigate to. If null, the bot's current x coordinate will be used.
* @param {number} y, the y coordinate to navigate to. If null, the bot's current y coordinate will be used.
* @param {number} z, the z coordinate to navigate to. If null, the bot's current z coordinate will be used.
* @returns {Promise<boolean>} true if the position was reached, false otherwise.
* @example
* let position = getPosition(bot);
* await skills.goToPosition(bot, position.x, position.y, position.x + 20);
**/
if (x == null) x = bot.entity.position.x;
if (y == null) y = bot.entity.position.y;
if (z == null) z = bot.entity.position.z;
@ -178,16 +177,16 @@ export async function goToPosition(bot, x, y, z) {
}
/**
* Give one of the specified item to the specified player
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemType, the name of the item to give.
* @param {string} username, the username of the player to give the item to.
* @returns {Promise<boolean>} true if the item was given, false otherwise.
* @example
* await skills.giveToPlayer(bot, "oak_log", "player1");
**/
export async function giveToPlayer(bot, itemType, username) {
/**
* Give one of the specified item to the specified player
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} itemType, the name of the item to give.
* @param {string} username, the username of the player to give the item to.
* @returns {Promise<boolean>} true if the item was given, false otherwise.
* @example
* await skills.giveToPlayer(bot, "oak_log", "player1");
**/
let player = bot.players[username].entity
if (!player)
return false;
@ -201,15 +200,15 @@ export async function giveToPlayer(bot, itemType, username) {
}
/**
* Navigate to the given player.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to navigate to.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.goToPlayer(bot, "player");
**/
export async function goToPlayer(bot, username) {
/**
* Navigate to the given player.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to navigate to.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.goToPlayer(bot, "player");
**/
let player = bot.players[username].entity
if (!player)
return false;
@ -219,3 +218,24 @@ export async function goToPlayer(bot, username) {
bot.pathfinder.setGoal(new pf.goals.GoalNear(pos.x, pos.y, pos.z, 3));
return true;
}
export async function followPlayer(bot, username) {
/**
* Follow the given player endlessly.
* @param {MinecraftBot} bot, reference to the minecraft bot.
* @param {string} username, the username of the player to follow.
* @returns {Promise<boolean>} true if the player was found, false otherwise.
* @example
* await skills.followPlayer(bot, "player");
**/
let player = bot.players[username].entity
if (!player)
return false;
bot.pathfinder.setMovements(new pf.Movements(bot));
let pos = player.position;
bot.pathfinder.setGoal(new pf.goals.GoalFollow(player, 3), true);
return true;
}