2023-11-12 14:53:23 -08:00
import { writeFileSync , readFileSync , mkdirSync } from 'fs' ;
2023-11-13 20:33:34 -08:00
import { getQueryDocs } from './queries.js' ;
import { getSkillDocs } from './skill_library.js' ;
2023-11-15 18:53:02 -06:00
import { sendRequest , embed , cosineSimilarity } from './gpt.js' ;
2023-11-12 14:53:23 -08:00
2023-11-07 09:44:56 -06:00
export class History {
2023-11-15 13:18:38 -08:00
constructor ( agent , save _path ) {
2023-11-12 14:53:23 -08:00
this . name = agent . name ;
2023-11-15 13:18:38 -08:00
this . save _path = save _path ;
this . turns = [ ] ;
2023-11-12 14:53:23 -08:00
// These define an agent's long term memory
2023-11-13 20:33:34 -08:00
this . bio = '' ;
2023-11-12 14:53:23 -08:00
this . memory = '' ;
2023-11-15 13:18:38 -08:00
// Variables for controlling the agent's memory and knowledge
2023-11-12 14:53:23 -08:00
this . max _messages = 20 ;
2023-11-15 13:18:38 -08:00
this . fewshot = 5 ;
this . examples = [ ] ;
this . selected _examples = [ ] ;
2023-11-07 09:44:56 -06:00
}
2023-08-15 23:39:02 -07:00
2023-11-07 09:44:56 -06:00
getHistory ( ) {
return this . turns ;
}
2023-08-15 23:39:02 -07:00
2023-11-15 18:53:02 -06:00
getSystemMessage ( include _examples = true ) {
2023-11-13 20:33:34 -08:00
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 brief in your responses , omit needless words , and do not give instructions unless asked . ` ;
system _message += getQueryDocs ( ) ;
system _message += getSkillDocs ( ) ;
2023-11-15 13:18:38 -08:00
if ( this . bio != '' )
system _message += '\n\nBio:\n' + this . bio ;
if ( this . memory != '' )
system _message += '\n\nMemory:\n' + this . memory ;
2023-11-15 18:53:02 -06:00
if ( include _examples && this . selected _examples . length > 0 ) {
2023-11-15 13:18:38 -08:00
for ( let i = 0 ; i < this . selected _examples . length ; i ++ ) {
system _message += '\n\nExample ' + ( i + 1 ) + ':\n\n' ;
system _message += this . stringifyTurns ( this . selected _examples [ i ] . turns ) ;
}
2023-11-13 20:33:34 -08:00
}
return system _message ;
}
2023-11-12 14:53:23 -08:00
2023-11-15 13:18:38 -08:00
stringifyTurns ( turns ) {
let res = '' ;
for ( let turn of turns ) {
if ( turn . role === 'assistant' ) {
res += ` \n \n Your output: \n ${ turn . content } ` ;
} else if ( turn . role === 'system' ) {
res += ` \n \n System output: ${ turn . content } ` ;
} else {
res += ` \n \n User input: ${ turn . content } ` ;
}
}
return res . trim ( ) ;
}
2023-11-13 20:33:34 -08:00
async storeMemories ( turns ) {
2023-11-15 11:38:17 -08:00
let memory _prompt = 'Update your "Memory" with 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.' ;
2023-11-13 20:33:34 -08:00
if ( this . memory != '' ) {
2023-11-15 11:38:17 -08:00
memory _prompt += ' Include information from your previous memory if it is still relevant. Your output will replace your previous memory.' ;
2023-11-13 20:33:34 -08:00
}
2023-11-18 10:26:02 -08:00
memory _prompt += ' Your output should be a brief list of things you have learned using the following formats:\n' ;
2023-11-15 11:38:17 -08:00
memory _prompt += '- When the player... output...\n' ;
memory _prompt += '- I learned that player [name]...\n' ;
2023-11-15 13:18:38 -08:00
memory _prompt += this . stringifyTurns ( turns ) ;
2023-11-13 20:33:34 -08:00
let memory _turns = [ { 'role' : 'user' , 'content' : memory _prompt } ]
2023-11-15 18:53:02 -06:00
this . memory = await sendRequest ( memory _turns , this . getSystemMessage ( false ) ) ;
2023-11-15 13:18:38 -08:00
}
async loadExamples ( ) {
let examples = [ ] ;
try {
const data = readFileSync ( 'utils/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 ) {
2023-11-18 10:26:02 -08:00
if ( turn . role != 'assistant' )
2023-11-15 13:18:38 -08:00
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 setExamples ( ) {
let messages = '' ;
for ( let turn of this . turns ) {
2023-11-18 10:26:02 -08:00
if ( turn . role != 'assistant' )
2023-11-15 13:18:38 -08:00
messages += turn . content . substring ( turn . content . indexOf ( ':' ) + 1 ) . trim ( ) + '\n' ;
}
messages = messages . trim ( ) ;
const embedding = await embed ( messages ) ;
this . examples . sort ( ( a , b ) => {
2023-11-15 18:53:02 -06:00
return cosineSimilarity ( a . embedding , embedding ) - cosineSimilarity ( b . embedding , embedding ) ;
2023-11-15 13:18:38 -08:00
} ) ;
this . selected _examples = this . examples . slice ( - this . fewshot ) ;
for ( let example of this . selected _examples ) {
console . log ( 'selected example: ' , example . turns [ 0 ] . content ) ;
}
}
2023-11-12 14:53:23 -08:00
async add ( name , content ) {
2023-11-07 09:44:56 -06:00
let role = 'assistant' ;
2023-11-12 13:57:22 -06:00
if ( name === 'system' ) {
role = 'system' ;
}
2023-11-13 20:33:34 -08:00
else if ( name !== this . name ) {
2023-11-07 09:44:56 -06:00
role = 'user' ;
2023-11-07 12:00:55 -06:00
content = ` ${ name } : ${ content } ` ;
2023-08-17 00:00:57 -07:00
}
2023-11-07 09:44:56 -06:00
this . turns . push ( { role , content } ) ;
2023-11-12 14:53:23 -08:00
// Summarize older turns into memory
if ( this . turns . length >= this . max _messages ) {
2023-11-15 18:53:02 -06:00
console . log ( 'summarizing memory' )
2023-11-15 11:38:17 -08:00
let to _summarize = [ this . turns . shift ( ) ] ;
while ( this . turns [ 0 ] . role != 'user' && this . turns . length > 0 )
to _summarize . push ( this . turns . shift ( ) ) ;
await this . storeMemories ( to _summarize ) ;
2023-11-12 14:53:23 -08:00
}
2023-11-15 13:18:38 -08:00
if ( role === 'user' )
await this . setExamples ( ) ;
2023-11-12 14:53:23 -08:00
}
2023-11-15 13:18:38 -08:00
save ( ) {
if ( this . save _path === '' || this . save _path == null ) return ;
2023-11-12 14:53:23 -08:00
// save history object to json file
mkdirSync ( 'bots' , { recursive : true } ) ;
2023-11-15 13:18:38 -08:00
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 ) => {
2023-11-12 14:53:23 -08:00
if ( err ) {
throw err ;
}
console . log ( "JSON data is saved." ) ;
} ) ;
}
2023-11-15 13:18:38 -08:00
load ( ) {
if ( this . save _path === '' || this . save _path == null ) return ;
2023-11-12 14:53:23 -08:00
try {
// load history object from json file
2023-11-15 13:18:38 -08:00
const data = readFileSync ( this . save _path , 'utf8' ) ;
2023-11-12 14:53:23 -08:00
const obj = JSON . parse ( data ) ;
this . turns = obj . turns ;
this . bio = obj . bio ;
this . memory = obj . memory ;
this . num _saved _turns = obj . num _saved _turns ;
} catch ( err ) {
console . log ( 'No history file found for ' + this . name + '.' ) ;
}
2023-08-17 00:00:57 -07:00
}
2023-11-07 09:44:56 -06:00
}