diff --git a/src/agent/agent.js b/src/agent/agent.js index 534d582..a4181c3 100644 --- a/src/agent/agent.js +++ b/src/agent/agent.js @@ -1,26 +1,31 @@ import { initBot } from '../utils/mcdata.js'; import { sendRequest } from '../utils/gpt.js'; import { History } from './history.js'; +import { Examples } from './examples.js'; import { Coder } from './coder.js'; import { containsCommand, commandExists, executeCommand } from './commands.js'; import { Events } from './events.js'; export class Agent { - constructor(name, profile=null, init_message=null) { + async start(name, profile=null, init_message=null) { this.name = name; - this.bot = initBot(name); + this.examples = new Examples(); this.history = new History(this); this.coder = new Coder(this); this.history.load(profile); + await this.examples.load('./src/examples.json'); + await this.coder.load(); + + this.bot = initBot(name); this.events = new Events(this, this.history.events) this.bot.on('login', async () => { - await this.history.loadExamples(); console.log(`${this.name} logged in.`); + this.coder.clear(); const ignore_messages = [ "Set own game mode to", @@ -76,7 +81,8 @@ export class Agent { } for (let i=0; i<5; i++) { - let res = await sendRequest(this.history.getHistory(), this.history.getSystemMessage()); + let history = await this.history.getHistory(this.examples); + let res = await sendRequest(history, this.history.getSystemMessage()); this.history.add(this.name, res); let command_name = containsCommand(res); @@ -85,6 +91,7 @@ export class Agent { console.log('Command message:', 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) continue; } diff --git a/src/agent/coder.js b/src/agent/coder.js index ed4e15b..264f39b 100644 --- a/src/agent/coder.js +++ b/src/agent/coder.js @@ -1,7 +1,7 @@ import { writeFile, readFile, mkdirSync } from 'fs'; -import { sendRequest, embed, cosineSimilarity } from '../utils/gpt.js'; -import { stringifyTurns } from '../utils/text.js'; +import { sendRequest } from '../utils/gpt.js'; import { getSkillDocs } from './skill-library.js'; +import { Examples } from './examples.js'; export class Coder { @@ -11,13 +11,14 @@ export class Coder { this.current_code = ''; this.file_counter = 0; this.fp = '/bots/'+agent.name+'/action-code/'; - this.agent.bot.interrupt_code = false; this.executing = false; - this.agent.bot.output = ''; this.code_template = ''; this.timedout = false; - this.fewshot = 3; - this.examples = []; + } + + async load() { + this.examples = new Examples(); + await this.examples.load('./src/examples_coder.json'); readFile('./bots/template.js', 'utf8', (err, data) => { if (err) throw err; @@ -62,7 +63,7 @@ export class Coder { system_message += "\n\nExamples:\nUser zZZn98: come here \nAssistant: I am going to navigate to zZZn98. ```\nawait skills.goToPlayer(bot, 'zZZn98');```\nSystem: Code execution finished successfully.\nAssistant: Done."; - let messages = agent_history.getHistory(false); + let messages = await agent_history.getHistory(this.examples); let code_return = null; let failures = 0; diff --git a/src/agent/commands.js b/src/agent/commands.js index 2944b18..0b13775 100644 --- a/src/agent/commands.js +++ b/src/agent/commands.js @@ -18,7 +18,7 @@ const argRegex = /(?:"[^"]*"|'[^']*'|[^,])+/g; export function containsCommand(message) { const commandMatch = message.match(commandRegex); if (commandMatch) - return commandName; + return "!" + commandMatch[1]; return null; } diff --git a/src/agent/examples.js b/src/agent/examples.js new file mode 100644 index 0000000..1f2a3e1 --- /dev/null +++ b/src/agent/examples.js @@ -0,0 +1,64 @@ +import { readFileSync } from 'fs'; +import { embed, cosineSimilarity } from '../utils/gpt.js'; +import { stringifyTurns } from '../utils/text.js'; + + +export class Examples { + constructor(select_num=3) { + this.examples = []; + this.select_num = select_num; + } + + async load(path) { + let examples = []; + try { + const data = readFileSync(path, 'utf8'); + examples = JSON.parse(data); + } catch (err) { + console.error('Examples failed to load!', err); + } + + this.examples = []; + for (let example of examples) { + let messages = ''; + for (let turn of example) { + if (turn.role != 'assistant') + messages += turn.content.substring(turn.content.indexOf(':')+1).trim() + '\n'; + } + messages = messages.trim(); + const embedding = await embed(messages); + this.examples.push({'embedding': embedding, 'turns': example}); + } + } + + async getRelevant(turns) { + let messages = ''; + for (let turn of turns) { + if (turn.role != 'assistant') + messages += turn.content.substring(turn.content.indexOf(':')+1).trim() + '\n'; + } + messages = messages.trim(); + const embedding = await embed(messages); + this.examples.sort((a, b) => { + return cosineSimilarity(b.embedding, embedding) - cosineSimilarity(a.embedding, embedding); + }); + let selected = this.examples.slice(0, this.select_num); + return JSON.parse(JSON.stringify(selected)); // deep copy + } + + async createExampleMessage(turns) { + let selected_examples = await this.getRelevant(turns); + + console.log('selected examples:'); + for (let example of selected_examples) { + console.log(example.turns[0]) + } + + let msg = 'Here are some examples of how to respond:\n'; + for (let i=0; i 0) { - let example_messages = 'Here are some examples of how to respond:\n'; - for (let example of this.selected_examples) { - example_messages += 'Example:\n' + stringifyTurns(example.turns) + '\n'; - } - return [{'role': 'system', 'content': example_messages}].concat(turns); + if (examples) { + let examples_msg = await examples.createExampleMessage(turns); + turns = examples_msg.concat(turns); } + return turns; } @@ -69,47 +63,6 @@ export class History { this.memory = await sendRequest(memory_turns, this.getSystemMessage()); } - 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 messages = ''; - for (let turn of example) { - if (turn.role != 'assistant') - messages += turn.content.substring(turn.content.indexOf(':')+1).trim() + '\n'; - } - messages = messages.trim(); - const embedding = await embed(messages); - this.examples.push({'embedding': embedding, 'turns': example}); - } - - await this.setExamples(); - } - - async setExamples() { - let messages = ''; - for (let turn of this.turns) { - if (turn.role != 'assistant') - messages += turn.content.substring(turn.content.indexOf(':')+1).trim() + '\n'; - } - messages = messages.trim(); - const embedding = await embed(messages); - this.examples.sort((a, b) => { - return cosineSimilarity(a.embedding, embedding) - cosineSimilarity(b.embedding, embedding); - }); - this.selected_examples = this.examples.slice(-this.fewshot); - for (let example of this.selected_examples) { - console.log('selected example: ', example.turns[0].content); - } - } - async add(name, content) { let role = 'assistant'; if (name === 'system') { @@ -129,9 +82,6 @@ export class History { to_summarize.push(this.turns.shift()); await this.storeMemories(to_summarize); } - - if (role != 'assistant') - await this.setExamples(); } save() { diff --git a/src/examples_coder.json b/src/examples_coder.json index ad332f6..fd73422 100644 --- a/src/examples_coder.json +++ b/src/examples_coder.json @@ -1,52 +1,34 @@ [ - { - "conversation": [ - {"role": "user", "content": "greg: Collect 10 wood"}, - {"role": "assistant", "content": "Let me see what's nearby... !blocks"}, - {"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"} - ], - "coder": [ - {"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs.\n```\n// I am going to collect 10 oak_log.\nawait skills.collectBlock(bot, 'oak_log', 10);\n```"}, - {"role": "user", "content": "Code execution finished successfully."}, - {"role": "assistant", "content": "DONE"} - ] - }, - - { - "conversation": [ - {"role": "user", "content": "bobby: Craft a plank"}, - {"role": "assistant", "content": "Okay! !craftable"}, - {"role": "system", "content": "CRAFTABLE_ITEMS\n- spruce_planks\n- spruce_wood\n- ladder"} - ], - "coder": [ - {"role": "assistant", "content": "```\n// I am going to craft a spruce_planks.\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"}, - {"role": "user", "content": "Code execution finished successfully."}, - {"role": "assistant", "content": "DONE"} - ] - }, - - { - "conversation": [ - {"role": "user", "content": "zZZn98: come here"} - ], - "coder": [ - {"role": "assistant", "content": "```\n// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, 'zZZn98');\n```"}, - {"role": "user", "content": "Code execution finished successfully."}, - {"role": "assistant", "content": "DONE"} - ] - }, - - { - "conversation": [ - {"role": "user", "content": "maya: go to the nearest oak log"} - ], - "coder": [ - {"role": "assistant", "content": "```\n// I am navigating to the nearest oak log.\nawait skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"}, - {"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."}, - {"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"}, - {"role": "user", "content": "Code execution finished successfully."}, - {"role": "assistant", "content": "DONE"} - ] - } - + [ + {"role": "user", "content": "greg: Collect 10 wood"}, + {"role": "assistant", "content": "```await skills.collectBlock(bot, 'oak_log', 10);\n```"}, + {"role": "system", "content": "Successfully collected 9 wood."}, + {"role": "assistant", "content": "I collected 9 oak logs, what next?"} + ], + [ + {"role": "user", "content": "bobby: Craft a plank"}, + {"role": "assistant", "content": "```\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"}, + {"role": "system", "content": "Successfully crafted 4 spruce_planks."}, + {"role": "assistant", "content": "I crafted 4 spruce planks!"} + ], + [ + {"role": "user", "content": "zZZn98: come here"}, + {"role": "assistant", "content": "```await skills.goToPlayer(bot, 'zZZn98');\n```"}, + {"role": "system", "content": "Code execution finished successfully."}, + {"role": "assistant", "content": "Here!"} + ], + [ + {"role": "user", "content": "maya: go to the nearest oak log"}, + {"role": "assistant", "content": "```await skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"}, + {"role": "system", "content": "Code output:\n!!Code threw exception!! Error: ReferenceError: nearestOakLog is not defined\n Write code to fix the problem and try again."}, + {"role": "assistant", "content": "Apologies, I made an error. Let me try that again.```\n// I am navigating to the nearest oak log.\nlet position = world.getNearestBlock(bot, 'oak_log', 20);\nawait skills.goToPosition(bot, position.x, position.y, position.z);\n```"}, + {"role": "system", "content": "Arrived at location."}, + {"role": "assistant", "content": "I found an oak log and I am now standing next to it. What next?"} + ], + [ + {"role": "user", "content": "234jeb: build a little tower"}, + {"role": "assistant", "content": "```let pos = bot.entity.position;\nfor (let i = 0; i < 5; i++) {\n await skills.placeBlock(bot, 'dirt', pos.x, pos.y + i, pos.z);\n}\n```"}, + {"role": "system", "content": "Successfully placed 5 dirt."}, + {"role": "assistant", "content": "I built a little tower!"} + ] ] \ No newline at end of file diff --git a/src/process/init-agent.js b/src/process/init-agent.js index 584f1bb..5eaadbb 100644 --- a/src/process/init-agent.js +++ b/src/process/init-agent.js @@ -20,4 +20,4 @@ const argv = yargs(args) }).argv const name = args[0]; -new Agent(name, argv.profile, argv.init_message); +new Agent().start(name, argv.profile, argv.init_message);