mindcraft/src/agent/coder.js

265 lines
9.7 KiB
JavaScript
Raw Normal View History

2024-01-03 22:16:50 -08:00
import { writeFile, readFile, mkdirSync } from 'fs';
import { sendRequest, embed, cosineSimilarity } from '../utils/gpt.js';
import { stringifyTurns } from '../utils/text.js';
export class Coder {
constructor(agent) {
this.agent = agent;
2023-11-15 17:00:02 -06:00
this.queued_code = '';
this.current_code = '';
2023-11-07 12:46:55 -06:00
this.file_counter = 0;
2023-12-21 15:11:38 -07:00
this.fp = '/bots/'+agent.name+'/action-code/';
2023-11-13 11:09:37 -06:00
this.agent.bot.interrupt_code = false;
2023-11-13 00:57:20 -06:00
this.executing = false;
this.agent.bot.output = '';
this.code_template = '';
2023-12-06 22:15:09 -06:00
this.timedout = false;
2024-01-03 22:16:50 -08:00
this.fewshot = 3;
this.examples = [];
2023-12-21 15:11:38 -07:00
readFile('./bots/template.js', 'utf8', (err, data) => {
if (err) throw err;
this.code_template = data;
});
2023-12-21 20:42:01 -07:00
mkdirSync('.' + this.fp, { recursive: true });
}
queueCode(code) {
2023-11-15 17:00:02 -06:00
this.queued_code = this.santitizeCode(code);
}
santitizeCode(code) {
const remove_strs = ['javascript', 'js']
for (let r of remove_strs) {
if (code.startsWith(r)) {
code = code.slice(r.length);
return code;
}
}
return code;
}
2023-11-07 12:46:55 -06:00
writeFilePromise(filename, src) {
// makes it so we can await this function
return new Promise((resolve, reject) => {
writeFile(filename, src, (err) => {
if (err) {
reject(err);
} else {
resolve();
}
});
});
}
2024-01-03 22:16:50 -08:00
async loadExamples() {
let examples = [];
try {
const data = readFileSync('./src/examples.json', 'utf8');
examples = JSON.parse(data);
} catch (err) {
console.log('No history examples found.');
}
this.examples = [];
for (let example of examples) {
let context = '';
for (let turn of example.conversation) {
context += turn.content + '\n';
}
context = context.trim();
const embedding = await embed(context);
this.examples.push({'embedding': embedding, 'turns': example});
}
await this.setExamples();
}
async sortExamples(messages) {
let context = '';
for (let turn of messages) {
context += turn.content + '\n';
}
context = context.trim();
const embedding = await embed(context);
this.examples.sort((a, b) => {
return cosineSimilarity(a.embedding, embedding) - cosineSimilarity(b.embedding, embedding);
});
}
async generateCode(agent_history) {
let system_message = "You are a minecraft bot that plays minecraft by writing javascript. Given the conversation between you and the user, use the provided skills and world queries to write your code. You will then be given a response to your code. If you are satisfied with the response, return output without writing any additional code. If you want to try again, output the code you want to try.";
system_message += getSkillDocs();
let messages = [];
this.sortExamples(agent_history.turns);
for (let example of this.examples.slice(-this.fewshot)) {
messages.push({
role: 'user',
content: stringifyTurns(example.conversation)
});
for (let i = 0; i < example.coder.length; i++) {
messages.push({
role: i % 2 == 0 ? 'assistant' : 'user',
content: example.coder[i]
});
}
}
messages.push({
role: 'user',
content: stringifyTurns(agent_history.turns),
});
let final_message = 'No code generated.';
for (let i=0; i<5; i++) {
let res = await sendRequest(messages, system_message);
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
if (!code)
break;
agent.coder.queueCode(code);
let code_return = await agent.coder.execute();
if (code_return.interrupted && !custom_return.timedout)
break;
messages.push({
role: 'assistant',
content: res
});
messages.push({
role: 'user',
content: code_return.message
});
final_message = code_return.message;
}
return final_message;
}
2023-11-13 00:57:20 -06:00
2023-12-08 16:18:20 -06:00
// returns {success: bool, message: string, interrupted: bool, timedout: false}
async execute() {
2023-12-06 22:15:09 -06:00
if (!this.queued_code) return {success: false, message: "No code to execute.", interrupted: false, timedout: false};
if (!this.code_template) return {success: false, message: "Code template not loaded.", interrupted: false, timedout: false};
let src = '';
2023-12-04 21:33:40 -06:00
let code = this.queued_code;
code = code.replaceAll('console.log(', 'log(bot,');
code = code.replaceAll('log("', 'log(bot,"');
2023-11-15 17:00:02 -06:00
// this may cause problems in callback functions
2023-12-04 21:33:40 -06:00
code = code.replaceAll(';\n', '; if(bot.interrupt_code) {log(bot, "Code interrupted.");return;}\n');
2023-11-15 17:00:02 -06:00
for (let line of code.split('\n')) {
src += ` ${line}\n`;
}
src = this.code_template.replace('/* CODE HERE */', src);
console.log("writing to file...", src)
2023-11-07 12:46:55 -06:00
2023-12-21 15:11:38 -07:00
let filename = this.file_counter + '.js';
// if (this.file_counter > 0) {
// let prev_filename = this.fp + (this.file_counter-1) + '.js';
// unlink(prev_filename, (err) => {
// console.log("deleted file " + prev_filename);
// if (err) console.error(err);
// });
// } commented for now, useful to keep files for debugging
2023-11-07 12:46:55 -06:00
this.file_counter++;
2023-12-21 15:11:38 -07:00
let write_result = await this.writeFilePromise('.' + this.fp + filename, src)
if (write_result) {
2023-11-07 12:46:55 -06:00
console.error('Error writing code execution file: ' + result);
2023-12-06 22:15:09 -06:00
return {success: false, message: result, interrupted: false, timedout: false};
2023-11-07 12:46:55 -06:00
}
2023-12-10 20:19:07 -06:00
let TIMEOUT;
2023-11-07 12:46:55 -06:00
try {
console.log('executing code...\n');
2023-12-21 15:11:38 -07:00
let execution_file = await import('../..' + this.fp + filename);
2023-11-13 00:57:20 -06:00
await this.stop();
2023-11-15 17:00:02 -06:00
this.current_code = this.queued_code;
2023-11-08 21:05:18 -08:00
this.executing = true;
2023-12-12 13:39:35 -06:00
TIMEOUT = this._startTimeout(10);
2023-12-06 22:15:09 -06:00
await execution_file.main(this.agent.bot); // open fire
2023-11-08 21:05:18 -08:00
this.executing = false;
2023-12-10 20:19:07 -06:00
clearTimeout(TIMEOUT);
2023-12-09 21:40:53 -06:00
2023-11-13 00:57:20 -06:00
let output = this.formatOutput(this.agent.bot);
2023-11-13 11:09:37 -06:00
let interrupted = this.agent.bot.interrupt_code;
2023-12-06 22:15:09 -06:00
let timedout = this.timedout;
2023-11-07 22:23:42 -06:00
this.clear();
2023-12-20 16:30:05 -08:00
this.agent.bot.emit("code_terminated");
2023-12-06 22:15:09 -06:00
return {success:true, message: output, interrupted, timedout};
2023-11-07 12:46:55 -06:00
} catch (err) {
2023-11-08 21:05:18 -08:00
this.executing = false;
2023-12-10 20:19:07 -06:00
clearTimeout(TIMEOUT);
console.error("Code execution triggered catch: " + err);
2023-11-13 00:57:20 -06:00
let message = this.formatOutput(this.agent.bot);
message += '!!Code threw exception!! Error: ' + err;
2023-11-13 11:09:37 -06:00
let interrupted = this.agent.bot.interrupt_code;
2023-11-13 00:57:20 -06:00
await this.stop();
2023-12-20 16:30:05 -08:00
this.agent.bot.emit("code_terminated");
2023-12-06 22:15:09 -06:00
return {success: false, message, interrupted, timedout: false};
2023-11-07 12:46:55 -06:00
}
}
2023-11-07 22:23:42 -06:00
2023-11-13 00:57:20 -06:00
formatOutput(bot) {
2023-12-08 16:18:20 -06:00
if (bot.interrupt_code && !this.timedout) return '';
2023-11-13 00:57:20 -06:00
let output = bot.output;
const MAX_OUT = 1000;
if (output.length > MAX_OUT) {
output = `Code output is very long (${output.length} chars) and has been shortened.\n
First outputs:\n${output.substring(0, MAX_OUT/2)}\n...skipping many lines.\nFinal outputs:\n ${output.substring(output.length - MAX_OUT/2)}`;
}
else {
output = 'Code output:\n' + output;
}
return output;
}
2023-11-13 00:57:20 -06:00
async stop() {
2023-12-06 22:15:09 -06:00
if (!this.executing) return;
2023-11-08 21:05:18 -08:00
while (this.executing) {
2023-11-13 11:09:37 -06:00
this.agent.bot.interrupt_code = true;
2023-11-08 21:05:18 -08:00
this.agent.bot.collectBlock.cancelTask();
this.agent.bot.pathfinder.stop();
2023-11-27 21:07:52 -06:00
this.agent.bot.pvp.stop();
2023-11-13 11:09:37 -06:00
console.log('waiting for code to finish executing... interrupt:', this.agent.bot.interrupt_code);
await new Promise(resolve => setTimeout(resolve, 1000));
2023-11-08 21:05:18 -08:00
}
this.clear();
2023-11-13 00:57:20 -06:00
}
clear() {
2023-11-07 22:23:42 -06:00
this.current_code = '';
2023-11-13 00:57:20 -06:00
this.agent.bot.output = '';
2023-11-13 11:09:37 -06:00
this.agent.bot.interrupt_code = false;
2023-12-06 22:15:09 -06:00
this.timedout = false;
}
_startTimeout(TIMEOUT_MINS=10) {
return setTimeout(async () => {
console.warn(`Code execution timed out after ${TIMEOUT_MINS} minutes. Attempting force stop.`);
this.timedout = true;
2023-12-08 16:18:20 -06:00
this.agent.bot.output += `\nAction performed for ${TIMEOUT_MINS} minutes and then timed out and stopped. You may want to continue or do something else.`;
2023-12-06 22:15:09 -06:00
this.stop(); // last attempt to stop
2023-12-08 16:18:20 -06:00
await new Promise(resolve => setTimeout(resolve, 5 * 1000)); // wait 5 seconds
2023-12-06 22:15:09 -06:00
if (this.executing) {
console.error(`Failed to stop. Killing process. Goodbye.`);
2023-12-09 21:40:53 -06:00
this.agent.bot.output += `\nForce stop failed! Process was killed and will be restarted. Goodbye world.`;
2023-12-12 13:39:35 -06:00
this.agent.bot.chat('Goodbye world.');
2023-12-08 16:18:20 -06:00
let output = this.formatOutput(this.agent.bot);
this.agent.history.add('system', output);
this.agent.history.save();
2023-12-06 22:15:09 -06:00
process.exit(1); // force exit program
}
console.log('Code execution stopped successfully.');
}, TIMEOUT_MINS*60*1000);
2023-11-07 22:23:42 -06:00
}
}