Merge pull request #14 from kolbytn/folder-refactor

Folder refactor
This commit is contained in:
Max Robinson 2024-01-02 20:41:01 -06:00 committed by GitHub
commit c78ace3873
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 215 additions and 274 deletions

4
.gitignore vendored
View file

@ -1,7 +1,7 @@
.vscode/ .vscode/
node_modules/ node_modules/
package-lock.json package-lock.json
temp.js
scratch.js scratch.js
agent_code/**
!agent_code/template.js !agent_code/template.js
bots/**/action-code/**
bots/**/save.json

View file

@ -1,5 +1,5 @@
import * as skills from '../utils/skills.js'; import * as skills from '../../../src/agent/skills.js';
import * as world from '../utils/world.js'; import * as world from '../../../src/agent/world.js';
import Vec3 from 'vec3'; import Vec3 from 'vec3';
const log = skills.log; const log = skills.log;

View file

@ -1,32 +0,0 @@
import { Agent } from '../agent.js';
import yargs from 'yargs';
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('Usage: node init_agent.js <agent_name> [-c] [-a]');
process.exit(1);
}
const argv = yargs(args)
.option('profile', {
alias: 'p',
type: 'string',
description: 'profile to use for agent'
})
.option('clear_memory', {
alias: 'c',
type: 'boolean',
description: 'restart memory from scratch'
})
.option('autostart', {
alias: 'a',
type: 'boolean',
description: 'automatically prompt the agent on startup'
}).argv
const name = argv._[0];
const save_path = './bots/'+name+'.json';
const load_path = !!argv.clear_memory ? './bots/'+argv.profile+'.json' : save_path;
const init_message = !!argv.autostart ? 'Agent process restarted. Notify the user and decide what to do.' : null;
new Agent(name, save_path, load_path, init_message);

View file

@ -1,3 +1,3 @@
import { AgentProcess } from './controller/agent-process.js'; import { AgentProcess } from './src/process/agent-process.js';
new AgentProcess('andy').start(true, false); new AgentProcess('andy').start('assist');

View file

