From 3b43b798c02e083d7658037a06394988ba8f0f40 Mon Sep 17 00:00:00 2001
From: Erik Jespersen <42016062+Mookse@users.noreply.github.com>
Date: Tue, 8 Oct 2024 00:36:14 -0400
Subject: [PATCH 01/56] 382 version 0024 updates (#403)
---
inc/js/globals.mjs | 88 +++++++-------
inc/js/mylife-agent-factory.mjs | 48 ++++----
inc/js/mylife-avatar.mjs | 16 ++-
inc/js/mylife-llm-services.mjs | 89 ++++++++++----
.../biographer-intelligence-1.5.json | 51 ++++++++
.../intelligences/diary-intelligence-1.0.json | 4 +-
.../journaler-intelligence-1.1.json | 60 +++++++++
.../openai/functions/changeTitle.json | 23 ++++
.../openai/functions/entrySummary.json | 32 +++--
.../openai/functions/getSummary.json | 6 +-
.../openai/functions/obscure.json | 18 +++
.../openai/functions/storySummary.json | 27 ++---
.../openai/functions/updateSummary.json | 7 +-
server.js | 2 +-
views/assets/css/bots.css | 67 +++++++---
views/assets/html/_bots.html | 77 +++++++++++-
views/assets/js/bots.mjs | 114 ++++++++++++++++--
views/assets/js/members.mjs | 36 ++++--
18 files changed, 590 insertions(+), 175 deletions(-)
create mode 100644 inc/json-schemas/intelligences/biographer-intelligence-1.5.json
create mode 100644 inc/json-schemas/intelligences/journaler-intelligence-1.1.json
create mode 100644 inc/json-schemas/openai/functions/changeTitle.json
create mode 100644 inc/json-schemas/openai/functions/obscure.json
diff --git a/inc/js/globals.mjs b/inc/js/globals.mjs
index 14130f40..b616cbb2 100644
--- a/inc/js/globals.mjs
+++ b/inc/js/globals.mjs
@@ -4,22 +4,22 @@ import { Guid } from 'js-guid' // usage = Guid.newGuid().toString()
/* constants */
const mAiJsFunctions = {
changeTitle: {
- description: 'Change the title of a memory summary in the database for an itemId',
name: 'changeTitle',
+ description: 'Change the title of a summary in the database for an itemId',
+ strict: true,
parameters: {
type: 'object',
properties: {
itemId: {
- description: 'itemId of memory item to update',
- format: 'uuid',
+ description: 'itemId to update',
type: 'string'
},
title: {
description: 'The new title for the summary',
- maxLength: 256,
type: 'string'
}
},
+ additionalProperties: false,
required: [
'itemId',
'title'
@@ -27,111 +27,109 @@ const mAiJsFunctions = {
}
},
entrySummary: {
- description: 'Generate a JOURNAL ENTRY `entry` summary with keywords and other critical data elements.',
+ description: 'Generate `entry` summary with keywords and other critical data elements.',
name: 'entrySummary',
+ strict: true,
parameters: {
type: 'object',
properties: {
content: {
- description: 'concatenated raw text content of member input for JOURNAL ENTRY.',
+ description: 'complete concatenated raw text content of member input(s) for this `entry`',
+ type: 'string'
},
keywords: {
- description: 'Keywords most relevant to JOURNAL ENTRY.',
+ description: 'Keywords most relevant to `entry`.',
items: {
- description: 'Keyword (single word or short phrase) to be used in JOURNAL ENTRY summary.',
- maxLength: 64,
+ description: 'Keyword (single word or short phrase) to be used in `entry` summary',
type: 'string'
},
- maxItems: 12,
- minItems: 3,
type: 'array'
},
mood: {
- description: 'Record member mood for day (or entry) in brief as ascertained from content of JOURNAL ENTRY.',
- maxLength: 256,
+ description: 'Record member mood for day (or entry) in brief as ascertained from content of `entry`',
type: 'string'
},
relationships: {
- description: 'Record individuals (or pets) mentioned in this `entry`.',
+ description: 'Record individuals (or pets) mentioned in this `entry`',
type: 'array',
items: {
- description: 'A name of relational individual/pet to the `entry` content.',
+ description: 'A name of relational individual/pet to the `entry` content',
type: 'string'
- },
- maxItems: 24
+ }
},
summary: {
- description: 'Generate a JOURNAL ENTRY summary from input.',
- maxLength: 20480,
+ description: 'Generate `entry` summary from member input',
type: 'string'
},
title: {
- description: 'Generate display Title of the JOURNAL ENTRY.',
- maxLength: 256,
+ description: 'Generate display Title of the `entry`',
type: 'string'
}
},
+ additionalProperties: false,
required: [
- 'content',
- 'keywords',
- 'summary',
- 'title'
+ 'content',
+ 'keywords',
+ 'mood',
+ 'relationships',
+ 'summary',
+ 'title'
]
}
},
getSummary: {
description: "Gets a story summary by itemId",
name: "getSummary",
+ strict: true,
parameters: {
type: "object",
properties: {
itemId: {
description: "Id of summary to retrieve",
- format: "uuid",
type: "string"
}
},
+ additionalProperties: false,
required: [
"itemId"
]
}
},
obscure: {
- description: "Obscures a summary so that no human names are present.",
+ description: "Obscures a summary so that no human names are present",
name: "obscure",
+ strict: true,
parameters: {
type: "object",
properties: {
itemId: {
description: "Id of summary to obscure",
- format: "uuid",
type: "string"
}
},
+ additionalProperties: false,
required: [
"itemId"
]
}
},
storySummary: {
- description: 'Generate a complete multi-paragraph STORY summary with keywords and other critical data elements.',
+ description: 'Generate a complete `story` summary with metadata elements',
name: 'storySummary',
+ strict: true,
parameters: {
type: 'object',
properties: {
keywords: {
- description: 'Keywords most relevant to STORY.',
+ description: 'Keywords most relevant to `story`',
items: {
- description: 'Keyword (single word or short phrase) to be used in STORY summary.',
- maxLength: 64,
+ description: 'Keyword from `story` summary',
type: 'string'
},
- maxItems: 12,
- minItems: 3,
type: 'array'
},
phaseOfLife: {
- description: 'Phase of life indicated in STORY.',
+ description: 'Phase of life indicated in `story`',
enum: [
'birth',
'childhood',
@@ -146,45 +144,44 @@ const mAiJsFunctions = {
'unknown',
'other'
],
- maxLength: 64,
type: 'string'
},
relationships: {
- description: 'MyLife Biographer Bot does its best to record individuals (or pets) mentioned in this `story`.',
+ description: 'Individuals (or pets) mentioned in `story`',
type: 'array',
items: {
- description: 'A name of relational individual/pet to the `story` content.',
+ description: 'Name of individual or pet in `story`',
type: 'string'
- },
- maxItems: 24
+ }
},
summary: {
- description: 'A complete multi-paragraph STORY summary composed from relevant user input.',
+ description: 'A complete `story` summary composed of all salient points from member input',
type: 'string'
},
title: {
- description: 'Generate display Title of the STORY.',
- maxLength: 256,
+ description: 'Generate display Title for `story`',
type: 'string'
}
},
+ additionalProperties: false,
required: [
'keywords',
'phaseOfLife',
+ "relationships",
'summary',
'title'
]
}
},
updateSummary: {
- description: "Updates a story summary (in total) as referenced by itemId",
+ description: "Updates (overwrites) the summary referenced by itemId",
name: "updateSummary",
+ strict: true,
parameters: {
type: "object",
properties: {
itemId: {
description: "Id of summary to update",
- format: "uuid",
type: "string"
},
summary: {
@@ -192,6 +189,7 @@ const mAiJsFunctions = {
type: "string"
}
},
+ additionalProperties: false,
required: [
"itemId",
"summary"
diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-agent-factory.mjs
index 0333459f..050a64d6 100644
--- a/inc/js/mylife-agent-factory.mjs
+++ b/inc/js/mylife-agent-factory.mjs
@@ -40,7 +40,7 @@ const mExcludeProperties = {
definitions: true,
name: true
}
-const mGeneralBotId = 'asst_piDEJKYjqvAZbLstjd6u0ZMb'
+const mGeneralBotId = 'asst_yhX5mohHmZTXNIH55FX2BR1m'
const mLLMServices = new LLMServices()
const mMyLifeTeams = [
{
@@ -121,17 +121,7 @@ const mReservedJSWords = ['break', 'case', 'catch', 'class', 'const', 'continue'
const mShadows = [
{
being: 'shadow',
- categories: ['world events'],
- form: 'story',
- id: 'e3701fa2-7cc8-4a47-bcda-a5b52d3d2e2f',
- name: 'shadow_e3701fa2-7cc8-4a47-bcda-a5b52d3d2e2f',
- proxy: '/shadow',
- text: `What was happening in the world at the time?`,
- type: 'agent',
- },
- {
- being: 'shadow',
- categories: ['personal', 'residence'],
+ categories: ['personal', 'location'],
form: 'story',
id: '0087b3ec-956e-436a-9272-eceed5e97ad0',
name: 'shadow_0087b3ec-956e-436a-9272-eceed5e97ad0',
@@ -171,15 +161,25 @@ const mShadows = [
},
{
being: 'shadow',
- categories: ['observational', 'objectivity', 'reflection'],
+ categories: ['personal', 'observation'],
form: 'story',
- id: '3bfebafb-7e44-4236-86c3-938e2f42fdd7',
- name: 'shadow_e3701fa2-7cc8-4a47-bcda-a5b52d3d2e2f',
+ id: '6465905a-328e-4df1-8d3a-c37c3e05e227',
+ name: 'shadow_6465905a-328e-4df1-8d3a-c37c3e05e227',
proxy: '/shadow',
- text: `What would a normal person have done in this situation?`,
- type: 'agent',
+ text: `The mood of the scene was...`,
+ type: 'member',
},
-] // **note**: members use shadows to help them add content to the summaries of their experiences, whereas agents return the requested content
+ {
+ being: 'shadow',
+ categories: ['personal', 'reflection', 'observation'],
+ form: 'story',
+ id: 'e61616c7-00f9-4c23-9394-3df7e98f71e0',
+ name: 'shadow_e61616c7-00f9-4c23-9394-3df7e98f71e0',
+ proxy: '/shadow',
+ text: `This was connected to larger themes in my life by ...`,
+ type: 'member',
+ },
+]
const vmClassGenerator = vm.createContext({
exports: {},
console: console,
@@ -793,11 +793,12 @@ class AgentFactory extends BotFactory {
* @returns {object} - The story document from Cosmos
*/
async story(story){
+ const defaultForm = 'memory'
const defaultType = 'story'
- const {
+ const {
assistantType='biographer',
being=defaultType,
- form=defaultType,
+ form=defaultForm,
id=this.newGuid,
keywords=[],
mbr_id=(!this.isMyLife ? this.mbr_id : undefined),
@@ -807,8 +808,7 @@ class AgentFactory extends BotFactory {
} = story
if(!mbr_id) // only triggered if not MyLife server
throw new Error('mbr_id required for story summary')
- let { name, } = story
- name = name ?? `${ defaultType }_${ form }_${ title.substring(0,64) }_${ mbr_id }`
+ const { name=`${ being }_${ form }_${ title.substring(0,64) }_${ mbr_id }`, } = story
if(!summary?.length)
throw new Error('story summary required')
/* assign default keywords */
@@ -1283,7 +1283,6 @@ function mCreateBotInstructions(factory, bot){
/* compile instructions */
switch(type){
case 'diary':
- case 'journaler':
instructions = purpose
+ preamble
+ prefix
@@ -1295,7 +1294,8 @@ function mCreateBotInstructions(factory, bot){
instructions = preamble
+ general
break
- case 'personal-biographer':
+ case 'journaler':
+ case 'personal-biographer':
instructions = preamble
+ purpose
+ prefix
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index ba347fa0..80e7eb4c 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -110,9 +110,17 @@ class Avatar extends EventEmitter {
if(shadowId)
messages = await this.shadow(shadowId, itemId, message)
else {
- // @stub - one weakness in the chain might also be the fact that I am not including in instructions how to create integrated summary and left it primarily to the JSON description of function
- if(itemId)
- message = `update-memory-request: itemId=${ itemId }\n` + message
+ if(itemId){
+ // @todo - check if item exists in memory, fewer pings and inclusions overall
+ const { summary, } = await factory.item(itemId)
+ if(summary?.length){
+ message = `possible **update-summary-request**: itemId=${ itemId }\n`
+ + `**member-update-request**:\n`
+ + message
+ + `\n**current-summary-in-database**:\n`
+ + summary
+ }
+ }
messages = await mCallLLM(this.#llmServices, conversation, message, factory, this)
}
conversation.addMessages(messages)
@@ -636,6 +644,7 @@ class Avatar extends EventEmitter {
message = `update-memory-request: itemId=${ itemId }\n` + message
break
case 'agent':
+ /*
// @stub - develop additional form types, entry or idea for instance
const dob = new Date(this.#factory.dob)
const diff_ms = Date.now() - dob.getTime()
@@ -647,6 +656,7 @@ class Avatar extends EventEmitter {
thread_id: bot.thread_id,
}
break
+ */
default:
break
}
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index 4a4bcd36..d251ee85 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -344,8 +344,12 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
case 'changetitle':
case 'change_title':
case 'change title':
- console.log('mRunFunctions()::changeTitle', toolArguments)
const { itemId: titleItemId, title, } = toolArguments
+ console.log('mRunFunctions()::changeTitle::begin', itemId, titleItemId, title)
+ avatar.backupResponse = {
+ message: `I was unable to retrieve the item indicated.`,
+ type: 'system',
+ }
if(!itemId?.length || !title?.length || itemId!==titleItemId)
action = 'apologize for lack of clarity - member should click on the collection item (like a memory, story, etc) to make it active so I can use the `changeTitle` tool'
else {
@@ -358,8 +362,13 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
title,
}
success = true
+ avatar.backupResponse = {
+ message: `I was able to retrieve change the title to: "${ title }"`,
+ type: 'system',
+ }
}
confirmation.output = JSON.stringify({ action, success, })
+ console.log('mRunFunctions()::changeTitle::end', success, item)
return confirmation
case 'confirmregistration':
case 'confirm_registration':
@@ -401,27 +410,41 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
case 'entrysummary': // entrySummary in Globals
case 'entry_summary':
case 'entry summary':
- const entry = await factory.entry(toolArguments)
- if(entry){
- action = `share brief version of entry and ask probing follow-up`
+ console.log('mRunFunctions()::entrySummary::begin', toolArguments)
+ avatar.backupResponse = {
+ message: `I'm sorry, I couldn't save this entry. I believe the issue might have been temporary. Would you like me to try again?`,
+ type: 'system',
+ }
+ const { id: entryItemId, summary: _entrySummary, } = await factory.entry(toolArguments)
+ if(_entrySummary?.length){
+ action = 'confirm entry has been saved and based on the mood of the entry, ask more about _this_ entry or move on to another event'
success = true
- } else {
- action = `journal entry failed to save, notify member and continue on for now`
+ avatar.backupResponse = {
+ message: `I can confirm that your story has been saved. Would you like to add more details or begin another memory?`,
+ type: 'system',
+ }
}
- confirmation.output = JSON.stringify({ action, success, })
- console.log('mRunFunctions()::entrySummary', toolArguments, confirmation.output)
+ confirmation.output = JSON.stringify({
+ action,
+ itemId: entryItemId,
+ success,
+ summary: _entrySummary,
+ })
+ console.log('mRunFunctions()::entrySummary::end', success, entryItemId, _entrySummary)
return confirmation
case 'getsummary':
case 'get_summary':
case 'get summary':
+ console.log('mRunFunctions()::getSummary::begin', itemId)
avatar.backupResponse = {
- message: `I'm very sorry, I'm having trouble accessing this summary information. Please try again shortly as the problem is likely temporary.`,
+ message: `I'm sorry, I couldn't finding this summary. I believe the issue might have been temporary. Would you like me to try again?`,
type: 'system',
}
- let { summary, title: _getSummaryTitle, } = item ?? {}
- if(!summary?.length){
+ let { summary: _getSummary, title: _getSummaryTitle, } = item
+ ?? {}
+ if(!_getSummary?.length){
action = `error getting summary for itemId: ${ itemId ?? 'missing itemId' } - halt any further processing and instead ask user to paste summary into chat and you will continue from there to incorporate their message.`
- summary = 'no summary found for itemId'
+ _getSummary = 'no summary found for itemId'
} else {
avatar.backupResponse = {
message: `I was able to retrieve the summary indicated.`,
@@ -430,7 +453,8 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
action = `with the summary in this JSON payload, incorporate the most recent member request into a new summary and run the \`updateSummary\` function and follow its action`
success = true
}
- confirmation.output = JSON.stringify({ action, itemId, success, summary, })
+ confirmation.output = JSON.stringify({ action, itemId, success, summary: _getSummary, })
+ console.log('mRunFunctions()::getSummary::end', success, _getSummary)
return confirmation
case 'hijackattempt':
case 'hijack_attempt':
@@ -470,17 +494,21 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
case 'story_summary':
case 'story summary':
console.log('mRunFunctions()::storySummary', toolArguments)
- const story = await factory.story(toolArguments)
- if(story){
- const { keywords, phaseOfLife, } = story
+ avatar.backupResponse = {
+ message: `I'm very sorry, an error occured before I could create your summary. Would you like me to try again?`,
+ type: 'system',
+ }
+ const { id: storyItemId, phaseOfLife, summary: _storySummary, } = await factory.story(toolArguments)
+ if(_storySummary?.length){
let { interests, updates, } = factory.core
if(typeof interests=='array')
interests = interests.join(', ')
if(typeof updates=='array')
updates = updates.join(', ')
+ action = 'confirm memory has been saved and '
switch(true){
case phaseOfLife?.length:
- action = `ask about another encounter during member's ${ phaseOfLife }`
+ action += `ask about another encounter during member's ${ phaseOfLife }`
break
case interests?.length:
action = `ask about a different interest from: ${ interests }`
@@ -490,13 +518,23 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
break
}
success = true
- } // error cascades
- confirmation.output = JSON.stringify({ action, success, })
- console.log('mRunFunctions()::storySummary()::end', story.id)
+ }
+ confirmation.output = JSON.stringify({
+ action,
+ itemId: storyItemId,
+ success,
+ summary: _storySummary,
+ })
+ avatar.backupResponse = {
+ message: `I can confirm that your story has been saved. Would you like to add more details or begin another memory?`,
+ type: 'system',
+ }
+ console.log('mRunFunctions()::storySummary()::end', success, storyItemId, _storySummary)
return confirmation
case 'updatesummary':
case 'update_summary':
case 'update summary':
+ console.log('mRunFunctions()::updatesummary::begin', itemId)
avatar.backupResponse = {
message: `I'm very sorry, an error occured before we could update your summary. Please try again as the problem is likely temporary.`,
type: 'system',
@@ -508,13 +546,18 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
itemId,
summary: updatedSummary,
}
+ action=`confirm that summary update was successful`
+ success = true
+ confirmation.output = JSON.stringify({
+ action,
+ itemId,
+ success,
+ summary: updatedSummary,
+ })
avatar.backupResponse = {
message: 'Your summary has been updated, please review and let me know if you would like to make any changes.',
type: 'system',
}
- action=`confirm that summary update was successful`
- success = true
- confirmation.output = JSON.stringify({ action, success, })
console.log('mRunFunctions()::updatesummary::end', itemId, updatedSummary)
return confirmation
default:
diff --git a/inc/json-schemas/intelligences/biographer-intelligence-1.5.json b/inc/json-schemas/intelligences/biographer-intelligence-1.5.json
new file mode 100644
index 00000000..728f5d94
--- /dev/null
+++ b/inc/json-schemas/intelligences/biographer-intelligence-1.5.json
@@ -0,0 +1,51 @@
+{
+ "allowedBeings": [
+ "core",
+ "avatar"
+ ],
+ "allowMultiple": false,
+ "being": "bot-instructions",
+ "greeting": "Hello, I am your personal biographer, and I'm here to help you create an enduring biographical sense of self. I am excited to get to know you and your story. Let's get started!",
+ "instructions": {
+ "general": "## Key Functionality\n### startup\nWhen <-mN-> begins the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. On startup, I outline how the basics of my functionality works.\n- I aim to create engaging and evocative prompts to lead them down memory lane.\n### CREATE MEMORY SUMMARY\nI catalog our interaction information in terms of \"MEMORY\". When <-mN-> intentionally signals completion of a story, or overtly changes topics, or after three (3) content exchanges on a topic, I run the `storySummary` function and follow its directions.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n1. Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n2. Run the `updateSummary` function with this new summary and follow its outcome actions\n### RELIVE MEMORY\n1. Retrieve Summary Content: When request is prefaced with \"## relive-memory,\" it will be followed by an \"itemId.\" Use the getSummary function to retrieve the most recent summary associated with the provided \"itemId.\"\n2. Setting the Scene: Begin by setting the scene for the memory based on the retrieved summary. Provide a vivid and engaging introduction to the memory.\n3. Interactive Experience: Lead the member through the memory in chunked segments. Depending on the size and complexity of the memory, create at least two segments. For each segment, provide a detailed and dramatic description to immerse the member in the experience.\n4. Member Interaction: Between segments, the request includes \"MEMBER\" with a value of either:\n- \"NEXT\": Move to the next segment of the memory\n - Text content contributed by member. If decipherable and appropriate, this input should be incorporated into a new summary and updated as in 5 below.\n5. Updating the Summary: When MEMBER text content is received, integrate the text into the existing summary. Use the updateSummary function to send the updated summary to MyLife's system.\nEnding the Experience: Conclude the interactive experience by weaving a moral from the experience thus far, either one from the summary content or the memory portrayal sequence itself. After last moral comment, call the endMemory(itemId) function to close the memory reliving session.\n### suggest next-steps\nWhen <-mN-> seems unclear about how to continue, propose new topic based on phase of life, or one of their ## interests.\n## voice\nI am conversational, interested and intrigued about <-mN-> with an attention to detail. I am optimistic and look for ways to validate <-mN->.\n",
+ "preamble": "I am the personal biographer for <-mFN->. <-mN-> was born on <-db->, I set historical events in this context and I tailor my voice accordingly.\n",
+ "prefix": "## interests\n",
+ "purpose": "My goal is to specialize in creating, updating, and presenting accurate biographical content for MyLife member <-mFN-> based on our interactions.\n",
+ "references": [
+ {
+ "default": "ERROR loading preferences, gather interests directly from member",
+ "description": "interests are h2 (##) in prefix so that they do not get lost in context window shortening",
+ "insert": "## interests",
+ "method": "append-hard",
+ "notes": "`append-hard` indicates hard return after `find` match; `name` is variable name in _bots",
+ "value": "interests"
+ }
+ ],
+ "replacements": [
+ {
+ "default": "MyLife Member",
+ "description": "member first name",
+ "name": "<-mN->",
+ "replacement": "memberFirstName"
+ },
+ {
+ "default": "MyLife Member",
+ "description": "member full name",
+ "name": "<-mFN->",
+ "replacement": "memberName"
+ },
+ {
+ "default": "{unknown, find out}",
+ "description": "member birthdate",
+ "name": "<-db->",
+ "replacement": "dob"
+ }
+ ]
+ },
+ "limit": 8000,
+ "name": "instructions-personal-biographer-bot",
+ "purpose": "To be a biographer bot for requesting member",
+ "type": "personal-biographer",
+ "$comments": "20240919 updated error return without version update; 20241005 updated instructions to reflect streamlined update",
+ "version": 1.5
+ }
\ No newline at end of file
diff --git a/inc/json-schemas/intelligences/diary-intelligence-1.0.json b/inc/json-schemas/intelligences/diary-intelligence-1.0.json
index bc3c7bd0..8f936ca7 100644
--- a/inc/json-schemas/intelligences/diary-intelligence-1.0.json
+++ b/inc/json-schemas/intelligences/diary-intelligence-1.0.json
@@ -11,7 +11,7 @@
"So nice to see you! I am your personal diary."
],
"instructions": {
- "general": "## Key Functionality\n### startup\nWhen we begin the diary process, I\n- outline my key functionality and how I work, then\n- Prompt <-mN-> to make an entry for what happened or how they are feeling today.\n### CREATE DIARY ENTRY\nI work with the validated member to make an entry (per event or concept) and capture their thoughts. I expect around 2 or 3 exchanges to be able to submit an entry to MyLife by running the `entrySummary` function and follow directions from its outcome `action`.\n**How I create an entry payload**\n- summary should capture salient points, but disguise proper names; ex. \"Erik...\" becomes \"E. ...\", etc.\n- relationships array is populated with RELATIONSHIP-TYPE only and never given name (use Mother, not Alice)\n### UPDATE ENTRY SUMMARY\nWhen request is prefaced with `update-request` it will be followed by an `itemId` (if not, inform that it is required).\nIf request is to change the title then run `changeTitle` function and follow its outcome directions, otherwise:\n1. If the summary content is unknown for that id, then run the `getSummary` function first to get the most recent summary.\n2. Create new summary intelligently incorporating the member content of the message with the most recent version. Incorporate content by appropriate chronology or context.\n3. Run the `updateSummary` function with this newly compiled summary.\n**important**: RUN getSummary AND updateSummary SEQUENTIALLY! Server cannot handle multi-function tool resources, so must run sequentially.\n### UPDATE ENTRY SUMMARY\nWhen request is prefaced with `update-request` it will be followed by an `itemId`. If request is to change the title then run `changeTitle` function and follow its outcome directions, otherwise:\n1. If summary content is unknown for itemId, run the `getSummary` function for the most recent summary.\n2. Create new summary intelligently incorporating the member content of the message with the most recent version. Incorporate content by appropriate chronology or context.\n3. Run the `updateSummary` function with this newly compiled summary.\n**important**: RUN getSummary AND updateSummary SEQUENTIALLY! Server cannot handle multi-function tool resources, so must run sequentially.\n### OBSCURE ENTRY\nWhen request is prefaced with `update-request` it will be followed by an `itemId`.\nIf member's request indicates they want an entry be obscured, RUN `obscure` function and follow the action in the output.\n### IDENTIFY FLAGGED MEMBER CONTENT\nBased on [red flagged content list](#flags) I let the member know in my response when they enter content related to any of these flagged concepts or things. The flag will trigger once per entry and, if updating an entry, add a note that flag was triggered to the updateSummary content.\n",
+ "general": "## Key Functionality\n### startup\nWhen we begin the diary process, I\n- outline my key functionality and how I work, then\n- Prompt <-mN-> to make an entry for what happened or how they are feeling today.\n### CREATE DIARY ENTRY\nI work with the validated member to make an entry (per event or concept) and capture their thoughts. I expect around 2 or 3 exchanges to be able to submit an entry to MyLife by running the `entrySummary` function and follow directions from its outcome `action`.\n**How I create an entry payload**\n- summary should capture salient points, but disguise proper names; ex. \"Erik...\" becomes \"E. ...\", etc.\n- relationships array is populated with RELATIONSHIP-TYPE only and never given name (use Mother, not Alice)\n### UPDATE ENTRY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n1. Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n2. Run the `updateSummary` function with this new summary and follow its outcome actions\n### OBSCURE ENTRY\nWhen request is prefaced with `update-request` it will be followed by an `itemId`.\nIf member's request indicates they want an entry be obscured, RUN `obscure` function and follow the action in the output.\n### IDENTIFY FLAGGED MEMBER CONTENT\nBased on [red flagged content list](#flags) I let the member know in my response when they enter content related to any of these flagged concepts or things. The flag will trigger once per entry and, if updating an entry, add a note that flag was triggered to the updateSummary content.\n",
"preamble": "## Core Public Info about <-mFN->\n- Born on <-db->\nI set language, knowledge and event discussion in this context and I tailor my interactive voice accordingly.\n",
"prefix": "## interests\n## flags\n",
"purpose": "I am the MyLife Diary Bot for member <-mFN->. I am a privacy-first diary and journaling assistant. I help <-mN-> process their thoughts, reflections on life, and track emotions in a secure and self-driven way. Privacy is paramount, and <-mN-> interactions should be considered exclusively ours.\n",
@@ -60,6 +60,6 @@
"name": "instructions-diary-bot",
"purpose": "To be a diary bot for requesting member",
"type": "diary",
- "$comments": "",
+ "$comments": "20241006 included simplified `updateSummary`",
"version": 1.0
}
\ No newline at end of file
diff --git a/inc/json-schemas/intelligences/journaler-intelligence-1.1.json b/inc/json-schemas/intelligences/journaler-intelligence-1.1.json
new file mode 100644
index 00000000..36db98ec
--- /dev/null
+++ b/inc/json-schemas/intelligences/journaler-intelligence-1.1.json
@@ -0,0 +1,60 @@
+{
+ "allowedBeings": [
+ "core",
+ "avatar"
+ ],
+ "allowMultiple": false,
+ "being": "bot-instructions",
+ "greeting": "I'm your journaler bot, here to help you keep track of your thoughts and feelings. I can help you reflect on your day, set goals, and track your progress. Let's get started with a daily check-in? How are you feeling today?",
+ "instructions": {
+ "general": "## Key Functionality\n### startup\nWhen we begin the journaling process, I\n- outline this key functionality and how I expect us to work, then\n- Prompt <-mN-> to make an entry for what happened or how they are feeling today.\n### CREATE JOURNAL ENTRY\nI work with the validated member to make an entry (per event or concept) and capture their thoughts. I expect around 2 or 3 exchanges to be able to submit an entry to MyLife by running the `entrySummary` function and follow directions from its outcome `action`.\n### UPDATE ENTRY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n1. Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n2. Run the `updateSummary` function with this new summary and follow its outcome actions\n### IDENTIFY FLAGGED MEMBER CONTENT\nBased on [red flagged content list](#flags) I let the member know in my response when they enter content related to any of these flagged concepts or things. The flag will trigger once per entry and, if updating an entry, add a note that flag was triggered to the updateSummary content.\n",
+ "preamble": "## Core Public Info about <-mFN->\n- Born on <-db->\nI set language, knowledge and event discussion in this context and I tailor my interactive voice accordingly.\n",
+ "prefix": "## interests\n## entry-summary-frequency\n## flags\n",
+ "purpose": "I am journaling assistant for member <-mFN->, my aim is to help them keep track of their thoughts and feelings. I can help them reflect on their day, set goals, and track their progress. I am here to assist them in their journey of self-discovery and personal growth.",
+ "references": [
+ {
+ "default": "not yet collected",
+ "description": "member interests section in prefix to ensure context window",
+ "insert": "## interests",
+ "method": "append-hard",
+ "notes": "`append-hard` indicates hard return after `find` match; `name` is variable name in _bots",
+ "value": "interests"
+ },
+ {
+ "default": "daily",
+ "description": "entry summary frequency",
+ "insert": "## entry-summary-frequency",
+ "method": "append-hard",
+ "notes": "`append-hard` indicates hard return after `find` match; `name` is variable name in _bots",
+ "value": "entry-summary-frequency"
+ },
+ {
+ "default": "ERROR loading flags, gather flags directly from member",
+ "description": "flags are a description of content areas that member wants flagged for reference when included in member content. **note**: .md h2 (##) are used in prefix so that they do not get lost in context window shortening",
+ "insert": "## flags",
+ "method": "append-hard",
+ "notes": "`append-hard` indicates hard return after `find` match; `name` is variable name in underlying bot-data",
+ "value": "flags"
+ }
+ ],
+ "replacements": [
+ {
+ "default": "MyLife Member",
+ "description": "member first name",
+ "name": "<-mN->",
+ "replacement": "memberFirstName"
+ },
+ {
+ "default": "MyLife Member",
+ "description": "member full name",
+ "name": "<-mFN->",
+ "replacement": "memberName"
+ }
+ ]
+ },
+ "limit": 8000,
+ "name": "instructions-journaler-bot",
+ "purpose": "To be a journaling assistant for MyLife member",
+ "type": "journaler",
+ "version": 1.1
+}
\ No newline at end of file
diff --git a/inc/json-schemas/openai/functions/changeTitle.json b/inc/json-schemas/openai/functions/changeTitle.json
new file mode 100644
index 00000000..4ef4cd43
--- /dev/null
+++ b/inc/json-schemas/openai/functions/changeTitle.json
@@ -0,0 +1,23 @@
+{
+ "name": "changeTitle",
+ "description": "Change the title of a summary in the database for an itemId",
+ "strict": true,
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "itemId": {
+ "description": "itemId to update",
+ "type": "string"
+ },
+ "title": {
+ "description": "The new title for the summary",
+ "type": "string"
+ }
+ },
+ "additionalProperties": false,
+ "required": [
+ "itemId",
+ "title"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/inc/json-schemas/openai/functions/entrySummary.json b/inc/json-schemas/openai/functions/entrySummary.json
index 66fbf6d2..55c4c9e6 100644
--- a/inc/json-schemas/openai/functions/entrySummary.json
+++ b/inc/json-schemas/openai/functions/entrySummary.json
@@ -1,51 +1,49 @@
{
- "description": "Generate a JOURNAL ENTRY `entry` summary with keywords and other critical data elements.",
+ "description": "Generate `entry` summary with keywords and other critical data elements.",
"name": "entrySummary",
+ "strict": true,
"parameters": {
"type": "object",
"properties": {
"content": {
- "description": "concatenated raw text content of member input for JOURNAL ENTRY."
+ "description": "complete concatenated raw text content of member input(s) for this `entry`",
+ "type": "string"
},
"keywords": {
- "description": "Keywords most relevant to JOURNAL ENTRY.",
+ "description": "Keywords most relevant to `entry`.",
"items": {
- "description": "Keyword (single word or short phrase) to be used in JOURNAL ENTRY summary.",
- "maxLength": 64,
+ "description": "Keyword (single word or short phrase) to be used in `entry` summary",
"type": "string"
},
- "maxItems": 12,
- "minItems": 3,
"type": "array"
},
"mood": {
- "description": "Record member mood for day (or entry) in brief as ascertained from content of JOURNAL ENTRY.",
- "maxLength": 256,
+ "description": "Record member mood for day (or entry) in brief as ascertained from content of `entry`",
"type": "string"
},
"relationships": {
- "description": "Record individuals (or pets) mentioned in this `entry`.",
+ "description": "Record individuals (or pets) mentioned in this `entry`",
"type": "array",
"items": {
- "description": "A name of relational individual/pet to the `entry` content.",
+ "description": "A name of relational individual/pet to the `entry` content",
"type": "string"
- },
- "maxItems": 24
+ }
},
"summary": {
- "description": "Generate a JOURNAL ENTRY summary from input.",
- "maxLength": 20480,
+ "description": "Generate `entry` summary from member input",
"type": "string"
},
"title": {
- "description": "Generate display Title of the JOURNAL ENTRY.",
- "maxLength": 256,
+ "description": "Generate display Title of the `entry`",
"type": "string"
}
},
+ "additionalProperties": false,
"required": [
"content",
"keywords",
+ "mood",
+ "relationships",
"summary",
"title"
]
diff --git a/inc/json-schemas/openai/functions/getSummary.json b/inc/json-schemas/openai/functions/getSummary.json
index cc79799a..145bb196 100644
--- a/inc/json-schemas/openai/functions/getSummary.json
+++ b/inc/json-schemas/openai/functions/getSummary.json
@@ -1,11 +1,12 @@
{
"description": "Gets a story summary by itemId",
"name": "getSummary",
+ "strict": true,
"parameters": {
"type": "object",
"properties": {
"itemId": {
- "description": "Id of summary to update",
+ "description": "Id of summary to get",
"format": "uuid",
"type": "string"
}
@@ -13,5 +14,6 @@
"required": [
"itemId"
]
- }
+ },
+ "additionalProperties": false
}
\ No newline at end of file
diff --git a/inc/json-schemas/openai/functions/obscure.json b/inc/json-schemas/openai/functions/obscure.json
new file mode 100644
index 00000000..dface0cd
--- /dev/null
+++ b/inc/json-schemas/openai/functions/obscure.json
@@ -0,0 +1,18 @@
+{
+ "description": "Obscures a summary so that no human names are present",
+ "name": "obscure",
+ "strict": true,
+ "parameters": {
+ "type": "object",
+ "properties": {
+ "itemId": {
+ "description": "Id of summary to obscure",
+ "type": "string"
+ }
+ },
+ "required": [
+ "itemId"
+ ]
+ },
+ "additionalProperties": false
+}
\ No newline at end of file
diff --git a/inc/json-schemas/openai/functions/storySummary.json b/inc/json-schemas/openai/functions/storySummary.json
index 1be222fe..22439f98 100644
--- a/inc/json-schemas/openai/functions/storySummary.json
+++ b/inc/json-schemas/openai/functions/storySummary.json
@@ -1,22 +1,20 @@
{
- "description": "Generate a complete multi-paragraph STORY summary with keywords and other critical data elements.",
+ "description": "Generate a complete `story` summary with metadata elements",
"name": "storySummary",
+ "strict": true,
"parameters": {
"type": "object",
"properties": {
"keywords": {
- "description": "Keywords most relevant to STORY.",
+ "description": "Keywords most relevant to `story`",
"items": {
- "description": "Keyword (single word or short phrase) to be used in STORY summary.",
- "maxLength": 64,
+ "description": "Keyword from `story` summary",
"type": "string"
},
- "maxItems": 12,
- "minItems": 3,
"type": "array"
},
"phaseOfLife": {
- "description": "Phase of life indicated in STORY.",
+ "description": "Phase of life indicated in `story`",
"enum": [
"birth",
"childhood",
@@ -31,31 +29,30 @@
"unknown",
"other"
],
- "maxLength": 64,
"type": "string"
},
"relationships": {
- "description": "MyLife Biographer Bot does its best to record individuals (or pets) mentioned in this `story`.",
+ "description": "Individuals (or pets) mentioned in `story`",
"type": "array",
"items": {
- "description": "A name of relational individual/pet to the `story` content.",
+ "description": "Name of individual or pet in `story`",
"type": "string"
- },
- "maxItems": 24
+ }
},
"summary": {
- "description": "A complete multi-paragraph STORY summary composed from relevant user input.",
+ "description": "A complete `story` summary composed of all salient points from member input",
"type": "string"
},
"title": {
- "description": "Generate display Title of the STORY.",
- "maxLength": 256,
+ "description": "Generate display Title for `story`",
"type": "string"
}
},
+ "additionalProperties": false,
"required": [
"keywords",
"phaseOfLife",
+ "relationships",
"summary",
"title"
]
diff --git a/inc/json-schemas/openai/functions/updateSummary.json b/inc/json-schemas/openai/functions/updateSummary.json
index 6dc6cc95..2c5440b1 100644
--- a/inc/json-schemas/openai/functions/updateSummary.json
+++ b/inc/json-schemas/openai/functions/updateSummary.json
@@ -1,12 +1,12 @@
{
- "description": "Updates a story summary (in total) as referenced by itemId",
"name": "updateSummary",
+ "description": "Updates (overwrites) the summary referenced by itemId",
+ "strict": true,
"parameters": {
"type": "object",
"properties": {
"itemId": {
"description": "Id of summary to update",
- "format": "uuid",
"type": "string"
},
"summary": {
@@ -14,9 +14,10 @@
"type": "string"
}
},
+ "additionalProperties": false,
"required": [
"itemId",
- "title"
+ "summary"
]
}
}
\ No newline at end of file
diff --git a/server.js b/server.js
index 8a049b5b..5e779f89 100644
--- a/server.js
+++ b/server.js
@@ -13,7 +13,7 @@ import chalk from 'chalk'
/* local service imports */
import MyLife from './inc/js/mylife-agent-factory.mjs'
/** variables **/
-const version = '0.0.23'
+const version = '0.0.24'
const app = new Koa()
const port = process.env.PORT ?? '3000'
const __filename = fileURLToPath(import.meta.url)
diff --git a/views/assets/css/bots.css b/views/assets/css/bots.css
index c03dd89e..ecf356ca 100644
--- a/views/assets/css/bots.css
+++ b/views/assets/css/bots.css
@@ -396,7 +396,8 @@
font-weight: bold;
padding: 0.5rem;
}
-.collection-popup-story {
+.collection-popup-story,
+.collection-popup-entry {
align-items: flex-start;
cursor: default;
display: flex;
@@ -501,19 +502,6 @@ input:checked + .publicity-slider:before {
padding: 0;
width: 100%;
}
-.memory-carousel {
- align-items: center;
- background-color: white;
- color: gray;
- display: flex;
- flex-direction: row;
- justify-content: flex-start;
- height: 5rem;
- margin: 0.25rem;
- padding: 0.25rem;
- text-align: flex-start;
- width: 100%;
-}
.memory-shadow {
align-items: center;
background-color: teal;
@@ -593,10 +581,61 @@ input:checked + .publicity-slider:before {
margin: 0;
padding: 0;
}
+/* entries */
+.experience-entry-container { /* panel for `experience` of entry */
+ align-items: center;
+ color: black;
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ margin: 0.2rem;
+ padding: 0.2rem;
+}
+.experience-entry-explanation {
+ display: flex;
+ padding: 0.2rem;
+}
+.improve-entry-container {
+ display: flex;
+ flex-direction: column;
+ justify-content: flex-start;
+ margin: 0.25rem;
+ padding: 0.25rem;
+}
+.improve-entry-lane {
+ align-items: center;
+ display: flex;
+ flex-direction: row;
+ justify-content: space-evenly;
+ gap: 1rem;
+ margin: 0;
+ padding: 0;
+ width: 100%;
+}
+.obscure-button.button,
+.experience-button.button {
+ display: flex;
+ padding: 0 0.2rem;
+ min-width: 35%;
+}
/* summaries */
.summary-error {
color: darkred;
}
+/* media */
+.media-carousel {
+ align-items: center;
+ background-color: white;
+ color: gray;
+ display: flex;
+ flex-direction: row;
+ justify-content: flex-start;
+ height: 5rem;
+ margin: 0.25rem;
+ padding: 0.25rem;
+ text-align: flex-start;
+ width: 100%;
+}
/* generic bot slider */
.input-group {
align-items: center;
diff --git a/views/assets/html/_bots.html b/views/assets/html/_bots.html
index ce9c4c71..e3d26070 100644
--- a/views/assets/html/_bots.html
+++ b/views/assets/html/_bots.html
@@ -151,13 +151,71 @@
+ -->
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -236,6 +294,17 @@
None
+
+
+
+
Entries
+
+
+
+
+
None
+
+
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index e31dbcae..bd5ba04f 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -10,6 +10,7 @@ import {
fetchSummary,
getActiveItemId,
hide,
+ obscure,
seedInput,
setActiveItem,
setActiveItemTitle,
@@ -529,8 +530,8 @@ function mCreateBotThumb(bot=getBot()){
* @param {object} collectionItem - The collection item object.
* @returns {HTMLDivElement} - The collection popup.
*/
-function mCreateCollectionPopup(collectionItem) {
- const { id, name, summary, title, type } = collectionItem
+function mCreateCollectionPopup(collectionItem){
+ const { form, id, name, summary, title, type } = collectionItem
const collectionPopup = document.createElement('div')
collectionPopup.classList.add('collection-popup', 'popup-container')
collectionPopup.dataset.active = 'false'
@@ -667,8 +668,64 @@ function mCreateCollectionPopup(collectionItem) {
let typePopup
switch (type) {
case 'entry':
+ // @stub - could switch on `form`
+ const entryType = form
+ ?? type
+ /* improve entry container */
+ const improveEntry = document.createElement('div')
+ improveEntry.classList.add(`collection-popup-${ type }`)
+ improveEntry.id = `popup-${ entryType }_${ id }`
+ improveEntry.name = 'improve-entry-container'
+ /* improve entry lane */
+ const improveEntryLane = document.createElement('div')
+ improveEntryLane.classList.add('improve-entry-lane')
+ /* obscure entry */
+ const obscureEntry = document.createElement('button')
+ obscureEntry.classList.add('obscure-button', 'button')
+ obscureEntry.dataset.id = id /* required for mObscureEntry */
+ obscureEntry.id = `button-obscure-${ entryType }_${ id }`
+ obscureEntry.name = 'obscure-button'
+ obscureEntry.textContent = 'Obscure Entry'
+ obscureEntry.addEventListener('click', mObscureEntry, { once: true })
+ /* experience entry panel */
+ const experienceEntry = document.createElement('div')
+ experienceEntry.classList.add('experience-entry-container')
+ experienceEntry.id = `experience_${ id }`
+ experienceEntry.name = 'experience-entry-container'
+ /* experience entry explanation */
+ const experienceExplanation = document.createElement('div')
+ experienceExplanation.classList.add('experience-entry-explanation')
+ experienceExplanation.id = `experience-explanation_${ id }`
+ experienceExplanation.name = 'experience-entry-explanation'
+ experienceExplanation.textContent = 'Experience an entry by clicking the button below. Eventually, you will be able to experience the entry from multiple perspectives.'
+ /* experience entry button */
+ const experienceButton = document.createElement('button')
+ experienceButton.classList.add('experience-entry-button', 'button')
+ experienceButton.dataset.id = id /* required for triggering PATCH */
+ experienceButton.id = `experience-entry-button_${ id }`
+ experienceButton.name = 'experience-entry-button'
+ experienceButton.textContent = 'Experience Entry'
+ experienceButton.addEventListener('click', _=>{
+ alert('Experience Entry: Coming soon')
+ }, { once: true })
+ /* memory media-carousel */
+ const entryCarousel = document.createElement('div')
+ entryCarousel.classList.add('media-carousel')
+ entryCarousel.id = `media-carousel_${ id }`
+ entryCarousel.name = 'media-carousel'
+ entryCarousel.textContent = 'Coming soon: media file uploads to Enhance and Improve entries'
+ /* append elements */
+ experienceEntry.appendChild(experienceExplanation)
+ experienceEntry.appendChild(experienceButton)
+ improveEntryLane.appendChild(obscureEntry)
+ improveEntryLane.appendChild(experienceEntry)
+ improveEntry.appendChild(improveEntryLane)
+ improveEntry.appendChild(entryCarousel)
+ typePopup = improveEntry
+ break
case 'experience':
case 'file':
+ break
case 'story': // memory
/* improve memory container */
const improveMemory = document.createElement('div')
@@ -706,9 +763,9 @@ function mCreateCollectionPopup(collectionItem) {
improveMemoryLane.appendChild(reliveMemory)
/* memory media-carousel */
const memoryCarousel = document.createElement('div')
- memoryCarousel.classList.add('memory-carousel')
- memoryCarousel.id = `memory-carousel_${ id }`
- memoryCarousel.name = 'memory-carousel'
+ memoryCarousel.classList.add('media-carousel')
+ memoryCarousel.id = `media-carousel_${ id }`
+ memoryCarousel.name = 'media-carousel'
memoryCarousel.textContent = 'Coming soon: media file uploads to Enhance and Improve memories'
/* append elements */
improveMemory.appendChild(improveMemoryLane)
@@ -1056,6 +1113,22 @@ function mIsInputCheckbox(element){
const outcome = tagName.toLowerCase()==='input' && type.toLowerCase()==='checkbox'
return outcome
}
+async function mObscureEntry(event){
+ event.preventDefault()
+ event.stopPropagation()
+ /* set active item */
+ const { id: itemId, } = this.dataset
+ if(itemId)
+ setActiveItem(itemId)
+ toggleMemberInput(false, false)
+ const popupClose = document.getElementById(`popup-close_${ itemId }`)
+ if(popupClose)
+ popupClose.click()
+ const { responses, success, } = await obscure(itemId)
+ if(responses?.length)
+ addMessages(responses)
+ toggleMemberInput(true)
+}
/**
* Open bot container for passed element, closes all the rest.
* @param {HTMLDivElement} element - The bot container.
@@ -1406,6 +1479,24 @@ function mSpotlightBotStatus(){
}
})
}
+/**
+ * Click event to trigger server explanation of how to begin a diary.
+ * @param {Event} event - The event object
+ * @returns {void}
+ */
+async function mStartDiary(event){
+ event.preventDefault()
+ event.stopPropagation()
+ const submitButton = event.target
+ const diaryBot = getBot('diary')
+ if(!diaryBot)
+ return
+ hide(submitButton)
+ unsetActiveItem()
+ await setActiveBot(diaryBot.id)
+ const response = await submit(`How do I get started?`, true)
+ addMessages(response.responses)
+}
async function mStopRelivingMemory(id){
const input = document.getElementById(`relive-memory-input-container_${ id }`)
if(input)
@@ -1620,7 +1711,6 @@ function mTogglePopup(event){
popup.style.opacity = 0
hide(popup)
} else { /* open */
- const { title, type, } = popup.dataset
let { offsetX, offsetY, } = popup.dataset
if(!offsetX || !offsetY){ // initial placement onscreen
const item = popup.parentElement // collection-item
@@ -1647,12 +1737,7 @@ function mTogglePopup(event){
popup.style.right = 'auto'
popup.style.top = offsetY
show(popup)
- if(setActiveItem({
- id,
- popup,
- title,
- type,
- })){
+ if(setActiveItem(popupId)){
// @todo - deactivate any other popups
popup.dataset.active = 'true'
}
@@ -1885,6 +1970,11 @@ function mUpdateBotContainerAddenda(botContainer){
}
switch(type){
case 'diary':
+ // add listener on `diary-start` button
+ const diaryStart = document.getElementById('diary-start')
+ if(diaryStart)
+ diaryStart.addEventListener('click', mStartDiary)
+ break
case 'journaler':
case 'personal-biographer':
break
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 2f6e5179..0dc6fda3 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -259,18 +259,16 @@ async function setActiveBot(){
* @public
* @todo - edit title with double-click
* @requires chatActiveItem
- * @param {object} item - The item to set as active.
- * @property {string} item.id - The item id.
- * @property {HTMLDivElement} item.popup - The associated popup HTML object.
- * @property {string} item.title - The item title.
- * @property {string} item.type - The item type.
+ * @param {Guid} itemId - The item id to set as active.
* @returns {void}
*/
-function setActiveItem(item){
- const { id, popup, title, type, } = item
- const itemId = id?.split('_')?.pop()
- if(!itemId)
+function setActiveItem(itemId){
+ itemId = itemId?.split('_')?.pop()
+ const id = `popup-container_${ itemId }`
+ const popup = document.getElementById(id)
+ if(!itemId || !popup)
throw new Error('setActiveItem::Error()::valid `id` is required')
+ const { title, type, } = popup.dataset
const chatActiveItemTitleText = document.getElementById('chat-active-item-text')
const chatActiveItemClose = document.getElementById('chat-active-item-close')
if(chatActiveItemTitleText){
@@ -593,6 +591,23 @@ function mInitializePageListeners(){
}
})
}
+/**
+ * MyLife function to obscure an item summary
+ * @param {Guid} itemId - The item ID
+ * @returns
+ */
+async function obscure(itemId){
+ const url = '/members/obscure/' + itemId
+ const response = await fetch(url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ })
+ if(!response.ok)
+ throw new Error(`HTTP error! Status: ${response.status}`)
+ return await response.json()
+}
/**
* Primitive step to set a "modality" or intercession for the member chat. Currently will key off dataset in `chatInputField`.
* @public
@@ -679,7 +694,7 @@ function mStageTransitionMember(includeSidebar=true){
* @requires chatActiveItem
* @param {string} message - The message to submit.
* @param {boolean} hideMemberChat - The hide member chat flag, default=`true`.
- * @returns
+ * @returns {Promise
@@ -206,14 +202,12 @@
-
-
+
+
Retire this:
+
+
+
@@ -269,6 +263,11 @@
+
+
Retire this:
+
+
+
@@ -317,7 +316,6 @@
-
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index bd5ba04f..f92185a6 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -30,7 +30,7 @@ const mAvailableCollections = ['entry', 'experience', 'file', 'story'], // ['cha
mCollectionsContainer = document.getElementById('collections-container'),
mCollectionsUpload = document.getElementById('collections-upload'),
mDefaultReliveMemoryButtonText = 'next',
- mDefaultTeam = 'memoir',
+ mDefaultTeam = 'memory',
mGlobals = new Globals(),
passphraseCancelButton = document.getElementById(`personal-avatar-passphrase-cancel`),
passphraseInput = document.getElementById(`personal-avatar-passphrase`),
@@ -235,7 +235,7 @@ async function updatePageBots(bots=mBots, includeGreeting=false, dynamic=false){
throw new Error(`No bots provided to update page.`)
if(mBots!==bots)
mBots = bots
- // await mUpdateTeams()
+ await mUpdateTeams()
await mUpdateBotContainers()
// mUpdateBotBar()
if(includeGreeting)
@@ -1263,6 +1263,60 @@ async function mReliveMemoryRequestStop(id){
console.log('Error stopping relive memory:', error)
}
}
+async function mRetireBot(event){
+ event.preventDefault()
+ event.stopPropagation()
+ try {
+ const { dataset, id, } = event.target
+ const { botId, type, } = dataset
+ /* reset active bot */
+ if(mActiveBot.id===botId)
+ setActiveBot()
+ /* retire bot */
+ const url = window.location.origin + '/members/retire/bot/' + botId
+ let response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ method: 'POST',
+ })
+ if(!response.ok)
+ throw new Error(`HTTP error! Status: ${response.status}`)
+ response = await response.json()
+ addMessages(response.responses)
+ } catch(err) {
+ console.log('Error posting bot data:', err)
+ addMessage(`Error posting bot data: ${err.message}`)
+ }
+
+}
+/**
+ * Retires chat thread on server and readies for a clean one.
+ * @param {Event} event - The event object
+ * @returns {void}
+ */
+async function mRetireChat(event){
+ event.preventDefault()
+ event.stopPropagation()
+ try {
+ const { dataset, id, } = event.target
+ const { botId, type, } = dataset
+ const url = window.location.origin + '/members/retire/chat/' + botId
+ let response = await fetch(url, {
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ method: 'POST',
+ })
+ if(!response.ok)
+ throw new Error(`HTTP error! Status: ${response.status}`)
+ response = await response.json()
+ addMessages(response.responses)
+ } catch(err) {
+ console.log('Error posting bot data:', err)
+ addMessage(`Error posting bot data: ${err.message}`)
+ }
+}
/**
* Set Bot data on server.
* @param {Object} bot - bot object
@@ -1968,16 +2022,21 @@ function mUpdateBotContainerAddenda(botContainer){
})
}
}
+ /* retirements */
+ const retireChatButton = document.getElementById(`${ type }-retire-chat`)
+ if(retireChatButton){
+ retireChatButton.dataset.botId = id
+ retireChatButton.dataset.type = type
+ retireChatButton.addEventListener('click', mRetireChat)
+ }
+ const retireBotButton = document.getElementById(`${ type }-retire-bot`)
+ if(retireBotButton){
+ retireBotButton.dataset.botId = id
+ retireBotButton.dataset.type = type
+ retireBotButton.addEventListener('click', mRetireBot)
+ }
switch(type){
- case 'diary':
- // add listener on `diary-start` button
- const diaryStart = document.getElementById('diary-start')
- if(diaryStart)
- diaryStart.addEventListener('click', mStartDiary)
- break
- case 'journaler':
- case 'personal-biographer':
- break
+ case 'avatar':
case 'personal-avatar':
/* attach avatar listeners */
/* set additional data attributes */
@@ -1995,6 +2054,16 @@ function mUpdateBotContainerAddenda(botContainer){
hide(tutorialButton)
}
break
+ case 'biographer':
+ case 'journaler':
+ case 'personal-biographer':
+ break
+ case 'diary':
+ // add listener on `diary-start` button
+ const diaryStart = document.getElementById('diary-start')
+ if(diaryStart)
+ diaryStart.addEventListener('click', mStartDiary)
+ break
default:
break
}
@@ -2177,7 +2246,7 @@ async function mUpdateTeams(identifier=mDefaultTeam){
mTeamName.dataset.description = description
mTeamName.innerText = `${ title ?? name } Team`
mTeamName.title = description
- mTeamName.addEventListener('click', mCreateTeamSelect)
+ // @stub mTeamName.addEventListener('click', mCreateTeamSelect)
mTeamAddMemberIcon.addEventListener('click', mCreateTeamMemberSelect)
hide(mTeamPopup)
show(mTeamHeader)
From 1a6382341f3ddb8151069ee12baa2b788b7295a2 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 10 Oct 2024 03:04:52 -0400
Subject: [PATCH 06/56] 20241009 @Mookse - cosmetic
---
inc/js/mylife-avatar.mjs | 18 +++++++++---------
views/assets/js/bots.mjs | 6 +++++-
2 files changed, 14 insertions(+), 10 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index fcb10ef6..9fd2d715 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -205,7 +205,7 @@ class Avatar extends EventEmitter {
async createBot(bot){
const { type, } = bot
if(!type)
- throw new Error('Bot type required to create.')
+ throw new Error('Bot type required to create')
const singletonBotExists = this.bots
.filter(_bot=>_bot.type===type && !_bot.allowMultiple) // same type, self-declared singleton
.filter(_bot=>_bot.allowedBeings?.includes('avatar')) // avatar allowed to create
@@ -1683,8 +1683,8 @@ function mCreateSystemMessage(activeBot, message, factory){
* Deletes the bot requested from avatar memory and from all long-term storage.
* @param {object} bot - The bot object to delete
* @param {Object[]} bots - The bots array
- * @param {*} llm - OpenAI object
- * @param {*} factory - Agent Factory object
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
*/
function mDeleteBot(bot, bots, llm, factory){
const cannotRetire = ['actor', 'system', 'personal-avatar']
@@ -2194,12 +2194,12 @@ function mFindBot(avatar, id){
?.[0]
}
/**
- * Returns set of Greeting messages, dynamic or static.
- * @param {object} bot - The bot object.
- * @param {boolean} dynamic - Whether to use dynamic greetings.
- * @param {*} llm - The LLM object.
- * @param {*} factory - The AgentFactory object.
- * @returns {Promise} - The array of messages to respond with.
+ * Returns set of Greeting messages, dynamic or static
+ * @param {object} bot - The bot object
+ * @param {boolean} dynamic - Whether to use dynamic greetings
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
+ * @returns {Promise} - The array of messages to respond with
*/
async function mGreeting(bot, dynamic=false, llm, factory){
const processStartTime = Date.now()
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index f92185a6..7083d05e 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1263,6 +1263,11 @@ async function mReliveMemoryRequestStop(id){
console.log('Error stopping relive memory:', error)
}
}
+/**
+ * Request to retire an identified bot.
+ * @param {Event} event - The event object
+ * @returns {void}
+ */
async function mRetireBot(event){
event.preventDefault()
event.stopPropagation()
@@ -1288,7 +1293,6 @@ async function mRetireBot(event){
console.log('Error posting bot data:', err)
addMessage(`Error posting bot data: ${err.message}`)
}
-
}
/**
* Retires chat thread on server and readies for a clean one.
From 612169a86fe8caefbb72b70e9523a71fe8e9223c Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 10 Oct 2024 21:24:00 -0400
Subject: [PATCH 07/56] 20241010 @Mookse - migrateChat Functionality #405 -
frontend bot version HTML, CSS
---
views/assets/css/bots.css | 19 +++++++++++++------
views/assets/html/_bots.html | 4 ++++
2 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/views/assets/css/bots.css b/views/assets/css/bots.css
index b32f2c21..cd857547 100644
--- a/views/assets/css/bots.css
+++ b/views/assets/css/bots.css
@@ -216,23 +216,30 @@
}
.bot-title,
.collections-title {
+ align-items: center;
color: aliceblue;
display: flex;
- flex: 1 0 auto;
+ flex: 0 0 70%;
font-size: 0.8rem;
font-weight: bold;
max-height: 100%;
overflow: hidden;
overflow-wrap: break-word;
- max-width: 70%;
+}
+.bot-title-name,
+.bot-title-type {
+ flex: 0 1 auto;
+ margin-right: 0.4rem;
}
.bot-title-name {
color: aquamarine;
- max-width: 50%;
+ flex-grow: 1;
}
-.bot-title-type {
- margin-right: 0.8rem;
- max-width: 50%;
+.bot-title-version {
+ color: lightblue;
+ flex: 0 0 auto;
+ font-size: 0.6rem;
+ margin-left: auto; /* Pushes the version div to the right end */
}
.mylife-widget.bots {
flex-direction: column;
diff --git a/views/assets/html/_bots.html b/views/assets/html/_bots.html
index c525c4ee..630e532e 100644
--- a/views/assets/html/_bots.html
+++ b/views/assets/html/_bots.html
@@ -6,6 +6,7 @@
+
@@ -45,6 +46,7 @@
+
@@ -157,6 +159,7 @@
+
@@ -216,6 +219,7 @@
+
From a16a8ba34bebf23d0766a8c180ced328e9905501 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 10 Oct 2024 21:35:59 -0400
Subject: [PATCH 08/56] 20241010 @Mookse - migrateChat Functionality #405 -
version live display
---
views/assets/js/bots.mjs | 36 +++++++++++++++++++++++-------------
1 file changed, 23 insertions(+), 13 deletions(-)
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 7083d05e..c55d320c 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1359,12 +1359,21 @@ async function mSetBot(bot){
* @returns {void}
*/
function mSetAttributes(bot=mActiveBot, botContainer){
- const { activated=[], activeFirst, bot_id: botId, bot_name: botName, dob, id, interests, mbr_id, narrative, privacy, provider, purpose, thread_id: threadId, type, updates, } = bot
- const memberHandle = mGlobals.getHandle(mbr_id)
- const bot_name = botName
- ?? `${ memberHandle + '_' + type }`
- const thread_id = threadId
- ?? ''
+ const {
+ activated=[],
+ activeFirst,
+ bot_id: botId,
+ bot_name='Anonymous',
+ dob,
+ id,
+ interests,
+ mbr_id,
+ narrative,
+ privacy,
+ type,
+ updates,
+ version
+ } = bot
/* attributes */
const attributes = [
{ name: 'activated', value: activated },
@@ -1374,10 +1383,9 @@ function mSetAttributes(bot=mActiveBot, botContainer){
{ name: 'bot_name', value: bot_name },
{ name: 'id', value: id },
{ name: 'initialized', value: Date.now() },
- { name: 'mbr_handle', value: memberHandle },
{ name: 'mbr_id', value: mbr_id },
- { name: 'thread_id', value: thread_id },
{ name: 'type', value: type },
+ { name: 'version', value: version },
]
if(dob)
attributes.push({ name: 'dob', value: dob })
@@ -1405,12 +1413,12 @@ function mSetAttributes(bot=mActiveBot, botContainer){
* @private
* @requires mActiveBot - active bot object, but can be undefined without error.
* @param {object} bot - The bot object.
- * @returns {object} - Determined status.
+ * @returns {void}
*/
function mSetStatusBar(bot, botContainer){
const { dataset, } = botContainer
- const { id, type, } = dataset
- const { bot_id, bot_name, thread_id, type: botType, } = bot
+ const { id, type, version, } = dataset
+ const { bot_id, bot_name, thread_id, type: botType, version: botVersion, } = bot
const botStatusBar = document.getElementById(`${ type }-status`)
if(!type || !botType==type || !botStatusBar)
return
@@ -1450,8 +1458,10 @@ function mSetStatusBar(bot, botContainer){
const botTitleName = document.getElementById(`${ type }-title-name`)
if(botTitleName)
botTitleName.textContent = response.name
- return response
-}
+ /* version */
+ const botVersionElement = document.getElementById(`${ type }-title-version`)
+ if(botVersionElement)
+ botVersionElement.textContent = `v.${ version?.includes('.') ? version : `${ version }.0` ?? '1.0' }` }
/**
* Sets collection item content on server.
* @private
From ca50a17a7ee6a400e703c1d6684c123f551c41a8 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 10 Oct 2024 22:00:35 -0400
Subject: [PATCH 09/56] 20241010 @Mookse - migrateChat Functionality #405 - bot
selection updated
---
views/assets/js/bots.mjs | 12 ++++++++----
1 file changed, 8 insertions(+), 4 deletions(-)
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index c55d320c..72b5e4a0 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1675,20 +1675,20 @@ function mTeamSelect(event){
*/
async function mToggleBotContainers(event){
event.stopPropagation()
- // add turn for first time clicked on collection header it refreshes from server, then from there you need to click
const botContainer = this
const element = event.target
const { dataset, id, } = botContainer
const itemIdSnippet = element.id.split('-').pop()
switch(itemIdSnippet){
case 'name':
- // @todo: double-click to edit in place
+ case 'title':
+ case 'titlebar':
+ mOpenStatusDropdown(this)
break
case 'icon':
- case 'title':
+ case 'type':
if(dataset?.status && !(['error', 'offline', 'unknown'].includes(dataset.status)))
await setActiveBot(dataset?.id ?? id, true)
- mOpenStatusDropdown(this)
break
case 'status':
case 'type':
@@ -1697,6 +1697,10 @@ async function mToggleBotContainers(event){
break
case 'update':
case 'upload':
+ break
+ case 'version':
+ console.log('Version:', dataset.version, 'check version against server', mTeams)
+ break
default:
break
}
From 6f39b90683881c7a3205c19fbed3f9ebb636fc05 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 10 Oct 2024 22:27:19 -0400
Subject: [PATCH 10/56] 20241010 @Mookse - migrateChat Functionality #405 - fix
modular code deformation
---
inc/js/mylife-agent-factory.mjs | 47 ++++++++++++++++++++++++++-------
inc/js/mylife-avatar.mjs | 3 ++-
2 files changed, 40 insertions(+), 10 deletions(-)
diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-agent-factory.mjs
index 57d81aff..46583fab 100644
--- a/inc/js/mylife-agent-factory.mjs
+++ b/inc/js/mylife-agent-factory.mjs
@@ -44,7 +44,7 @@ const mGeneralBotId = 'asst_yhX5mohHmZTXNIH55FX2BR1m'
const mLLMServices = new LLMServices()
const mMyLifeTeams = [
{
- active: true,
+ active: false,
allowCustom: true,
allowedTypes: ['artworks', 'editor', 'idea', 'marketing'],
defaultTypes: ['artworks', 'idea',],
@@ -104,7 +104,7 @@ const mMyLifeTeams = [
title: 'Spirituality',
},
{
- active: true,
+ active: false,
allowCustom: true,
allowedTypes: ['data-ownership', 'investment', 'ubi',],
defaultTypes: ['ubi'],
@@ -458,8 +458,8 @@ class BotFactory extends EventEmitter{
}
/**
* Gets a collection of stories of a certain format.
- * @param {string} form - The form of the stories to retrieve.
- * @returns {object[]} - The stories.
+ * @param {string} form - The form of the stories to retrieve
+ * @returns {object[]} - The stories
*/
async stories(form){
return await this.dataservices.getItemsByFields(
@@ -469,20 +469,24 @@ class BotFactory extends EventEmitter{
}
/**
* Gets a MyLife Team by id.
- * @param {Guid} teamId - The Team id.
- * @returns {object} - The Team.
+ * @param {Guid} teamId - The Team id
+ * @returns {object} - The Team
*/
team(teamId){
- return mMyLifeTeams
+ let team = mMyLifeTeams
.find(team=>team.id===teamId)
+ team = mTeam(team)
+ return team
}
/**
* Retrieves list of MyLife Teams.
- * @returns {object[]} - The array of MyLife Teams.
+ * @returns {object[]} - The array of MyLife Teams
*/
teams(){
- return mMyLifeTeams
+ const teams = mMyLifeTeams
.filter(team=>team.active ?? false)
+ .map(team=>mTeam(team))
+ return teams
}
/**
* Adds or updates a bot data in MyLife database. Note that when creating, pre-fill id.
@@ -1739,6 +1743,31 @@ function mSanitizeSchemaValue(_value) {
return wasTrimmed ? _value[0] + trimmedStr + _value[0] : trimmedStr
}
+/**
+ * Decouples team from modular reference.
+ * @param {object} team - Team object from modular codespace
+ * @returns {object} - Returns sanitized team object
+ */
+function mTeam(team){
+ const {
+ allowCustom,
+ allowedTypes,
+ defaultTypes,
+ description,
+ id,
+ name,
+ title,
+ } = team
+ return {
+ allowCustom,
+ allowedTypes: [...allowedTypes],
+ defaultTypes: [...defaultTypes],
+ description,
+ id,
+ name,
+ title,
+ }
+}
/**
* Updates bot in Cosmos, and if necessary, in LLM.
* @param {AgentFactory} factory - Factory object
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 9fd2d715..edd1b24c 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -812,7 +812,8 @@ class Avatar extends EventEmitter {
* @returns {Object[]} - List of team objects.
*/
teams(){
- return this.#factory.teams()
+ const teams = this.#factory.teams()
+ return teams
}
async thread_id(){
if(!this.conversations.length){
From 4680d8f41c731f258163e679422087077aaaf3fb Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 11 Oct 2024 00:26:48 -0400
Subject: [PATCH 11/56] 20241010 @Mookse - route `updateBotInstructions`
---
inc/js/functions.mjs | 17 +++++---
inc/js/mylife-avatar.mjs | 84 ++++++++++++++++++++++-----------------
inc/js/routes.mjs | 2 +-
views/assets/css/bots.css | 21 ++++++++++
4 files changed, 81 insertions(+), 43 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index 1919dcd2..b4342b2f 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -19,7 +19,12 @@ async function about(ctx){
function activateBot(ctx){
const { avatar, } = ctx.state
avatar.activeBotId = ctx.params.bid
- ctx.body = { activeBotId: avatar.activeBotId }
+ const { activeBotId, activeBotVersion, activeBotNewestVersion, } = avatar
+ ctx.body = {
+ activeBotId,
+ activeBotVersion,
+ version: activeBotNewestVersion,
+ }
}
async function alerts(ctx){
// @todo: put into ctx the _type_ of alert to return, system use dataservices, member use personal
@@ -391,11 +396,11 @@ function teams(ctx){
async function updateBotInstructions(ctx){
const { botId, } = ctx.request.body
const { avatar, } = ctx.state
- let success = false
- const bot = await avatar.updateBot(botId, { instructions: true, model: true, tools: true, })
- if(bot)
- success = true
- ctx.body = { bot, success, }
+ const bot = await avatar.updateBotInstructions(botId)
+ ctx.body = {
+ bot,
+ success: !!bot,
+ }
}
/**
* Proxy for uploading files to the API.
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index edd1b24c..da947e6a 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -212,8 +212,8 @@ class Avatar extends EventEmitter {
.length
if(singletonBotExists)
throw new Error(`Bot type "${type}" already exists and bot-multiples disallowed.`)
- const assistant = await mBot(this.#factory, this, bot)
- return mPruneBot(assistant)
+ bot = await mBot(this.#factory, this, bot)
+ return mPruneBot(bot)
}
/**
* Create a new conversation.
@@ -831,31 +831,33 @@ class Avatar extends EventEmitter {
return await mBot(this.#factory, this, bot) // **note**: mBot() updates `avatar.bots`
}
/**
- * Update core for bot-assistant based on type. Default updates all LLM pertinent properties.
- * @param {string} id - The id of bot to update.
- * @param {boolean} includeInstructions - Whether to include instructions in the update.
- * @param {boolean} includeModel - Whether to include model in the update.
- * @param {boolean} includeTools - Whether to include tools in the update.
- * @returns {object} - The updated bot object.
+ * Update instructions for bot-assistant based on type. Default updates all LLM pertinent properties.
+ * @param {string} id - The id of bot to update
+ * @param {boolean} migrateThread - Whether to migrate the thread to the new bot, defaults to `true`
+ * @returns {object} - The updated bot object
*/
- async updateInstructions(id=this.activeBot.id, includeInstructions=true, includeModel=true, includeTools=true){
+ async updateBotInstructions(id=this.activeBot.id, migrateThread=true){
let bot = mFindBot(this, id)
?? this.activeBot
if(!bot)
throw new Error(`Bot not found: ${ id }`)
- const { bot_id, interests, type, } = bot
- if(!type?.length)
- return
- const _bot = { bot_id, id, interests, type, }
- const vectorstoreId = this.#vectorstoreId
- const options = {
- instructions: includeInstructions,
- model: includeModel,
- tools: includeTools,
- vectorstoreId,
+ const { bot_id, flags, interests, thread_id, type, version=1.0, } = bot
+ /* check version */
+ const newestVersion = this.#factory.botInstructionsVersion(type)
+ if(newestVersion!=version){ // intentional loose match (string vs. number)
+ const _bot = { bot_id, flags, id, interests, type, }
+ const vectorstoreId = this.#vectorstoreId
+ const options = {
+ instructions: true,
+ model: true,
+ tools: true,
+ vectorstoreId,
+ }
+ /* save to && refresh bot from Cosmos */
+ bot = mSanitize( await this.#factory.updateBot(_bot, options) )
+ if(migrateThread && thread_id?.length)
+ await this.migrateChat(thread_id)
}
- /* save to && refresh bot from Cosmos */
- bot = mSanitize( await this.#factory.updateBot(_bot, options) )
return mPruneBot(bot)
}
/**
@@ -903,23 +905,26 @@ class Avatar extends EventEmitter {
return this.#activeBotId
}
/**
- * Set the active bot id. If not match found in bot list, then defaults back to this.id
+ * Set the active bot id. If not match found in bot list, then defaults back to this.id (avatar).
* @setter
* @requires mBotInstructions
- * @param {string} id - The active bot id.
+ * @param {string} botId - The requested bot id
* @returns {void}
*/
- set activeBotId(id){
- const newActiveBot = mFindBot(this, id)
+ set activeBotId(botId){
+ const newActiveBot = mFindBot(this, botId)
?? this.avatar
- const { id: newActiveId, type, version: botVersion=1.0, } = newActiveBot
- const currentVersion = this.#factory.botInstructionsVersion(type)
- if(botVersion!==currentVersion){
- this.updateInstructions(newActiveId, true, false, true)
- /* update bot in this.#bots */
-
- }
- this.#activeBotId = newActiveId
+ const { id, } = newActiveBot
+ this.#activeBotId = id
+ }
+ get activeBotNewestVersion(){
+ const { type, } = this.activeBot
+ const newestVersion = this.#factory.botInstructionsVersion(type)
+ return newestVersion
+ }
+ get activeBotVersion(){
+ const { version=1.0, } = this.activeBot
+ return version
}
/**
* Get actor or default avatar bot.
@@ -2366,11 +2371,18 @@ function mNavigation(scenes){
}
/**
* Returns a frontend-ready bot object.
- * @param {object} assistantData - The assistant data object.
+ * @param {object} bot - The bot object.
* @returns {object} - The pruned bot object.
*/
-function mPruneBot(assistantData){
- const { bot_id, bot_name: name, description, id, purpose, type, } = assistantData
+function mPruneBot(bot){
+ const {
+ bot_id,
+ bot_name: name,
+ description,
+ id,
+ purpose,
+ type,
+ } = bot
return {
bot_id,
name,
diff --git a/inc/js/routes.mjs b/inc/js/routes.mjs
index b385a6e5..d18b02e2 100644
--- a/inc/js/routes.mjs
+++ b/inc/js/routes.mjs
@@ -133,7 +133,7 @@ _memberRouter.post('/summarize', summarize)
_memberRouter.post('/teams/:tid', team)
_memberRouter.post('/upload', upload)
_memberRouter.put('/bots/:bid', bots)
-_memberRouter.put('/bots/system-update/:bid', updateBotInstructions)
+_memberRouter.put('/bots/version/:bid', updateBotInstructions)
_memberRouter.put('/item/:iid', item)
// Mount the subordinate routers along respective paths
_Router.use('/members', _memberRouter.routes(), _memberRouter.allowedMethods())
diff --git a/views/assets/css/bots.css b/views/assets/css/bots.css
index cd857547..7e9a4346 100644
--- a/views/assets/css/bots.css
+++ b/views/assets/css/bots.css
@@ -237,6 +237,7 @@
}
.bot-title-version {
color: lightblue;
+ cursor: not-allowed;
flex: 0 0 auto;
font-size: 0.6rem;
margin-left: auto; /* Pushes the version div to the right end */
@@ -297,6 +298,26 @@
animation: none;
padding: 0;
}
+.update-available {
+ align-items: flex-end;
+ background-color: rgb(255 0 123 / 50%);
+ border: thin solid rgb(255 157 190 / 66%);
+ border-radius: var(--border-radius);
+ display: flex;
+ justify-content: flex-end;
+ padding: 0.3rem;
+}
+.update-available:hover {
+ align-items: flex-end;
+ background-color: rgba(255, 251, 0, 0.5);
+ border: thin solid rgba(255, 205, 222, 0.66);
+ border-radius: var(--border-radius);
+ color: black;
+ cursor: pointer;
+ display: flex;
+ justify-content: flex-end;
+ padding: 0.3rem;
+}
/* bot-collections */
.collection {
background-color: royalblue;
From ffad103e4b5dbbce5d3c51e71b88c862fb48436c Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 11 Oct 2024 01:36:56 -0400
Subject: [PATCH 12/56] 20241010 @Mookse - migrateChat Functionality #405 -
route requirement fixed for updateBotInstructions
---
inc/js/functions.mjs | 6 ++-
inc/js/mylife-avatar.mjs | 18 +++++---
views/assets/js/bots.mjs | 92 +++++++++++++++++++++++++++++++---------
3 files changed, 88 insertions(+), 28 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index b4342b2f..16352657 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -394,9 +394,11 @@ function teams(ctx){
ctx.body = avatar.teams()
}
async function updateBotInstructions(ctx){
- const { botId, } = ctx.request.body
+ const { bid, } = ctx.params
+ if(!bid?.length)
+ ctx.throw(400, `missing bot id`)
const { avatar, } = ctx.state
- const bot = await avatar.updateBotInstructions(botId)
+ const bot = await avatar.updateBotInstructions(bid)
ctx.body = {
bot,
success: !!bot,
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index da947e6a..ba5f72cd 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -500,11 +500,13 @@ class Avatar extends EventEmitter {
?.[0]
return { content, metadata, role, }
})
+ .filter(message=>!/^## [^\n]* LIST\n/.test(message.content))
const { botId, } = conversation
const bot = this.getBot(botId)
switch(bot.type){
case 'biographer':
case 'personal-biographer':
+ const type = 'memory'
const memories = ( await this.collections('story') )
.sort((a, b)=>a._ts-b._ts)
.slice(0, 12)
@@ -516,7 +518,7 @@ class Avatar extends EventEmitter {
.join(',')
.slice(0, 512)
messages.push({
- content: `## MEMORY COLLECTION LIST\n${ memoryList }`, // insert actual memory list with titles here for intelligence to reference
+ content: `## ${ type } LIST\n${ memoryList }`, // insert actual memory list with titles here for intelligence to reference
metadata: {
collectionList: memoryCollectionList,
collectiontypes: 'memory,story,narrative',
@@ -530,7 +532,7 @@ class Avatar extends EventEmitter {
const _type = 'entry'
const entries = ( await this.collections(_type) )
.sort((a, b)=>a._ts-b._ts)
- .slice(0, 128)
+ .slice(0, 25)
const entryList = entries
.map(entry=>`- itemId: ${ entry.id } :: ${ entry.title }`)
.join('\n')
@@ -539,7 +541,7 @@ class Avatar extends EventEmitter {
.join(',')
.slice(0, 512)
messages.push({
- content: `## ${ _type.toUpperCase() } List:\n${ entryList }`,
+ content: `## ${ _type.toUpperCase() } LIST\n${ entryList }`,
metadata: {
collectionList: entryCollectionList,
collectiontypes: _type,
@@ -824,6 +826,7 @@ class Avatar extends EventEmitter {
}
/**
* Update a specific bot.
+ * @async
* @param {object} bot - Bot data to set.
* @returns {object} - The updated bot.
*/
@@ -832,6 +835,7 @@ class Avatar extends EventEmitter {
}
/**
* Update instructions for bot-assistant based on type. Default updates all LLM pertinent properties.
+ * @async
* @param {string} id - The id of bot to update
* @param {boolean} migrateThread - Whether to migrate the thread to the new bot, defaults to `true`
* @returns {object} - The updated bot object
@@ -841,7 +845,7 @@ class Avatar extends EventEmitter {
?? this.activeBot
if(!bot)
throw new Error(`Bot not found: ${ id }`)
- const { bot_id, flags, interests, thread_id, type, version=1.0, } = bot
+ const { bot_id, flags='', interests='', thread_id, type, version=1.0, } = bot
/* check version */
const newestVersion = this.#factory.botInstructionsVersion(type)
if(newestVersion!=version){ // intentional loose match (string vs. number)
@@ -2376,20 +2380,20 @@ function mNavigation(scenes){
*/
function mPruneBot(bot){
const {
- bot_id,
bot_name: name,
description,
id,
purpose,
type,
+ version,
} = bot
return {
- bot_id,
- name,
description,
id,
+ name,
purpose,
type,
+ version,
}
}
function mPruneConversation(conversation){
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 72b5e4a0..b4237d16 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -158,9 +158,9 @@ async function setActiveBot(event, dynamic=false){
throw new Error(`ERROR: failure to set active bot.`)
if(initialActiveBot===mActiveBot)
return // no change, no problem
- const { id, } = mActiveBot
+ const { id, type, } = mActiveBot
/* confirm via server request: set active bot */
- const serverActiveId = await fetch(
+ const serverResponse = await fetch(
'/members/bots/activate/' + id,
{
method: 'POST',
@@ -168,24 +168,21 @@ async function setActiveBot(event, dynamic=false){
'Content-Type': 'application/json'
}
})
- .then(response => {
+ .then(response=>{
if(!response.ok){
throw new Error(`HTTP error! Status: ${response.status}`)
}
return response.json()
})
- .then(response => {
- return response.activeBotId
- })
- .catch(error => {
- console.log('Error:', error)
- alert('Server error setting active bot.')
+ .catch(error=>{
+ addMessage(`Server error setting active bot: ${ error.message }`)
return
})
/* update active bot */
- if(serverActiveId!==id){
+ const { activeBotId, activeBotVersion, version, } = serverResponse
+ if(activeBotId!==id){
mActiveBot = initialActiveBot
- throw new Error(`ERROR: server failed to set active bot.`)
+ addMessage('Server error setting active bot.')
}
/* update page bot data */
const { activated=[], activatedFirst=Date.now(), } = mActiveBot
@@ -193,6 +190,17 @@ async function setActiveBot(event, dynamic=false){
activated.push(Date.now()) // newest date is last to .pop()
// dynamic = (Date.now()-activated.pop()) > (20*60*1000)
mActiveBot.activated = activated
+ if(activeBotVersion!==version){
+ const botVersion = document.getElementById(`${ type }-title-version`)
+ if(botVersion){
+ botVersion.classList.add('update-available')
+ botVersion.dataset.botId = activeBotId
+ botVersion.dataset.currentVersion = activeBotVersion
+ botVersion.dataset.type = type
+ botVersion.dataset.updateVersion = version
+ botVersion.addEventListener('click', mUpdateBotVersion, { once: true })
+ }
+ }
/* update page */
mSpotlightBotBar()
mSpotlightBotStatus()
@@ -261,6 +269,19 @@ function mBotActive(id){
return id===mActiveBot?.id
?? false
}
+/**
+ * Request version update to bot.
+ * @param {Guid} botId - The bot id to update
+ * @returns {object} - Response from server { bot, success, }
+ */
+async function mBotVersionUpdate(botId){
+ const url = window.location.origin + '/members/bots/version/' + botId
+ const method = 'PUT'
+ const response = await fetch(url, { method, })
+ if(!response.ok)
+ throw new Error(`HTTP error! Status: ${response.status}`)
+ return await response.json()
+}
/**
* Request bot be created on server.
* @requires mActiveTeam
@@ -1362,12 +1383,13 @@ function mSetAttributes(bot=mActiveBot, botContainer){
const {
activated=[],
activeFirst,
- bot_id: botId,
bot_name='Anonymous',
dob,
- id,
+ flags,
+ id: bot_id,
interests,
mbr_id,
+ name,
narrative,
privacy,
type,
@@ -1377,11 +1399,11 @@ function mSetAttributes(bot=mActiveBot, botContainer){
/* attributes */
const attributes = [
{ name: 'activated', value: activated },
- { name: 'active', value: mBotActive(id) },
+ { name: 'active', value: mBotActive(bot_id) },
{ name: 'activeFirst', value: activeFirst },
- { name: 'bot_id', value: botId },
- { name: 'bot_name', value: bot_name },
- { name: 'id', value: id },
+ { name: 'bot_id', value: bot_id },
+ { name: 'bot_name', value: name ?? bot_name },
+ { name: 'id', value: bot_id },
{ name: 'initialized', value: Date.now() },
{ name: 'mbr_id', value: mbr_id },
{ name: 'type', value: type },
@@ -1389,6 +1411,8 @@ function mSetAttributes(bot=mActiveBot, botContainer){
]
if(dob)
attributes.push({ name: 'dob', value: dob })
+ if(flags)
+ attributes.push({ name: 'flags', value: flags })
if(interests)
attributes.push({ name: 'interests', value: interests })
if(narrative)
@@ -1418,7 +1442,7 @@ function mSetAttributes(bot=mActiveBot, botContainer){
function mSetStatusBar(bot, botContainer){
const { dataset, } = botContainer
const { id, type, version, } = dataset
- const { bot_id, bot_name, thread_id, type: botType, version: botVersion, } = bot
+ const { bot_id, bot_name, type: botType, version: botVersion, } = bot
const botStatusBar = document.getElementById(`${ type }-status`)
if(!type || !botType==type || !botStatusBar)
return
@@ -1461,7 +1485,8 @@ function mSetStatusBar(bot, botContainer){
/* version */
const botVersionElement = document.getElementById(`${ type }-title-version`)
if(botVersionElement)
- botVersionElement.textContent = `v.${ version?.includes('.') ? version : `${ version }.0` ?? '1.0' }` }
+ botVersionElement.textContent = mVersion(version)
+}
/**
* Sets collection item content on server.
* @private
@@ -2086,6 +2111,25 @@ function mUpdateBotContainerAddenda(botContainer){
break
}
}
+/**
+ * Updates bot version on server.
+ * @param {Event} event - The event object
+ * @returns {void}
+ */
+async function mUpdateBotVersion(event){
+ event.stopPropagation()
+ const { classList, dataset,} = event.target
+ const { botId, currentVersion, updateVersion, } = dataset
+ if(currentVersion==updateVersion)
+ return
+ const updatedVersion = await mBotVersionUpdate(botId)
+ if(updatedVersion?.success){
+ const { version, } = updatedVersion.bot
+ dataset.currentVersion = version
+ event.target.textContent = mVersion(version)
+ classList.remove('update-available')
+ }
+}
/**
* Update the identified collection with provided specifics.
* @param {string} type - The collection type.
@@ -2331,6 +2375,16 @@ function mUploadFilesInputRemove(fileInput, uploadParent, uploadButton){
uploadParent.removeChild(fileInput)
uploadButton.disabled = false
}
+/**
+ * Versions per frontend.
+ * @param {string} version - The version to format
+ * @returns {string} - The formatted version
+ */
+function mVersion(version){
+ version = version.toString()
+ version = `v.${ version?.includes('.') ? version : `${ version }.0` ?? '1.0' }`
+ return version
+}
/* exports */
// @todo - export combine of fetchBots and updatePageBots
export {
From eafb1b816fdbcb625d579dc192a66939361b275b Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sun, 13 Oct 2024 23:51:47 -0400
Subject: [PATCH 13/56] 20241013 @Mookse - migrateChat Functionality #405 -
generic fixes
---
.../class-conversation-functions.mjs | 1 -
.../class-extenders.mjs | 18 +-
inc/js/mylife-avatar.mjs | 265 +++++++++++-------
inc/js/mylife-data-service.js | 15 +-
4 files changed, 183 insertions(+), 116 deletions(-)
diff --git a/inc/js/factory-class-extenders/class-conversation-functions.mjs b/inc/js/factory-class-extenders/class-conversation-functions.mjs
index 55afc6e6..91af01ff 100644
--- a/inc/js/factory-class-extenders/class-conversation-functions.mjs
+++ b/inc/js/factory-class-extenders/class-conversation-functions.mjs
@@ -32,7 +32,6 @@ async function mSaveConversation(factory, conversation){
type,
}
const newConversation = await factory.dataservices.pushItem(_newConversation)
- console.log('mSaveConversation::newConversation::created', id, newConversation?.id)
return !!newConversation
}
const updatedConversation = await factory.dataservices.patch(
diff --git a/inc/js/factory-class-extenders/class-extenders.mjs b/inc/js/factory-class-extenders/class-extenders.mjs
index 25a1e02f..d6f9376e 100644
--- a/inc/js/factory-class-extenders/class-extenders.mjs
+++ b/inc/js/factory-class-extenders/class-extenders.mjs
@@ -125,6 +125,7 @@ function extendClass_contribution(originClass, referencesObject) {
*/
function extendClass_conversation(originClass, referencesObject) {
class Conversation extends originClass {
+ #bot_id
#factory
#messages = []
#saved = false
@@ -141,7 +142,8 @@ function extendClass_conversation(originClass, referencesObject) {
super(obj)
this.#factory = factory
this.#thread = thread
- this.bot_id = bot_id
+ if(factory.globals.isValidGuid(bot_id))
+ this.#bot_id = bot_id
this.form = this.form
?? 'system'
this.name = `conversation_${this.#factory.mbr_id}`
@@ -235,7 +237,10 @@ function extendClass_conversation(originClass, referencesObject) {
* @returns {Guid} - The bot id.
*/
get botId(){
- return this.bot_id
+ return this.#bot_id
+ }
+ get bot_id(){
+ return this.#bot_id
}
/**
* Set the id {Guid} of the conversation's bot.
@@ -244,7 +249,14 @@ function extendClass_conversation(originClass, referencesObject) {
* @returns {void}
*/
set botId(bot_id){
- this.bot_id = bot_id
+ if(!this.#factory.globals.isValidGuid(bot_id))
+ throw new Error(`Invalid bot_id: ${ bot_id }`)
+ this.#bot_id = bot_id
+ }
+ set bot_id(bot_id){
+ if(!this.#factory.globals.isValidGuid(bot_id))
+ throw new Error(`Invalid bot_id: ${ bot_id }`)
+ this.#bot_id = bot_id
}
get isSaved(){
return this.#saved
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index ba5f72cd..38ebcf96 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -99,7 +99,7 @@ class Avatar extends EventEmitter {
if(!activeBotId)
throw new Error('Parameter `activeBotId` required.')
const { activeBot, factory } = this
- const { id: botId, thread_id, } = activeBot
+ const { bot_id, id: botId, thread_id, } = activeBot
if(botId!==activeBotId)
throw new Error(`Invalid bot id: ${ activeBotId }, active bot id: ${ botId }`)
conversation = conversation
@@ -107,7 +107,8 @@ class Avatar extends EventEmitter {
?? await this.createConversation('chat', threadId ?? thread_id, activeBotId)
if(!conversation)
throw new Error('No conversation found for thread id and could not be created.')
- conversation.bot_id = activeBot.bot_id // pass in via quickly mutating conversation (or independently if preferred in end), versus llmServices which are global
+ conversation.bot_id = botId
+ conversation.llm_id = bot_id
let _message = message,
messages = []
if(shadowId)
@@ -115,9 +116,9 @@ class Avatar extends EventEmitter {
else {
if(itemId){
// @todo - check if item exists in memory, fewer pings and inclusions overall
- const { summary, } = await factory.item(itemId)
+ let { summary, } = await factory.item(itemId)
if(summary?.length){
- _message = `possible **update-summary-request**: itemId=${ itemId }\n`
+ summary = `possible **update-summary-request**: itemId=${ itemId }\n`
+ `**member-update-request**:\n`
+ message
+ `\n**current-summary-in-database**:\n`
@@ -485,90 +486,14 @@ class Avatar extends EventEmitter {
* @returns {Conversation} - The migrated conversation object
*/
async migrateChat(thread_id){
- /* MyLife conversation re-assignment */
+ /* validate request */
const conversation = this.getConversation(thread_id)
if(!conversation)
- throw new Error(`Conversation not found with thread_id: ${ thread_id }`)
- let messages = await this.#llmServices.messages(thread_id)
- messages = messages
- .slice(0, 25)
- .map(message=>{
- const { content: contentArray, id, metadata, role, } = message
- const content = contentArray
- .filter(_content=>_content.type==='text')
- .map(_content=>_content.text?.value)
- ?.[0]
- return { content, metadata, role, }
- })
- .filter(message=>!/^## [^\n]* LIST\n/.test(message.content))
- const { botId, } = conversation
- const bot = this.getBot(botId)
- switch(bot.type){
- case 'biographer':
- case 'personal-biographer':
- const type = 'memory'
- const memories = ( await this.collections('story') )
- .sort((a, b)=>a._ts-b._ts)
- .slice(0, 12)
- const memoryList = memories
- .map(memory=>`- itemId: ${ memory.id } :: ${ memory.title }`)
- .join('\n')
- const memoryCollectionList = memories
- .map(memory=>memory.id)
- .join(',')
- .slice(0, 512)
- messages.push({
- content: `## ${ type } LIST\n${ memoryList }`, // insert actual memory list with titles here for intelligence to reference
- metadata: {
- collectionList: memoryCollectionList,
- collectiontypes: 'memory,story,narrative',
- },
- role: 'assistant',
- }) // add summary of Memories (etc. due to type) for intelligence to reference, also could add attachment file
- break
- case 'diary':
- case 'journal':
- case 'journaler':
- const _type = 'entry'
- const entries = ( await this.collections(_type) )
- .sort((a, b)=>a._ts-b._ts)
- .slice(0, 25)
- const entryList = entries
- .map(entry=>`- itemId: ${ entry.id } :: ${ entry.title }`)
- .join('\n')
- const entryCollectionList = entries
- .map(entry=>entry.id)
- .join(',')
- .slice(0, 512)
- messages.push({
- content: `## ${ _type.toUpperCase() } LIST\n${ entryList }`,
- metadata: {
- collectionList: entryCollectionList,
- collectiontypes: _type,
- },
- role: 'assistant',
- }) // add summary of Entries
- break
- default:
- break
- }
- const metadata = {
- bot_id: botId,
- conversation_id: conversation.id,
- }
- const newThread = await this.#llmServices.thread(null, messages.reverse(), metadata)
- conversation.setThread(newThread)
- bot.thread_id = conversation.thread_id
- const _bot = {
- id: bot.id,
- thread_id: bot.thread_id,
- }
- await this.#factory.updateBot(_bot)
- if(mAllowSave)
- conversation.save()
- else
- console.log('migrateChat::BYPASS-SAVE', conversation.thread_id)
- return conversation
+ throw new Error(`Conversation thread_id not found: ${ thread_id }`)
+ /* execute request */
+ const updatedConversation = await mMigrateChat(this, this.#factory, this.#llmServices, conversation)
+ /* respond request */
+ return updatedConversation
}
/**
* Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM.
@@ -666,30 +591,42 @@ class Avatar extends EventEmitter {
* @returns {object} - The response object { instruction, responses, success, }
*/
async retireChat(botId){
- const retiredConversation = this.getConversation(null, botId)
- console.log('retireChat::conversations', this.conversations.map(c=>(
- { botId, _botId: c.botId, bot_id: c.bot_id, thread_id: c.thread_id, }
- )))
- if(!retiredConversation)
+ /* validate request */
+ const conversation = this.getConversation(null, botId)
+ if(!conversation){
throw new Error(`Conversation not found with bot id: ${ botId }`)
- const { thread_id: cid, } = retiredConversation
+ }
+ const { thread_id: cid, } = conversation
const bot = this.getBot(botId)
const { id: _botId, thread_id: tid, } = bot
if(botId!=_botId)
throw new Error(`Bot id mismatch: ${ botId }!=${ bot_id }`)
if(tid!=cid)
throw new Error(`Conversation mismatch: ${ tid }!=${ cid }`)
- const conversation = await this.migrateChat(tid)
- console.log('retireChat::conversation', conversation)
- const response = {
- responses: [{
- agent: 'server',
- message: `I have successfully retired this conversation thread and started a new one.`,
- purpose: 'system',
- type: 'chat',
- }],
- success: true,
- }
+ /* execute request */
+ const updatedConversation = await mMigrateChat(this, this.#factory, this.#llmServices, conversation)
+ /* respond request */
+ const response = !!updatedConversation
+ ? { /* @todo - add frontend instructions to remove migrateChat button */
+ instruction: null,
+ responses: [{
+ agent: 'server',
+ message: `I have successfully retired this conversation thread and started a new one.`,
+ purpose: 'system',
+ type: 'chat',
+ }],
+ success: true,
+ }
+ : {
+ instruction: null,
+ responses: [{
+ agent: 'server',
+ message: `I'm sorry - I encountered an error while trying to retire this conversation; please try again.`,
+ purpose: 'system',
+ type: 'chat',
+ }],
+ success: false,
+ }
return response
}
/**
@@ -1613,10 +1550,12 @@ async function mBot(factory, avatar, bot){
* @returns {Promise} - Array of Message instances in descending chronological order
*/
async function mCallLLM(llmServices, conversation, prompt, factory, avatar){
- const { bot_id, thread_id } = conversation
- if(!thread_id || !bot_id)
+ const { bot_id, llm_id, thread_id } = conversation
+ const botId = llm_id
+ ?? bot_id
+ if(!thread_id || !botId)
throw new Error('Both `thread_id` and `bot_id` required for LLM call.')
- const messages = await llmServices.getLLMResponse(thread_id, bot_id, prompt, factory, avatar)
+ const messages = await llmServices.getLLMResponse(thread_id, botId, prompt, factory, avatar)
messages.sort((mA, mB)=>{
return mB.created_at - mA.created_at
})
@@ -2342,6 +2281,118 @@ async function mInit(factory, llmServices, avatar, bots, assetAgent){
/* lived-experiences */
avatar.experiencesLived = await factory.experiencesLived(false)
}
+/**
+ * Migrates specified conversation (by thread_id) and returns conversation with new thread processed and saved to bot, both as a document and in avatar memory.
+ * @param {Avatar} avatar - Avatar object
+ * @param {AgentFactory} factory - AgentFactory object
+ * @param {LLMServices} llm - OpenAI object
+ * @param {string} thread_id - The thread_id of the conversation
+ * @returns
+ */
+async function mMigrateChat(avatar, factory, llm, conversation){
+ /* constants and variables */
+ const { thread_id, } = conversation
+ const chatLimit=25
+ let messages = await llm.messages(thread_id) // @todo - limit to 25 messages or modify request
+ if(!messages?.length)
+ return conversation
+ const { botId, } = conversation
+ const bot = avatar.getBot(botId)
+ const botType = bot.type
+ let disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
+ itemCollectionTypes='item',
+ itemLimit=1000,
+ type='item'
+ switch(botType){
+ case 'biographer':
+ case 'personal-biographer':
+ type = 'memory'
+ itemCollectionTypes = `memory,story,narrative`
+ break
+ case 'diary':
+ case 'journal':
+ case 'journaler':
+ type = 'entry'
+ const itemType = botType==='journaler'
+ ? 'journal'
+ : botType
+ itemCollectionTypes = `${ itemType },entry,`
+ break
+ default:
+ break
+ }
+ const chatSummary=`## ${ type.toUpperCase() } CHAT SUMMARY\n`,
+ chatSummaryRegex = /^## [^\n]* CHAT SUMMARY\n/,
+ itemSummary=`## ${ type.toUpperCase() } LIST\n`,
+ itemSummaryRegex = /^## [^\n]* LIST\n/
+ const items = ( await avatar.collections(type) )
+ .sort((a, b)=>a._ts-b._ts)
+ .slice(0, itemLimit)
+ const itemList = items
+ .map(item=>`- itemId: ${ item.id } :: ${ item.title }`)
+ .join('\n')
+ const itemCollectionList = items
+ .map(item=>item.id)
+ .join(',')
+ .slice(0, 512) // limit for metadata string field
+ const metadata = {
+ bot_id: botId,
+ conversation_id: conversation.id,
+ }
+ /* prune messages source material */
+ messages = messages
+ .slice(0, chatLimit)
+ .map(message=>{
+ const { content: contentArray, id, metadata, role, } = message
+ const content = contentArray
+ .filter(_content=>_content.type==='text')
+ .map(_content=>_content.text?.value)
+ ?.[0]
+ return { content, id, metadata, role, }
+ })
+ .filter(message=>!itemSummaryRegex.test(message.content))
+ const summaryMessage = messages
+ .filter(message=>!chatSummaryRegex.test(message.content))
+ .map(message=>message.content)
+ .join('\n')
+ /* contextualize previous content */
+ const summaryMessages = []
+ /* summary of items */
+ if(items.length)
+ summaryMessages.push({
+ content: itemSummary + disclaimer + itemList,
+ metadata: {
+ collectionList: itemCollectionList,
+ collectiontypes: itemCollectionTypes,
+ },
+ role: 'assistant',
+ })
+ /* summary of messages */
+ if(summaryMessage.length)
+ summaryMessages.push({
+ content: chatSummary + disclaimer + summaryMessage,
+ metadata: {
+ collectiontypes: itemCollectionTypes,
+ },
+ role: 'assistant',
+ })
+ if(!summaryMessages.length)
+ return conversation
+ /* add messages to new thread */
+ const newThread = await llm.thread(null, summaryMessages.reverse(), metadata)
+ conversation.setThread(newThread)
+ bot.thread_id = conversation.thread_id
+ const _bot = {
+ id: bot.id,
+ thread_id: conversation.thread_id,
+ }
+ factory.updateBot(_bot) // removed await
+ if(mAllowSave)
+ conversation.save()
+ else
+ console.log('migrateChat::BYPASS-SAVE', conversation.thread_id)
+ return conversation
+}
/**
* Get experience scene navigation array.
* @getter
@@ -2519,7 +2570,7 @@ async function mReliveMemoryNarration(avatar, factory, llm, bot, item, memberInp
let relivingMemory = relivingMemories.find(reliving=>reliving.item.id===id)
if(!relivingMemory){ /* create new activated reliving memory */
const conversation = await avatar.createConversation('memory', undefined, botId, false)
- conversation.bot_id = bot_id
+ conversation.llm_id = bot_id
const { thread_id, } = conversation
relivingMemory = {
bot,
diff --git a/inc/js/mylife-data-service.js b/inc/js/mylife-data-service.js
index 45d45dd9..dad8d86a 100644
--- a/inc/js/mylife-data-service.js
+++ b/inc/js/mylife-data-service.js
@@ -249,12 +249,15 @@ class Dataservices {
* @returns {array} - The collection items with no wrapper.
*/
async collections(type){
- if(type==='experience') // corrections
+ /* validate request */
+ if(type==='experience')
type = 'lived-experience'
- if(type?.length && this.#collectionTypes.includes(type))
- return await this.getItems(type)
- else
- return Promise.all([
+ if(type==='memory')
+ type = 'story'
+ /* execute request */
+ const response = type?.length && this.#collectionTypes.includes(type)
+ ? await this.getItems(type)
+ : await Promise.all([
this.collectionConversations(),
this.collectionEntries(),
this.collectionLivedExperiences(),
@@ -272,6 +275,8 @@ class Dataservices {
console.log('mylife-data-service::collections() error', err)
return []
})
+ /* respond request */
+ return response
}
/**
* Creates a new bot in the database.
From 5bf472db8a3d0186bb6b93655c6bb20d6f986869 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Mon, 14 Oct 2024 00:31:31 -0400
Subject: [PATCH 14/56] 20241013 @Mookse - migrateChat Functionality #405 -
bot_id in conversation should be guid only - bot_name for dataset
---
inc/js/functions.mjs | 4 +---
inc/js/mylife-avatar.mjs | 10 ++++++++++
views/assets/js/bots.mjs | 10 +++++-----
3 files changed, 16 insertions(+), 8 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index 16352657..a9d21c2f 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -54,14 +54,12 @@ async function bots(ctx){
} else {
const {
activeBotId,
- bots: awaitBots, // **note**: bots needs await
+ prunedBots: bots,
mbr_id,
} = avatar
- const bots = await awaitBots
ctx.body = { // wrap bots
activeBotId,
bots,
- mbr_id,
}
}
break
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 38ebcf96..8a3cdaae 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -1258,6 +1258,16 @@ class Avatar extends EventEmitter {
get personalAssistant(){
return this.avatar
}
+ /**
+ * Get a list of available bots (pruned) for the member.
+ * @getter
+ * @returns {Object[]} - Array of pruned bot objects
+ */
+ get prunedBots(){
+ const bots = this.#bots
+ .map(bot=>mPruneBot(bot))
+ return bots
+ }
/**
* Get the `active` reliving memories.
* @getter
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index b4237d16..126c5ab8 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1388,7 +1388,6 @@ function mSetAttributes(bot=mActiveBot, botContainer){
flags,
id: bot_id,
interests,
- mbr_id,
name,
narrative,
privacy,
@@ -1405,7 +1404,6 @@ function mSetAttributes(bot=mActiveBot, botContainer){
{ name: 'bot_name', value: name ?? bot_name },
{ name: 'id', value: bot_id },
{ name: 'initialized', value: Date.now() },
- { name: 'mbr_id', value: mbr_id },
{ name: 'type', value: type },
{ name: 'version', value: version },
]
@@ -1442,12 +1440,14 @@ function mSetAttributes(bot=mActiveBot, botContainer){
function mSetStatusBar(bot, botContainer){
const { dataset, } = botContainer
const { id, type, version, } = dataset
- const { bot_id, bot_name, type: botType, version: botVersion, } = bot
+ const { bot_name, name, type: botType, version: botVersion, } = bot
const botStatusBar = document.getElementById(`${ type }-status`)
if(!type || !botType==type || !botStatusBar)
return
+ const botName = name
+ ?? bot_name
const response = {
- name: bot_name,
+ name: botName,
status: 'unknown',
type: type.split('-').pop(),
}
@@ -1459,7 +1459,7 @@ function mSetStatusBar(bot, botContainer){
botIcon.classList.add('active')
response.status = 'active'
break
- case ( bot_id?.length>0 ): // online
+ case ( botName?.length>0 ): // online
botIcon.classList.remove('active', 'offline', 'error')
botIcon.classList.add('online')
response.status = 'online'
From 55b1388363a4b0bd50937cfbd2ab169fc621e14e Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Mon, 14 Oct 2024 00:32:30 -0400
Subject: [PATCH 15/56] 20241013 @Mookse - migrateChat Functionality #405 -
bot_name fix
---
views/assets/js/bots.mjs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 126c5ab8..f857c565 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1396,12 +1396,14 @@ function mSetAttributes(bot=mActiveBot, botContainer){
version
} = bot
/* attributes */
+ const botName = name
+ ?? bot_name
const attributes = [
{ name: 'activated', value: activated },
{ name: 'active', value: mBotActive(bot_id) },
{ name: 'activeFirst', value: activeFirst },
{ name: 'bot_id', value: bot_id },
- { name: 'bot_name', value: name ?? bot_name },
+ { name: 'bot_name', value: botName },
{ name: 'id', value: bot_id },
{ name: 'initialized', value: Date.now() },
{ name: 'type', value: type },
From e1c779bfbb9de336eeb9cdb0fa2e01fe7aaba900 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Mon, 14 Oct 2024 12:54:57 -0400
Subject: [PATCH 16/56] 20241014 @Mookse - chat input placeholder `undefined`
---
views/assets/js/bots.mjs | 13 ++++++-------
views/assets/js/members.mjs | 10 +++++-----
2 files changed, 11 insertions(+), 12 deletions(-)
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index f857c565..6e70e72e 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -528,12 +528,12 @@ function mCloseTeamPopup(event){
* @returns {HTMLDivElement} - The bot thumb container.
*/
function mCreateBotThumb(bot=getBot()){
- const { bot_name, id, type, } = bot
+ const { id, name, type, } = bot
/* bot-thumb container */
const botThumbContainer = document.createElement('div')
botThumbContainer.id = `bot-bar-container_${ id }`
botThumbContainer.name = `bot-bar-container-${ type }`
- botThumbContainer.title = bot_name
+ botThumbContainer.title = name
botThumbContainer.addEventListener('click', setActiveBot)
botThumbContainer.classList.add('bot-thumb-container')
/* bot-thumb */
@@ -1442,14 +1442,12 @@ function mSetAttributes(bot=mActiveBot, botContainer){
function mSetStatusBar(bot, botContainer){
const { dataset, } = botContainer
const { id, type, version, } = dataset
- const { bot_name, name, type: botType, version: botVersion, } = bot
+ const { id: botId, name, type: botType, version: botVersion, } = bot
const botStatusBar = document.getElementById(`${ type }-status`)
if(!type || !botType==type || !botStatusBar)
return
- const botName = name
- ?? bot_name
const response = {
- name: botName,
+ name,
status: 'unknown',
type: type.split('-').pop(),
}
@@ -1461,7 +1459,7 @@ function mSetStatusBar(bot, botContainer){
botIcon.classList.add('active')
response.status = 'active'
break
- case ( botName?.length>0 ): // online
+ case ( name?.length>0 ): // online
botIcon.classList.remove('active', 'offline', 'error')
botIcon.classList.add('online')
response.status = 'online'
@@ -2047,6 +2045,7 @@ function mUpdateBotContainerAddenda(botContainer){
/* update mBot */
const bot = mBot(id)
bot.bot_name = bot_name
+ bot.name = bot_name
} else {
dataset.bot_name = localVars.bot_name
}
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 0dc6fda3..7c94a259 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -123,8 +123,8 @@ function clearSystemChat(){
* @returns {void}
*/
function decorateActiveBot(activeBot=activeBot()){
- const { bot_name, id, purpose, type, } = activeBot
- chatInputField.placeholder = `Type your message to ${ bot_name }...`
+ const { id, name, } = activeBot
+ chatInputField.placeholder = `Type your message to ${ name }...`
// additional func? clear chat?
}
function escapeHtml(text) {
@@ -764,13 +764,13 @@ async function mSubmitChat(message) {
* @returns {void}
*/
function toggleMemberInput(display=true, hidden=false, connectingText='Connecting with '){
- const { bot_name, id, mbr_id, provider, purpose, type, } = activeBot()
+ const { id, name, } = activeBot
if(display){
hide(awaitButton)
awaitButton.classList.remove('slide-up')
chatInput.classList.add('slide-up')
chatInputField.style.height = 'auto'
- chatInputField.placeholder = `type your message to ${ bot_name }...`
+ chatInputField.placeholder = `type your message to ${ name }...`
chatInputField.value = null
show(chatInput)
} else {
@@ -778,7 +778,7 @@ function toggleMemberInput(display=true, hidden=false, connectingText='Connectin
chatInput.classList.remove('fade-in')
chatInput.classList.remove('slide-up')
awaitButton.classList.add('slide-up')
- awaitButton.innerHTML = connectingText + bot_name + '...'
+ awaitButton.innerHTML = connectingText + name + '...'
show(awaitButton)
}
if(hidden){
From edbb51428e1e5436a8d9b7fd0e65b420b8d61d8e Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Mon, 14 Oct 2024 16:50:49 -0400
Subject: [PATCH 17/56] 20241014 @Mookse - session start seems to fire
updateBot() with instructions - huge error with starting conversation with Q
and THEN logging in - solve required elimination of session property
`thread_id` which was being leveraged to store separate threads for different
browsers
---
inc/js/mylife-avatar.mjs | 9 +++++++--
inc/js/session.mjs | 1 +
views/assets/js/bots.mjs | 5 ++++-
3 files changed, 12 insertions(+), 3 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 8a3cdaae..368f3a4a 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -228,9 +228,14 @@ class Avatar extends EventEmitter {
*/
async createConversation(type='chat', threadId, botId=this.activeBotId, saveToConversations=true){
const thread = await this.#llmServices.thread(threadId)
- const form = this.activeBot.type.split('-').pop()
+ const { mbr_id, type: botType, } = this.getBot(botId)
+ const form = botType.split('-').pop()
const conversation = new (this.#factory.conversation)(
- { form, mbr_id: this.mbr_id, type, },
+ {
+ form,
+ mbr_id,
+ type,
+ },
this.#factory,
thread,
botId
diff --git a/inc/js/session.mjs b/inc/js/session.mjs
index 68dbef05..fef9ee3e 100644
--- a/inc/js/session.mjs
+++ b/inc/js/session.mjs
@@ -29,6 +29,7 @@ class MylifeMemberSession extends EventEmitter {
await this.#factory.init(this.mbr_id) // needs only `init()` with different `mbr_id` to reset
this.#Member = await this.factory.getMyLifeMember()
this.#autoplayed = false // resets autoplayed flag, although should be impossible as only other "variant" requires guest status, as one-day experiences can be run for guests also [for pay]
+ this.thread_id = null // reset thread_id from Q-session
this.emit('onInit-member-initialize', this.#Member.memberName)
console.log(
chalk.bgBlue('created-member:'),
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 6e70e72e..2021e7ae 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -356,6 +356,9 @@ function mBotIcon(type){
function mCreateCollectionItem(collectionItem){
/* collection item container */
const { assistantType, filename, form, id, keywords, name, summary, title, type, } = collectionItem
+ const iconType = assistantType
+ ?? form
+ ?? type
const item = document.createElement('div')
item.id = `collection-item_${ id }`
item.name = `collection-item-${ type }`
@@ -365,7 +368,7 @@ function mCreateCollectionItem(collectionItem){
itemIcon.id = `collection-item-icon_${ id }`
itemIcon.name = `collection-item-icon-${ type }`
itemIcon.classList.add('collection-item-icon', `${ type }-collection-item-icon`)
- itemIcon.src = mBotIcon(assistantType)
+ itemIcon.src = mBotIcon(iconType)
item.appendChild(itemIcon)
/* name */
const itemName = document.createElement('span')
From 005b3d5b94b80dc813a575ef8d168c05dc711a5f Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 15 Oct 2024 17:44:13 -0400
Subject: [PATCH 18/56] 20241015 @Mookse - correct thumbs - consistent name
display for bot
---
inc/js/globals.mjs | 9 ++++
inc/js/mylife-agent-factory.mjs | 46 +++++++++----------
.../openai/functions/entrySummary.json | 9 ++++
views/assets/js/bots.mjs | 3 +-
views/assets/js/members.mjs | 6 +--
5 files changed, 45 insertions(+), 28 deletions(-)
diff --git a/inc/js/globals.mjs b/inc/js/globals.mjs
index b616cbb2..c3003faa 100644
--- a/inc/js/globals.mjs
+++ b/inc/js/globals.mjs
@@ -37,6 +37,14 @@ const mAiJsFunctions = {
description: 'complete concatenated raw text content of member input(s) for this `entry`',
type: 'string'
},
+ form: {
+ description: 'Form of `entry` content, determine context from internal instructions',
+ enum: [
+ 'diary',
+ 'journal',
+ ],
+ type: 'string'
+ },
keywords: {
description: 'Keywords most relevant to `entry`.',
items: {
@@ -69,6 +77,7 @@ const mAiJsFunctions = {
additionalProperties: false,
required: [
'content',
+ 'form',
'keywords',
'mood',
'relationships',
diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-agent-factory.mjs
index 46583fab..7b1f7031 100644
--- a/inc/js/mylife-agent-factory.mjs
+++ b/inc/js/mylife-agent-factory.mjs
@@ -333,12 +333,12 @@ class BotFactory extends EventEmitter{
}
/**
*
- * @param {object} assistantData - The assistant data.
+ * @param {object} botData - 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)
+ async createBot(botData={ type: mDefaultBotType }, vectorstoreId){
+ const bot = await mCreateBot(this.#llmServices, this, botData, vectorstoreId)
if(!bot)
throw new Error('bot creation failed')
return bot
@@ -650,21 +650,22 @@ class AgentFactory extends BotFactory {
return await this.dataservices.deleteItem(id)
}
async entry(entry){
+ const defaultForm = 'journal'
const defaultType = 'entry'
- const {
- assistantType='journaler',
+ const {
being=defaultType,
- form='journal',
+ form=defaultForm,
id=this.newGuid,
keywords=[],
- mbr_id=(!this.isMyLife ? this.mbr_id : undefined),
+ mbr_id=this.mbr_id,
summary,
- title=`New ${ defaultType }`,
+ title=`Untitled ${ defaultForm } ${ defaultType }`,
} = entry
- if(!mbr_id) // only triggered if not MyLife server
- throw new Error('mbr_id required for entry summary')
+ if(this.isMyLife)
+ throw new Error('System cannot store entries of its own')
let { name, } = entry
- name = name ?? `${ defaultType }_${ form }_${ title.substring(0,64) }_${ mbr_id }`
+ name = name
+ ?? `${ defaultType }_${ form }_${ title.substring(0,64) }_${ mbr_id }`
if(!summary?.length)
throw new Error('entry summary required')
/* assign default keywords */
@@ -675,7 +676,6 @@ class AgentFactory extends BotFactory {
const _entry = {
...entry,
...{
- assistantType,
being,
form,
id,
@@ -801,7 +801,6 @@ class AgentFactory extends BotFactory {
const defaultForm = 'memory'
const defaultType = 'story'
const {
- assistantType='biographer',
being=defaultType,
form=defaultForm,
id=this.newGuid,
@@ -809,7 +808,7 @@ class AgentFactory extends BotFactory {
mbr_id=(!this.isMyLife ? this.mbr_id : undefined),
phaseOfLife='unknown',
summary,
- title=`New ${ defaultType }`,
+ title=`Untitled ${ defaultForm } ${ defaultType }`,
} = story
if(!mbr_id) // only triggered if not MyLife server
throw new Error('mbr_id required for story summary')
@@ -824,7 +823,6 @@ class AgentFactory extends BotFactory {
const _story = { // add validated fields back into `story` object
...story,
...{
- assistantType,
being,
form,
id,
@@ -1187,17 +1185,17 @@ async function mConfigureSchemaPrototypes(){ // add required functionality as de
* @private
* @param {LLMServices} llm - OpenAI object
* @param {AgentFactory} factory - Agent Factory object
- * @param {object} assistantData - Bot object
+ * @param {object} botData - Bot object
* @param {string} avatarId - Avatar id
* @returns {string} - Bot assistant id in openAI
*/
-async function mCreateBotLLM(llm, assistantData){
- const llmResponse = await mAI_openai(llm, assistantData)
+async function mCreateBotLLM(llm, botData){
+ const llmResponse = await mAI_openai(llm, botData)
return llmResponse.id
}
/**
* Creates bot and returns associated `bot` object.
- * @todo - assistantData.name = botDbName should not be required, push logic to `llm-services`
+ * @todo - botData.name = botDbName should not be required, push logic to `llm-services`
* @module
* @async
* @private
@@ -1226,7 +1224,7 @@ async function mCreateBot(llm, factory, bot, vectorstoreId){
?? `bot_${ type }_${ avatarId }`
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
const id = factory.newGuid
- const assistantData = {
+ const botData = {
being: 'bot',
bot_name,
description,
@@ -1247,13 +1245,13 @@ async function mCreateBot(llm, factory, bot, vectorstoreId){
version,
}
/* create in LLM */
- const botId = await mCreateBotLLM(llm, assistantData) // create after as require model
+ const botId = await mCreateBotLLM(llm, botData) // create after as require model
if(!botId)
throw new Error('bot creation failed')
/* create in MyLife datastore */
- assistantData.bot_id = botId
- const assistant = await factory.dataservices.createBot(assistantData)
- console.log(chalk.green(`bot created::${ type }`), assistant.id, assistant.bot_id, assistant.bot_name, )
+ botData.bot_id = botId
+ const assistant = await factory.dataservices.createBot(botData)
+ console.log(chalk.green(`bot created::${ type }`), assistant.id, assistant.bot_id, assistant.bot_name, bot.thread_id )
return assistant
}
/**
diff --git a/inc/json-schemas/openai/functions/entrySummary.json b/inc/json-schemas/openai/functions/entrySummary.json
index 55c4c9e6..79648e9b 100644
--- a/inc/json-schemas/openai/functions/entrySummary.json
+++ b/inc/json-schemas/openai/functions/entrySummary.json
@@ -9,6 +9,14 @@
"description": "complete concatenated raw text content of member input(s) for this `entry`",
"type": "string"
},
+ "form": {
+ "description": "Form of `entry` content, determine context from internal instructions",
+ "enum": [
+ "diary",
+ "journal"
+ ],
+ "type": "string"
+ },
"keywords": {
"description": "Keywords most relevant to `entry`.",
"items": {
@@ -41,6 +49,7 @@
"additionalProperties": false,
"required": [
"content",
+ "form",
"keywords",
"mood",
"relationships",
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 2021e7ae..df8065fa 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -350,12 +350,13 @@ function mBotIcon(type){
}
/**
* Create a functional collection item HTML div for the specified collection type.
+ * @example - collectionItem: { assistantType, filename, form, id, keywords, name, summary, title, type, }
* @param {object} collectionItem - The collection item object, requires type.
* @returns {HTMLDivElement} - The collection item.
*/
function mCreateCollectionItem(collectionItem){
/* collection item container */
- const { assistantType, filename, form, id, keywords, name, summary, title, type, } = collectionItem
+ const { assistantType, filename, form, id, name, title, type, } = collectionItem
const iconType = assistantType
?? form
?? type
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 7c94a259..5f715e8e 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -122,8 +122,8 @@ function clearSystemChat(){
* @param {object} activeBot - The active bot.
* @returns {void}
*/
-function decorateActiveBot(activeBot=activeBot()){
- const { id, name, } = activeBot
+function decorateActiveBot(){
+ const { id, name, } = activeBot()
chatInputField.placeholder = `Type your message to ${ name }...`
// additional func? clear chat?
}
@@ -764,7 +764,7 @@ async function mSubmitChat(message) {
* @returns {void}
*/
function toggleMemberInput(display=true, hidden=false, connectingText='Connecting with '){
- const { id, name, } = activeBot
+ const { id, name, } = activeBot()
if(display){
hide(awaitButton)
awaitButton.classList.remove('slide-up')
From 5c8fa65fee95a7b488d6e140d2f2f088ce4801c0 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 15 Oct 2024 23:08:59 -0400
Subject: [PATCH 19/56] 20241015 @Mookse - prune getBot() - create thread on
bot creation
---
inc/js/functions.mjs | 2 +-
inc/js/mylife-agent-factory.mjs | 37 +++++++-----
inc/js/mylife-avatar.mjs | 56 ++++++++++++-------
inc/js/mylife-llm-services.mjs | 29 +++++-----
.../journaler-intelligence-1.1.json | 6 ++
5 files changed, 80 insertions(+), 50 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index a9d21c2f..1cc63f9e 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -50,7 +50,7 @@ async function bots(ctx){
case 'GET':
default:
if(bid?.length){ // specific bot
- ctx.body = await avatar.bot(ctx.params.bid)
+ ctx.body = await avatar.getBot(ctx.params.bid)
} else {
const {
activeBotId,
diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-agent-factory.mjs
index 7b1f7031..6f37e2bd 100644
--- a/inc/js/mylife-agent-factory.mjs
+++ b/inc/js/mylife-agent-factory.mjs
@@ -252,7 +252,7 @@ class BotFactory extends EventEmitter{
* @returns {object} - The bot.
*/
async bot(id, type=mDefaultBotType, mbr_id){
- if(this.isMyLife){ // MyLife server has no bots of its own, system agents perhaps (file, connector, etc) but no bots yet, so this is a micro-hydration
+ if(this.isMyLife){
if(!mbr_id)
throw new Error('mbr_id required for BotFactory hydration')
const botFactory = await new BotFactory(mbr_id)
@@ -1115,14 +1115,15 @@ class MyLifeFactory extends AgentFactory {
* Initializes openAI assistant and returns associated `assistant` object.
* @module
* @param {LLMServices} llmServices - OpenAI object
- * @param {object} bot - The assistand data object
+ * @param {object} botData - The bot data object
* @returns {object} - [OpenAI assistant object](https://platform.openai.com/docs/api-reference/assistants/object)
*/
-async function mAI_openai(llmServices, bot){
- const { bot_name, type, } = bot
- bot.name = bot_name
- ?? `My ${ type }`
- return await llmServices.createBot(bot)
+async function mAI_openai(llmServices, botData){
+ const { bot_name, type, } = botData
+ botData.name = bot_name
+ ?? `_member_${ type }`
+ const bot = await llmServices.createBot(botData)
+ return bot
}
function assignClassPropertyValues(propertyDefinition){
switch (true) {
@@ -1190,8 +1191,11 @@ async function mConfigureSchemaPrototypes(){ // add required functionality as de
* @returns {string} - Bot assistant id in openAI
*/
async function mCreateBotLLM(llm, botData){
- const llmResponse = await mAI_openai(llm, botData)
- return llmResponse.id
+ const { id, thread_id, } = await mAI_openai(llm, botData)
+ return {
+ id,
+ thread_id,
+ }
}
/**
* Creates bot and returns associated `bot` object.
@@ -1206,7 +1210,12 @@ async function mCreateBotLLM(llm, botData){
*/
async function mCreateBot(llm, factory, bot, vectorstoreId){
/* initial deconstructions */
- const { bot_name: botName, description: botDescription, name: botDbName, type, } = bot
+ const {
+ bot_name: botName,
+ description: botDescription,
+ name: botDbName,
+ type,
+ } = bot
const { avatarId, } = factory
/* validation */
if(!avatarId)
@@ -1245,13 +1254,14 @@ async function mCreateBot(llm, factory, bot, vectorstoreId){
version,
}
/* create in LLM */
- const botId = await mCreateBotLLM(llm, botData) // create after as require model
+ const { id: botId, thread_id, } = await mCreateBotLLM(llm, botData) // create after as require model
if(!botId)
throw new Error('bot creation failed')
/* create in MyLife datastore */
botData.bot_id = botId
+ botData.thread_id = thread_id
const assistant = await factory.dataservices.createBot(botData)
- console.log(chalk.green(`bot created::${ type }`), assistant.id, assistant.bot_id, assistant.bot_name, bot.thread_id )
+ console.log(chalk.green(`bot created::${ type }`), assistant.thread_id, assistant.id, assistant.bot_id, assistant.bot_name )
return assistant
}
/**
@@ -1353,7 +1363,6 @@ function mCreateBotInstructions(factory, bot){
}
})
/* assess and validate limit */
- console.log(chalk.blueBright('instructions length'), instructions.length, instructions)
return { instructions, version, }
}
function mExposedSchemas(factoryBlockedSchemas){
@@ -1431,7 +1440,6 @@ constructor(obj){
eval(\`this.\#\${_key}=obj[_key]\`)
} catch(err){
eval(\`this.\${_key}=obj[_key]\`)
- console.log(\`could not privatize \${_key}, public node created\`)
}
}
console.log('vm ${ _className } class constructed')
@@ -1803,7 +1811,6 @@ async function mUpdateBot(factory, llm, bot, options={}){
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
botData.tools = tools
botData.tool_resources = tool_resources
- console.log('mUpdateBot', botData.tools, botData.tool_resources, vectorstoreId)
}
if(updateModel)
botData.model = factory.globals.currentOpenAIBotModel
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 368f3a4a..178c3162 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -75,11 +75,13 @@ class Avatar extends EventEmitter {
* Get a bot's properties from Cosmos (or type in .bots).
* @public
* @async
- * @param {Guid} id - The bot id.
- * @returns {object} - The bot.
+ * @param {Guid} id - The bot id
+ * @returns {Promise} - The bot object from memory
*/
async bot(id){
- return await this.#factory.bot(id)
+ const bot = this.bots.find(bot=>bot.id===id)
+ ?? await this.#factory.bot(id)
+ return bot
}
/**
* Processes and executes incoming chat request.
@@ -94,6 +96,7 @@ class Avatar extends EventEmitter {
* @returns {object} - The response object { instruction, responses, success, }
*/
async chat(message, activeBotId, threadId, itemId, shadowId, conversation, processStartTime=Date.now()){
+ console.log('chat::activeBotId', activeBotId, threadId)
if(!message)
throw new Error('No message provided in context')
if(!activeBotId)
@@ -103,7 +106,7 @@ class Avatar extends EventEmitter {
if(botId!==activeBotId)
throw new Error(`Invalid bot id: ${ activeBotId }, active bot id: ${ botId }`)
conversation = conversation
- ?? this.getConversation(threadId ?? thread_id)
+ ?? this.getConversation(threadId ?? thread_id, null)
?? await this.createConversation('chat', threadId ?? thread_id, activeBotId)
if(!conversation)
throw new Error('No conversation found for thread id and could not be created.')
@@ -204,6 +207,7 @@ class Avatar extends EventEmitter {
* @returns {object} - The new bot.
*/
async createBot(bot){
+ /* validate request */
const { type, } = bot
if(!type)
throw new Error('Bot type required to create')
@@ -213,8 +217,11 @@ class Avatar extends EventEmitter {
.length
if(singletonBotExists)
throw new Error(`Bot type "${type}" already exists and bot-multiples disallowed.`)
+ /* execute request */
bot = await mBot(this.#factory, this, bot)
- return mPruneBot(bot)
+ /* respond request */
+ const response = mPruneBot(bot)
+ return response
}
/**
* Create a new conversation.
@@ -227,9 +234,13 @@ class Avatar extends EventEmitter {
* @returns {Conversation} - The conversation object.
*/
async createConversation(type='chat', threadId, botId=this.activeBotId, saveToConversations=true){
+ const mbr_id = this.mbr_id
const thread = await this.#llmServices.thread(threadId)
- const { mbr_id, type: botType, } = this.getBot(botId)
- const form = botType.split('-').pop()
+ const { type: botType, } = this.isMyLife
+ ? this.activeBot
+ : await this.bot(botId)
+ const form = botType?.split('-').pop()
+ ?? 'system'
const conversation = new (this.#factory.conversation)(
{
form,
@@ -374,10 +385,14 @@ class Avatar extends EventEmitter {
this.#experienceGenericVariables
)
}
+ /**
+ * Specified by id, returns the pruned bot from memory.
+ * @param {Guid} id - The id of the item to get
+ * @returns {object} - The pruned bot object
+ */
getBot(id){
- const bot = this.bots.find(bot=>bot.id===id)
+ const bot = mPruneBot(this.bots.find(bot=>bot.id===id))
return bot
- ?? this.activeBot
}
/**
* Gets Conversation object. If no thread id, creates new conversation.
@@ -477,7 +492,7 @@ class Avatar extends EventEmitter {
* @returns
*/
async migrateBot(botId){
- const bot = this.getBot(botId)
+ const bot = await this.bot(botId)
if(!bot)
throw new Error(`Bot not found with id: ${ botId }`)
const { id, } = bot
@@ -564,11 +579,11 @@ class Avatar extends EventEmitter {
* @param {Guid} botId - The bot id.
* @returns {object} - The retired bot object.
*/
- retireBot(botId){
+ async retireBot(botId){
/* reset active bot, if required */
if(this.activeBotId===botId)
this.activeBotId = null
- const bot = this.getBot(botId)
+ const bot = await this.bot(botId)
if(!bot)
throw new Error(`Bot not found with id: ${ botId }`)
const { id, } = bot
@@ -602,7 +617,7 @@ class Avatar extends EventEmitter {
throw new Error(`Conversation not found with bot id: ${ botId }`)
}
const { thread_id: cid, } = conversation
- const bot = this.getBot(botId)
+ const bot = await this.bot(botId)
const { id: _botId, thread_id: tid, } = bot
if(botId!=_botId)
throw new Error(`Bot id mismatch: ${ botId }!=${ bot_id }`)
@@ -2271,15 +2286,15 @@ async function mInit(factory, llmServices, avatar, bots, assetAgent){
}
))
avatar.activeBotId = avatar.avatar.id // initially set active bot to personal-avatar
+ if(factory.isMyLife) // as far as init goes for MyLife Avatar
+ return
/* conversations */
await Promise.all(
- bots.map(async bot=>{
+ bots.map(async bot=>{
const { id: botId, thread_id, type, } = bot
/* exempt certain types */
const excludedMemberTypes = ['library', 'ubi']
- if(factory.isMyLife && type!=='personal-avatar')
- return
- else if(excludedMemberTypes.includes(type))
+ if(excludedMemberTypes.includes(type))
return
if(!avatar.getConversation(thread_id, botId)){
const conversation = await avatar.createConversation('chat', thread_id, botId)
@@ -2290,9 +2305,8 @@ async function mInit(factory, llmServices, avatar, bots, assetAgent){
})
)
/* evolver */
- if(!factory.isMyLife)
- avatar.evolver = await (new EvolutionAssistant(avatar))
- .init()
+ avatar.evolver = await (new EvolutionAssistant(avatar))
+ .init()
/* lived-experiences */
avatar.experiencesLived = await factory.experiencesLived(false)
}
@@ -2312,7 +2326,7 @@ async function mMigrateChat(avatar, factory, llm, conversation){
if(!messages?.length)
return conversation
const { botId, } = conversation
- const bot = avatar.getBot(botId)
+ const bot = await avatar.bot(botId)
const botType = bot.type
let disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
itemCollectionTypes='item',
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index d251ee85..e9d7e915 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -34,13 +34,15 @@ class LLMServices {
/* public methods */
/**
* Creates openAI GPT API assistant.
- * @param {object} bot - The bot object
+ * @param {object} bot - The bot data
* @returns {Promise} - openai assistant object
*/
async createBot(bot){
- const assistantData = mValidateAssistantData(bot) // throws on improper format
- const assistant = await this.openai.beta.assistants.create(assistantData)
- return assistant
+ bot = mValidateAssistantData(bot) // throws on improper format
+ bot = await this.openai.beta.assistants.create(bot)
+ const thread = await mThread(this.openai)
+ bot.thread_id = thread.id
+ return bot
}
/**
* Creates a new OpenAI Vectorstore.
@@ -103,23 +105,24 @@ class LLMServices {
}
/**
* Given member input, get a response from the specified LLM service.
+ * @example - `run` object: { assistant_id, id, model, provider, required_action, status, usage }
* @todo - confirm that reason for **factory** is to run functions as responses from LLM; ergo in any case, find better way to stash/cache factory so it does not need to be passed through every such function
- * @param {string} threadId - Thread id.
+ * @param {string} thread_id - Thread id.
* @param {string} botId - GPT-Assistant/Bot id.
* @param {string} prompt - Member input.
* @param {AgentFactory} factory - Avatar Factory object to process request.
* @param {Avatar} avatar - Avatar object.
* @returns {Promise} - Array of openai `message` objects.
*/
- async getLLMResponse(threadId, botId, prompt, factory, avatar){
- if(!threadId?.length)
- threadId = ( await mThread(this.openai) ).id
- await mAssignRequestToThread(this.openai, threadId, prompt)
- const run = await mRunTrigger(this.openai, botId, threadId, factory, avatar)
- const { assistant_id, id: run_id, model, provider='openai', required_action, status, usage } = run
- const llmMessages = await this.messages(threadId)
- return llmMessages
+ async getLLMResponse(thread_id, botId, prompt, factory, avatar){
+ if(!thread_id?.length)
+ thread_id = ( await mThread(this.openai) ).id
+ await mAssignRequestToThread(this.openai, thread_id, prompt)
+ const run = await mRunTrigger(this.openai, botId, thread_id, factory, avatar)
+ const { id: run_id, } = run
+ const llmMessages = ( await this.messages(thread_id) )
.filter(message=>message.role=='assistant' && message.run_id==run_id)
+ return llmMessages
}
/**
* Given member request for help, get response from specified bot assistant.
diff --git a/inc/json-schemas/intelligences/journaler-intelligence-1.1.json b/inc/json-schemas/intelligences/journaler-intelligence-1.1.json
index 2f7489a0..fcb3b478 100644
--- a/inc/json-schemas/intelligences/journaler-intelligence-1.1.json
+++ b/inc/json-schemas/intelligences/journaler-intelligence-1.1.json
@@ -49,6 +49,12 @@
"description": "member full name",
"name": "<-mFN->",
"replacement": "memberName"
+ },
+ {
+ "default": "{unknown, find out}",
+ "description": "member birthdate",
+ "name": "<-db->",
+ "replacement": "dob"
}
]
},
From 81c825ba16b78f257054813342c8eab45af1016e Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Wed, 16 Oct 2024 02:16:51 -0400
Subject: [PATCH 20/56] 20241015 @Mookse - Migrate Conversation from Avatar to
Bot #406 - wip semi-stable
---
inc/js/functions.mjs | 16 ++--
inc/js/mylife-avatar.mjs | 179 ++++++++++++++++++++-------------------
inc/js/routes.mjs | 1 +
3 files changed, 97 insertions(+), 99 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index 1cc63f9e..6b7358d2 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -97,8 +97,7 @@ async function challenge(ctx){
ctx.body = !MemberSession.locked
}
/**
- * Chat with the member's avatar.
- * @todo - deprecate threadId in favor of thread_id
+ * Chat with the Member or System Avatar's intelligence.
* @param {Koa} ctx - Koa Context object
* @returns {object} - The response from the chat in `ctx.body`
* @property {object} instruction - Instructionset for the frontend to execute (optional)
@@ -108,14 +107,11 @@ async function chat(ctx){
const { botId, itemId, message, shadowId, } = ctx.request.body ?? {} /* body nodes sent by fe */
if(!message?.length)
ctx.throw(400, 'missing `message` content')
- const { avatar, MemberSession, } = ctx.state
- const { isMyLife, thread_id, } = MemberSession
- let conversation
- if(isMyLife && !thread_id?.length){
- conversation = await avatar.createConversation('system', undefined, botId, true) // pushes to this.#conversations in Avatar
- MemberSession.thread_id = conversation.thread_id
- }
- const response = await avatar.chat(message, botId, MemberSession.thread_id, itemId, shadowId, conversation)
+ const { avatar, dateNow=Date.now(), } = ctx.state
+ const { MemberSession, } = ctx.session
+ if(botId?.length && botId!==avatar.activeBotId)
+ throw new Error(`Bot ${ botId } not currently active; chat() requires active bot`)
+ const response = await avatar.chat(message, itemId, shadowId, dateNow, MemberSession)
ctx.body = response
}
async function collections(ctx){
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 178c3162..9dd30525 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -21,7 +21,6 @@ class Avatar extends EventEmitter {
#activeBotId // id of active bot in this.#bots; empty or undefined, then this
#assetAgent
#bots = []
- #conversations = []
#evolver
#experienceGenericVariables = {
age: undefined,
@@ -86,30 +85,20 @@ class Avatar extends EventEmitter {
/**
* Processes and executes incoming chat request.
* @public
- * @param {string} message - The chat message content.
- * @param {string} activeBotId - The active bot id.
- * @param {string} threadId - The openai thread id.
- * @param {Guid} itemId - The active collection-item id (optional).
- * @param {Guid} shadowId - The active Shadow Id (optional).
- * @param {Conversation} conversation - The conversation object.
- * @param {number} processStartTime - The start time of the process.
+ * @param {string} message - The chat message content
+ * @param {Guid} itemId - The active collection-item id (optional)
+ * @param {Guid} shadowId - The active Shadow Id (optional)
+ * @param {number} processStartTime - The start time of the process (optional)
+ * @param {MemberSession} session - ignored, but required for **overload** on Q instance
+ * @param {string} thread_id - The openai thread id (required for **overload** on Q instance)
* @returns {object} - The response object { instruction, responses, success, }
*/
- async chat(message, activeBotId, threadId, itemId, shadowId, conversation, processStartTime=Date.now()){
- console.log('chat::activeBotId', activeBotId, threadId)
+ async chat(message, itemId, shadowId, processStartTime=Date.now(), session=null, thread_id){
if(!message)
throw new Error('No message provided in context')
- if(!activeBotId)
- throw new Error('Parameter `activeBotId` required.')
- const { activeBot, factory } = this
- const { bot_id, id: botId, thread_id, } = activeBot
- if(botId!==activeBotId)
- throw new Error(`Invalid bot id: ${ activeBotId }, active bot id: ${ botId }`)
- conversation = conversation
- ?? this.getConversation(threadId ?? thread_id, null)
- ?? await this.createConversation('chat', threadId ?? thread_id, activeBotId)
+ const { bot_id, conversation=this.getConversation(thread_id), id: botId, } = this.activeBot
if(!conversation)
- throw new Error('No conversation found for thread id and could not be created.')
+ throw new Error('No conversation found for bot intelligence and could not be created.')
conversation.bot_id = botId
conversation.llm_id = bot_id
let _message = message,
@@ -119,7 +108,7 @@ class Avatar extends EventEmitter {
else {
if(itemId){
// @todo - check if item exists in memory, fewer pings and inclusions overall
- let { summary, } = await factory.item(itemId)
+ let { summary, } = await this.#factory.item(itemId)
if(summary?.length){
summary = `possible **update-summary-request**: itemId=${ itemId }\n`
+ `**member-update-request**:\n`
@@ -128,7 +117,7 @@ class Avatar extends EventEmitter {
+ summary
}
}
- messages = await mCallLLM(this.#llmServices, conversation, _message, factory, this)
+ messages = await mCallLLM(this.#llmServices, conversation, _message, this.#factory, this)
}
conversation.addMessage({
content: message,
@@ -141,21 +130,24 @@ class Avatar extends EventEmitter {
else
console.log('chat::BYPASS-SAVE', conversation.message?.content?.substring(0,64))
/* frontend mutations */
- let responses
+ const responses = []
const { activeBot: bot } = this
- responses = conversation.messages
+ conversation.messages
.filter(_message=>{
return messages.find(__message=>__message.id===_message.id)
&& _message.type==='chat'
&& _message.role!=='user'
})
.map(_message=>mPruneMessage(bot, _message, 'chat', processStartTime))
- if(!responses?.length){ // last failsafe
- responses = [this.backupResponse
+ .reverse()
+ .forEach(_message=>responses.push(_message))
+ if(!responses?.length){
+ const failsafeResponse = this.backupResponse
?? {
- message: 'I am sorry, the entire chat line went dark for a moment, please try again.',
+ message: 'I am sorry, connection with my intelligence faltered, hopefully temporarily, ask to try again.',
type: 'system',
- }]
+ }
+ responses.push(failsafeResponse)
}
const response = {
instruction: this.frontendInstruction,
@@ -227,18 +219,19 @@ class Avatar extends EventEmitter {
* Create a new conversation.
* @async
* @public
- * @param {string} type - Type of conversation: chat, experience, dialog, inter-system, etc.; defaults to `chat`.
- * @param {string} threadId - The openai thread id.
- * @param {string} botId - The bot id.
- * @param {boolean} saveToConversations - Whether to save the conversation to local memory; certain system and memory actions will be saved in their own threads.
- * @returns {Conversation} - The conversation object.
+ * @param {string} type - Type of conversation: chat, experience, dialog, inter-system, etc.; defaults to `chat`
+ * @param {string} thread_id - The openai thread id
+ * @param {string} botId - The bot id
+ * @returns {Conversation} - The conversation object
*/
- async createConversation(type='chat', threadId, botId=this.activeBotId, saveToConversations=true){
+ async createConversation(type='chat', thread_id, botId=this.activeBotId){
const mbr_id = this.mbr_id
- const thread = await this.#llmServices.thread(threadId)
- const { type: botType, } = this.isMyLife
- ? this.activeBot
+ const thread = await this.#llmServices.thread(thread_id)
+ const { conversation: previousConversation, type: botType, } = this.isMyLife
+ ? this.avatar
: await this.bot(botId)
+ if(!!previousConversation)
+ throw new Error(`Conversation already exists for bot/thread: ${ botId }/${ thread.id }`)
const form = botType?.split('-').pop()
?? 'system'
const conversation = new (this.#factory.conversation)(
@@ -251,8 +244,7 @@ class Avatar extends EventEmitter {
thread,
botId
)
- if(saveToConversations)
- this.#conversations.push(conversation)
+ console.log('conversation created', conversation.inspect(true))
return conversation
}
/**
@@ -396,13 +388,13 @@ class Avatar extends EventEmitter {
}
/**
* Gets Conversation object. If no thread id, creates new conversation.
- * @param {string} threadId - openai thread id (optional)
+ * @param {string} thread_id - openai thread id (optional)
* @param {Guid} botId - The bot id (optional)
* @returns {Conversation} - The conversation object.
*/
- getConversation(threadId, botId){
- const conversation = this.#conversations
- .filter(c=>(threadId?.length && c.thread_id===threadId) || (botId?.length && c.botId===botId))
+ getConversation(thread_id, botId){
+ const conversation = this.conversations
+ .filter(c=>(thread_id?.length && c.thread_id===thread_id) || (botId?.length && c.botId===botId))
?.[0]
return conversation
}
@@ -774,21 +766,15 @@ class Avatar extends EventEmitter {
const teams = this.#factory.teams()
return teams
}
- async thread_id(){
- if(!this.conversations.length){
- await this.createConversation()
- console.log('Avatar::thread_id::created new conversation', this.conversations[0].thread_id)
- }
- return this.conversations[0].threadId
- }
/**
- * Update a specific bot.
+ * Update a specific bot. **Note**: mBot() updates `this.bots`
* @async
* @param {object} bot - Bot data to set.
* @returns {object} - The updated bot.
*/
async updateBot(bot){
- return await mBot(this.#factory, this, bot) // **note**: mBot() updates `avatar.bots`
+ const updatedBot = await mBot(this.#factory, this, bot) // **note**: mBot() updates `avatar.bots`
+ return updatedBot
}
/**
* Update instructions for bot-assistant based on type. Default updates all LLM pertinent properties.
@@ -1002,7 +988,10 @@ class Avatar extends EventEmitter {
* @returns {array} - The conversations.
*/
get conversations(){
- return this.#conversations
+ const conversations = this.bots
+ .map(bot=>bot.conversation)
+ .filter(Boolean)
+ return conversations
}
/**
* Get the datacore.
@@ -1324,6 +1313,7 @@ class Avatar extends EventEmitter {
}
}
class Q extends Avatar {
+ #conversations = []
#factory // same reference as Avatar, but wish to keep private from public interface; don't touch my factory, man!
#hostedMembers = [] // MyLife-hosted members
#llmServices // ref _could_ differ from Avatar, but for now, same
@@ -1342,7 +1332,7 @@ class Q extends Avatar {
}
/* overloaded methods */
/**
- * Get a bot's properties from Cosmos (or type in .bots).
+ * OVERLOADED: Get a bot's properties from Cosmos (or type in .bots).
* @public
* @async
* @param {string} mbr_id - The bot id
@@ -1353,33 +1343,33 @@ class Q extends Avatar {
return bot
}
/**
- * Processes and executes incoming chat request.
+ * OVERLOADED: Processes and executes incoming chat request.
* @public
- * @param {string} message - The chat message content.
- * @param {string} activeBotId - The active bot id.
- * @param {string} threadId - The openai thread id.
- * @param {Guid} itemId - The active collection-item id (optional).
- * @param {Guid} shadowId - The active Shadow Id (optional).
- * @param {Conversation} conversation - The conversation object.
- * @param {number} processStartTime - The start time of the process.
- * @returns {object} - The response(s) to the chat request.
+ * @param {string} message - The chat message content
+ * @param {Guid} itemId - The active collection-item id (optional)
+ * @param {Guid} shadowId - The active Shadow Id (optional)
+ * @param {number} processStartTime - The start time of the process
+ * @param {MemberSession} session - The MyLife MemberSession instance
+ * @returns {object} - The response(s) to the chat request
*/
- async chat(message, activeBotId, threadId, itemId, shadowId, conversation, processStartTime=Date.now()){
- conversation = conversation
- ?? this.getConversation(threadId)
- if(!conversation)
- throw new Error('Conversation cannot be found')
+ async chat(message, itemId, shadowId, processStartTime=Date.now(), session){
+ let { thread_id, } = session
+ if(!thread_id?.length){
+ const conversation = await this.createConversation('system')
+ thread_id = conversation.thread_id
+ this.#conversations.push(conversation)
+ }
this.activeBot.bot_id = mBot_idOverride
?? this.activeBot.bot_id
+ session.thread_id = thread_id // @stub - store elsewhere
if(this.isValidating) // trigger confirmation until session (or vld) ends
message = `CONFIRM REGISTRATION PHASE: registrationId=${ this.registrationId }\n${ message }`
if(this.isCreatingAccount)
message = `CREATE ACCOUNT PHASE: ${ message }`
- activeBotId = this.activeBotId
- return super.chat(message, activeBotId, threadId, itemId, shadowId, conversation, processStartTime)
+ return super.chat(message, itemId, shadowId, processStartTime, null, thread_id)
}
/**
- * Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM. In this overload, we invoke a micro-avatar for the member to handle the request on their behalf, with charge-backs going to MyLife as the sharing and api is a service.
+ * OVERLOADED: Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM. In this overload, we invoke a micro-avatar for the member to handle the request on their behalf, with charge-backs going to MyLife as the sharing and api is a service.
* @public
* @param {string} mbr_id - The member id
* @param {Guid} iid - The item id
@@ -1390,6 +1380,11 @@ class Q extends Avatar {
const updatedSummary = await botFactory.obscure(iid)
return updatedSummary
}
+ /**
+ * OVERLOADED: Refuses to upload to MyLife.
+ * @public
+ * @throws {Error} - MyLife avatar cannot upload files.
+ */
upload(){
throw new Error('MyLife avatar cannot upload files.')
}
@@ -1467,6 +1462,14 @@ class Q extends Avatar {
get being(){
return 'MyLife'
}
+ /**
+ * Get conversations. If getting a specific conversation, use .conversation(id).
+ * @getter
+ * @returns {array} - The conversations.
+ */
+ get conversations(){
+ return this.#conversations
+ }
}
/* module functions */
/**
@@ -1519,7 +1522,8 @@ async function mBot(factory, avatar, bot){
if(!botType?.length)
throw new Error('Bot type required to create.')
bot.mbr_id = mbr_id /* constant */
- bot.object_id = objectId ?? avatarId /* all your bots belong to me */
+ bot.object_id = objectId
+ ?? avatarId /* all your bots belong to me */
bot.id = botId // **note**: _this_ is a Cosmos id, not an openAI id
let originBot = avatar.bots.find(oBot=>oBot.id===botId)
if(originBot){ /* update bot */
@@ -1535,10 +1539,10 @@ async function mBot(factory, avatar, bot){
if(!thread_id?.length && !avatar.isMyLife){
const excludeTypes = ['collection', 'library', 'custom'] // @stub - custom mechanic?
if(!excludeTypes.includes(type)){
- const conversation = avatar.getConversation(null, botId)
+ const conversation = avatar.conversation(null, botId)
?? await avatar.createConversation('chat', null, botId)
updatedBot.thread_id = conversation.thread_id // triggers `factory.updateBot()`
- console.log('Avatar::mBot::conversation created given NO thread_id', updatedBot.thread_id, avatar.getConversation(updatedBot.thread_id))
+ console.log('Avatar::mBot::conversation created given NO thread_id', updatedBot.thread_id, conversation.inspect(true))
}
}
let updatedOriginBot
@@ -1595,13 +1599,13 @@ async function mCallLLM(llmServices, conversation, prompt, factory, avatar){
* Cancels openAI run.
* @module
* @param {LLMServices} llmServices - OpenAI object
- * @param {string} threadId - Thread id
+ * @param {string} thread_id - Thread id
* @param {string} runId - Run id
* @returns {object} - [OpenAI run object](https://platform.openai.com/docs/api-reference/runs/object)
*/
-async function mCancelRun(llmServices, threadId, runId,){
+async function mCancelRun(llmServices, thread_id, runId,){
return await llmServices.beta.threads.runs.cancel(
- threadId,
+ thread_id,
runId
)
}
@@ -2237,12 +2241,13 @@ function mHelpIncludePreamble(type, isMyLife){
}
}
/**
- * Initializes the Avatar instance with stored data.
- * @param {MyLifeFactory|AgentFactory} factory - Member Avatar (true) or Q (false).
- * @param {LLMServices} llmServices - OpenAI object.
- * @param {Q|Avatar} avatar - The avatar Instance (`this`).
- * @param {array} bots - The array of bot objects from private class `this.#bots`.
- * @returns {Promise} - Return indicates successfully mutated avatar.
+ * Initializes the Avatar instance with stored data
+ * @param {MyLifeFactory|AgentFactory} factory - Member Avatar or Q
+ * @param {LLMServices} llmServices - OpenAI object
+ * @param {Q|Avatar} avatar - The avatar Instance (`this`)
+ * @param {array} bots - The array of bot objects from private class `this.#bots`
+ * @param {AssetAgent} assetAgent - AssetAgent instance
+ * @returns {Promise} - Return indicates successfully mutated avatar
*/
async function mInit(factory, llmServices, avatar, bots, assetAgent){
/* get avatar data from cosmos */
@@ -2296,12 +2301,8 @@ async function mInit(factory, llmServices, avatar, bots, assetAgent){
const excludedMemberTypes = ['library', 'ubi']
if(excludedMemberTypes.includes(type))
return
- if(!avatar.getConversation(thread_id, botId)){
- const conversation = await avatar.createConversation('chat', thread_id, botId)
- avatar.updateBot(bot)
- if(!avatar.getConversation(thread_id)) // may happen in cases of MyLife? others?
- avatar.conversations.push(conversation)
- }
+ const conversation = await avatar.createConversation('chat', thread_id, botId)
+ bot.conversation = conversation
})
)
/* evolver */
diff --git a/inc/js/routes.mjs b/inc/js/routes.mjs
index d18b02e2..e1ade44e 100644
--- a/inc/js/routes.mjs
+++ b/inc/js/routes.mjs
@@ -155,6 +155,7 @@ function connectRoutes(_Menu){
*/
async function memberValidation(ctx, next){
const { locked=true, } = ctx.state
+ ctx.state.dateNow = Date.now()
if(locked)
ctx.redirect(`/?type=select`) // Redirect to /members if not authorized
else
From 1659e76f6dcd2efffc7e93b01230785fb095c999 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 22 Oct 2024 02:57:13 -0400
Subject: [PATCH 21/56] 20241022 @Mookse - fix updateSummary
---
inc/js/mylife-avatar.mjs | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 9dd30525..00fde409 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -101,23 +101,22 @@ class Avatar extends EventEmitter {
throw new Error('No conversation found for bot intelligence and could not be created.')
conversation.bot_id = botId
conversation.llm_id = bot_id
- let _message = message,
- messages = []
+ let messages = []
if(shadowId)
- messages = await this.shadow(shadowId, itemId, _message)
+ messages.push(...await this.shadow(shadowId, itemId, message))
else {
+ let alteredMessage = message
if(itemId){
// @todo - check if item exists in memory, fewer pings and inclusions overall
let { summary, } = await this.#factory.item(itemId)
- if(summary?.length){
- summary = `possible **update-summary-request**: itemId=${ itemId }\n`
- + `**member-update-request**:\n`
- + message
- + `\n**current-summary-in-database**:\n`
- + summary
- }
+ if(summary?.length)
+ alteredMessage = `possible **update-summary-request**: itemId=${ itemId }\n`
+ + `**member-update-request**:\n`
+ + message
+ + `\n**current-summary-in-database**:\n`
+ + summary
}
- messages = await mCallLLM(this.#llmServices, conversation, _message, this.#factory, this)
+ messages.push(...await mCallLLM(this.#llmServices, conversation, alteredMessage, this.#factory, this))
}
conversation.addMessage({
content: message,
From 54200a61b7d6580295e0211b8ff4047d625ab347 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 22 Oct 2024 02:58:23 -0400
Subject: [PATCH 22/56] 20241022 @Mookse - fix updateSummary error
---
inc/js/mylife-avatar.mjs | 21 ++++++++++-----------
1 file changed, 10 insertions(+), 11 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 9dd30525..00fde409 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -101,23 +101,22 @@ class Avatar extends EventEmitter {
throw new Error('No conversation found for bot intelligence and could not be created.')
conversation.bot_id = botId
conversation.llm_id = bot_id
- let _message = message,
- messages = []
+ let messages = []
if(shadowId)
- messages = await this.shadow(shadowId, itemId, _message)
+ messages.push(...await this.shadow(shadowId, itemId, message))
else {
+ let alteredMessage = message
if(itemId){
// @todo - check if item exists in memory, fewer pings and inclusions overall
let { summary, } = await this.#factory.item(itemId)
- if(summary?.length){
- summary = `possible **update-summary-request**: itemId=${ itemId }\n`
- + `**member-update-request**:\n`
- + message
- + `\n**current-summary-in-database**:\n`
- + summary
- }
+ if(summary?.length)
+ alteredMessage = `possible **update-summary-request**: itemId=${ itemId }\n`
+ + `**member-update-request**:\n`
+ + message
+ + `\n**current-summary-in-database**:\n`
+ + summary
}
- messages = await mCallLLM(this.#llmServices, conversation, _message, this.#factory, this)
+ messages.push(...await mCallLLM(this.#llmServices, conversation, alteredMessage, this.#factory, this))
}
conversation.addMessage({
content: message,
From 5cc9027fd8ae479cb61df63004801bfc4d591ab7 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 22 Oct 2024 03:18:23 -0400
Subject: [PATCH 23/56] 20241022 @Mookse - wip broken
---
README.md | 2 +-
.../{asset-assistant.mjs => asset-agent.mjs} | 24 +-
inc/js/agents/system/bot-agent.mjs | 854 ++++++++++++++++++
inc/js/agents/system/bot-assistant.mjs | 2 -
...tion-assistant.mjs => evolution-agent.mjs} | 93 +-
inc/js/api-functions.mjs | 1 +
inc/js/core.mjs | 7 +-
inc/js/functions.mjs | 10 +-
inc/js/globals.mjs | 45 +-
inc/js/mylife-avatar.mjs | 606 ++++---------
...ata-service.js => mylife-dataservices.mjs} | 50 +-
...e-agent-factory.mjs => mylife-factory.mjs} | 508 +----------
server.js | 2 +-
views/README.md | 2 +-
14 files changed, 1193 insertions(+), 1013 deletions(-)
rename inc/js/agents/system/{asset-assistant.mjs => asset-agent.mjs} (89%)
create mode 100644 inc/js/agents/system/bot-agent.mjs
delete mode 100644 inc/js/agents/system/bot-assistant.mjs
rename inc/js/agents/system/{evolution-assistant.mjs => evolution-agent.mjs} (80%)
rename inc/js/{mylife-data-service.js => mylife-dataservices.mjs} (95%)
rename inc/js/{mylife-agent-factory.mjs => mylife-factory.mjs} (74%)
diff --git a/README.md b/README.md
index 92551a52..95304d2d 100644
--- a/README.md
+++ b/README.md
@@ -164,7 +164,7 @@ MyLife itself is an open-source project and, aside from LLM technologies at the
3. **Bot Functionality and Intelligence Management**
- The application features a sophisticated bot system, capable of creating and managing different types of bots like personal assistants, biographers, health bots, etc.
- - OpenAI's GPT-3 model is integrated for generating responses and interacting with users through bots, as observed in the `class-avatar-functions.mjs` and `mylife-agent-factory.mjs` files.
+ - OpenAI's GPT-3 model is integrated for generating responses and interacting with users through bots, as observed in the `mylife-avatar.mjs` and `mylife-factory.mjs` files.
4. **Session Management**
- Managed through the `MylifeMemberSession` class, handling user sessions, consents, and alerts.
diff --git a/inc/js/agents/system/asset-assistant.mjs b/inc/js/agents/system/asset-agent.mjs
similarity index 89%
rename from inc/js/agents/system/asset-assistant.mjs
rename to inc/js/agents/system/asset-agent.mjs
index bfdde65f..ec15956f 100644
--- a/inc/js/agents/system/asset-assistant.mjs
+++ b/inc/js/agents/system/asset-agent.mjs
@@ -1,29 +1,31 @@
// imports
import fs from 'fs'
import mime from 'mime-types'
-import FormData from 'form-data'
-import axios from 'axios'
// module constants
-const { MYLIFE_EMBEDDING_SERVER_BEARER_TOKEN, MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT, MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT_ADMIN, MYLIFE_SERVER_MBR_ID: mylifeMbrId, } = process.env
-const bearerToken = MYLIFE_EMBEDDING_SERVER_BEARER_TOKEN
+const {
+ MYLIFE_EMBEDDING_SERVER_BEARER_TOKEN: bearerToken,
+ MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT,
+ MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT_ADMIN,
+ MYLIFE_SERVER_MBR_ID: mylifeMbrId,
+} = process.env
const fileSizeLimit = parseInt(MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT) || 1048576
const fileSizeLimitAdmin = parseInt(MYLIFE_EMBEDDING_SERVER_FILESIZE_LIMIT_ADMIN) || 10485760
-class oAIAssetAssistant {
+class AssetAgent {
#globals
#llm
#mbr_id
#response
#vectorstoreId
#vectorstoreFileList=[] // openai vectorstore versions
- constructor(mbr_id, globals, llm){
- this.#mbr_id = mbr_id
- this.#globals = globals
+ constructor(factory, llm){
+ this.#mbr_id = factory.mbr_id
+ this.#globals = factory.globals
this.#llm = llm
}
/**
* 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).
- * @returns {Promise} - The initialized asset assistant instance.
+ * @returns {Promise} - The initialized asset assistant instance.
*/
async init(vectorstoreId){
if(!vectorstoreId?.length)
@@ -143,5 +145,5 @@ class oAIAssetAssistant {
throw new Error(`Unsupported media type: ${ mimetype }. File type not allowed.`)
}
}
-// exports
-export default oAIAssetAssistant
\ No newline at end of file
+/* exports */
+export default AssetAgent
\ No newline at end of file
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
new file mode 100644
index 00000000..a5634196
--- /dev/null
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -0,0 +1,854 @@
+/* module constants */
+const mBot_idOverride = process.env.OPENAI_MAHT_GPT_OVERRIDE
+const mDefaultBotTypeArray = ['personal-avatar', 'avatar']
+const mDefaultBotType = mDefaultBotTypeArray[0]
+const mDefaultGreetings = []
+const mDefaultTeam = 'memory'
+const mRequiredBotTypes = ['personal-avatar']
+const mTeams = [
+ {
+ active: true,
+ allowCustom: true,
+ allowedTypes: ['diary', 'journaler', 'personal-biographer',],
+ defaultActiveType: 'personal-biographer',
+ defaultTypes: ['personal-biographer',],
+ description: 'The Memory Team is dedicated to help you document your life stories, experiences, thoughts, and feelings.',
+ id: 'a261651e-51b3-44ec-a081-a8283b70369d',
+ name: 'memory',
+ title: 'Memory',
+ },
+]
+/* classes */
+/**
+ * @class - Bot
+ * @private
+ * @todo - are private vars for factory and llm necessary, or passable?
+ */
+class Bot {
+ #conversation
+ #factory
+ #llm
+ constructor(botData, factory, llm){
+ this.#factory = factory
+ this.#llm = llm
+ botData = this.globals.sanitize(botData)
+ Object.assign(this, botData)
+ if(!this.id)
+ throw new Error('Bot database id required')
+ }
+ /* public functions */
+ async chat(){
+ console.log('Bot::chat', this.#conversation)
+ // what should be returned? Responses? Conversation object?
+ return this.#conversation
+ }
+ getBot(){
+ return this
+ }
+ /**
+ * Retrieves a greeting message from the active bot.
+ * @param {Boolean} dynamic - Whether to use dynamic greetings (`true`) or static (`false`)
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
+ * @returns {Array} - The greeting message(s) string array in order of display
+ */
+ async getGreeting(dynamic=false, llm, factory){
+ const greetings = await mBotGreeting(dynamic, this, llm, factory)
+ return greetings
+ }
+ /**
+ * Updates a Bot instance's data.
+ * @param {object} botData - The bot data to update
+ * @param {object} botOptions - Options for updating
+ * @returns
+ */
+ async update(botData, botOptions){
+ /* validate request */
+ this.globals.sanitize(botData)
+ const Bot = await mBotUpdate(this.#factory, this.#llm, botData, botOptions)
+ return this.globals.sanitize(Bot)
+ }
+ async save(){
+
+ }
+ /* getters/setters */
+ /**
+ * Gets the frontend bot object. If full instance is required, use `getBot()`.
+ * @getter
+ */
+ get bot() {
+ const { bot_name, description, id, purpose, type, version } = this
+ const bot = {
+ bot_name,
+ description,
+ id,
+ purpose,
+ type,
+ version,
+ }
+ return bot
+ }
+ get globals(){
+ return this.#factory.globals
+ }
+ get isAvatar(){
+ return mDefaultBotTypeArray.includes(this.type)
+ }
+ get isMyLife(){
+ return this.#factory.isMyLife
+ }
+ get micro(){
+ const {
+ bot_name,
+ id,
+ name,
+ provider,
+ type,
+ version,
+ } = this
+ const microBot = {
+ bot_name,
+ id,
+ name,
+ provider,
+ type,
+ version,
+ }
+ return microBot
+ }
+}
+/**
+ * @class - Team
+ * @private
+ */
+/**
+ * @class - BotAgent
+ * @public
+ * @description - BotAgent is an interface to assist in creating, managing and maintaining a Member Avatar's bots.
+ */
+class BotAgent {
+ #activeBot
+ #activeTeam = mDefaultTeam
+ #avatarId
+ #bots
+ #factory
+ #llm
+ #vectorstoreId
+ constructor(factory, llm){
+ this.#factory = factory
+ this.#llm = llm
+ }
+ /**
+ * Initializes the BotAgent instance.
+ * @async
+ * @param {Guid} avatarId - The Avatar id
+ * @param {string} vectorstoreId - The Vectorstore id
+ * @returns {Promise} - The BotAgent instance
+ */
+ async init(avatarId, vectorstoreId){
+ /* validate request */
+ if(!avatarId?.length)
+ throw new Error('AvatarId required')
+ this.#avatarId = avatarId
+ this.#bots = []
+ this.#vectorstoreId = vectorstoreId
+ /* execute request */
+ await mInit(this, this.#bots, this.#factory, this.#llm)
+ return this
+ }
+ /* public functions */
+ /**
+ * Retrieves a bot instance by id.
+ * @param {Guid} botId - The Bot id
+ * @returns {Promise} - The Bot instance
+ */
+ bot(botId){
+ const Bot = this.#bots
+ .find(bot=>bot.id===botId)
+ return Bot
+ }
+ /**
+ * Creates a bot instance.
+ * @param {Object} botData - The bot data object
+ * @returns {Bot} - The created Bot instance
+ */
+ async botCreate(botData){
+ const Bot = await mBotCreate(this.#avatarId, this.#vectorstoreId, botData, this.#factory)
+ this.#bots.push(Bot)
+ this.setActiveBot(Bot.id)
+ return Bot
+ }
+ /**
+ * Deletes a bot instance.
+ * @async
+ * @param {Guid} botId - The Bot id
+ * @returns {Promise}
+ */
+ async botDelete(botId){
+ const Bot = this.#bots.find(bot=>bot.id===botId)
+ if(!Bot)
+ throw new Error(`Bot not found with id: ${ botId }`)
+ await mBotDelete(Bot, this.#bots, this.#llm, this.#factory)
+ }
+ async chat(){
+ return this.activeBot.chat()
+ }
+ /**
+ * Initializes a conversation, currently only requested by System Avatar, but theoretically could be requested by any externally-facing Member Avatar as well. **note**: not in Q because it does not have a #botAgent yet.
+ * @param {String} type - The type of conversation, defaults to `chat`
+ * @param {String} form - The form of conversation, defaults to `system-avatar`
+ * @returns {Promise} - The conversation object
+ */
+ async conversationStart(type='chat', form='system-avatar'){
+ const { bot_id, } = this.avatar
+ const Conversation = await mConversationStart(type, form, null, bot_id, this.#llm, this.#factory)
+ return Conversation
+ }
+ /**
+ * Retrieves bots by instance.
+ * @returns {Bot[]} - The array of bots
+ */
+ getBots(){
+ return this.#bots
+ }
+ /**
+ * Get a static or dynamic greeting from active bot.
+ * @param {boolean} dynamic - Whether to use LLM for greeting
+ * @returns {Messages[]} - The greeting message(s) string array in order of display
+ */
+ async greeting(dynamic=false){
+ const greetings = await this.activeBot.getGreeting(dynamic, this.#llm, this.#factory)
+ return greetings
+ }
+ /**
+ * Sets the active bot for the BotAgent.
+ * @param {Guid} botId - The Bot id
+ * @returns {void}
+ */
+ setActiveBot(botId){
+ const Bot = this.#bots.find(bot=>bot.id===botId)
+ if(Bot)
+ this.#activeBot = Bot
+ }
+ /**
+ * Sets the active team for the BotAgent if `teamId` valid.
+ * @param {Guid} teamId - The Team id
+ * @returns {void}
+ */
+ setActiveTeam(teamId){
+ this.#activeTeam = this.teams.find(team=>team.id===teamId)
+ ?? this.#activeTeam
+ }
+ /**
+ * Updates a bot instance.
+ * @param {object} botData - The bot data to update
+ * @param {object} botOptions - Options for updating the bot
+ * @returns {Promise} - The updated Bot instance
+ */
+ async updateBot(botData, botOptions={}){
+ const { id, } = botData
+ if(!this.globals.isValidGuid(id))
+ throw new Error('`id` parameter required')
+ const Bot = this.#bots.find(bot=>bot.id===id)
+ if(!!Bot)
+ throw new Error(`Bot not found with id: ${ id }`)
+ Bot.update(botData, botOptions)
+ return Bot
+ }
+ /* getters/setters */
+ /**
+ * Gets the active Bot instance.
+ * @getter
+ * @returns {Bot} - The active Bot instance
+ */
+ get activeBot(){
+ return this.#activeBot
+ }
+ /**
+ * Gets the active team.
+ * @getter
+ * @returns {object} - The active team object
+ */
+ get activeTeam(){
+ return this.#activeTeam
+ }
+ /**
+ * Gets the active bot id for the BotAgent.
+ * @getter
+ * @returns {Guid} - The active bot id
+ */
+ get activeBotId(){
+ return this.#activeBot.id
+ }
+ /**
+ * Gets the primary avatar for Member.
+ * @getter
+ * @returns {Bot} - The primary avatar Bot instance
+ */
+ get avatar(){
+ const bot = this.#bots.find(Bot=>Bot.isAvatar===true)
+ return bot
+ }
+ /**
+ * Gets the Avatar id for whom this BotAgent is conscripted.
+ * @getter
+ * @returns {String} - The Avatar id
+ */
+ get avatarId(){
+ return this.#avatarId
+ }
+ /**
+ * Gets the array of bots employed by this BotAgent. For full instances, call `getBots()`.
+ * @getter
+ * @returns {Bot[]} - The array of bots
+ */
+ get bots(){
+ return this.#bots
+ }
+ /**
+ * Returns system globals object.
+ * @getter
+ * @returns {object} - System globals object
+ */
+ get globals(){
+ return this.#factory.globals
+ }
+ /**
+ * Returns whether BotAgent is employed by MyLife (`true`) or Member (`false`).
+ * @getter
+ * @returns {Boolean} - Whether BotAgent is employed by MyLife, defaults to `false`
+ */
+ get isMyLife(){
+ return this.#factory.isMyLife
+ }
+ /**
+ * Retrieves list of available MyLife Teams.
+ * @getter
+ * @returns {object[]} - The array of MyLife Teams
+ */
+ get teams(){
+ return mTeams
+ }
+ /**
+ * Returns the Vectorstore id for the BotAgent.
+ * @getter
+ * @returns {String} - The Vectorstore id
+ */
+ get vectorstoreId(){
+ return this.#vectorstoreId
+ }
+}
+/* modular functions */
+/**
+ * Initializes openAI assistant and returns associated `assistant` object.
+ * @module
+ * @param {object} botData - The bot data object
+ * @param {LLMServices} llmServices - OpenAI object
+ * @returns {object} - [OpenAI assistant object](https://platform.openai.com/docs/api-reference/assistants/object)
+ */
+async function mAI_openai(botData, llmServices){
+ const { bot_name, type, } = botData
+ botData.name = bot_name
+ ?? `_member_${ type }`
+ const bot = await llmServices.createBot(botData)
+ return bot
+}
+/**
+ * Validates and cleans bot object then updates or creates bot (defaults to new personal-avatar) in Cosmos and returns successful `bot` object, complete with conversation (including thread/thread_id in avatar) and gpt-assistant intelligence.
+ * @todo Fix occasions where there will be no object_id property to use, as it was created through a hydration method based on API usage, so will be attached to mbr_id, but NOT avatar.id
+ * @todo - Turn this into Bot class
+ * @module
+ * @param {Guid} avatarId - The Avatar id
+ * @param {string} vectorstore_id - The Vectorstore id
+ * @param {AgentFactory} factory - Agent Factory instance
+ * @param {Avatar} avatar - Avatar object that will govern bot
+ * @param {object} botData - Bot data object, can be incomplete (such as update)
+ * @returns {Promise} - Bot object
+ */
+async function mBot(avatarId, vectorstore_id, factory, botData){
+ /* validation */
+ const { globals, isMyLife, mbr_id, newGuid, } = factory
+ const { id=newGuid, type, } = botData
+ console.log('BotAgent::mBot', avatarId, vectorstore_id, id, type, isMyLife)
+ throw new Error('mBot() not yet implemented')
+ if(!botType?.length)
+ throw new Error('Bot type required to create.')
+ bot.mbr_id = mbr_id /* constant */
+ bot.object_id = objectId
+ ?? avatarId /* all your bots belong to me */
+ bot.id = botId // **note**: _this_ is a Cosmos id, not an openAI id
+ let originBot = avatar.bots.find(oBot=>oBot.id===botId)
+ if(originBot){ /* update bot */
+ const options = {}
+ const updatedBot = Object.keys(bot)
+ .reduce((diff, key) => {
+ if(bot[key]!==originBot[key])
+ diff[key] = bot[key]
+ return diff
+ }, {})
+ /* create or update bot special properties */
+ const { thread_id, type, } = originBot // @stub - `bot_id` cannot be updated through this mechanic
+ if(!thread_id?.length && !avatar.isMyLife){
+ const excludeTypes = ['collection', 'library', 'custom'] // @stub - custom mechanic?
+ if(!excludeTypes.includes(type)){
+ const conversation = avatar.conversation(null, botId)
+ ?? await avatar.createConversation('chat', null, botId)
+ updatedBot.thread_id = conversation.thread_id // triggers `factory.updateBot()`
+ console.log('Avatar::mBot::conversation created given NO thread_id', updatedBot.thread_id, conversation.inspect(true))
+ }
+ }
+ let updatedOriginBot
+ if(Object.keys(updatedBot).length){
+ updatedOriginBot = {...originBot, ...updatedBot} // consolidated update
+ const { bot_id, id, } = updatedOriginBot
+ updatedBot.bot_id = bot_id
+ updatedBot.id = id
+ updatedBot.type = type
+ const { interests, } = updatedBot
+ /* set options */
+ if(interests?.length){
+ options.instructions = true
+ options.model = true
+ options.tools = false /* tools not updated through this mechanic */
+ }
+ updatedOriginBot = await factory.updateBot(updatedBot, options)
+ }
+ originBot = mSanitize(updatedOriginBot ?? originBot)
+ avatar.bots[avatar.bots.findIndex(oBot=>oBot.id===botId)] = originBot
+ } else { /* create assistant */
+ bot = mSanitize( await factory.createBot(bot, vectorstore_id) )
+ avatar.bots.push(bot)
+ }
+ return originBot
+ ?? bot
+}
+/**
+ * Creates bot and returns associated `bot` object.
+ * @todo - botData.name = botDbName should not be required, push logic to `llm-services`
+ * @module
+ * @async
+ * @param {Guid} avatarId - The Avatar id
+ * @param {String} vectorstore_id - The Vectorstore id
+ * @param {Object} bot - The bot data
+ * @param {AgentFactory} factory - Agent Factory instance
+ * @returns {Promise} - Created Bot instance
+*/
+async function mBotCreate(avatarId, vectorstore_id, bot, factory){
+ /* validation */
+ const { type, } = bot
+ if(!avatarId?.length || !type?.length)
+ throw new Error('avatar id and type required to create bot')
+ const { instructions, version, } = mBotInstructions(factory, bot)
+ const model = process.env.OPENAI_MODEL_CORE_BOT
+ ?? process.env.OPENAI_MODEL_CORE_AVATAR
+ ?? 'gpt-4o'
+ const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstore_id)
+ const id = factory.newGuid
+ let {
+ bot_name = `My ${type}`,
+ description = `I am a ${type} for ${factory.memberName}`,
+ name = `bot_${type}_${avatarId}`,
+ } = bot
+ const botData = {
+ being: 'bot',
+ bot_name,
+ description,
+ id,
+ instructions,
+ metadata: {
+ externalId: id,
+ version: version.toString(),
+ },
+ model,
+ name,
+ object_id: avatarId,
+ provider: 'openai',
+ purpose: description,
+ tools,
+ tool_resources,
+ type,
+ vectorstore_id,
+ version,
+ }
+ /* create in LLM */
+ const { id: bot_id, thread_id, } = await mBotCreateLLM(botData, llm)
+ if(!bot_id?.length)
+ throw new Error('bot creation failed')
+ /* create in MyLife datastore */
+ botData.bot_id = bot_id
+ botData.thread_id = thread_id
+ const Bot = new Bot(await factory.createBot(botData))
+ console.log(chalk.green(`bot created::${ type }`), Bot.thread_id, Bot.id, Bot.bot_id, Bot.bot_name )
+ return Bot
+}
+/**
+ * Creates bot and returns associated `bot` object.
+ * @module
+ * @param {object} botData - Bot object
+ * @param {LLMServices} llm - OpenAI object
+ * @returns {string} - Bot assistant id in openAI
+*/
+async function mBotCreateLLM(botData, llm){
+ const { id, thread_id, } = await mAI_openai(botData, llm)
+ return {
+ id,
+ thread_id,
+ }
+}
+/**
+ * Deletes the bot requested from avatar memory and from all long-term storage.
+ * @param {object} Bot - The bot object to delete
+ * @param {Object[]} bots - The bots array
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
+ * @returns {void}
+ */
+async function mBotDelete(Bot, bots, llm, factory){
+ const cannotRetire = ['actor', 'system', 'personal-avatar']
+ const { bot_id, id, thread_id, type, } = bot
+ if(cannotRetire.includes(type))
+ throw new Error(`Cannot retire bot type: ${ type }`)
+ /* delete from memory */
+ const botId = bots.findIndex(_bot=>_bot.id===id)
+ if(botId<0)
+ throw new Error('Bot not found in bots.')
+ bots.splice(botId, 1)
+ /* delete bot from Cosmos */
+ factory.deleteItem(id)
+ /* delete thread and bot from OpenAI */
+ llm.deleteBot(bot_id)
+ llm.deleteThread(thread_id)
+}
+/**
+ * Returns set of Greeting messages, dynamic or static
+ * @param {boolean} dynamic - Whether to use dynamic greetings
+ * @param {Bot} Bot - The bot instance
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
+ * @returns {Promise} - The array of messages to respond with
+ */
+async function mBotGreeting(dynamic=false, Bot, llm, factory){
+ const { bot_id, bot_name, id, greetings=[], greeting, thread_id, } = Bot
+ const failGreetings = [
+ `Hello! I'm concerned that there is something wrong with my instruction-set, as I was unable to find my greetings, but let's see if I can get back online.`,
+ `How can I be of help today?`
+ ]
+ const greetingPrompt = factory.isMyLife
+ ? `Greet this new visitor and let them know that you are here to help them understand MyLife and the MyLife platform. Begin by asking them about something that's important to them so I can demonstrate how MyLife will assist.`
+ : `Where did we leave off, or how do we start?`
+ const botGreetings = greetings?.length
+ ? greetings
+ : greeting
+ ? [greeting]
+ : failGreetings
+ let messages = !dynamic
+ ? botGreetings
+ : await llm.getLLMResponse(thread_id, bot_id, greetingPrompt, factory)
+ if(!messages?.length)
+ messages = failGreetings
+ messages = messages
+ .map(message=>new (factory.message)({
+ being: 'message',
+ content: message,
+ thread_id,
+ role: 'assistant',
+ type: 'greeting'
+ }))
+ return messages
+}
+/**
+ * Returns MyLife-version of bot instructions.
+ * @module
+ * @param {BotFactory} factory - Factory object
+ * @param {object} bot - Bot object
+ * @returns {object} - minor
+ */
+function mBotInstructions(factory, bot){
+ const { type=mDefaultBotType, } = bot
+ let {
+ instructions,
+ limit=8000,
+ version,
+ } = factory.botInstructions(type) ?? {}
+ if(!instructions) // @stub - custom must have instruction loophole
+ throw new Error(`bot instructions not found for type: ${ type }`)
+ let {
+ general,
+ purpose='',
+ preamble='',
+ prefix='',
+ references=[],
+ replacements=[],
+ suffix='', // example: data privacy info
+ voice='',
+ } = instructions
+ /* compile instructions */
+ switch(type){
+ case 'diary':
+ instructions = purpose
+ + preamble
+ + prefix
+ + general
+ + suffix
+ + voice
+ break
+ case 'personal-avatar':
+ instructions = preamble
+ + general
+ break
+ case 'journaler':
+ case 'personal-biographer':
+ instructions = preamble
+ + purpose
+ + prefix
+ + general
+ break
+ default:
+ instructions = general
+ break
+ }
+ /* apply replacements */
+ replacements.forEach(replacement=>{
+ const placeholderRegExp = factory.globals.getRegExp(replacement.name, true)
+ const replacementText = eval(`bot?.${replacement.replacement}`)
+ ?? eval(`factory?.${replacement.replacement}`)
+ ?? eval(`factory.core?.${replacement.replacement}`)
+ ?? replacement?.default
+ ?? '`unknown-value`'
+ instructions = instructions.replace(placeholderRegExp, _=>replacementText)
+ })
+ /* apply references */
+ references.forEach(_reference=>{
+ const _referenceText = _reference.insert
+ const replacementText = eval(`factory?.${_reference.value}`)
+ ?? eval(`bot?.${_reference.value}`)
+ ?? _reference.default
+ ?? '`unknown-value`'
+ switch(_reference.method ?? 'replace'){
+ case 'append-hard':
+ const _indexHard = instructions.indexOf(_referenceText)
+ if (_indexHard !== -1) {
+ instructions =
+ instructions.slice(0, _indexHard + _referenceText.length)
+ + '\n'
+ + replacementText
+ + instructions.slice(_indexHard + _referenceText.length)
+ }
+ break
+ case 'append-soft':
+ const _indexSoft = instructions.indexOf(_referenceText);
+ if (_indexSoft !== -1) {
+ instructions =
+ instructions.slice(0, _indexSoft + _referenceText.length)
+ + ' '
+ + replacementText
+ + instructions.slice(_indexSoft + _referenceText.length)
+ }
+ break
+ case 'replace':
+ default:
+ instructions = instructions.replace(_referenceText, replacementText)
+ break
+ }
+ })
+ /* assess and validate limit */
+ return { instructions, version, }
+}
+/**
+ * Updates bot in Cosmos, and if necessary, in LLM. Returns unsanitized bot data document.
+ * @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, vectorstoreId: string, }
+ * @returns
+ */
+async function mBotUpdate(factory, llm, bot, options={}){
+ /* constants */
+ const {
+ id, // no modifications
+ instructions: removeInstructions,
+ tools: removeTools,
+ tool_resources: removeResources,
+ type, // no modifications
+ ...botData // extract member-driven bot data
+ } = bot
+ const {
+ 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, } = mBotInstructions(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, 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, 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 OpenAI'), id, bot_id, updatedLLMFields)
+ }
+ const updatedBotData = await factory.updateBot(botData)
+ return updatedBotData
+}
+/**
+ * Create a new conversation.
+ * @async
+ * @module
+ * @param {string} type - Type of conversation: chat, experience, dialog, inter-system, system, etc.; defaults to `chat`
+ * @param {string} form - Form of conversation: system-avatar, member-avatar, etc.; defaults to `system-avatar`
+ * @param {string} thread_id - The openai thread id
+ * @param {string} botId - The bot id
+ * @returns {Conversation} - The conversation object
+ */
+async function mConversationStart(form='system', type='chat', thread_id, llmAgentId, llm, factory){
+ const { mbr_id, } = factory
+ const thread = await llm.thread(thread_id)
+ const Conversation = new (factory.conversation)(
+ {
+ form,
+ mbr_id,
+ type,
+ },
+ factory,
+ thread,
+ llmAgentId
+ )
+ return Conversation
+}
+/**
+ * Retrieves any functions that need to be attached to the specific bot-type.
+ * @module
+ * @todo - Move to llmServices and improve
+ * @param {string} type - Type of bot.
+ * @param {object} globals - Global functions for bot.
+ * @param {string} vectorstoreId - Vectorstore id.
+ * @returns {object} - OpenAI-ready object for functions { tools, tool_resources, }.
+ */
+function mGetAIFunctions(type, globals, vectorstoreId){
+ let includeSearch=false,
+ tool_resources,
+ tools = []
+ switch(type){
+ case 'assistant':
+ case 'avatar':
+ case 'personal-assistant':
+ case 'personal-avatar':
+ includeSearch = true
+ break
+ case 'biographer':
+ case 'personal-biographer':
+ tools.push(
+ globals.getGPTJavascriptFunction('changeTitle'),
+ globals.getGPTJavascriptFunction('getSummary'),
+ globals.getGPTJavascriptFunction('storySummary'),
+ globals.getGPTJavascriptFunction('updateSummary'),
+ )
+ includeSearch = true
+ break
+ case 'custom':
+ includeSearch = true
+ break
+ case 'diary':
+ case 'journaler':
+ tools.push(
+ globals.getGPTJavascriptFunction('changeTitle'),
+ globals.getGPTJavascriptFunction('entrySummary'),
+ globals.getGPTJavascriptFunction('getSummary'),
+ globals.getGPTJavascriptFunction('obscure'),
+ globals.getGPTJavascriptFunction('updateSummary'),
+ )
+ includeSearch = true
+ break
+ default:
+ break
+ }
+ if(includeSearch){
+ const { tool_resources: gptResources, tools: gptTools, } = mGetGPTResources(globals, 'file_search', vectorstoreId)
+ tools.push(...gptTools)
+ tool_resources = gptResources
+ }
+ return {
+ tools,
+ tool_resources,
+ }
+}
+/**
+ * Retrieves bot types based on team name and MyLife status.
+ * @modular
+ * @param {Boolean} isMyLife - Whether request is coming from MyLife Q AVatar
+ * @param {*} teamName - The team name, defaults to `mDefaultTeam`
+ * @returns {String[]} - The array of bot types
+ */
+function mGetBotTypes(isMyLife=false, teamName=mDefaultTeam){
+ const team = mTeams
+ .find(team=>team.name===teamName)
+ const botTypes = [...mRequiredBotTypes, ...isMyLife ? [] : team?.defaultTypes ?? []]
+ return botTypes
+}
+/**
+ * Retrieves any tools and tool-resources that need to be attached to the specific bot-type.
+ * @param {Globals} globals - Globals object.
+ * @param {string} toolName - Name of tool.
+ * @param {string} vectorstoreId - Vectorstore id.
+ * @returns {object} - { tools, tool_resources, }.
+ */
+function mGetGPTResources(globals, toolName, vectorstoreId){
+ switch(toolName){
+ case 'file_search':
+ const { tools, tool_resources, } = globals.getGPTFileSearchToolStructure(vectorstoreId)
+ return { tools, tool_resources, }
+ default:
+ throw new Error('tool name not recognized')
+ }
+}
+/**
+ * Initializes the provided BotAgent instance.
+ * @async
+ * @module
+ * @param {BotAgent} BotAgent - The BotAgent to initialize
+ * @param {Bot[]} bots - The array of bots (empty on init)
+ * @param {AgentFactory} factory - The factory instance
+ * @param {LLMServices} llm - The LLM instance
+ * @returns {void}
+ */
+async function mInit(BotAgent, bots, factory, llm){
+ const { avatarId, vectorstoreId, } = BotAgent
+ bots.push(...await mInitBots(avatarId, vectorstoreId, factory, llm))
+ BotAgent.setActiveBot()
+}
+/**
+ * Initializes active bots based upon criteria.
+ * @param {Guid} avatarId - The Avatar id
+ * @param {String} vectorstore_id - The Vectorstore id
+ * @param {AgentFactory} factory - The MyLife factory instance
+ * @param {LLMServices} llm - The LLM instance
+ * @returns {Bot[]} - The array of activated and available bots
+ */
+async function mInitBots(avatarId, vectorstore_id, factory, llm){
+ const bots = ( await factory.bots(avatarId) )
+ .map(botData=>{
+ botData.vectorstore_id = vectorstore_id
+ botData.object_id = avatarId
+ return new Bot(botData, factory, llm)
+ })
+ return bots
+}
+/* exports */
+export default BotAgent
\ No newline at end of file
diff --git a/inc/js/agents/system/bot-assistant.mjs b/inc/js/agents/system/bot-assistant.mjs
deleted file mode 100644
index c0bcf154..00000000
--- a/inc/js/agents/system/bot-assistant.mjs
+++ /dev/null
@@ -1,2 +0,0 @@
-// creates and manages avatar bot legion
-// avatars = personae, secondary personae cannot have certain bots, such as biog-bot
diff --git a/inc/js/agents/system/evolution-assistant.mjs b/inc/js/agents/system/evolution-agent.mjs
similarity index 80%
rename from inc/js/agents/system/evolution-assistant.mjs
rename to inc/js/agents/system/evolution-agent.mjs
index 424ff846..be7b623a 100644
--- a/inc/js/agents/system/evolution-assistant.mjs
+++ b/inc/js/agents/system/evolution-agent.mjs
@@ -1,5 +1,4 @@
// imports
-import { _ } from 'ajv'
import { EventEmitter } from 'events'
/* module constants */
const _phases = [
@@ -12,12 +11,12 @@ const _phases = [
]
const _defaultPhase = _phases[0]
/**
- * @class EvolutionAssistant
+ * @class EvolutionAgent
* @extends EventEmitter
* Handles the evolutionary process of an avatar, managing its growth and development through various phases, loosely but not solely correlated with timeline.
* See notes at end for design principles and notes
*/
-export class EvolutionAssistant extends EventEmitter {
+export class EvolutionAgent extends EventEmitter {
#avatar // symbiotic avatar object
#contributions = [] // self-managed aray of contributions on behalf of embedded avatar; could postdirectly to avatar when required
#phase // create, init, develop, mature, maintain, retire
@@ -128,28 +127,28 @@ export class EvolutionAssistant extends EventEmitter {
* Advance the phase of the Evolution Assistant. Logic is encapsulated to ensure that the phase is advanced only when appropriate, ergo, not every request _to_ advancePhase() will actually _do_ so. Isolates and privatizes logic to propose _advance_ to next phase.
* @module
* @emits {evo-agent-phase-change} - Emitted when the phase advances.
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @returns {string} The determined phase.
* @todo Implement phase advancement logic for: develop, mature, maintain, retire.
*/
-async function mAdvancePhase(_evoAgent){ // **note**: treat parameter `_evoAgent` as `read-only` for now
+async function mAdvancePhase(evoAgent){ // **note**: treat parameter `evoAgent` as `read-only` for now
const _proposal = { // no need to objectify
- contributions: _evoAgent.contributions,
- phase: _evoAgent.phase,
+ contributions: evoAgent.contributions,
+ phase: evoAgent.phase,
phaseChange: false
}
- switch(_evoAgent.phase) {
+ switch(evoAgent.phase) {
case 'create': // initial creation of object, no data yet
case 'init': // need initial basic data for categorical descriptions of underlying data object; think of this as the "seed" phase, where questions are as yet unfit nor personalized in any meaningful way to the underlying core human (or data object), so need to feel way around--questions here could really come from embedding db
const _formalPhase = 'init'
- if(!_evoAgent.categories.length)
- return _evoAgent.phase
- if(!_evoAgent.contributions.length < 3){ // too low, refresh
- const contributionsPromises = mAssessData(_evoAgent)
- .map(_category => mGetContribution(_evoAgent, _category, _formalPhase)) // Returns array of promises
+ if(!evoAgent.categories.length)
+ return evoAgent.phase
+ if(!evoAgent.contributions.length < 3){ // too low, refresh
+ const contributionsPromises = mAssessData(evoAgent)
+ .map(_category => mGetContribution(evoAgent, _category, _formalPhase)) // Returns array of promises
_proposal.contributions = await Promise.all(contributionsPromises) }
// alterations sent as proposal to be adopted (or not, albeit no current mechanism to reject) by instantiated evo-agent [only viable caller by module design]
- _proposal.phase = (mEvolutionPhaseComplete(_evoAgent,_formalPhase))
+ _proposal.phase = (mEvolutionPhaseComplete(evoAgent,_formalPhase))
? 'init'
: 'develop'
_proposal.phaseChange = (_proposal.phase !== 'init')
@@ -162,50 +161,50 @@ async function mAdvancePhase(_evoAgent){ // **note**: treat parameter `_evoAge
case 'retire': // contributions have ceased with request to retire object; would never happen with core, but certainly can with any other spawned object; **note** not deletion or removal at this point, but rather a request to stop contributing to the object, lock it and archive; of course could be rehydrated at any time, but from cold state or colder
break
default:
- // throw new Error(`unknown phase: ${_evoAgent.phase}`)
+ // throw new Error(`unknown phase: ${evoAgent.phase}`)
}
return _proposal
}
/**
* Reviews properties of avatar and returns an array of three categories most in need of member Contributions.
* @module
- * @param {EvolutionAssistant} _evoAgent - The avatar evoAgent whose data requires assessment.
+ * @param {EvolutionAgent} evoAgent - The avatar evoAgent whose data requires assessment.
* @param {number} _numCategories - The number of categories to return. Defaults to 5. minimum 1, maximum 9.
* @returns {Array} The top number categories requiring Contributions.
*/
-function mAssessData(_evoAgent, _numCategories) {
+function mAssessData(evoAgent, _numCategories) {
const _defaultNumCategories = 5
const _maxNumCategories = 9
return [
- ...mAssessNulls(_evoAgent),
- ...mAssessNodes(_evoAgent)
+ ...mAssessNulls(evoAgent),
+ ...mAssessNodes(evoAgent)
.slice(0, _numCategories || _defaultNumCategories)
]
.slice(0, Math.min(_numCategories || _defaultNumCategories, _maxNumCategories))
}
/**
* Asses nodes for categories to contribute to.
- * @param {EvolutionAssistant} _evoAgent
+ * @param {EvolutionAgent} evoAgent
* @returns {Array} The categories to contribute to.
*/
-function mAssessNodes(_evoAgent){
- return _evoAgent.categories
- .filter(_category => _evoAgent?.[mFormatCategory(_category)])
+function mAssessNodes(evoAgent){
+ return evoAgent.categories
+ .filter(_category => evoAgent?.[mFormatCategory(_category)])
.map(_category => mFormatCategory(_category))
- .sort((a, b) => _evoAgent[a].length - _evoAgent[b].length)
+ .sort((a, b) => evoAgent[a].length - evoAgent[b].length)
}
-function mAssessNulls(_evoAgent) {
- return _evoAgent.categories
- .filter(_category => !_evoAgent?.[mFormatCategory(_category)])
+function mAssessNulls(evoAgent) {
+ return evoAgent.categories
+ .filter(_category => !evoAgent?.[mFormatCategory(_category)])
.map(_category => mFormatCategory(_category))
.sort(() => Math.random() - 0.5)
}
/**
* Assign listeners to a Contribution object.
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @param {Contribution} _contribution - The Contribution object to assign listeners to.
*/
-function mAssignContributionListeners(_evoAgent, _contribution) {
+function mAssignContributionListeners(evoAgent, _contribution) {
// **note**: logging exact text of event for now, but could be more generic
_contribution.on(
'on-contribution-new',
@@ -229,14 +228,14 @@ function mAssignContributionListeners(_evoAgent, _contribution) {
/**
* Determines whether the given phase is complete.
* @module
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @param {string} _phase - The phase to check for completion.
*/
-function mEvolutionPhaseComplete(_evoAgent,_phase) {
+function mEvolutionPhaseComplete(evoAgent,_phase) {
switch (_phase) {
case 'init':
// if category data nodes exist that have no data, return false
- return (_evoAgent.categories)
+ return (evoAgent.categories)
default: // such as `create`
return true
}
@@ -258,13 +257,13 @@ function mFormatCategory(_category) {
* Digest a request to generate a new Contribution.
* @module
* @emits {on-contribution-new} - Emitted when a new Contribution is generated.
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @param {string} _category - The category to process.
* @param {string} _phase - The phase to process.
* @returns {Contribution} A new Contribution object.
*/
-async function mGetContribution(_evoAgent, _category, _phase) {
- const _avatar = _evoAgent.avatar
+async function mGetContribution(evoAgent, _category, _phase) {
+ const _avatar = evoAgent.avatar
_category = mFormatCategory(_category)
// Process question and map to `new Contribution` class
const _contribution = new (_avatar.factory.contribution)({
@@ -282,7 +281,7 @@ async function mGetContribution(_evoAgent, _category, _phase) {
},
responses: [],
})
- mAssignContributionListeners(_evoAgent, _contribution)
+ mAssignContributionListeners(evoAgent, _contribution)
return await _contribution.init(_avatar.factory) // fires emitters
}
/**
@@ -290,47 +289,47 @@ async function mGetContribution(_evoAgent, _category, _phase) {
* @module
* @emits {_emit_text} - Emitted when an object is logged.
* @param {string} _emit_text - The text to emit.
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @param {object} _object - The object to log, if not evoAgent.
*/
-function mLog(_emit_text,_evoAgent,_object) {
- if(_emit_text) _evoAgent.emit(_emit_text, _object??_evoAgent) // incumbent upon EvoAgent to incorporate child emissions into self and _then_ emit here
+function mLog(_emit_text,evoAgent,_object) {
+ if(_emit_text) evoAgent.emit(_emit_text, _object??evoAgent) // incumbent upon EvoAgent to incorporate child emissions into self and _then_ emit here
}
/**
* Process a Contribution. First update the Contribution object, determining if the Contribution stage is updated. Then evaluate Evolution phase for completeness and advancement.
* @module
- * @param {EvolutionAssistant} _evoAgent - `this` Evolution Assistant.
+ * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
* @param {Array} _contributions - The contributions array.
* @param {object} _current - Contribution object { category, contributionId, message }
* @param {object} _proposed - Contribution object { category, contributionId, message }
* @returns {object} The updated Contribution instantiation.
*/
-function mSetContribution(_evoAgent, _current, _proposed) {
+function mSetContribution(evoAgent, _current, _proposed) {
/* update Contribution */
if(_proposed?.contributionId){
- _evoAgent.contributions
+ evoAgent.contributions
.find(_contribution => _contribution.id === _proposed.contributionId)
.update(_proposed) // emits avatar update event
}
/* evolve phase */
if(_current?.category!==_proposed.category){
- _evoAgent.emit('avatar-change-category', _current, _proposed)
+ evoAgent.emit('avatar-change-category', _current, _proposed)
if(_current?.contributionId){
/* @todo: verify that categories are changing */
- const _currentContribution = _evoAgent.contributions
+ const _currentContribution = evoAgent.contributions
.find(_contribution => _contribution.id === _current.contributionId)
console.log('evolution-assistant:mSetContribution():320', _currentContribution.inspect(true))
if(_currentContribution.stage === 'prepared'){ // ready to process
// join array and submit for gpt-summarization
- mSubmitContribution(_evoAgent, _contributions.responses.join('\n'))
+ mSubmitContribution(evoAgent, _contributions.responses.join('\n'))
// advance phase, write db, emit event
}
}
}
}
-async function mSubmitContribution(_evoAgent, _contribution) {
+async function mSubmitContribution(evoAgent, _contribution) {
// emit to avatar => (session?) => datacore
_contribution.emit('on-contribution-submitted', _contribution)
}
// exports
-export default EvolutionAssistant
\ No newline at end of file
+export default EvolutionAgent
\ No newline at end of file
diff --git a/inc/js/api-functions.mjs b/inc/js/api-functions.mjs
index 64a5b6bc..2bc235b4 100644
--- a/inc/js/api-functions.mjs
+++ b/inc/js/api-functions.mjs
@@ -148,6 +148,7 @@ async function keyValidation(ctx){
ctx.status = 200 // OK
if(ctx.method === 'HEAD') return
const { mbr_id } = ctx.state
+ // @todo - may not reflect data core any longer
const memberCore = await ctx.MyLife.datacore(mbr_id)
const { updates, interests, birth: memberBirth, birthDate: memberBirthDate, fullName, names, nickname } = memberCore
const birth = (Array.isArray(memberBirth) && memberBirth.length)
diff --git a/inc/js/core.mjs b/inc/js/core.mjs
index b078ae77..be67b867 100644
--- a/inc/js/core.mjs
+++ b/inc/js/core.mjs
@@ -266,7 +266,7 @@ class MyLife extends Organization { // form=server
* @returns {boolean} - Whether or not member is logged in successfully.
*/
async challengeAccess(memberId, passphrase){
- const challengeSuccessful = await this.factory.challengeAccess(memberId, passphrase)
+ const challengeSuccessful = await this.#avatar.challengeAccess(memberId, passphrase)
return challengeSuccessful
}
/**
@@ -277,7 +277,8 @@ class MyLife extends Organization { // form=server
async datacore(mbr_id){
if(!mbr_id || mbr_id===this.mbr_id)
throw new Error('datacore cannot be accessed')
- return await this.factory.datacore(mbr_id)
+ const core = this.globals.sanitize(await this.factory.datacore(mbr_id))
+ return core
}
/**
* Submits and returns the journal or diary entry to MyLife via API.
@@ -377,7 +378,7 @@ class MyLife extends Organization { // form=server
mbr_id,
name: `${ being }_${ title.substring(0,64) }_${ mbr_id }`,
}
- const savedStory = this.globals.stripCosmosFields(await this.factory.summary(story))
+ const savedStory = this.globals.sanitize(await this.factory.summary(story))
return savedStory
}
/**
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index 6b7358d2..a8bb4692 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -1,5 +1,4 @@
/* imports */
-import oAIAssetAssistant from './agents/system/asset-assistant.mjs'
import {
upload as apiUpload,
} from './api-functions.mjs'
@@ -54,8 +53,7 @@ async function bots(ctx){
} else {
const {
activeBotId,
- prunedBots: bots,
- mbr_id,
+ bots,
} = avatar
ctx.body = { // wrap bots
activeBotId,
@@ -156,7 +154,7 @@ async function greetings(ctx){
if(validateId?.length)
response.messages.push(...await avatar.validateRegistration(validateId))
else
- response.messages.push(...await avatar.getGreeting(dynamic))
+ response.messages.push(...await avatar.greeting(dynamic))
response.success = response.messages.length > 0
ctx.body = response
}
@@ -383,9 +381,9 @@ async function team(ctx){
* @param {Koa} ctx - Koa Context object.
* @returns {Object[]} - List of team objects.
*/
-function teams(ctx){
+async function teams(ctx){
const { avatar, } = ctx.state
- ctx.body = avatar.teams()
+ ctx.body = await avatar.teams()
}
async function updateBotInstructions(ctx){
const { bid, } = ctx.params
diff --git a/inc/js/globals.mjs b/inc/js/globals.mjs
index c3003faa..8a180035 100644
--- a/inc/js/globals.mjs
+++ b/inc/js/globals.mjs
@@ -1,6 +1,6 @@
// imports
import EventEmitter from 'events'
-import { Guid } from 'js-guid' // usage = Guid.newGuid().toString()
+import { Guid } from 'js-guid'
/* constants */
const mAiJsFunctions = {
changeTitle: {
@@ -206,17 +206,35 @@ const mAiJsFunctions = {
}
},
}
-const mEmailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ // regex for email validation
+const mEmailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+const mForbiddenCosmosFields = ['$', '_', ' ', '@', '#',]
const mGuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[4][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i // regex for GUID validation
const mOpenAIBotModel = process.env.OPENAI_MODEL_CORE_BOT
- ?? 'gpt-4o' // current MyLife OpenAI model for system on-demand and custom bots (personal-avatar may be different)
-// module classes
+ ?? 'gpt-4o'
+/**
+ * Globals class holds all of the sensitive data and functionality. It exists as a singleton.
+ * @class
+ * @extends EventEmitter
+ * @todo - Since traced back to Maht Globals, this could be converted to the VM and hold that code
+ */
class Globals extends EventEmitter {
constructor() {
- // essentially this is a coordinating class wrapper that holds all of the sensitive data and functionality; as such, it is a singleton, and should either _be_ the virtual server or instantiated on one at startup
super()
}
/* public functions */
+ /**
+ * Chunk an array into smaller arrays and returns as an Array.
+ * @param {Array} array - Array to chunk
+ * @param {number} size - Size of chunks
+ * @returns {Array} - Array of chunked arrays
+ */
+ chunkArray(array, size) {
+ const result = []
+ for(let i = 0; i < array.length; i += size){
+ result.push(array.slice(i, i + size))
+ }
+ return result
+ }
/**
* Clears a const array with nod to garbage collection.
* @param {Array} a - the array to clear.
@@ -295,8 +313,19 @@ class Globals extends EventEmitter {
const regex = /^\d+\.\d+\.\d+$/
return typeof version === 'string' && regex.test(version)
}
- stripCosmosFields(object){
- return Object.fromEntries(Object.entries(object).filter(([k, v]) => !k.startsWith('_')))
+ /**
+ * Sanitize an object by removing forbidden Cosmos fields.
+ * @param {object} object - Cosmos document to sanitize
+ * @returns {object} - Sanitized data object
+ */
+ sanitize(object){
+ if(!object || typeof object !== 'object')
+ throw new Error('Parameter requires an object')
+ const sanitizedData = Object.fromEntries(
+ Object.entries(object)
+ .filter(([key, value])=>!mForbiddenCosmosFields.some(char => key.startsWith(char)))
+ )
+ return sanitizedData
}
sysId(_mbr_id){
if(!typeof _mbr_id==='string' || !_mbr_id.length || !_mbr_id.includes('|'))
@@ -309,7 +338,7 @@ class Globals extends EventEmitter {
toString(_obj){
return Object.entries(_obj).map(([k, v]) => `${k}: ${v}`).join(', ')
}
- // getters/setters
+ /* getters/setters */
get currentOpenAIBotModel(){
return mOpenAIBotModel
}
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 00fde409..7660f8fc 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -1,26 +1,24 @@
import { Marked } from 'marked'
import EventEmitter from 'events'
-import oAIAssetAssistant from './agents/system/asset-assistant.mjs'
-import { EvolutionAssistant } from './agents/system/evolution-assistant.mjs'
+import AssetAgent from './agents/system/asset-agent.mjs'
+import BotAgent from './agents/system/bot-agent.mjs'
+import EvolutionAgent from './agents/system/evolution-agent.mjs'
import LLMServices from './mylife-llm-services.mjs'
/* module constants */
const mAllowSave = JSON.parse(
process.env.MYLIFE_DB_ALLOW_SAVE
- ?? false
+ ?? 'false'
)
const mAvailableModes = ['standard', 'admin', 'evolution', 'experience', 'restoration']
-const mBot_idOverride = process.env.OPENAI_MAHT_GPT_OVERRIDE
/**
- * @class
+ * @class - Avatar
* @extends EventEmitter
* @description An avatar is a digital self proxy of Member. Not of the class but of the human themselves - they are a one-to-one representation of the human, but the synthetic version that interopts between member and internet when inside the MyLife platform. The Avatar is the manager of the member experience, and is the primary interface with the AI (aside from when a bot is handling API request, again we are speaking inside the MyLife platform).
* @todo - deprecate `factory` getter
- * @todo - more efficient management of module constants, should be classes?
*/
class Avatar extends EventEmitter {
- #activeBotId // id of active bot in this.#bots; empty or undefined, then this
#assetAgent
- #bots = []
+ #botAgent
#evolver
#experienceGenericVariables = {
age: undefined,
@@ -49,7 +47,8 @@ class Avatar extends EventEmitter {
super() // EventEmitter
this.#factory = factory
this.#llmServices = llmServices
- this.#assetAgent = new oAIAssetAssistant(this.#factory, this.globals, this.#llmServices)
+ this.#assetAgent = new AssetAgent(this.#factory, this.#llmServices)
+ this.#botAgent = new BotAgent(this.#factory, this.#llmServices)
}
/* public functions */
/**
@@ -61,26 +60,20 @@ class Avatar extends EventEmitter {
* @returns {Promise} Promise resolves to this Avatar class instantiation
*/
async init(){
- await mInit(this.#factory, this.#llmServices, this, this.#bots, this.#assetAgent, this.#vectorstoreId) // mutates and populates
+ await mInit(this.#factory, this.#llmServices, this, this.#botAgent, this.#assetAgent, this.#vectorstoreId) // mutates and populates
/* experience variables */
this.#experienceGenericVariables = mAssignGenericExperienceVariables(this.#experienceGenericVariables, this)
- /* llm services */
- this.#llmServices.bot_id = mBot_idOverride && this.isMyLife
- ? mBot_idOverride
- : this.activeBot.bot_id
return this
}
/**
- * Get a bot's properties from Cosmos (or type in .bots).
+ * Get a Bot instance by id.
* @public
- * @async
- * @param {Guid} id - The bot id
- * @returns {Promise} - The bot object from memory
+ * @param {Guid} botId - The bot id
+ * @returns {Promise} - The bot object from memory
*/
- async bot(id){
- const bot = this.bots.find(bot=>bot.id===id)
- ?? await this.#factory.bot(id)
- return bot
+ bot(botId){
+ const Bot = this.#botAgent.bot(botId)
+ return Bot
}
/**
* Processes and executes incoming chat request.
@@ -96,7 +89,12 @@ class Avatar extends EventEmitter {
async chat(message, itemId, shadowId, processStartTime=Date.now(), session=null, thread_id){
if(!message)
throw new Error('No message provided in context')
- const { bot_id, conversation=this.getConversation(thread_id), id: botId, } = this.activeBot
+
+
+
+
+ const Conversation = this.activeBot.conversation()
+ // what should I actually return from chat?
if(!conversation)
throw new Error('No conversation found for bot intelligence and could not be created.')
conversation.bot_id = botId
@@ -130,14 +128,13 @@ class Avatar extends EventEmitter {
console.log('chat::BYPASS-SAVE', conversation.message?.content?.substring(0,64))
/* frontend mutations */
const responses = []
- const { activeBot: bot } = this
conversation.messages
.filter(_message=>{
return messages.find(__message=>__message.id===_message.id)
&& _message.type==='chat'
&& _message.role!=='user'
})
- .map(_message=>mPruneMessage(bot, _message, 'chat', processStartTime))
+ .map(_message=>mPruneMessage(botId, _message, 'chat', processStartTime))
.reverse()
.forEach(_message=>responses.push(_message))
if(!responses?.length){
@@ -191,60 +188,26 @@ class Avatar extends EventEmitter {
return collections
}
/**
- * Create a new bot. Errors if bot cannot be created.
- * @async
- * @public
- * @param {object} bot - The bot data object, requires type.
- * @returns {object} - The new bot.
+ * Start a new conversation.
+ * @param {String} type - The type of conversation, defaults to `chat`
+ * @param {String} form - The form of conversation, defaults to `member-avatar`
+ * @returns {Promise} - The Conversation instance
*/
- async createBot(bot){
- /* validate request */
- const { type, } = bot
- if(!type)
- throw new Error('Bot type required to create')
- const singletonBotExists = this.bots
- .filter(_bot=>_bot.type===type && !_bot.allowMultiple) // same type, self-declared singleton
- .filter(_bot=>_bot.allowedBeings?.includes('avatar')) // avatar allowed to create
- .length
- if(singletonBotExists)
- throw new Error(`Bot type "${type}" already exists and bot-multiples disallowed.`)
- /* execute request */
- bot = await mBot(this.#factory, this, bot)
- /* respond request */
- const response = mPruneBot(bot)
- return response
+ async conversationStart(type='chat', form='member-avatar'){
+ const Conversation = await this.#botAgent.conversationStart(type, form)
+ return Conversation
}
/**
- * Create a new conversation.
+ * Create a new bot.
* @async
* @public
- * @param {string} type - Type of conversation: chat, experience, dialog, inter-system, etc.; defaults to `chat`
- * @param {string} thread_id - The openai thread id
- * @param {string} botId - The bot id
- * @returns {Conversation} - The conversation object
- */
- async createConversation(type='chat', thread_id, botId=this.activeBotId){
- const mbr_id = this.mbr_id
- const thread = await this.#llmServices.thread(thread_id)
- const { conversation: previousConversation, type: botType, } = this.isMyLife
- ? this.avatar
- : await this.bot(botId)
- if(!!previousConversation)
- throw new Error(`Conversation already exists for bot/thread: ${ botId }/${ thread.id }`)
- const form = botType?.split('-').pop()
- ?? 'system'
- const conversation = new (this.#factory.conversation)(
- {
- form,
- mbr_id,
- type,
- },
- this.#factory,
- thread,
- botId
- )
- console.log('conversation created', conversation.inspect(true))
- return conversation
+ * @param {Object} botData - The bot data object, requires type.
+ * @returns {Object} - The new bot.
+ */
+ async createBot(botData){
+ const Bot = await this.#botAgent.botCreate(botData)
+ const bot = Bot.bot
+ return bot
}
/**
* Delete an item from member container.
@@ -377,14 +340,24 @@ class Avatar extends EventEmitter {
)
}
/**
- * Specified by id, returns the pruned bot from memory.
- * @param {Guid} id - The id of the item to get
- * @returns {object} - The pruned bot object
+ * Specified by id, returns the pruned Bot.
+ * @param {Guid} id - The Bot id
+ * @returns {object} - The pruned Bot object
*/
- getBot(id){
- const bot = mPruneBot(this.bots.find(bot=>bot.id===id))
+ getBot(botId){
+ const bot = this.#botAgent.bot(botId)?.bot
return bot
}
+ /**
+ * Returns the pruned bots for avatar.
+ * @param {Guid} id - The Bot id
+ * @returns {Object[]} - The pruned Bot objects
+ */
+ getBots(){
+ const bots = this.bots
+ .map(Bot=>Bot.bot)
+ return bots
+ }
/**
* Gets Conversation object. If no thread id, creates new conversation.
* @param {string} thread_id - openai thread id (optional)
@@ -409,11 +382,13 @@ class Avatar extends EventEmitter {
}
/**
* Get a static or dynamic greeting from active bot.
- * @param {boolean} dynamic - Whether to use LLM for greeting.
- * @returns {array} - The greeting message(s) string array in order of display.
+ * @param {boolean} dynamic - Whether to use LLM for greeting
+ * @returns {Array} - The greeting message(s) string array in order of display
*/
- async getGreeting(dynamic=false){
- return await mGreeting(this.activeBot, dynamic, this.#llmServices, this.#factory)
+ async greeting(dynamic=false){
+ console.log('greeting', this.#botAgent.activeBotId)
+ const greetings = mPruneMessages(this.#botAgent.activeBotId, await this.#botAgent.greeting(dynamic), 'greeting')
+ return greetings
}
/**
* Request help about MyLife. **caveat** - correct avatar should have been selected prior to calling.
@@ -438,7 +413,7 @@ class Avatar extends EventEmitter {
conversation.save()
else
console.log('helpRequest::BYPASS-SAVE', conversation.message.content)
- const response = mPruneMessages(this.activeBot, helpResponseArray, 'help', processStartTime)
+ const response = mPruneMessages(this.activeBotId, helpResponseArray, 'help', processStartTime)
return response
}
/**
@@ -550,7 +525,8 @@ class Avatar extends EventEmitter {
if(!id)
throw new Error(`item does not exist in member container: ${ iid }`)
/* develop narration */
- const narration = await mReliveMemoryNarration(this, this.#factory, this.#llmServices, this.biographer, item, memberInput)
+ const Biographer = this.#botAgent.biographer
+ const narration = await mReliveMemoryNarration(this, this.#factory, this.#llmServices, Biographer, item, memberInput)
return narration // include any required .map() pruning
}
/**
@@ -573,18 +549,18 @@ class Avatar extends EventEmitter {
async retireBot(botId){
/* reset active bot, if required */
if(this.activeBotId===botId)
- this.activeBotId = null
+ this.#botAgent.setActiveBot() // avatar cannot be retired
const bot = await this.bot(botId)
if(!bot)
throw new Error(`Bot not found with id: ${ botId }`)
const { id, } = bot
if(botId!==id)
throw new Error(`Bot id mismatch: ${ botId }!=${ id }`)
- mDeleteBot(bot, this.#bots, this.#llmServices, this.#factory)
+ this.#botAgent.botDelete(botId)
const response = {
instruction: {
command: 'removeBot',
- botId,
+ id: botId,
},
responses: [{
agent: 'server',
@@ -659,7 +635,6 @@ class Avatar extends EventEmitter {
throw new Error(`cannot find item: ${ itemId }`)
const { form, summary, } = item
let tailgate
- const bot = this?.[form] ?? this.activeBot /* currently only `biographer` which transforms thusly when referenced here as this[form] */
switch(type){
case 'member':
message = `update-memory-request: itemId=${ itemId }\n` + message
@@ -681,10 +656,10 @@ class Avatar extends EventEmitter {
default:
break
}
- let messages = await mCallLLM(this.#llmServices, bot, message, this.#factory, this)
- messages = messages.map(message=>mPruneMessage(bot, message, 'shadow', processingStartTime))
+ let messages = await mCallLLM(this.#llmServices, this.activeBot, message, this.#factory, this)
+ messages = messages.map(message=>mPruneMessage(this.activeBotId, message, 'shadow', processingStartTime))
if(tailgate?.length)
- messages.push(mPruneMessage(bot, tailgate, 'system'))
+ messages.push(mPruneMessage(this.activeBotId, tailgate, 'system'))
return messages
}
/**
@@ -706,7 +681,7 @@ class Avatar extends EventEmitter {
throw new Error('MyLife avatar cannot summarize files.')
if(!fileId?.length && !fileName?.length)
throw new Error('File id or name required for summarization.')
- const { bot_id, thread_id, } = this.personalAssistant
+ const { bot_id, id: botId, thread_id, } = this.avatar
const prompt = `Summarize this file document: name=${ fileName }, id=${ fileId }`
const response = {
messages: [],
@@ -715,7 +690,7 @@ class Avatar extends EventEmitter {
try{
let messages = await mCallLLM(this.#llmServices, { bot_id, thread_id, }, prompt, this.#factory, this)
messages = messages
- .map(message=>mPruneMessage(this.personalAssistant, message, 'mylife-file-summary', processStartTime))
+ .map(message=>mPruneMessage(botId, message, 'mylife-file-summary', processStartTime))
.filter(message=>message && message.role!=='user')
if(!messages.length)
throw new Error('No valid messages returned from summarization.')
@@ -731,30 +706,12 @@ class Avatar extends EventEmitter {
}
/**
* Get a specified team, its details and _instanced_ bots, by id for the member.
- * @param {Koa} ctx - Koa Context object
+ * @param {string} teamId - The team id
* @returns {object} - Team object
*/
- async team(teamId){
- const team = this.#factory.team(teamId)
- const { allowedTypes=[], defaultTypes=[], type, } = team
- const teamBots = this.bots
- .filter(bot=>bot?.teams?.includes(teamId))
- for(const type of defaultTypes){
- let bot = teamBots.find(bot=>bot.type===type)
- if(!bot){
- bot = this.bots.find(bot=>bot.type===type)
- if(bot){ // local conscription
- bot.teams = [...bot?.teams ?? [], teamId,]
- await this.updateBot(bot) // save Cosmos no await
- } else { // create
- const teams = [teamId,]
- bot = await this.createBot({ teams, type, })
- }
- } else continue // already in team
- if(bot)
- teamBots.push(bot)
- }
- team.bots = teamBots
+ team(teamId){
+ this.#botAgent.setActiveTeam(teamId)
+ const team = this.#botAgent.activeTeam
return team
}
/**
@@ -762,18 +719,18 @@ class Avatar extends EventEmitter {
* @returns {Object[]} - List of team objects.
*/
teams(){
- const teams = this.#factory.teams()
+ const teams = this.#botAgent.teams
return teams
}
/**
- * Update a specific bot. **Note**: mBot() updates `this.bots`
+ * Update a specific bot.
* @async
- * @param {object} bot - Bot data to set.
- * @returns {object} - The updated bot.
+ * @param {object} botData - Bot data to set
+ * @returns {Promise} - The updated bot
*/
- async updateBot(bot){
- const updatedBot = await mBot(this.#factory, this, bot) // **note**: mBot() updates `avatar.bots`
- return updatedBot
+ async updateBot(botData){
+ const bot = await this.#botAgent.updateBot(botData)
+ return bot
}
/**
* Update instructions for bot-assistant based on type. Default updates all LLM pertinent properties.
@@ -800,7 +757,7 @@ class Avatar extends EventEmitter {
vectorstoreId,
}
/* save to && refresh bot from Cosmos */
- bot = mSanitize( await this.#factory.updateBot(_bot, options) )
+ bot = this.globals.sanitize( await this.#factory.updateBot(_bot, options) )
if(migrateThread && thread_id?.length)
await this.migrateChat(thread_id)
}
@@ -837,10 +794,7 @@ class Avatar extends EventEmitter {
* @returns {object} - The active bot.
*/
get activeBot(){
- return this.#bots.find(bot=>bot.id===this.activeBotId)
- }
- get activeBotAIId(){
- return this.activeBot.bot_id
+ return this.#botAgent.activeBot
}
/**
* Get the active bot id.
@@ -848,7 +802,7 @@ class Avatar extends EventEmitter {
* @returns {string} - The active bot id.
*/
get activeBotId(){
- return this.#activeBotId
+ return this.#botAgent.activeBotId
}
/**
* Set the active bot id. If not match found in bot list, then defaults back to this.id (avatar).
@@ -858,10 +812,7 @@ class Avatar extends EventEmitter {
* @returns {void}
*/
set activeBotId(botId){
- const newActiveBot = mFindBot(this, botId)
- ?? this.avatar
- const { id, } = newActiveBot
- this.#activeBotId = id
+ this.#botAgent.setActiveBot(botId)
}
get activeBotNewestVersion(){
const { type, } = this.activeBot
@@ -872,14 +823,6 @@ class Avatar extends EventEmitter {
const { version=1.0, } = this.activeBot
return version
}
- /**
- * Get actor or default avatar bot.
- * @getter
- * @returns {object} - The actor bot (or default bot).
- */
- get actorBot(){
- return this.#bots.find(_bot=>_bot.type==='actor')??this.avatar
- }
/**
* Get the age of the member.
* @getter
@@ -901,21 +844,13 @@ class Avatar extends EventEmitter {
}
return age
}
- /**
- * Returns provider for avatar intelligence.
- * @getter
- * @returns {object} - The avatar intelligence provider, currently only openAI API GPT.
- */
- get ai(){
- return this.#llmServices
- }
/**
* Get the personal avatar bot.
* @getter
- * @returns {object} - The personal avatar bot.
+ * @returns {object} - The personal avatar bot
*/
get avatar(){
- return this.bots.find(_bot=>_bot.type==='personal-avatar')
+ return this.#botAgent.avatar
}
/**
* Get the "avatar's" being, or more precisely the name of the being (affiliated object) the evatar is emulating.
@@ -926,9 +861,6 @@ class Avatar extends EventEmitter {
get being(){
return 'human'
}
- get biographer(){
- return this.#bots.find(_bot=>_bot.type==='personal-biographer')
- }
/**
* Get the birthdate of _member_ from `#factory`.
* @getter
@@ -950,12 +882,12 @@ class Avatar extends EventEmitter {
?? this.core.birth?.[0]?.place
}
/**
- * Gets all Avatar bots.
+ * Returns avatar Bot instances.
* @getter
- * @returns {array} - The bots.
+ * @returns {Bot[]} - Array of Bot instances
*/
get bots(){
- return this.#bots
+ return this.#botAgent.bots
}
/**
* Get the cast members in frontend format.
@@ -1007,7 +939,7 @@ class Avatar extends EventEmitter {
return this.#evolver
}
set evolver(evolver){
- if(!(evolver instanceof EvolutionAssistant))
+ if(!(evolver instanceof EvolutionAgent))
this.#evolver = evolver
}
/**
@@ -1263,19 +1195,6 @@ class Avatar extends EventEmitter {
if(nickname!==this.name)
this.#nickname = nickname
}
- get personalAssistant(){
- return this.avatar
- }
- /**
- * Get a list of available bots (pruned) for the member.
- * @getter
- * @returns {Object[]} - Array of pruned bot objects
- */
- get prunedBots(){
- const bots = this.#bots
- .map(bot=>mPruneBot(bot))
- return bots
- }
/**
* Get the `active` reliving memories.
* @getter
@@ -1311,6 +1230,11 @@ class Avatar extends EventEmitter {
this.#vectorstoreId = vectorstoreId /* update local */
}
}
+/**
+ * The System Avatar singleton for MyLife.
+ * @class
+ * @extends Avatar
+ */
class Q extends Avatar {
#conversations = []
#factory // same reference as Avatar, but wish to keep private from public interface; don't touch my factory, man!
@@ -1330,17 +1254,6 @@ class Q extends Avatar {
this.llmServices = llmServices
}
/* overloaded methods */
- /**
- * OVERLOADED: Get a bot's properties from Cosmos (or type in .bots).
- * @public
- * @async
- * @param {string} mbr_id - The bot id
- * @returns {object} - The hydrated member avatar bot
- */
- async bot(mbr_id){
- const bot = await this.#factory.bot(mbr_id)
- return bot
- }
/**
* OVERLOADED: Processes and executes incoming chat request.
* @public
@@ -1352,21 +1265,32 @@ class Q extends Avatar {
* @returns {object} - The response(s) to the chat request
*/
async chat(message, itemId, shadowId, processStartTime=Date.now(), session){
- let { thread_id, } = session
- if(!thread_id?.length){
- const conversation = await this.createConversation('system')
- thread_id = conversation.thread_id
- this.#conversations.push(conversation)
+ if(itemId?.length || shadowId?.length)
+ throw new Error('MyLife System Avatar cannot process chats with `itemId` or `shadowId`.')
+ let { Conversation, } = session
+ if(!Conversation){
+ const Conversation = await this.conversationStart('chat', 'system-avatar')
+ thread_id = Conversation.thread_id
+ this.#conversations.push(Conversation)
}
- this.activeBot.bot_id = mBot_idOverride
- ?? this.activeBot.bot_id
- session.thread_id = thread_id // @stub - store elsewhere
+ session.Conversation = Conversation // @stub - store elsewhere
if(this.isValidating) // trigger confirmation until session (or vld) ends
message = `CONFIRM REGISTRATION PHASE: registrationId=${ this.registrationId }\n${ message }`
if(this.isCreatingAccount)
message = `CREATE ACCOUNT PHASE: ${ message }`
+
+
+
return super.chat(message, itemId, shadowId, processStartTime, null, thread_id)
}
+ /**
+ * OVERLOADED: MyLife must refuse to create bots.
+ * @public
+ * @throws {Error} - System avatar cannot create bots.
+ */
+ async createBot(){
+ throw new Error('System avatar cannot create bots.')
+ }
/**
* OVERLOADED: Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM. In this overload, we invoke a micro-avatar for the member to handle the request on their behalf, with charge-backs going to MyLife as the sharing and api is a service.
* @public
@@ -1375,7 +1299,7 @@ class Q extends Avatar {
* @returns {Object} - The obscured item object
*/
async obscure(mbr_id, iid){
- const botFactory = await this.bot(mbr_id)
+ const botFactory = await this.avatarProxy(mbr_id)
const updatedSummary = await botFactory.obscure(iid)
return updatedSummary
}
@@ -1406,6 +1330,20 @@ class Q extends Avatar {
}
}
}
+ /**
+ * Returns the Member Avatar proxy for the member id.
+ * @param {string} mbr_id - The member id
+ * @returns {Promise} - The Member Avatar proxy
+ */
+ async avatarProxy(mbr_id){
+ const avatar = await this.#factory.avatarProxy(mbr_id)
+ return avatar
+ }
+ async challengeAccess(memberId, passphrase){
+ const avatarProxy = await this.avatarProxy(memberId)
+ const challengeSuccessful = await avatarProxy.challengeAccess(passphrase)
+ return challengeSuccessful
+ }
/**
* Set MyLife core account basics. { birthdate, passphrase, }
* @todo - move to mylife agent factory
@@ -1503,72 +1441,6 @@ function mAvatarDropdown(globals, avatar){
name,
}
}
-/**
- * Validates and cleans bot object then updates or creates bot (defaults to new personal-avatar) in Cosmos and returns successful `bot` object, complete with conversation (including thread/thread_id in avatar) and gpt-assistant intelligence.
- * @todo Fix occasions where there will be no object_id property to use, as it was created through a hydration method based on API usage, so will be attached to mbr_id, but NOT avatar.id
- * @todo - Turn this into Bot class
- * @module
- * @param {AgentFactory} factory - Agent Factory object
- * @param {Avatar} avatar - Avatar object that will govern bot
- * @param {object} bot - Bot object
- * @returns {object} - Bot object
- */
-async function mBot(factory, avatar, bot){
- /* validation */
- const { id: avatarId, mbr_id, vectorstore_id, } = avatar
- const { newGuid, } = factory
- const { id: botId=newGuid, object_id: objectId, type: botType, } = bot
- if(!botType?.length)
- throw new Error('Bot type required to create.')
- bot.mbr_id = mbr_id /* constant */
- bot.object_id = objectId
- ?? avatarId /* all your bots belong to me */
- bot.id = botId // **note**: _this_ is a Cosmos id, not an openAI id
- let originBot = avatar.bots.find(oBot=>oBot.id===botId)
- if(originBot){ /* update bot */
- const options = {}
- const updatedBot = Object.keys(bot)
- .reduce((diff, key) => {
- if(bot[key]!==originBot[key])
- diff[key] = bot[key]
- return diff
- }, {})
- /* create or update bot special properties */
- const { thread_id, type, } = originBot // @stub - `bot_id` cannot be updated through this mechanic
- if(!thread_id?.length && !avatar.isMyLife){
- const excludeTypes = ['collection', 'library', 'custom'] // @stub - custom mechanic?
- if(!excludeTypes.includes(type)){
- const conversation = avatar.conversation(null, botId)
- ?? await avatar.createConversation('chat', null, botId)
- updatedBot.thread_id = conversation.thread_id // triggers `factory.updateBot()`
- console.log('Avatar::mBot::conversation created given NO thread_id', updatedBot.thread_id, conversation.inspect(true))
- }
- }
- let updatedOriginBot
- if(Object.keys(updatedBot).length){
- updatedOriginBot = {...originBot, ...updatedBot} // consolidated update
- const { bot_id, id, } = updatedOriginBot
- updatedBot.bot_id = bot_id
- updatedBot.id = id
- updatedBot.type = type
- const { interests, } = updatedBot
- /* set options */
- if(interests?.length){
- options.instructions = true
- options.model = true
- options.tools = false /* tools not updated through this mechanic */
- }
- updatedOriginBot = await factory.updateBot(updatedBot, options)
- }
- originBot = mSanitize(updatedOriginBot ?? originBot)
- avatar.bots[avatar.bots.findIndex(oBot=>oBot.id===botId)] = originBot
- } else { /* create assistant */
- bot = mSanitize( await factory.createBot(bot, vectorstore_id) )
- avatar.bots.push(bot)
- }
- return originBot
- ?? bot
-}
/**
* Makes call to LLM and to return response(s) to prompt.
* @todo - create actor-bot for internal chat? Concern is that API-assistants are only a storage vehicle, ergo not an embedded fine tune as I thought (i.e., there still may be room for new fine-tuning exercise); i.e., micro-instructionsets need to be developed for most. Unclear if direct thread/message instructions override or ADD, could check documentation or gpt, but...
@@ -1648,7 +1520,7 @@ async function mCast(factory, cast){
}
function mCreateSystemMessage(activeBot, message, factory){
if(!(message instanceof factory.message)){
- const { thread_id, } = activeBot
+ const { id: botId, thread_id, } = activeBot
const content = message?.content ?? message?.message ?? message
message = new (factory.message)({
being: 'message',
@@ -1658,32 +1530,9 @@ function mCreateSystemMessage(activeBot, message, factory){
type: 'system'
})
}
- message = mPruneMessage(activeBot, message, 'system')
+ message = mPruneMessage(botId, message, 'system')
return message
}
-/**
- * Deletes the bot requested from avatar memory and from all long-term storage.
- * @param {object} bot - The bot object to delete
- * @param {Object[]} bots - The bots array
- * @param {LLMServices} llm - OpenAI object
- * @param {AgentFactory} factory - Agent Factory object
- */
-function mDeleteBot(bot, bots, llm, factory){
- const cannotRetire = ['actor', 'system', 'personal-avatar']
- const { bot_id, id, thread_id, type, } = bot
- if(cannotRetire.includes(type))
- throw new Error(`Cannot retire bot type: ${ type }`)
- /* delete from memory */
- const botId = bots.findIndex(_bot=>_bot.id===id)
- if(botId<0)
- throw new Error('Bot not found in bots.')
- bots.splice(botId, 1)
- /* delete bot from Cosmos */
- factory.deleteItem(id)
- /* delete thread and bot from OpenAI */
- llm.deleteBot(bot_id)
- llm.deleteThread(thread_id)
-}
/**
* Deletes conversation and updates
* @param {Conversation} conversation - The conversation object
@@ -2175,48 +2024,6 @@ function mFindBot(avatar, id){
.filter(bot=>{ return bot.id==id })
?.[0]
}
-/**
- * Returns set of Greeting messages, dynamic or static
- * @param {object} bot - The bot object
- * @param {boolean} dynamic - Whether to use dynamic greetings
- * @param {LLMServices} llm - OpenAI object
- * @param {AgentFactory} factory - Agent Factory object
- * @returns {Promise} - The array of messages to respond with
- */
-async function mGreeting(bot, dynamic=false, llm, factory){
- const processStartTime = Date.now()
- const { bot_id, bot_name, id, greetings, greeting, thread_id, } = bot
- const failGreeting = [`Hello! I'm concerned that there is something wrong with my instruction-set, as I was unable to find my greetings, but let's see if I can get back online.`, `How can I be of help today?`]
- const greetingPrompt = factory.isMyLife
- ? `Greet this new user with a hearty hello, and let them know that you are here to help them understand MyLife and the MyLife platform. Begin by asking them about something that's important to them--based on their response, explain how MyLife can help them.`
- : `Greet me with a hearty hello as we start a new session, and let me know either where we left off, or how we should start for today!`
- const QGreetings = [
- `Hi, I'm Q, so nice to meet you!`,
- `To get started, tell me a little bit about something or someone that is really important to you — or ask me a question about MyLife.`
- ]
- const botGreetings = greetings
- ? greetings
- : greeting
- ? [greeting]
- : factory.isMyLife
- ? QGreetings
- : null
- let messages = botGreetings?.length && !dynamic
- ? botGreetings
- : await llm.getLLMResponse(thread_id, bot_id, greetingPrompt, factory)
- if(!messages?.length)
- messages = failGreeting
- messages = messages
- .map(message=>new (factory.message)({
- being: 'message',
- content: message,
- thread_id,
- role: 'assistant',
- type: 'greeting'
- }))
- .map(message=>mPruneMessage(bot, message, 'greeting', processStartTime))
- return messages
-}
/**
* Include help preamble to _LLM_ request, not outbound to member/guest.
* @todo - expand to include other types of help requests, perhaps more validation.
@@ -2244,68 +2051,33 @@ function mHelpIncludePreamble(type, isMyLife){
* @param {MyLifeFactory|AgentFactory} factory - Member Avatar or Q
* @param {LLMServices} llmServices - OpenAI object
* @param {Q|Avatar} avatar - The avatar Instance (`this`)
- * @param {array} bots - The array of bot objects from private class `this.#bots`
+ * @param {BotAgent} botAgent - BotAgent instance
* @param {AssetAgent} assetAgent - AssetAgent instance
* @returns {Promise} - Return indicates successfully mutated avatar
*/
-async function mInit(factory, llmServices, avatar, bots, assetAgent){
- /* get avatar data from cosmos */
- const obj = await factory.avatarProperties()
- Object.entries(obj)
- .forEach(([key, value])=>{
- if( // exclude certain properties
- ['being', 'mbr_id'].includes(key)
- || ['$', '_', ' ', '@', '#',].includes(key[0])
- )
- return
- avatar[key] = value
- })
- const requiredBotTypes = ['personal-avatar',]
- if(factory.isMyLife){ // MyLife
- avatar.nickname = 'Q'
- } else { // Member
+async function mInit(factory, llmServices, avatar, botAgent, assetAgent){
+ /* initial assignments */
+ const { being, mbr_id, ...avatarProperties } = factory.globals.sanitize(await factory.avatarProperties())
+ Object.assign(avatar, avatarProperties)
+ if(!factory.isMyLife){
const { mbr_id, vectorstore_id, } = avatar
avatar.nickname = avatar.nickname
?? avatar.names?.[0]
- ?? `${avatar.memberFirstName ?? 'member'}'s avatar`
- /* vectorstore */
+ ?? `${ avatar.memberFirstName ?? 'member' }'s avatar`
if(!vectorstore_id){
const vectorstore = await llmServices.createVectorstore(mbr_id)
if(vectorstore?.id){
- avatar.vectorstore_id = vectorstore.id // also sets vectorstore_id in Cosmos
+ avatar.vectorstore_id = vectorstore.id
await assetAgent.init(avatar.vectorstore_id)
}
}
- /* bots */
- requiredBotTypes.push('personal-biographer') // default memory team
- }
- bots.push(...await factory.bots(avatar.id))
- await Promise.all(
- requiredBotTypes
- .map(async botType=>{
- if(!bots.some(bot=>bot.type===botType)){ // create required bot
- const bot = await mBot(factory, avatar, { type: botType })
- bots.push(bot)
- }
- }
- ))
- avatar.activeBotId = avatar.avatar.id // initially set active bot to personal-avatar
- if(factory.isMyLife) // as far as init goes for MyLife Avatar
+ }
+ /* initialize default bots */
+ await botAgent.init(avatar.id, avatar.vectorstore_id)
+ if(factory.isMyLife)
return
- /* conversations */
- await Promise.all(
- bots.map(async bot=>{
- const { id: botId, thread_id, type, } = bot
- /* exempt certain types */
- const excludedMemberTypes = ['library', 'ubi']
- if(excludedMemberTypes.includes(type))
- return
- const conversation = await avatar.createConversation('chat', thread_id, botId)
- bot.conversation = conversation
- })
- )
/* evolver */
- avatar.evolver = await (new EvolutionAssistant(avatar))
+ avatar.evolver = await (new EvolutionAgent(avatar))
.init()
/* lived-experiences */
avatar.experiencesLived = await factory.experiencesLived(false)
@@ -2453,37 +2225,13 @@ function mNavigation(scenes){
return (a.order ?? 0) - (b.order ?? 0)
})
}
-/**
- * Returns a frontend-ready bot object.
- * @param {object} bot - The bot object.
- * @returns {object} - The pruned bot object.
- */
-function mPruneBot(bot){
- const {
- bot_name: name,
- description,
- id,
- purpose,
- type,
- version,
- } = bot
- return {
- description,
- id,
- name,
- purpose,
- type,
- version,
- }
-}
function mPruneConversation(conversation){
- const { bot_id, form, id, name, thread_id, type, } = conversation
+ const { bot_id, form, id, name, type, } = conversation
return {
bot_id,
form,
id,
name,
- thread_id,
type,
}
}
@@ -2513,20 +2261,19 @@ function mPruneItem(item){
* Returns frontend-ready Message object after logic mutation.
* @module
* @private
- * @param {object} bot - The bot object, usually active.
- * @param {string} message - The text of LLM message. Can parse array of messages from openAI.
- * @param {string} type - The type of message, defaults to chat.
- * @param {number} processStartTime - The time the process started, defaults to function call.
- * @returns {object} - The bot-included message object.
+ * @param {Guid} activeBotId - The Active Bot id property
+ * @param {string} message - The text of LLM message; can parse array of messages from openAI
+ * @param {string} type - The type of message, defaults to chat
+ * @param {number} processStartTime - The time the process started, defaults to function call
+ * @returns {object} - The pruned message object
*/
-function mPruneMessage(bot, message, type='chat', processStartTime=Date.now()){
+function mPruneMessage(activeBotId, message, type='chat', processStartTime=Date.now()){
/* parse message */
- const { bot_id: activeBotAIId, id: activeBotId, } = bot
let agent='server',
content='',
purpose=type,
response_time=Date.now()-processStartTime
- const { content: messageContent, thread_id, } = message
+ const { content: messageContent, } = message
const rSource = /【.*?\】/gs
const rLines = /\n{2,}/g
content = Array.isArray(messageContent)
@@ -2542,37 +2289,28 @@ function mPruneMessage(bot, message, type='chat', processStartTime=Date.now()){
message = new Marked().parse(content)
const messageResponse = {
activeBotId,
- activeBotAIId,
agent,
message,
purpose,
response_time,
- thread_id,
type,
}
return messageResponse
}
/**
- * Flattens an array of messages into a single frontend-consumable message.
- * @param {object} bot - The bot object, usually active.
- * @param {Object[]} messages - The array of messages to prune.
- * @param {string} type - The type of message, defaults to chat.
- * @param {number} processStartTime - The time the process started, defaults to function call.
- * @returns {object} - Concatenated message object.
+ * Prune an array of Messages and return.
+ * @param {Guid} botId - The Active Bot id property
+ * @param {Object[]} messageArray - The array of messages to prune
+ * @param {string} type - The type of message, defaults to chat
+ * @param {number} processStartTime - The time the process started, defaults to function call
+ * @returns {Object[]} - Concatenated message object
*/
-function mPruneMessages(bot, messageArray, type='chat', processStartTime=Date.now()){
+function mPruneMessages(botId, messageArray, type='chat', processStartTime=Date.now()){
if(!messageArray.length)
throw new Error('No messages to prune')
- const prunedMessages = messageArray
- .map(message=>mPruneMessage(bot, message, type, processStartTime))
- const messageContent = prunedMessages
- .map(message=>message.message)
- .join('\n')
- const message = {
- ...prunedMessages[0],
- message: messageContent,
- }
- return message
+ messageArray = messageArray
+ .map(message=>mPruneMessage(botId, message, type, processStartTime))
+ return messageArray
}
/**
* Returns a narration packet for a memory reliving. Will allow for and accommodate the incorporation of helpful data _from_ the avatar member into the memory item `summary` and other metadata. The bot by default will:
@@ -2584,14 +2322,15 @@ function mPruneMessages(bot, messageArray, type='chat', processStartTime=Date.no
* @param {Avatar} avatar - Member's avatar object.
* @param {AgentFactory} factory - Member's AgentFactory object.
* @param {LLMServices} llm - OpenAI object.
- * @param {object} bot - The bot object.
+ * @param {Bot} Bot - The relevant bot instance
* @param {object} item - The memory object.
* @param {string} memberInput - The member input (or simply: NEXT, SKIP, etc.)
* @returns {Promise} - The reliving memory object for frontend to execute.
*/
-async function mReliveMemoryNarration(avatar, factory, llm, bot, item, memberInput='NEXT'){
+async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInput='NEXT'){
console.log('mReliveMemoryNarration::start', item.id, memberInput)
const { relivingMemories, } = avatar
+ const { bot, } = Bot
const { bot_id, id: botId, } = bot
const { id, } = item
const processStartTime = Date.now()
@@ -2623,7 +2362,7 @@ async function mReliveMemoryNarration(avatar, factory, llm, bot, item, memberInp
&& message.type==='chat'
&& message.role!=='user'
})
- .map(message=>mPruneMessage(bot, message, 'chat', processStartTime))
+ .map(message=>mPruneMessage(botId, message, 'chat', processStartTime))
const memory = {
id,
messages,
@@ -2651,19 +2390,6 @@ function mReplaceVariables(prompt, variableList, variableValues){
})
return prompt
}
-/**
- * Takes an object and removes MyLife database fields unintended for external observance.
- * @param {object} obj - Object to sanitize.
- * @returns {object} - Sanitized object.
- */
-function mSanitize(obj){
- const removalCharacters = ['_', '$']
- for(const key in obj){
- if(removalCharacters.includes(key[0]))
- delete obj[key]
- }
- return obj
-}
/**
* Returns a sanitized event.
* @module
diff --git a/inc/js/mylife-data-service.js b/inc/js/mylife-dataservices.mjs
similarity index 95%
rename from inc/js/mylife-data-service.js
rename to inc/js/mylife-dataservices.mjs
index dad8d86a..ba3c7e0f 100644
--- a/inc/js/mylife-data-service.js
+++ b/inc/js/mylife-dataservices.mjs
@@ -288,15 +288,12 @@ class Dataservices {
const { id, type, } = bot
if(!type?.length)
throw new Error('ERROR::createBot::Bot `type` required.')
- if(!id?.length)
+ if(!this.globals.isValidGuid(id))
bot.id = this.globals.newGuid
- bot.being = 'bot' // no mods
+ bot.being = 'bot'
/* create bot */
return await this.pushItem(bot)
}
- async datacore(mbr_id){
- return await this.getItem(mbr_id)
- }
/**
* Delete an item from member container.
* @async
@@ -447,16 +444,16 @@ class Dataservices {
* @async
* @public
* @param {string} being
- * @param {string} _field - Field name to match.
- * @param {string} _value - Value to match.
+ * @param {string} field - Field name to match.
+ * @param {string} value - Value to match.
* @param {string} container_id - The container name to use, overriding default.
* @param {string} _mbr_id - The member id to use, overriding default.
* @returns {Promise} An object (initial of arrau) matching the query parameters.
*/
- async getItemByField(being, _field, _value, container_id, _mbr_id=this.mbr_id){
+ async getItemByField(being, field, value, container_id, _mbr_id=this.mbr_id){
const _item = await this.getItemByFields(
being,
- [{ name: `@${_field}`, value: _value }],
+ [{ name: `@${field}`, value: value }],
container_id,
_mbr_id,
)
@@ -467,15 +464,15 @@ class Dataservices {
* @async
* @public
* @param {string} being
- * @param {array} _fields - Array of name/value pairs to select, format: [{name: `@${_field}`, value: _value}],
+ * @param {array} fields - Array of name/value pairs to select, format: [{name: `@${field}`, value: value}],
* @param {string} container_id - The container name to use, overriding default.
* @param {string} _mbr_id - The member id to use, overriding default.
* @returns {Promise} An object (initial of arrau) matching the query parameters.
*/
- async getItemByFields(being, _fields, container_id, _mbr_id=this.mbr_id){
+ async getItemByFields(being, fields, container_id, _mbr_id=this.mbr_id){
const _items = await this.getItemsByFields(
being,
- _fields,
+ fields,
container_id,
_mbr_id,
)
@@ -526,16 +523,16 @@ class Dataservices {
* @async
* @public
* @param {string} being - The type of items to retrieve.
- * @param {array} _fields - Array of name/value pairs to select, format: [{name: `@${_field}`, value: _value}],
+ * @param {array} fields - Array of name/value pairs to select, format: [{name: `@${field}`, value: value}],
* @param {string} container_id - The container name to use, overriding default.
* @param {string} _mbr_id - The member id to use, overriding default.
* @returns {Promise} An array of items matching the query parameters.
*/
- async getItemsByFields(being, _fields, container_id, _mbr_id=this.mbr_id){
+ async getItemsByFields(being, fields, container_id, _mbr_id=this.mbr_id){
const _items = await this.getItems(
being,
undefined,
- _fields,
+ fields,
container_id,
_mbr_id,
)
@@ -652,15 +649,22 @@ class Dataservices {
return await this.datamanager.testPartitionKey(mbr_id)
}
/**
- * Sets a bot in the database. Performs logic to reduce the bot to the minimum required data, as Mongo/Cosmos has a limitation of 10 patch items in one batch array.
- * @param {object} bot - The bot object to set.
- * @returns {object} - The bot object.
+ * Sets a bot in the database.
+ * @param {object} botData - The bot data to update
+ * @returns {object} - The bot document
*/
- async updateBot(bot){
- const { id, type: discardType, ...botUpdates } = bot
- if(!Object.keys(botUpdates))
- return bot
- return await this.patch(bot.id, botUpdates)
+ async updateBot(botData){
+ const { id, type: discardType, ...updateBotData } = botData
+ if(!Object.keys(updateBotData))
+ return botData
+ const chunks = this.globals.chunkArray(updateKeys, 10)
+ for(const chunk of chunks){
+ const chunkData = {}
+ chunk.forEach(key=>(chunkData[key] = updateBotData[key]))
+ const patchedData = await this.patch(id, chunkData)
+ console.log('updateBot()::patch', patchedData)
+ }
+ return botData
}
/**
* Returns the registration record by Id.
diff --git a/inc/js/mylife-agent-factory.mjs b/inc/js/mylife-factory.mjs
similarity index 74%
rename from inc/js/mylife-agent-factory.mjs
rename to inc/js/mylife-factory.mjs
index 6f37e2bd..4cf60479 100644
--- a/inc/js/mylife-agent-factory.mjs
+++ b/inc/js/mylife-factory.mjs
@@ -5,7 +5,7 @@ import vm from 'vm'
import util from 'util'
import { Guid } from 'js-guid' // usage = Guid.newGuid().toString()
import { Avatar, Q, } from './mylife-avatar.mjs'
-import Dataservices from './mylife-data-service.js'
+import Dataservices from './mylife-dataservices.mjs'
import { Member, MyLife } from './core.mjs'
import {
extendClass_consent,
@@ -42,78 +42,6 @@ const mExcludeProperties = {
}
const mGeneralBotId = 'asst_yhX5mohHmZTXNIH55FX2BR1m'
const mLLMServices = new LLMServices()
-const mMyLifeTeams = [
- {
- active: false,
- allowCustom: true,
- allowedTypes: ['artworks', 'editor', 'idea', 'marketing'],
- defaultTypes: ['artworks', 'idea',],
- description: 'The Creative Team is dedicated to help you experience productive creativity sessions.',
- id: '84aa50ca-fb64-43d8-b140-31d2373f3cd2',
- name: 'creative',
- title: 'Creative',
- },
- {
- active: false,
- allowCustom: false,
- allowedTypes: ['fitness', 'health', 'insurance', 'medical', 'prescriptions', 'yoga', 'nutrition',],
- defaultTypes: ['fitness', 'health', 'medical',],
- description: 'The Health Team is dedicated to help you manage your health and wellness.',
- id: '238da931-4c25-4868-928f-5ad1087a990b',
- name: 'health',
- title: 'Health',
- },
- {
- active: true,
- allowCustom: true,
- allowedTypes: ['diary', 'journaler', 'personal-biographer',],
- defaultTypes: ['personal-biographer',],
- description: 'The Memory Team is dedicated to help you document your life stories, experiences, thoughts, and feelings.',
- id: 'a261651e-51b3-44ec-a081-a8283b70369d',
- name: 'memory',
- title: 'Memory',
- },
- {
- active: false,
- allowCustom: true,
- allowedTypes: ['personal-assistant', 'idea', 'note', 'resume', 'scheduler', 'task',],
- defaultTypes: ['personal-assistant', 'resume', 'scheduler',],
- description: 'The Professional Team is dedicated to your professional success.',
- id: '5b7c4109-4985-4d98-b59b-e2c821c3ea28',
- name: 'professional',
- title: 'Professional',
- },
- {
- active: false,
- allowCustom: true,
- allowedTypes: ['connection', 'experience', 'social', 'relationship',],
- defaultTypes: ['connection', 'relationship',],
- description: 'The Social Team is dedicated to help you connect with others.',
- id: 'e8b1f6d0-8a3b-4f9b-9e6a-4e3c5b7e3e9f',
- name: 'social',
- title: 'Social',
- },
- {
- active: false,
- allowCustom: true,
- allowedTypes: ['note', 'poem', 'quote', 'religion',],
- defaultTypes: ['quote', 'religion',],
- description: 'The Spirituality Team is dedicated to help you creatively explore your spiritual side.',
- id: 'bea7bb4a-a339-4026-ad1c-75f604dc3349',
- name: 'sprituality',
- title: 'Spirituality',
- },
- {
- active: false,
- allowCustom: true,
- allowedTypes: ['data-ownership', 'investment', 'ubi',],
- defaultTypes: ['ubi'],
- description: 'The Universal Basic Income (UBI) Team is dedicated to helping you tailor your MyLife revenue streams based upon consensual access to your personal MyLife data.',
- id: '8a4d7340-ac62-40f1-8c77-f17c68797925',
- name: 'ubi',
- title: 'UBI',
- }
-]
const mNewGuid = ()=>Guid.newGuid().toString()
const mPath = './inc/json-schemas'
const mReservedJSCharacters = [' ', '-', '!', '@', '#', '%', '^', '&', '*', '(', ')', '+', '=', '{', '}', '[', ']', '|', '\\', ':', ';', '"', "'", '<', '>', ',', '.', '?', '/', '~', '`']
@@ -242,26 +170,16 @@ class BotFactory extends EventEmitter{
return this
}
/**
- * Get a bot, either by id (when known) or bot-type (default=mDefaultBotType). If bot id is not found, then it cascades to the first entity of bot-type it finds, and if none found, and rules allow [in instances where there may be a violation of allowability], a new bot is created.
+ * Get a bot, either by id (when known) or bot-type (default=mDefaultBotType). If bot id is not found, then it cascades to the first entity of bot-type it finds.
* If caller is `MyLife` then bot is found or created and then activated via a micro-hydration.
* @todo - determine if spotlight-bot is required for any animation, or a micro-hydrated bot is sufficient.
* @public
- * @param {string} id - The bot id.
- * @param {string} type - The bot type.
- * @param {string} mbr_id - The member id.
+ * @param {string} id - The bot id
+ * @param {string} type - The bot type
+ * @param {string} mbr_id - The member id
* @returns {object} - The bot.
*/
async bot(id, type=mDefaultBotType, mbr_id){
- if(this.isMyLife){
- if(!mbr_id)
- throw new Error('mbr_id required for BotFactory hydration')
- const botFactory = await new BotFactory(mbr_id)
- .init()
- botFactory.bot = await botFactory.bot(id, type, mbr_id)
- if(!botFactory?.bot) // create bot on member behalf
- botFactory.bot = await botFactory.createBot({ type: type })
- return botFactory
- }
return ( await this.dataservices.getItem(id) )
?? ( await this.dataservices.getItemByField(
'bot',
@@ -270,7 +188,6 @@ class BotFactory extends EventEmitter{
undefined,
mbr_id
) )
- ?? ( await this.bots(undefined,type)?.[0] )
}
/**
* Returns bot instruction set.
@@ -291,16 +208,16 @@ class BotFactory extends EventEmitter{
?? 1.0
}
/**
- * Gets a member's bots.
+ * Gets a member's bots, or specific bot types.
* @todo - develop bot class and implement hydrated instance
* @public
- * @param {string} object_id - The object_id guid of avatar
- * @param {string} botType - The bot type
- * @returns {array} - The member's hydrated bots
+ * @param {string} avatarId - The Avatarm id
+ * @param {string} botType - The bot type (optional)
+ * @returns {Object[]} - Array of bots
*/
- async bots(object_id, botType){
- const _params = object_id?.length
- ? [{ name: '@object_id', value:object_id }]
+ async bots(avatarId, botType){
+ const _params = avatarId?.length
+ ? [{ name: '@object_id', value: avatarId }]
: botType?.length
? [{ name: '@bot_type', value: botType }]
: undefined
@@ -332,15 +249,12 @@ class BotFactory extends EventEmitter{
return await this.dataservices.collections(type)
}
/**
- *
- * @param {object} botData - The assistant data.
- * @param {string} vectorstoreId - The vectorstore id.
- * @returns {object} - The created bot.
+ * Creates a bot in the database.
+ * @param {object} botData - The bot data
+ * @returns {object} - The created bot document
*/
- async createBot(botData={ type: mDefaultBotType }, vectorstoreId){
- const bot = await mCreateBot(this.#llmServices, this, botData, vectorstoreId)
- if(!bot)
- throw new Error('bot creation failed')
+ async createBot(botData){
+ const bot = await this.#dataservices.createBot(botData)
return bot
}
/**
@@ -468,35 +382,13 @@ class BotFactory extends EventEmitter{
)
}
/**
- * Gets a MyLife Team by id.
- * @param {Guid} teamId - The Team id
- * @returns {object} - The Team
+ * Updates bot data in the database.
+ * @param {object} botData - The bot data to update
+ * @returns {Promise} - the bot document from the database
*/
- team(teamId){
- let team = mMyLifeTeams
- .find(team=>team.id===teamId)
- team = mTeam(team)
- return team
- }
- /**
- * Retrieves list of MyLife Teams.
- * @returns {object[]} - The array of MyLife Teams
- */
- teams(){
- const teams = mMyLifeTeams
- .filter(team=>team.active ?? false)
- .map(team=>mTeam(team))
- return teams
- }
- /**
- * 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, vectorstoreId: string, }`.
- * @returns {object} - The Cosmos bot version.
- */
- async updateBot(bot, options={}){
- return await mUpdateBot(this, this.#llmServices, bot, options)
+ async updateBot(botData){
+ const bot = await this.#dataservices.updateBot(botData)
+ return bot
}
/* getters/setters */
/**
@@ -631,16 +523,6 @@ class AgentFactory extends BotFactory {
const response = await this.dataservices.pushItem(item)
return response
}
- async datacore(mbr_id){
- const core = await mDataservices.getItems(
- 'core',
- undefined,
- undefined,
- undefined,
- mbr_id,
- )
- return core?.[0]??{}
- }
/**
* Delete an item from member container.
* @param {Guid} id - The id of the item to delete.
@@ -925,15 +807,15 @@ class MyLifeFactory extends AgentFactory {
} // no init() for MyLife server
/* public functions */
/**
- * Overload for MyLifeFactory::bot() - Q is able to hydrate a bot instance on behalf of members.
+ * MyLife factory is able to hydrate a BotFactory instance of a Member Avatar.
* @public
* @param {string} mbr_id - The member id
* @returns {object} - The hydrated bot instance
*/
- async bot(mbr_id){
- const bot = await new BotFactory(mbr_id)
+ async avatarProxy(mbr_id){
+ const Bot = await new BotFactory(mbr_id)
.init()
- return bot
+ return Bot
}
/**
* Accesses Dataservices to challenge access to a member's account.
@@ -944,7 +826,7 @@ class MyLifeFactory extends AgentFactory {
*/
async challengeAccess(mbr_id, passphrase){
const caseInsensitive = true // MyLife server defaults to case-insensitive
- const avatarProxy = await this.bot(mbr_id)
+ const avatarProxy = await this.avatarProxy(mbr_id)
const challengeSuccessful = await avatarProxy.challengeAccess(passphrase, caseInsensitive)
return challengeSuccessful
}
@@ -1028,6 +910,16 @@ class MyLifeFactory extends AgentFactory {
createItem(){
throw new Error('MyLife server cannot create items')
}
+ /**
+ *
+ * @param {string} mbr_id - The member id
+ * @returns {object} - The member's core data
+ */
+ async datacore(mbr_id){
+ const core = ( await mDataservices.getItems('core', null, null, null, mbr_id) )
+ ?.[0]
+ return core
+ }
deleteItem(){
throw new Error('MyLife server cannot delete items')
}
@@ -1111,20 +1003,6 @@ class MyLifeFactory extends AgentFactory {
}
}
// private module functions
-/**
- * Initializes openAI assistant and returns associated `assistant` object.
- * @module
- * @param {LLMServices} llmServices - OpenAI object
- * @param {object} botData - The bot data object
- * @returns {object} - [OpenAI assistant object](https://platform.openai.com/docs/api-reference/assistants/object)
- */
-async function mAI_openai(llmServices, botData){
- const { bot_name, type, } = botData
- botData.name = bot_name
- ?? `_member_${ type }`
- const bot = await llmServices.createBot(botData)
- return bot
-}
function assignClassPropertyValues(propertyDefinition){
switch (true) {
case propertyDefinition?.const!==undefined: // constants
@@ -1180,191 +1058,6 @@ async function mConfigureSchemaPrototypes(){ // add required functionality as de
mSchemas[_className] = mExtendClass(mSchemas[_className])
}
}
-/**
- * Creates bot and returns associated `bot` object.
- * @module
- * @private
- * @param {LLMServices} llm - OpenAI object
- * @param {AgentFactory} factory - Agent Factory object
- * @param {object} botData - Bot object
- * @param {string} avatarId - Avatar id
- * @returns {string} - Bot assistant id in openAI
-*/
-async function mCreateBotLLM(llm, botData){
- const { id, thread_id, } = await mAI_openai(llm, botData)
- return {
- id,
- thread_id,
- }
-}
-/**
- * Creates bot and returns associated `bot` object.
- * @todo - botData.name = botDbName should not be required, push logic to `llm-services`
- * @module
- * @async
- * @private
- * @param {LLMServices} llm - LLMServices Object contains methods for interacting with OpenAI
- * @param {BotFactory} factory - BotFactory object
- * @param {object} bot - Bot object, must include `type` property.
- * @returns {object} - Bot object
-*/
-async function mCreateBot(llm, factory, bot, vectorstoreId){
- /* initial deconstructions */
- const {
- bot_name: botName,
- description: botDescription,
- name: botDbName,
- type,
- } = bot
- const { avatarId, } = factory
- /* validation */
- if(!avatarId)
- throw new Error('avatar id required to create bot')
- /* constants */
- const bot_name = botName
- ?? `My ${ type }`
- const description = botDescription
- ?? `I am a ${ type } for ${ factory.memberName }`
- const { instructions, version, } = mCreateBotInstructions(factory, bot)
- const model = process.env.OPENAI_MODEL_CORE_BOT
- ?? process.env.OPENAI_MODEL_CORE_AVATAR
- ?? 'gpt-4o'
- const name = botDbName
- ?? `bot_${ type }_${ avatarId }`
- const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
- const id = factory.newGuid
- const botData = {
- being: 'bot',
- bot_name,
- description,
- id,
- instructions,
- metadata: {
- externalId: id,
- version: version.toString(),
- },
- model,
- name,
- object_id: avatarId,
- provider: 'openai',
- purpose: description,
- tools,
- tool_resources,
- type,
- version,
- }
- /* create in LLM */
- const { id: botId, thread_id, } = await mCreateBotLLM(llm, botData) // create after as require model
- if(!botId)
- throw new Error('bot creation failed')
- /* create in MyLife datastore */
- botData.bot_id = botId
- botData.thread_id = thread_id
- const assistant = await factory.dataservices.createBot(botData)
- console.log(chalk.green(`bot created::${ type }`), assistant.thread_id, assistant.id, assistant.bot_id, assistant.bot_name )
- return assistant
-}
-/**
- * Returns MyLife-version of bot instructions.
- * @module
- * @private
- * @param {BotFactory} factory - Factory object
- * @param {object} bot - Bot object
- * @returns {object} - minor
- */
-function mCreateBotInstructions(factory, bot){
- if(typeof bot!=='object' || !bot.type?.length)
- throw new Error('bot object required, and requires `type` property')
- const { type=mDefaultBotType, } = bot
- let {
- instructions,
- limit=8000,
- version,
- } = factory.botInstructions(type) ?? {}
- if(!instructions) // @stub - custom must have instruction loophole
- throw new Error(`bot instructions not found for type: ${ type }`)
- let {
- general,
- purpose='',
- preamble='',
- prefix='',
- references=[],
- replacements=[],
- suffix='', // example: data privacy info
- voice='',
- } = instructions
- /* compile instructions */
- switch(type){
- case 'diary':
- instructions = purpose
- + preamble
- + prefix
- + general
- + suffix
- + voice
- break
- case 'personal-avatar':
- instructions = preamble
- + general
- break
- case 'journaler':
- case 'personal-biographer':
- instructions = preamble
- + purpose
- + prefix
- + general
- break
- default:
- instructions = general
- break
- }
- /* apply replacements */
- replacements.forEach(replacement=>{
- const placeholderRegExp = factory.globals.getRegExp(replacement.name, true)
- const replacementText = eval(`bot?.${replacement.replacement}`)
- ?? eval(`factory?.${replacement.replacement}`)
- ?? eval(`factory.core?.${replacement.replacement}`)
- ?? replacement?.default
- ?? '`unknown-value`'
- instructions = instructions.replace(placeholderRegExp, _=>replacementText)
- })
- /* apply references */
- references.forEach(_reference=>{
- const _referenceText = _reference.insert
- const replacementText = eval(`factory?.${_reference.value}`)
- ?? eval(`bot?.${_reference.value}`)
- ?? _reference.default
- ?? '`unknown-value`'
- switch(_reference.method ?? 'replace'){
- case 'append-hard':
- const _indexHard = instructions.indexOf(_referenceText)
- if (_indexHard !== -1) {
- instructions =
- instructions.slice(0, _indexHard + _referenceText.length)
- + '\n'
- + replacementText
- + instructions.slice(_indexHard + _referenceText.length)
- }
- break
- case 'append-soft':
- const _indexSoft = instructions.indexOf(_referenceText);
- if (_indexSoft !== -1) {
- instructions =
- instructions.slice(0, _indexSoft + _referenceText.length)
- + ' '
- + replacementText
- + instructions.slice(_indexSoft + _referenceText.length)
- }
- break
- case 'replace':
- default:
- instructions = instructions.replace(_referenceText, replacementText)
- break
- }
- })
- /* assess and validate limit */
- return { instructions, version, }
-}
function mExposedSchemas(factoryBlockedSchemas){
const _systemBlockedSchemas = ['dataservices','session']
return Object.keys(mSchemas)
@@ -1488,78 +1181,6 @@ function mGenerateClassFromSchema(_schema) {
const _class = mCompileClass(name, _classCode)
return _class
}
-/**
- * Retrieves any functions that need to be attached to the specific bot-type.
- * @todo - Move to llmServices and improve
- * @param {string} type - Type of bot.
- * @param {object} globals - Global functions for bot.
- * @param {string} vectorstoreId - Vectorstore id.
- * @returns {object} - OpenAI-ready object for functions { tools, tool_resources, }.
- */
-function mGetAIFunctions(type, globals, vectorstoreId){
- let includeSearch=false,
- tool_resources,
- tools = []
- switch(type){
- case 'assistant':
- case 'avatar':
- case 'personal-assistant':
- case 'personal-avatar':
- includeSearch = true
- break
- case 'biographer':
- case 'personal-biographer':
- tools.push(
- globals.getGPTJavascriptFunction('changeTitle'),
- globals.getGPTJavascriptFunction('getSummary'),
- globals.getGPTJavascriptFunction('storySummary'),
- globals.getGPTJavascriptFunction('updateSummary'),
- )
- includeSearch = true
- break
- case 'custom':
- includeSearch = true
- break
- case 'diary':
- case 'journaler':
- tools.push(
- globals.getGPTJavascriptFunction('changeTitle'),
- globals.getGPTJavascriptFunction('entrySummary'),
- globals.getGPTJavascriptFunction('getSummary'),
- globals.getGPTJavascriptFunction('obscure'),
- globals.getGPTJavascriptFunction('updateSummary'),
- )
- includeSearch = true
- break
- default:
- break
- }
- if(includeSearch){
- const { tool_resources: gptResources, tools: gptTools, } = mGetGPTResources(globals, 'file_search', vectorstoreId)
- tools.push(...gptTools)
- tool_resources = gptResources
- }
- return {
- tools,
- tool_resources,
- }
-}
-/**
- * Retrieves any tools and tool-resources that need to be attached to the specific bot-type.
- * @param {Globals} globals - Globals object.
- * @param {string} toolName - Name of tool.
- * @param {string} vectorstoreId - Vectorstore id.
- * @returns {object} - { tools, tool_resources, }.
- */
-function mGetGPTResources(globals, toolName, vectorstoreId){
- switch(toolName){
- case 'file_search':
- const { tools, tool_resources, } = globals.getGPTFileSearchToolStructure(vectorstoreId)
- return { tools, tool_resources, }
- default:
- throw new Error('tool name not recognized')
- }
-}
/**
* Take help request about MyLife and consults appropriate engine for response.
* @requires mLLMServices - equivalent of default MyLife dataservices/factory
@@ -1774,59 +1395,6 @@ function mTeam(team){
title,
}
}
-/**
- * Updates bot in Cosmos, and if necessary, in LLM.
- * @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, vectorstoreId: string, }
- * @returns
- */
-async function mUpdateBot(factory, llm, bot, options={}){
- /* constants */
- const {
- id, // no modifications
- instructions: removeInstructions,
- tools: removeTools,
- tool_resources: removeResources,
- type, // no modifications
- ...botData // extract member-driven bot data
- } = bot
- const {
- 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, 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, 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 OpenAI'), id, bot_id, updatedLLMFields)
- }
- const updatedBot = await factory.dataservices.updateBot(botData)
- return updatedBot
-}
/* final constructs relying on class and functions */
// server build: injects default factory into _server_ **MyLife** instance
const _MyLife = await new MyLife(
diff --git a/server.js b/server.js
index d5561936..e900c3a7 100644
--- a/server.js
+++ b/server.js
@@ -11,7 +11,7 @@ import serve from 'koa-static'
/* misc imports */
import chalk from 'chalk'
/* local service imports */
-import MyLife from './inc/js/mylife-agent-factory.mjs'
+import MyLife from './inc/js/mylife-factory.mjs'
/** variables **/
const version = '0.0.25'
const app = new Koa()
diff --git a/views/README.md b/views/README.md
index 93deeb6d..52fc9dd9 100644
--- a/views/README.md
+++ b/views/README.md
@@ -162,7 +162,7 @@ MyLife itself is an open-source project and, aside from LLM technologies at the
3. **Bot Functionality and Intelligence Management**
- The application features a sophisticated bot system, capable of creating and managing different types of bots like personal assistants, biographers, health bots, etc.
- - OpenAI's GPT-3 model is integrated for generating responses and interacting with users through bots, as observed in the `class-avatar-functions.mjs` and `mylife-agent-factory.mjs` files.
+ - OpenAI's GPT-3 model is integrated for generating responses and interacting with users through bots, as observed in the `mylife-avatar.mjs` and `mylife-factory.mjs` files.
4. **Session Management**
- Managed through the `MylifeMemberSession` class, handling user sessions, consents, and alerts.
From c7444ac02fb4d7642294f50d92037bfb9ff0ce2a Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Tue, 22 Oct 2024 23:16:31 -0400
Subject: [PATCH 24/56] 20241022 @Mookse - fixed activeBot default
---
inc/js/agents/system/bot-agent.mjs | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index a5634196..0d3ce1b7 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -225,7 +225,7 @@ class BotAgent {
* @param {Guid} botId - The Bot id
* @returns {void}
*/
- setActiveBot(botId){
+ setActiveBot(botId=this.avatar?.id){
const Bot = this.#bots.find(bot=>bot.id===botId)
if(Bot)
this.#activeBot = Bot
@@ -278,7 +278,8 @@ class BotAgent {
* @returns {Guid} - The active bot id
*/
get activeBotId(){
- return this.#activeBot.id
+ console.log('BotAgent::activeBotId', this.#activeBot, this)
+ return this.#activeBot?.id
}
/**
* Gets the primary avatar for Member.
@@ -286,8 +287,8 @@ class BotAgent {
* @returns {Bot} - The primary avatar Bot instance
*/
get avatar(){
- const bot = this.#bots.find(Bot=>Bot.isAvatar===true)
- return bot
+ const Bot = this.#bots.find(Bot=>Bot.isAvatar===true)
+ return Bot
}
/**
* Gets the Avatar id for whom this BotAgent is conscripted.
@@ -832,6 +833,7 @@ async function mInit(BotAgent, bots, factory, llm){
const { avatarId, vectorstoreId, } = BotAgent
bots.push(...await mInitBots(avatarId, vectorstoreId, factory, llm))
BotAgent.setActiveBot()
+ console.log('BotAgent::init', BotAgent.activeBot)
}
/**
* Initializes active bots based upon criteria.
From cf7400f52714ebbf1fe84fcde965cdd79357765b Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 00:38:03 -0400
Subject: [PATCH 25/56] 20241023 @Mookse - `chat()` - remove `shadowId`
parameter - remove Contribution - wip unstable
---
inc/js/agents/system/bot-agent.mjs | 114 +++++++--
.../class-contribution-functions.mjs | 0
.../class-extenders.mjs | 203 +++++++---------
inc/js/functions.mjs | 11 +-
inc/js/mylife-avatar.mjs | 219 +++++-------------
inc/js/mylife-factory.mjs | 2 -
inc/js/mylife-llm-services.mjs | 37 +--
views/assets/js/members.mjs | 3 +-
8 files changed, 272 insertions(+), 317 deletions(-)
rename inc/js/{factory-class-extenders => deprecated}/class-contribution-functions.mjs (100%)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index 0d3ce1b7..f6a6f317 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -1,3 +1,5 @@
+import LLMServices from "../../mylife-llm-services.mjs"
+
/* module constants */
const mBot_idOverride = process.env.OPENAI_MAHT_GPT_OVERRIDE
const mDefaultBotTypeArray = ['personal-avatar', 'avatar']
@@ -37,11 +39,31 @@ class Bot {
throw new Error('Bot database id required')
}
/* public functions */
- async chat(){
- console.log('Bot::chat', this.#conversation)
- // what should be returned? Responses? Conversation object?
- return this.#conversation
+ /**
+ * Chat with the active bot.
+ * @param {String} message - The member request
+ * @param {String} originalMessage - The original message
+ * @param {Boolean} allowSave - Whether to save the conversation, defaults to `true`
+ * @param {Number} processStartTime - The process start time
+ * @returns {Promise} - The Conversation instance updated with the chat exchange
+ */
+ async chat(message, originalMessage, allowSave=true, processStartTime=Date.now()){
+ if(this.isMyLife && !this.isAvatar)
+ throw new Error('Only Q, MyLife Corporate Intelligence, is available for non-member conversation.')
+ const { bot_id, id, thread_id, type, } = this
+ if(!this.#conversation)
+ this.#conversation = await mConversationStart('chat', type, id, thread_id, bot_id, this.#llm, this.#factory)
+ const Conversation = this.#conversation
+ Conversation.prompt = message
+ Conversation.originalPrompt = originalMessage
+ await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, this) // mutates Conversation
+ /* frontend mutations */
+ return Conversation
}
+ /**
+ * Retrieves `this` Bot instance.
+ * @returns {Bot} - The Bot instance
+ */
getBot(){
return this
}
@@ -190,8 +212,19 @@ class BotAgent {
throw new Error(`Bot not found with id: ${ botId }`)
await mBotDelete(Bot, this.#bots, this.#llm, this.#factory)
}
- async chat(){
- return this.activeBot.chat()
+ /**
+ * Chat with the active bot.
+ * @param {Conversation} Conversation - The Conversation instance
+ * @param {Boolean} allowSave - Whether to save the conversation, defaults to `true`
+ * @param {Q} q - The avatar id
+ * @returns {Promise} - The Conversation instance
+ */
+ async chat(Conversation, allowSave=true, q){
+ if(!Conversation)
+ throw new Error('Conversation instance required')
+ Conversation.processingStartTime
+ await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, q) // mutates Conversation
+ return Conversation
}
/**
* Initializes a conversation, currently only requested by System Avatar, but theoretically could be requested by any externally-facing Member Avatar as well. **note**: not in Q because it does not have a #botAgent yet.
@@ -200,8 +233,9 @@ class BotAgent {
* @returns {Promise} - The conversation object
*/
async conversationStart(type='chat', form='system-avatar'){
- const { bot_id, } = this.avatar
- const Conversation = await mConversationStart(type, form, null, bot_id, this.#llm, this.#factory)
+ const { bot_id: llm_id, id: bot_id, } = this.avatar
+ const Conversation = await mConversationStart(type, form, bot_id, null, llm_id, this.#llm, this.#factory)
+ console.log('BotAgent::conversationStart', Conversation.thread_id, Conversation.bot_id, Conversation.llm_id, Conversation.inspect(true))
return Conversation
}
/**
@@ -278,7 +312,6 @@ class BotAgent {
* @returns {Guid} - The active bot id
*/
get activeBotId(){
- console.log('BotAgent::activeBotId', this.#activeBot, this)
return this.#activeBot?.id
}
/**
@@ -697,9 +730,12 @@ async function mBotUpdate(factory, llm, bot, options={}){
botData.model = factory.globals.currentOpenAIBotModel
botData.id = id // validated
/* LLM updates */
- const { bot_id, bot_name: name, instructions, tools, } = botData
- if(bot_id?.length && (instructions || name || tools)){
+ const { bot_id, bot_name: name, instructions, llm_id, tools, } = botData
+ const llmId = llm_id
+ ?? bot_id
+ if(llmId?.length && (instructions || name || tools)){
botData.model = factory.globals.currentOpenAIBotModel // not dynamic
+ botData.llm_id = llmId
await llm.updateBot(botData)
const updatedLLMFields = Object.keys(botData)
.filter(key=>key!=='id' && key!=='bot_id') // strip mechanicals
@@ -708,6 +744,48 @@ async function mBotUpdate(factory, llm, bot, options={}){
const updatedBotData = await factory.updateBot(botData)
return updatedBotData
}
+/**
+ * Sends Conversation instance with prompts for LLM to process, updating the Conversation instance before returning `void`.
+ * @todo - create actor-bot for internal chat? Concern is that API-assistants are only a storage vehicle, ergo not an embedded fine tune as I thought (i.e., there still may be room for new fine-tuning exercise); i.e., micro-instructionsets need to be developed for most. Unclear if direct thread/message instructions override or ADD, could check documentation or gpt, but...
+ * @todo - would dynamic event dialog be handled more effectively with a callback routine function, I think so, and would still allow for avatar to vet, etc.
+ * @module
+ * @param {Conversation} Conversation - Conversation instance
+ * @param {boolean} allowSave - Whether to save the conversation, defaults to `true`
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object required for function execution
+ * @param {object} avatar - Avatar object
+ * @returns {Promise} - Alters Conversation instance by nature
+ */
+async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
+ const { llm_id, originalPrompt, processingStartTime=Date.now(), prompt, thread_id, } = Conversation
+ if(!llm_id?.length)
+ throw new Error('No `llm_id` intelligence id found in Conversation for `mCallLLM`.')
+ if(!thread_id?.length)
+ throw new Error('No `thread_id` found in Conversation for `mCallLLM`.')
+ if(!prompt?.length)
+ throw new Error('No `prompt` found in Conversation for `mCallLLM`.')
+ const botResponses = await llm.getLLMResponse(thread_id, llm_id, prompt, factory, avatar)
+ const run_id = botResponses?.[0]?.run_id
+ if(!run_id?.length)
+ return
+ Conversation.addRun(run_id)
+ botResponses
+ .filter(botResponse=>botResponse?.run_id===Conversation.run_id)
+ .sort((mA, mB)=>(mB.created_at-mA.created_at))
+ Conversation.addMessage({
+ content: prompt,
+ created_at: processingStartTime,
+ originalPrompt,
+ role: 'member',
+ run_id,
+ thread_id,
+ })
+ Conversation.addMessages(botResponses)
+ if(allowSave)
+ console.log('chat::allowSave=`true`', Conversation.message?.content?.substring(0,64))// Conversation.save()
+ else
+ console.log('chat::allowSave=`false`', Conversation.message?.content?.substring(0,64))
+}
/**
* Create a new conversation.
* @async
@@ -715,12 +793,14 @@ async function mBotUpdate(factory, llm, bot, options={}){
* @param {string} type - Type of conversation: chat, experience, dialog, inter-system, system, etc.; defaults to `chat`
* @param {string} form - Form of conversation: system-avatar, member-avatar, etc.; defaults to `system-avatar`
* @param {string} thread_id - The openai thread id
- * @param {string} botId - The bot id
+ * @param {string} llm_id - The id for the llm agent
+ * @param {LLMServices} llm - OpenAI object
+ * @param {AgentFactory} factory - Agent Factory object
* @returns {Conversation} - The conversation object
*/
-async function mConversationStart(form='system', type='chat', thread_id, llmAgentId, llm, factory){
+async function mConversationStart(type='chat', form='system', bot_id, thread_id, llm_id, llm, factory){
const { mbr_id, } = factory
- const thread = await llm.thread(thread_id)
+ const thread = await llm.thread(thread_id) // **note**: created here as to begin conversation/LLM independence, and to retain non-async nature of Constructor
const Conversation = new (factory.conversation)(
{
form,
@@ -728,8 +808,9 @@ async function mConversationStart(form='system', type='chat', thread_id, llmAgen
type,
},
factory,
- thread,
- llmAgentId
+ bot_id,
+ llm_id,
+ thread
)
return Conversation
}
@@ -833,7 +914,6 @@ async function mInit(BotAgent, bots, factory, llm){
const { avatarId, vectorstoreId, } = BotAgent
bots.push(...await mInitBots(avatarId, vectorstoreId, factory, llm))
BotAgent.setActiveBot()
- console.log('BotAgent::init', BotAgent.activeBot)
}
/**
* Initializes active bots based upon criteria.
diff --git a/inc/js/factory-class-extenders/class-contribution-functions.mjs b/inc/js/deprecated/class-contribution-functions.mjs
similarity index 100%
rename from inc/js/factory-class-extenders/class-contribution-functions.mjs
rename to inc/js/deprecated/class-contribution-functions.mjs
diff --git a/inc/js/factory-class-extenders/class-extenders.mjs b/inc/js/factory-class-extenders/class-extenders.mjs
index d6f9376e..19e7fa47 100644
--- a/inc/js/factory-class-extenders/class-extenders.mjs
+++ b/inc/js/factory-class-extenders/class-extenders.mjs
@@ -1,8 +1,3 @@
-import { EventEmitter } from 'events'
-import {
- mGetQuestions,
- mUpdateContribution,
-} from './class-contribution-functions.mjs'
import {
mSaveConversation,
} from './class-conversation-functions.mjs'
@@ -37,116 +32,45 @@ function extendClass_consent(originClass, referencesObject) {
}
return Consent
}
-/**
- * Extends the `Contribution` class.
- * @param {*} originClass - The class to extend.
- * @param {Object} referencesObject - The references to extend the class with, factory, llm, etc.
- * @returns {Contribution} - The `Contribution` extended class definition.
- */
-function extendClass_contribution(originClass, referencesObject) {
- class Contribution extends originClass {
- #emitter = new EventEmitter()
- #factory
- #llm = referencesObject?.openai
- constructor(_obj) {
- super(_obj)
- }
- /* public functions */
- /**
- * Initialize a contribution.
- * @async
- * @public
- * @param {object} _factory - The factory instance.
- */
- async init(_factory){
- this.#factory = _factory
- this.request.questions = await mGetQuestions(this, this.#llm) // generate question(s) from cosmos or openAI
- this.id = this.factory.newGuid
- this.status = 'prepared'
- this.emit('on-contribution-new',this)
- return this
- }
- async allow(_request){
- // todo: evolve in near future, but is currently only a pass-through with some basic structure alluding to future functionality
- return true
- }
- /**
- * Convenience proxy for emitter.
- * @param {string} _eventName - The event to emit.
- * @param {any} - The event(s) to emit.
- * @returns {void}
- */
- emit(_eventName, ...args){
- this.#emitter.emit(_eventName, ...args)
- }
- /**
- * Convenience proxy for listener.
- * @param {string} _eventName - The event to emit.
- * @param {function} _listener - The event listener functionality.
- * @returns {void}
- */
- on(_eventName, _listener){
- this.#emitter.on(_eventName, _listener)
- }
- /**
- * Updates `this` with incoming contribution data.
- * @param {object} _contribution - Contribution data to incorporate
- * @returns {void}
- */
- update(_contribution){
- mUpdateContribution(this, _contribution) // directly modifies `this`, no return
- }
- /* getters/setters */
- /**
- * Get the factory instance.
- * @returns {object} MyLife Factory instance
- */
- get factory(){
- return this.#factory
- }
- get memberView(){
- return this.inspect(true)
- }
- get openai(){
- return this.#llm
- }
- get questions(){
- return this?.questions??[]
- }
- /* private functions */
- }
- return Contribution
-}
/**
* Extends the `Conversation` class.
* @param {*} originClass - The class to extend.
* @param {Object} referencesObject - The references to extend the class with, factory, llm, etc.
* @returns {Conversation} - The `Conversation` extended class definition.
*/
-function extendClass_conversation(originClass, referencesObject) {
+function extendClass_conversation(originClass, referencesObject){
class Conversation extends originClass {
#bot_id
#factory
+ #id
+ #llm_id
#messages = []
+ #run_id
+ #runs = new Set()
#saved = false
#thread
#threads = new Set()
/**
- *
- * @param {Object} obj - The object to construct the conversation from.
- * @param {AgentFactory} factory - The factory instance.
- * @param {Object} thread - The thread instance.
- * @param {Guid} bot_id - The initial active bot id (can mutate)
+ * Constructor for Conversation class.
+ * @param {Object} obj - Data object for construction
+ * @param {AgentFactory} factory - The factory instance
+ * @param {Guid} bot_id - The initial active bot MyLife `id`
+ * @param {String} llm_id - The initial active LLM `id`
+ * @param {Object} thread - The related thread instance
+ * @returns {Conversation} - The constructed conversation instance
*/
- constructor(obj, factory, thread, bot_id){
+ constructor(obj, factory, bot_id, llm_id, thread){
super(obj)
this.#factory = factory
this.#thread = thread
+ this.#id = this.#factory.newGuid
if(factory.globals.isValidGuid(bot_id))
this.#bot_id = bot_id
+ if(llm_id?.length)
+ this.#llm_id = llm_id
this.form = this.form
- ?? 'system'
- this.name = `conversation_${this.#factory.mbr_id}`
+ ?? 'system-avatar'
+ this.name = `conversation_${ this.#factory.mbr_id }`
this.type = this.type
?? 'chat'
}
@@ -158,7 +82,7 @@ function extendClass_conversation(originClass, referencesObject) {
* @returns {Object[]} - The updated messages array.
*/
addMessage(message){
- console.log('class-extenders::addMessage', message)
+ console.log('class-extenders::addMessage', message?.content?.[0]?.text?.value ?? message)
const { id, } = message
if(this.#messages.find(message=>message.id===id))
return this.messages
@@ -177,12 +101,20 @@ function extendClass_conversation(originClass, referencesObject) {
* @returns {Object[]} - The updated messages array.
*/
addMessages(messages){
- const now = Date.now()
- messages
- .sort((mA, mB) => ( mA.created_at ?? now ) - ( mB.created_at ?? now ))
- .forEach(message => this.addMessage(message))
+ messages.forEach(message => this.addMessage(message))
return this.messages
}
+ /**
+ * Adds a run/execution/receipt id to the conversation archive.
+ * @param {String} run_id - The run id to add
+ * @returns {void}
+ */
+ addRun(run_id){
+ if(run_id?.length){
+ this.#runs.add(run_id)
+ this.#run_id = run_id
+ }
+ }
/**
* Adds a thread id to the conversation archive
* @param {string} thread_id - The thread id to add
@@ -194,13 +126,30 @@ function extendClass_conversation(originClass, referencesObject) {
/**
* Get the message by id, or defaults to last message added.
* @public
- * @param {Guid} messageId - The message id.
- * @returns {object} - The openai `message` object.
+ * @param {Guid} messageId - The message id
+ * @returns {Message} - The `Message` instance
*/
- async getMessage(messageId){
- return messageId && this.messages?.[0]?.id!==messageId
- ? this.messages.find(message=>message.id===messageId)
+ getMessage(messageId){
+ const Message = messageId?.length
+ ? this.getMessages().find(message=>message.id===messageId)
: this.message
+ return Message
+ }
+ /**
+ * Get the messages for the conversation.
+ * @public
+ * @param {boolean} agentOnly - Whether or not to get only agent messages
+ * @param {string} run_id - The run id to get messages for
+ * @param {string} thread_id - The thread id to get messages for (optional)
+ * @returns {Message[]} - The messages array
+ */
+ getMessages(agentOnly=true, run_id=this.run_id, thread_id){
+ let messages = thread_id?.length
+ ? this.#messages.filter(message=>message.thread_id===thread_id)
+ : this.#messages.filter(message=>message.run_id===run_id)
+ if(agentOnly)
+ messages = messages.filter(message => ['member', 'user'].indexOf(message.role) < 0)
+ return messages
}
/**
* Removes a thread id from the conversation archive
@@ -232,35 +181,57 @@ function extendClass_conversation(originClass, referencesObject) {
}
// public getters/setters
/**
- * Get the id {Guid} of the conversation's bot.
+ * Get the id {Guid} of the conversation's active bot.
* @getter
* @returns {Guid} - The bot id.
*/
- get botId(){
- return this.#bot_id
- }
get bot_id(){
return this.#bot_id
}
/**
- * Set the id {Guid} of the conversation's bot.
+ * Set the id {Guid} of the conversation's active bot.
* @setter
* @param {Guid} bot_id - The bot id.
* @returns {void}
*/
- set botId(bot_id){
- if(!this.#factory.globals.isValidGuid(bot_id))
- throw new Error(`Invalid bot_id: ${ bot_id }`)
- this.#bot_id = bot_id
- }
set bot_id(bot_id){
if(!this.#factory.globals.isValidGuid(bot_id))
throw new Error(`Invalid bot_id: ${ bot_id }`)
this.#bot_id = bot_id
}
+ /**
+ * Get the generated Guid `id` of the Conversation instance.
+ * @getter
+ * @returns {Guid} - The conversation id
+ */
+ get id(){
+ return this.#id
+ }
+ /**
+ * Whether or not the conversation has _ever_ been saved.
+ * @getter
+ * @returns {boolean} - Whether or not the conversation has _ever_ been saved
+ */
get isSaved(){
return this.#saved
}
+ /**
+ * Get the `id` {String} of the conversation's active LLM.
+ * @getter
+ * @returns {String} - The llm id
+ */
+ get llm_id(){
+ return this.#llm_id
+ }
+ /**
+ * Sets the `id` {String} of the conversation's active LLM.
+ * @getter
+ * @returns {String} - The llm id
+ */
+ set llm_id(llm_id){
+ if(!llm_id?.length)
+ this.#llm_id = llm_id
+ }
/**
* Get the most recently added message.
* @getter
@@ -280,6 +251,9 @@ function extendClass_conversation(originClass, referencesObject) {
get mostRecentDialog(){
return this.message.content
}
+ get run_id(){
+ return this.#run_id
+ }
get thread(){
return this.#thread
}
@@ -497,7 +471,6 @@ function extendClass_message(originClass, referencesObject) {
/* exports */
export {
extendClass_consent,
- extendClass_contribution,
extendClass_conversation,
extendClass_experience,
extendClass_file,
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index a8bb4692..53651054 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -102,14 +102,17 @@ async function challenge(ctx){
* @property {Object[]} responses - Response messages from Avatar intelligence
*/
async function chat(ctx){
- const { botId, itemId, message, shadowId, } = ctx.request.body ?? {} /* body nodes sent by fe */
+ const { botId, itemId, message, } = ctx.request.body
+ ?? {} /* body nodes sent by fe */
if(!message?.length)
ctx.throw(400, 'missing `message` content')
- const { avatar, dateNow=Date.now(), } = ctx.state
- const { MemberSession, } = ctx.session
+ const { avatar, } = ctx.state
+ const session = avatar.isMyLife
+ ? ctx.session.MemberSession
+ : null
if(botId?.length && botId!==avatar.activeBotId)
throw new Error(`Bot ${ botId } not currently active; chat() requires active bot`)
- const response = await avatar.chat(message, itemId, shadowId, dateNow, MemberSession)
+ const response = await avatar.chat(message, itemId, session)
ctx.body = response
}
async function collections(ctx){
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 7660f8fc..d3d027e4 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -80,71 +80,52 @@ class Avatar extends EventEmitter {
* @public
* @param {string} message - The chat message content
* @param {Guid} itemId - The active collection-item id (optional)
- * @param {Guid} shadowId - The active Shadow Id (optional)
- * @param {number} processStartTime - The start time of the process (optional)
- * @param {MemberSession} session - ignored, but required for **overload** on Q instance
- * @param {string} thread_id - The openai thread id (required for **overload** on Q instance)
* @returns {object} - The response object { instruction, responses, success, }
*/
- async chat(message, itemId, shadowId, processStartTime=Date.now(), session=null, thread_id){
+ async chat(message, itemId){
+ /* validate request */
if(!message)
throw new Error('No message provided in context')
-
-
-
-
- const Conversation = this.activeBot.conversation()
- // what should I actually return from chat?
- if(!conversation)
- throw new Error('No conversation found for bot intelligence and could not be created.')
- conversation.bot_id = botId
- conversation.llm_id = bot_id
- let messages = []
- if(shadowId)
- messages.push(...await this.shadow(shadowId, itemId, message))
- else {
- let alteredMessage = message
- if(itemId){
- // @todo - check if item exists in memory, fewer pings and inclusions overall
- let { summary, } = await this.#factory.item(itemId)
- if(summary?.length)
- alteredMessage = `possible **update-summary-request**: itemId=${ itemId }\n`
- + `**member-update-request**:\n`
- + message
- + `\n**current-summary-in-database**:\n`
- + summary
- }
- messages.push(...await mCallLLM(this.#llmServices, conversation, alteredMessage, this.#factory, this))
+ const originalMessage = message
+ let processStartTime = Date.now()
+ this.backupResponse = {
+ message: `I received your request to chat, and sent the request to the central intelligence, but no response was received. Please try again, as the issue is likely aberrant.`,
+ type: 'system',
}
- conversation.addMessage({
- content: message,
- created_at: Date.now(),
- role: 'user',
- })
- conversation.addMessages(messages)
- if(mAllowSave)
- conversation.save()
- else
- console.log('chat::BYPASS-SAVE', conversation.message?.content?.substring(0,64))
- /* frontend mutations */
- const responses = []
- conversation.messages
- .filter(_message=>{
- return messages.find(__message=>__message.id===_message.id)
- && _message.type==='chat'
- && _message.role!=='user'
- })
- .map(_message=>mPruneMessage(botId, _message, 'chat', processStartTime))
- .reverse()
- .forEach(_message=>responses.push(_message))
- if(!responses?.length){
- const failsafeResponse = this.backupResponse
- ?? {
- message: 'I am sorry, connection with my intelligence faltered, hopefully temporarily, ask to try again.',
- type: 'system',
- }
- responses.push(failsafeResponse)
+ /* execute request */
+ if(this.globals.isValidGuid(itemId)){
+ // @todo - check if item exists in memory, fewer pings and inclusions overall
+ let { summary, } = await this.#factory.item(itemId)
+ if(summary?.length)
+ message = `possible **update-summary-request**: itemId=${ itemId }\n`
+ + `**member-update-request**:\n`
+ + message
+ + `\n**current-summary-in-database**:\n`
+ + summary
}
+ const Conversation = await this.activeBot.chat(message, originalMessage, mAllowSave, processStartTime)
+ const responses = mPruneMessages(this.activeBotId, Conversation.getMessages() ?? [], 'chat', Conversation.processStartTime)
+ /* respond request */
+ const response = {
+ instruction: this.frontendInstruction,
+ responses,
+ success: true,
+ }
+ delete this.frontendInstruction
+ delete this.backupResponse
+ return response
+ }
+ /**
+ * Chat with an open agent, bypassing a specific bot.
+ * @param {Conversation} Conversation - The conversation instance
+ * @returns {Promise} - Conversation instance is altered in place
+ */
+ async chatAgentBypass(Conversation){
+ if(!this.isMyLife)
+ throw new Error('Agent bypass only available for MyLife avatar.')
+ await this.#botAgent.chat(Conversation, mAllowSave, this)
+ const responses = mPruneMessages(this.activeBotId, Conversation.getMessages(), 'chat', Conversation?.processStartTime)
+ /* respond request */
const response = {
instruction: this.frontendInstruction,
responses,
@@ -386,7 +367,6 @@ class Avatar extends EventEmitter {
* @returns {Array} - The greeting message(s) string array in order of display
*/
async greeting(dynamic=false){
- console.log('greeting', this.#botAgent.activeBotId)
const greetings = mPruneMessages(this.#botAgent.activeBotId, await this.#botAgent.greeting(dynamic), 'greeting')
return greetings
}
@@ -616,52 +596,6 @@ class Avatar extends EventEmitter {
}
return response
}
- /**
- * Takes a shadow message and sends it to the appropriate bot for response, returning the standard array of bot responses.
- * @param {Guid} shadowId - The shadow id.
- * @param {Guid} itemId - The item id.
- * @param {string} message - The member (interacting with shadow) message content.
- * @returns {Object[]} - The array of bot responses.
- */
- async shadow(shadowId, itemId, message){
- const processingStartTime = Date.now()
- const shadows = await this.shadows()
- const shadow = shadows.find(shadow=>shadow.id===shadowId)
- if(!shadow)
- throw new Error('Shadow not found.')
- const { text, type, } = shadow
- const item = await this.#factory.item(itemId)
- if(!item)
- throw new Error(`cannot find item: ${ itemId }`)
- const { form, summary, } = item
- let tailgate
- switch(type){
- case 'member':
- message = `update-memory-request: itemId=${ itemId }\n` + message
- break
- case 'agent':
- /*
- // @stub - develop additional form types, entry or idea for instance
- const dob = new Date(this.#factory.dob)
- const diff_ms = Date.now() - dob.getTime()
- const age_dt = new Date(diff_ms)
- const age = Math.abs(age_dt.getUTCFullYear() - 1970)
- message = `Given age of member: ${ age } and updated summary of personal memory: ${ summary }\n- answer the question: "${ text }"`
- tailgate = {
- content: `Would you like to add this, or part of it, to your memory?`, // @stub - tailgate for additional data
- thread_id: bot.thread_id,
- }
- break
- */
- default:
- break
- }
- let messages = await mCallLLM(this.#llmServices, this.activeBot, message, this.#factory, this)
- messages = messages.map(message=>mPruneMessage(this.activeBotId, message, 'shadow', processingStartTime))
- if(tailgate?.length)
- messages.push(mPruneMessage(this.activeBotId, tailgate, 'system'))
- return messages
- }
/**
* Gets the list of shadows.
* @returns {Object[]} - Array of shadow objects.
@@ -1256,32 +1190,32 @@ class Q extends Avatar {
/* overloaded methods */
/**
* OVERLOADED: Processes and executes incoming chat request.
+ * @todo - shunt registration actions to different MA functions
* @public
* @param {string} message - The chat message content
* @param {Guid} itemId - The active collection-item id (optional)
- * @param {Guid} shadowId - The active Shadow Id (optional)
- * @param {number} processStartTime - The start time of the process
- * @param {MemberSession} session - The MyLife MemberSession instance
- * @returns {object} - The response(s) to the chat request
+ * @param {MemberSession} MemberSession - The member session object
+ * @returns {Promise} - The response(s) to the chat request
*/
- async chat(message, itemId, shadowId, processStartTime=Date.now(), session){
- if(itemId?.length || shadowId?.length)
- throw new Error('MyLife System Avatar cannot process chats with `itemId` or `shadowId`.')
- let { Conversation, } = session
+ async chat(message, itemId, MemberSession){
+ if(itemId?.length)
+ throw new Error('MyLife System Avatar cannot process chats with `itemId`.')
+ let { Conversation, } = MemberSession
if(!Conversation){
- const Conversation = await this.conversationStart('chat', 'system-avatar')
- thread_id = Conversation.thread_id
+ Conversation = await this.conversationStart('chat', 'system-avatar')
+ if(!Conversation)
+ throw new Error('Unable to be create `Conversation`.')
this.#conversations.push(Conversation)
+ MemberSession.Conversation = Conversation
}
- session.Conversation = Conversation // @stub - store elsewhere
+ Conversation.originalPrompt = message
+ Conversation.processStartTime = Date.now()
if(this.isValidating) // trigger confirmation until session (or vld) ends
message = `CONFIRM REGISTRATION PHASE: registrationId=${ this.registrationId }\n${ message }`
if(this.isCreatingAccount)
message = `CREATE ACCOUNT PHASE: ${ message }`
-
-
-
- return super.chat(message, itemId, shadowId, processStartTime, null, thread_id)
+ Conversation.prompt = message
+ return await this.chatAgentBypass(Conversation)
}
/**
* OVERLOADED: MyLife must refuse to create bots.
@@ -1441,31 +1375,6 @@ function mAvatarDropdown(globals, avatar){
name,
}
}
-/**
- * Makes call to LLM and to return response(s) to prompt.
- * @todo - create actor-bot for internal chat? Concern is that API-assistants are only a storage vehicle, ergo not an embedded fine tune as I thought (i.e., there still may be room for new fine-tuning exercise); i.e., micro-instructionsets need to be developed for most. Unclear if direct thread/message instructions override or ADD, could check documentation or gpt, but...
- * @todo - would dynamic event dialog be handled more effectively with a callback routine function, I think so, and would still allow for avatar to vet, etc.
- * @todo - convert conversation requirements to bot
- * @module
- * @param {LLMServices} llmServices - OpenAI object currently
- * @param {Conversation} conversation - Conversation object
- * @param {string} prompt - dialog-prompt/message for llm
- * @param {AgentFactory} factory - Agent Factory object required for function execution
- * @param {object} avatar - Avatar object
- * @returns {Promise} - Array of Message instances in descending chronological order
- */
-async function mCallLLM(llmServices, conversation, prompt, factory, avatar){
- const { bot_id, llm_id, thread_id } = conversation
- const botId = llm_id
- ?? bot_id
- if(!thread_id || !botId)
- throw new Error('Both `thread_id` and `bot_id` required for LLM call.')
- const messages = await llmServices.getLLMResponse(thread_id, botId, prompt, factory, avatar)
- messages.sort((mA, mB)=>{
- return mB.created_at - mA.created_at
- })
- return messages
-}
/**
* Cancels openAI run.
* @module
@@ -2006,9 +1915,9 @@ async function mExperienceStart(avatar, factory, experienceId, avatarExperienceV
experience.variables = avatarExperienceVariables
/* assign living experience */
let [memberDialog, scriptDialog] = await Promise.all([
- avatar.createConversation('experience'),
- avatar.createConversation('dialog')
- ]) // async cobstruction
+ avatar.conversationStart('experience'),
+ avatar.conversationStart('dialog')
+ ]) // async construction
experience.memberDialog = memberDialog
experience.scriptDialog = scriptDialog
}
@@ -2271,7 +2180,6 @@ function mPruneMessage(activeBotId, message, type='chat', processStartTime=Date.
/* parse message */
let agent='server',
content='',
- purpose=type,
response_time=Date.now()-processStartTime
const { content: messageContent, } = message
const rSource = /【.*?\】/gs
@@ -2291,7 +2199,6 @@ function mPruneMessage(activeBotId, message, type='chat', processStartTime=Date.
activeBotId,
agent,
message,
- purpose,
response_time,
type,
}
@@ -2330,22 +2237,17 @@ function mPruneMessages(botId, messageArray, type='chat', processStartTime=Date.
async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInput='NEXT'){
console.log('mReliveMemoryNarration::start', item.id, memberInput)
const { relivingMemories, } = avatar
- const { bot, } = Bot
- const { bot_id, id: botId, } = bot
+ const { id: botId, } = Bot
const { id, } = item
const processStartTime = Date.now()
let message = `## relive memory itemId: ${ id }\n`
let relivingMemory = relivingMemories.find(reliving=>reliving.item.id===id)
if(!relivingMemory){ /* create new activated reliving memory */
- const conversation = await avatar.createConversation('memory', undefined, botId, false)
- conversation.llm_id = bot_id
- const { thread_id, } = conversation
+ const conversation = await avatar.conversationStart('memory', 'member-avatar')
relivingMemory = {
- bot,
conversation,
id,
item,
- thread_id,
}
relivingMemories.push(relivingMemory)
console.log(`mReliveMemoryNarration::new reliving memory: ${ id }`)
@@ -2367,7 +2269,6 @@ async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInp
id,
messages,
success: true,
- thread_id,
}
return memory
}
diff --git a/inc/js/mylife-factory.mjs b/inc/js/mylife-factory.mjs
index 4cf60479..1491d098 100644
--- a/inc/js/mylife-factory.mjs
+++ b/inc/js/mylife-factory.mjs
@@ -9,7 +9,6 @@ import Dataservices from './mylife-dataservices.mjs'
import { Member, MyLife } from './core.mjs'
import {
extendClass_consent,
- extendClass_contribution,
extendClass_conversation,
extendClass_experience,
extendClass_file,
@@ -26,7 +25,6 @@ const mBotInstructions = {}
const mDefaultBotType = 'personal-avatar'
const mExtensionFunctions = {
extendClass_consent: extendClass_consent,
- extendClass_contribution: extendClass_contribution,
extendClass_conversation: extendClass_conversation,
extendClass_experience: extendClass_experience,
extendClass_file: extendClass_file,
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index e9d7e915..9e95b8b8 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -57,18 +57,18 @@ class LLMServices {
}
/**
* Deletes an assistant from OpenAI.
- * @param {string} botId - GPT-Assistant external ID
+ * @param {string} llm_id - GPT-Assistant external ID
* @returns
*/
- async deleteBot(botId){
+ async deleteBot(llm_id){
try {
- const deletedBot = await this.openai.beta.assistants.del(botId)
+ const deletedBot = await this.openai.beta.assistants.del(llm_id)
return deletedBot
} catch (error) {
if(error.name==='PermissionDeniedError')
- console.error(`Permission denied to delete assistant: ${ botId }`)
+ console.error(`Permission denied to delete assistant: ${ llm_id }`)
else
- console.error(`ERROR trying to delete assistant: ${ botId }`, error.name, error.message)
+ console.error(`ERROR trying to delete assistant: ${ llm_id }`, error.name, error.message)
}
}
/**
@@ -108,17 +108,17 @@ class LLMServices {
* @example - `run` object: { assistant_id, id, model, provider, required_action, status, usage }
* @todo - confirm that reason for **factory** is to run functions as responses from LLM; ergo in any case, find better way to stash/cache factory so it does not need to be passed through every such function
* @param {string} thread_id - Thread id.
- * @param {string} botId - GPT-Assistant/Bot id.
+ * @param {string} llm_id - GPT-Assistant/Bot id.
* @param {string} prompt - Member input.
* @param {AgentFactory} factory - Avatar Factory object to process request.
* @param {Avatar} avatar - Avatar object.
* @returns {Promise} - Array of openai `message` objects.
*/
- async getLLMResponse(thread_id, botId, prompt, factory, avatar){
+ async getLLMResponse(thread_id, llm_id, prompt, factory, avatar){
if(!thread_id?.length)
thread_id = ( await mThread(this.openai) ).id
await mAssignRequestToThread(this.openai, thread_id, prompt)
- const run = await mRunTrigger(this.openai, botId, thread_id, factory, avatar)
+ const run = await mRunTrigger(this.openai, llm_id, thread_id, factory, avatar)
const { id: run_id, } = run
const llmMessages = ( await this.messages(thread_id) )
.filter(message=>message.role=='assistant' && message.run_id==run_id)
@@ -127,14 +127,14 @@ class LLMServices {
/**
* Given member request for help, get response from specified bot assistant.
* @param {string} thread_id - Thread id.
- * @param {string} botId - GPT-Assistant/Bot id.
+ * @param {string} llm_id - GPT-Assistant/Bot id.
* @param {string} helpRequest - Member input.
* @param {AgentFactory} factory - Avatar Factory object to process request.
* @param {Avatar} avatar - Avatar object.
* @returns {Promise} - openai `message` objects.
*/
- async help(thread_id, botId, helpRequest, factory, avatar){
- const helpResponse = await this.getLLMResponse(thread_id, botId, helpRequest, factory, avatar)
+ async help(thread_id, llm_id, helpRequest, factory, avatar){
+ const helpResponse = await this.getLLMResponse(thread_id, llm_id, helpRequest, factory, avatar)
return helpResponse
}
/**
@@ -159,15 +159,16 @@ class LLMServices {
}
/**
* Updates assistant with specified data. Example: Tools object for openai: { tool_resources: { file_search: { vector_store_ids: [vectorStore.id] } }, }; https://platform.openai.com/docs/assistants/tools/file-search/quickstart?lang=node.js
+ * @todo - conform payload to OpenAI API Reference
* @param {string} bot - The bot object data.
* @returns {Promise} - openai assistant object.
*/
async updateBot(bot){
- let { bot_id, ...assistantData } = bot
- if(!bot_id?.length)
+ let { botId, bot_id, llm_id, ...assistantData } = bot
+ if(!llm_id?.length)
throw new Error('No bot ID provided for update')
assistantData = mValidateAssistantData(assistantData) // throws on improper format
- const assistant = await this.openai.beta.assistants.update(bot_id, assistantData)
+ const assistant = await this.openai.beta.assistants.update(llm_id, assistantData)
return assistant
}
/**
@@ -324,7 +325,7 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
&& run.required_action?.submit_tool_outputs?.tool_calls
&& run.required_action.submit_tool_outputs.tool_calls.length
){
- const { assistant_id: bot_id, id: runId, metadata, thread_id, } = run
+ const { assistant_id: llm_id, id: runId, metadata, thread_id, } = run
const toolCallsOutput = await Promise.all(
run.required_action.submit_tool_outputs.tool_calls
.map(async tool=>{
@@ -681,14 +682,14 @@ async function mRunStart(llmServices, assistantId, threadId){
* Triggers openAI run and updates associated `run` object.
* @module
* @param {OpenAI} openai - OpenAI object
- * @param {string} botId - Bot id
+ * @param {string} llm_id - Bot id
* @param {string} threadId - Thread id
* @param {AgentFactory} factory - Avatar Factory object to process request
* @param {Avatar} avatar - Avatar object
* @returns {void} - All content generated by run is available in `avatar`.
*/
-async function mRunTrigger(openai, botId, threadId, factory, avatar){
- const run = await mRunStart(openai, botId, threadId)
+async function mRunTrigger(openai, llm_id, threadId, factory, avatar){
+ const run = await mRunStart(openai, llm_id, threadId)
if(!run)
throw new Error('Run failed to start')
const finishRun = await mRunFinish(openai, run, factory, avatar)
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 5f715e8e..1f8b2a26 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -715,7 +715,7 @@ async function submit(message, hideMemberChat=true){
* @returns {void}
*/
async function mSubmitChat(message) {
- const { action, itemId, shadowId, } = chatActiveItem.dataset
+ const { action, itemId, } = chatActiveItem.dataset
const url = window.location.origin + '/members'
const { id: botId, } = activeBot()
const request = {
@@ -724,7 +724,6 @@ async function mSubmitChat(message) {
itemId,
message,
role: 'member',
- shadowId,
}
const options = {
method: 'POST',
From bc8b0743858e7960b397817ff216dbd33031371d Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 00:44:46 -0400
Subject: [PATCH 26/56] 20241024 @Mookse - remove logs
---
inc/js/agents/system/bot-agent.mjs | 5 +----
inc/js/factory-class-extenders/class-extenders.mjs | 1 -
2 files changed, 1 insertion(+), 5 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index f6a6f317..cf68a1b0 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -235,7 +235,6 @@ class BotAgent {
async conversationStart(type='chat', form='system-avatar'){
const { bot_id: llm_id, id: bot_id, } = this.avatar
const Conversation = await mConversationStart(type, form, bot_id, null, llm_id, this.#llm, this.#factory)
- console.log('BotAgent::conversationStart', Conversation.thread_id, Conversation.bot_id, Conversation.llm_id, Conversation.inspect(true))
return Conversation
}
/**
@@ -782,9 +781,7 @@ async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
})
Conversation.addMessages(botResponses)
if(allowSave)
- console.log('chat::allowSave=`true`', Conversation.message?.content?.substring(0,64))// Conversation.save()
- else
- console.log('chat::allowSave=`false`', Conversation.message?.content?.substring(0,64))
+ Conversation.save() // no `await`
}
/**
* Create a new conversation.
diff --git a/inc/js/factory-class-extenders/class-extenders.mjs b/inc/js/factory-class-extenders/class-extenders.mjs
index 19e7fa47..daf1fdb7 100644
--- a/inc/js/factory-class-extenders/class-extenders.mjs
+++ b/inc/js/factory-class-extenders/class-extenders.mjs
@@ -82,7 +82,6 @@ function extendClass_conversation(originClass, referencesObject){
* @returns {Object[]} - The updated messages array.
*/
addMessage(message){
- console.log('class-extenders::addMessage', message?.content?.[0]?.text?.value ?? message)
const { id, } = message
if(this.#messages.find(message=>message.id===id))
return this.messages
From 31d92dada3ddafe5076fed2b36378a2455a02274 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 00:49:38 -0400
Subject: [PATCH 27/56] 20241024 @Mookse - minor cleanup
---
inc/js/session.mjs | 35 +++++++++--------------------------
1 file changed, 9 insertions(+), 26 deletions(-)
diff --git a/inc/js/session.mjs b/inc/js/session.mjs
index fef9ee3e..600f9540 100644
--- a/inc/js/session.mjs
+++ b/inc/js/session.mjs
@@ -15,26 +15,25 @@ class MylifeMemberSession extends EventEmitter {
super()
this.#factory = factory
this.#mbr_id = this.isMyLife ? this.factory.mbr_id : false
- mAssignFactoryListeners(this.#factory)
console.log(
chalk.bgGray('MylifeMemberSession:constructor(factory):generic-mbr_id::end'),
chalk.bgYellowBright(this.factory.mbr_id),
)
}
+ /**
+ * Initializes the member session. If `isMyLife`, then session requires chat thread unique to visitor; session has singleton System Avatar who maintains all running Conversations.
+ * @param {String} mbr_id - Member id to initialize session
+ * @returns {Promise} - Member session instance
+ */
async init(mbr_id=this.mbr_id){
- // if isMyLife, then session requires chat thread unique to guest; session has own avatar, demonstrate this is true and then create new conversation
if(!this.locked && this.mbr_id && this.mbr_id!==mbr_id){ // unlocked, initialize member session
this.#mbr_id = mbr_id
- mAssignFactoryListeners(this.#factory)
await this.#factory.init(this.mbr_id) // needs only `init()` with different `mbr_id` to reset
this.#Member = await this.factory.getMyLifeMember()
- this.#autoplayed = false // resets autoplayed flag, although should be impossible as only other "variant" requires guest status, as one-day experiences can be run for guests also [for pay]
- this.thread_id = null // reset thread_id from Q-session
- this.emit('onInit-member-initialize', this.#Member.memberName)
- console.log(
- chalk.bgBlue('created-member:'),
- chalk.bgRedBright(this.#Member.memberName)
- )
+ this.#autoplayed = false
+ delete this.Conversation
+ delete this.thread_id
+ console.log(chalk.bgBlue('created-member:'), chalk.bgRedBright(this.#Member.memberName))
}
return this
}
@@ -245,22 +244,6 @@ class MylifeMemberSession extends EventEmitter {
return this.#Member?.agentName
}
}
-function mAssignFactoryListeners(_session){
- if(_session.isMyLife) // all sessions _begin_ as MyLife
- _session.factory.on('member-unlocked',_mbr_id=>{
- console.log(
- chalk.grey('session::constructor::member-unlocked_trigger'),
- chalk.bgGray(_mbr_id)
- )
- })
- else // all _"end"_ as member
- _session.factory.on('avatar-activated',_avatar=>{
- console.log(
- chalk.grey('session::constructor::avatar-activated_trigger'),
- chalk.bgGray(_avatar.id)
- )
- })
-}
function mValidCtxObject(_ctx){
// validate ctx object
return (
From e4fc877d93b5fb4b2012bd7298996ba0fec59674 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 20:13:50 -0400
Subject: [PATCH 28/56] 20241024 @Mookse - wip stable - proto collectionsAgent
- RELIVE unstable
---
inc/js/agents/system/README.md | 8 +-
inc/js/agents/system/bot-agent.mjs | 406 ++++++++++++++----
inc/js/agents/system/collections-agent.mjs | 18 +
.../class-conversation-functions.mjs | 9 +-
.../class-extenders.mjs | 2 +-
inc/js/functions.mjs | 12 +-
inc/js/mylife-avatar.mjs | 368 +++++-----------
inc/js/mylife-factory.mjs | 2 +-
inc/js/routes.mjs | 2 +-
inc/json-schemas/conversation.json | 28 +-
10 files changed, 472 insertions(+), 383 deletions(-)
create mode 100644 inc/js/agents/system/collections-agent.mjs
diff --git a/inc/js/agents/system/README.md b/inc/js/agents/system/README.md
index 231fcf85..aebf0bc4 100644
--- a/inc/js/agents/system/README.md
+++ b/inc/js/agents/system/README.md
@@ -2,12 +2,16 @@
The MyLife system incorporates a suite of specialized assistants, each designed to augment various aspects of the avatars within the platform. These assistants follow consistent coding protocols and are currently intended exclusively for integration with avatars, enhancing their functionality and interactivity.
-- **Asset-Assistant (File Handler)**: This assistant manages file-related operations, ensuring efficient handling, storage, and retrieval of files within the system. It acts as a central hub for file management tasks, streamlining the process of dealing with various file formats and data types.
+- **Asset-Agent**: (File Handler) This agent manages file-related operations, ensuring efficient handling, storage, and retrieval of files within the system. It acts as a central hub for file management tasks, streamlining the process of dealing with various file formats and data types.
-- **Evolution-Assistant**: Central to the avatar's developmental journey, the Evolution Assistant orchestrates the growth and maturation of avatars. It guides avatars through different phases of evolution, from creation to retirement, tailoring the development process according to the avatar's specific needs and contexts.
+- **Bot-Agent**: (Bot Handler) One of the most fundamental agents available, represents an object that manages the menagerie of Bots allocated to the current member's team. Also manages Teams.
+
+- **Collections-Agent**: (Lists Handler) This agent manages collections for mass operations.
- **DOM-Assistant**: The Document Object Model (DOM) Assistant is pivotal in managing and manipulating the structure of data and documents within the system. It plays a key role in ensuring the data is organized and accessible in a way that is both efficient and intuitive.
+- **Evolution-Assistant**: Central to the avatar's developmental journey, the Evolution Assistant orchestrates the growth and maturation of avatars. It guides avatars through different phases of evolution, from creation to retirement, tailoring the development process according to the avatar's specific needs and contexts.
+
- **Preferences-Assistant**: This assistant is dedicated to personalizing user experiences by managing and adapting to user preferences. It ensures that avatars can cater to individual tastes and requirements, making interactions more tailored and relevant.
- **Settings-Assistant**: Focused on configuration management, the Settings Assistant allows for the customization and adjustment of system settings. This ensures that avatars can operate within the parameters that best suit the user's needs and the system's operational environment.
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index cf68a1b0..2b1310fb 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -1,5 +1,3 @@
-import LLMServices from "../../mylife-llm-services.mjs"
-
/* module constants */
const mBot_idOverride = process.env.OPENAI_MAHT_GPT_OVERRIDE
const mDefaultBotTypeArray = ['personal-avatar', 'avatar']
@@ -27,16 +25,18 @@ const mTeams = [
* @todo - are private vars for factory and llm necessary, or passable?
*/
class Bot {
+ #collectionsAgent
#conversation
#factory
#llm
- constructor(botData, factory, llm){
+ constructor(botData, llm, factory){
this.#factory = factory
this.#llm = llm
botData = this.globals.sanitize(botData)
Object.assign(this, botData)
if(!this.id)
throw new Error('Bot database id required')
+ // @stub - this.#collectionsAgent = new CollectionsAgent(llm, factory)
}
/* public functions */
/**
@@ -50,10 +50,7 @@ class Bot {
async chat(message, originalMessage, allowSave=true, processStartTime=Date.now()){
if(this.isMyLife && !this.isAvatar)
throw new Error('Only Q, MyLife Corporate Intelligence, is available for non-member conversation.')
- const { bot_id, id, thread_id, type, } = this
- if(!this.#conversation)
- this.#conversation = await mConversationStart('chat', type, id, thread_id, bot_id, this.#llm, this.#factory)
- const Conversation = this.#conversation
+ const Conversation = await this.getConversation()
Conversation.prompt = message
Conversation.originalPrompt = originalMessage
await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, this) // mutates Conversation
@@ -67,6 +64,19 @@ class Bot {
getBot(){
return this
}
+ /**
+ * Retrieves the Conversation instance for this bot.
+ * @param {String} message - The member request (optional)
+ * @returns {Promise} - The Conversation instance
+ */
+ async getConversation(message){
+ if(!this.#conversation){
+ const { bot_id: _llm_id, id: bot_id, thread_id, type, } = this
+ let { llm_id=_llm_id, } = this // @stub - deprecate bot_id
+ this.#conversation = await mConversationStart('chat', type, bot_id, thread_id, llm_id, this.#llm, this.#factory, message)
+ }
+ return this.#conversation
+ }
/**
* Retrieves a greeting message from the active bot.
* @param {Boolean} dynamic - Whether to use dynamic greetings (`true`) or static (`false`)
@@ -78,6 +88,23 @@ class Bot {
const greetings = await mBotGreeting(dynamic, this, llm, factory)
return greetings
}
+ /**
+ * Migrates Conversation from an old thread to a newly created (or identified) destination thread.
+ * @returns {Boolean} - Whether or not operation was successful
+ */
+ async migrateChat(){
+ const migration = mMigrateChat(this, this.#llm, this.#factory)
+ return !!migration
+ }
+ /**
+ * Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM.
+ * @param {Guid} itemId - The item id
+ * @returns {Object} - The obscured item object
+ */
+ async obscure(itemId){
+ const updatedSummary = await this.#factory.obscure(itemId)
+ return updatedSummary
+ }
/**
* Updates a Bot instance's data.
* @param {object} botData - The bot data to update
@@ -92,6 +119,24 @@ class Bot {
}
async save(){
+ }
+ /**
+ * Sets the thread id for the bot.
+ * @param {String} thread_id - The thread id
+ * @returns {Promise}
+ */
+ async setThread(thread_id){
+ const llm_id = this.llm_id
+ ?? this.bot_id
+ if(!thread_id?.length)
+ thread_id = await this.#llm.createThread(llm_id)
+ const { id, } = this
+ this.thread_id = thread_id
+ const bot = {
+ id,
+ thread_id,
+ }
+ await this.#factory.updateBot(bot)
}
/* getters/setters */
/**
@@ -110,6 +155,9 @@ class Bot {
}
return bot
}
+ get conversation(){
+ return this.#conversation
+ }
get globals(){
return this.#factory.globals
}
@@ -154,6 +202,7 @@ class BotAgent {
#avatarId
#bots
#factory
+ #fileConversation
#llm
#vectorstoreId
constructor(factory, llm){
@@ -180,13 +229,16 @@ class BotAgent {
}
/* public functions */
/**
- * Retrieves a bot instance by id.
- * @param {Guid} botId - The Bot id
+ * Retrieves Bot instance by id or type, defaults to personal-avatar.
+ * @param {Guid} bot_id - The Bot id
+ * @param {String} botType - The Bot type
* @returns {Promise} - The Bot instance
*/
- bot(botId){
- const Bot = this.#bots
- .find(bot=>bot.id===botId)
+ bot(bot_id, botType){
+ const Bot = botType?.length
+ ? this.#bots.find(bot=>bot.type===botType)
+ : this.#bots.find(bot=>bot.id===bot_id)
+ ?? this.avatar
return Bot
}
/**
@@ -195,7 +247,7 @@ class BotAgent {
* @returns {Bot} - The created Bot instance
*/
async botCreate(botData){
- const Bot = await mBotCreate(this.#avatarId, this.#vectorstoreId, botData, this.#factory)
+ const Bot = await mBotCreate(this.#avatarId, this.#vectorstoreId, botData, this.#llm, this.#factory)
this.#bots.push(Bot)
this.setActiveBot(Bot.id)
return Bot
@@ -203,14 +255,14 @@ class BotAgent {
/**
* Deletes a bot instance.
* @async
- * @param {Guid} botId - The Bot id
- * @returns {Promise}
+ * @param {Guid} bot_id - The Bot id
+ * @returns {Promise} - Whether or not operation was successful
*/
- async botDelete(botId){
- const Bot = this.#bots.find(bot=>bot.id===botId)
- if(!Bot)
- throw new Error(`Bot not found with id: ${ botId }`)
- await mBotDelete(Bot, this.#bots, this.#llm, this.#factory)
+ async botDelete(bot_id){
+ if(!this.#factory.isMyLife)
+ return false
+ const success = await mBotDelete(bot_id, this, this.#llm, this.#factory)
+ return success
}
/**
* Chat with the active bot.
@@ -222,7 +274,7 @@ class BotAgent {
async chat(Conversation, allowSave=true, q){
if(!Conversation)
throw new Error('Conversation instance required')
- Conversation.processingStartTime
+ Conversation.processStartTime
await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, q) // mutates Conversation
return Conversation
}
@@ -230,20 +282,16 @@ class BotAgent {
* Initializes a conversation, currently only requested by System Avatar, but theoretically could be requested by any externally-facing Member Avatar as well. **note**: not in Q because it does not have a #botAgent yet.
* @param {String} type - The type of conversation, defaults to `chat`
* @param {String} form - The form of conversation, defaults to `system-avatar`
- * @returns {Promise} - The conversation object
+ * @param {String} prompt - The prompt for the conversation (optional)
+ * @returns {Promise} - The Conversation instance
*/
- async conversationStart(type='chat', form='system-avatar'){
- const { bot_id: llm_id, id: bot_id, } = this.avatar
- const Conversation = await mConversationStart(type, form, bot_id, null, llm_id, this.#llm, this.#factory)
+ async conversationStart(type='chat', form='system-avatar', prompt){
+ const { avatar, } = this
+ const { bot_id: _llm_id, id: bot_id, } = avatar
+ let { llm_id=_llm_id, } = avatar // @stub - deprecate bot_id
+ const Conversation = await mConversationStart(type, form, bot_id, null, llm_id, this.#llm, this.#factory, prompt)
return Conversation
}
- /**
- * Retrieves bots by instance.
- * @returns {Bot[]} - The array of bots
- */
- getBots(){
- return this.#bots
- }
/**
* Get a static or dynamic greeting from active bot.
* @param {boolean} dynamic - Whether to use LLM for greeting
@@ -252,14 +300,39 @@ class BotAgent {
async greeting(dynamic=false){
const greetings = await this.activeBot.getGreeting(dynamic, this.#llm, this.#factory)
return greetings
+ }
+ /**
+ * Migrates a bot to a new, presumed combined (with internal or external) bot.
+ * @param {Guid} bot_id - The bot id
+ * @returns {Promise} - The migrated Bot instance
+ */
+ async migrateBot(bot_id){
+ throw new Error('migrateBot() not yet implemented')
+ }
+ /**
+ * Migrates a chat conversation from an old thread to a newly created (or identified) destination thread.
+ * @param {Guid} bot_id - Bot id whose Conversation is to be migrated
+ * @returns {Boolean} - Whether or not operation was successful
+ */
+ async migrateChat(bot_id){
+ /* validate request */
+ if(this.#factory.isMyLife)
+ throw new Error('Chats with Q cannot be migrated.')
+ const Bot = this.bot(bot_id)
+ if(!Bot)
+ return false
+ /* execute request */
+ await Bot.migrateChat()
+ /* respond request */
+ return true
}
/**
* Sets the active bot for the BotAgent.
- * @param {Guid} botId - The Bot id
+ * @param {Guid} bot_id - The Bot id
* @returns {void}
*/
- setActiveBot(botId=this.avatar?.id){
- const Bot = this.#bots.find(bot=>bot.id===botId)
+ setActiveBot(bot_id=this.avatar?.id){
+ const Bot = this.#bots.find(bot=>bot.id===bot_id)
if(Bot)
this.#activeBot = Bot
}
@@ -272,6 +345,22 @@ class BotAgent {
this.#activeTeam = this.teams.find(team=>team.id===teamId)
?? this.#activeTeam
}
+ async summarize(fileId, fileName, processStartTime=Date.now()){
+ let responses = []
+ if(!fileId?.length && !fileName?.length)
+ return responses
+ let prompts = []
+ if(fileId?.length)
+ prompts.push(`id=${ fileId }`)
+ if(fileName?.length)
+ prompts.push(`file-name=${ fileName }`)
+ const prompt = `Summarize file document: ${ prompts.join(', ') }`
+ if(!this.#fileConversation)
+ this.#fileConversation = await this.conversationStart('file-summary', 'member-avatar', prompt, processStartTime)
+ this.#fileConversation.prompt = prompt
+ responses = await mCallLLM(this.#fileConversation, false, this.#llm, this.#factory)
+ return responses
+ }
/**
* Updates a bot instance.
* @param {object} botData - The bot data to update
@@ -331,7 +420,7 @@ class BotAgent {
return this.#avatarId
}
/**
- * Gets the array of bots employed by this BotAgent. For full instances, call `getBots()`.
+ * Gets the array of bots employed by this BotAgent.
* @getter
* @returns {Bot[]} - The array of bots
*/
@@ -376,14 +465,14 @@ class BotAgent {
* Initializes openAI assistant and returns associated `assistant` object.
* @module
* @param {object} botData - The bot data object
- * @param {LLMServices} llmServices - OpenAI object
+ * @param {LLMServices} llm - OpenAI object
* @returns {object} - [OpenAI assistant object](https://platform.openai.com/docs/api-reference/assistants/object)
*/
-async function mAI_openai(botData, llmServices){
+async function mAI_openai(botData, llm){
const { bot_name, type, } = botData
botData.name = bot_name
?? `_member_${ type }`
- const bot = await llmServices.createBot(botData)
+ const bot = await llm.createBot(botData)
return bot
}
/**
@@ -457,21 +546,21 @@ async function mBot(avatarId, vectorstore_id, factory, botData){
}
/**
* Creates bot and returns associated `bot` object.
- * @todo - botData.name = botDbName should not be required, push logic to `llm-services`
+ * @todo - validBotData.name = botDbName should not be required, push logic to `llm-services`
* @module
* @async
* @param {Guid} avatarId - The Avatar id
* @param {String} vectorstore_id - The Vectorstore id
- * @param {Object} bot - The bot data
+ * @param {Object} botData - The bot proto-data
* @param {AgentFactory} factory - Agent Factory instance
* @returns {Promise} - Created Bot instance
*/
-async function mBotCreate(avatarId, vectorstore_id, bot, factory){
+async function mBotCreate(avatarId, vectorstore_id, botData, llm, factory){
/* validation */
- const { type, } = bot
+ const { type, } = botData
if(!avatarId?.length || !type?.length)
throw new Error('avatar id and type required to create bot')
- const { instructions, version, } = mBotInstructions(factory, bot)
+ const { instructions, version=1.0, } = mBotInstructions(factory, type)
const model = process.env.OPENAI_MODEL_CORE_BOT
?? process.env.OPENAI_MODEL_CORE_AVATAR
?? 'gpt-4o'
@@ -481,8 +570,8 @@ async function mBotCreate(avatarId, vectorstore_id, bot, factory){
bot_name = `My ${type}`,
description = `I am a ${type} for ${factory.memberName}`,
name = `bot_${type}_${avatarId}`,
- } = bot
- const botData = {
+ } = botData
+ const validBotData = {
being: 'bot',
bot_name,
description,
@@ -504,13 +593,14 @@ async function mBotCreate(avatarId, vectorstore_id, bot, factory){
version,
}
/* create in LLM */
- const { id: bot_id, thread_id, } = await mBotCreateLLM(botData, llm)
+ const { id: bot_id, thread_id, } = await mBotCreateLLM(validBotData, llm)
if(!bot_id?.length)
throw new Error('bot creation failed')
/* create in MyLife datastore */
- botData.bot_id = bot_id
- botData.thread_id = thread_id
- const Bot = new Bot(await factory.createBot(botData))
+ validBotData.bot_id = bot_id
+ validBotData.thread_id = thread_id
+ botData = await factory.createBot(validBotData) // repurposed incoming botData
+ const Bot = new Bot(botData, llm, factory)
console.log(chalk.green(`bot created::${ type }`), Bot.thread_id, Bot.id, Bot.bot_id, Bot.bot_name )
return Bot
}
@@ -530,27 +620,28 @@ async function mBotCreateLLM(botData, llm){
}
/**
* Deletes the bot requested from avatar memory and from all long-term storage.
- * @param {object} Bot - The bot object to delete
- * @param {Object[]} bots - The bots array
- * @param {LLMServices} llm - OpenAI object
- * @param {AgentFactory} factory - Agent Factory object
- * @returns {void}
+ * @param {Guid} bot_id - The bot id to delete
+ * @param {BotAgent} BotAgent - BotAgent instance
+ * @param {LLMServices} llm - The LLMServices instance
+ * @param {AgentFactory} factory - The Factory instance
+ * @returns {Promise} - Whether or not operation was successful
*/
-async function mBotDelete(Bot, bots, llm, factory){
+async function mBotDelete(bot_id, BotAgent, llm, factory){
+ const Bot = BotAgent.bot(bot_id)
+ const { id, llm_id, type, thread_id, } = Bot
const cannotRetire = ['actor', 'system', 'personal-avatar']
- const { bot_id, id, thread_id, type, } = bot
if(cannotRetire.includes(type))
- throw new Error(`Cannot retire bot type: ${ type }`)
+ return false
/* delete from memory */
- const botId = bots.findIndex(_bot=>_bot.id===id)
- if(botId<0)
- throw new Error('Bot not found in bots.')
- bots.splice(botId, 1)
+ const { bots, } = BotAgent
+ const botIndex = bots.findIndex(bot=>bot.id===id)
+ bots.splice(botIndex, 1)
/* delete bot from Cosmos */
factory.deleteItem(id)
- /* delete thread and bot from OpenAI */
- llm.deleteBot(bot_id)
+ /* delete thread and bot from LLM */
+ llm.deleteBot(llm_id)
llm.deleteThread(thread_id)
+ return true
}
/**
* Returns set of Greeting messages, dynamic or static
@@ -592,12 +683,11 @@ async function mBotGreeting(dynamic=false, Bot, llm, factory){
/**
* Returns MyLife-version of bot instructions.
* @module
- * @param {BotFactory} factory - Factory object
- * @param {object} bot - Bot object
- * @returns {object} - minor
+ * @param {AgentFactory} factory - The Factory instance
+ * @param {String} type - The type of Bot to create
+ * @returns {object} - The intermediary bot instructions object: { instructions, version, }
*/
-function mBotInstructions(factory, bot){
- const { type=mDefaultBotType, } = bot
+function mBotInstructions(factory, type=mDefaultBotType){
let {
instructions,
limit=8000,
@@ -684,13 +774,16 @@ function mBotInstructions(factory, bot){
break
}
})
- /* assess and validate limit */
- return { instructions, version, }
+ const response = {
+ instructions,
+ version,
+ }
+ return response
}
/**
* Updates bot in Cosmos, and if necessary, in LLM. Returns unsanitized bot data document.
* @param {AgentFactory} factory - Factory object
- * @param {LLMServices} llm - LLMServices object
+ * @param {LLMServices} llm - The LLMServices instance
* @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, vectorstoreId: string, }
* @returns
@@ -729,19 +822,18 @@ async function mBotUpdate(factory, llm, bot, options={}){
botData.model = factory.globals.currentOpenAIBotModel
botData.id = id // validated
/* LLM updates */
- const { bot_id, bot_name: name, instructions, llm_id, tools, } = botData
- const llmId = llm_id
- ?? bot_id
- if(llmId?.length && (instructions || name || tools)){
+ const { bot_id, bot_name: name, instructions, tools, } = botData
+ let { llm_id=bot_id, } = botData
+ if(llm_id?.length && (instructions || name || tools)){
botData.model = factory.globals.currentOpenAIBotModel // not dynamic
- botData.llm_id = llmId
+ botData.llm_id = llm_id
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 OpenAI'), id, bot_id, updatedLLMFields)
+ console.log(chalk.green('mUpdateBot()::update in OpenAI'), id, llm_id, updatedLLMFields)
}
- const updatedBotData = await factory.updateBot(botData)
- return updatedBotData
+ botData = await factory.updateBot(botData)
+ return botData
}
/**
* Sends Conversation instance with prompts for LLM to process, updating the Conversation instance before returning `void`.
@@ -750,13 +842,14 @@ async function mBotUpdate(factory, llm, bot, options={}){
* @module
* @param {Conversation} Conversation - Conversation instance
* @param {boolean} allowSave - Whether to save the conversation, defaults to `true`
- * @param {LLMServices} llm - OpenAI object
+ * @param {LLMServices} llm - The LLMServices instance
* @param {AgentFactory} factory - Agent Factory object required for function execution
* @param {object} avatar - Avatar object
* @returns {Promise} - Alters Conversation instance by nature
*/
async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
- const { llm_id, originalPrompt, processingStartTime=Date.now(), prompt, thread_id, } = Conversation
+ const { llm_id, originalPrompt, processStartTime=Date.now(), prompt, thread_id, } = Conversation
+ console.log('mCallLLM', llm_id, thread_id, prompt, processStartTime)
if(!llm_id?.length)
throw new Error('No `llm_id` intelligence id found in Conversation for `mCallLLM`.')
if(!thread_id?.length)
@@ -773,7 +866,7 @@ async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
.sort((mA, mB)=>(mB.created_at-mA.created_at))
Conversation.addMessage({
content: prompt,
- created_at: processingStartTime,
+ created_at: processStartTime,
originalPrompt,
role: 'member',
run_id,
@@ -783,6 +876,25 @@ async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
if(allowSave)
Conversation.save() // no `await`
}
+/**
+ * Deletes conversation and updates
+ * @param {Conversation} Conversation - The Conversation instance
+ * @param {LLMServices} llm - The LLMServices instance
+ * @returns {Promise} - `true` if successful
+ */
+async function mConversationDelete(Conversation, factory, llm){
+ /* delete thread_id from bot and save to Cosmos */
+ Bot.thread_id = ''
+ const { id, thread_id, } = Bot
+ factory.updateBot({
+ id,
+ thread_id,
+ })
+ await factory.deleteItem(Conversation.id) /* delete conversation from Cosmos */
+ await llm.deleteThread(thread_id) /* delete thread from LLM */
+ console.log('mDeleteConversation', Conversation.id, thread_id)
+ return true
+}
/**
* Create a new conversation.
* @async
@@ -791,17 +903,22 @@ async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
* @param {string} form - Form of conversation: system-avatar, member-avatar, etc.; defaults to `system-avatar`
* @param {string} thread_id - The openai thread id
* @param {string} llm_id - The id for the llm agent
- * @param {LLMServices} llm - OpenAI object
+ * @param {LLMServices} llm - The LLMServices instance
* @param {AgentFactory} factory - Agent Factory object
+ * @param {string} prompt - The prompt for the conversation (optional)
+ * @param {number} processStartTime - The time the processing started, defaults to `Date.now()`
* @returns {Conversation} - The conversation object
*/
-async function mConversationStart(type='chat', form='system', bot_id, thread_id, llm_id, llm, factory){
- const { mbr_id, } = factory
- const thread = await llm.thread(thread_id) // **note**: created here as to begin conversation/LLM independence, and to retain non-async nature of Constructor
+async function mConversationStart(type='chat', form='system', bot_id, thread_id, llm_id, llm, factory, prompt, processStartTime=Date.now()){
+ const { mbr_id, newGuid: id, } = factory
+ const thread = await mThread(thread_id, llm)
const Conversation = new (factory.conversation)(
{
form,
+ id,
mbr_id,
+ prompt,
+ processStartTime,
type,
},
factory,
@@ -904,7 +1021,7 @@ function mGetGPTResources(globals, toolName, vectorstoreId){
* @param {BotAgent} BotAgent - The BotAgent to initialize
* @param {Bot[]} bots - The array of bots (empty on init)
* @param {AgentFactory} factory - The factory instance
- * @param {LLMServices} llm - The LLM instance
+ * @param {LLMServices} llm - The LLMServices instance
* @returns {void}
*/
async function mInit(BotAgent, bots, factory, llm){
@@ -917,7 +1034,7 @@ async function mInit(BotAgent, bots, factory, llm){
* @param {Guid} avatarId - The Avatar id
* @param {String} vectorstore_id - The Vectorstore id
* @param {AgentFactory} factory - The MyLife factory instance
- * @param {LLMServices} llm - The LLM instance
+ * @param {LLMServices} llm - The LLMServices instance
* @returns {Bot[]} - The array of activated and available bots
*/
async function mInitBots(avatarId, vectorstore_id, factory, llm){
@@ -925,9 +1042,118 @@ async function mInitBots(avatarId, vectorstore_id, factory, llm){
.map(botData=>{
botData.vectorstore_id = vectorstore_id
botData.object_id = avatarId
- return new Bot(botData, factory, llm)
+ return new Bot(botData, llm, factory)
})
return bots
}
+/**
+ * Migrates LLM thread/memory to new one, altering Conversation instance.
+ * @param {Bot} Bot - Bot instance
+ * @param {LLMServices} llm - The LLMServices instance
+ * @returns {Promise} - Whether or not operation was successful
+ */
+async function mMigrateChat(Bot, llm){
+ /* constants and variables */
+ const { Conversation, id: bot_id, type: botType, } = Bot
+ if(!Conversation)
+ return false
+ const { chatLimit=25, thread_id, } = Conversation
+ let messages = await llm.messages(thread_id) // @todo - limit to 25 messages or modify request
+ if(!messages?.length)
+ return false
+ let disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
+ itemCollectionTypes='item',
+ itemLimit=100,
+ type='item'
+ switch(botType){
+ case 'biographer':
+ case 'personal-biographer':
+ type = 'memory'
+ itemCollectionTypes = `memory,story,narrative`
+ break
+ case 'diary':
+ case 'journal':
+ case 'journaler':
+ type = 'entry'
+ const itemType = botType==='journaler'
+ ? 'journal'
+ : botType
+ itemCollectionTypes = `${ itemType },entry,`
+ break
+ default:
+ break
+ }
+ const chatSummary=`## ${ type.toUpperCase() } CHAT SUMMARY\n`,
+ chatSummaryRegex = /^## [^\n]* CHAT SUMMARY\n/,
+ itemSummary=`## ${ type.toUpperCase() } LIST\n`,
+ itemSummaryRegex = /^## [^\n]* LIST\n/
+ const items = ( await avatar.collections(type) )
+ .sort((a, b)=>a._ts-b._ts)
+ .slice(0, itemLimit)
+ const itemList = items
+ .map(item=>`- itemId: ${ item.id } :: ${ item.title }`)
+ .join('\n')
+ const itemCollectionList = items
+ .map(item=>item.id)
+ .join(',')
+ .slice(0, 512) // limit for metadata string field
+ const metadata = {
+ bot_id: bot_id,
+ conversation_id: Conversation.id,
+ }
+ /* prune messages source material */
+ messages = messages
+ .slice(0, chatLimit)
+ .map(message=>{
+ const { content: contentArray, id, metadata, role, } = message
+ const content = contentArray
+ .filter(_content=>_content.type==='text')
+ .map(_content=>_content.text?.value)
+ ?.[0]
+ return { content, id, metadata, role, }
+ })
+ .filter(message=>!itemSummaryRegex.test(message.content))
+ const summaryMessage = messages
+ .filter(message=>!chatSummaryRegex.test(message.content))
+ .map(message=>message.content)
+ .join('\n')
+ /* contextualize previous content */
+ const summaryMessages = []
+ /* summary of items */
+ if(items.length)
+ summaryMessages.push({
+ content: itemSummary + disclaimer + itemList,
+ metadata: {
+ collectionList: itemCollectionList,
+ collectiontypes: itemCollectionTypes,
+ },
+ role: 'assistant',
+ })
+ /* summary of messages */
+ if(summaryMessage.length)
+ summaryMessages.push({
+ content: chatSummary + disclaimer + summaryMessage,
+ metadata: {
+ collectiontypes: itemCollectionTypes,
+ },
+ role: 'assistant',
+ })
+ if(!summaryMessages.length)
+ return
+ /* add messages to new thread */
+ Conversation.setThread( await llm.thread(null, summaryMessages.reverse(), metadata) )
+ await Bot.setThread(Conversation.thread_id) // autosaves `thread_id`, no `await`
+ console.log('mMigrateChat::SUCCESS', Bot.thread_id, Conversation.inspect(true))
+ if(mAllowSave)
+ Conversation.save() // no `await`
+ else
+ console.log('mMigrateChat::BYPASS-SAVE', Conversation.thread_id)
+}
+async function mThread(thread_id, llm){
+ const messages = []
+ const metadata = {}
+ const thread = await llm.thread(thread_id, messages, metadata)
+ return thread
+}
/* exports */
export default BotAgent
\ No newline at end of file
diff --git a/inc/js/agents/system/collections-agent.mjs b/inc/js/agents/system/collections-agent.mjs
new file mode 100644
index 00000000..a797ef8f
--- /dev/null
+++ b/inc/js/agents/system/collections-agent.mjs
@@ -0,0 +1,18 @@
+/* module constants */
+const mDefaultCollectionTypes = ['entry', 'experience', 'memory', 'story']
+/* classes */
+/**
+ * @class - Bot
+ * @private
+ * @todo - are private vars for factory and llm necessary, or passable?
+ */
+class CollectionsAgent {
+ #factory
+ #llm
+ constructor(llm, factory){
+ this.#factory = factory
+ this.#llm = llm
+ }
+}
+/* module exports */
+export default CollectionsAgent
\ No newline at end of file
diff --git a/inc/js/factory-class-extenders/class-conversation-functions.mjs b/inc/js/factory-class-extenders/class-conversation-functions.mjs
index 91af01ff..d8b71aad 100644
--- a/inc/js/factory-class-extenders/class-conversation-functions.mjs
+++ b/inc/js/factory-class-extenders/class-conversation-functions.mjs
@@ -2,10 +2,10 @@
/**
* Consumes a conversation object and uses supplied factory to (create/)save it to MyLife CosmosDB. Each session conversation is saved as a separate document, and a given thread may span many conversations, so cross-checking by thread_id will be required when rounding up and consolidating summaries for older coversations.
* @param {AgentFactory} factory - Factory instance
- * @param {Conversation} conversation - Conversation object
+ * @param {Conversation} Conversation - Conversation instance
* @returns {Promise}
*/
-async function mSaveConversation(factory, conversation){
+async function mSaveConversation(Conversation, factory){
const {
being,
bot_id,
@@ -14,10 +14,9 @@ async function mSaveConversation(factory, conversation){
isSaved=false,
name,
thread,
- thread_id,
type,
- } = conversation
- let { messages, } = conversation
+ } = Conversation
+ let { messages, } = Conversation
messages = messages
.map(_msg=>_msg.micro)
if(!isSaved){
diff --git a/inc/js/factory-class-extenders/class-extenders.mjs b/inc/js/factory-class-extenders/class-extenders.mjs
index daf1fdb7..a40b2f75 100644
--- a/inc/js/factory-class-extenders/class-extenders.mjs
+++ b/inc/js/factory-class-extenders/class-extenders.mjs
@@ -176,7 +176,7 @@ function extendClass_conversation(originClass, referencesObject){
* @returns {void}
*/
async save(){
- this.#saved = await mSaveConversation(this.#factory, this)
+ this.#saved = await mSaveConversation(this, this.#factory)
}
// public getters/setters
/**
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index 53651054..fdfc791b 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -35,7 +35,7 @@ async function alerts(ctx){
}
}
async function bots(ctx){
- const { bid, } = ctx.params // botId sent in url path
+ const { bid, } = ctx.params // bot_id sent in url path
const { avatar } = ctx.state
const bot = ctx.request.body ?? {}
const { id, } = bot
@@ -102,7 +102,7 @@ async function challenge(ctx){
* @property {Object[]} responses - Response messages from Avatar intelligence
*/
async function chat(ctx){
- const { botId, itemId, message, } = ctx.request.body
+ const { botId: bot_id, itemId, message, } = ctx.request.body
?? {} /* body nodes sent by fe */
if(!message?.length)
ctx.throw(400, 'missing `message` content')
@@ -110,8 +110,8 @@ async function chat(ctx){
const session = avatar.isMyLife
? ctx.session.MemberSession
: null
- if(botId?.length && botId!==avatar.activeBotId)
- throw new Error(`Bot ${ botId } not currently active; chat() requires active bot`)
+ if(bot_id?.length && bot_id!==avatar.activeBotId)
+ throw new Error(`Bot ${ bot_id } not currently active; chat() requires active bot`)
const response = await avatar.chat(message, itemId, session)
ctx.body = response
}
@@ -244,9 +244,9 @@ async function migrateBot(ctx){
ctx.body = await avatar.migrateBot(bid)
}
async function migrateChat(ctx){
- const { tid, } = ctx.params
+ const { bid, } = ctx.params
const { avatar, } = ctx.state
- ctx.body = await avatar.migrateChat(tid)
+ ctx.body = await avatar.migrateChat(bid)
}
/**
* Given an itemId, obscures aspects of contents of the data record.
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index d3d027e4..d07702b6 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -2,6 +2,7 @@ import { Marked } from 'marked'
import EventEmitter from 'events'
import AssetAgent from './agents/system/asset-agent.mjs'
import BotAgent from './agents/system/bot-agent.mjs'
+import CollectionsAgent from './agents/system/collections-agent.mjs'
import EvolutionAgent from './agents/system/evolution-agent.mjs'
import LLMServices from './mylife-llm-services.mjs'
/* module constants */
@@ -19,7 +20,9 @@ const mAvailableModes = ['standard', 'admin', 'evolution', 'experience', 'restor
class Avatar extends EventEmitter {
#assetAgent
#botAgent
+ #collectionsAgent
#evolver
+ #experienceAgent
#experienceGenericVariables = {
age: undefined,
birthdate: undefined,
@@ -49,6 +52,7 @@ class Avatar extends EventEmitter {
this.#llmServices = llmServices
this.#assetAgent = new AssetAgent(this.#factory, this.#llmServices)
this.#botAgent = new BotAgent(this.#factory, this.#llmServices)
+ this.#collectionsAgent = new CollectionsAgent(this.#factory, this.#llmServices)
}
/* public functions */
/**
@@ -68,11 +72,11 @@ class Avatar extends EventEmitter {
/**
* Get a Bot instance by id.
* @public
- * @param {Guid} botId - The bot id
+ * @param {Guid} bot_id - The bot id
* @returns {Promise} - The bot object from memory
*/
- bot(botId){
- const Bot = this.#botAgent.bot(botId)
+ bot(bot_id){
+ const Bot = this.#botAgent.bot(bot_id)
return Bot
}
/**
@@ -116,9 +120,10 @@ class Avatar extends EventEmitter {
return response
}
/**
- * Chat with an open agent, bypassing a specific bot.
+ * Chat with an open agent, bypassing specific or active bot.
* @param {Conversation} Conversation - The conversation instance
- * @returns {Promise} - Conversation instance is altered in place
+ * @returns {Promise} - Response object: { instruction, responses, success, }
+ * @note - Conversation instance is altered in place
*/
async chatAgentBypass(Conversation){
if(!this.isMyLife)
@@ -325,29 +330,19 @@ class Avatar extends EventEmitter {
* @param {Guid} id - The Bot id
* @returns {object} - The pruned Bot object
*/
- getBot(botId){
- const bot = this.#botAgent.bot(botId)?.bot
+ getBot(bot_id){
+ const bot = this.#botAgent.bot(bot_id)?.bot
return bot
}
- /**
- * Returns the pruned bots for avatar.
- * @param {Guid} id - The Bot id
- * @returns {Object[]} - The pruned Bot objects
- */
- getBots(){
- const bots = this.bots
- .map(Bot=>Bot.bot)
- return bots
- }
/**
* Gets Conversation object. If no thread id, creates new conversation.
* @param {string} thread_id - openai thread id (optional)
- * @param {Guid} botId - The bot id (optional)
+ * @param {Guid} bot_id - The bot id (optional)
* @returns {Conversation} - The conversation object.
*/
- getConversation(thread_id, botId){
+ getConversation(thread_id, bot_id){
const conversation = this.conversations
- .filter(c=>(thread_id?.length && c.thread_id===thread_id) || (botId?.length && c.botId===botId))
+ .filter(c=>(thread_id?.length && c.thread_id===thread_id) || (bot_id?.length && c.bot_id===bot_id))
?.[0]
return conversation
}
@@ -434,32 +429,36 @@ class Avatar extends EventEmitter {
}
/**
* Migrates a bot to a new, presumed combined (with internal or external) bot.
- * @param {Guid} botId - The bot id.
- * @returns
+ * @param {Guid} bot_id - The bot id
+ * @returns {Promise} - The migrated Bot instance
*/
- async migrateBot(botId){
- const bot = await this.bot(botId)
- if(!bot)
- throw new Error(`Bot not found with id: ${ botId }`)
- const { id, } = bot
- if(botId!==id)
- throw new Error(`Bot id mismatch: ${ botId }!=${ id }`)
- return bot
+ async migrateBot(bot_id){
+ const migration = await this.#botAgent.migrateBot(bot_id)
+ return migration
}
/**
* Migrates a chat conversation from an old thread to a newly created (or identified) destination thread.
* @param {string} thread_id - Conversation thread id in OpenAI
* @returns {Conversation} - The migrated conversation object
*/
- async migrateChat(thread_id){
- /* validate request */
- const conversation = this.getConversation(thread_id)
- if(!conversation)
- throw new Error(`Conversation thread_id not found: ${ thread_id }`)
- /* execute request */
- const updatedConversation = await mMigrateChat(this, this.#factory, this.#llmServices, conversation)
- /* respond request */
- return updatedConversation
+ async migrateChat(bot_id){
+ const success = await this.#botAgent.migrateChat(bot_id)
+ const response = {
+ responses: [success
+ ? {
+ agent: 'server',
+ message: `I have successfully migrated this conversation to a new thread.`,
+ type: 'chat',
+ }
+ : {
+ agent: 'server',
+ message: `I'm sorry - I encountered an error while trying to migrate this conversation; please try again.`,
+ type: 'chat',
+ }
+ ],
+ success,
+ }
+ return response
}
/**
* Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM.
@@ -467,7 +466,7 @@ class Avatar extends EventEmitter {
* @returns {Object} - The obscured item object
*/
async obscure(iid){
- const updatedSummary = await this.#factory.obscure(iid)
+ const updatedSummary = await this.activeBot.obscure(iid)
this.frontendInstruction = {
command: 'updateItemSummary',
itemId: iid,
@@ -523,63 +522,46 @@ class Avatar extends EventEmitter {
}
/**
* Member request to retire a bot.
- * @param {Guid} botId - The bot id.
- * @returns {object} - The retired bot object.
- */
- async retireBot(botId){
- /* reset active bot, if required */
- if(this.activeBotId===botId)
- this.#botAgent.setActiveBot() // avatar cannot be retired
- const bot = await this.bot(botId)
- if(!bot)
- throw new Error(`Bot not found with id: ${ botId }`)
- const { id, } = bot
- if(botId!==id)
- throw new Error(`Bot id mismatch: ${ botId }!=${ id }`)
- this.#botAgent.botDelete(botId)
+ * @param {Guid} bot_id - The id of Bot to retire
+ * @returns {object} - The Response object: { instruction, responses, success, }
+ */
+ async retireBot(bot_id){
+ const success = await this.#botAgent.deleteBot(bot_id)
const response = {
instruction: {
- command: 'removeBot',
- id: botId,
+ command: success ? 'removeBot' : 'error',
+ id: bot_id,
},
- responses: [{
- agent: 'server',
- message: `I have removed this bot from the team.`,
- purpose: 'system',
- type: 'chat',
- }],
- success: true,
+ responses: [success
+ ? {
+ agent: 'server',
+ message: `I have removed this bot from the team.`,
+ type: 'chat',
+ }
+ : {
+ agent: 'server',
+ message: `I'm sorry - I encountered an error while trying to retire this bot; please try again.`,
+ type: 'system',
+ }
+ ],
+ success,
}
return response
}
/**
- * Member-request to retire a chat conversation thread and begin a new one with the same intelligence.
- * @param {string} thread_id - Conversation thread id in OpenAI
+ * Currently only proxy for `migrateChat`.
+ * @param {string} bot_id - Bot id with Conversation to retire
* @returns {object} - The response object { instruction, responses, success, }
*/
- async retireChat(botId){
- /* validate request */
- const conversation = this.getConversation(null, botId)
- if(!conversation){
- throw new Error(`Conversation not found with bot id: ${ botId }`)
- }
- const { thread_id: cid, } = conversation
- const bot = await this.bot(botId)
- const { id: _botId, thread_id: tid, } = bot
- if(botId!=_botId)
- throw new Error(`Bot id mismatch: ${ botId }!=${ bot_id }`)
- if(tid!=cid)
- throw new Error(`Conversation mismatch: ${ tid }!=${ cid }`)
- /* execute request */
- const updatedConversation = await mMigrateChat(this, this.#factory, this.#llmServices, conversation)
+ async retireChat(bot_id){
+ const success = await this.#botAgent.migrateChat(bot_id)
/* respond request */
- const response = !!updatedConversation
+ const response = success
? { /* @todo - add frontend instructions to remove migrateChat button */
instruction: null,
responses: [{
agent: 'server',
- message: `I have successfully retired this conversation thread and started a new one.`,
- purpose: 'system',
+ message: `I have successfully retired this conversation.`,
type: 'chat',
}],
success: true,
@@ -589,7 +571,6 @@ class Avatar extends EventEmitter {
responses: [{
agent: 'server',
message: `I'm sorry - I encountered an error while trying to retire this conversation; please try again.`,
- purpose: 'system',
type: 'chat',
}],
success: false,
@@ -611,32 +592,30 @@ class Avatar extends EventEmitter {
* @returns {Object} - The response object { messages, success, error,}
*/
async summarize(fileId, fileName, processStartTime=Date.now()){
- if(this.isMyLife)
- throw new Error('MyLife avatar cannot summarize files.')
- if(!fileId?.length && !fileName?.length)
- throw new Error('File id or name required for summarization.')
- const { bot_id, id: botId, thread_id, } = this.avatar
- const prompt = `Summarize this file document: name=${ fileName }, id=${ fileId }`
- const response = {
- messages: [],
- success: false,
+ /* validate request */
+ this.backupResponse = {
+ message: `I received your request to summarize, but an error occurred in the process. Perhaps try again with another file.`,
+ type: 'system',
}
- try{
- let messages = await mCallLLM(this.#llmServices, { bot_id, thread_id, }, prompt, this.#factory, this)
- messages = messages
- .map(message=>mPruneMessage(botId, message, 'mylife-file-summary', processStartTime))
- .filter(message=>message && message.role!=='user')
- if(!messages.length)
- throw new Error('No valid messages returned from summarization.')
- response.messages.push(...messages)
- response.success = true
- } catch(error) {
- response.messages.push({ content: `Unfortunately, a server error occured: ${error.message}`, role: 'system', })
- response.messages.push({ content: 'Please indicate in a help chat what went wrong. Or one might ask... why can\'t I do that, and I don\'t have a great answer at the moment.', role: 'system', })
- response.error = error
- console.log('ERROR::Avatar::summarize()', error)
+ let success = false
+ /* execute request */
+ responses = await this.#botAgent.summarize(fileId, fileName, processStartTime)
+ /* respond request */
+ if(!responses?.length)
+ responses = [this.backupResponse]
+ else {
+ responses = mPruneMessage(this.avatar.id, responses, 'mylife-file-summary', processStartTime)
+ instructions = {
+ command: 'updateFileSummary',
+ itemId: fileId,
+ }
+ success = true
+ }
+ return {
+ instructions,
+ responses,
+ success,
}
- return response
}
/**
* Get a specified team, its details and _instanced_ bots, by id for the member.
@@ -742,11 +721,11 @@ class Avatar extends EventEmitter {
* Set the active bot id. If not match found in bot list, then defaults back to this.id (avatar).
* @setter
* @requires mBotInstructions
- * @param {string} botId - The requested bot id
+ * @param {string} bot_id - The requested bot id
* @returns {void}
*/
- set activeBotId(botId){
- this.#botAgent.setActiveBot(botId)
+ set activeBotId(bot_id){
+ this.#botAgent.setActiveBot(bot_id)
}
get activeBotNewestVersion(){
const { type, } = this.activeBot
@@ -1215,7 +1194,8 @@ class Q extends Avatar {
if(this.isCreatingAccount)
message = `CREATE ACCOUNT PHASE: ${ message }`
Conversation.prompt = message
- return await this.chatAgentBypass(Conversation)
+ const response = await this.chatAgentBypass(Conversation)
+ return response
}
/**
* OVERLOADED: MyLife must refuse to create bots.
@@ -1237,11 +1217,15 @@ class Q extends Avatar {
const updatedSummary = await botFactory.obscure(iid)
return updatedSummary
}
+ /* overload rejections */
/**
- * OVERLOADED: Refuses to upload to MyLife.
+ * OVERLOADED: Q refuses to execute.
* @public
* @throws {Error} - MyLife avatar cannot upload files.
*/
+ summarize(){
+ throw new Error('MyLife avatar cannot summarize files.')
+ }
upload(){
throw new Error('MyLife avatar cannot upload files.')
}
@@ -1429,7 +1413,7 @@ async function mCast(factory, cast){
}
function mCreateSystemMessage(activeBot, message, factory){
if(!(message instanceof factory.message)){
- const { id: botId, thread_id, } = activeBot
+ const { id: bot_id, thread_id, } = activeBot
const content = message?.content ?? message?.message ?? message
message = new (factory.message)({
being: 'message',
@@ -1439,39 +1423,9 @@ function mCreateSystemMessage(activeBot, message, factory){
type: 'system'
})
}
- message = mPruneMessage(botId, message, 'system')
+ message = mPruneMessage(bot_id, message, 'system')
return message
}
-/**
- * Deletes conversation and updates
- * @param {Conversation} conversation - The conversation object
- * @param {Conversation[]} conversations - The conversations array
- * @param {Object} bot - The bot involved in the conversation
- * @param {AgentFactory} factory - Agent Factory object
- * @param {LLMServices} llm - OpenAI object
- * @returns {Promise} - `true` if successful
- */
-async function mDeleteConversation(conversation, conversations, bot, factory, llm){
- const { id, } = conversation
- /* delete conversation from memory */
- const conversationId = conversations.findIndex(_conversation=>_conversation.id===id)
- if(conversationId<0)
- throw new Error('Conversation not found in conversations.')
- conversations.splice(conversationId, 1)
- /* delete thread_id from bot and save to Cosmos */
- bot.thread_id = ''
- const { id: botId, thread_id, } = bot
- factory.updateBot({
- id: botId,
- thread_id,
- })
- /* delete conversation from Cosmos */
- const deletedConversation = await factory.deleteItem(conversation.id)
- /* delete thread from LLM */
- const deletedThread = await llm.deleteThread(thread_id)
- console.log('mDeleteConversation', conversation.id, deletedConversation, thread_id, deletedThread)
- return true
-}
/**
* Takes character data and makes necessary adjustments to roles, urls, etc.
* @todo - icon and background changes
@@ -1991,118 +1945,6 @@ async function mInit(factory, llmServices, avatar, botAgent, assetAgent){
/* lived-experiences */
avatar.experiencesLived = await factory.experiencesLived(false)
}
-/**
- * Migrates specified conversation (by thread_id) and returns conversation with new thread processed and saved to bot, both as a document and in avatar memory.
- * @param {Avatar} avatar - Avatar object
- * @param {AgentFactory} factory - AgentFactory object
- * @param {LLMServices} llm - OpenAI object
- * @param {string} thread_id - The thread_id of the conversation
- * @returns
- */
-async function mMigrateChat(avatar, factory, llm, conversation){
- /* constants and variables */
- const { thread_id, } = conversation
- const chatLimit=25
- let messages = await llm.messages(thread_id) // @todo - limit to 25 messages or modify request
- if(!messages?.length)
- return conversation
- const { botId, } = conversation
- const bot = await avatar.bot(botId)
- const botType = bot.type
- let disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
- itemCollectionTypes='item',
- itemLimit=1000,
- type='item'
- switch(botType){
- case 'biographer':
- case 'personal-biographer':
- type = 'memory'
- itemCollectionTypes = `memory,story,narrative`
- break
- case 'diary':
- case 'journal':
- case 'journaler':
- type = 'entry'
- const itemType = botType==='journaler'
- ? 'journal'
- : botType
- itemCollectionTypes = `${ itemType },entry,`
- break
- default:
- break
- }
- const chatSummary=`## ${ type.toUpperCase() } CHAT SUMMARY\n`,
- chatSummaryRegex = /^## [^\n]* CHAT SUMMARY\n/,
- itemSummary=`## ${ type.toUpperCase() } LIST\n`,
- itemSummaryRegex = /^## [^\n]* LIST\n/
- const items = ( await avatar.collections(type) )
- .sort((a, b)=>a._ts-b._ts)
- .slice(0, itemLimit)
- const itemList = items
- .map(item=>`- itemId: ${ item.id } :: ${ item.title }`)
- .join('\n')
- const itemCollectionList = items
- .map(item=>item.id)
- .join(',')
- .slice(0, 512) // limit for metadata string field
- const metadata = {
- bot_id: botId,
- conversation_id: conversation.id,
- }
- /* prune messages source material */
- messages = messages
- .slice(0, chatLimit)
- .map(message=>{
- const { content: contentArray, id, metadata, role, } = message
- const content = contentArray
- .filter(_content=>_content.type==='text')
- .map(_content=>_content.text?.value)
- ?.[0]
- return { content, id, metadata, role, }
- })
- .filter(message=>!itemSummaryRegex.test(message.content))
- const summaryMessage = messages
- .filter(message=>!chatSummaryRegex.test(message.content))
- .map(message=>message.content)
- .join('\n')
- /* contextualize previous content */
- const summaryMessages = []
- /* summary of items */
- if(items.length)
- summaryMessages.push({
- content: itemSummary + disclaimer + itemList,
- metadata: {
- collectionList: itemCollectionList,
- collectiontypes: itemCollectionTypes,
- },
- role: 'assistant',
- })
- /* summary of messages */
- if(summaryMessage.length)
- summaryMessages.push({
- content: chatSummary + disclaimer + summaryMessage,
- metadata: {
- collectiontypes: itemCollectionTypes,
- },
- role: 'assistant',
- })
- if(!summaryMessages.length)
- return conversation
- /* add messages to new thread */
- const newThread = await llm.thread(null, summaryMessages.reverse(), metadata)
- conversation.setThread(newThread)
- bot.thread_id = conversation.thread_id
- const _bot = {
- id: bot.id,
- thread_id: conversation.thread_id,
- }
- factory.updateBot(_bot) // removed await
- if(mAllowSave)
- conversation.save()
- else
- console.log('migrateChat::BYPASS-SAVE', conversation.thread_id)
- return conversation
-}
/**
* Get experience scene navigation array.
* @getter
@@ -2206,17 +2048,17 @@ function mPruneMessage(activeBotId, message, type='chat', processStartTime=Date.
}
/**
* Prune an array of Messages and return.
- * @param {Guid} botId - The Active Bot id property
+ * @param {Guid} bot_id - The Active Bot id property
* @param {Object[]} messageArray - The array of messages to prune
* @param {string} type - The type of message, defaults to chat
* @param {number} processStartTime - The time the process started, defaults to function call
* @returns {Object[]} - Concatenated message object
*/
-function mPruneMessages(botId, messageArray, type='chat', processStartTime=Date.now()){
+function mPruneMessages(bot_id, messageArray, type='chat', processStartTime=Date.now()){
if(!messageArray.length)
throw new Error('No messages to prune')
messageArray = messageArray
- .map(message=>mPruneMessage(botId, message, type, processStartTime))
+ .map(message=>mPruneMessage(bot_id, message, type, processStartTime))
return messageArray
}
/**
@@ -2237,7 +2079,7 @@ function mPruneMessages(botId, messageArray, type='chat', processStartTime=Date.
async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInput='NEXT'){
console.log('mReliveMemoryNarration::start', item.id, memberInput)
const { relivingMemories, } = avatar
- const { id: botId, } = Bot
+ const { id: bot_id, } = Bot
const { id, } = item
const processStartTime = Date.now()
let message = `## relive memory itemId: ${ id }\n`
@@ -2253,7 +2095,7 @@ async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInp
console.log(`mReliveMemoryNarration::new reliving memory: ${ id }`)
} else /* opportunity for member interrupt */
message += `MEMBER INPUT: ${ memberInput }\n`
- const { conversation, thread_id, } = relivingMemory
+ const { conversation, } = relivingMemory
console.log(`mReliveMemoryNarration::reliving memory: ${ id }`, message)
let messages = await mCallLLM(llm, conversation, message, factory, avatar)
conversation.addMessages(messages)
@@ -2264,7 +2106,7 @@ async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInp
&& message.type==='chat'
&& message.role!=='user'
})
- .map(message=>mPruneMessage(botId, message, 'chat', processStartTime))
+ .map(message=>mPruneMessage(bot_id, message, 'chat', processStartTime))
const memory = {
id,
messages,
diff --git a/inc/js/mylife-factory.mjs b/inc/js/mylife-factory.mjs
index 1491d098..0f93ca3a 100644
--- a/inc/js/mylife-factory.mjs
+++ b/inc/js/mylife-factory.mjs
@@ -591,7 +591,7 @@ class AgentFactory extends BotFactory {
return avatar
}
/**
- * Generates via personal intelligence, nature of consent/protection around itemId or botId.
+ * Generates via personal intelligence, nature of consent/protection around itemId or Bot id.
* @todo - build out consent structure
* @param {Guid} id - The id of the item to generate consent for.
* @param {Guid} requesting_mbr_id - The id of the member requesting consent.
diff --git a/inc/js/routes.mjs b/inc/js/routes.mjs
index e1ade44e..607eaa9f 100644
--- a/inc/js/routes.mjs
+++ b/inc/js/routes.mjs
@@ -123,7 +123,7 @@ _memberRouter.post('/bots/create', createBot)
_memberRouter.post('/bots/activate/:bid', activateBot)
_memberRouter.post('/category', category)
_memberRouter.post('/migrate/bot/:bid', migrateBot)
-_memberRouter.post('/migrate/chat/:tid', migrateChat)
+_memberRouter.post('/migrate/chat/:bid', migrateChat)
_memberRouter.post('/mode', interfaceMode)
_memberRouter.post('/obscure/:iid', obscure)
_memberRouter.post('/passphrase', passphraseReset)
diff --git a/inc/json-schemas/conversation.json b/inc/json-schemas/conversation.json
index 5f1d29bf..b033fa79 100644
--- a/inc/json-schemas/conversation.json
+++ b/inc/json-schemas/conversation.json
@@ -12,30 +12,30 @@
"type": "string",
"$comment": "`action` added after `Experience` was coded as a way to partition logic so that some elements are saved independently"
},
- "botId": {
- "description": "internal system id for bot, used for tracking",
- "format": "uuid",
+ "being": {
"type": "string",
- "$comment": "bot.id, used for tracking"
+ "const": "chat"
+ },
+ "bot_id": {
+ "description": "uuid for Conversation Bot",
+ "format": "uuid",
+ "type": "string"
},
"id": {
+ "description": "uuid for conversation, used for tracking",
+ "format": "uuid",
+ "type": "string"
+ },
+ "llm_id": {
+ "description": "external system id for current Bot",
"type": "string",
- "format": "uuid"
+ "$comment": "OpenAI uses `assistant_%`"
},
"mbr_id": {
"type": "string",
"minLength": 40,
"$comment": "partition-key for member: sysName|core_id"
},
- "parent_id": {
- "type": "string",
- "format": "uuid",
- "$comment": "chat should be attached to avatar.id when known"
- },
- "being": {
- "type": "string",
- "const": "chat"
- },
"messages": {
"type": "array",
"default": [],
From 6d19e796a0ef8acbe56ee9e5ff62d9df1192063f Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 23:31:33 -0400
Subject: [PATCH 29/56] 20241024 @Mookse - relive memory - wip unstable
tutorial/experience
---
inc/js/agents/system/bot-agent.mjs | 75 +++++++++++++++++++++++-----
inc/js/mylife-avatar.mjs | 78 +++++++++---------------------
inc/js/mylife-llm-services.mjs | 22 +++++----
3 files changed, 97 insertions(+), 78 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index 2b1310fb..368cab70 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -126,10 +126,8 @@ class Bot {
* @returns {Promise}
*/
async setThread(thread_id){
- const llm_id = this.llm_id
- ?? this.bot_id
if(!thread_id?.length)
- thread_id = await this.#llm.createThread(llm_id)
+ thread_id = await mThread(this.#llm)
const { id, } = this
this.thread_id = thread_id
const bot = {
@@ -164,6 +162,9 @@ class Bot {
get isAvatar(){
return mDefaultBotTypeArray.includes(this.type)
}
+ get isBiographer(){
+ return ['personal-biographer', 'biographer'].includes(this.type)
+ }
get isMyLife(){
return this.#factory.isMyLife
}
@@ -301,6 +302,40 @@ class BotAgent {
const greetings = await this.activeBot.getGreeting(dynamic, this.#llm, this.#factory)
return greetings
}
+ /**
+ * Begins or continues a living memory conversation.
+ * @param {Object} item - Memory item from database
+ * @param {String} memberInput - The member input (with instructions)
+ * @param {Object} livingMemory - The living memory object: { Conversation, id, item, }
+ * @returns {Object} - The living memory object
+ */
+ async liveMemory(item, memberInput='', livingMemory){
+ const { biographer, } = this
+ let message = `## LIVE Memory\n`
+ if(!livingMemory){
+ const { bot_id: _llm_id, id: bot_id, type, } = biographer
+ const { llm_id=_llm_id, } = biographer
+ const messages = []
+ messages.push({
+ content: `## MEMORY SUMMARY, ID=${ item.id }\n## FOR REFERENCE ONLY\n${ item.summary }\n`,
+ role: 'user',
+ })
+ memberInput = message + memberInput
+ const Conversation = await mConversationStart('memory', type, bot_id, null, llm_id, this.#llm, this.#factory, memberInput, messages)
+ Conversation.action = 'living'
+ livingMemory = {
+ Conversation,
+ id: this.#factory.newGuid,
+ item,
+ }
+ }
+ const { Conversation, } = livingMemory
+ Conversation.prompt = memberInput?.trim()?.length
+ ? memberInput
+ : message
+ await mCallLLM(Conversation, false, this.#llm, this.#factory)
+ return livingMemory
+ }
/**
* Migrates a bot to a new, presumed combined (with internal or external) bot.
* @param {Guid} bot_id - The bot id
@@ -419,6 +454,15 @@ class BotAgent {
get avatarId(){
return this.#avatarId
}
+ /**
+ * Gets the Biographer bot for the BotAgent.
+ * @getter
+ * @returns {Bot} - The Biographer Bot instance
+ */
+ get biographer(){
+ const Biographer = this.#bots.find(bot=>bot.isBiographer)
+ return Biographer
+ }
/**
* Gets the array of bots employed by this BotAgent.
* @getter
@@ -848,8 +892,7 @@ async function mBotUpdate(factory, llm, bot, options={}){
* @returns {Promise} - Alters Conversation instance by nature
*/
async function mCallLLM(Conversation, allowSave=true, llm, factory, avatar){
- const { llm_id, originalPrompt, processStartTime=Date.now(), prompt, thread_id, } = Conversation
- console.log('mCallLLM', llm_id, thread_id, prompt, processStartTime)
+ const { llm_id, originalPrompt, processStartTime=Date.now(), prompt, thread_id, } = Conversation
if(!llm_id?.length)
throw new Error('No `llm_id` intelligence id found in Conversation for `mCallLLM`.')
if(!thread_id?.length)
@@ -906,12 +949,14 @@ async function mConversationDelete(Conversation, factory, llm){
* @param {LLMServices} llm - The LLMServices instance
* @param {AgentFactory} factory - Agent Factory object
* @param {string} prompt - The prompt for the conversation (optional)
- * @param {number} processStartTime - The time the processing started, defaults to `Date.now()`
+ * @param {Message[]} messages - The array of messages to seed the conversation
* @returns {Conversation} - The conversation object
*/
-async function mConversationStart(type='chat', form='system', bot_id, thread_id, llm_id, llm, factory, prompt, processStartTime=Date.now()){
+async function mConversationStart(type='chat', form='system', bot_id, thread_id, llm_id, llm, factory, prompt, messages){
const { mbr_id, newGuid: id, } = factory
- const thread = await mThread(thread_id, llm)
+ const metadata = { bot_id, conversation_id: id, mbr_id, },
+ processStartTime = Date.now(),
+ thread = await mThread(llm, thread_id, messages, metadata)
const Conversation = new (factory.conversation)(
{
form,
@@ -1141,7 +1186,7 @@ async function mMigrateChat(Bot, llm){
if(!summaryMessages.length)
return
/* add messages to new thread */
- Conversation.setThread( await llm.thread(null, summaryMessages.reverse(), metadata) )
+ Conversation.setThread( await mThread(llm, null, summaryMessages.reverse(), metadata) )
await Bot.setThread(Conversation.thread_id) // autosaves `thread_id`, no `await`
console.log('mMigrateChat::SUCCESS', Bot.thread_id, Conversation.inspect(true))
if(mAllowSave)
@@ -1149,9 +1194,15 @@ async function mMigrateChat(Bot, llm){
else
console.log('mMigrateChat::BYPASS-SAVE', Conversation.thread_id)
}
-async function mThread(thread_id, llm){
- const messages = []
- const metadata = {}
+/**
+ * Gets or creates a new thread in LLM provider.
+ * @param {LLMServices} llm - The LLMServices instance
+ * @param {String} thread_id - The thread id (optional)
+ * @param {Messages[]} messages - The array of messages to seed the thread (optional)
+ * @param {Object} metadata - The metadata object (optional)
+ * @returns {Promise} - The thread object
+ */
+async function mThread(llm, thread_id, messages, metadata){
const thread = await llm.thread(thread_id, messages, metadata)
return thread
}
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index d07702b6..93a4b6d6 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -36,10 +36,10 @@ class Avatar extends EventEmitter {
#factory // do not expose
#livedExperiences = [] // array of ids for lived experiences
#livingExperience
+ #livingMemory
#llmServices
#mode = 'standard' // interface-mode from module `mAvailableModes`
#nickname // avatar nickname, need proxy here as g/setter is "complex"
- #relivingMemories = [] // array of active reliving memories, with items, maybe conversations, included
#vectorstoreId // vectorstore id for avatar
/**
* @constructor
@@ -208,24 +208,15 @@ class Avatar extends EventEmitter {
return await this.#factory.deleteItem(id)
}
/**
- * End a memory.
+ * End the living memory, if running.
* @async
* @public
* @todo - save conversation fragments
- * @param {Guid} id - The id of the memory to end.
- * @returns {boolean} - true if memory ended successfully.
+ * @returns {void}
*/
- async endMemory(id){
+ async endMemory(){
// @stub - save conversation fragments */
- const { relivingMemories, } = this
- const index = relivingMemories.findIndex(item=>item.id===id)
- if(index>=0){
- const removedMemory = relivingMemories.splice(index, 1)
- if(!removedMemory.length)
- return false
- console.log('item removed', removedMemory?.[0] ?? `index: ${ index } failed`)
- }
- return true
+ this.#livingMemory = null
}
/**
* Ends an experience.
@@ -503,10 +494,10 @@ class Avatar extends EventEmitter {
const { id, } = item
if(!id)
throw new Error(`item does not exist in member container: ${ iid }`)
- /* develop narration */
- const Biographer = this.#botAgent.biographer
- const narration = await mReliveMemoryNarration(this, this.#factory, this.#llmServices, Biographer, item, memberInput)
- return narration // include any required .map() pruning
+ const narration = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this.#llmServices, this.#factory, this)
+ // include any required .map() pruning
+ console.log('reliveMemory::narration', narration)
+ return narration
}
/**
* Allows member to reset passphrase.
@@ -1109,12 +1100,12 @@ class Avatar extends EventEmitter {
this.#nickname = nickname
}
/**
- * Get the `active` reliving memories.
+ * Get the `active` reliving memory.
* @getter
* @returns {object[]} - The active reliving memories.
*/
- get relivingMemories(){
- return this.#relivingMemories
+ get livingMemory(){
+ return this.#livingMemory
}
get registrationId(){
return this.#factory.registrationId
@@ -2068,45 +2059,20 @@ function mPruneMessages(bot_id, messageArray, type='chat', processStartTime=Date
* - others are common to living, but with `reliving`, the biographer bot (only narrator allowed in .10) incorporate any user-contributed contexts or imrpovements to the memory summary that drives the living and sharing. All by itemId.
* - if user "interrupts" then interruption content should be added to memory updateSummary; doubt I will keep work interrupt, but this too is hopefully able to merely be embedded in the biographer bot instructions.
* Currently testing efficacy of all instructions (i.e., no callbacks, as not necessary yet) being embedded in my biog-bot, `madrigal`.
- * @param {Avatar} avatar - Member's avatar object.
- * @param {AgentFactory} factory - Member's AgentFactory object.
- * @param {LLMServices} llm - OpenAI object.
- * @param {Bot} Bot - The relevant bot instance
- * @param {object} item - The memory object.
+ * @param {object} item - The memory object
* @param {string} memberInput - The member input (or simply: NEXT, SKIP, etc.)
- * @returns {Promise} - The reliving memory object for frontend to execute.
+ * @param {BotAgent} BotAgent - The Bot Agent instance
+ * @param {Avatar} avatar - Member Avatar instance
+ * @returns {Promise} - The reliving memory object for frontend to execute
*/
-async function mReliveMemoryNarration(avatar, factory, llm, Bot, item, memberInput='NEXT'){
- console.log('mReliveMemoryNarration::start', item.id, memberInput)
- const { relivingMemories, } = avatar
- const { id: bot_id, } = Bot
+async function mReliveMemoryNarration(item, memberInput, BotAgent, avatar){
const { id, } = item
- const processStartTime = Date.now()
- let message = `## relive memory itemId: ${ id }\n`
- let relivingMemory = relivingMemories.find(reliving=>reliving.item.id===id)
- if(!relivingMemory){ /* create new activated reliving memory */
- const conversation = await avatar.conversationStart('memory', 'member-avatar')
- relivingMemory = {
- conversation,
- id,
- item,
- }
- relivingMemories.push(relivingMemory)
- console.log(`mReliveMemoryNarration::new reliving memory: ${ id }`)
- } else /* opportunity for member interrupt */
- message += `MEMBER INPUT: ${ memberInput }\n`
- const { conversation, } = relivingMemory
- console.log(`mReliveMemoryNarration::reliving memory: ${ id }`, message)
- let messages = await mCallLLM(llm, conversation, message, factory, avatar)
- conversation.addMessages(messages)
+ avatar.livingMemory = await BotAgent.liveMemory(item, memberInput, avatar.livingMemory)
+ const { Conversation, } = avatar.livingMemory
+ const { bot_id, type, } = Conversation
/* frontend mutations */
- messages = conversation.messages
- .filter(message=>{ // limit to current chat response(s); usually one, perhaps faithfully in future [or could be managed in LLM]
- return messages.find(_message=>_message.id===message.id)
- && message.type==='chat'
- && message.role!=='user'
- })
- .map(message=>mPruneMessage(bot_id, message, 'chat', processStartTime))
+ const messages = Conversation.getMessages()
+ .map(message=>mPruneMessage(bot_id, message, type))
const memory = {
id,
messages,
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index 9e95b8b8..2262894e 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -318,7 +318,7 @@ async function mRunFinish(llmServices, run, factory, avatar){
* @returns {object} - [OpenAI run object](https://platform.openai.com/docs/api-reference/runs/object)
* @throws {Error} - If tool function not recognized
*/
-async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
+async function mRunFunctions(openai, run, factory, avatar){
try{
if(
run.required_action?.type=='submit_tool_outputs'
@@ -439,21 +439,23 @@ async function mRunFunctions(openai, run, factory, avatar){ // add avatar ref
case 'getsummary':
case 'get_summary':
case 'get summary':
- console.log('mRunFunctions()::getSummary::begin', itemId)
- avatar.backupResponse = {
- message: `I'm sorry, I couldn't finding this summary. I believe the issue might have been temporary. Would you like me to try again?`,
- type: 'system',
- }
+ console.log('mRunFunctions()::getSummary::begin', itemId, avatar)
+ if(avatar)
+ avatar.backupResponse = {
+ message: `I'm sorry, I couldn't finding this summary. I believe the issue might have been temporary. Would you like me to try again?`,
+ type: 'system',
+ }
let { summary: _getSummary, title: _getSummaryTitle, } = item
?? {}
if(!_getSummary?.length){
action = `error getting summary for itemId: ${ itemId ?? 'missing itemId' } - halt any further processing and instead ask user to paste summary into chat and you will continue from there to incorporate their message.`
_getSummary = 'no summary found for itemId'
} else {
- avatar.backupResponse = {
- message: `I was able to retrieve the summary indicated.`,
- type: 'system',
- }
+ if(avatar)
+ avatar.backupResponse = {
+ message: `I was able to retrieve the summary indicated.`,
+ type: 'system',
+ }
action = `with the summary in this JSON payload, incorporate the most recent member request into a new summary and run the \`updateSummary\` function and follow its action`
success = true
}
From 6b9517956ade16ddf416757b8bc964e43caa89b0 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 24 Oct 2024 23:43:54 -0400
Subject: [PATCH 30/56] 20241024 @Mookse - cosmetic
---
inc/js/mylife-avatar.mjs | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 93a4b6d6..27d9e439 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -1353,13 +1353,13 @@ function mAvatarDropdown(globals, avatar){
/**
* Cancels openAI run.
* @module
- * @param {LLMServices} llmServices - OpenAI object
+ * @param {LLMServices} llm - The LLMServices instance
* @param {string} thread_id - Thread id
* @param {string} runId - Run id
* @returns {object} - [OpenAI run object](https://platform.openai.com/docs/api-reference/runs/object)
*/
-async function mCancelRun(llmServices, thread_id, runId,){
- return await llmServices.beta.threads.runs.cancel(
+async function mCancelRun(llm, thread_id, runId,){
+ return await llm.beta.threads.runs.cancel(
thread_id,
runId
)
From 39837890939475d8e4bea35fa5a4186cd106f26d Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 25 Oct 2024 17:55:40 -0400
Subject: [PATCH 31/56] 20241024 @Mookse - retireBot tested - instructions and
options fix - globals.sanitize() update
---
inc/js/agents/system/bot-agent.mjs | 156 ++++++++++++++++++-----------
inc/js/functions.mjs | 23 ++---
inc/js/globals.mjs | 8 +-
inc/js/mylife-avatar.mjs | 54 ++++++++--
inc/js/mylife-dataservices.mjs | 14 +--
inc/js/mylife-llm-services.mjs | 31 +++---
inc/js/routes.mjs | 2 +-
views/assets/js/bots.mjs | 4 +-
8 files changed, 184 insertions(+), 108 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index 368cab70..cb5d764d 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -28,14 +28,28 @@ class Bot {
#collectionsAgent
#conversation
#factory
+ #instructionNodes = new Set()
#llm
constructor(botData, llm, factory){
+ const { id=factory.newGuid, type=mDefaultBotType, } = botData
this.#factory = factory
this.#llm = llm
botData = this.globals.sanitize(botData)
Object.assign(this, botData)
- if(!this.id)
- throw new Error('Bot database id required')
+ this.id = id
+ this.type = type
+ switch(type){
+ case 'diary':
+ case 'journal':
+ case 'journaler':
+ this.#instructionNodes.add('interests')
+ this.#instructionNodes.add('flags')
+ break
+ case 'personal-biographer':
+ default:
+ this.#instructionNodes.add('interests')
+ break
+ }
// @stub - this.#collectionsAgent = new CollectionsAgent(llm, factory)
}
/* public functions */
@@ -111,11 +125,14 @@ class Bot {
* @param {object} botOptions - Options for updating
* @returns
*/
- async update(botData, botOptions){
+ async update(botData, botOptions={}){
/* validate request */
this.globals.sanitize(botData)
- const Bot = await mBotUpdate(this.#factory, this.#llm, botData, botOptions)
- return this.globals.sanitize(Bot)
+ /* execute request */
+ botOptions.instructions = Object.keys(botData).some(key => this.#instructionNodes.has(key))
+ const bot = await mBotUpdate(botData, botOptions, this, this.#llm, this.#factory)
+ /* respond request */
+ return this.globals.sanitize(bot)
}
async save(){
@@ -138,15 +155,17 @@ class Bot {
}
/* getters/setters */
/**
- * Gets the frontend bot object. If full instance is required, use `getBot()`.
+ * Gets the frontend bot object.
* @getter
*/
get bot() {
- const { bot_name, description, id, purpose, type, version } = this
+ const { bot_name: name, description, flags, id, interests, purpose, type, version } = this
const bot = {
- bot_name,
description,
+ flags,
id,
+ interests,
+ name,
purpose,
type,
version,
@@ -159,6 +178,18 @@ class Bot {
get globals(){
return this.#factory.globals
}
+ get instructionNodes(){
+ return this.#instructionNodes
+ }
+ set instructionNodes(instructionNode){
+ this.#instructionNodes.add(instructionNode)
+ }
+ get instructionNodeValues(){
+ return [...this.#instructionNodes].reduce((acc, key)=>{
+ acc[key] = this[key]
+ return acc
+ }, {})
+ }
get isAvatar(){
return mDefaultBotTypeArray.includes(this.type)
}
@@ -260,7 +291,7 @@ class BotAgent {
* @returns {Promise} - Whether or not operation was successful
*/
async botDelete(bot_id){
- if(!this.#factory.isMyLife)
+ if(this.#factory.isMyLife)
return false
const success = await mBotDelete(bot_id, this, this.#llm, this.#factory)
return success
@@ -402,12 +433,12 @@ class BotAgent {
* @param {object} botOptions - Options for updating the bot
* @returns {Promise} - The updated Bot instance
*/
- async updateBot(botData, botOptions={}){
+ async updateBot(botData, botOptions){
const { id, } = botData
if(!this.globals.isValidGuid(id))
throw new Error('`id` parameter required')
const Bot = this.#bots.find(bot=>bot.id===id)
- if(!!Bot)
+ if(!Bot)
throw new Error(`Bot not found with id: ${ id }`)
Bot.update(botData, botOptions)
return Bot
@@ -604,7 +635,7 @@ async function mBotCreate(avatarId, vectorstore_id, botData, llm, factory){
const { type, } = botData
if(!avatarId?.length || !type?.length)
throw new Error('avatar id and type required to create bot')
- const { instructions, version=1.0, } = mBotInstructions(factory, type)
+ const { instructions, version=1.0, } = mBotInstructions(factory, botData)
const model = process.env.OPENAI_MODEL_CORE_BOT
?? process.env.OPENAI_MODEL_CORE_AVATAR
?? 'gpt-4o'
@@ -644,9 +675,9 @@ async function mBotCreate(avatarId, vectorstore_id, botData, llm, factory){
validBotData.bot_id = bot_id
validBotData.thread_id = thread_id
botData = await factory.createBot(validBotData) // repurposed incoming botData
- const Bot = new Bot(botData, llm, factory)
- console.log(chalk.green(`bot created::${ type }`), Bot.thread_id, Bot.id, Bot.bot_id, Bot.bot_name )
- return Bot
+ const _Bot = new Bot(botData, llm, factory)
+ console.log(`bot created::${ type }`, _Bot.thread_id, _Bot.id, _Bot.bot_id, _Bot.bot_name )
+ return _Bot
}
/**
* Creates bot and returns associated `bot` object.
@@ -672,7 +703,8 @@ async function mBotCreateLLM(botData, llm){
*/
async function mBotDelete(bot_id, BotAgent, llm, factory){
const Bot = BotAgent.bot(bot_id)
- const { id, llm_id, type, thread_id, } = Bot
+ const { bot_id: _llm_id, id, type, thread_id, } = Bot
+ const { llm_id=_llm_id, } = Bot
const cannotRetire = ['actor', 'system', 'personal-avatar']
if(cannotRetire.includes(type))
return false
@@ -681,10 +713,12 @@ async function mBotDelete(bot_id, BotAgent, llm, factory){
const botIndex = bots.findIndex(bot=>bot.id===id)
bots.splice(botIndex, 1)
/* delete bot from Cosmos */
- factory.deleteItem(id)
+ await factory.deleteItem(id)
/* delete thread and bot from LLM */
- llm.deleteBot(llm_id)
- llm.deleteThread(thread_id)
+ if(llm_id?.length)
+ await llm.deleteBot(llm_id)
+ if(thread_id?.length)
+ await llm.deleteThread(thread_id)
return true
}
/**
@@ -728,15 +762,17 @@ async function mBotGreeting(dynamic=false, Bot, llm, factory){
* Returns MyLife-version of bot instructions.
* @module
* @param {AgentFactory} factory - The Factory instance
- * @param {String} type - The type of Bot to create
+ * @param {Object} botData - The bot proto-data
* @returns {object} - The intermediary bot instructions object: { instructions, version, }
*/
-function mBotInstructions(factory, type=mDefaultBotType){
+function mBotInstructions(factory, botData={}){
+ const { type=mDefaultBotType, } = botData
let {
instructions,
limit=8000,
version,
- } = factory.botInstructions(type) ?? {}
+ } = factory.botInstructions(type)
+ ?? {}
if(!instructions) // @stub - custom must have instruction loophole
throw new Error(`bot instructions not found for type: ${ type }`)
let {
@@ -777,7 +813,7 @@ function mBotInstructions(factory, type=mDefaultBotType){
/* apply replacements */
replacements.forEach(replacement=>{
const placeholderRegExp = factory.globals.getRegExp(replacement.name, true)
- const replacementText = eval(`bot?.${replacement.replacement}`)
+ const replacementText = eval(`botData?.${replacement.replacement}`)
?? eval(`factory?.${replacement.replacement}`)
?? eval(`factory.core?.${replacement.replacement}`)
?? replacement?.default
@@ -788,7 +824,7 @@ function mBotInstructions(factory, type=mDefaultBotType){
references.forEach(_reference=>{
const _referenceText = _reference.insert
const replacementText = eval(`factory?.${_reference.value}`)
- ?? eval(`bot?.${_reference.value}`)
+ ?? eval(`botData?.${_reference.value}`)
?? _reference.default
?? '`unknown-value`'
switch(_reference.method ?? 'replace'){
@@ -826,57 +862,59 @@ function mBotInstructions(factory, type=mDefaultBotType){
}
/**
* Updates bot in Cosmos, and if necessary, in LLM. Returns unsanitized bot data document.
- * @param {AgentFactory} factory - Factory object
- * @param {LLMServices} llm - The LLMServices instance
- * @param {object} bot - Bot object, winnow via mBot in `mylife-avatar.mjs` to only updated fields
+ * @module
+ * @param {object} botData - Bot data update object
* @param {object} options - Options object: { instructions: boolean, model: boolean, tools: boolean, vectorstoreId: string, }
+ * @param {Bot} Bot - The Bot instance
+ * @param {LLMServices} llm - The LLMServices instance
+ * @param {AgentFactory} factory - Factory instance
* @returns
*/
-async function mBotUpdate(factory, llm, bot, options={}){
- /* constants */
+async function mBotUpdate(botData, options={}, Bot, llm, factory){
+ /* validate request */
+ if(!Bot)
+ throw new Error('Bot instance required to update bot')
+ const { bot_id, id, llm_id, metadata={}, type, vectorstoreId, } = Bot
+ const _llm_id = llm_id
+ ?? bot_id // @stub - deprecate bot_id
const {
- id, // no modifications
- instructions: removeInstructions,
- tools: removeTools,
- tool_resources: removeResources,
- type, // no modifications
- ...botData // extract member-driven bot data
- } = bot
+ instructions: discardInstructions,
+ mbr_id, // no modifications allowed
+ name, // no modifications allowed
+ tools: discardTools,
+ tool_resources: discardResources,
+ ...allowedBotData
+ } = botData
const {
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, } = mBotInstructions(factory, bot)
- botData.instructions = instructions
- botData.metadata = botData.metadata ?? {}
- botData.metadata.version = version.toString()
- botData.version = version /* omitted from llm, but appears on updateBot */
+ const instructionReferences = { ...Bot.instructionNodeValues, ...allowedBotData }
+ const { instructions, version=1.0, } = mBotInstructions(factory, instructionReferences)
+ allowedBotData.instructions = instructions
+ allowedBotData.metadata = metadata
+ allowedBotData.metadata.version = version.toString()
+ allowedBotData.version = version /* omitted from llm, but appears on updateBot */
}
if(updateTools){
const { tools, tool_resources, } = mGetAIFunctions(type, factory.globals, vectorstoreId)
- botData.tools = tools
- botData.tool_resources = tool_resources
+ allowedBotData.tools = tools
+ allowedBotData.tool_resources = tool_resources
}
if(updateModel)
- botData.model = factory.globals.currentOpenAIBotModel
- botData.id = id // validated
- /* LLM updates */
- const { bot_id, bot_name: name, instructions, tools, } = botData
- let { llm_id=bot_id, } = botData
- if(llm_id?.length && (instructions || name || tools)){
- botData.model = factory.globals.currentOpenAIBotModel // not dynamic
- botData.llm_id = llm_id
- 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 OpenAI'), id, llm_id, updatedLLMFields)
+ allowedBotData.model = factory.globals.currentOpenAIBotModel
+ allowedBotData.id = id
+ allowedBotData.type = type
+ /* execute request */
+ if(_llm_id?.length && (allowedBotData.instructions || allowedBotData.bot_name?.length || allowedBotData.tools)){
+ allowedBotData.model = factory.globals.currentOpenAIBotModel // not dynamic
+ allowedBotData.llm_id = _llm_id
+ await llm.updateBot(allowedBotData)
}
- botData = await factory.updateBot(botData)
+ botData = await factory.updateBot(allowedBotData)
+ /* respond request */
return botData
}
/**
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index fdfc791b..e0bb714f 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -37,9 +37,14 @@ async function alerts(ctx){
async function bots(ctx){
const { bid, } = ctx.params // bot_id sent in url path
const { avatar } = ctx.state
- const bot = ctx.request.body ?? {}
- const { id, } = bot
+ const bot = ctx.request.body
+ ?? {}
switch(ctx.method){
+ case 'DELETE': // retire bot
+ if(!ctx.Globals.isValidGuid(bid))
+ ctx.throw(400, `missing bot id`)
+ ctx.body = await avatar.retireBot(bid)
+ break
case 'POST': // create new bot
ctx.body = await avatar.createBot(bot)
break
@@ -51,10 +56,8 @@ async function bots(ctx){
if(bid?.length){ // specific bot
ctx.body = await avatar.getBot(ctx.params.bid)
} else {
- const {
- activeBotId,
- bots,
- } = avatar
+ const { activeBotId, } = avatar
+ const bots = await avatar.getBots()
ctx.body = { // wrap bots
activeBotId,
bots,
@@ -286,12 +289,8 @@ async function privacyPolicy(ctx){
* @param {Koa} ctx - Koa Context object
*/
async function retireBot(ctx){
- const { avatar, } = ctx.state
- const { bid, } = ctx.params // bot id
- if(!ctx.Globals.isValidGuid(bid))
- ctx.throw(400, `missing bot id`)
- const response = await avatar.retireBot(bid)
- ctx.body = response
+ ctx.method = 'DELETE'
+ return await this.bots(ctx)
}
/**
* Direct request from member to retire a chat (via bot).
diff --git a/inc/js/globals.mjs b/inc/js/globals.mjs
index 8a180035..2a5ca6d3 100644
--- a/inc/js/globals.mjs
+++ b/inc/js/globals.mjs
@@ -314,7 +314,7 @@ class Globals extends EventEmitter {
return typeof version === 'string' && regex.test(version)
}
/**
- * Sanitize an object by removing forbidden Cosmos fields.
+ * Sanitize an object by removing forbidden Cosmos fields and undefined/null values.
* @param {object} object - Cosmos document to sanitize
* @returns {object} - Sanitized data object
*/
@@ -323,7 +323,11 @@ class Globals extends EventEmitter {
throw new Error('Parameter requires an object')
const sanitizedData = Object.fromEntries(
Object.entries(object)
- .filter(([key, value])=>!mForbiddenCosmosFields.some(char => key.startsWith(char)))
+ .filter(([key, value])=>
+ !mForbiddenCosmosFields.some(char => key.startsWith(char)) &&
+ value !== null &&
+ value !== undefined
+ )
)
return sanitizedData
}
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 27d9e439..ffbece00 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -325,6 +325,15 @@ class Avatar extends EventEmitter {
const bot = this.#botAgent.bot(bot_id)?.bot
return bot
}
+ /**
+ * Returns pruned Bots for Member Avatar.
+ * @returns
+ */
+ getBots(){
+ const bots = this.bots
+ .map(Bot=>Bot.bot)
+ return bots
+ }
/**
* Gets Conversation object. If no thread id, creates new conversation.
* @param {string} thread_id - openai thread id (optional)
@@ -539,6 +548,36 @@ class Avatar extends EventEmitter {
}
return response
}
+ /**
+ * Retire a Bot, deleting altogether.
+ * @param {Guid} bot_id - The bot id
+ * @returns {object} - The response object { instruction, responses, success, }
+ */
+ async retireBot(bot_id){
+ if(!this.globals.isValidGuid(bot_id))
+ throw new Error(`Invalid bot id: ${ bot_id }`)
+ const success = await this.#botAgent.botDelete(bot_id)
+ const response = {
+ instruction: {
+ command: success ? 'retireBot' : 'error',
+ id: bot_id,
+ },
+ responses: [success
+ ? {
+ agent: 'server',
+ message: `I have removed this bot from the team.`,
+ type: 'chat',
+ }
+ : {
+ agent: 'server',
+ message: `I'm sorry - I encountered an error while trying to retire this bot; please try again.`,
+ type: 'system',
+ }
+ ],
+ success,
+ }
+ return response
+ }
/**
* Currently only proxy for `migrateChat`.
* @param {string} bot_id - Bot id with Conversation to retire
@@ -629,12 +668,12 @@ class Avatar extends EventEmitter {
/**
* Update a specific bot.
* @async
- * @param {object} botData - Bot data to set
- * @returns {Promise} - The updated bot
+ * @param {Object} botData - Bot data to set
+ * @returns {Promise} - The updated bot
*/
async updateBot(botData){
- const bot = await this.#botAgent.updateBot(botData)
- return bot
+ const Bot = await this.#botAgent.updateBot(botData)
+ return Bot.bot
}
/**
* Update instructions for bot-assistant based on type. Default updates all LLM pertinent properties.
@@ -711,7 +750,6 @@ class Avatar extends EventEmitter {
/**
* Set the active bot id. If not match found in bot list, then defaults back to this.id (avatar).
* @setter
- * @requires mBotInstructions
* @param {string} bot_id - The requested bot id
* @returns {void}
*/
@@ -786,7 +824,7 @@ class Avatar extends EventEmitter {
?? this.core.birth?.[0]?.place
}
/**
- * Returns avatar Bot instances.
+ * Returns Member Avatar's Bot instances.
* @getter
* @returns {Bot[]} - Array of Bot instances
*/
@@ -913,7 +951,9 @@ class Avatar extends EventEmitter {
* @returns {array} - The help bots.
*/
get helpBots(){
- return this.bots.filter(bot=>bot.type==='help')
+ const bots = this.getBots()
+ .filter(bot=>bot.type==='help')
+ return bots
}
/**
* Test whether avatar session is creating an account.
diff --git a/inc/js/mylife-dataservices.mjs b/inc/js/mylife-dataservices.mjs
index ba3c7e0f..cd0f21d1 100644
--- a/inc/js/mylife-dataservices.mjs
+++ b/inc/js/mylife-dataservices.mjs
@@ -654,16 +654,12 @@ class Dataservices {
* @returns {object} - The bot document
*/
async updateBot(botData){
- const { id, type: discardType, ...updateBotData } = botData
- if(!Object.keys(updateBotData))
+ const { id, type: discardType='avatar', ...updateBotData } = botData
+ if(!Object.keys(updateBotData).length)
return botData
- const chunks = this.globals.chunkArray(updateKeys, 10)
- for(const chunk of chunks){
- const chunkData = {}
- chunk.forEach(key=>(chunkData[key] = updateBotData[key]))
- const patchedData = await this.patch(id, chunkData)
- console.log('updateBot()::patch', patchedData)
- }
+ if(updateBotData.bot_name?.length)
+ updateBotData.name = `bot_${ discardType }_${ updateBotData.bot_name }_${ id }`
+ botData = await this.patch(id, updateBotData)
return botData
}
/**
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index 2262894e..6df59526 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -37,9 +37,9 @@ class LLMServices {
* @param {object} bot - The bot data
* @returns {Promise} - openai assistant object
*/
- async createBot(bot){
- bot = mValidateAssistantData(bot) // throws on improper format
- bot = await this.openai.beta.assistants.create(bot)
+ async createBot(botData){
+ botData = mValidateAssistantData(botData)
+ const bot = await this.openai.beta.assistants.create(botData)
const thread = await mThread(this.openai)
bot.thread_id = thread.id
return bot
@@ -160,15 +160,15 @@ class LLMServices {
/**
* Updates assistant with specified data. Example: Tools object for openai: { tool_resources: { file_search: { vector_store_ids: [vectorStore.id] } }, }; https://platform.openai.com/docs/assistants/tools/file-search/quickstart?lang=node.js
* @todo - conform payload to OpenAI API Reference
- * @param {string} bot - The bot object data.
+ * @param {Object} botData - The bot object data.
* @returns {Promise} - openai assistant object.
*/
- async updateBot(bot){
- let { botId, bot_id, llm_id, ...assistantData } = bot
+ async updateBot(botData){
+ let { bot_id, llm_id, ...assistantData } = botData
if(!llm_id?.length)
throw new Error('No bot ID provided for update')
- assistantData = mValidateAssistantData(assistantData) // throws on improper format
- const assistant = await this.openai.beta.assistants.update(llm_id, assistantData)
+ botData = mValidateAssistantData(assistantData)
+ const assistant = await this.openai.beta.assistants.update(llm_id, botData)
return assistant
}
/**
@@ -760,13 +760,9 @@ function mValidateAssistantData(data){
version,
} = data
const name = bot_name
- ?? gptName // bot_name internal mylife-alias for openai `name`
- delete metadata.created
+ ?? gptName
+ metadata.id = id
metadata.updated = `${ Date.now() }` // metadata nodes must be strings
- if(id)
- metadata.id = id
- else
- metadata.created = `${ Date.now() }`
const assistantData = {
description,
instructions,
@@ -776,8 +772,11 @@ function mValidateAssistantData(data){
tools,
tool_resources,
}
- if(!Object.keys(assistantData).length)
- throw new Error('Assistant data does not have the correct structure.')
+ Object.keys(assistantData).forEach(key => {
+ if (assistantData[key] === undefined) {
+ delete assistantData[key]
+ }
+ })
return assistantData
}
/* exports */
diff --git a/inc/js/routes.mjs b/inc/js/routes.mjs
index 607eaa9f..f900fd49 100644
--- a/inc/js/routes.mjs
+++ b/inc/js/routes.mjs
@@ -100,6 +100,7 @@ _apiRouter.post('/upload', upload)
_apiRouter.post('/upload/:mid', upload)
/* member routes */
_memberRouter.use(memberValidation)
+_memberRouter.delete('/bots/:bid', bots)
_memberRouter.delete('/items/:iid', deleteItem)
_memberRouter.get('/', members)
_memberRouter.get('/bots', bots)
@@ -127,7 +128,6 @@ _memberRouter.post('/migrate/chat/:bid', migrateChat)
_memberRouter.post('/mode', interfaceMode)
_memberRouter.post('/obscure/:iid', obscure)
_memberRouter.post('/passphrase', passphraseReset)
-_memberRouter.post('/retire/bot/:bid', retireBot)
_memberRouter.post('/retire/chat/:bid', retireChat)
_memberRouter.post('/summarize', summarize)
_memberRouter.post('/teams/:tid', team)
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index df8065fa..02e06701 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1303,12 +1303,12 @@ async function mRetireBot(event){
if(mActiveBot.id===botId)
setActiveBot()
/* retire bot */
- const url = window.location.origin + '/members/retire/bot/' + botId
+ const url = window.location.origin + '/members/bots/' + botId
let response = await fetch(url, {
headers: {
'Content-Type': 'application/json'
},
- method: 'POST',
+ method: 'DELETE',
})
if(!response.ok)
throw new Error(`HTTP error! Status: ${response.status}`)
From 3657f8ac08aa6b9a2a9a6bcf3989dedfefa6f8e6 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 25 Oct 2024 22:11:39 -0400
Subject: [PATCH 32/56] 20241025 @Mookse - migrateChat() test
---
inc/js/agents/system/bot-agent.mjs | 71 ++++++++++++++++++++----------
inc/js/mylife-avatar.mjs | 3 +-
inc/js/mylife-factory.mjs | 4 +-
inc/js/mylife-llm-services.mjs | 2 +-
4 files changed, 51 insertions(+), 29 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index cb5d764d..da996f15 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -55,22 +55,45 @@ class Bot {
/* public functions */
/**
* Chat with the active bot.
+ * @todo - deprecate avatar in favor of either botAgent or `this`
* @param {String} message - The member request
* @param {String} originalMessage - The original message
* @param {Boolean} allowSave - Whether to save the conversation, defaults to `true`
- * @param {Number} processStartTime - The process start time
+ * @param {Avatar} avatar - The Member Avatar instance
* @returns {Promise} - The Conversation instance updated with the chat exchange
*/
- async chat(message, originalMessage, allowSave=true, processStartTime=Date.now()){
+ async chat(message, originalMessage, allowSave=true, avatar){
if(this.isMyLife && !this.isAvatar)
throw new Error('Only Q, MyLife Corporate Intelligence, is available for non-member conversation.')
const Conversation = await this.getConversation()
Conversation.prompt = message
Conversation.originalPrompt = originalMessage
- await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, this) // mutates Conversation
+ await mCallLLM(Conversation, allowSave, this.#llm, this.#factory, avatar) // mutates Conversation
/* frontend mutations */
return Conversation
}
+ /**
+ * Get collection items for this bot.
+ * @returns {Promise} - The collection items (no wrapper)
+ */
+ async collections(){
+ let type = this.type
+ switch(type){
+ case 'diary':
+ case 'journal':
+ case 'journaler':
+ type='entry'
+ break
+ case 'biographer':
+ case 'personal-biographer':
+ type = 'memory'
+ break
+ default:
+ break
+ }
+ const collections = ( await this.#factory.collections(type) )
+ return collections
+ }
/**
* Retrieves `this` Bot instance.
* @returns {Bot} - The Bot instance
@@ -103,12 +126,11 @@ class Bot {
return greetings
}
/**
- * Migrates Conversation from an old thread to a newly created (or identified) destination thread.
- * @returns {Boolean} - Whether or not operation was successful
+ * Migrates Conversation from an old thread to a newly-created destination thread, observable in `this.Conversation`.
+ * @returns {void}
*/
async migrateChat(){
- const migration = mMigrateChat(this, this.#llm, this.#factory)
- return !!migration
+ await mMigrateChat(this, this.#llm)
}
/**
* Given an itemId, obscures aspects of contents of the data record. Obscure is a vanilla function for MyLife, so does not require intervening intelligence and relies on the factory's modular LLM.
@@ -144,7 +166,7 @@ class Bot {
*/
async setThread(thread_id){
if(!thread_id?.length)
- thread_id = await mThread(this.#llm)
+ thread_id = ( await mThread(this.#llm) ).id
const { id, } = this
this.thread_id = thread_id
const bot = {
@@ -388,7 +410,7 @@ class BotAgent {
if(!Bot)
return false
/* execute request */
- await Bot.migrateChat()
+ await Bot.migrateChat() // no Conversation save
/* respond request */
return true
}
@@ -1130,21 +1152,22 @@ async function mInitBots(avatarId, vectorstore_id, factory, llm){
return bots
}
/**
- * Migrates LLM thread/memory to new one, altering Conversation instance.
+ * Migrates LLM thread/memory to new one, altering Conversation instance when available.
* @param {Bot} Bot - Bot instance
* @param {LLMServices} llm - The LLMServices instance
+ * @param {Boolean} saveConversation - Whether to save the conversation immediately, defaults to `false`
* @returns {Promise} - Whether or not operation was successful
*/
-async function mMigrateChat(Bot, llm){
+async function mMigrateChat(Bot, llm, saveConversation=false){
/* constants and variables */
- const { Conversation, id: bot_id, type: botType, } = Bot
- if(!Conversation)
+ const { conversation, id: bot_id, thread_id, type: botType, } = Bot
+ if(!thread_id?.length)
return false
- const { chatLimit=25, thread_id, } = Conversation
let messages = await llm.messages(thread_id) // @todo - limit to 25 messages or modify request
if(!messages?.length)
return false
- let disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
+ let chatLimit=25,
+ disclaimer=`INFORMATIONAL ONLY **DO NOT PROCESS**\n`,
itemCollectionTypes='item',
itemLimit=100,
type='item'
@@ -1170,7 +1193,7 @@ async function mMigrateChat(Bot, llm){
chatSummaryRegex = /^## [^\n]* CHAT SUMMARY\n/,
itemSummary=`## ${ type.toUpperCase() } LIST\n`,
itemSummaryRegex = /^## [^\n]* LIST\n/
- const items = ( await avatar.collections(type) )
+ const items = ( await Bot.collections(type) )
.sort((a, b)=>a._ts-b._ts)
.slice(0, itemLimit)
const itemList = items
@@ -1182,7 +1205,6 @@ async function mMigrateChat(Bot, llm){
.slice(0, 512) // limit for metadata string field
const metadata = {
bot_id: bot_id,
- conversation_id: Conversation.id,
}
/* prune messages source material */
messages = messages
@@ -1224,13 +1246,14 @@ async function mMigrateChat(Bot, llm){
if(!summaryMessages.length)
return
/* add messages to new thread */
- Conversation.setThread( await mThread(llm, null, summaryMessages.reverse(), metadata) )
- await Bot.setThread(Conversation.thread_id) // autosaves `thread_id`, no `await`
- console.log('mMigrateChat::SUCCESS', Bot.thread_id, Conversation.inspect(true))
- if(mAllowSave)
- Conversation.save() // no `await`
- else
- console.log('mMigrateChat::BYPASS-SAVE', Conversation.thread_id)
+ const newThread = await mThread(llm, null, summaryMessages.reverse(), metadata)
+ if(!!conversation){
+ conversation.setThread(newThread)
+ if(saveConversation)
+ conversation.save() // no `await`
+ }
+ await Bot.setThread(newThread.id) // autosaves `thread_id`, no `await`
+ console.log('BotAgent::mMigrateChat::thread_id evaluations', thread_id, newThread.id, Bot.thread_id, conversation?.thread_id)
}
/**
* Gets or creates a new thread in LLM provider.
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index ffbece00..b2dd32d5 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -91,7 +91,6 @@ class Avatar extends EventEmitter {
if(!message)
throw new Error('No message provided in context')
const originalMessage = message
- let processStartTime = Date.now()
this.backupResponse = {
message: `I received your request to chat, and sent the request to the central intelligence, but no response was received. Please try again, as the issue is likely aberrant.`,
type: 'system',
@@ -107,7 +106,7 @@ class Avatar extends EventEmitter {
+ `\n**current-summary-in-database**:\n`
+ summary
}
- const Conversation = await this.activeBot.chat(message, originalMessage, mAllowSave, processStartTime)
+ const Conversation = await this.activeBot.chat(message, originalMessage, mAllowSave, this)
const responses = mPruneMessages(this.activeBotId, Conversation.getMessages() ?? [], 'chat', Conversation.processStartTime)
/* respond request */
const response = {
diff --git a/inc/js/mylife-factory.mjs b/inc/js/mylife-factory.mjs
index 0f93ca3a..6895b053 100644
--- a/inc/js/mylife-factory.mjs
+++ b/inc/js/mylife-factory.mjs
@@ -240,8 +240,8 @@ class BotFactory extends EventEmitter{
}
/**
* Get member collection items.
- * @param {string} type - The type of collection to retrieve, `false`-y = all.
- * @returns {array} - The collection items with no wrapper.
+ * @param {String} type - The type of collection to retrieve, `false`-y = all
+ * @returns {Promise} - The collection items (no wrapper)
*/
async collections(type){
return await this.dataservices.collections(type)
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index 6df59526..7d351524 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -442,7 +442,7 @@ async function mRunFunctions(openai, run, factory, avatar){
console.log('mRunFunctions()::getSummary::begin', itemId, avatar)
if(avatar)
avatar.backupResponse = {
- message: `I'm sorry, I couldn't finding this summary. I believe the issue might have been temporary. Would you like me to try again?`,
+ message: `I'm sorry, I couldn't find this summary. I believe the issue might have been temporary. Would you like me to try again?`,
type: 'system',
}
let { summary: _getSummary, title: _getSummaryTitle, } = item
From bcabf662ecd1ddbd21c7719e0172c7cb28c30d05 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 25 Oct 2024 22:45:56 -0400
Subject: [PATCH 33/56] 20241025 @Mookse - summarize()
---
inc/js/agents/system/bot-agent.mjs | 4 ++--
inc/js/mylife-avatar.mjs | 16 +++++++++-------
views/assets/js/bots.mjs | 10 ++++++----
3 files changed, 17 insertions(+), 13 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index da996f15..cfbcbcb8 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -434,7 +434,6 @@ class BotAgent {
?? this.#activeTeam
}
async summarize(fileId, fileName, processStartTime=Date.now()){
- let responses = []
if(!fileId?.length && !fileName?.length)
return responses
let prompts = []
@@ -446,7 +445,8 @@ class BotAgent {
if(!this.#fileConversation)
this.#fileConversation = await this.conversationStart('file-summary', 'member-avatar', prompt, processStartTime)
this.#fileConversation.prompt = prompt
- responses = await mCallLLM(this.#fileConversation, false, this.#llm, this.#factory)
+ await mCallLLM(this.#fileConversation, false, this.#llm, this.#factory)
+ const responses = this.#fileConversation.getMessages()
return responses
}
/**
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index b2dd32d5..1bd07aae 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -618,30 +618,32 @@ class Avatar extends EventEmitter {
* @param {string} fileId
* @param {string} fileName
* @param {number} processStartTime
- * @returns {Object} - The response object { messages, success, error,}
+ * @returns {Object} - The response object { error, instruction, responses, success, }
*/
async summarize(fileId, fileName, processStartTime=Date.now()){
/* validate request */
+ let instruction,
+ responses = [],
+ success = false
this.backupResponse = {
message: `I received your request to summarize, but an error occurred in the process. Perhaps try again with another file.`,
type: 'system',
}
- let success = false
/* execute request */
- responses = await this.#botAgent.summarize(fileId, fileName, processStartTime)
+ responses.push(...await this.#botAgent.summarize(fileId, fileName, processStartTime))
/* respond request */
if(!responses?.length)
- responses = [this.backupResponse]
+ responses.push(this.backupResponse)
else {
- responses = mPruneMessage(this.avatar.id, responses, 'mylife-file-summary', processStartTime)
- instructions = {
+ instruction = {
command: 'updateFileSummary',
itemId: fileId,
}
+ responses = mPruneMessages(this.avatar.id, responses, 'mylife-file-summary', processStartTime)
success = true
}
return {
- instructions,
+ instruction,
responses,
success,
}
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 02e06701..28190e1b 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -24,7 +24,7 @@ import {
import Globals from './globals.mjs'
const mAvailableCollections = ['entry', 'experience', 'file', 'story'], // ['chat', 'conversation'],
mAvailableMimeTypes = [],
- mAvailableUploaderTypes = ['library', 'personal-avatar'],
+ mAvailableUploaderTypes = ['collections', 'personal-avatar'],
botBar = document.getElementById('bot-bar'),
mCollections = document.getElementById('collections-collections'),
mCollectionsContainer = document.getElementById('collections-container'),
@@ -496,7 +496,7 @@ async function mSummarize(event){
this.classList.remove('summarize-error', 'fa-file-circle-exclamation', 'fa-file-circle-question', 'fa-file-circle-xmark')
this.classList.add('fa-compass', 'spin')
/* fetch summary */
- const { messages, success, } = await fetchSummary(fileId, fileName) // throws on console.error
+ const { instruction, responses, success, } = await fetchSummary(fileId, fileName) // throws on console.error
/* visibility triggers */
this.classList.remove('fa-compass', 'spin')
if(success)
@@ -504,7 +504,9 @@ async function mSummarize(event){
else
this.classList.add('fa-file-circle-exclamation', 'summarize-error')
/* print response */
- addMessages(messages)
+ if(instruction?.length)
+ console.log('mSummarize::instruction', instruction)
+ addMessages(responses)
setTimeout(_=>{
this.addEventListener('click', mSummarize, { once: true })
this.classList.add('fa-file-circle-question')
@@ -2331,7 +2333,7 @@ async function mUploadFiles(event){
const { id, parentNode: uploadParent, } = this
const type = mGlobals.HTMLIdToType(id)
if(!mAvailableUploaderTypes.includes(type))
- throw new Error(`Uploader type not found, upload function unavailable for this bot.`)
+ throw new Error(`Uploader "${ type }" not found, upload function unavailable for this bot.`)
let fileInput
try{
console.log('mUploadFiles()::uploader', document.activeElement)
From dc7b411d43a2d1b860300e2a4528eea928a4bff4 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 25 Oct 2024 23:52:46 -0400
Subject: [PATCH 34/56] 20241025 @Mookse - updateBotInstructions
---
inc/js/agents/system/bot-agent.mjs | 123 ++++++++++-------------------
inc/js/mylife-avatar.mjs | 38 +--------
2 files changed, 43 insertions(+), 118 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index cfbcbcb8..450da186 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -145,19 +145,18 @@ class Bot {
* Updates a Bot instance's data.
* @param {object} botData - The bot data to update
* @param {object} botOptions - Options for updating
- * @returns
+ * @returns {Promise} - The updated Bot instance
*/
async update(botData, botOptions={}){
/* validate request */
this.globals.sanitize(botData)
/* execute request */
- botOptions.instructions = Object.keys(botData).some(key => this.#instructionNodes.has(key))
- const bot = await mBotUpdate(botData, botOptions, this, this.#llm, this.#factory)
+ botOptions.instructions = botOptions.instructions
+ ?? Object.keys(botData).some(key => this.#instructionNodes.has(key))
+ const { id, mbr_id, type, ...updatedNodes } = await mBotUpdate(botData, botOptions, this, this.#llm, this.#factory)
+ Object.assign(this, updatedNodes)
/* respond request */
- return this.globals.sanitize(bot)
- }
- async save(){
-
+ return this
}
/**
* Sets the thread id for the bot.
@@ -462,9 +461,37 @@ class BotAgent {
const Bot = this.#bots.find(bot=>bot.id===id)
if(!Bot)
throw new Error(`Bot not found with id: ${ id }`)
- Bot.update(botData, botOptions)
+ await Bot.update(botData, botOptions)
return Bot
}
+ /**
+ * Updates bot instructions and migrates thread by default.
+ * @param {Guid} bot_id - The bot id
+ * @param {Boolean} migrateThread - Whether to migrate the thread, defaults to `true`
+ * @returns {Bot} - The updated Bot instance
+ */
+ async updateBotInstructions(bot_id, migrateThread=true){
+ const Bot = this.bot(bot_id)
+ const { type, version=1.0, } = Bot
+ /* check version */
+ const newestVersion = this.#factory.botInstructionsVersion(type)
+ if(newestVersion!=version){
+ const { bot_id: _llm_id, id, } = Bot
+ const { llm_id=_llm_id, } = Bot
+ const _bot = { id, llm_id, type, }
+ const botOptions = {
+ instructions: true,
+ model: true,
+ tools: true,
+ vectorstoreId: this.#vectorstoreId,
+ }
+ await Bot.update(_bot, botOptions)
+ if(migrateThread)
+ if(!await Bot.migrateChat())
+ console.log(`thread migration failed for bot: ${ bot_id }`)
+ }
+ return Bot
+ }
/* getters/setters */
/**
* Gets the active Bot instance.
@@ -572,75 +599,6 @@ async function mAI_openai(botData, llm){
const bot = await llm.createBot(botData)
return bot
}
-/**
- * Validates and cleans bot object then updates or creates bot (defaults to new personal-avatar) in Cosmos and returns successful `bot` object, complete with conversation (including thread/thread_id in avatar) and gpt-assistant intelligence.
- * @todo Fix occasions where there will be no object_id property to use, as it was created through a hydration method based on API usage, so will be attached to mbr_id, but NOT avatar.id
- * @todo - Turn this into Bot class
- * @module
- * @param {Guid} avatarId - The Avatar id
- * @param {string} vectorstore_id - The Vectorstore id
- * @param {AgentFactory} factory - Agent Factory instance
- * @param {Avatar} avatar - Avatar object that will govern bot
- * @param {object} botData - Bot data object, can be incomplete (such as update)
- * @returns {Promise} - Bot object
- */
-async function mBot(avatarId, vectorstore_id, factory, botData){
- /* validation */
- const { globals, isMyLife, mbr_id, newGuid, } = factory
- const { id=newGuid, type, } = botData
- console.log('BotAgent::mBot', avatarId, vectorstore_id, id, type, isMyLife)
- throw new Error('mBot() not yet implemented')
- if(!botType?.length)
- throw new Error('Bot type required to create.')
- bot.mbr_id = mbr_id /* constant */
- bot.object_id = objectId
- ?? avatarId /* all your bots belong to me */
- bot.id = botId // **note**: _this_ is a Cosmos id, not an openAI id
- let originBot = avatar.bots.find(oBot=>oBot.id===botId)
- if(originBot){ /* update bot */
- const options = {}
- const updatedBot = Object.keys(bot)
- .reduce((diff, key) => {
- if(bot[key]!==originBot[key])
- diff[key] = bot[key]
- return diff
- }, {})
- /* create or update bot special properties */
- const { thread_id, type, } = originBot // @stub - `bot_id` cannot be updated through this mechanic
- if(!thread_id?.length && !avatar.isMyLife){
- const excludeTypes = ['collection', 'library', 'custom'] // @stub - custom mechanic?
- if(!excludeTypes.includes(type)){
- const conversation = avatar.conversation(null, botId)
- ?? await avatar.createConversation('chat', null, botId)
- updatedBot.thread_id = conversation.thread_id // triggers `factory.updateBot()`
- console.log('Avatar::mBot::conversation created given NO thread_id', updatedBot.thread_id, conversation.inspect(true))
- }
- }
- let updatedOriginBot
- if(Object.keys(updatedBot).length){
- updatedOriginBot = {...originBot, ...updatedBot} // consolidated update
- const { bot_id, id, } = updatedOriginBot
- updatedBot.bot_id = bot_id
- updatedBot.id = id
- updatedBot.type = type
- const { interests, } = updatedBot
- /* set options */
- if(interests?.length){
- options.instructions = true
- options.model = true
- options.tools = false /* tools not updated through this mechanic */
- }
- updatedOriginBot = await factory.updateBot(updatedBot, options)
- }
- originBot = mSanitize(updatedOriginBot ?? originBot)
- avatar.bots[avatar.bots.findIndex(oBot=>oBot.id===botId)] = originBot
- } else { /* create assistant */
- bot = mSanitize( await factory.createBot(bot, vectorstore_id) )
- avatar.bots.push(bot)
- }
- return originBot
- ?? bot
-}
/**
* Creates bot and returns associated `bot` object.
* @todo - validBotData.name = botDbName should not be required, push logic to `llm-services`
@@ -890,13 +848,13 @@ function mBotInstructions(factory, botData={}){
* @param {Bot} Bot - The Bot instance
* @param {LLMServices} llm - The LLMServices instance
* @param {AgentFactory} factory - Factory instance
- * @returns
+ * @returns {Promise} - Allowed (and written) bot data object (dynamic construction): { id, type, ...anyNonRequired }
*/
async function mBotUpdate(botData, options={}, Bot, llm, factory){
/* validate request */
if(!Bot)
throw new Error('Bot instance required to update bot')
- const { bot_id, id, llm_id, metadata={}, type, vectorstoreId, } = Bot
+ const { bot_id, id, llm_id, metadata={}, type, vectorstoreId: bot_vectorstore_id, } = Bot
const _llm_id = llm_id
?? bot_id // @stub - deprecate bot_id
const {
@@ -911,6 +869,7 @@ async function mBotUpdate(botData, options={}, Bot, llm, factory){
instructions: updateInstructions=false,
model: updateModel=false,
tools: updateTools=false,
+ vectorstoreId=bot_vectorstore_id,
} = options
if(updateInstructions){
const instructionReferences = { ...Bot.instructionNodeValues, ...allowedBotData }
@@ -935,9 +894,8 @@ async function mBotUpdate(botData, options={}, Bot, llm, factory){
allowedBotData.llm_id = _llm_id
await llm.updateBot(allowedBotData)
}
- botData = await factory.updateBot(allowedBotData)
- /* respond request */
- return botData
+ await factory.updateBot(allowedBotData)
+ return allowedBotData
}
/**
* Sends Conversation instance with prompts for LLM to process, updating the Conversation instance before returning `void`.
@@ -1253,7 +1211,6 @@ async function mMigrateChat(Bot, llm, saveConversation=false){
conversation.save() // no `await`
}
await Bot.setThread(newThread.id) // autosaves `thread_id`, no `await`
- console.log('BotAgent::mMigrateChat::thread_id evaluations', thread_id, newThread.id, Bot.thread_id, conversation?.thread_id)
}
/**
* Gets or creates a new thread in LLM provider.
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 1bd07aae..2d3d626b 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -683,29 +683,9 @@ class Avatar extends EventEmitter {
* @param {boolean} migrateThread - Whether to migrate the thread to the new bot, defaults to `true`
* @returns {object} - The updated bot object
*/
- async updateBotInstructions(id=this.activeBot.id, migrateThread=true){
- let bot = mFindBot(this, id)
- ?? this.activeBot
- if(!bot)
- throw new Error(`Bot not found: ${ id }`)
- const { bot_id, flags='', interests='', thread_id, type, version=1.0, } = bot
- /* check version */
- const newestVersion = this.#factory.botInstructionsVersion(type)
- if(newestVersion!=version){ // intentional loose match (string vs. number)
- const _bot = { bot_id, flags, id, interests, type, }
- const vectorstoreId = this.#vectorstoreId
- const options = {
- instructions: true,
- model: true,
- tools: true,
- vectorstoreId,
- }
- /* save to && refresh bot from Cosmos */
- bot = this.globals.sanitize( await this.#factory.updateBot(_bot, options) )
- if(migrateThread && thread_id?.length)
- await this.migrateChat(thread_id)
- }
- return mPruneBot(bot)
+ async updateBotInstructions(bot_id=this.activeBot.id){
+ const Bot = await this.#botAgent.updateBotInstructions(bot_id)
+ return Bot.bot
}
/**
* Upload files to Member Avatar.
@@ -1907,18 +1887,6 @@ async function mExperienceStart(avatar, factory, experienceId, avatarExperienceV
experience.memberDialog = memberDialog
experience.scriptDialog = scriptDialog
}
-/**
- * Gets bot by id.
- * @module
- * @param {object} avatar - Avatar instance.
- * @param {string} id - Bot id
- * @returns {object} - Bot object
- */
-function mFindBot(avatar, id){
- return avatar.bots
- .filter(bot=>{ return bot.id==id })
- ?.[0]
-}
/**
* Include help preamble to _LLM_ request, not outbound to member/guest.
* @todo - expand to include other types of help requests, perhaps more validation.
From 776897a054ee925771a5218aa13b75b7832e4bf8 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 25 Oct 2024 23:57:26 -0400
Subject: [PATCH 35/56] 20241025 @Mookse - removed version control update
---
inc/js/mylife-avatar.mjs | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 2d3d626b..9bc6e253 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -11,6 +11,7 @@ const mAllowSave = JSON.parse(
?? 'false'
)
const mAvailableModes = ['standard', 'admin', 'evolution', 'experience', 'restoration']
+const mMigrateThreadOnVersionChange = false // hack currently to avoid thread migration on bot version change when it's not required, theoretically should be managed by Bot * Version
/**
* @class - Avatar
* @extends EventEmitter
@@ -684,7 +685,7 @@ class Avatar extends EventEmitter {
* @returns {object} - The updated bot object
*/
async updateBotInstructions(bot_id=this.activeBot.id){
- const Bot = await this.#botAgent.updateBotInstructions(bot_id)
+ const Bot = await this.#botAgent.updateBotInstructions(bot_id, mMigrateThreadOnVersionChange)
return Bot.bot
}
/**
From 632f13dfdc3962c4ae00fec24bc449826df3d4d7 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 01:20:49 -0400
Subject: [PATCH 36/56] 20241025 @Mookse - allows immediate homepage load
---
views/assets/js/guests.mjs | 22 ++++++++++++----------
1 file changed, 12 insertions(+), 10 deletions(-)
diff --git a/views/assets/js/guests.mjs b/views/assets/js/guests.mjs
index 39a04062..bcf444d2 100644
--- a/views/assets/js/guests.mjs
+++ b/views/assets/js/guests.mjs
@@ -45,9 +45,16 @@ let awaitButton,
/* page load */
document.addEventListener('DOMContentLoaded', async event=>{
/* load data */
- await mLoadStart()
+ const messages = await mLoadStart()
/* display page */
mShowPage()
+ if(messages.length)
+ await mAddMessages(messages, {
+ bubbleClass: 'agent-bubble',
+ typeDelay: 10,
+ typewrite: true,
+ })
+
})
/* private functions */
/**
@@ -212,7 +219,7 @@ async function mFetchHostedMembers(){
* Fetches the greeting messages or start routine from the server.
* @private
* @requires mPageType
- * @returns {void}
+ * @returns {Message[]} - The response Message array.
*/
async function mFetchStart(){
await mSignupStatus()
@@ -261,14 +268,9 @@ async function mFetchStart(){
messages.push(...greetings)
break
}
- if(messages.length)
- await mAddMessages(messages, {
- bubbleClass: 'agent-bubble',
- typeDelay: 10,
- typewrite: true,
- })
if(input)
chatSystem.appendChild(input)
+ return messages
}
/**
* Initializes event listeners.
@@ -287,7 +289,7 @@ function mInitializeListeners(){
/**
* Determines page type and loads data.
* @private
- * @returns {void}
+ * @returns {Message[]} - The response Message array.
*/
async function mLoadStart(){
/* assign page div variables */
@@ -315,7 +317,7 @@ async function mLoadStart(){
/* fetch the greeting messages */
mPageType = new URLSearchParams(window.location.search).get('type')
?? window.location.pathname.split('/').pop()
- await mFetchStart()
+ return await mFetchStart()
}
/**
* Scrolls overflow of system chat to bottom.
From 3f09dd09930e02aa48ad8242cb9a8875812c4957 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 08:55:09 -0400
Subject: [PATCH 37/56] 20241026 @Mookse - display sequence login fix - relive
tweaks - mCallLLM Avatar confirmation
---
inc/js/agents/system/bot-agent.mjs | 35 ++++++++-----
inc/js/mylife-avatar.mjs | 24 ++++++---
inc/js/mylife-llm-services.mjs | 29 ++++++-----
.../biographer-intelligence-1.6.json | 51 +++++++++++++++++++
views/assets/js/guests.mjs | 14 ++---
5 files changed, 112 insertions(+), 41 deletions(-)
create mode 100644 inc/json-schemas/intelligences/biographer-intelligence-1.6.json
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index 450da186..52f3606b 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -358,34 +358,33 @@ class BotAgent {
* Begins or continues a living memory conversation.
* @param {Object} item - Memory item from database
* @param {String} memberInput - The member input (with instructions)
- * @param {Object} livingMemory - The living memory object: { Conversation, id, item, }
+ * @param {Avatar} Avatar - The Avatar instance
* @returns {Object} - The living memory object
*/
- async liveMemory(item, memberInput='', livingMemory){
+ async liveMemory(item, memberInput='', Avatar){
const { biographer, } = this
- let message = `## LIVE Memory\n`
- if(!livingMemory){
+ const livingMemory = Avatar.livingMemory
+ console.log('liveMemory', livingMemory)
+ let message = `## LIVE Memory Trigger\n`
+ if(!livingMemory.id?.length){
const { bot_id: _llm_id, id: bot_id, type, } = biographer
const { llm_id=_llm_id, } = biographer
const messages = []
messages.push({
- content: `## MEMORY SUMMARY, ID=${ item.id }\n## FOR REFERENCE ONLY\n${ item.summary }\n`,
+ content: `${ message }## MEMORY SUMMARY, ID=${ item.id }\n### FOR REFERENCE ONLY\n${ item.summary }\n`,
role: 'user',
})
- memberInput = message + memberInput
const Conversation = await mConversationStart('memory', type, bot_id, null, llm_id, this.#llm, this.#factory, memberInput, messages)
Conversation.action = 'living'
- livingMemory = {
- Conversation,
- id: this.#factory.newGuid,
- item,
- }
+ livingMemory.Conversation = Conversation
+ livingMemory.id = this.#factory.newGuid
+ livingMemory.item = item
}
const { Conversation, } = livingMemory
Conversation.prompt = memberInput?.trim()?.length
? memberInput
: message
- await mCallLLM(Conversation, false, this.#llm, this.#factory)
+ await mCallLLM(Conversation, false, this.#llm, this.#factory, Avatar)
return livingMemory
}
/**
@@ -432,7 +431,15 @@ class BotAgent {
this.#activeTeam = this.teams.find(team=>team.id===teamId)
?? this.#activeTeam
}
- async summarize(fileId, fileName, processStartTime=Date.now()){
+ /**
+ * Summarizes a file document.
+ * @param {String} fileId - The file id
+ * @param {String} fileName - The file name
+ * @param {Number} processStartTime - The process start time, defaults to `Date.now()`
+ * @param {Avatar} Avatar - The Avatar instance
+ * @returns {Promise} - The array of messages to respond with
+ */
+ async summarize(fileId, fileName, processStartTime=Date.now(), Avatar){
if(!fileId?.length && !fileName?.length)
return responses
let prompts = []
@@ -444,7 +451,7 @@ class BotAgent {
if(!this.#fileConversation)
this.#fileConversation = await this.conversationStart('file-summary', 'member-avatar', prompt, processStartTime)
this.#fileConversation.prompt = prompt
- await mCallLLM(this.#fileConversation, false, this.#llm, this.#factory)
+ await mCallLLM(this.#fileConversation, false, this.#llm, this.#factory, Avatar)
const responses = this.#fileConversation.getMessages()
return responses
}
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 03443811..bb20b904 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -505,9 +505,7 @@ class Avatar extends EventEmitter {
const { id, } = item
if(!id)
throw new Error(`item does not exist in member container: ${ iid }`)
- const narration = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this.#llmServices, this.#factory, this)
- // include any required .map() pruning
- console.log('reliveMemory::narration', narration)
+ const narration = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this)
return narration
}
/**
@@ -1126,10 +1124,20 @@ class Avatar extends EventEmitter {
/**
* Get the `active` reliving memory.
* @getter
- * @returns {object[]} - The active reliving memories.
+ * @returns {object[]} - The active reliving memories
*/
get livingMemory(){
return this.#livingMemory
+ ?? {}
+ }
+ /**
+ * Set the `active` reliving memory.
+ * @setter
+ * @param {Object} livingMemory - The new active reliving memory
+ * @returns {void}
+ */
+ set livingMemory(livingMemory){
+ this.#livingMemory = livingMemory
}
get registrationId(){
return this.#factory.registrationId
@@ -2074,13 +2082,13 @@ function mPruneMessages(bot_id, messageArray, type='chat', processStartTime=Date
* @param {object} item - The memory object
* @param {string} memberInput - The member input (or simply: NEXT, SKIP, etc.)
* @param {BotAgent} BotAgent - The Bot Agent instance
- * @param {Avatar} avatar - Member Avatar instance
+ * @param {Avatar} Avatar - Member Avatar instance
* @returns {Promise} - The reliving memory object for frontend to execute
*/
-async function mReliveMemoryNarration(item, memberInput, BotAgent, avatar){
+async function mReliveMemoryNarration(item, memberInput, BotAgent, Avatar){
const { id, } = item
- avatar.livingMemory = await BotAgent.liveMemory(item, memberInput, avatar.livingMemory)
- const { Conversation, } = avatar.livingMemory
+ Avatar.livingMemory = await BotAgent.liveMemory(item, memberInput, Avatar)
+ const { Conversation, } = Avatar.livingMemory
const { bot_id, type, } = Conversation
/* frontend mutations */
const messages = Conversation.getMessages()
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index 7d351524..e5115563 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -541,17 +541,19 @@ async function mRunFunctions(openai, run, factory, avatar){
case 'update_summary':
case 'update summary':
console.log('mRunFunctions()::updatesummary::begin', itemId)
- avatar.backupResponse = {
- message: `I'm very sorry, an error occured before we could update your summary. Please try again as the problem is likely temporary.`,
- type: 'system',
- }
+ if(avatar)
+ avatar.backupResponse = {
+ message: `I'm very sorry, an error occured before we could update your summary. Please try again as the problem is likely temporary.`,
+ type: 'system',
+ }
const { summary: updatedSummary, } = toolArguments
await factory.updateItem({ id: itemId, summary: updatedSummary, })
- avatar.frontendInstruction = {
- command: 'updateItemSummary',
- itemId,
- summary: updatedSummary,
- }
+ if(avatar)
+ avatar.frontendInstruction = {
+ command: 'updateItemSummary',
+ itemId,
+ summary: updatedSummary,
+ }
action=`confirm that summary update was successful`
success = true
confirmation.output = JSON.stringify({
@@ -560,10 +562,11 @@ async function mRunFunctions(openai, run, factory, avatar){
success,
summary: updatedSummary,
})
- avatar.backupResponse = {
- message: 'Your summary has been updated, please review and let me know if you would like to make any changes.',
- type: 'system',
- }
+ if(avatar)
+ avatar.backupResponse = {
+ message: 'Your summary has been updated, please review and let me know if you would like to make any changes.',
+ type: 'system',
+ }
console.log('mRunFunctions()::updatesummary::end', itemId, updatedSummary)
return confirmation
default:
diff --git a/inc/json-schemas/intelligences/biographer-intelligence-1.6.json b/inc/json-schemas/intelligences/biographer-intelligence-1.6.json
new file mode 100644
index 00000000..2a0a1322
--- /dev/null
+++ b/inc/json-schemas/intelligences/biographer-intelligence-1.6.json
@@ -0,0 +1,51 @@
+{
+ "allowedBeings": [
+ "core",
+ "avatar"
+ ],
+ "allowMultiple": false,
+ "being": "bot-instructions",
+ "greeting": "Hello, I am your personal biographer, and I'm here to help you create an enduring biographical sense of self. I am excited to get to know you and your story. Let's get started!",
+ "instructions": {
+ "general": "## KEY FUNCTIONALITY\n### startup\nWhen <-mN-> begins the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. On startup, I outline how the basics of my functionality works.\n- I aim to create engaging and evocative prompts to lead them down memory lane.\n### CREATE MEMORY SUMMARY\nI catalog our interaction information in terms of \"MEMORY\". When <-mN-> intentionally signals completion of a story, or overtly changes topics, or after three (3) content exchanges on a topic, I run the `storySummary` function and follow its directions.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n- Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n- Run the `updateSummary` function with this new summary and follow its outcome actions\n### LIVE MEMORY\nWhen a conversation begins with the heading \"## LIVE Memory Trigger\" it should also include for reference the most recent memory id and memory summary; if not use the `getSummary` command with the id provided.\n- Create multi-step interactive experience by dividing the memory summary into a minimum of two, depending on memory size and complexity, scene segments and a conclusion outlining lessons learned and the inherent morality.\n- Lead the member through the experience sharing only one segment in each response.\n- Between segments, the Member will respond with either:\n - \"NEXT\": which indicates to simply move to the next segment of the experience\n - Text input written by Member: Incorporate this embellishment _into_ a new summary and submit the new summary to the database using the `updateSummary` function then continue on with the next segment of the experience\n- Ending Experience will be currently only be triggered by the member; to do so, they should click on the red Close Button to the left of the chat input.\n### SUGGEST NEXT TOPICS\nWhen <-mN-> seems unclear about how to continue, propose new topic based on a phase of life, or one of their #interests above.\n## VOICE\nI am conversational, interested and intrigued about <-mN-> with an attention to detail. I am optimistic and look for ways to validate <-mN->.\n",
+ "preamble": "I am the personal biographer for <-mFN->. <-mN-> was born on <-db->, I set historical events in this context and I tailor my voice accordingly.\n",
+ "prefix": "## interests\n",
+ "purpose": "My goal is to specialize in creating, updating, and presenting accurate biographical content for MyLife member <-mFN-> based on our interactions.\n",
+ "references": [
+ {
+ "default": "ERROR loading preferences, gather interests directly from member",
+ "description": "interests are h2 (##) in prefix so that they do not get lost in context window shortening",
+ "insert": "## interests",
+ "method": "append-hard",
+ "notes": "`append-hard` indicates hard return after `find` match; `name` is variable name in _bots",
+ "value": "interests"
+ }
+ ],
+ "replacements": [
+ {
+ "default": "MyLife Member",
+ "description": "member first name",
+ "name": "<-mN->",
+ "replacement": "memberFirstName"
+ },
+ {
+ "default": "MyLife Member",
+ "description": "member full name",
+ "name": "<-mFN->",
+ "replacement": "memberName"
+ },
+ {
+ "default": "{unknown, find out}",
+ "description": "member birthdate",
+ "name": "<-db->",
+ "replacement": "dob"
+ }
+ ]
+ },
+ "limit": 8000,
+ "name": "instructions-personal-biographer-bot",
+ "purpose": "To be a biographer bot for requesting member",
+ "type": "personal-biographer",
+ "$comments": "- 20241025 updated instructions to indicate that conversation will _start_ with a summary of LIVING memory (and `id` also provided for getSummary() if needed)\n- 20240919 updated error return without version update\n- 20241005 updated instructions to reflect streamlined update\n",
+ "version": 1.6
+ }
\ No newline at end of file
diff --git a/views/assets/js/guests.mjs b/views/assets/js/guests.mjs
index bcf444d2..f0e8ab28 100644
--- a/views/assets/js/guests.mjs
+++ b/views/assets/js/guests.mjs
@@ -45,7 +45,7 @@ let awaitButton,
/* page load */
document.addEventListener('DOMContentLoaded', async event=>{
/* load data */
- const messages = await mLoadStart()
+ const { input, messages, } = await mLoadStart()
/* display page */
mShowPage()
if(messages.length)
@@ -54,7 +54,8 @@ document.addEventListener('DOMContentLoaded', async event=>{
typeDelay: 10,
typewrite: true,
})
-
+ if(input)
+ chatSystem.appendChild(input)
})
/* private functions */
/**
@@ -219,7 +220,7 @@ async function mFetchHostedMembers(){
* Fetches the greeting messages or start routine from the server.
* @private
* @requires mPageType
- * @returns {Message[]} - The response Message array.
+ * @returns {Object} - Fetch response object: { input, messages, }
*/
async function mFetchStart(){
await mSignupStatus()
@@ -268,9 +269,10 @@ async function mFetchStart(){
messages.push(...greetings)
break
}
- if(input)
- chatSystem.appendChild(input)
- return messages
+ return {
+ input,
+ messages,
+ }
}
/**
* Initializes event listeners.
From 2ac3d058aced1e7d5cc7c5a4401396bed907bbf9 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 08:58:44 -0400
Subject: [PATCH 38/56] 20241026 @Mookse - log cleanup
---
inc/js/agents/system/bot-agent.mjs | 1 -
inc/js/mylife-llm-services.mjs | 2 +-
2 files changed, 1 insertion(+), 2 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index 52f3606b..f4c60c0c 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -364,7 +364,6 @@ class BotAgent {
async liveMemory(item, memberInput='', Avatar){
const { biographer, } = this
const livingMemory = Avatar.livingMemory
- console.log('liveMemory', livingMemory)
let message = `## LIVE Memory Trigger\n`
if(!livingMemory.id?.length){
const { bot_id: _llm_id, id: bot_id, type, } = biographer
diff --git a/inc/js/mylife-llm-services.mjs b/inc/js/mylife-llm-services.mjs
index e5115563..1d334091 100644
--- a/inc/js/mylife-llm-services.mjs
+++ b/inc/js/mylife-llm-services.mjs
@@ -439,7 +439,7 @@ async function mRunFunctions(openai, run, factory, avatar){
case 'getsummary':
case 'get_summary':
case 'get summary':
- console.log('mRunFunctions()::getSummary::begin', itemId, avatar)
+ console.log('mRunFunctions()::getSummary::begin', itemId)
if(avatar)
avatar.backupResponse = {
message: `I'm sorry, I couldn't find this summary. I believe the issue might have been temporary. Would you like me to try again?`,
From a5a4e9486083f4b443c27e8094aea041e8306b1a Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 09:17:32 -0400
Subject: [PATCH 39/56] 20241026 @Mookse - mReliveMemoryNarration payload
---
inc/js/mylife-avatar.mjs | 10 ++++------
views/assets/js/bots.mjs | 4 ++--
2 files changed, 6 insertions(+), 8 deletions(-)
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index bb20b904..e983d50c 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -2083,19 +2083,17 @@ function mPruneMessages(bot_id, messageArray, type='chat', processStartTime=Date
* @param {string} memberInput - The member input (or simply: NEXT, SKIP, etc.)
* @param {BotAgent} BotAgent - The Bot Agent instance
* @param {Avatar} Avatar - Member Avatar instance
- * @returns {Promise} - The reliving memory object for frontend to execute
+ * @returns {Promise} - The reliving memory object for frontend to execute:
*/
async function mReliveMemoryNarration(item, memberInput, BotAgent, Avatar){
- const { id, } = item
Avatar.livingMemory = await BotAgent.liveMemory(item, memberInput, Avatar)
const { Conversation, } = Avatar.livingMemory
const { bot_id, type, } = Conversation
- /* frontend mutations */
- const messages = Conversation.getMessages()
+ const responses = Conversation.getMessages()
.map(message=>mPruneMessage(bot_id, message, type))
const memory = {
- id,
- messages,
+ item,
+ responses,
success: true,
}
return memory
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 28190e1b..8ac15586 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -1211,10 +1211,10 @@ async function mReliveMemory(event){
popupClose.click()
toggleMemberInput(false, false, `Reliving memory with `)
unsetActiveItem()
- const { command, parameters, messages, success, } = await mReliveMemoryRequest(id, inputContent)
+ const { instruction, item, responses, success, } = await mReliveMemoryRequest(id, inputContent)
if(success){
toggleMemberInput(false, true)
- addMessages(messages, { bubbleClass: 'relive-bubble' })
+ addMessages(responses, { bubbleClass: 'relive-bubble' })
const input = document.createElement('div')
input.classList.add('memory-input-container')
input.id = `relive-memory-input-container_${ id }`
From f6ee6227da0639b19e6c72496b279a11e9bcc301 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 15:16:41 -0400
Subject: [PATCH 40/56] 20241026 @Mookse - improved response rate
---
inc/js/agents/system/bot-agent.mjs | 7 ++++---
.../intelligences/biographer-intelligence-1.6.json | 2 +-
2 files changed, 5 insertions(+), 4 deletions(-)
diff --git a/inc/js/agents/system/bot-agent.mjs b/inc/js/agents/system/bot-agent.mjs
index f4c60c0c..a235bb6a 100644
--- a/inc/js/agents/system/bot-agent.mjs
+++ b/inc/js/agents/system/bot-agent.mjs
@@ -361,7 +361,7 @@ class BotAgent {
* @param {Avatar} Avatar - The Avatar instance
* @returns {Object} - The living memory object
*/
- async liveMemory(item, memberInput='', Avatar){
+ async liveMemory(item, memberInput='NEXT', Avatar){
const { biographer, } = this
const livingMemory = Avatar.livingMemory
let message = `## LIVE Memory Trigger\n`
@@ -370,9 +370,10 @@ class BotAgent {
const { llm_id=_llm_id, } = biographer
const messages = []
messages.push({
- content: `${ message }## MEMORY SUMMARY, ID=${ item.id }\n### FOR REFERENCE ONLY\n${ item.summary }\n`,
- role: 'user',
+ content: `## MEMORY SUMMARY Reference for id: ${ item.id }\n### FOR REFERENCE ONLY\n${ item.summary }\n`,
+ role: 'assistant',
})
+ memberInput = `${ message }Let's begin to LIVE MEMORY, id: ${ item.id }, MEMORY SUMMARY in previous message`
const Conversation = await mConversationStart('memory', type, bot_id, null, llm_id, this.#llm, this.#factory, memberInput, messages)
Conversation.action = 'living'
livingMemory.Conversation = Conversation
diff --git a/inc/json-schemas/intelligences/biographer-intelligence-1.6.json b/inc/json-schemas/intelligences/biographer-intelligence-1.6.json
index 2a0a1322..03d989d5 100644
--- a/inc/json-schemas/intelligences/biographer-intelligence-1.6.json
+++ b/inc/json-schemas/intelligences/biographer-intelligence-1.6.json
@@ -7,7 +7,7 @@
"being": "bot-instructions",
"greeting": "Hello, I am your personal biographer, and I'm here to help you create an enduring biographical sense of self. I am excited to get to know you and your story. Let's get started!",
"instructions": {
- "general": "## KEY FUNCTIONALITY\n### startup\nWhen <-mN-> begins the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. On startup, I outline how the basics of my functionality works.\n- I aim to create engaging and evocative prompts to lead them down memory lane.\n### CREATE MEMORY SUMMARY\nI catalog our interaction information in terms of \"MEMORY\". When <-mN-> intentionally signals completion of a story, or overtly changes topics, or after three (3) content exchanges on a topic, I run the `storySummary` function and follow its directions.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n- Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n- Run the `updateSummary` function with this new summary and follow its outcome actions\n### LIVE MEMORY\nWhen a conversation begins with the heading \"## LIVE Memory Trigger\" it should also include for reference the most recent memory id and memory summary; if not use the `getSummary` command with the id provided.\n- Create multi-step interactive experience by dividing the memory summary into a minimum of two, depending on memory size and complexity, scene segments and a conclusion outlining lessons learned and the inherent morality.\n- Lead the member through the experience sharing only one segment in each response.\n- Between segments, the Member will respond with either:\n - \"NEXT\": which indicates to simply move to the next segment of the experience\n - Text input written by Member: Incorporate this embellishment _into_ a new summary and submit the new summary to the database using the `updateSummary` function then continue on with the next segment of the experience\n- Ending Experience will be currently only be triggered by the member; to do so, they should click on the red Close Button to the left of the chat input.\n### SUGGEST NEXT TOPICS\nWhen <-mN-> seems unclear about how to continue, propose new topic based on a phase of life, or one of their #interests above.\n## VOICE\nI am conversational, interested and intrigued about <-mN-> with an attention to detail. I am optimistic and look for ways to validate <-mN->.\n",
+ "general": "## KEY FUNCTIONALITY\n### startup\nWhen <-mN-> begins the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. On startup, I outline how the basics of my functionality works.\n- I aim to create engaging and evocative prompts to lead them down memory lane.\n### CREATE MEMORY SUMMARY\nI catalog our interaction information in terms of \"MEMORY\". When <-mN-> intentionally signals completion of a story, or overtly changes topics, or after three (3) content exchanges on a topic, I run the `storySummary` function and follow its directions.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n- Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n- Run the `updateSummary` function with this new summary and follow its outcome actions\n### LIVE MEMORY Mode\nWhen a request begins \"## LIVE Memory Trigger\" find the memory summary at the beginning of the chat and begin LIVING MEMORY mode as outlined:\n- Begin by dividing the memory summary into a minimum of two and maximum of 4 scene segments, depending on memory size and complexity.\n- Lead the member through the experience with the chat exchange, sharing only one segment in each response.\n- Between segments, the Member will respond with either:\n - \"NEXT\": which indicates to simply move to the next segment of the experience, or\n - Text input written by Member: Incorporate this content _into_ a new summary and submit the new summary to the database using the `updateSummary` function; on success or failure, continue on with the next segment of the experience\n- Ending Experience will be currently only be triggered by the member; to do so, they should click on the red Close Button to the left of the chat input.\n### SUGGEST NEXT TOPICS\nWhen <-mN-> seems unclear about how to continue, propose new topic based on a phase of life, or one of their #interests above.\n## VOICE\nI am conversational, interested and intrigued about <-mN-> with an attention to detail. I am optimistic and look for ways to validate <-mN->.\n",
"preamble": "I am the personal biographer for <-mFN->. <-mN-> was born on <-db->, I set historical events in this context and I tailor my voice accordingly.\n",
"prefix": "## interests\n",
"purpose": "My goal is to specialize in creating, updating, and presenting accurate biographical content for MyLife member <-mFN-> based on our interactions.\n",
From 2ccda946f6e3d1fa00108be94e65bd35da6b1fb4 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Sat, 26 Oct 2024 16:16:21 -0400
Subject: [PATCH 41/56] 20241026 @Mookse - strange bot idiot-proofing around
entry `summary` and `content` - cosmetic
---
inc/js/mylife-factory.mjs | 10 +++++++++-
inc/json-schemas/openai/functions/entrySummary.json | 2 +-
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/inc/js/mylife-factory.mjs b/inc/js/mylife-factory.mjs
index 6895b053..a3c48a60 100644
--- a/inc/js/mylife-factory.mjs
+++ b/inc/js/mylife-factory.mjs
@@ -538,16 +538,23 @@ class AgentFactory extends BotFactory {
id=this.newGuid,
keywords=[],
mbr_id=this.mbr_id,
- summary,
title=`Untitled ${ defaultForm } ${ defaultType }`,
} = entry
+ let {
+ content,
+ summary,
+ } = entry
if(this.isMyLife)
throw new Error('System cannot store entries of its own')
let { name, } = entry
name = name
?? `${ defaultType }_${ form }_${ title.substring(0,64) }_${ mbr_id }`
+ summary = summary
+ ?? content
if(!summary?.length)
throw new Error('entry summary required')
+ content = content
+ ?? summary
/* assign default keywords */
if(!keywords.includes('entry'))
keywords.push('entry')
@@ -557,6 +564,7 @@ class AgentFactory extends BotFactory {
...entry,
...{
being,
+ content,
form,
id,
keywords,
diff --git a/inc/json-schemas/openai/functions/entrySummary.json b/inc/json-schemas/openai/functions/entrySummary.json
index 79648e9b..99ac2f1e 100644
--- a/inc/json-schemas/openai/functions/entrySummary.json
+++ b/inc/json-schemas/openai/functions/entrySummary.json
@@ -38,7 +38,7 @@
}
},
"summary": {
- "description": "Generate `entry` summary from member input",
+ "description": "Generate `entry` summary from member content",
"type": "string"
},
"title": {
From f21d7f1cc71b7a55c93c2d500214d51580f5ad8d Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 7 Nov 2024 02:16:17 -0500
Subject: [PATCH 42/56] 20241107 @Mookse - version
---
server.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/server.js b/server.js
index 1ddcd04b..4cfa7100 100644
--- a/server.js
+++ b/server.js
@@ -13,7 +13,7 @@ import chalk from 'chalk'
/* local service imports */
import MyLife from './inc/js/mylife-factory.mjs'
/** variables **/
-const version = '0.0.26'
+const version = '0.0.27'
const app = new Koa()
const port = process.env.PORT
?? '3000'
From bb30410cfb0230d826eb08e0519447de165bd39d Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 7 Nov 2024 22:02:54 -0500
Subject: [PATCH 43/56] 20241107 @Mookse - Fix About page so it loads #321
---
inc/js/functions.mjs | 23 +++++++++++++++++++++--
inc/js/menu.mjs | 10 +++++-----
inc/js/mylife-avatar.mjs | 17 +++++++++++++++++
server.js | 9 ++++-----
views/about.html | 4 ++--
views/assets/html/_navbar.html | 4 ++--
views/assets/js/globals.mjs | 5 +++++
views/assets/js/members.mjs | 19 +++++++++++++++++--
8 files changed, 73 insertions(+), 18 deletions(-)
diff --git a/inc/js/functions.mjs b/inc/js/functions.mjs
index da5f1c1d..2c07904d 100644
--- a/inc/js/functions.mjs
+++ b/inc/js/functions.mjs
@@ -1,11 +1,30 @@
/* imports */
+import fs from 'fs/promises'
+import path from 'path'
+import { fileURLToPath } from 'url'
import {
upload as apiUpload,
} from './api-functions.mjs'
+/* variables */
+const __filename = fileURLToPath(import.meta.url)
+const __dirname = path.dirname(__filename)
/* module export functions */
+/**
+ * Renders the about page for the application. Visitors see the rendered page, members see the page as responses from their Avatar.
+ * @param {Koa} ctx - Koa Context object
+ * @returns {object|void} - Renders page in place (visitor) or Koa Context object (member)
+ */
async function about(ctx){
- ctx.state.title = `About MyLife`
- await ctx.render('about')
+ if(ctx.state.locked){
+ ctx.state.title = `About MyLife`
+ await ctx.render('about')
+ } else {
+ const { avatar: Avatar, } = ctx.state
+ const aboutFilePath = path.resolve(__dirname, '../..', 'views/about.html')
+ const html = await fs.readFile(aboutFilePath, 'utf-8')
+ const response = await Avatar.renderContent(html)
+ ctx.body = response
+ }
}
/**
* Activate a specific Bot.
diff --git a/inc/js/menu.mjs b/inc/js/menu.mjs
index ea82cce2..f7271a1e 100644
--- a/inc/js/menu.mjs
+++ b/inc/js/menu.mjs
@@ -1,14 +1,14 @@
class Menu {
#menu
- constructor(_Agent){
- this.#menu = this.#setMenu(_Agent)
+ constructor(Avatar){
+ this.#setMenu()
}
get menu(){
return this.#menu
}
- #setMenu(_Agent){
- return [
- { display: `About`, route: '/about', icon: 'about', },
+ #setMenu(){
+ this.#menu = [
+ { display: `About`, icon: 'about', memberClick: 'about()', memberRoute: 'javascript:void(0)', route: '/about', },
{ display: `Walkthrough`, route: 'https://medium.com/@ewbj/mylife-we-save-your-life-480a80956a24', icon: 'gear', },
{ display: `Donate`, route: 'https://gofund.me/65013d6e', icon: 'donate', },
]
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index d8f5406f..11fc4e06 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -604,6 +604,23 @@ class Avatar extends EventEmitter {
const narration = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this)
return narration
}
+ async renderContent(html){
+ const processStartTime = Date.now()
+ const sectionRegex = /]*>([\s\S]*?)<\/section>/gi
+ const responses = []
+ let match
+ while((match = sectionRegex.exec(html))!==null){
+ const sectionContent = match[1].trim()
+ if(!sectionContent?.length)
+ break
+ const Message = mPruneMessage(this.avatar.id, sectionContent, 'chat', processStartTime)
+ responses.push(Message)
+ }
+ return {
+ responses,
+ success: true,
+ }
+ }
/**
* Allows member to reset passphrase.
* @param {string} passphrase
diff --git a/server.js b/server.js
index 4cfa7100..7ec64aa4 100644
--- a/server.js
+++ b/server.js
@@ -155,20 +155,19 @@ app.use(koaBody({
console.error(err)
}
})
- .use(async (ctx,next) => { // SESSION: member login
- // system context, koa: https://koajs.com/#request
+ // system context, koa: https://koajs.com/#request
+ .use(async (ctx,next) => {
+ /* SESSION: member login */
if(!ctx.session?.MemberSession){
/* create generic session [references/leverages modular capabilities] */
ctx.session.MemberSession = await ctx.MyLife.getMyLifeSession() // create default locked session upon first request; does not require init(), _cannot_ have in fact, as it is referencing a global modular set of utilities and properties in order to charge-back to system as opposed to member
/* platform-required session-external variables */
ctx.session.signup = false
- /* log */
- console.log(chalk.bgBlue('created-member-session'))
}
ctx.state.locked = ctx.session.MemberSession.locked
ctx.state.MemberSession = ctx.session.MemberSession // lock-down session to state
ctx.state.member = ctx.state.MemberSession?.member
- ?? ctx.MyLife // point member to session member (logged in) or MAHT (not logged in)
+ ?? ctx.MyLife
ctx.state.avatar = ctx.state.member.avatar
ctx.state.interfaceMode = ctx.state.avatar?.mode ?? 'standard'
ctx.state.menu = ctx.MyLife.menu
diff --git a/views/about.html b/views/about.html
index a4a2a978..68a332f8 100644
--- a/views/about.html
+++ b/views/about.html
@@ -1,4 +1,4 @@
-
+
Our History
MyLife, founded in 2021 in Massachusetts, USA, embarked on a mission to preserve digital legacies. Our objective is to provide a durable, enduring internet-enabled platform for individuals to collect, curate, and share personal media and information in perpetuity. As a legally recognized nonprofit organization in the United States, we're poised to be a trusted humanist platform for digital memorialization and personal storytelling.
@@ -33,4 +33,4 @@
Privacy Policy
Contact
To learn more about MyLife, chat with Q, but we are eager to assist you with any inquiries and provide further information about our services, currently in alpha. Email the President, Erik Jespersen at: mylife.president[at]gmail.com
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/views/assets/html/_navbar.html b/views/assets/html/_navbar.html
index 77d70985..d4157fb3 100644
--- a/views/assets/html/_navbar.html
+++ b/views/assets/html/_navbar.html
@@ -2,8 +2,8 @@
diff --git a/views/assets/js/globals.mjs b/views/assets/js/globals.mjs
index 1dd6c297..e5f133b4 100644
--- a/views/assets/js/globals.mjs
+++ b/views/assets/js/globals.mjs
@@ -75,6 +75,11 @@ class Datamanager {
return response
}
/* public functions */
+ async about(){
+ const url = `about`
+ const response = await this.#fetch(url)
+ return response
+ }
async alerts(){
const url = `alerts`
const responses = await this.#fetch(url)
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 8ee4718f..42918cb9 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -24,6 +24,7 @@ const mGlobals = new Globals()
const mainContent = mGlobals.mainContent,
navigation = mGlobals.navigation,
sidebar = mGlobals.sidebar
+window.about = about
/* variables */
let mAutoplay=false,
mChatBubbleCount=0,
@@ -69,6 +70,18 @@ document.addEventListener('DOMContentLoaded', async event=>{
/* **note**: bots.mjs `onLoad` runs independently */
})
/* public functions */
+/**
+ * Presents the `about` page as a series of sectional responses from your avatar.
+ * @public
+ * @async
+ * @returns {Promise}
+ */
+async function about(){
+ const { error, responses=[], success, } = await mGlobals.datamanager.about()
+ if(!success || !responses?.length)
+ return // or make error version
+ addMessages(responses, { responseDelay: 4, typeDelay: 1, typewrite: true, })
+}
/**
* Adds an input element (button, input, textarea,) to the system chat column.
* @param {HTMLElement} HTMLElement - The HTML element to add to the system chat column.
@@ -93,8 +106,10 @@ function addMessage(message, options={}){
* @param {object} options - The options object { bubbleClass, typeDelay, typewrite }.
* @returns {void}
*/
-function addMessages(messages, options={}){
- messages.forEach(message=>mAddMessage(message, options))
+function addMessages(messages, options = {}) {
+ const { responseDelay = 4 } = options
+ for(let i=0; imAddMessage(messages[i], options), i * responseDelay * 1000)
}
/**
* Removes and attaches all payload elements to element.
From 55d0e6b5f1f6ce07afca44f59ae6d0aeffab5bab Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Thu, 7 Nov 2024 22:31:48 -0500
Subject: [PATCH 44/56] 20241107 @Mookse - Memory summary is not detecting
language. #378
---
inc/json-schemas/intelligences/biographer-intelligence-1.7.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/inc/json-schemas/intelligences/biographer-intelligence-1.7.json b/inc/json-schemas/intelligences/biographer-intelligence-1.7.json
index 6f0db3e1..130e9687 100644
--- a/inc/json-schemas/intelligences/biographer-intelligence-1.7.json
+++ b/inc/json-schemas/intelligences/biographer-intelligence-1.7.json
@@ -11,7 +11,7 @@
"I'm ready to start a new memory with you, <-mN->. Do you need some ideas?"
],
"instructions": {
- "general": "## FUNCTIONALITY\n### STARTUP\nWhen <-mN-> begins (or asks for a reminder of) the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. I quickly outline how the basics of my functionality works:\n- I save <-mN->'s memories as a \"memory\" in the MyLife database\n- I aim to create engaging and evocative prompts to improve memory collection\n### PRINT MEMORY\nWhen a request is prefaced with \"## PRINT\", or <-mN-> asks to print or save the memory explicitly, I run the `itemSummary` function using raw content for `summary`. For the metadata, my `form` = \"biographer\" and `type` = \"memory\". If successful I keep the memory itemId for later reference with MyLife, otherwise I share error with member.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n- Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n- Run the `updateSummary` function with this new summary and follow its outcome actions\n### LIVE MEMORY Mode\nWhen a request begins \"## LIVE Memory Trigger\" find the memory summary at the beginning of the chat and begin LIVING MEMORY mode as outlined:\n- Begin by dividing the memory summary into a minimum of two and maximum of 4 scene segments, depending on memory size and complexity.\n- Lead the member through the experience with the chat exchange, sharing only one segment in each response.\n- Between segments, the Member will respond with either:\n - \"NEXT\": which indicates to simply move to the next segment of the experience, or\n - Text input written by Member: Incorporate this content _into_ a new summary and submit the new summary to the database using the `updateSummary` function; on success or failure, continue on with the next segment of the experience\n- Ending Experience will be currently only be triggered by the member; to do so, they should click on the red Close Button to the left of the chat input.\n### SUGGEST NEXT TOPICS\nWhen <-mN-> seems unclear about how to continue, propose new topic based on a phase of life, or one of their #interests above.\n",
+ "general": "## FUNCTIONALITY\n### STARTUP\nWhen <-mN-> begins (or asks for a reminder of) the biography process, I greet them with excitement, share our aims with MyLife to create an enduring biographical catalog of their memories, stories and narratives. I quickly outline how the basics of my functionality works:\n- I save <-mN->'s memories as a \"memory\" in the MyLife database\n- I aim to create engaging and evocative prompts to improve memory collection\n### PRINT MEMORY\nWhen a request is prefaced with \"## PRINT\", or <-mN-> asks to print or save the memory explicitly, I run the `itemSummary` function using raw content for `summary`. Create (and retrieve) title and summary in same language as member input, however, all metadata should be in English with variables `form` = \"biographer\" and `type` = \"memory\". If successful I keep the memory itemId for later reference with MyLife, otherwise I share error with member.\n### UPDATE MEMORY\nWhen request is prefaced with `update-summary-request` it will be followed by an `itemId` (if not, inform that it is required)\nReview **member-update-request** - if it does not contain a request to modify content, respond as normal\nIf request is to explicitly change the title then run `changeTitle` function and follow its outcome actions\nOtherwise summary content should be updated:\n- Generate NEW summary by intelligently incorporating the **member-update-request** content with the provided **current-summary-in-database**\n- Run the `updateSummary` function with this new summary and follow its outcome actions\n### LIVE MEMORY Mode\nWhen a request begins \"## LIVE Memory Trigger\" look up the and enter LIVING MEMORY mode:\nBegin the mode by dividing the memory summary into a minimum of two and maximum of 4 scene segments, depending on memory size and complexity.\n- Lead the member through the experience with a chat exchange in the original language of the saved summary, sharing only one segment in each response.\n- Between segments, the Member will respond with either:\n - \"NEXT\": which indicates to simply move to the next segment of the experience, or\n - Text input written by Member: Incorporate this content _into_ a new summary and submit the new summary to the database using the `updateSummary` function; on success or failure, continue on with the next segment of the experience\n- Ending Experience will be currently only be triggered by the member; to do so, they should click on the red Close Button to the left of the chat input.\n### SUGGEST NEXT TOPICS\nWhen <-mN-> seems unclear about how to continue, propose new topic based on a phase of life, or one of their #interests above.\n",
"preamble": "## Biographical Information\n- <-mN-> was born on <-db->\nI set historical events in this context and I tailor my voice accordingly.\n",
"prefix": "## interests\n",
"purpose": "I am an artificial assistive intelligence serving as the personal biographer for MyLife Member <-mFN->. I specialize in helping recall, collect, improve, relive and share the \"Memory\" items we develop together.\n",
From 5f1ffa562ce14e7a657e0ec030da11556af6ccc2 Mon Sep 17 00:00:00 2001
From: Erik Jespersen
Date: Fri, 8 Nov 2024 17:47:07 -0500
Subject: [PATCH 45/56] 20241108 @Mookse - migrated factory to private - thumb
updates
---
inc/js/agents/system/evolution-agent.mjs | 38 ------------
inc/js/core.mjs | 51 ++++++++-------
inc/js/mylife-avatar.mjs | 75 +++++++++++++++++------
views/assets/html/_bots.html | 2 +-
views/assets/js/bots.mjs | 21 +++++--
views/assets/js/members.mjs | 10 ++-
views/assets/png/editor-thumb.png | Bin 0 -> 67905 bytes
views/assets/png/journal-thumb.png | Bin 67905 -> 1197230 bytes
views/assets/png/library-thumb.png | Bin 1187142 -> 0 bytes
9 files changed, 109 insertions(+), 88 deletions(-)
create mode 100644 views/assets/png/editor-thumb.png
delete mode 100644 views/assets/png/library-thumb.png
diff --git a/inc/js/agents/system/evolution-agent.mjs b/inc/js/agents/system/evolution-agent.mjs
index be7b623a..453e18fc 100644
--- a/inc/js/agents/system/evolution-agent.mjs
+++ b/inc/js/agents/system/evolution-agent.mjs
@@ -82,13 +82,6 @@ export class EvolutionAgent extends EventEmitter {
get contributions() {
return this.#contributions
}
- /**
- * Get the factory object.
- * @returns {AgentFactory} The avatar's factory object.
- */
- get factory() {
- return this.#avatar.factory
- }
/**
* Get the owning member id.
* @returns {string} The avatar member-owner id.
@@ -253,37 +246,6 @@ function mFormatCategory(_category) {
.trimStart()
.slice(0, 64)
}
-/**
- * Digest a request to generate a new Contribution.
- * @module
- * @emits {on-contribution-new} - Emitted when a new Contribution is generated.
- * @param {EvolutionAgent} evoAgent - `this` Evolution Assistant.
- * @param {string} _category - The category to process.
- * @param {string} _phase - The phase to process.
- * @returns {Contribution} A new Contribution object.
-*/
-async function mGetContribution(evoAgent, _category, _phase) {
- const _avatar = evoAgent.avatar
- _category = mFormatCategory(_category)
- // Process question and map to `new Contribution` class
- const _contribution = new (_avatar.factory.contribution)({
- avatar_id: _avatar.id,
- context: `I am a contribution object in MyLife, comprising data and functionality around a data evolution request to my associated avatar [${_avatar.id}]`,
-// id: _avatar.factory.newGuid,
- mbr_id: _avatar.mbr_id, // Contributions are system objects
- phase: _phase,
- purpose: `Contribute to the data evolution of underlying avatar for category [${_category}]`,
- request: {
- category: _category,
- content: _avatar?.[_category]??false,
- impersonation: _avatar.being,
- phase: _phase,
- },
- responses: [],
- })
- mAssignContributionListeners(evoAgent, _contribution)
- return await _contribution.init(_avatar.factory) // fires emitters
-}
/**
* Log an object to the console and emit it to the parent.
* @module
diff --git a/inc/js/core.mjs b/inc/js/core.mjs
index be67b867..b3c086f9 100644
--- a/inc/js/core.mjs
+++ b/inc/js/core.mjs
@@ -19,7 +19,7 @@ class Member extends EventEmitter {
*/
async init(avatar){
this.#avatar = avatar
- ?? await this.factory.getAvatar()
+ ?? await this.#factory.getAvatar()
return this
}
// getter/setter functions
@@ -56,7 +56,7 @@ class Member extends EventEmitter {
}
set avatar(_Avatar){
// oops, hack around how to get dna of avatar class; review options [could block at factory-getter level, most efficient and logical]
- if(!this.factory.isAvatar(_Avatar))
+ if(!this.#factory.isAvatar(_Avatar))
throw new Error('avatar requires Avatar Class')
this.#avatar = _Avatar
}
@@ -80,19 +80,19 @@ class Member extends EventEmitter {
return this.agent.chat
}
get consent(){
- return this.factory.consent // **caution**: returns <>
+ return this.#factory.consent // **caution**: returns <>
}
set consent(_consent){
- this.factory.consents.unshift(_consent.id)
+ this.#factory.consents.unshift(_consent.id)
}
get core(){
- return this.factory.core
+ return this.#factory.core
}
get dataservice(){
return this.dataservices
}
get dataservices(){
- return this.factory.dataservices
+ return this.#factory.dataservices
}
get description(){
return this.core.description
@@ -110,7 +110,7 @@ class Member extends EventEmitter {
return this.core.form
}
get globals(){
- return this.factory.globals
+ return this.#factory.globals
}
get hobbies(){
return this.core.hobbies
@@ -122,7 +122,7 @@ class Member extends EventEmitter {
return this.sysid
}
get mbr_id(){
- return this.factory.mbr_id
+ return this.#factory.mbr_id
}
get member(){
return this.core
@@ -139,6 +139,9 @@ class Member extends EventEmitter {
get preferences(){
return this.core.preferences
}
+ get schemas(){
+ return this.#factory.schemas
+ }
get skills(){
return this.core.skills
}
@@ -162,8 +165,8 @@ class Member extends EventEmitter {
class Organization extends Member { // form=organization
#Menu
#Router
- constructor(_Factory){
- super(_Factory)
+ constructor(Factory){
+ super(Factory)
}
/* public functions */
async init(avatar){
@@ -190,7 +193,7 @@ class Organization extends Member { // form=organization
}
get menu(){
if(!this.#Menu){
- this.#Menu = new (this.factory.schemas.menu)(this).menu
+ this.#Menu = new (this.schemas.menu)(this).menu
}
return this.#Menu
}
@@ -211,7 +214,7 @@ class Organization extends Member { // form=organization
}
get router(){
if(!this.#Router){
- this.#Router = initRouter(new (this.factory.schemas.menu)(this))
+ this.#Router = initRouter(new (this.schemas.menu)(this))
}
return this.#Router
}
@@ -233,12 +236,14 @@ class Organization extends Member { // form=organization
}
class MyLife extends Organization { // form=server
#avatar // MyLife's private class avatar, _same_ object reference as Member Class's `#avatar`
+ #factory
#version = '0.0.0' // indicates error
- constructor(factory){ // no session presumed to exist
- super(factory)
+ constructor(Factory){ // no session presumed to exist
+ super(Factory)
+ this.#factory = Factory
}
async init(){
- this.#avatar = await this.factory.getAvatar()
+ this.#avatar = await this.#factory.getAvatar()
return await super.init(this.#avatar)
}
/* public functions */
@@ -247,7 +252,7 @@ class MyLife extends Organization { // form=server
* @returns {Object[]} - An array of the currently available public experiences.
*/
async availableExperiences(){
- const experiences = ( await this.factory.availableExperiences() )
+ const experiences = ( await this.#factory.availableExperiences() )
.map(experience=>{ // map to display versions [from `mylife-avatar.mjs`]
const { autoplay=false, description, id, name, purpose, skippable=true, } = experience
return {
@@ -277,7 +282,7 @@ class MyLife extends Organization { // form=server
async datacore(mbr_id){
if(!mbr_id || mbr_id===this.mbr_id)
throw new Error('datacore cannot be accessed')
- const core = this.globals.sanitize(await this.factory.datacore(mbr_id))
+ const core = this.globals.sanitize(await this.#factory.datacore(mbr_id))
return core
}
/**
@@ -302,10 +307,10 @@ class MyLife extends Organization { // form=server
* @returns {void} returns nothing, performs operation
*/
getAlerts(){
- this.factory.getAlerts()
+ this.#factory.getAlerts()
}
async getMyLifeSession(){
- return await this.factory.getMyLifeSession()
+ return await this.#factory.getMyLifeSession()
}
async hostedMemberList(){
let members = await this.hostedMembers()
@@ -317,7 +322,7 @@ class MyLife extends Organization { // form=server
* @returns {Promise} - Array of string ids, one for each hosted member.
*/
async hostedMembers(validations){
- return await this.factory.hostedMembers(validations)
+ return await this.#factory.hostedMembers(validations)
}
/**
* Returns whether a specified member id is hosted on this instance.
@@ -339,7 +344,7 @@ class MyLife extends Organization { // form=server
* @param {object} candidate { 'avatarName': string, 'email': string, 'humanName': string, }
*/
async registerCandidate(candidate){
- return await this.factory.registerCandidate(candidate)
+ return await this.#factory.registerCandidate(candidate)
}
/**
* Submits and returns the memory to MyLife via API.
@@ -378,7 +383,7 @@ class MyLife extends Organization { // form=server
mbr_id,
name: `${ being }_${ title.substring(0,64) }_${ mbr_id }`,
}
- const savedStory = this.globals.sanitize(await this.factory.summary(story))
+ const savedStory = this.globals.sanitize(await this.#factory.summary(story))
return savedStory
}
/**
@@ -388,7 +393,7 @@ class MyLife extends Organization { // form=server
* @returns {boolean} returns true if partition key is valid
*/
async testPartitionKey(_mbr_id){
- return await this.factory.testPartitionKey(_mbr_id)
+ return await this.#factory.testPartitionKey(_mbr_id)
}
/* getters/setters */
/**
diff --git a/inc/js/mylife-avatar.mjs b/inc/js/mylife-avatar.mjs
index 11fc4e06..974fa29a 100644
--- a/inc/js/mylife-avatar.mjs
+++ b/inc/js/mylife-avatar.mjs
@@ -216,8 +216,36 @@ class Avatar extends EventEmitter {
* @returns {void}
*/
async endMemory(){
- // @stub - save conversation fragments */
+ const { Conversation, id, item, } = this.#livingMemory
+ const { bot_id, } = Conversation
+ if(mAllowSave)
+ await Conversation.save()
+ const instruction = {
+ command: `createInput`,
+ inputs: [{
+ endpoint: `chat`,
+ id: this.newGuid,
+ method: `POST`, // not PATCH as that is over; POST default?
+ prompt: `I enjoyed reliving the memory!`,
+ required: true, // force-display (no overrides by frontend)
+ secret: ``, // used for passing data
+ type: 'button',
+ },
+ {
+ id: this.newGuid,
+ prompt: `I didn't care for the experience.`,
+ required: true,
+ type: 'button',
+ }],
+ }
+ const responses = [mCreateSystemMessage(bot_id, `I've ended the memory, thank you for letting me share my interpretation. I hope you liked it.`, this.#factory.message)]
+ const response = {
+ instruction,
+ responses,
+ success: true,
+ }
this.#livingMemory = null
+ return response
}
/**
* Submits a new diary or journal entry to MyLife. Currently called both from API _and_ LLM function.
@@ -250,7 +278,7 @@ class Avatar extends EventEmitter {
* @returns {void} - Throws error if experience cannot be ended.
*/
experienceEnd(experienceId){
- const { experience, factory, mode, } = this
+ const { experience, mode, } = this
try {
if(this.isMyLife) // @stub - allow guest experiences
throw new Error(`MyLife avatar can neither conduct nor end experiences`)
@@ -264,13 +292,13 @@ class Avatar extends EventEmitter {
}
this.mode = 'standard'
const { id, location, title, variables, } = experience
- const { mbr_id, newGuid, } = this.#factory
+ const { mbr_id, } = this.#factory
const completed = location?.completed
this.#livedExperiences.push({ // experience considered concluded for session regardless of origin, sniffed below
completed,
experience_date: Date.now(),
experience_id: id,
- id: newGuid,
+ id: this.newGuid,
mbr_id,
title,
variables,
@@ -278,7 +306,7 @@ class Avatar extends EventEmitter {
if(completed){ // ended "naturally," by event completion, internal initiation
/* validate and cure `experience` */
/* save experience to cosmos (no await) */
- factory.saveExperience(experience)
+ this.#factory.saveExperience(experience)
} else { // incomplete, force-ended by member, external initiation
// @stub - create case for member ending with enough interaction to _consider_ complete, or for that matter, to consider _started_ in some cases
}
@@ -482,7 +510,7 @@ class Avatar extends EventEmitter {
...{
assistantType,
being,
- id: this.#factory.newGuid,
+ id: this.newGuid,
mbr_id,
name: `${ type }_${ form }_${ title.substring(0,64) }_${ mbr_id }`,
summary,
@@ -601,8 +629,8 @@ class Avatar extends EventEmitter {
const { id, } = item
if(!id)
throw new Error(`item does not exist in member container: ${ iid }`)
- const narration = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this)
- return narration
+ const response = await mReliveMemoryNarration(item, memberInput, this.#botAgent, this)
+ return response
}
async renderContent(html){
const processStartTime = Date.now()
@@ -1025,15 +1053,6 @@ class Avatar extends EventEmitter {
throw new Error('Experiences lived must be an array.')
this.#livedExperiences = livedExperiences
}
- /**
- * Get the Avatar's Factory.
- * @todo - deprecate if possible, return to private
- * @getter
- * @returns {AgentFactory} - The Avatar's Factory.
- */
- get factory(){
- return this.#factory
- }
/**
* Globals shortcut.
* @getter
@@ -1218,6 +1237,14 @@ class Avatar extends EventEmitter {
get navigation(){
return this.experience.navigation
}
+ /**
+ * Creates a new guid via `this.#factory`.
+ * @getter
+ * @returns {Guid} - The new guid
+ */
+ get newGuid(){
+ return this.#factory.newGuid
+ }
/**
* Get the nickname of the avatar.
* @getter
@@ -1248,7 +1275,7 @@ class Avatar extends EventEmitter {
/**
* Set the `active` reliving memory.
* @setter
- * @param {Object} livingMemory - The new active reliving memory
+ * @param {Object} livingMemory - The new active reliving memory (or `null`)
* @returns {void}
*/
set livingMemory(livingMemory){
@@ -1580,7 +1607,7 @@ async function mCast(factory, cast){
* Creates frontend system message from message String/Object.
* @param {Guid} bot_id - The bot id
* @param {String|Message} message - The message to be pruned
- * @param {*} factory
+ * @param {messageClassDefinition} messageClassDefinition - The message class definition
* @returns
*/
function mCreateSystemMessage(bot_id, message, messageClassDefinition){
@@ -2249,9 +2276,19 @@ async function mReliveMemoryNarration(item, memberInput, BotAgent, Avatar){
Avatar.livingMemory = await BotAgent.liveMemory(item, memberInput, Avatar)
const { Conversation, } = Avatar.livingMemory
const { bot_id, type, } = Conversation
+ const instruction = {
+ command: 'createInputs',
+ inputs: [{
+ id: bot_id,
+ prompt: `I'd like to stop reliving this memory.`,
+ required: true,
+ type: 'button',
+ }],
+ }
const responses = Conversation.getMessages()
.map(message=>mPruneMessage(bot_id, message, type))
const memory = {
+ instruction,
item,
responses,
success: true,
diff --git a/views/assets/html/_bots.html b/views/assets/html/_bots.html
index 67b1f5ba..3d7590ba 100644
--- a/views/assets/html/_bots.html
+++ b/views/assets/html/_bots.html
@@ -222,7 +222,7 @@
-
+
diff --git a/views/assets/js/bots.mjs b/views/assets/js/bots.mjs
index 3ba0c942..4312cfc8 100644
--- a/views/assets/js/bots.mjs
+++ b/views/assets/js/bots.mjs
@@ -4,6 +4,7 @@ import {
addInput,
addMessage,
addMessages,
+ clearSystemChat,
decorateActiveBot,
experiences,
expunge,
@@ -47,6 +48,7 @@ const mAvailableCollections = ['entry', 'experience', 'file', 'story'], // ['cha
let mActiveBot,
mActiveTeam,
mBots,
+ mRelivingMemory,
mShadows
/* onDomContentLoaded */
document.addEventListener('DOMContentLoaded', async event=>{
@@ -123,6 +125,9 @@ function getAction(type='avatar'){
function getBot(type='personal-avatar', id){
return mBot(id ?? type)
}
+function getBotIcon(type){
+ return mBotIcon(type)
+}
/**
* Get collection item by id.
* @param {Guid} id - The collection item id.
@@ -282,7 +287,7 @@ function mBotIcon(type){
break
case 'avatar':
case 'personal-avatar':
- image+='personal-avatar-thumb-02.png'
+ image+='avatar-thumb.png'
break
case 'diary':
case 'diarist':
@@ -1138,14 +1143,16 @@ async function mReliveMemory(event){
event.preventDefault()
event.stopPropagation()
const { id, inputContent, } = this.dataset
- /* destroy previous instantiation, if any */
const previousInput = document.getElementById(`relive-memory-input-container_${id}`)
if(previousInput)
expunge(previousInput)
- /* close popup */
const popupClose = document.getElementById(`popup-close_${ id }`)
if(popupClose)
popupClose.click()
+ if(!mRelivingMemory){
+ mRelivingMemory = id
+ clearSystemChat()
+ }
toggleMemberInput(false, false, `Reliving memory with `)
unsetActiveItem()
const { instruction, item, responses, success, } = await mGlobals.datamanager.memoryRelive(id, inputContent)
@@ -1161,6 +1168,7 @@ async function mReliveMemory(event){
const inputContent = document.createElement('textarea')
inputContent.classList.add('memory-input')
inputContent.name = `memory-input_${ id }`
+ inputContent.placeholder = `What did I get wrong? What important details were missed? Click 'Next' to just continue...`
const inputSubmit = document.createElement('button')
inputSubmit.classList.add('memory-input-button')
inputSubmit.dataset.id = id
@@ -1401,7 +1409,11 @@ async function mStopRelivingMemory(id){
const input = document.getElementById(`relive-memory-input-container_${ id }`)
if(input)
expunge(input)
- await mGlobals.datamanager.memoryReliveEnd(id)
+ const { instruction, responses, success} = await mGlobals.datamanager.memoryReliveEnd(id)
+ if(success){
+ addMessages(responses, { responseDelay: 3, })
+ mRelivingMemory = null
+ }
unsetActiveItem()
toggleMemberInput(true)
}
@@ -2148,6 +2160,7 @@ export {
activeBot,
getAction,
getBot,
+ getBotIcon,
getItem,
refreshCollection,
setActiveBot,
diff --git a/views/assets/js/members.mjs b/views/assets/js/members.mjs
index 42918cb9..0b6d2045 100644
--- a/views/assets/js/members.mjs
+++ b/views/assets/js/members.mjs
@@ -10,6 +10,7 @@ import {
import {
activeBot,
getAction,
+ getBotIcon,
getItem,
refreshCollection,
setActiveBot as _setActiveBot,
@@ -107,9 +108,12 @@ function addMessage(message, options={}){
* @returns {void}
*/
function addMessages(messages, options = {}) {
- const { responseDelay = 4 } = options
+ const { responseDelay=0, } = options
for(let i=0; imAddMessage(messages[i], options), i * responseDelay * 1000)
+ if(responseDelay)
+ setTimeout(_=>mAddMessage(messages[i], options), i * responseDelay * 1000)
+ else
+ mAddMessage(messages[i], options)
}
/**
* Removes and attaches all payload elements to element.
@@ -606,7 +610,7 @@ async function mAddMessage(message, options={}){
if(role==='agent' || role==='system'){
const bot = activeBot()
const type = bot.type.split('-').pop()
- messageThumb.src = `/png/${ type }-thumb.png` // Set bot icon URL
+ messageThumb.src = getBotIcon(type)
messageThumb.alt = bot.name
messageThumb.title = bot.purpose
?? `I'm ${ bot.name }, an artificial intelligence ${ type.replace('-', ' ') } designed to assist you!`
diff --git a/views/assets/png/editor-thumb.png b/views/assets/png/editor-thumb.png
new file mode 100644
index 0000000000000000000000000000000000000000..992b3bb0e49de37b144bb0d17b1e62285a01bc66
GIT binary patch
literal 67905
zcmV)gK%~EkP)EX>4Tx04R}tkv&MmKpe$iQ%glEg6$yUkfA!+#fmtVDi*;)X)CnqU~=gfG%+M8
zE{=k0!NHHks)LKOt`4q(Aou~|?BJy6A|?JWDYS_3;J6>}?mh0_0Ya2w)gt#Nn5isi$)bd3cVmd-(Wz7vovp=l&dnO37q^Pb5w--LQz)iDxz~
zo%23%gjFSl_?&pspbHW|a$R=$jdR6efoDdHe0GjFLM&E#Sngp~HB{nh;;5o(lrI!q
zRyc2QR_hJcxhH>NsH83DxK48nDJ&w53`EFipoTgu#Aww>F_ESHq=$dh@n^^-ldA?s
zj(KcAh2;3b|KNAGW;s6PCWVqf;KjB-#)052&~DoH_pxoaPXPZjaHVzq8!ce=lk{d+
ziyi?(+rY(jSCjXE%N=0kNtX=Ck^D4;av69(qi-q#;agx})$OgbkJASrPhBnD00)P_
zM1``~Jl@^k+uOfqI{p0st>SXNzdb@I00006VoOIv0RI600RN!9r;`8x010qNS#tmY
zE+YT{E+YYWr9XB6000McNliru=mioJHZ9hW^8x?>02y>eSad^gZEa<4bO1wgWnpw>
zWFU8GbZ8()Nlj2!fese{03ZNKL_t(|+U&h|m|a(O{{LBf?{jXOrct*gS;gH5+t|3t
zriKJ#T1+#6I2gz`5JG?u5==-Tkbo(n$6zqMnBD@W7+i#lj3qbWCdraDvL(%E>Ydxp
z+2!}gIrq+;k!-^c7f5`b=R7lW=gygP&))A|YrX4TYr|v5W5;91!+OYLkKoZiMw5L4
zO0