mirror of
https://github.com/kolbytn/mindcraft.git
synced 2025-04-23 14:42:07 +02:00
init commands
This commit is contained in:
parent
c78ace3873
commit
b3fc11cfec
8 changed files with 312 additions and 59 deletions
|
@ -3,7 +3,7 @@ import { sendRequest } from '../utils/gpt.js';
|
|||
import { History } from './history.js';
|
||||
import { Coder } from './coder.js';
|
||||
import { getQuery, containsQuery } from './queries.js';
|
||||
import { containsCodeBlock } from './skill-library.js';
|
||||
import { getCommand, containsCommand } from './commands.js';
|
||||
import { Events } from './events.js';
|
||||
|
||||
|
||||
|
@ -57,36 +57,27 @@ export class Agent {
|
|||
for (let i=0; i<5; i++) {
|
||||
let res = await sendRequest(this.history.getHistory(), this.history.getSystemMessage());
|
||||
this.history.add(this.name, res);
|
||||
let query_cmd = containsQuery(res);
|
||||
if (query_cmd) { // contains query
|
||||
let message = res.substring(0, res.indexOf(query_cmd)).trim();
|
||||
|
||||
let query_name = containsQuery(res);
|
||||
let command_name = containsCommand(res);
|
||||
|
||||
if (query_name || command_name) { // contains query or command
|
||||
console.log('Query/Command response:', res);
|
||||
|
||||
let execute_name = query_name ? query_name : command_name;
|
||||
let message = res.substring(0, res.indexOf(execute_name)).trim();
|
||||
if (message)
|
||||
this.bot.chat(message);
|
||||
let query = getQuery(query_cmd);
|
||||
let query_res = query.perform(this);
|
||||
console.log('Agent used query:', query_cmd, 'and got:', query_res)
|
||||
this.history.add('system', query_res);
|
||||
}
|
||||
else if (containsCodeBlock(res)) { // contains code block
|
||||
console.log('Agent is executing code:', res)
|
||||
|
||||
let message = res.substring(0, res.indexOf('```')).trim();
|
||||
if (message)
|
||||
this.bot.chat(message);
|
||||
let code = res.substring(res.indexOf('```')+3, res.lastIndexOf('```'));
|
||||
let execute_func = query_name ? getQuery(query_name) : getCommand(command_name);
|
||||
let execute_res = await execute_func.perform(this);
|
||||
|
||||
if (code) {
|
||||
this.coder.queueCode(code);
|
||||
let code_return = await this.coder.execute();
|
||||
let message = code_return.message;
|
||||
if (code_return.interrupted && !code_return.timedout)
|
||||
console.log('Agent executed:', execute_name, 'and got:', execute_res);
|
||||
|
||||
if (execute_res)
|
||||
this.history.add('system', execute_res);
|
||||
else
|
||||
break;
|
||||
if (!code_return.success) {
|
||||
message += "\nWrite code to fix the problem and try again.";
|
||||
}
|
||||
console.log('code return:', message);
|
||||
this.history.add('system', message);
|
||||
}
|
||||
}
|
||||
else { // conversation response
|
||||
this.bot.chat(res);
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import { writeFile, readFile, unlink, mkdirSync } from 'fs';
|
||||
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) {
|
||||
|
@ -12,6 +15,8 @@ export class Coder {
|
|||
this.agent.bot.output = '';
|
||||
this.code_template = '';
|
||||
this.timedout = false;
|
||||
this.fewshot = 3;
|
||||
this.examples = [];
|
||||
|
||||
readFile('./bots/template.js', 'utf8', (err, data) => {
|
||||
if (err) throw err;
|
||||
|
@ -49,6 +54,92 @@ export class Coder {
|
|||
});
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// returns {success: bool, message: string, interrupted: bool, timedout: false}
|
||||
async execute() {
|
||||
|
|
119
src/agent/commands.js
Normal file
119
src/agent/commands.js
Normal file
|
@ -0,0 +1,119 @@
|
|||
import * as skills from './skills.js';
|
||||
import * as world from './world.js';
|
||||
|
||||
|
||||
function wrapExecution(func) {
|
||||
return async function (agent) {
|
||||
agent.bot.output = '';
|
||||
agent.coder.executing = true;
|
||||
let res = await func(agent);
|
||||
if (res)
|
||||
agent.bot.output += '\n' + res;
|
||||
agent.coder.executing = false;
|
||||
return '\n' + agent.bot.output + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
const commandList = [
|
||||
{
|
||||
name: '!execute_action',
|
||||
description: 'Write and execute code to perform custom behaviors not available as a command.',
|
||||
perform: async function (agent) {
|
||||
let res = await agent.coder.generateCode(agent.history);
|
||||
if (res)
|
||||
return '\n' + res + '\n';
|
||||
}
|
||||
},
|
||||
{
|
||||
name: '!abort',
|
||||
description: 'Force stop all actions and commands that are currently executing.',
|
||||
perform: async function (agent) {
|
||||
await agent.coder.stop();
|
||||
}
|
||||
},
|
||||
// {
|
||||
// name: '!gotoplayer',
|
||||
// description: 'Go to the nearest player.',
|
||||
// perform: wrapExecution(async (agent) => {
|
||||
// let player_name = world.getNearbyPlayerNames(agent.bot);
|
||||
// if (player_name.length == 0)
|
||||
// return 'No players nearby.';
|
||||
// await skills.goToPlayer(agent.bot, player_name[0]);
|
||||
// })
|
||||
// },
|
||||
// {
|
||||
// name: '!followplayer',
|
||||
// description: 'Follow the nearest player.',
|
||||
// perform: wrapExecution(async (agent) => {
|
||||
// let player_name = world.getNearbyPlayerNames(agent.bot);
|
||||
// if (player_name.length == 0)
|
||||
// return 'No players nearby.';
|
||||
// await skills.followPlayer(agent.bot, player_name[0]);
|
||||
// })
|
||||
// },
|
||||
// {
|
||||
// name: '!collectwood',
|
||||
// description: 'Collect 3 wood logs of any type.',
|
||||
// perform: wrapExecution(async (agent) => {
|
||||
// let blocks = world.getNearbyBlockTypes(agent.bot);
|
||||
// for (let block of blocks) {
|
||||
// if (block.includes('log')) {
|
||||
// await skills.collectBlock(agent.bot, block, 3);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// return 'No wood nearby.';
|
||||
// })
|
||||
// },
|
||||
// {
|
||||
// name: '!collectstone',
|
||||
// description: 'Collect 3 cobblestone blocks.',
|
||||
// perform: wrapExecution(async (agent) => {
|
||||
// let inventory = world.getInventoryCounts(agent.bot);
|
||||
// for (const item in inventory) {
|
||||
// if (inventory[item] && inventory[item] > 0 && item.includes('pickaxe')) {
|
||||
// if (await skills.equip(agent.bot, 'pickaxe'))
|
||||
// await skills.collectBlock(agent.bot, 'stone', 3);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// return 'No pickaxe in inventory.';
|
||||
// })
|
||||
// },
|
||||
// {
|
||||
// name: '!fightmob',
|
||||
// description: 'Fight the nearest mob.',
|
||||
// perform: wrapExecution(async (agent) => {
|
||||
// let mobs = world.getNearbyMobTypes(agent.bot);
|
||||
// if (mobs.length == 0)
|
||||
// return 'No mobs nearby.';
|
||||
// await skills.attackMob(agent.bot, mobs[0], true);
|
||||
// })
|
||||
// }
|
||||
];
|
||||
|
||||
const commandMap = {};
|
||||
for (let command of commandList) {
|
||||
commandMap[command.name] = command;
|
||||
}
|
||||
|
||||
export function getCommand(name) {
|
||||
return commandMap[name];
|
||||
}
|
||||
|
||||
export function containsCommand(message) {
|
||||
for (let command of commandList) {
|
||||
if (message.includes(command.name)) {
|
||||
return command.name;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function getCommandDocs() {
|
||||
let docs = `\n*COMMAND DOCS\n You can use the following commands to execute actions in the world. Use the command name in your response and the results of the command will be included in the next input. Do not use commands not listed below. If trying to perform an action outside of the scope the listed commands, use the !custom command to write custom code.\n`;
|
||||
for (let command of commandList) {
|
||||
docs += command.name + ': ' + command.description + '\n';
|
||||
}
|
||||
return docs + '*\n';
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
|
||||
import { getQueryDocs } from './queries.js';
|
||||
import { getSkillDocs } from './skill-library.js';
|
||||
import { getCommandDocs } from './commands.js';
|
||||
import { sendRequest, embed, cosineSimilarity } from '../utils/gpt.js';
|
||||
import { stringifyTurns } from '../utils/text.js';
|
||||
|
||||
|
||||
export class History {
|
||||
|
@ -39,7 +40,7 @@ export class History {
|
|||
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. 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 += getSkillDocs();
|
||||
system_message += getCommandDocs();
|
||||
if (this.bio != '')
|
||||
system_message += '\n\nBio:\n' + this.bio;
|
||||
if (this.memory != '')
|
||||
|
@ -48,21 +49,6 @@ export class History {
|
|||
return system_message;
|
||||
}
|
||||
|
||||
stringifyTurns(turns) {
|
||||
let res = '';
|
||||
for (let turn of turns) {
|
||||
if (turn.role === 'assistant') {
|
||||
res += `\nYour output:\n${turn.content}`;
|
||||
} else if (turn.role === 'system') {
|
||||
res += `\nSystem output: ${turn.content}`;
|
||||
} else {
|
||||
res += `\nUser input: ${turn.content}`;
|
||||
|
||||
}
|
||||
}
|
||||
return res.trim();
|
||||
}
|
||||
|
||||
async storeMemories(turns) {
|
||||
console.log("To summarize:", turns)
|
||||
let memory_prompt = 'Update your "Memory" by summarizing the following conversation. Your "Memory" is for storing information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you may need to remember for later. Also include things that you have learned through player feedback or by executing code. Do not include information found in your Docs or that you got right on the first try. Be extremely brief and clear.';
|
||||
|
@ -75,7 +61,7 @@ export class History {
|
|||
memory_prompt += '- I learned that player [name]...\n';
|
||||
|
||||
memory_prompt += 'This is the conversation to summarize:\n';
|
||||
memory_prompt += this.stringifyTurns(turns);
|
||||
memory_prompt += stringifyTurns(turns);
|
||||
|
||||
memory_prompt += 'Summarize relevant information from your previous memory and this conversation:\n';
|
||||
|
||||
|
|
|
@ -547,7 +547,7 @@ export async function goToPlayer(bot, username) {
|
|||
log(bot, `Failed to reach ${username}.`);
|
||||
return false;
|
||||
}
|
||||
log(bot, `Player is now at ${player.position}.`);
|
||||
log(bot, `You have reached the player at position ${player.position}.`);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,56 +15,56 @@
|
|||
{"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"},
|
||||
{"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": "assistant", "content": "I see some oak logs, dirt, and cobblestone. I'll collect oak logs. !execute_action"}
|
||||
],
|
||||
|
||||
[
|
||||
{"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"},
|
||||
{"role": "assistant", "content": "```\n// I am going to craft a spruce_planks.\nawait skills.craftRecipe(bot, 'spruce_planks');\n```"}
|
||||
{"role": "assistant", "content": "!execute_action"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "zZZn98: come here"},
|
||||
{"role": "assistant", "content": "```\n// I am going to navigate to zZZn98.\nawait skills.goToPlayer(bot, 'zZZn98');\n```"},
|
||||
{"role": "assistant", "content": "!execute_action"},
|
||||
{"role": "system", "content": "Code execution finished successfully."},
|
||||
{"role": "assistant", "content": "Here!"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
|
||||
{"role": "assistant", "content": "Collecting sand...\n```\n// I am going to collect 3 sand and give to hanky.\nawait skills.collectBlock(bot, 'sand', 3);\nawait skills.giveToPlayer(bot, 'sand', 'hanky');\n```"},
|
||||
{"role": "assistant", "content": "Collecting sand... !execute_action"},
|
||||
{"role": "system", "content": "Code Output:\nYou have reached player hanky.\nCode execution finished successfully."},
|
||||
{"role": "assistant", "content": "Here!"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "joe: Follow me!"},
|
||||
{"role": "assistant", "content": "Sure!\n```\n// I am going to follow joe.\nawait skills.followPlayer(bot, 'joe');\n```"}
|
||||
{"role": "assistant", "content": "Sure! !execute_action"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "hanky: kill that zombie!"},
|
||||
{"role": "assistant", "content": "I'm attacking!\n```\n//I'm going to attack the nearest zombie.\nawait skills.attackMob(bot, 'zombie');\n```"},
|
||||
{"role": "assistant", "content": "I'm attacking! !execute_action"},
|
||||
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
|
||||
{"role": "assistant", "content": "I could not find a zombie nearby."}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "billybob: stop"},
|
||||
{"role": "assistant", "content": "```\n// I am going to write empty code to stop whatever I am doing\n```"}
|
||||
{"role": "assistant", "content": "!execute_action"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "maya: go to the nearest oak log"},
|
||||
{"role": "assistant", "content": "```\n// I am navigating to the nearest oak log.\nawait skills.goToPosition(bot, nearestOakLog.x, nearestOakLog.y, nearestOakLog.z);\n```"},
|
||||
{"role": "assistant", "content": "!execute_action`"},
|
||||
{"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": "assistant", "content": "Apologies, I made an error. Would you like me to try again?"}
|
||||
],
|
||||
|
||||
[
|
||||
{"role": "user", "content": "trixy88: craft some sticks"},
|
||||
{"role": "assistant", "content": "```\n// I am going to craft 8 sticks\nawait skills.craftRecipe(bot, 'stick');\nlog(bot, crafted)\nawait skills.craftRecipe(bot, 'stick');```"}
|
||||
{"role": "assistant", "content": "!execute_action"}
|
||||
]
|
||||
]
|
52
src/examples_coder.json
Normal file
52
src/examples_coder.json
Normal file
|
@ -0,0 +1,52 @@
|
|||
[
|
||||
{
|
||||
"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"}
|
||||
]
|
||||
}
|
||||
|
||||
]
|
14
src/utils/text.js
Normal file
14
src/utils/text.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
export function stringifyTurns(turns) {
|
||||
let res = '';
|
||||
for (let turn of turns) {
|
||||
if (turn.role === 'assistant') {
|
||||
res += `\nYour output:\n${turn.content}`;
|
||||
} else if (turn.role === 'system') {
|
||||
res += `\nSystem output: ${turn.content}`;
|
||||
} else {
|
||||
res += `\nUser input: ${turn.content}`;
|
||||
|
||||
}
|
||||
}
|
||||
return res.trim();
|
||||
}
|
Loading…
Add table
Reference in a new issue