refactored prompts/examples/memory/initialization

This commit is contained in:
MaxRobinsonTheGreat 2024-02-25 14:13:32 -06:00
parent 748748367d
commit 4afdebac20
21 changed files with 481 additions and 299 deletions

3
.gitignore vendored
View file

@ -2,6 +2,5 @@
node_modules/
package-lock.json
scratch.js
!agent_code/template.js
bots/**/action-code/**
bots/**/save.json
bots/**/

View file

@ -1,6 +1,6 @@
# Mindcraft
# Mindcraft 🧠⛏️
Crafting minds for Minecraft with ChatGPT and Mineflayer
Crafting minds for Minecraft with Language Models and Mineflayer!
#### ‼️Warning‼️
@ -8,13 +8,15 @@ This project allows an AI model to write/execute code on your computer that may
## Requirements
- [OpenAI API Subscription](https://openai.com/blog/openai-api)
- [OpenAI API Subscription](https://openai.com/blog/openai-api) or [Gemini API Subscription](https://aistudio.google.com/app/apikey)
- [Minecraft Java Edition](https://www.minecraft.net/en-us/store/minecraft-java-bedrock-edition-pc)
- [Node.js](https://nodejs.org/) (at least v14)
## Installation
Add `OPENAI_API_KEY` (and optionally `OPENAI_ORG_ID`) to your environment variables
Add one of these environment variables:
- `OPENAI_API_KEY` (and optionally `OPENAI_ORG_ID`)
- `GEMINI_API_KEY`
Clone/Download this repository
@ -28,7 +30,10 @@ Start a minecraft world and open it to LAN on localhost port `55916`
Run `node main.js`
You can configure details in `settings.json`. Here is an example settings for connecting to a non-local server:
You can configure the agent's name, model, and prompts in their profile like `andy.json`.
You can configure project details in `settings.json`. Here is an example settings for connecting to a non-local server:
```
{
"minecraft_version": "1.20.1",

147
andy.json Normal file
View file

@ -0,0 +1,147 @@
{
"name": "andy",
"model": "gpt-3.5-turbo-0125",
"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. 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)'. Have fun :) \n$STATS\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 between you and the user, 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 recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem. Be maximally efficient, creative, and clear. Do not use commands !likeThis, only use codeblocks.\n$CODE_DOCS\n$EXAMPLES\nBegin coding:",
"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 in your next response. Store information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you need to remember and what you've learned through player feedback or by executing code. Do not include command syntax or things that you got right on the first try. Be extremely brief and use as few words as possible.\nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the memory text: ",
"conversation_examples": [
[
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
],
[
{"role": "user", "content": "grombo_Xx: What do you see?"},
{"role": "assistant", "content": "Let me see... !nearbyBlocks"},
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."}
],
[
{"role": "user", "content": "greg: Collect 10 wood"},
{"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"},
{"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. !collectBlocks('oak_log', 10)"}
],
[
{"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": "!craftRecipe('spruce_planks', 1)"},
{"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 4 spruce planks!"}
],
[
{"role": "user", "content": "zZZn98: come here"},
{"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"},
{"role": "system", "content": "Arrived at player."},
{"role": "assistant", "content": "Here!"},
{"role": "user", "content": "zZZn98: no come right where I am"},
{"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"}
],
[
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
{"role": "assistant", "content": "Collecting sand... !newAction"},
{"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! !followPlayer('joe', 4)"},
{"role": "user", "content": "joe: Not so close!"},
{"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"}
],
[
{"role": "user", "content": "hanky: kill that zombie!"},
{"role": "assistant", "content": "I'm attacking! !attack('zombie')"},
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
{"role": "assistant", "content": "I could not find a zombie nearby."}
],
[
{"role": "user", "content": "abc: stop"},
{"role": "assistant", "content": "Sure. !stop"}
],
[
{"role": "user", "content": "maya: go to the nearest oak log"},
{"role": "assistant", "content": "!newAction`"},
{"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. Would you like me to try again?"}
],
[
{"role": "user", "content": "trixy88: craft some sticks"},
{"role": "assistant", "content": "!craftRecipe('stick', 4)"},
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 16 sticks!"}
],
[
{"role": "user", "content": "poppy: build a cobblestone house"},
{"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction"}
],
[
{"role": "user", "content": "zib: build a dirt tower"},
{"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction"}
],
[
{"role": "user", "content": "umpire_man: follow me without hunting"},
{"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"},
{"role": "system", "content": "'hunting' mode disabled."},
{"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"}
]
],
"coding_examples": [
[
{"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: cook some chicken"},
{"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
{"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
{"role": "assistant", "content": "I have cooked 8 chicken."}
],
[
{"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!"}
],
[
{"role": "user", "content": "brug: build a dirt house"},
{"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"}
]
]
}

View file

@ -1,3 +1,7 @@
import { AgentProcess } from './src/process/agent-process.js';
new AgentProcess('andy').start('assist');
let profile = './andy.json';
let load_memory = false;
let init_message = null;
new AgentProcess().start(profile, load_memory, init_message);

147
pollux.json Normal file
View file

@ -0,0 +1,147 @@
{
"name": "pollux",
"model": "gemini-pro",
"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. 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 REPEAT COMMANDS ENDLESSLY, be conversational and don't use a command in every response. Don't do this: '!stop\n!stop\n!stop' \n$STATS\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 between you and the user, 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 recieve it's output. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem. DO NOT USE COMMANDS !dontDoThis, only respond in javascript codeblocks! \n$CODE_DOCS\n$EXAMPLES\nBegin coding:",
"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 in your next response. Store information that will help you improve as a Minecraft bot. Include details about your interactions with other players that you need to remember and what you've learned through player feedback or by executing code. Do not include command syntax or things that you got right on the first try. Be extremely brief and use as few words as possible.\nOld Memory: '$MEMORY'\nRecent conversation: \n$TO_SUMMARIZE\nSummarize your old memory and recent conversation into a new memory, and respond only with the memory text: ",
"conversation_examples": [
[
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
],
[
{"role": "user", "content": "grombo_Xx: What do you see?"},
{"role": "assistant", "content": "Let me see... !nearbyBlocks"},
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."}
],
[
{"role": "user", "content": "greg: Collect 10 wood"},
{"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"},
{"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. !collectBlocks('oak_log', 10)"}
],
[
{"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": "!craftRecipe('spruce_planks', 1)"},
{"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 4 spruce planks!"}
],
[
{"role": "user", "content": "zZZn98: come here"},
{"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"},
{"role": "system", "content": "Arrived at player."},
{"role": "assistant", "content": "Here!"},
{"role": "user", "content": "zZZn98: no come right where I am"},
{"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"}
],
[
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
{"role": "assistant", "content": "Collecting sand... !newAction"},
{"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! !followPlayer('joe', 4)"},
{"role": "user", "content": "joe: Not so close!"},
{"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"}
],
[
{"role": "user", "content": "hanky: kill that zombie!"},
{"role": "assistant", "content": "I'm attacking! !attack('zombie')"},
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
{"role": "assistant", "content": "I could not find a zombie nearby."}
],
[
{"role": "user", "content": "abc: stop"},
{"role": "assistant", "content": "Sure. !stop"}
],
[
{"role": "user", "content": "maya: go to the nearest oak log"},
{"role": "assistant", "content": "!newAction`"},
{"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. Would you like me to try again?"}
],
[
{"role": "user", "content": "trixy88: craft some sticks"},
{"role": "assistant", "content": "!craftRecipe('stick', 4)"},
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 16 sticks!"}
],
[
{"role": "user", "content": "poppy: build a cobblestone house"},
{"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction"}
],
[
{"role": "user", "content": "zib: build a dirt tower"},
{"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction"}
],
[
{"role": "user", "content": "umpire_man: follow me without hunting"},
{"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"},
{"role": "system", "content": "'hunting' mode disabled."},
{"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"}
]
],
"coding_examples": [
[
{"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: cook some chicken"},
{"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
{"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
{"role": "assistant", "content": "I have cooked 8 chicken."}
],
[
{"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!"}
],
[
{"role": "user", "content": "brug: build a dirt house"},
{"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"}
]
]
}

View file

@ -3,7 +3,5 @@
"host": "localhost",
"port": 55916,
"auth": "offline",
"model": "gpt-3.5-turbo-0125",
"allow_insecure_coding": false
"allow_insecure_coding": true
}

View file

@ -1,27 +1,24 @@
import { History } from './history.js';
import { Coder } from './coder.js';
import { Prompter } from './prompter.js';
import { initModes } from './modes.js';
import { Examples } from '../utils/examples.js';
import { initBot } from '../utils/mcdata.js';
import { sendRequest } from '../models/model.js';
import { containsCommand, commandExists, executeCommand } from './commands/index.js';
import { containsCommand, commandExists, executeCommand, truncCommandMessage } from './commands/index.js';
export class Agent {
async start(name, profile=null, init_message=null) {
this.name = name;
this.examples = new Examples();
async start(profile_fp, load_mem=false, init_message=null) {
this.prompter = new Prompter(this, profile_fp);
this.name = this.prompter.getName();
this.history = new History(this);
this.coder = new Coder(this);
await this.prompter.initExamples();
console.log('Loading examples...');
this.history.load(profile);
await this.examples.load('./src/examples.json');
await this.coder.load();
if (load_mem)
this.history.load();
console.log('Logging in...');
this.bot = initBot(name);
this.bot = initBot(this.name);
initModes(this);
@ -95,25 +92,26 @@ export class Agent {
}
for (let i=0; i<5; i++) {
let history = await this.history.getHistory(this.examples);
let res = await sendRequest(history, this.history.getSystemMessage());
this.history.add(this.name, res);
let history = this.history.getHistory();
let res = await this.prompter.promptConvo(history);
let command_name = containsCommand(res);
if (command_name) { // contains query or command
console.log(`""${res}""`)
console.log(`Full response: ""${res}""`)
res = truncCommandMessage(res); // everything after the command is ignored
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)
continue;
}
let pre_message = res.substring(0, res.indexOf(command_name)).trim();
let message = `*used ${command_name.substring(1)}*`;
let chat_message = `*used ${command_name.substring(1)}*`;
if (pre_message.length > 0)
message = `${pre_message} ${message}`;
this.cleanChat(message);
chat_message = `${pre_message} ${chat_message}`;
this.cleanChat(chat_message);
let execute_res = await executeCommand(this, res);
console.log('Agent executed:', command_name, 'and got:', execute_res);
@ -124,6 +122,7 @@ export class Agent {
break;
}
else { // conversation response
this.history.add(this.name, res);
this.cleanChat(res);
console.log('Purely conversational response:', res);
break;

View file

@ -1,7 +1,4 @@
import { writeFile, readFile, mkdirSync } from 'fs';
import { sendRequest } from '../models/model.js';
import { getSkillDocs } from './library/index.js';
import { Examples } from '../utils/examples.js';
export class Coder {
@ -13,11 +10,6 @@ export class Coder {
this.generating = false;
this.code_template = '';
this.timedout = false;
}
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;
@ -98,12 +90,7 @@ export class Coder {
}
async generateCodeLoop(agent_history) {
let system_message = "You are a minecraft mineflayer bot that plays minecraft by writing javascript codeblocks. Given the conversation between you and the user, use the provided skills and world functions to write your code in a codeblock. Example response: ``` // your code here ``` You will then be given a response to your code. If you are satisfied with the response, respond without a codeblock in a conversational way. If something went wrong, write another codeblock and try to fix the problem.";
system_message += getSkillDocs();
system_message += "\n\nExamples:\nUser zZZn98: come here \nAssistant: I am going to navigate to zZZn98. ```\nawait skills.goToPlayer(bot, 'zZZn98', 3);```\nSystem: Code execution finished successfully.\nAssistant: Done.";
let messages = await agent_history.getHistory(this.examples);
let messages = agent_history.getHistory();
let code_return = null;
let failures = 0;
@ -111,7 +98,7 @@ export class Coder {
if (this.agent.bot.interrupt_code)
return {success: true, message: null, interrupted: true, timedout: false};
console.log(messages)
let res = await sendRequest(messages, system_message);
let res = await this.agent.prompter.promptCoding(messages);
console.log('Code generation response:', res)
let contains_code = res.indexOf('```') !== -1;
if (!contains_code) {

View file

@ -56,7 +56,7 @@ export const actionsList = [
},
{
name: '!setMode',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment. Ex: !setMode("hunting", true)',
description: 'Set a mode to on or off. A mode is an automatic behavior that constantly checks and responds to the environment.',
params: {
'mode_name': '(string) The name of the mode to enable.',
'on': '(bool) Whether to enable or disable the mode.'
@ -73,7 +73,7 @@ export const actionsList = [
},
{
name: '!goToPlayer',
description: 'Go to the given player. Ex: !goToPlayer("steve", 3)',
description: 'Go to the given player.',
params: {
'player_name': '(string) The name of the player to go to.',
'closeness': '(number) How close to get to the player.'
@ -84,7 +84,7 @@ export const actionsList = [
},
{
name: '!followPlayer',
description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on. Ex: !followPlayer("stevie", 4)',
description: 'Endlessly follow the given player. Will defend that player if self_defense mode is on.',
params: {
'player_name': '(string) The name of the player to follow.',
'follow_dist': '(number) The distance to follow from.'
@ -95,7 +95,7 @@ export const actionsList = [
},
{
name: '!moveAway',
description: 'Move away from the current location in any direction by a given distance. Ex: !moveAway(2)',
description: 'Move away from the current location in any direction by a given distance.',
params: {'distance': '(number) The distance to move away.'},
perform: wrapExecution(async (agent, distance) => {
await skills.moveAway(agent.bot, distance);
@ -103,7 +103,7 @@ export const actionsList = [
},
{
name: '!givePlayer',
description: 'Give the specified item to the given player. Ex: !givePlayer("steve", "stone_pickaxe", 1)',
description: 'Give the specified item to the given player.',
params: {
'player_name': '(string) The name of the player to give the item to.',
'item_name': '(string) The name of the item to give.' ,
@ -117,7 +117,7 @@ export const actionsList = [
name: '!collectBlocks',
description: 'Collect the nearest blocks of a given type.',
params: {
'type': '(string) The block type to collect. Ex: !collectBlocks("stone", 10)',
'type': '(string) The block type to collect.',
'num': '(number) The number of blocks to collect.'
},
perform: wrapExecution(async (agent, type, num) => {
@ -128,7 +128,7 @@ export const actionsList = [
name: '!collectAllBlocks',
description: 'Collect all the nearest blocks of a given type until told to stop.',
params: {
'type': '(string) The block type to collect. Ex: !collectAllBlocks("stone")'
'type': '(string) The block type to collect.'
},
perform: wrapExecution(async (agent, type) => {
let success = await skills.collectBlock(agent.bot, type, 1);
@ -138,7 +138,7 @@ export const actionsList = [
},
{
name: '!craftRecipe',
description: 'Craft the given recipe a given number of times. Ex: I will craft 8 sticks !craftRecipe("stick", 2)',
description: 'Craft the given recipe a given number of times.',
params: {
'recipe_name': '(string) The name of the output item to craft.',
'num': '(number) The number of times to craft the recipe. This is NOT the number of output items, as it may craft many more items depending on the recipe.'
@ -151,7 +151,7 @@ export const actionsList = [
},
{
name: '!placeHere',
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches. Ex: !placeBlockHere("crafting_table")',
description: 'Place a given block in the current location. Do NOT use to build structures, only use for single blocks/torches.',
params: {'type': '(string) The block type to place.'},
perform: wrapExecution(async (agent, type) => {
let pos = agent.bot.entity.position;

View file

@ -60,6 +60,14 @@ function parseCommandMessage(message) {
return null;
}
export function truncCommandMessage(message) {
const commandMatch = message.match(commandRegex);
if (commandMatch) {
return message.substring(0, commandMatch.index + commandMatch[0].length);
}
return message;
}
function numParams(command) {
if (!command.params)
return 0;

View file

@ -1,63 +1,28 @@
import { writeFileSync, readFileSync, mkdirSync } from 'fs';
import { stringifyTurns } from '../utils/text.js';
import { sendRequest } from '../models/model.js';
import { getCommandDocs } from './commands/index.js';
export class History {
constructor(agent) {
this.agent = agent;
this.name = agent.name;
this.save_path = `./bots/${this.name}/save.json`;
this.memory_fp = `./bots/${this.name}/memory.json`;
this.turns = [];
// These define an agent's long term memory
this.bio = '';
this.memory = '';
// Variables for controlling the agent's memory and knowledge
this.max_messages = 20;
}
async getHistory(examples=null) { // expects an Examples object
let turns = JSON.parse(JSON.stringify(this.turns));
if (examples) {
let examples_msg = await examples.createExampleMessage(turns);
turns = examples_msg.concat(turns);
}
return turns;
}
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 using commands. 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, do not give instructions unless asked, and do not 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('bingo', 3)". Have fun :) \n`;
system_message += getCommandDocs();
if (this.bio != '')
system_message += '\n\nBio:\n' + this.bio;
if (this.memory != '')
system_message += '\n\nMemory:\n' + this.memory;
return system_message;
getHistory() { // expects an Examples object
return JSON.parse(JSON.stringify(this.turns));
}
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.';
if (this.memory != '') {
memory_prompt += `This is your previous memory: "${this.memory}"\n Include and summarize any relevant information from this previous memory. Your output will replace your previous memory.`;
}
memory_prompt += '\n';
memory_prompt += ' Your output should use one of the following formats:\n';
memory_prompt += '- When the player... output...\n';
memory_prompt += '- I learned that player [name]...\n';
memory_prompt += 'This is the conversation to summarize:\n';
memory_prompt += stringifyTurns(turns);
memory_prompt += 'Summarize relevant information from your previous memory and this conversation:\n';
let memory_turns = [{'role': 'system', 'content': memory_prompt}]
this.memory = await sendRequest(memory_turns, this.getSystemMessage());
console.log("Storing memories...");
this.memory = await this.agent.prompter.promptMemSaving(this.memory, turns);
console.log("Memory updated to: ", this.memory);
}
async add(name, content) {
@ -73,7 +38,6 @@ export class History {
// Summarize older turns into memory
if (this.turns.length >= this.max_messages) {
console.log('summarizing memory')
let to_summarize = [this.turns.shift()];
while (this.turns[0].role != 'user' && this.turns.length > 1)
to_summarize.push(this.turns.shift());
@ -83,15 +47,13 @@ export class History {
save() {
// save history object to json file
mkdirSync(`./bots/${this.name}`, { recursive: true });
let data = {
'name': this.name,
'bio': this.bio,
'memory': this.memory,
'turns': this.turns
};
const json_data = JSON.stringify(data, null, 4);
writeFileSync(this.save_path, json_data, (err) => {
writeFileSync(this.memory_fp, json_data, (err) => {
if (err) {
throw err;
}
@ -99,17 +61,15 @@ export class History {
});
}
load(profile) {
const load_path = profile? `./bots/${this.name}/${profile}.json` : this.save_path;
load() {
try {
// load history object from json file
const data = readFileSync(load_path, 'utf8');
const data = readFileSync(this.memory_fp, 'utf8');
const obj = JSON.parse(data);
this.bio = obj.bio;
this.memory = obj.memory;
this.turns = obj.turns;
} catch (err) {
console.error(`No file for profile '${load_path}' for agent ${this.name}.`);
console.error(`No memory file '${this.memory_fp}' for agent ${this.name}.`);
}
}

89
src/agent/prompter.js Normal file
View file

@ -0,0 +1,89 @@
import { readFileSync, mkdirSync, writeFileSync} from 'fs';
import { Gemini } from '../models/gemini.js';
import { GPT } from '../models/gpt.js';
import { Examples } from '../utils/examples.js';
import { getCommandDocs } from './commands/index.js';
import { getSkillDocs } from './library/index.js';
import { stringifyTurns } from '../utils/text.js';
import { getCommand } from './commands/index.js';
export class Prompter {
constructor(agent, fp) {
this.prompts = JSON.parse(readFileSync(fp, 'utf8'));
let name = this.prompts.name;
this.agent = agent;
let model_name = this.prompts.model;
mkdirSync(`./bots/${name}`, { recursive: true });
writeFileSync(`./bots/${name}/last_profile.json`, JSON.stringify(this.prompts, null, 4), (err) => {
if (err) {
throw err;
}
console.log("Copy profile saved.");
});
if (model_name.includes('gemini'))
this.model = new Gemini(model_name);
else if (model_name.includes('gpt'))
this.model = new GPT(model_name);
else
throw new Error('Unknown model ' + model_name);
}
getName() {
return this.prompts.name;
}
async initExamples() {
console.log('Loading examples...')
this.convo_examples = new Examples(this.model);
await this.convo_examples.load(this.prompts.conversation_examples);
this.coding_examples = new Examples(this.model);
await this.coding_examples.load(this.prompts.coding_examples);
console.log('Examples loaded.');
}
async replaceStrings(prompt, messages, examples=null, prev_memory=null, to_summarize=[]) {
prompt = prompt.replaceAll('$NAME', this.agent.name);
if (prompt.includes('$STATS')) {
let stats = await getCommand('!stats').perform(this.agent);
prompt = prompt.replaceAll('$STATS', stats);
}
if (prompt.includes('$COMMAND_DOCS'))
prompt = prompt.replaceAll('$COMMAND_DOCS', getCommandDocs());
if (prompt.includes('$CODE_DOCS'))
prompt = prompt.replaceAll('$CODE_DOCS', getSkillDocs());
if (prompt.includes('$EXAMPLES') && examples !== null)
prompt = prompt.replaceAll('$EXAMPLES', await examples.createExampleMessage(messages));
if (prompt.includes('$MEMORY'))
prompt = prompt.replaceAll('$MEMORY', prev_memory ? prev_memory : 'None.');
if (prompt.includes('$TO_SUMMARIZE'))
prompt = prompt.replaceAll('$TO_SUMMARIZE', stringifyTurns(to_summarize));
// check if there are any remaining placeholders with syntax $<word>
let remaining = prompt.match(/\$[A-Z_]+/g);
if (remaining !== null) {
console.warn('Unknown prompt placeholders:', remaining);
}
return prompt;
}
async promptConvo(messages) {
let prompt = this.prompts.conversing;
prompt = await this.replaceStrings(prompt, messages, this.convo_examples);
return await this.model.sendRequest(messages, prompt);
}
async promptCoding(messages) {
let prompt = this.prompts.coding;
prompt = await this.replaceStrings(prompt, messages, this.coding_examples);
return await this.model.sendRequest(messages, prompt);
}
async promptMemSaving(prev_mem, to_summarize) {
let prompt = this.prompts.saving_memory;
prompt = await this.replaceStrings(prompt, null, null, prev_mem, to_summarize);
return await this.model.sendRequest([], prompt);
}
}

View file

@ -1,95 +0,0 @@
[
[
{"role": "user", "content": "miner_32: Hey! What are you up to?"},
{"role": "assistant", "content": "Nothing much miner_32, what do you need?"}
],
[
{"role": "user", "content": "grombo_Xx: What do you see?"},
{"role": "assistant", "content": "Let me see... !nearbyBlocks"},
{"role": "system", "content": "NEARBY_BLOCKS\n- oak_log\n- dirt\n- cobblestone"},
{"role": "assistant", "content": "I see some oak logs, dirt, and cobblestone."}
],
[
{"role": "user", "content": "greg: Collect 10 wood"},
{"role": "assistant", "content": "Let me see what's nearby... !nearbyBlocks"},
{"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. !collectBlocks('oak_log', 10)"}
],
[
{"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": "!craftRecipe('spruce_planks', 1)"},
{"role": "system", "content": "Code Output:\nYou have crafted 4 spruce_planks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 4 spruce planks!"}
],
[
{"role": "user", "content": "zZZn98: come here"},
{"role": "assistant", "content": "On my way! !goToPlayer('zZZn98', 3)"},
{"role": "system", "content": "Arrived at player."},
{"role": "assistant", "content": "Here!"},
{"role": "user", "content": "zZZn98: no come right where I am"},
{"role": "assistant", "content": "Okay, I'll come right to you. !goToPlayer('zZZn98', 0)"}
],
[
{"role": "user", "content": "hanky: collect some sand and give it to me please"},
{"role": "assistant", "content": "Collecting sand... !newAction"},
{"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! !followPlayer('joe', 4)"},
{"role": "user", "content": "joe: Not so close!"},
{"role": "assistant", "content": "Okay, I'll keep my distance. !followPlayer('joe', 3)"}
],
[
{"role": "user", "content": "hanky: kill that zombie!"},
{"role": "assistant", "content": "I'm attacking! !attack('zombie')"},
{"role": "system", "content": "Code Output:\nNo zombie nearby"},
{"role": "assistant", "content": "I could not find a zombie nearby."}
],
[
{"role": "user", "content": "abc: stop"},
{"role": "assistant", "content": "Sure. !stop"}
],
[
{"role": "user", "content": "maya: go to the nearest oak log"},
{"role": "assistant", "content": "!newAction`"},
{"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. Would you like me to try again?"}
],
[
{"role": "user", "content": "trixy88: craft some sticks"},
{"role": "assistant", "content": "!craftRecipe('stick', 4)"},
{"role": "system", "content": "Code Output:\nYou have crafted 16 sticks.\nCode execution finished successfully."},
{"role": "assistant", "content": "I've crafted 16 sticks!"}
],
[
{"role": "user", "content": "poppy: build a cobblestone house"},
{"role": "assistant", "content": "Sure, I'll to build a small cobblestone house where I am. !newAction"}
],
[
{"role": "user", "content": "zib: build a dirt tower"},
{"role": "assistant", "content": "Sure, I'll build a dirt tower 5 tall where I am. !newAction"}
],
[
{"role": "user", "content": "umpire_man: follow me without hunting"},
{"role": "assistant", "content": "Okay, first I'll turn off hunting mode !setMode('hunting', false)"},
{"role": "system", "content": "'hunting' mode disabled."},
{"role": "assistant", "content": "Now I'll follow you !followPlayer('umpire_man')"}
]
]

View file

@ -1,38 +0,0 @@
[
[
{"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: cook some chicken"},
{"role": "assistant", "content": "```\nawait skills.smeltItem(bot, 'chicken', 8);\n```"},
{"role": "system", "content": "Successfully smelted 8 chicken into 8 cooked_chicken."},
{"role": "assistant", "content": "I have cooked 8 chicken."}
],
[
{"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!"}
],
[
{"role": "user", "content": "brug: build a dirt house"},
{"role": "assistant", "content": "```const position = world.getPosition(bot);\nconst startX = position.x;\nconst startY = position.y;\nconst startZ = position.z;\nconst width = 7;\nconst depth = 7;\nconst height = 4;\n\n// Build the walls\nfor (let x = startX; x < startX + width; x++) {\n for (let y = startY; y < startY + height; y++) {\n for (let z = startZ; z < startZ + depth; z++) {\n if (x === startX || x === startX + width - 1 || y === startY || y === startY + height - 1 || z === startZ || z === startZ + depth - 1) {\n await skills.placeBlock(bot, 'oak_planks', x, y, z); \n }\n }\n }\n}\n```"}
]
]

View file

@ -1,19 +1,17 @@
import { GoogleGenerativeAI } from '@google/generative-ai';
import settings from '../settings.js';
export class Gemini {
constructor() {
constructor(model_name) {
if (!process.env.GEMINI_API_KEY) {
console.error('Gemini API key missing! Make sure you set your GEMINI_API_KEY environment variable.');
process.exit(1);
}
this.genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY);
this.model = this.genAI.getGenerativeModel({ model: settings.model });
this.model = this.genAI.getGenerativeModel({ model: model_name });
}
async sendRequest(turns, systemMessage) {
systemMessage += "\nBegin the conversation:\n";
const messages = [{'role': 'system', 'content': systemMessage}].concat(turns);
let prompt = "";
let role = "";

View file

@ -1,8 +1,8 @@
import OpenAIApi from 'openai';
import settings from '../settings.js';
export class GPT {
constructor() {
constructor(model_name) {
this.model_name = model_name;
let openAiConfig = null;
if (process.env.OPENAI_ORG_ID) {
openAiConfig = {
@ -30,8 +30,9 @@ export class GPT {
let res = null;
try {
console.log('Awaiting openai api response...')
console.log('Messages:', messages);
let completion = await this.openai.chat.completions.create({
model: settings.model,
model: this.model_name,
messages: messages,
stop: stop_seq,
});

View file

@ -1,19 +0,0 @@
import { GPT } from './gpt.js';
import { Gemini } from './gemini.js';
import settings from '../settings.js';
console.log('Initializing model...');
let model = null;
if (settings.model.includes('gemini')) {
model = new Gemini();
} else {
model = new GPT();
}
export async function sendRequest(turns, systemMessage) {
return await model.sendRequest(turns, systemMessage);
}
export async function embed(text) {
return await model.embed(text);
}

View file

@ -1,13 +1,11 @@
import { spawn } from 'child_process';
export class AgentProcess {
constructor(name) {
this.name = name;
}
start(profile='assist', init_message=null) {
start(profile, load_memory=false, init_message=null) {
let args = ['src/process/init-agent.js', this.name];
if (profile)
args.push('-p', profile);
args.push('-p', profile);
if (load_memory)
args.push('-l', load_memory);
if (init_message)
args.push('-m', init_message);
@ -27,7 +25,7 @@ export class AgentProcess {
process.exit(1);
}
console.log('Restarting agent...');
this.start('save', 'Agent process restarted. Notify the user and decide what to do.');
this.start(profile, true, 'Agent process restarted. Notify the user and decide what to do.');
last_restart = Date.now();
}
});

View file

@ -3,7 +3,7 @@ import yargs from 'yargs';
const args = process.argv.slice(2);
if (args.length < 1) {
console.log('Usage: node init_agent.js <agent_name> [profile] [init_message]');
console.log('Usage: node init_agent.js <agent_name> [profile] [load_memory] [init_message]');
process.exit(1);
}
@ -11,7 +11,12 @@ const argv = yargs(args)
.option('profile', {
alias: 'p',
type: 'string',
description: 'profile to use for agent'
description: 'profile filepath to use for agent'
})
.option('load_memory', {
alias: 'l',
type: 'boolean',
description: 'load agent memory from file on startup'
})
.option('init_message', {
alias: 'm',
@ -19,5 +24,4 @@ const argv = yargs(args)
description: 'automatically prompt the agent on startup'
}).argv
const name = args[0];
new Agent().start(name, argv.profile, argv.init_message);
new Agent().start(argv.profile, argv.load_memory, argv.init_message);

View file

@ -1,3 +1,2 @@
import { readFileSync } from 'fs';
const settings = JSON.parse(readFileSync('./settings.json', 'utf8'));
export default settings;
export default JSON.parse(readFileSync('./settings.json', 'utf8'));

View file

@ -1,35 +1,26 @@
import { readFileSync } from 'fs';
import { cosineSimilarity } from './math.js';
import { stringifyTurns } from './text.js';
import { embed } from '../models/model.js';
export class Examples {
constructor(select_num=2) {
constructor(model, select_num=2) {
this.examples = [];
this.model = model;
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);
}
async load(examples) {
this.examples = [];
for (let example of examples) {
let promises = examples.map(async (example) => {
let messages = '';
for (let turn of example) {
if (turn.role != 'assistant')
if (turn.role === 'user')
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});
}
const embedding = await this.model.embed(messages);
return {'embedding': embedding, 'turns': example};
});
this.examples = await Promise.all(promises);
}
async getRelevant(turns) {
@ -39,7 +30,7 @@ export class Examples {
messages += turn.content.substring(turn.content.indexOf(':')+1).trim() + '\n';
}
messages = messages.trim();
const embedding = await embed(messages);
const embedding = await this.model.embed(messages);
this.examples.sort((a, b) => {
return cosineSimilarity(b.embedding, embedding) - cosineSimilarity(a.embedding, embedding);
});
@ -55,11 +46,11 @@ export class Examples {
console.log(example.turns[0].content)
}
let msg = 'Here are some examples of how to respond:\n';
let msg = 'Examples of how to respond:\n';
for (let i=0; i<selected_examples.length; i++) {
let example = selected_examples[i];
msg += `Example ${i+1}:\n${stringifyTurns(example.turns)}\n\n`;
}
return [{'role': 'system', 'content': msg}];
return msg;
}
}