@ -1,37 +1,47 @@
import { initBot } from './utils/mcdata.js'; import { initBot } from '../utils/mcdata.js';
import { sendRequest } from './utils/gpt.js'; import { sendRequest } from '../utils/gpt.js';
import { History } from './utils/history.js'; import { History } from './history.js';
import { Coder } from './utils/coder.js'; import { Coder } from './coder.js';
import { getQuery, containsQuery } from './utils/queries.js'; import { getQuery, containsQuery } from './queries.js';
import { containsCodeBlock } from './utils/skill-library.js'; import { containsCodeBlock } from './skill-library.js';
import { Events } from './utils/events.js'; import { Events } from './events.js';
export class Agent { export class Agent {
constructor(name, save_path, load_path=null, init_message=null) { constructor(name, profile=null, init_message=null) {
this.name = name; this.name = name;
this.bot = initBot(name); this.bot = initBot(name);
this.history = new History(this, save_path); this.history = new History(this);
this.history.loadExamples();
this.coder = new Coder(this); this.coder = new Coder(this);
if (load_path) { this.history.load(profile);
this.history.load(load_path);
}
this.events = new Events(this, this.history.events) this.events = new Events(this, this.history.events)
this.bot.on('login', () => { this.bot.on('login', async () => {
this.bot.chat('Hello world! I am ' + this.name); this.bot.chat('Hello world! I am ' + this.name);
console.log(`${this.name} logged in.`); console.log(`${this.name} logged in.`);
const ignore_messages = [
"Set own game mode to",
"Set the time to",
"Set the difficulty to",
"Teleported ",
"Set the weather to",
"Gamerule "
];
this.bot.on('chat', (username, message) => { this.bot.on('chat', (username, message) => {
if (username === this.name) return; if (username === this.name) return;
if (ignore_messages.some((m) => message.startsWith(m))) return;
console.log('received message from', username, ':', message); console.log('received message from', username, ':', message);
this.handleMessage(username, message); this.handleMessage(username, message);
}); });
await this.history.loadExamples();
if (init_message) { if (init_message) {
this.handleMessage('system', init_message); this.handleMessage('system', init_message);
} else { } else {
@ -41,7 +51,8 @@ export class Agent {
} }
async handleMessage(source, message) { async handleMessage(source, message) {
await this.history.add(source, message); if (!!source && !!message)
await this.history.add(source, message);
for (let i=0; i<5; i++) { for (let i=0; i<5; i++) {
let res = await sendRequest(this.history.getHistory(), this.history.getSystemMessage()); let res = await sendRequest(this.history.getHistory(), this.history.getSystemMessage());

View file

@ -1,4 +1,4 @@
import { writeFile, readFile, unlink } from 'fs'; import { writeFile, readFile, unlink, mkdirSync } from 'fs';
export class Coder { export class Coder {
constructor(agent) { constructor(agent) {
@ -6,17 +6,19 @@ export class Coder {
this.queued_code = ''; this.queued_code = '';
this.current_code = ''; this.current_code = '';
this.file_counter = 0; this.file_counter = 0;
this.fp = './agent_code/'; this.fp = '/bots/'+agent.name+'/action-code/';
this.agent.bot.interrupt_code = false; this.agent.bot.interrupt_code = false;
this.executing = false; this.executing = false;
this.agent.bot.output = ''; this.agent.bot.output = '';
this.code_template = ''; this.code_template = '';
this.timedout = false; this.timedout = false;
readFile(this.fp+'template.js', 'utf8', (err, data) => { readFile('./bots/template.js', 'utf8', (err, data) => {
if (err) throw err; if (err) throw err;
this.code_template = data; this.code_template = data;
}); });
mkdirSync('.' + this.fp, { recursive: true });
} }
queueCode(code) { queueCode(code) {
@ -67,7 +69,7 @@ export class Coder {
console.log("writing to file...", src) console.log("writing to file...", src)
let filename = this.fp + this.file_counter + '.js'; let filename = this.file_counter + '.js';
// if (this.file_counter > 0) { // if (this.file_counter > 0) {
// let prev_filename = this.fp + (this.file_counter-1) + '.js'; // let prev_filename = this.fp + (this.file_counter-1) + '.js';
// unlink(prev_filename, (err) => { // unlink(prev_filename, (err) => {
@ -77,7 +79,7 @@ export class Coder {
// } commented for now, useful to keep files for debugging // } commented for now, useful to keep files for debugging
this.file_counter++; this.file_counter++;
let write_result = await this.writeFilePromise(filename, src); let write_result = await this.writeFilePromise('.' + this.fp + filename, src)
if (write_result) { if (write_result) {
console.error('Error writing code execution file: ' + result); console.error('Error writing code execution file: ' + result);
@ -86,7 +88,7 @@ export class Coder {
let TIMEOUT; let TIMEOUT;
try { try {
console.log('executing code...\n'); console.log('executing code...\n');
let execution_file = await import('.'+filename); let execution_file = await import('../..' + this.fp + filename);
await this.stop(); await this.stop();
this.current_code = this.queued_code; this.current_code = this.queued_code;

View file

@ -1,13 +1,13 @@
import { writeFileSync, readFileSync, mkdirSync } from 'fs'; import { writeFileSync, readFileSync, mkdirSync } from 'fs';
import { getQueryDocs } from './queries.js'; import { getQueryDocs } from './queries.js';
import { getSkillDocs } from './skill-library.js'; import { getSkillDocs } from './skill-library.js';
import { sendRequest, embed, cosineSimilarity } from './gpt.js'; import { sendRequest, embed, cosineSimilarity } from '../utils/gpt.js';
export class History { export class History {
constructor(agent, save_path) { constructor(agent) {
this.name = agent.name; this.name = agent.name;
this.save_path = save_path; this.save_path = `./bots/${this.name}/save.json`;
this.turns = []; this.turns = [];
// These define an agent's long term memory // These define an agent's long term memory
@ -86,7 +86,7 @@ export class History {
async loadExamples() { async loadExamples() {
let examples = []; let examples = [];
try { try {
const data = readFileSync('utils/examples.json', 'utf8'); const data = readFileSync('./src/examples.json', 'utf8');
examples = JSON.parse(data); examples = JSON.parse(data);
} catch (err) { } catch (err) {
console.log('No history examples found.'); console.log('No history examples found.');
@ -148,12 +148,9 @@ export class History {
await this.setExamples(); await this.setExamples();
} }
save(save_path=null) { save() {
if (save_path == null)
save_path = this.save_path;
if (save_path === '' || save_path == null) return;
// save history object to json file // save history object to json file
mkdirSync('bots', { recursive: true }); mkdirSync(`./bots/${this.name}`, { recursive: true });
let data = { let data = {
'name': this.name, 'name': this.name,
'bio': this.bio, 'bio': this.bio,
@ -162,7 +159,7 @@ export class History {
'turns': this.turns 'turns': this.turns
}; };
const json_data = JSON.stringify(data, null, 4); const json_data = JSON.stringify(data, null, 4);
writeFileSync(save_path, json_data, (err) => { writeFileSync(this.save_path, json_data, (err) => {
if (err) { if (err) {
throw err; throw err;
} }
@ -170,10 +167,8 @@ export class History {
}); });
} }
load(load_path=null) { load(profile) {
if (load_path == null) const load_path = profile? `./bots/${this.name}/${profile}.json` : this.save_path;
load_path = this.save_path;
if (load_path === '' || load_path == null) return;
try { try {
// load history object from json file // load history object from json file
const data = readFileSync(load_path, 'utf8'); const data = readFileSync(load_path, 'utf8');
@ -183,8 +178,7 @@ export class History {
this.events = obj.events; this.events = obj.events;
this.turns = obj.turns; this.turns = obj.turns;
} catch (err) { } catch (err) {
console.log('No history file found for ' + this.name + '.'); console.error(`No file for profile '${load_path}' for agent ${this.name}.`);
console.log(load_path);
} }
} }
} }

131
src/agent/queries.js Normal file
View file

@ -0,0 +1,131 @@
import { getNearestBlock, getNearbyMobTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from './world.js';
import { getAllItems } from '../utils/mcdata.js';
const pad = (str) => {
return '\n' + str + '\n';
}
const queryList = [
{
name: "!stats",
description: "Get your bot's stats",
perform: function (agent) {
let bot = agent.bot;
let res = 'STATS';
res += `\n- position: x:${bot.entity.position.x}, y:${bot.entity.position.y}, z:${bot.entity.position.z}`;
res += `\n- health: ${bot.health} / 20`;
if (bot.time.timeOfDay < 6000) {
res += '\n- time: Morning';
} else if (bot.time.timeOfDay < 12000) {
res += '\n- time: Afternoon';
} else {
res += '\n- time: Night';
}
return pad(res);
}
},
{
name: "!inventory",
description: "Get your bot's inventory.",
perform: function (agent) {
let bot = agent.bot;
let inventory = getInventoryCounts(bot);
let res = 'INVENTORY';
for (const item in inventory) {
if (inventory[item] && inventory[item] > 0)
res += `\n- ${item}: ${inventory[item]}`;
}
if (res == 'INVENTORY') {
res += ': none';
}
return pad(res);
}
},
{
name: "!blocks",
description: "Get the blocks near the bot.",
perform: function (agent) {
let bot = agent.bot;
let res = 'NEARBY_BLOCKS';
let blocks = getNearbyBlockTypes(bot);
for (let i = 0; i < blocks.length; i++) {
res += `\n- ${blocks[i]}`;
}
if (blocks.length == 0) {
res += ': none';
}
return pad(res);
}
},
{
name: "!craftable",
description: "Get the craftable items with the bot's inventory.",
perform: function (agent) {
const bot = agent.bot;
const table = getNearestBlock(bot, 'crafting_table');
let res = 'CRAFTABLE_ITEMS';
for (const item of getAllItems()) {
let recipes = bot.recipesFor(item.id, null, 1, table);
if (recipes.length > 0) {
res += `\n- ${item.name}`;
}
}
if (res == 'CRAFTABLE_ITEMS') {
res += ': none';
}
return pad(res);
}
},
{
name: "!entities",
description: "Get the nearby players and entities.",
perform: function (agent) {
let bot = agent.bot;
let res = 'NEARBY_ENTITIES';
for (const entity of getNearbyPlayerNames(bot)) {
res += `\n- player: ${entity}`;
}
for (const entity of getNearbyMobTypes(bot)) {
res += `\n- mob: ${entity}`;
}
if (res == 'NEARBY_ENTITIES') {
res += ': none';
}
return pad(res);
}
},
{
name: "!action",
description: "Get the currently executing code.",
perform: function (agent) {
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';
}

View file

@ -1,4 +1,4 @@
import { getItemId, getItemName } from "./mcdata.js"; import { getItemId, getItemName } from "../utils/mcdata.js";
import { getNearestBlocks, getNearestBlock, getInventoryCounts, getInventoryStacks, getNearbyMobs, getNearbyBlocks } from "./world.js"; import { getNearestBlocks, getNearestBlock, getInventoryCounts, getInventoryStacks, getNearbyMobs, getNearbyBlocks } from "./world.js";
import pf from 'mineflayer-pathfinder'; import pf from 'mineflayer-pathfinder';
import Vec3 from 'vec3'; import Vec3 from 'vec3';

View file

@ -1,4 +1,4 @@
import { getAllBlockIds } from './mcdata.js'; import { getAllBlockIds } from '../utils/mcdata.js';
export function getNearestBlocks(bot, block_types, distance=16, count=1) { export function getNearestBlocks(bot, block_types, distance=16, count=1) {

View file

@ -4,13 +4,12 @@ export class AgentProcess {
constructor(name) { constructor(name) {
this.name = name; this.name = name;
} }
start(clear_memory=false, autostart=false, profile='assist') { start(profile='assist', init_message=null) {
let args = ['controller/init-agent.js', this.name]; let args = ['src/process/init-agent.js', this.name];
args.push('-p', profile); if (profile)
if (clear_memory) args.push('-p', profile);
args.push('-c'); if (init_message)
if (autostart) args.push('-m', init_message);
args.push('-a');
const agentProcess = spawn('node', args, { const agentProcess = spawn('node', args, {
stdio: 'inherit', stdio: 'inherit',
@ -28,7 +27,7 @@ export class AgentProcess {
process.exit(1); process.exit(1);
} }
console.log('Restarting agent...'); console.log('Restarting agent...');
this.start(false, true); this.start('save', 'Agent process restarted. Notify the user and decide what to do.');
last_restart = Date.now(); last_restart = Date.now();
} }
}); });

23
src/process/init-agent.js Normal file
View file

@ -0,0 +1,23 @@
import { Agent } from '../agent/agent.js';
import yargs from 'yargs';
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('Usage: node init_agent.js <agent_name> [profile] [init_message]');
process.exit(1);
}
const argv = yargs(args)
.option('profile', {
alias: 'p',
type: 'string',
description: 'profile to use for agent'
})
.option('init_message', {
alias: 'm',
type: 'string',
description: 'automatically prompt the agent on startup'
}).argv
const name = args[0];
new Agent(name, argv.profile, argv.init_message);

View file

@ -1,111 +0,0 @@
import { readFileSync } from 'fs';
import { getNearestBlock, getNearbyMobTypes, getNearbyPlayerNames, getNearbyBlockTypes, getInventoryCounts } from './world.js';
import { getAllItems } from './mcdata.js';
export function getStats(bot) {
let res = 'STATS';
res += `\n- position: x:${bot.entity.position.x}, y:${bot.entity.position.y}, z:${bot.entity.position.z}`;
res += `\n- health: ${bot.health} / 20`;
if (bot.time.timeOfDay < 6000) {
res += '\n- time: Morning';
} else if (bot.time.timeOfDay < 12000) {
res += '\n- time: Afternoon';
} else {
res += '\n- time: Night';
}
return res;
}
export function getInventory(bot) {
let inventory = getInventoryCounts(bot);
let res = 'INVENTORY';
for (const item in inventory) {
if (inventory[item] && inventory[item] > 0)
res += `\n- ${item}: ${inventory[item]}`;
}
if (res == 'INVENTORY') {
res += ': none';
}
return res;
}
export function getBlocks(bot) {
let res = 'NEARBY_BLOCKS';
let blocks = getNearbyBlockTypes(bot);
for (let i = 0; i < blocks.length; i++) {
res += `\n- ${blocks[i]}`;
}
if (blocks.length == 0) {
res += ': none';
}
return res;
}
export function getNearbyEntities(bot) {
let res = 'NEARBY_ENTITIES';
for (const entity of getNearbyPlayerNames(bot)) {
res += `\n- player: ${entity}`;
}
for (const entity of getNearbyMobTypes(bot)) {
res += `\n- mob: ${entity}`;
}
if (res == 'NEARBY_ENTITIES') {
res += ': none';
}
return res;
}
export function getCraftable(bot) {
const table = getNearestBlock(bot, 'crafting_table');
let res = 'CRAFTABLE_ITEMS';
for (const item of getAllItems()) {
let recipes = bot.recipesFor(item.id, null, 1, table);
if (recipes.length > 0) {
res += `\n- ${item.name}`;
}
}
if (res == 'CRAFTABLE_ITEMS') {
res += ': none';
}
return res;
}
export function getDetailedSkills() {
let res = 'namespace skills {';
let contents = readFileSync("./utils/skills.js", "utf-8").split('\n');
for (let i = 0; i < contents.length; i++) {
if (contents[i].slice(0, 3) == '/**') {
res += '\t' + contents[i];
} else if (contents[i].slice(0, 2) == ' *') {
res += '\t' + contents[i];
} else if (contents[i].slice(0, 4) == ' **/') {
res += '\t' + contents[i] + '\n\n';
}
}
res = res.trim() + '\n}'
return res;
}
export function getWorldFunctions() {
let res = 'namespace world {';
let contents = readFileSync("./utils/world.js", "utf-8").split('\n');
for (let i = 0; i < contents.length; i++) {
if (contents[i].slice(0, 3) == '/**') {
res += '\t' + contents[i];
} else if (contents[i].slice(0, 2) == ' *') {
res += '\t' + contents[i];
} else if (contents[i].slice(0, 4) == ' **/') {
res += '\t' + contents[i] + '\n\n';
}
}
res = res.trim() + '\n}'
return res;
}

View file

@ -1,76 +0,0 @@
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 (agent) {
return pad(getStats(agent.bot));
}
},
{
name: "!inventory",
description: "Get your bot's inventory.",
perform: function (agent) {
return pad(getInventory(agent.bot));
}
},
{
name: "!blocks",
description: "Get the blocks near the bot.",
perform: function (agent) {
return pad(getBlocks(agent.bot));
}
},
{
name: "!craftable",
description: "Get the craftable items with the bot's inventory.",
perform: function (agent) {
return pad(getCraftable(agent.bot));
}
},
{
name: "!entities",
description: "Get the nearby players and entities.",
perform: function (agent) {
return pad(getNearbyEntities(agent.bot));
}
},
{
name: "!action",
description: "Get the currently executing code.",
perform: function (agent) {
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';
}