2024-02-25 14:13:32 -06:00
import { readFileSync , mkdirSync , writeFileSync } from 'fs' ;
import { Examples } from '../utils/examples.js' ;
2025-02-04 13:02:57 -06:00
import { getCommandDocs } from '../agent/commands/index.js' ;
import { getSkillDocs } from '../agent/library/index.js' ;
2025-02-05 15:14:33 -06:00
import { SkillLibrary } from "../agent/library/skill_library.js" ;
2024-02-25 14:13:32 -06:00
import { stringifyTurns } from '../utils/text.js' ;
2025-02-04 13:02:57 -06:00
import { getCommand } from '../agent/commands/index.js' ;
2024-12-17 00:33:18 +08:00
import settings from '../../settings.js' ;
2024-02-25 14:13:32 -06:00
2025-02-04 13:02:57 -06:00
import { Gemini } from './gemini.js' ;
import { GPT } from './gpt.js' ;
import { Claude } from './claude.js' ;
import { Mistral } from './mistral.js' ;
import { ReplicateAPI } from './replicate.js' ;
import { Local } from './local.js' ;
import { Novita } from './novita.js' ;
import { GroqCloudAPI } from './groq.js' ;
import { HuggingFace } from './huggingface.js' ;
import { Qwen } from "./qwen.js" ;
import { Grok } from "./grok.js" ;
import { DeepSeek } from './deepseek.js' ;
2025-02-17 15:59:17 -06:00
import { OpenRouter } from './openrouter.js' ;
2025-02-05 15:14:33 -06:00
2024-02-25 14:13:32 -06:00
export class Prompter {
constructor ( agent , fp ) {
2024-04-24 11:28:04 -07:00
this . agent = agent ;
2024-06-03 18:23:29 -05:00
this . profile = JSON . parse ( readFileSync ( fp , 'utf8' ) ) ;
2025-02-05 15:58:40 -06:00
let default _profile = JSON . parse ( readFileSync ( './profiles/defaults/_default.json' , 'utf8' ) ) ;
2025-02-05 16:18:33 -06:00
let base _fp = settings . base _profile ;
let base _profile = JSON . parse ( readFileSync ( base _fp , 'utf8' ) ) ;
2025-02-05 15:58:40 -06:00
2025-02-05 16:18:33 -06:00
// first use defaults to fill in missing values in the base profile
2025-02-05 15:58:40 -06:00
for ( let key in default _profile ) {
2025-02-05 16:18:33 -06:00
if ( base _profile [ key ] === undefined )
base _profile [ key ] = default _profile [ key ] ;
2025-02-05 15:58:40 -06:00
}
2025-02-05 16:18:33 -06:00
// then use base profile to fill in missing values in the individual profile
2025-02-05 15:58:40 -06:00
for ( let key in base _profile ) {
2024-11-21 23:29:52 -06:00
if ( this . profile [ key ] === undefined )
2025-02-05 15:58:40 -06:00
this . profile [ key ] = base _profile [ key ] ;
2024-11-21 23:29:52 -06:00
}
2025-02-05 16:18:33 -06:00
// base overrides default, individual overrides base
2024-11-21 23:29:52 -06:00
2025-02-05 15:58:40 -06:00
2024-04-24 11:28:04 -07:00
this . convo _examples = null ;
this . coding _examples = null ;
2024-10-12 20:40:16 -05:00
2024-06-03 18:23:29 -05:00
let name = this . profile . name ;
2024-10-12 20:40:16 -05:00
this . cooldown = this . profile . cooldown ? this . profile . cooldown : 0 ;
this . last _prompt _time = 0 ;
2024-12-04 20:30:57 -06:00
this . awaiting _coding = false ;
2024-10-12 20:40:16 -05:00
2024-08-31 15:29:34 -07:00
// try to get "max_tokens" parameter, else null
let max _tokens = null ;
if ( this . profile . max _tokens )
max _tokens = this . profile . max _tokens ;
2024-04-24 11:28:04 -07:00
2025-01-25 12:26:22 -06:00
let chat _model _profile = this . _selectAPI ( this . profile . model ) ;
this . chat _model = this . _createModel ( chat _model _profile ) ;
2024-04-24 11:28:04 -07:00
2025-01-25 12:26:22 -06:00
if ( this . profile . code _model ) {
let code _model _profile = this . _selectAPI ( this . profile . code _model ) ;
this . code _model = this . _createModel ( code _model _profile ) ;
}
else {
this . code _model = this . chat _model ;
2024-08-31 15:29:34 -07:00
}
2024-04-24 11:28:04 -07:00
2024-06-03 18:23:29 -05:00
let embedding = this . profile . embedding ;
2024-06-01 16:23:14 -05:00
if ( embedding === undefined ) {
2025-01-25 12:26:22 -06:00
if ( chat _model _profile . api !== 'ollama' )
embedding = { api : chat _model _profile . api } ;
2024-06-01 16:23:14 -05:00
else
embedding = { api : 'none' } ;
}
2024-04-24 11:28:04 -07:00
else if ( typeof embedding === 'string' || embedding instanceof String )
embedding = { api : embedding } ;
console . log ( 'Using embedding settings:' , embedding ) ;
2024-11-07 11:35:09 -06:00
try {
if ( embedding . api === 'google' )
this . embedding _model = new Gemini ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'openai' )
this . embedding _model = new GPT ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'replicate' )
this . embedding _model = new ReplicateAPI ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'ollama' )
this . embedding _model = new Local ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'qwen' )
this . embedding _model = new Qwen ( embedding . model , embedding . url ) ;
2025-01-06 20:40:12 -06:00
else if ( embedding . api === 'mistral' )
this . embedding _model = new Mistral ( embedding . model , embedding . url ) ;
2025-02-17 13:13:45 -06:00
else if ( embedding . api === 'huggingface' )
this . embedding _model = new HuggingFace ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'groq' )
this . embedding _model = new GroqCloudAPI ( embedding . model , embedding . url ) ;
else if ( embedding . api === 'novita' )
this . embedding _model = new Novita ( embedding . model , embedding . url ) ;
2024-11-07 11:35:09 -06:00
else {
this . embedding _model = null ;
2025-02-17 13:13:45 -06:00
let embedding _name = embedding ? embedding . api : '[NOT SPECIFIED]'
console . warn ( 'Unsupported embedding: ' + embedding _name + '. Using word-overlap instead, expect reduced performance. Recommend using a supported embedding model. See Readme.' ) ;
2024-11-07 11:35:09 -06:00
}
}
catch ( err ) {
2025-02-17 13:13:45 -06:00
console . warn ( 'Warning: Failed to initialize embedding model:' , err . message ) ;
console . log ( 'Continuing anyway, using word-overlap instead.' ) ;
2024-04-24 11:28:04 -07:00
this . embedding _model = null ;
}
2024-12-15 00:05:12 +08:00
this . skill _libary = new SkillLibrary ( agent , this . embedding _model ) ;
2024-02-25 14:13:32 -06:00
mkdirSync ( ` ./bots/ ${ name } ` , { recursive : true } ) ;
2024-06-03 18:23:29 -05:00
writeFileSync ( ` ./bots/ ${ name } /last_profile.json ` , JSON . stringify ( this . profile , null , 4 ) , ( err ) => {
2024-02-25 14:13:32 -06:00
if ( err ) {
2024-11-07 10:25:49 -06:00
throw new Error ( 'Failed to save profile:' , err ) ;
2024-02-25 14:13:32 -06:00
}
console . log ( "Copy profile saved." ) ;
} ) ;
}
2025-01-25 12:26:22 -06:00
_selectAPI ( profile ) {
if ( typeof profile === 'string' || profile instanceof String ) {
profile = { model : profile } ;
2025-02-04 13:02:57 -06:00
}
if ( ! profile . api ) {
2025-01-25 12:26:22 -06:00
if ( profile . model . includes ( 'gemini' ) )
profile . api = 'google' ;
2025-02-17 15:59:17 -06:00
else if ( profile . model . includes ( 'openrouter/' ) )
profile . api = 'openrouter' ; // must do before others bc shares model names
2025-02-03 18:35:58 -06:00
else if ( profile . model . includes ( 'gpt' ) || profile . model . includes ( 'o1' ) || profile . model . includes ( 'o3' ) )
2025-01-25 12:26:22 -06:00
profile . api = 'openai' ;
else if ( profile . model . includes ( 'claude' ) )
profile . api = 'anthropic' ;
else if ( profile . model . includes ( 'huggingface/' ) )
profile . api = "huggingface" ;
2025-02-04 13:02:57 -06:00
else if ( profile . model . includes ( 'replicate/' ) )
2025-01-25 12:26:22 -06:00
profile . api = 'replicate' ;
else if ( profile . model . includes ( 'mistralai/' ) || profile . model . includes ( "mistral/" ) )
model _profile . api = 'mistral' ;
else if ( profile . model . includes ( "groq/" ) || profile . model . includes ( "groqcloud/" ) )
profile . api = 'groq' ;
else if ( profile . model . includes ( 'novita/' ) )
profile . api = 'novita' ;
else if ( profile . model . includes ( 'qwen' ) )
profile . api = 'qwen' ;
else if ( profile . model . includes ( 'grok' ) )
profile . api = 'xai' ;
else if ( profile . model . includes ( 'deepseek' ) )
profile . api = 'deepseek' ;
else
profile . api = 'ollama' ;
}
return profile ;
}
_createModel ( profile ) {
let model = null ;
if ( profile . api === 'google' )
2025-02-04 13:02:57 -06:00
model = new Gemini ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'openai' )
2025-02-04 13:02:57 -06:00
model = new GPT ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'anthropic' )
2025-02-04 13:02:57 -06:00
model = new Claude ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'replicate' )
2025-02-17 15:59:17 -06:00
model = new ReplicateAPI ( profile . model . replace ( 'replicate/' , '' ) , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'ollama' )
2025-02-04 13:02:57 -06:00
model = new Local ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'mistral' )
2025-02-04 13:02:57 -06:00
model = new Mistral ( profile . model , profile . url , profile . params ) ;
2025-02-05 17:05:55 +08:00
else if ( profile . api === 'groq' )
2025-02-04 13:02:57 -06:00
model = new GroqCloudAPI ( profile . model . replace ( 'groq/' , '' ) . replace ( 'groqcloud/' , '' ) , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'huggingface' )
2025-02-04 13:02:57 -06:00
model = new HuggingFace ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'novita' )
2025-02-04 13:02:57 -06:00
model = new Novita ( profile . model . replace ( 'novita/' , '' ) , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'qwen' )
2025-02-04 13:02:57 -06:00
model = new Qwen ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'xai' )
2025-02-04 13:02:57 -06:00
model = new Grok ( profile . model , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else if ( profile . api === 'deepseek' )
2025-02-04 13:02:57 -06:00
model = new DeepSeek ( profile . model , profile . url , profile . params ) ;
2025-02-17 15:59:17 -06:00
else if ( profile . api === 'openrouter' )
model = new OpenRouter ( profile . model . replace ( 'openrouter/' , '' ) , profile . url , profile . params ) ;
2025-01-25 12:26:22 -06:00
else
2025-02-04 13:02:57 -06:00
throw new Error ( 'Unknown API:' , profile . api ) ;
2025-01-25 12:26:22 -06:00
return model ;
}
2024-02-25 14:13:32 -06:00
getName ( ) {
2024-06-03 18:23:29 -05:00
return this . profile . name ;
}
getInitModes ( ) {
return this . profile . modes ;
2024-02-25 14:13:32 -06:00
}
async initExamples ( ) {
2024-11-07 10:25:49 -06:00
try {
2025-01-09 15:16:35 -06:00
this . convo _examples = new Examples ( this . embedding _model , settings . num _examples ) ;
this . coding _examples = new Examples ( this . embedding _model , settings . num _examples ) ;
2024-11-07 10:25:49 -06:00
2024-11-25 13:05:02 -06:00
// Wait for both examples to load before proceeding
await Promise . all ( [
2024-11-07 10:25:49 -06:00
this . convo _examples . load ( this . profile . conversation _examples ) ,
2024-11-08 18:33:57 +08:00
this . coding _examples . load ( this . profile . coding _examples ) ,
2024-12-15 00:05:12 +08:00
this . skill _libary . initSkillLibrary ( )
2024-11-07 10:25:49 -06:00
] ) ;
2024-11-25 17:15:23 -06:00
console . log ( 'Examples initialized.' ) ;
2024-11-07 10:25:49 -06:00
} catch ( error ) {
console . error ( 'Failed to initialize examples:' , error ) ;
throw error ;
}
2024-02-25 14:13:32 -06:00
}
2024-10-10 22:15:55 -05:00
async replaceStrings ( prompt , messages , examples = null , to _summarize = [ ] , last _goals = null ) {
2024-02-25 14:13:32 -06:00
prompt = prompt . replaceAll ( '$NAME' , this . agent . name ) ;
if ( prompt . includes ( '$STATS' ) ) {
let stats = await getCommand ( '!stats' ) . perform ( this . agent ) ;
prompt = prompt . replaceAll ( '$STATS' , stats ) ;
}
2024-04-02 19:18:13 -05:00
if ( prompt . includes ( '$INVENTORY' ) ) {
let inventory = await getCommand ( '!inventory' ) . perform ( this . agent ) ;
prompt = prompt . replaceAll ( '$INVENTORY' , inventory ) ;
}
2024-11-21 23:29:52 -06:00
if ( prompt . includes ( '$ACTION' ) ) {
prompt = prompt . replaceAll ( '$ACTION' , this . agent . actions . currentActionLabel ) ;
}
2024-02-25 14:13:32 -06:00
if ( prompt . includes ( '$COMMAND_DOCS' ) )
2025-01-09 15:15:25 -06:00
prompt = prompt . replaceAll ( '$COMMAND_DOCS' , getCommandDocs ( ) ) ;
2024-12-17 00:33:18 +08:00
if ( prompt . includes ( '$CODE_DOCS' ) ) {
const code _task _content = messages . slice ( ) . reverse ( ) . find ( msg =>
2024-11-09 01:29:24 +08:00
msg . role !== 'system' && msg . content . includes ( '!newAction(' )
2024-12-17 00:33:18 +08:00
) ? . content ? . match ( /!newAction\((.*?)\)/ ) ? . [ 1 ] || '' ;
2024-11-09 01:29:24 +08:00
prompt = prompt . replaceAll (
'$CODE_DOCS' ,
2024-12-17 00:33:18 +08:00
await this . skill _libary . getRelevantSkillDocs ( code _task _content , settings . relevant _docs _count )
2024-11-09 01:29:24 +08:00
) ;
2024-11-02 20:26:56 +08:00
}
2025-01-19 18:28:17 +08:00
prompt = prompt . replaceAll ( '$COMMAND_DOCS' , getCommandDocs ( ) ) ;
2024-02-25 14:13:32 -06:00
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' ) )
2024-10-10 22:15:55 -05:00
prompt = prompt . replaceAll ( '$MEMORY' , this . agent . history . memory ) ;
2024-02-25 14:13:32 -06:00
if ( prompt . includes ( '$TO_SUMMARIZE' ) )
prompt = prompt . replaceAll ( '$TO_SUMMARIZE' , stringifyTurns ( to _summarize ) ) ;
2024-04-24 13:34:09 -07:00
if ( prompt . includes ( '$CONVO' ) )
prompt = prompt . replaceAll ( '$CONVO' , 'Recent conversation:\n' + stringifyTurns ( messages ) ) ;
2024-06-23 20:15:28 -05:00
if ( prompt . includes ( '$SELF_PROMPT' ) ) {
2024-10-10 22:15:55 -05:00
let self _prompt = this . agent . self _prompter . on ? ` YOUR CURRENT ASSIGNED GOAL: " ${ this . agent . self _prompter . prompt } " \n ` : '' ;
2024-06-23 20:15:28 -05:00
prompt = prompt . replaceAll ( '$SELF_PROMPT' , self _prompt ) ;
}
2024-04-24 13:34:09 -07:00
if ( prompt . includes ( '$LAST_GOALS' ) ) {
let goal _text = '' ;
for ( let goal in last _goals ) {
if ( last _goals [ goal ] )
goal _text += ` You recently successfully completed the goal ${ goal } . \n `
else
goal _text += ` You recently failed to complete the goal ${ goal } . \n `
}
prompt = prompt . replaceAll ( '$LAST_GOALS' , goal _text . trim ( ) ) ;
}
if ( prompt . includes ( '$BLUEPRINTS' ) ) {
if ( this . agent . npc . constructions ) {
let blueprints = '' ;
for ( let blueprint in this . agent . npc . constructions ) {
blueprints += blueprint + ', ' ;
}
prompt = prompt . replaceAll ( '$BLUEPRINTS' , blueprints . slice ( 0 , - 2 ) ) ;
}
}
2024-02-25 14:13:32 -06:00
// check if there are any remaining placeholders with syntax $<word>
let remaining = prompt . match ( /\$[A-Z_]+/g ) ;
if ( remaining !== null ) {
2024-04-02 19:18:13 -05:00
console . warn ( 'Unknown prompt placeholders:' , remaining . join ( ', ' ) ) ;
2024-02-25 14:13:32 -06:00
}
return prompt ;
}
2024-10-12 20:40:16 -05:00
async checkCooldown ( ) {
let elapsed = Date . now ( ) - this . last _prompt _time ;
if ( elapsed < this . cooldown && this . cooldown > 0 ) {
await new Promise ( r => setTimeout ( r , this . cooldown - elapsed ) ) ;
}
this . last _prompt _time = Date . now ( ) ;
}
2024-02-25 14:13:32 -06:00
async promptConvo ( messages ) {
2024-12-07 17:54:49 -06:00
this . most _recent _msg _time = Date . now ( ) ;
let current _msg _time = this . most _recent _msg _time ;
2024-11-19 22:45:47 -06:00
for ( let i = 0 ; i < 3 ; i ++ ) { // try 3 times to avoid hallucinations
await this . checkCooldown ( ) ;
2024-12-18 11:21:05 -06:00
if ( current _msg _time !== this . most _recent _msg _time ) {
return '' ;
}
2024-11-19 22:45:47 -06:00
let prompt = this . profile . conversing ;
prompt = await this . replaceStrings ( prompt , messages , this . convo _examples ) ;
let generation = await this . chat _model . sendRequest ( messages , prompt ) ;
// in conversations >2 players LLMs tend to hallucinate and role-play as other bots
// the FROM OTHER BOT tag should never be generated by the LLM
if ( generation . includes ( '(FROM OTHER BOT)' ) ) {
console . warn ( 'LLM hallucinated message as another bot. Trying again...' ) ;
continue ;
}
2024-12-07 17:54:49 -06:00
if ( current _msg _time !== this . most _recent _msg _time ) {
2025-01-06 14:59:51 +08:00
console . warn ( this . agent . name + ' received new message while generating, discarding old response.' ) ;
2024-12-07 17:54:49 -06:00
return '' ;
}
2024-11-19 22:45:47 -06:00
return generation ;
}
2024-12-05 16:06:05 -06:00
return '' ;
2024-02-25 14:13:32 -06:00
}
async promptCoding ( messages ) {
2024-12-04 20:30:57 -06:00
if ( this . awaiting _coding ) {
console . warn ( 'Already awaiting coding response, returning no response.' ) ;
2024-12-05 16:06:05 -06:00
return '```//no response```' ;
2024-12-04 20:30:57 -06:00
}
this . awaiting _coding = true ;
2024-10-12 20:40:16 -05:00
await this . checkCooldown ( ) ;
2024-06-03 18:23:29 -05:00
let prompt = this . profile . coding ;
2024-02-25 14:13:32 -06:00
prompt = await this . replaceStrings ( prompt , messages , this . coding _examples ) ;
2025-01-25 12:26:22 -06:00
let resp = await this . code _model . sendRequest ( messages , prompt ) ;
2024-12-04 20:30:57 -06:00
this . awaiting _coding = false ;
return resp ;
2024-02-25 14:13:32 -06:00
}
2024-10-10 22:15:55 -05:00
async promptMemSaving ( to _summarize ) {
2024-10-12 20:40:16 -05:00
await this . checkCooldown ( ) ;
2024-06-03 18:23:29 -05:00
let prompt = this . profile . saving _memory ;
2024-10-10 22:15:55 -05:00
prompt = await this . replaceStrings ( prompt , null , null , to _summarize ) ;
2024-04-24 11:28:04 -07:00
return await this . chat _model . sendRequest ( [ ] , prompt ) ;
2024-02-25 14:13:32 -06:00
}
2024-04-23 20:47:01 -07:00
2024-11-21 23:29:52 -06:00
async promptShouldRespondToBot ( new _message ) {
await this . checkCooldown ( ) ;
let prompt = this . profile . bot _responder ;
let messages = this . agent . history . getHistory ( ) ;
messages . push ( { role : 'user' , content : new _message } ) ;
prompt = await this . replaceStrings ( prompt , null , null , messages ) ;
let res = await this . chat _model . sendRequest ( [ ] , prompt ) ;
return res . trim ( ) . toLowerCase ( ) === 'respond' ;
}
2024-04-23 20:47:01 -07:00
async promptGoalSetting ( messages , last _goals ) {
2024-06-03 18:23:29 -05:00
let system _message = this . profile . goal _setting ;
2024-04-24 13:34:09 -07:00
system _message = await this . replaceStrings ( system _message , messages ) ;
let user _message = 'Use the below info to determine what goal to target next\n\n' ;
user _message += '$LAST_GOALS\n$STATS\n$INVENTORY\n$CONVO'
2024-10-10 22:15:55 -05:00
user _message = await this . replaceStrings ( user _message , messages , null , null , last _goals ) ;
2024-04-24 13:34:09 -07:00
let user _messages = [ { role : 'user' , content : user _message } ] ;
2024-04-30 13:31:51 -07:00
let res = await this . chat _model . sendRequest ( user _messages , system _message ) ;
2024-04-24 13:34:09 -07:00
let goal = null ;
try {
let data = res . split ( '```' ) [ 1 ] . replace ( 'json' , '' ) . trim ( ) ;
goal = JSON . parse ( data ) ;
} catch ( err ) {
console . log ( 'Failed to parse goal:' , res , err ) ;
}
if ( ! goal || ! goal . name || ! goal . quantity || isNaN ( parseInt ( goal . quantity ) ) ) {
console . log ( 'Failed to set goal:' , res ) ;
return null ;
}
goal . quantity = parseInt ( goal . quantity ) ;
return goal ;
2024-04-23 20:47:01 -07:00
}
2024-04-30 13:31:51 -07:00
}