mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-08-10 17:25:34 +02:00
commit
e034ea8b1f
11 changed files with 117 additions and 48 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,6 +1,5 @@
|
||||||
.vscode/
|
.vscode/
|
||||||
node_modules/
|
node_modules/
|
||||||
bots/
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
temp.js
|
temp.js
|
||||||
scratch.js
|
scratch.js
|
||||||
|
|
41
agent.js
41
agent.js
|
@ -4,20 +4,23 @@ import { History } from './utils/history.js';
|
||||||
import { Coder } from './utils/coder.js';
|
import { Coder } from './utils/coder.js';
|
||||||
import { getQuery, containsQuery } from './utils/queries.js';
|
import { getQuery, containsQuery } from './utils/queries.js';
|
||||||
import { containsCodeBlock } from './utils/skill-library.js';
|
import { containsCodeBlock } from './utils/skill-library.js';
|
||||||
|
import { Events } from './utils/events.js';
|
||||||
|
|
||||||
|
|
||||||
export class Agent {
|
export class Agent {
|
||||||
constructor(name, save_path, clear_memory=false, autostart=false) {
|
constructor(name, save_path, load_path=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, save_path);
|
||||||
this.history.loadExamples();
|
this.history.loadExamples();
|
||||||
this.coder = new Coder(this);
|
this.coder = new Coder(this);
|
||||||
|
|
||||||
if (!clear_memory) {
|
if (load_path) {
|
||||||
this.history.load();
|
this.history.load(load_path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.events = new Events(this, this.history.events)
|
||||||
|
|
||||||
this.bot.on('login', () => {
|
this.bot.on('login', () => {
|
||||||
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.`);
|
||||||
|
@ -26,26 +29,20 @@ export class Agent {
|
||||||
if (username === this.name) return;
|
if (username === this.name) return;
|
||||||
console.log('received message from', username, ':', message);
|
console.log('received message from', username, ':', message);
|
||||||
|
|
||||||
this.respond(username, message);
|
this.handleMessage(username, message);
|
||||||
this.history.save();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.bot.on('finished_executing', () => {
|
if (init_message) {
|
||||||
setTimeout(() => {
|
this.handleMessage('system', init_message);
|
||||||
if (!this.coder.executing) {
|
} else {
|
||||||
// return to default behavior
|
this.bot.emit('finished_executing');
|
||||||
}
|
}
|
||||||
}, 10000);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autostart)
|
|
||||||
this.respond('system', 'Agent process restarted. Notify the user and decide what to do.');
|
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async respond(username, message) {
|
async handleMessage(source, message) {
|
||||||
await this.history.add(username, 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());
|
||||||
this.history.add(this.name, res);
|
this.history.add(this.name, res);
|
||||||
|
@ -66,12 +63,13 @@ export class Agent {
|
||||||
if (message)
|
if (message)
|
||||||
this.bot.chat(message);
|
this.bot.chat(message);
|
||||||
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
||||||
|
|
||||||
if (code) {
|
if (code) {
|
||||||
this.coder.queueCode(code);
|
this.coder.queueCode(code);
|
||||||
let code_return = await this.coder.execute();
|
let code_return = await this.coder.execute();
|
||||||
let message = code_return.message;
|
let message = code_return.message;
|
||||||
if (code_return.interrupted && !code_return.timedout)
|
if (code_return.interrupted && !code_return.timedout)
|
||||||
break; // when interupted but not timed out, we were interupted by another conversation. end this one.
|
break;
|
||||||
if (!code_return.success) {
|
if (!code_return.success) {
|
||||||
message += "\nWrite code to fix the problem and try again.";
|
message += "\nWrite code to fix the problem and try again.";
|
||||||
}
|
}
|
||||||
|
@ -81,9 +79,12 @@ export class Agent {
|
||||||
}
|
}
|
||||||
else { // conversation response
|
else { // conversation response
|
||||||
this.bot.chat(res);
|
this.bot.chat(res);
|
||||||
console.log('Purely conversational response:', res)
|
console.log('Purely conversational response:', res);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.history.save();
|
||||||
|
this.bot.emit('finished_executing');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
7
bots/assist.json
Normal file
7
bots/assist.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "andy",
|
||||||
|
"bio": "You are playing minecraft and assisting other players in tasks.",
|
||||||
|
"memory": "",
|
||||||
|
"events": [],
|
||||||
|
"turns": []
|
||||||
|
}
|
|
@ -4,8 +4,9 @@ export class AgentProcess {
|
||||||
constructor(name) {
|
constructor(name) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
start(clear_memory=false, autostart=false) {
|
start(clear_memory=false, autostart=false, profile='assist') {
|
||||||
let args = ['controller/init-agent.js', this.name];
|
let args = ['controller/init-agent.js', this.name];
|
||||||
|
args.push('-p', profile);
|
||||||
if (clear_memory)
|
if (clear_memory)
|
||||||
args.push('-c');
|
args.push('-c');
|
||||||
if (autostart)
|
if (autostart)
|
||||||
|
@ -21,8 +22,8 @@ export class AgentProcess {
|
||||||
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
console.log(`Agent process exited with code ${code} and signal ${signal}`);
|
||||||
|
|
||||||
if (code !== 0) {
|
if (code !== 0) {
|
||||||
// agent must run for at least 30 seconds before restarting
|
// agent must run for at least 10 seconds before restarting
|
||||||
if (Date.now() - last_restart < 30 * 1000) {
|
if (Date.now() - last_restart < 10000) {
|
||||||
console.error('Agent process exited too quickly. Killing entire process. Goodbye.');
|
console.error('Agent process exited too quickly. Killing entire process. Goodbye.');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,11 @@ if (args.length < 1) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const argv = yargs(args)
|
const argv = yargs(args)
|
||||||
|
.option('profile', {
|
||||||
|
alias: 'p',
|
||||||
|
type: 'string',
|
||||||
|
description: 'profile to use for agent'
|
||||||
|
})
|
||||||
.option('clear_memory', {
|
.option('clear_memory', {
|
||||||
alias: 'c',
|
alias: 'c',
|
||||||
type: 'boolean',
|
type: 'boolean',
|
||||||
|
@ -20,8 +25,8 @@ const argv = yargs(args)
|
||||||
}).argv
|
}).argv
|
||||||
|
|
||||||
const name = argv._[0];
|
const name = argv._[0];
|
||||||
const clear_memory = !!argv.clear_memory;
|
|
||||||
const autostart = !!argv.autostart;
|
|
||||||
const save_path = './bots/'+name+'.json';
|
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, clear_memory, autostart);
|
new Agent(name, save_path, load_path, init_message);
|
||||||
|
|
|
@ -96,22 +96,22 @@ export class Coder {
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
clearTimeout(TIMEOUT);
|
clearTimeout(TIMEOUT);
|
||||||
|
|
||||||
this.agent.bot.emit('finished_executing');
|
|
||||||
let output = this.formatOutput(this.agent.bot);
|
let output = this.formatOutput(this.agent.bot);
|
||||||
let interrupted = this.agent.bot.interrupt_code;
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
let timedout = this.timedout;
|
let timedout = this.timedout;
|
||||||
this.clear();
|
this.clear();
|
||||||
|
this.agent.bot.emit("code_terminated");
|
||||||
return {success:true, message: output, interrupted, timedout};
|
return {success:true, message: output, interrupted, timedout};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.executing = false;
|
this.executing = false;
|
||||||
clearTimeout(TIMEOUT);
|
clearTimeout(TIMEOUT);
|
||||||
|
|
||||||
this.agent.bot.emit('finished_executing');
|
|
||||||
console.error("Code execution triggered catch: " + err);
|
console.error("Code execution triggered catch: " + err);
|
||||||
let message = this.formatOutput(this.agent.bot);
|
let message = this.formatOutput(this.agent.bot);
|
||||||
message += '!!Code threw exception!! Error: ' + err;
|
message += '!!Code threw exception!! Error: ' + err;
|
||||||
let interrupted = this.agent.bot.interrupt_code;
|
let interrupted = this.agent.bot.interrupt_code;
|
||||||
await this.stop();
|
await this.stop();
|
||||||
|
this.agent.bot.emit("code_terminated");
|
||||||
return {success: false, message, interrupted, timedout: false};
|
return {success: false, message, interrupted, timedout: false};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
47
utils/events.js
Normal file
47
utils/events.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
export class Events {
|
||||||
|
constructor(agent, events) {
|
||||||
|
this.events = events;
|
||||||
|
if (agent != null)
|
||||||
|
this.init(agent, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
init(agent, events) {
|
||||||
|
this.events = events;
|
||||||
|
for (let [event, callback, params] of events) {
|
||||||
|
if (callback != null)
|
||||||
|
agent.bot.on(event, this[callback].bind(this, agent, params));
|
||||||
|
}
|
||||||
|
|
||||||
|
agent.bot.on('time', () => {
|
||||||
|
if (agent.bot.time.timeOfDay == 0)
|
||||||
|
agent.bot.emit('sunrise');
|
||||||
|
else if (agent.bot.time.timeOfDay == 6000)
|
||||||
|
agent.bot.emit('noon');
|
||||||
|
else if (agent.bot.time.timeOfDay == 12000)
|
||||||
|
agent.bot.emit('sunset');
|
||||||
|
else if (agent.bot.time.timeOfDay == 18000)
|
||||||
|
agent.bot.emit('midnight');
|
||||||
|
});
|
||||||
|
|
||||||
|
agent.bot.on('health', () => {
|
||||||
|
if (agent.bot.health < 20)
|
||||||
|
agent.bot.emit('damaged');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeCode(agent, code) {
|
||||||
|
console.log('responding to event with code.');
|
||||||
|
agent.coder.queueCode(code);
|
||||||
|
let code_return = await agent.coder.execute();
|
||||||
|
console.log('code return:', code_return.message);
|
||||||
|
agent.history.add('system', code_return.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendThought(agent, message) {
|
||||||
|
agent.handleMessage(agent.name, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendChat(agent, message) {
|
||||||
|
agent.bot.chat(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -14,6 +14,9 @@ export class History {
|
||||||
this.bio = '';
|
this.bio = '';
|
||||||
this.memory = '';
|
this.memory = '';
|
||||||
|
|
||||||
|
// The bot's events
|
||||||
|
this.events = [];
|
||||||
|
|
||||||
// Variables for controlling the agent's memory and knowledge
|
// Variables for controlling the agent's memory and knowledge
|
||||||
this.max_messages = 20;
|
this.max_messages = 20;
|
||||||
this.fewshot = 5;
|
this.fewshot = 5;
|
||||||
|
@ -34,8 +37,7 @@ export class History {
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemMessage() {
|
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.
|
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, omit needless words, and do not give instructions unless asked.`;
|
||||||
Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, omit needless words, and do not give instructions unless asked.`;
|
|
||||||
system_message += getQueryDocs();
|
system_message += getQueryDocs();
|
||||||
system_message += getSkillDocs();
|
system_message += getSkillDocs();
|
||||||
if (this.bio != '')
|
if (this.bio != '')
|
||||||
|
@ -101,6 +103,8 @@ export class History {
|
||||||
const embedding = await embed(messages);
|
const embedding = await embed(messages);
|
||||||
this.examples.push({'embedding': embedding, 'turns': example});
|
this.examples.push({'embedding': embedding, 'turns': example});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await this.setExamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
async setExamples() {
|
async setExamples() {
|
||||||
|
@ -135,27 +139,30 @@ export class History {
|
||||||
if (this.turns.length >= this.max_messages) {
|
if (this.turns.length >= this.max_messages) {
|
||||||
console.log('summarizing memory')
|
console.log('summarizing memory')
|
||||||
let to_summarize = [this.turns.shift()];
|
let to_summarize = [this.turns.shift()];
|
||||||
while (this.turns[0].role != 'user' && this.turns.length > 0)
|
while (this.turns[0].role != 'user' && this.turns.length > 1)
|
||||||
to_summarize.push(this.turns.shift());
|
to_summarize.push(this.turns.shift());
|
||||||
await this.storeMemories(to_summarize);
|
await this.storeMemories(to_summarize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role === 'user')
|
if (role != 'assistant')
|
||||||
await this.setExamples();
|
await this.setExamples();
|
||||||
}
|
}
|
||||||
|
|
||||||
save() {
|
save(save_path=null) {
|
||||||
if (this.save_path === '' || this.save_path == null) return;
|
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', { recursive: true });
|
||||||
let data = {
|
let data = {
|
||||||
'name': this.name,
|
'name': this.name,
|
||||||
'bio': this.bio,
|
'bio': this.bio,
|
||||||
'memory': this.memory,
|
'memory': this.memory,
|
||||||
|
'events': this.events,
|
||||||
'turns': this.turns
|
'turns': this.turns
|
||||||
};
|
};
|
||||||
const json_data = JSON.stringify(data, null, 4);
|
const json_data = JSON.stringify(data, null, 4);
|
||||||
writeFileSync(this.save_path, json_data, (err) => {
|
writeFileSync(save_path, json_data, (err) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
@ -163,18 +170,21 @@ export class History {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
load() {
|
load(load_path=null) {
|
||||||
if (this.save_path === '' || this.save_path == null) return;
|
if (load_path == null)
|
||||||
|
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(this.save_path, 'utf8');
|
const data = readFileSync(load_path, 'utf8');
|
||||||
const obj = JSON.parse(data);
|
const obj = JSON.parse(data);
|
||||||
this.turns = obj.turns;
|
|
||||||
this.bio = obj.bio;
|
this.bio = obj.bio;
|
||||||
this.memory = obj.memory;
|
this.memory = obj.memory;
|
||||||
this.num_saved_turns = obj.num_saved_turns;
|
this.events = obj.events;
|
||||||
|
this.turns = obj.turns;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('No history file found for ' + this.name + '.');
|
console.log('No history file found for ' + this.name + '.');
|
||||||
|
console.log(load_path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -46,7 +46,7 @@ const queryList = [
|
||||||
perform: function (agent) {
|
perform: function (agent) {
|
||||||
return pad("Current code:\n`" + agent.coder.current_code +"`");
|
return pad("Current code:\n`" + agent.coder.current_code +"`");
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
const queryMap = {};
|
const queryMap = {};
|
||||||
|
@ -68,8 +68,7 @@ export function containsQuery(message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getQueryDocs() {
|
export function getQueryDocs() {
|
||||||
let docs = `\n*QUERY DOCS\n You can use the following commands to query for information about the world.
|
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`;
|
||||||
Use the query name in your response and the next input will have the requested information.\n`;
|
|
||||||
for (let query of queryList) {
|
for (let query of queryList) {
|
||||||
docs += query.name + ': ' + query.description + '\n';
|
docs += query.name + ': ' + query.description + '\n';
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,7 @@ import * as skills from './skills.js';
|
||||||
import * as world from './world.js';
|
import * as world from './world.js';
|
||||||
|
|
||||||
export function getSkillDocs() {
|
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```' \nYour 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";
|
||||||
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";
|
|
||||||
docstring += docHelper(Object.values(skills), 'skills');
|
docstring += docHelper(Object.values(skills), 'skills');
|
||||||
docstring += docHelper(Object.values(world), 'world');
|
docstring += docHelper(Object.values(world), 'world');
|
||||||
return docstring + '*\n';
|
return docstring + '*\n';
|
||||||
|
|
|
@ -183,15 +183,16 @@ export function getNearbyPlayerNames(bot) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function getNearbyBlockTypes(bot) {
|
export function getNearbyBlockTypes(bot, distance=16) {
|
||||||
/**
|
/**
|
||||||
* Get a list of all nearby block names.
|
* Get a list of all nearby block names.
|
||||||
* @param {Bot} bot - The bot to get nearby blocks for.
|
* @param {Bot} bot - The bot to get nearby blocks for.
|
||||||
|
* @param {number} distance - The maximum distance to search, default 16.
|
||||||
* @returns {string[]} - A list of all nearby blocks.
|
* @returns {string[]} - A list of all nearby blocks.
|
||||||
* @example
|
* @example
|
||||||
* let blocks = world.getNearbyBlockTypes(bot);
|
* let blocks = world.getNearbyBlockTypes(bot);
|
||||||
**/
|
**/
|
||||||
let blocks = getNearbyBlocks(bot, 16);
|
let blocks = getNearbyBlocks(bot, distance);
|
||||||
let found = [];
|
let found = [];
|
||||||
for (let i = 0; i < blocks.length; i++) {
|
for (let i = 0; i < blocks.length; i++) {
|
||||||
if (!found.includes(blocks[i].name)) {
|
if (!found.includes(blocks[i].name)) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue