refactored to SelfPrompter class and interruption logic

This commit is contained in:
MaxRobinsonTheGreat 2024-05-04 15:42:30 -05:00
parent d1890a3a3b
commit c7b115ebbf
4 changed files with 131 additions and 87 deletions

View file

@ -6,6 +6,7 @@ import { initBot } from '../utils/mcdata.js';
import { containsCommand, commandExists, executeCommand, truncCommandMessage, isAction } from './commands/index.js';
import { NPCContoller } from './npc/controller.js';
import { MemoryBank } from './memory_bank.js';
import { SelfPrompter } from './self_prompter.js';
export class Agent {
@ -16,6 +17,7 @@ export class Agent {
this.coder = new Coder(this);
this.npc = new NPCContoller(this);
this.memory_bank = new MemoryBank();
this.self_prompter = new SelfPrompter(this);
await this.prompter.initExamples();
@ -27,10 +29,6 @@ export class Agent {
if (load_mem)
this.history.load();
this.auto_prompting = false;
this.last_user_prompted_action = 0;
this.interrupt_auto_prompt = false;
this.bot.once('spawn', async () => {
// wait for a bit so stats are not undefined
await new Promise((resolve) => setTimeout(resolve, 1000));
@ -103,19 +101,14 @@ export class Agent {
}
if (execute_res)
this.cleanChat(execute_res);
if (isAction(user_command_name)) {
this.last_user_prompted_action = Date.now();
if (this.auto_prompting) {
this.interrupt_auto_prompt = true;
this.bot.chat('User initiated action. Stopping auto-prompting.');
}
}
return true;
}
}
const MAX_ATTEMPTS = 5;
let MAX_ATTEMPTS = 5;
if (!self_prompt && this.self_prompter.on)
MAX_ATTEMPTS = 1; // immediately respond to this message, then let self-prompting take over
for (let i=0; i<MAX_ATTEMPTS; i++) {
if (self_prompt && this.interrupt_auto_prompt) break;
if (self_prompt && this.self_prompter.on && this.self_prompter.interrupt) break;
let history = this.history.getHistory();
let res = await this.prompter.promptConvo(history);
@ -127,24 +120,27 @@ export class Agent {
this.history.add(this.name, res);
if (!commandExists(command_name)) {
this.history.add('system', `Command ${command_name} does not exist. Use !newAction to perform custom actions.`);
console.log('Agent hallucinated command:', command_name)
console.warn('Agent hallucinated command:', command_name)
continue;
}
let pre_message = res.substring(0, res.indexOf(command_name)).trim();
let chat_message = `*used ${command_name.substring(1)}*`;
if (pre_message.length > 0)
chat_message = `${pre_message} ${chat_message}`;
this.cleanChat(chat_message);
if (!self_prompt && isAction(command_name)) {
console.log('User initiated action.');
this.last_user_prompted_action = Date.now();
if (this.auto_prompting) {
this.interrupt_auto_prompt = true;
this.bot.chat('User initiated action. Stopping auto-prompting.');
}
if (command_name === '!stopSelfPrompt' && self_prompt) {
this.history.add('system', `Cannot stopSelfPrompt unless requested by user.`);
continue;
}
// let pre_message = res.substring(0, res.indexOf(command_name)).trim();
// let chat_message = `*used ${command_name.substring(1)}*`;
// if (pre_message.length > 0)
// chat_message = `${pre_message} ${chat_message}`;
this.cleanChat(res);
if (self_prompt && this.self_prompter.on && this.self_prompter.interrupt) break;
if (isAction(command_name) && !self_prompt && this.self_prompter.on) {
this.self_prompter.stopLoop(); // so agent doesn't respond from self-prompting loop
// will be automatically restarted by self-prompter
}
if (self_prompt && this.interrupt_auto_prompt) break;
let execute_res = await executeCommand(this, res);
@ -169,51 +165,6 @@ export class Agent {
return used_command;
}
async autoPrompt(prompt) {
if (this.auto_prompting) {
return 'Agent is already auto-prompting. Ignoring request.';
}
this.auto_prompting = true;
let first = true;
let no_command_count = 0;
const MAX_NO_COMMAND = 3;
while (!this.interrupt_auto_prompt) {
let msg;
if (first) {
msg = prompt;
first = false;
}
else {
msg = `You are self-prompting with the intial message: '${prompt}'. Your next response MUST contain a command !likeThis. Respond:`;
}
let used_command = await this.handleMessage('system', msg, true);
if (!used_command) {
no_command_count++;
if (no_command_count >= MAX_NO_COMMAND) {
let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`;
this.bot.chat(out);
console.warn(out);
break;
}
}
else
no_command_count = 0;
}
this.auto_prompting = false;
this.interrupt_auto_prompt = false;
return 'Auto-prompting finished.';
}
async waitStopAutoPrompt() {
while (this.auto_prompting) {
this.interrupt_auto_prompt = true;
await new Promise((resolve) => setTimeout(resolve, 500));
}
this.interrupt_auto_prompt = false;
}
startEvents() {
// Custom events
this.bot.on('time', () => {
@ -270,20 +221,27 @@ export class Agent {
// This update loop ensures that each update() is called one at a time, even if it takes longer than the interval
const INTERVAL = 300;
let last = Date.now();
setTimeout(async () => {
while (true) {
let start = Date.now();
await this.bot.modes.update();
await this.update(start - last);
let remaining = INTERVAL - (Date.now() - start);
if (remaining > 0) {
await new Promise((resolve) => setTimeout(resolve, remaining));
}
last = start;
}
}, INTERVAL);
this.bot.emit('idle');
}
async update(delta) {
await this.bot.modes.update();
await this.self_prompter.update(delta);
}
isIdle() {
return !this.coder.executing && !this.coder.generating;
}

View file

@ -37,7 +37,10 @@ export const actionsList = [
agent.coder.clear();
agent.coder.cancelResume();
agent.bot.emit('idle');
return 'Agent stopped.';
let msg = 'Agent stopped.';
if (agent.self_prompter.on)
msg += ' Self-prompting still active.';
return msg;
}
},
{
@ -241,7 +244,15 @@ export const actionsList = [
'prompt': '(string) The starting prompt.',
},
perform: async function (agent, prompt) {
agent.autoPrompt(prompt); // don't await, don't return
agent.self_prompter.start(prompt); // don't await, don't return
}
},
{
name: '!stopSelfPrompt',
description: 'Stop current action and self-prompting.',
perform: async function (agent) {
agent.self_prompter.stop(); // will stop and
return 'Self-prompting stopped.';
}
}
];

View file

@ -202,22 +202,13 @@ const modes = [
];
async function execute(mode, agent, func, timeout=-1) {
let was_auto_prompting = agent.auto_prompting;
if (was_auto_prompting) agent.interrupt_auto_prompt = true;
if (agent.self_prompter.on)
agent.self_prompter.stopLoop();
mode.active = true;
let code_return = await agent.coder.execute(async () => {
await func();
}, timeout);
mode.active = false;
if (was_auto_prompting) {
setTimeout(() => { // don't await
if (agent.isIdle()) { // not gonna work if another action is triggered because auto_prompting will be false. need to fix
let output = agent.bot.output;
let reprompt = `Agent was interrupted by ${mode.name} mode. Code output: "${output}". Continue self prompting:`;
agent.autoPrompt(reprompt);
}
}, 1000); // wait 1 second before re-enabling auto_prompt bc often another action will be triggered
}
console.log(`Mode ${mode.name} finished executing, code_return: ${code_return.message}`);
}

View file

@ -0,0 +1,84 @@
export class SelfPrompter {
constructor(agent) {
this.agent = agent;
this.on = false;
this.loop_active = false;
this.interrupt = false;
this.prompt = '';
this.idle_time = 0;
this.restart_after = 1000;
}
start(prompt) {
console.log('Self-prompting started.');
if (this.on) {
return 'Agent is already self-prompting. Ignoring request.';
}
this.on = true;
this.prompt = prompt;
this.startLoop();
}
async startLoop() {
if (this.loop_active) {
console.warn('Self-prompt loop is already active. Ignoring request.');
return;
}
console.log('starting self-prompt loop')
this.loop_active = true;
let no_command_count = 0;
const MAX_NO_COMMAND = 3;
while (!this.interrupt) {
let msg = `You are self-prompting with the prompt: '${this.prompt}'. Your next response MUST contain a command !withThisSyntax. Respond:`;
let used_command = await this.agent.handleMessage('system', msg, true);
if (!used_command) {
no_command_count++;
if (no_command_count >= MAX_NO_COMMAND) {
let out = `Agent did not use command in the last ${MAX_NO_COMMAND} auto-prompts. Stopping auto-prompting.`;
this.agent.bot.chat(out);
console.warn(out);
this.on = false;
break;
}
}
else
no_command_count = 0;
}
console.log('self prompt loop stopped')
this.loop_active = false;
this.interrupt = false;
}
update(delta) {
// automatically restarts loop
if (this.on && !this.loop_active && !this.interrupt) {
if (this.agent.isIdle())
this.idle_time += delta;
else
this.idle_time = 0;
if (this.idle_time >= this.restart_after) {
this.agent.bot.chat('Restarting self-prompting...');
this.startLoop();
this.idle_time = 0;
}
}
}
async stopLoop() {
// you can call this without await if you don't need to wait for it to finish
this.interrupt = true;
while (this.loop_active) {
await new Promise(r => setTimeout(r, 500));
}
this.interrupt = false;
}
async stop() {
this.interrupt = true;
await this.agent.coder.stop();
await this.stopLoop();
this.on = false;
}
}