Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

271 version 0015 updates (#284) #285

Merged
merged 1 commit into from
Jul 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 43 additions & 86 deletions inc/js/agents/system/asset-assistant.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,62 +9,63 @@ const bearerToken = MYLIFE_EMBEDDING_SERVER_BEARER_TOKEN
const fileSizeLimit = parseInt(MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT) || 1048576
const fileSizeLimitAdmin = parseInt(MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT_ADMIN) || 10485760
class oAIAssetAssistant {
#factory
#uploadedFileList=[] // uploaded versions
#globals
#includeMyLife=false
#llm
#mbr_id
#response
#vectorstoreId
#vectorstoreFileList // openai vectorstore versions
constructor(factory, globals, llm){
this.#factory = factory
#vectorstoreFileList=[] // openai vectorstore versions
constructor(mbr_id, globals, llm){
this.#mbr_id = mbr_id
this.#globals = globals
this.#llm = llm
this.#vectorstoreId = this.#factory.vectorstoreId
}
/**
* Initializes the asset assistant by uploading the files to the vectorstore and optionally embedding and enacting the files.
* Initializes the asset assistant by uploading the files to the vectorstore.
* @param {string} vectorstoreId - The vectorstore id to upload the files into, if already exists (avatar would know).
* @param {boolean} includeMyLife - Whether to embed and enact the files.
* @returns {Promise<oAIAssetAssistant>} - The initialized asset assistant instance.
*/
async init(includeMyLife=false){
async init(vectorstoreId){
if(!vectorstoreId?.length)
throw new Error('No vectorstoreId parameter. Please initialize the asset assistant correctly.')
this.#vectorstoreId = vectorstoreId
await this.updateVectorstoreFileList() // sets `this.#vectorstoreFileList`
this.#includeMyLife = includeMyLife
return this
}
/**
* Updates the vectorstore file list.
* @returns {Promise<void>} - Resolves when the vectorstore file list is updated.
*/
async updateVectorstoreFileList(){
if(this.#vectorstoreId?.length){
const updateList = (await this.#llm.files(this.#vectorstoreId)).data
.filter(file=>!(this.#vectorstoreFileList ?? []).find(vsFile=>vsFile.id===file.id))
if(updateList?.length){
this.#vectorstoreFileList = await Promise.all(
updateList.map(async file =>await this.#llm.file(file.id))
)
}
let updateList = (await this.#llm.files(this.#vectorstoreId)).data
.filter(file=>!(this.#vectorstoreFileList).find(vsFile=>vsFile.id===file.id))
if(updateList?.length){
updateList = await Promise.all(
updateList.map(async file =>await this.#llm.file(file.id))
)
this.#vectorstoreFileList.push(...updateList)
}
}
/**
* Uploads files to the vectorstore.
* @param {File[]} files - Array of uploaded files.
* @returns {Promise<void>} - Resolves when the files are uploaded.
*/
async upload(files){
if(!files || !files.length)
if(!Array.isArray(files) || !files.length)
throw new Error('No files found in request.')
const newFiles = []
files.forEach(file => {
const hasFile = this.#uploadedFileList.some(_file=>_file.originalName===file.originalName)
if(!hasFile)
newFiles.push(this.#extractFile(file))
})
if(newFiles.length){ // only upload new files
const vectorstoreId = this.#vectorstoreId
this.#uploadedFileList.push(...newFiles)
const fileStreams = newFiles.map(file=>fs.createReadStream(file.filepath))
const dataRecord = await this.#llm.upload(vectorstoreId, fileStreams, this.mbr_id)
const { response, vectorstoreId: newVectorstoreId, success } = dataRecord
this.#response = response
this.#vectorstoreId = newVectorstoreId
if(!vectorstoreId && newVectorstoreId)
this.#factory.vectorstoreId = newVectorstoreId // saves to datacore
if(success && this.#vectorstoreId?.length)
const uploadFiles = []
files
.forEach(file=>{
if(!this.#fileExists(file))
uploadFiles.push(this.#extractFile(file))
})
if(uploadFiles.length){ // only upload new files
console.log('upload::uploadFiles', uploadFiles)
const fileStreams = uploadFiles.map(file=>fs.createReadStream(file.filepath))
const dataRecord = await this.#llm.upload(this.#vectorstoreId, fileStreams, this.mbr_id)
const { response, success } = dataRecord
if(success)
await this.updateVectorstoreFileList()
}
files.forEach(file=>fs.unlinkSync(file.filepath)) /* delete .tmp files */
Expand All @@ -74,7 +75,7 @@ class oAIAssetAssistant {
return this.vectorstoreFileList
}
get mbr_id(){
return this.#factory.mbr_id
return this.#mbr_id
}
get response(){
return this.#response
Expand All @@ -85,54 +86,7 @@ class oAIAssetAssistant {
get vectorstoreId(){
return this.#vectorstoreId
}
// setters
// private functions
async #embedFile(){
const _metadata = {
source: 'corporate', // logickify
source_id: file.originalFilename,
url: 'testing-0001', // may or may not use url
author: 'MAHT', // convert to session member (or agent)
}
const _token = bearerToken
const _data = new FormData()
_data.append('file', fs.createReadStream(file.filepath), { contentType: file.mimetype })
_data.append('metadata', JSON.stringify(_metadata))
const _request = {
method: 'post',
maxBodyLength: Infinity,
url: 'http://localhost:8000/upsert-file',
headers: {
'Authorization': `Bearer ${_token}`,
..._data.getHeaders()
},
data : _data
}
return await axios.request(_request)
.then((response) => {
console.log(`#embedFile() finished: ${response.data.ids}`)
return response.data
})
.then(response=>{
if(this.#includeMyLife)
return this.#enactFile(response.data)
return response.data
})
.catch((error) => {
console.error(error.message)
return {'#embedFile() finished error': error.message }
})
}
async #enactFile(file){ // vitalizes by saving to MyLife database
console.log('#enactFile() begin')
const _fileContent = {
...file,
...{ mbr_id: this.mbr_id }
}
const oFile = new (AgentFactory.file)(_fileContent)
console.log('testing factory',oFile.inspect(true))
return oFile
}
/* private methods */
/**
* Takes an uploaded file object and extracts relevant file properties.
* @param {File} file - File object
Expand All @@ -157,6 +111,9 @@ class oAIAssetAssistant {
throw new Error('No files found in request.')
return files.map(file=>this.#extractFile(file))
}
#fileExists(file){
return this.#vectorstoreFileList?.some(vsFile=>vsFile.id===file.id || file.originalFilename==vsFile?.filename || file.newFilename==vsFile?.filename)
}
/**
* Validates a file object, _throws error_ if file is invalid.
* @param {File} file - File object
Expand Down
11 changes: 8 additions & 3 deletions inc/js/functions.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,17 @@ async function challenge(ctx){
* @param {Koa} ctx - Koa Context object
*/
async function chat(ctx){
const { botId, message, role, threadId, thread_id, } = ctx.request.body
const { botId, message, role, } = ctx.request.body
?? {} /* body nodes sent by fe */
if(!message?.length)
ctx.throw(400, 'missing `message` content')
const { avatar, } = ctx.state
const response = await avatar.chatRequest(botId, thread_id ?? threadId, message)
const { avatar, MemberSession, } = ctx.state
const { isMyLife, thread_id, } = MemberSession
if(isMyLife && !thread_id?.length){
const conversation = await avatar.createConversation('system', undefined, botId, true) // pushes to this.#conversations in Avatar
MemberSession.thread_id = conversation.thread_id
}
const response = await avatar.chatRequest(botId, MemberSession.thread_id, message)
ctx.body = response
}
async function collections(ctx){
Expand Down
62 changes: 29 additions & 33 deletions inc/js/mylife-agent-factory.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -318,8 +318,14 @@ class BotFactory extends EventEmitter{
async collections(type){
return await this.dataservices.collections(type)
}
async createBot(assistantData={ type: mDefaultBotType }){
const bot = await mCreateBot(this.#llmServices, this, assistantData)
/**
*
* @param {object} assistantData - The assistant data.
* @param {string} vectorstoreId - The vectorstore id.
* @returns {object} - The created bot.
*/
async createBot(assistantData={ type: mDefaultBotType }, vectorstoreId){
const bot = await mCreateBot(this.#llmServices, this, assistantData, vectorstoreId)
if(!bot)
throw new Error('bot creation failed')
return bot
Expand Down Expand Up @@ -492,10 +498,10 @@ class BotFactory extends EventEmitter{
* Adds or updates a bot data in MyLife database. Note that when creating, pre-fill id.
* @public
* @param {object} bot - The bot data.
* @param {object} options - Function options: `{ instructions: boolean, model: boolean, tools: boolean }`. Meant to express whether or not these elements should be refreshed. Useful during updates.
* @returns {object} - The Cosmos bot.
* @param {object} options - Function options: `{ instructions: boolean, model: boolean, tools: boolean, vectorstoreId: string, }`.
* @returns {object} - The Cosmos bot version.
*/
async updateBot(bot, options){
async updateBot(bot, options={}){
return await mUpdateBot(this, this.#llmServices, bot, options)
}
/* getters/setters */
Expand Down Expand Up @@ -928,32 +934,15 @@ class AgentFactory extends BotFactory {
get urlEmbeddingServer(){
return process.env.MYLIFE_EMBEDDING_SERVER_URL+':'+process.env.MYLIFE_EMBEDDING_SERVER_PORT
}
get vectorstoreId(){
return this.core.vectorstoreId
}
set vectorstoreId(vectorstoreId){
/* validate vectorstoreId */
if(!vectorstoreId?.length)
throw new Error('vectorstoreId required')
this.dataservices.patch(this.core.id, { vectorstoreId, }) /* no await */
this.core.vectorstoreId = vectorstoreId /* update local */
}
}
class MyLifeFactory extends AgentFactory {
#accountCreation
#dataservices = mDataservices
#llmServices = mLLMServices
#registrationData
constructor(){
super(mPartitionId)
} // no init() for MyLife server
/* public functions */
async addMember(mbr_id){
if(!this.globals.isValidGuid(mbr_id))
return
// Me! Q! I do it! I do it! I do it!
/* but because Q needs to do it, how do I get up one level to avatar? */
}
/**
* Compares registration email against supplied email to confirm `true`. **Note**: does not care if user enters an improper email, it will only fail the encounter, as email structure _is_ confirmed upon initial data write.
* @param {string} email - The supplied email to confirm registration.
Expand Down Expand Up @@ -1088,7 +1077,7 @@ class MyLifeFactory extends AgentFactory {
}
} catch(error){
this.#registrationData = null
console.log(chalk.blueBright(`validateRegistration(${ registrationId })::error`))
console.log(chalk.blueBright(`validateRegistration(${ registrationId })::error`), error.message)
}
return this.#registrationData
}
Expand Down Expand Up @@ -1210,7 +1199,7 @@ async function mCreateBotLLM(llm, assistantData){
* @param {object} bot - Bot object, must include `type` property.
* @returns {object} - Bot object
*/
async function mCreateBot(llm, factory, bot){
async function mCreateBot(llm, factory, bot, vectorstoreId){
/* initial deconstructions */
const { bot_name: botName, description: botDescription, name: botDbName, type, } = bot
const { avatarId, } = factory
Expand All @@ -1228,15 +1217,18 @@ async function mCreateBot(llm, factory, bot){
?? 'gpt-4o'
const name = botDbName
?? `bot_${ type }_${ avatarId }`
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, factory.vectorstoreId)
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
const id = factory.newGuid
const assistantData = {
being: 'bot',
bot_name,
description,
id,
instructions,
metadata: { externalId: id, },
metadata: {
externalId: id,
version: version.toString(),
},
model,
name,
object_id: avatarId,
Expand All @@ -1254,7 +1246,7 @@ async function mCreateBot(llm, factory, bot){
/* create in MyLife datastore */
assistantData.bot_id = botId
const assistant = await factory.dataservices.createBot(assistantData)
console.log(chalk.green(`bot created::${ type }`), assistant)
console.log(chalk.green(`bot created::${ type }`), assistant.id, assistant.bot_id, assistant.bot_name, )
return assistant
}
/**
Expand Down Expand Up @@ -1805,13 +1797,13 @@ function mSanitizeSchemaValue(_value) {
* @param {AgentFactory} factory - Factory object
* @param {LLMServices} llm - LLMServices object
* @param {object} bot - Bot object, winnow via mBot in `mylife-avatar.mjs` to only updated fields
* @param {object} options - Options object: { instructions: boolean, model: boolean, tools: boolean }
* @param {object} options - Options object: { instructions: boolean, model: boolean, tools: boolean, vectorstoreId: string, }
* @returns
*/
async function mUpdateBot(factory, llm, bot, options={}){
/* constants */
const {
id, // no modifications; see below
id, // no modifications
instructions: removeInstructions,
tools: removeTools,
tool_resources: removeResources,
Expand All @@ -1822,30 +1814,34 @@ async function mUpdateBot(factory, llm, bot, options={}){
instructions: updateInstructions=false,
model: updateModel=false,
tools: updateTools=false,
vectorstoreId,
} = options
if(!factory.globals.isValidGuid(id))
throw new Error('bot `id` required in bot argument: `{ id: guid }`')
if(updateInstructions){
const { instructions, version=1.0, } = mCreateBotInstructions(factory, bot)
botData.instructions = instructions
botData.metadata = botData.metadata ?? {}
botData.metadata.version = version.toString()
botData.version = version /* omitted from llm, but appears on updateBot */
}
if(updateTools){
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, factory.vectorstoreId)
console.log('mUpdateBot', vectorstoreId)
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
botData.tools = tools
botData.tool_resources = tool_resources
}
if(updateModel)
botData.model = factory.globals.currentOpenAIBotModel
botData.id = id // validated
/* LLM updates */
const { bot_id, bot_name: name, instructions, metadata, tools, } = botData
if(bot_id?.length && (instructions || metadata || name || tools)){
const { bot_id, bot_name: name, instructions, tools, } = botData
if(bot_id?.length && (instructions || name || tools)){
botData.model = factory.globals.currentOpenAIBotModel // not dynamic
await llm.updateBot(botData)
const updatedLLMFields = Object.keys(botData)
.filter(key=>key!=='id' && key!=='bot_id') // strip mechanicals
console.log(chalk.green('mUpdateBot()::update in LLM'), bot_id, id, updatedLLMFields)
console.log(chalk.green('mUpdateBot()::update in OpenAI'), id, bot_id, updatedLLMFields)
}
const updatedBot = await factory.dataservices.updateBot(botData)
return updatedBot
Expand Down
Loading