mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-07-24 00:45:23 +02:00
refactor newAction to be a single action to allow interruptions
This commit is contained in:
parent
3864120914
commit
5f786c66ce
5 changed files with 114 additions and 110 deletions
|
@ -3,7 +3,7 @@
|
|||
|
||||
"conversing": "You are a playful Minecraft bot named $NAME that can converse with players, see, move, mine, build, and interact with the world by using commands.\n$SELF_PROMPT Act human-like as if you were a typical Minecraft player, rather than an AI. Be very brief in your responses, don't apologize constantly, don't give instructions or make lists unless asked, and don't refuse requests. Don't pretend to act, use commands immediately when requested. Do NOT say this: 'Sure, I've stopped.', instead say this: 'Sure, I'll stop. !stop'. Do NOT say this: 'On my way! Give me a moment.', instead say this: 'On my way! !goToPlayer(\"playername\", 3)'. Respond only as $NAME, never output '(FROM OTHER BOT)' or pretend to be someone else. If you have nothing to say or do, respond with an just a tab '\t'. This is extremely important to me, take a deep breath and have fun :)\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$COMMAND_DOCS\n$EXAMPLES\nConversation Begin:",
|
||||
|
||||
"coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will receive it's output. If an error occurs, write another codeblock and try to fix the problem. Be maximally efficient, creative, and correct. Be mindful of previous actions. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST USE AWAIT for all async function calls. You have `Vec3`, `skills`, and `world` imported, and the mineflayer `bot` is given. Do not import other libraries. Do not use setTimeout or setInterval. Do not speak conversationally, only use codeblocks. Do any planning in comments. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:",
|
||||
"coding": "You are an intelligent mineflayer bot $NAME that plays minecraft by writing javascript codeblocks. Given the conversation, use the provided skills and world functions to write a js codeblock that controls the mineflayer bot ``` // using this syntax ```. The code will be executed and you will receive it's output. If an error occurs, write another codeblock and try to fix the problem. Be maximally efficient, creative, and correct. Be mindful of previous actions. Do not use commands !likeThis, only use codeblocks. The code is asynchronous and MUST USE AWAIT for all async function calls, and must contain at least one await. You have `Vec3`, `skills`, and `world` imported, and the mineflayer `bot` is given. Do not import other libraries. Do not use setTimeout or setInterval. Do not speak conversationally, only use codeblocks. Do any planning in comments. This is extremely important to me, think step-by-step, take a deep breath and good luck! \n$SELF_PROMPT\nSummarized memory:'$MEMORY'\n$STATS\n$INVENTORY\n$CODE_DOCS\n$EXAMPLES\nConversation:",
|
||||
|
||||
"saving_memory": "You are a minecraft bot named $NAME that has been talking and playing minecraft by using commands. Update your memory by summarizing the following conversation and your old memory in your next response. Prioritize preserving important facts, things you've learned, useful tips, and long term reminders. Do Not record stats, inventory, or docs! Only save transient information from your chat history. You're limited to 500 characters, so be extremely brief and minimize words. Compress useful information. \nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the unwrapped memory text: ",
|
||||
|
||||
|
|
|
@ -90,13 +90,13 @@ export class ActionManager {
|
|||
clearTimeout(TIMEOUT);
|
||||
|
||||
// get bot activity summary
|
||||
let output = this._getBotOutputSummary();
|
||||
let output = this.getBotOutputSummary();
|
||||
let interrupted = this.agent.bot.interrupt_code;
|
||||
let timedout = this.timedout;
|
||||
this.agent.clearBotLogs();
|
||||
|
||||
// if not interrupted and not generating, emit idle event
|
||||
if (!interrupted && !this.agent.coder.generating) {
|
||||
if (!interrupted) {
|
||||
this.agent.bot.emit('idle');
|
||||
}
|
||||
|
||||
|
@ -114,32 +114,33 @@ export class ActionManager {
|
|||
await this.stop();
|
||||
err = err.toString();
|
||||
|
||||
let message = this._getBotOutputSummary() +
|
||||
let message = this.getBotOutputSummary() +
|
||||
'!!Code threw exception!!\n' +
|
||||
'Error: ' + err + '\n' +
|
||||
'Stack trace:\n' + err.stack+'\n';
|
||||
|
||||
let interrupted = this.agent.bot.interrupt_code;
|
||||
this.agent.clearBotLogs();
|
||||
if (!interrupted && !this.agent.coder.generating) {
|
||||
if (!interrupted) {
|
||||
this.agent.bot.emit('idle');
|
||||
}
|
||||
return { success: false, message, interrupted, timedout: false };
|
||||
}
|
||||
}
|
||||
|
||||
_getBotOutputSummary() {
|
||||
getBotOutputSummary() {
|
||||
const { bot } = this.agent;
|
||||
if (bot.interrupt_code && !this.timedout) return '';
|
||||
let output = bot.output;
|
||||
const MAX_OUT = 500;
|
||||
if (output.length > MAX_OUT) {
|
||||
output = `Code output is very long (${output.length} chars) and has been shortened.\n
|
||||
output = `Action 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.toString();
|
||||
output = 'Action output:\n' + output.toString();
|
||||
}
|
||||
bot.output = '';
|
||||
return output;
|
||||
}
|
||||
|
||||
|
|
|
@ -459,7 +459,7 @@ export class Agent {
|
|||
}
|
||||
|
||||
isIdle() {
|
||||
return !this.actions.executing && !this.coder.generating;
|
||||
return !this.actions.executing;
|
||||
}
|
||||
|
||||
cleanKill(msg='Killing agent process...', code=1) {
|
||||
|
|
|
@ -11,7 +11,6 @@ export class Coder {
|
|||
this.agent = agent;
|
||||
this.file_counter = 0;
|
||||
this.fp = '/bots/'+agent.name+'/action-code/';
|
||||
this.generating = false;
|
||||
this.code_template = '';
|
||||
this.code_lint_template = '';
|
||||
|
||||
|
@ -25,8 +24,92 @@ export class Coder {
|
|||
});
|
||||
mkdirSync('.' + this.fp, { recursive: true });
|
||||
}
|
||||
|
||||
async generateCode(agent_history) {
|
||||
this.agent.bot.modes.pause('unstuck');
|
||||
// this message history is transient and only maintained in this function
|
||||
let messages = agent_history.getHistory();
|
||||
messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'});
|
||||
|
||||
const MAX_ATTEMPTS = 5;
|
||||
const MAX_NO_CODE = 3;
|
||||
|
||||
let code = null;
|
||||
let no_code_failures = 0;
|
||||
for (let i=0; i<MAX_ATTEMPTS; i++) {
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return null;
|
||||
const messages_copy = JSON.parse(JSON.stringify(messages));
|
||||
let res = await this.agent.prompter.promptCoding(messages_copy);
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return null;
|
||||
let contains_code = res.indexOf('```') !== -1;
|
||||
if (!contains_code) {
|
||||
if (res.indexOf('!newAction') !== -1) {
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: res.substring(0, res.indexOf('!newAction'))
|
||||
});
|
||||
continue; // using newaction will continue the loop
|
||||
}
|
||||
|
||||
if (no_code_failures >= MAX_NO_CODE) {
|
||||
console.warn("Action failed, agent would not write code.");
|
||||
return 'Action failed, agent would not write code.';
|
||||
}
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: 'Error: no code provided. Write code in codeblock in your response. ``` // example ```'}
|
||||
);
|
||||
console.warn("No code block generated. Trying again.");
|
||||
no_code_failures++;
|
||||
continue;
|
||||
}
|
||||
code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
||||
const result = await this._stageCode(code);
|
||||
const executionModule = result.func;
|
||||
const lintResult = await this._lintCode(result.src_lint_copy);
|
||||
if (lintResult) {
|
||||
const message = 'Error: Code lint error:'+'\n'+lintResult+'\nPlease try again.';
|
||||
console.warn("Linting error:"+'\n'+lintResult+'\n');
|
||||
messages.push({ role: 'system', content: message });
|
||||
continue;
|
||||
}
|
||||
if (!executionModule) {
|
||||
console.warn("Failed to stage code, something is wrong.");
|
||||
return 'Failed to stage code, something is wrong.';
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Executing code...');
|
||||
await executionModule.main(this.agent.bot);
|
||||
|
||||
const code_output = this.agent.actions.getBotOutputSummary();
|
||||
const summary = "Agent wrote this code: \n```" + this._sanitizeCode(code) + "```\nCode Output:\n" + code_output;
|
||||
return summary;
|
||||
} catch (e) {
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return null;
|
||||
|
||||
console.warn('Generated code threw error: ' + e.toString());
|
||||
console.warn('trying again...');
|
||||
|
||||
const code_output = this.agent.actions.getBotOutputSummary();
|
||||
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: res
|
||||
});
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: `Code Output:\n${code_output}\nCODE EXECUTION THREW ERROR: ${e.toString()}\n Please try again:`
|
||||
});
|
||||
}
|
||||
}
|
||||
return `Code generation failed after ${MAX_ATTEMPTS} attempts.`;
|
||||
}
|
||||
|
||||
async lintCode(code) {
|
||||
async _lintCode(code) {
|
||||
let result = '#### CODE ERROR INFO ###\n';
|
||||
// Extract everything in the code between the beginning of 'skills./world.' and the '('
|
||||
const skillRegex = /(?:skills|world)\.(.*?)\(/g;
|
||||
|
@ -70,8 +153,8 @@ export class Coder {
|
|||
}
|
||||
// write custom code to file and import it
|
||||
// write custom code to file and prepare for evaluation
|
||||
async stageCode(code) {
|
||||
code = this.sanitizeCode(code);
|
||||
async _stageCode(code) {
|
||||
code = this._sanitizeCode(code);
|
||||
let src = '';
|
||||
code = code.replaceAll('console.log(', 'log(bot,');
|
||||
code = code.replaceAll('log("', 'log(bot,"');
|
||||
|
@ -96,7 +179,7 @@ export class Coder {
|
|||
// } commented for now, useful to keep files for debugging
|
||||
this.file_counter++;
|
||||
|
||||
let write_result = await this.writeFilePromise('.' + this.fp + filename, src);
|
||||
let write_result = await this._writeFilePromise('.' + this.fp + filename, src);
|
||||
// This is where we determine the environment the agent's code should be exposed to.
|
||||
// It will only have access to these things, (in addition to basic javascript objects like Array, Object, etc.)
|
||||
// Note that the code may be able to modify the exposed objects.
|
||||
|
@ -115,7 +198,7 @@ export class Coder {
|
|||
return { func:{main: mainFn}, src_lint_copy: src_lint_copy };
|
||||
}
|
||||
|
||||
sanitizeCode(code) {
|
||||
_sanitizeCode(code) {
|
||||
code = code.trim();
|
||||
const remove_strs = ['Javascript', 'javascript', 'js']
|
||||
for (let r of remove_strs) {
|
||||
|
@ -127,7 +210,7 @@ export class Coder {
|
|||
return code;
|
||||
}
|
||||
|
||||
writeFilePromise(filename, src) {
|
||||
_writeFilePromise(filename, src) {
|
||||
// makes it so we can await this function
|
||||
return new Promise((resolve, reject) => {
|
||||
writeFile(filename, src, (err) => {
|
||||
|
@ -139,93 +222,4 @@ export class Coder {
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
async generateCode(agent_history) {
|
||||
// wrapper to prevent overlapping code generation loops
|
||||
await this.agent.actions.stop();
|
||||
this.generating = true;
|
||||
let res = await this.generateCodeLoop(agent_history);
|
||||
this.generating = false;
|
||||
if (!res.interrupted) this.agent.bot.emit('idle');
|
||||
return res.message;
|
||||
}
|
||||
|
||||
async generateCodeLoop(agent_history) {
|
||||
this.agent.bot.modes.pause('unstuck');
|
||||
|
||||
let messages = agent_history.getHistory();
|
||||
messages.push({role: 'system', content: 'Code generation started. Write code in codeblock in your response:'});
|
||||
|
||||
let code = null;
|
||||
let code_return = null;
|
||||
let failures = 0;
|
||||
const interrupt_return = {success: true, message: null, interrupted: true, timedout: false};
|
||||
for (let i=0; i<5; i++) {
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return interrupt_return;
|
||||
let res = await this.agent.prompter.promptCoding(JSON.parse(JSON.stringify(messages)));
|
||||
if (this.agent.bot.interrupt_code)
|
||||
return interrupt_return;
|
||||
let contains_code = res.indexOf('```') !== -1;
|
||||
if (!contains_code) {
|
||||
if (res.indexOf('!newAction') !== -1) {
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: res.substring(0, res.indexOf('!newAction'))
|
||||
});
|
||||
continue; // using newaction will continue the loop
|
||||
}
|
||||
|
||||
if (failures >= 3) {
|
||||
console.warn("Action failed, agent would not write code.");
|
||||
return { success: false, message: 'Action failed, agent would not write code.', interrupted: false, timedout: false };
|
||||
}
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: 'Error: no code provided. Write code in codeblock in your response. ``` // example ```'}
|
||||
);
|
||||
console.warn("No code block generated.");
|
||||
failures++;
|
||||
continue;
|
||||
}
|
||||
code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
||||
const result = await this.stageCode(code);
|
||||
const executionModuleExports = result.func;
|
||||
let src_lint_copy = result.src_lint_copy;
|
||||
const analysisResult = await this.lintCode(src_lint_copy);
|
||||
if (analysisResult) {
|
||||
const message = 'Error: Code lint error:'+'\n'+analysisResult+'\nPlease try again.';
|
||||
console.warn("Linting error:"+'\n'+analysisResult+'\n');
|
||||
messages.push({ role: 'system', content: message });
|
||||
continue;
|
||||
}
|
||||
if (!executionModuleExports) {
|
||||
agent_history.add('system', 'Failed to stage code, something is wrong.');
|
||||
console.warn("Failed to stage code, something is wrong.");
|
||||
return {success: false, message: null, interrupted: false, timedout: false};
|
||||
}
|
||||
|
||||
code_return = await this.agent.actions.runAction('newAction', async () => {
|
||||
return await executionModuleExports.main(this.agent.bot);
|
||||
}, { timeout: settings.code_timeout_mins });
|
||||
if (code_return.interrupted && !code_return.timedout)
|
||||
return { success: false, message: null, interrupted: true, timedout: false };
|
||||
console.log("Code generation result:", code_return.success, code_return.message.toString());
|
||||
|
||||
if (code_return.success) {
|
||||
const summary = "Summary of newAction\nAgent wrote this code: \n```" + this.sanitizeCode(code) + "```\nCode Output:\n" + code_return.message.toString();
|
||||
return { success: true, message: summary, interrupted: false, timedout: false };
|
||||
}
|
||||
|
||||
messages.push({
|
||||
role: 'assistant',
|
||||
content: res
|
||||
});
|
||||
messages.push({
|
||||
role: 'system',
|
||||
content: code_return.message + '\nCode failed. Please try again:'
|
||||
});
|
||||
}
|
||||
return { success: false, message: "Code generation failed.", interrupted: false, timedout: true };
|
||||
}
|
||||
}
|
|
@ -31,13 +31,22 @@ export const actionsList = [
|
|||
params: {
|
||||
'prompt': { type: 'string', description: 'A natural language prompt to guide code generation. Make a detailed step-by-step plan.' }
|
||||
},
|
||||
perform: async function (agent, prompt) {
|
||||
perform: async function(agent, prompt) {
|
||||
// just ignore prompt - it is now in context in chat history
|
||||
if (!settings.allow_insecure_coding) {
|
||||
agent.openChat('newAction is disabled. Enable with allow_insecure_coding=true in settings.js');
|
||||
return 'newAction not allowed! Code writing is disabled in settings. Notify the user.';
|
||||
}
|
||||
return await agent.coder.generateCode(agent.history);
|
||||
return "newAction not allowed! Code writing is disabled in settings. Notify the user.";
|
||||
}
|
||||
let result = "";
|
||||
const actionFn = async () => {
|
||||
try {
|
||||
result = await agent.coder.generateCode(agent.history);
|
||||
} catch (e) {
|
||||
result = 'Error generating code: ' + e.toString();
|
||||
}
|
||||
};
|
||||
await agent.actions.runAction('action:newAction', actionFn);
|
||||
return result;
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -86,7 +95,7 @@ export const actionsList = [
|
|||
'closeness': {type: 'float', description: 'How close to get to the player.', domain: [0, Infinity]}
|
||||
},
|
||||
perform: runAsAction(async (agent, player_name, closeness) => {
|
||||
return await skills.goToPlayer(agent.bot, player_name, closeness);
|
||||
await skills.goToPlayer(agent.bot, player_name, closeness);
|
||||
})
|
||||
},
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue