From 6d814c0c1d50d952ff65cdc95fe272c7057e3824 Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Mon, 2 Sep 2024 20:16:56 -0300 Subject: [PATCH] fix(llm.gblib): Tool fix. More templates. --- .../admin.gbapp/services/GBAdminService.ts | 43 ++++---- .../services/AzureDeployerService.ts | 1 - packages/basic.gblib/services/GBVMService.ts | 44 ++++---- .../basic.gblib/services/SystemKeywords.ts | 102 ++++++++++-------- packages/core.gbapp/services/GBMinService.ts | 4 +- packages/llm.gblib/services/ChatServices.ts | 13 ++- .../default.gbdialog}/describe-image-tool.bas | 0 .../default.gbdialog}/generate-image-tool.bas | 0 .../default.gbdialog}/search-tool.bas | 0 .../llm-tools.gbdata/products.csv | 10 +- .../llm-tools.gbdialog/get-price.bas | 8 +- .../llm-tools.gbdialog/start.bas | 1 - .../whatsapp.gbai/whatsapp.gbot/config.csv | 1 + 13 files changed, 122 insertions(+), 105 deletions(-) rename templates/{llm-tools.gbai/llm-tools.gbdialog => default.gbai/default.gbdialog}/describe-image-tool.bas (100%) rename templates/{llm-tools.gbai/llm-tools.gbdialog => default.gbai/default.gbdialog}/generate-image-tool.bas (100%) rename templates/{llm-tools.gbai/llm-tools.gbdialog => default.gbai/default.gbdialog}/search-tool.bas (100%) create mode 100644 templates/whatsapp.gbai/whatsapp.gbot/config.csv diff --git a/packages/admin.gbapp/services/GBAdminService.ts b/packages/admin.gbapp/services/GBAdminService.ts index e13b5fa6..fb90234c 100644 --- a/packages/admin.gbapp/services/GBAdminService.ts +++ b/packages/admin.gbapp/services/GBAdminService.ts @@ -168,34 +168,27 @@ export class GBAdminService implements IGBAdminService { ) { const packageName = text.split(' ')[1]; - if (!this.isSharePointPath(packageName)) { - const additionalPath = GBConfigService.get('ADDITIONAL_DEPLOY_PATH'); - if (additionalPath === undefined) { - throw new Error('ADDITIONAL_DEPLOY_PATH is not set and deployPackage was called.'); - } - await deployer['deployPackage2'](min, user, urlJoin(additionalPath, packageName)); - } else { - const folderName = text.split(' ')[2]; - const packageType = Path.extname(folderName).substr(1); - const gbaiPath = DialogKeywords.getGBAIPath(min.instance.botId, packageType, null); - const localFolder = Path.join('work', gbaiPath); + const folderName = text.split(' ')[2]; + const packageType = Path.extname(folderName).substr(1); + const gbaiPath = DialogKeywords.getGBAIPath(min.instance.botId, packageType, null); + const localFolder = Path.join('work', gbaiPath); - // .gbot packages are handled using storage API, so no download - // of local resources is required. - const gbai = DialogKeywords.getGBAIPath(min.instance.botId); + // .gbot packages are handled using storage API, so no download + // of local resources is required. + const gbai = DialogKeywords.getGBAIPath(min.instance.botId); - if (packageType === 'gbkb') { - await deployer['cleanupPackage'](min.instance, packageName); - } - - if (!GBConfigService.get('STORAGE_NAME')) { - const path = Path.join(GBConfigService.get('STORAGE_LIBRARY'), gbaiPath); - GBUtil.copyIfNewerRecursive(path, localFolder); - } else { - await deployer['downloadFolder'](min, Path.join('work', `${gbai}`), Path.basename(localFolder)); - } - await deployer['deployPackage2'](min, user, localFolder); + if (packageType === 'gbkb') { + await deployer['cleanupPackage'](min.instance, packageName); } + + if (!GBConfigService.get('STORAGE_NAME')) { + const path = Path.join(GBConfigService.get('STORAGE_LIBRARY'), gbaiPath); + GBUtil.copyIfNewerRecursive(path, localFolder); + } else { + await deployer['downloadFolder'](min, Path.join('work', `${gbai}`), Path.basename(localFolder)); + } + await deployer['deployPackage2'](min, user, localFolder); + } public static async rebuildIndexPackageCommand(min: GBMinInstance, deployer: GBDeployer) { const service = await AzureDeployerService.createInstance(deployer); diff --git a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts index 4aca5208..db8103a1 100644 --- a/packages/azuredeployer.gbapp/services/AzureDeployerService.ts +++ b/packages/azuredeployer.gbapp/services/AzureDeployerService.ts @@ -986,7 +986,6 @@ export class AzureDeployerService implements IGBInstallationDeployer { appSettings: [ { name: 'WEBSITES_CONTAINER_START_TIME_LIMIT', value: `${WebSiteResponseTimeout}` }, { name: 'WEBSITE_NODE_DEFAULT_VERSION', value: GBAdminService.getNodeVersion() }, - { name: 'ADDITIONAL_DEPLOY_PATH', value: `` }, { name: 'ADMIN_PASS', value: `${instance.adminPass}` }, { name: 'BOT_ID', value: `${instance.botId}` }, { name: 'CLOUD_SUBSCRIPTIONID', value: `${instance.cloudSubscriptionId}` }, diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 2fcdff46..363f3c18 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -453,7 +453,8 @@ export class GBVMService extends GBService { // Converts General Bots BASIC into regular VBS let basicCode: string = Fs.readFileSync(filename, 'utf8'); - + basicCode = GBVMService.normalizeQuotes(basicCode); + // Pre process SET SCHEDULE calls. const schedules = GBVMService.getSetScheduleKeywordArgs(basicCode); @@ -764,9 +765,6 @@ export class GBVMService extends GBService { } } - if (text) { - text = GBVMService.normalizeQuotes(text); - } resolve(text); }); }); @@ -783,14 +781,15 @@ export class GBVMService extends GBService { return text; } - public static getMetadata(mainName: string, propertiesText, description) { + public static getMetadata(mainName: string, propertiesText: string[][], description: string) { let properties = {}; if (!propertiesText || !description) { return {}; } - const getType = asClause => { + + const getType = (asClause: string) => { asClause = asClause.trim().toUpperCase(); - + if (asClause.indexOf('STRING') !== -1) { return 'string'; } else if (asClause.indexOf('OBJECT') !== -1) { @@ -801,41 +800,46 @@ export class GBVMService extends GBService { return 'enum'; } }; - + for (let i = 0; i < propertiesText.length; i++) { const propertiesExp = propertiesText[i]; const t = getType(propertiesExp[2]); let element; - + if (t === 'enum') { - element = z.enum(propertiesExp[2].split(',')); + const list = propertiesExp[2] as any; + element = z.enum(list.split(',')); } else if (t === 'string') { element = z.string(); } else if (t === 'object') { - element = z.string(); + element = z.string(); // Assuming 'object' is represented as a string here } else if (t === 'number') { element = z.number(); } else { GBLog.warn(`Element type invalid specified on .docx: ${propertiesExp[0]}`); } - - element.describe(propertiesExp[3]); + + element['description'] = propertiesExp[4]?.trim(); // Assuming description is in the 4th index element['type'] = t; properties[propertiesExp[1].trim()] = element; } - - let json = { + + const json = { type: 'function', function: { - name: `${mainName}`, + name: mainName, description: description ? description : '', parameters: zodToJsonSchema(z.object(properties)) - } + }, + arguments: propertiesText.reduce((acc, prop) => { + acc[prop[1].trim()] = prop[3]?.trim(); // Assuming value is in the 3rd index + return acc; + }, {}) }; - + return json; } - + public async parseField(line) { let required = line.indexOf('*') !== -1; let unique = /\bunique\b/gi.test(line); @@ -913,7 +917,7 @@ export class GBVMService extends GBService { // Pre-process "off-line" static KEYWORDS. let emmit = true; - const params = /^\s*PARAM\s*(.*)\s*AS\s*(.*)\s*LIKE\s*(.*)/gim; + const params = /^\s*PARAM\s*(.*)\s*AS\s*(.*)\s*LIKE\s*(.*)\s*DESCRIPTION\s*(.*)/gim; const param = params.exec(line); if (param) { properties.push(param); diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index daefd69a..f1eb525d 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -48,7 +48,7 @@ import { GBSSR } from '../../core.gbapp/services/GBSSR.js'; import urlJoin from 'url-join'; import Excel from 'exceljs'; import { BufferWindowMemory } from 'langchain/memory'; -import { TwitterApi } from 'twitter-api-v2'; +import csvdb from 'csv-database'; import Path from 'path'; import ComputerVisionClient from '@azure/cognitiveservices-computervision'; import ApiKeyCredentials from '@azure/ms-rest-js'; @@ -345,19 +345,22 @@ export class SystemKeywords { const memoryBeforeGC = process.memoryUsage().heapUsed / 1024 / 1024; // in MB delete this.cachedMerge[pid]; - + // Capture memory usage before GC GBLogEx.info(min, ``); - + setFlagsFromString('--expose_gc'); const gc = runInNewContext('gc'); // nocommit gc(); - + // Capture memory usage after GC const memoryAfterGC = process.memoryUsage().heapUsed / 1024 / 1024; // in MB - GBLogEx.info(min, `BASIC: Closing Handles... From ${memoryBeforeGC.toFixed(2)} MB to ${memoryAfterGC.toFixed(2)} MB`); + GBLogEx.info( + min, + `BASIC: Closing Handles... From ${memoryBeforeGC.toFixed(2)} MB to ${memoryAfterGC.toFixed(2)} MB` + ); } - + public async asPDF({ pid, data }) { let file = await this.renderTable(pid, data, true, false); return file; @@ -746,7 +749,7 @@ export class SystemKeywords { */ public async saveToStorageBatch({ pid, table, rows }): Promise { const { min } = await DialogKeywords.getProcessInfo(pid); - + if (rows.length === 0) { return; } @@ -755,7 +758,6 @@ export class SystemKeywords { let rowsDest = []; rows.forEach(row => { - if (GBUtil.hasSubObject(row)) { row = this.flattenJSON(row); } @@ -772,7 +774,7 @@ export class SystemKeywords { row = null; }); GBLogEx.info(min, `SAVE '${table}': ${rows.length} row(s).`); - + await retry( async bail => { await t.bulkCreate(rowsDest); @@ -1039,7 +1041,7 @@ export class SystemKeywords { public static async getFilter(text) { let filter; - const operators = [/\<\=/, /\<\>/, /\>\=/, /\/, /\bnot in\b/, /\bin\b/, /\=/]; + const operators = [/\<\=/, /\<\>/, /\>\=/, /\/,/\blike\b/, /\bnot in\b/, /\bin\b/, /\=/]; let done = false; await CollectionUtil.asyncForEach(operators, async op => { var re = new RegExp(op, 'gi'); @@ -1193,6 +1195,23 @@ export class SystemKeywords { header = results.text[0]; rows = results.text; + } else if (file.indexOf('.csv') !== -1) { + let res; + let path = DialogKeywords.getGBAIPath(min.botId, `gbdata`); + const csvFile = Path.join(GBConfigService.get('STORAGE_LIBRARY'), path, file); + const firstLine = Fs.readFileSync(csvFile, 'utf8').split('\n')[0]; + const headers = firstLine.split(','); + const db = await csvdb(csvFile, headers, ','); + if (args[0]) { + const systemFilter = await SystemKeywords.getFilter(args[0]); + let filter = {}; + filter[systemFilter.columnName] = systemFilter.value; + res = await db.get(filter); + } else { + res = await db.get(); + } + + return res.length > 1 ? res : res[0]; } else { const t = this.getTableFromName(file, min); @@ -1745,27 +1764,27 @@ export class SystemKeywords { private flattenJSON(obj, res = {}, separator = '_', parent = null) { for (let key in obj) { - if (!obj.hasOwnProperty(key) || typeof obj[key] === 'function') { - continue; - } - if (typeof obj[key] !== 'object' || obj[key] instanceof Date) { - // If not defined already, add the flattened field. - const newKey = `${parent ? parent + separator : ''}${key}`; - if (!res.hasOwnProperty(newKey)) { - res[newKey] = obj[key]; - } else { - GBLog.verbose(`Ignoring duplicated field in flatten operation to storage: ${key}.`); - } + if (!obj.hasOwnProperty(key) || typeof obj[key] === 'function') { + continue; + } + if (typeof obj[key] !== 'object' || obj[key] instanceof Date) { + // If not defined already, add the flattened field. + const newKey = `${parent ? parent + separator : ''}${key}`; + if (!res.hasOwnProperty(newKey)) { + res[newKey] = obj[key]; } else { - // Create a temporary reference to the nested object to prevent memory leaks. - const tempObj = obj[key]; - this.flattenJSON(tempObj, res, separator, `${parent ? parent + separator : ''}${key}`); - // Clear the reference to avoid holding unnecessary objects in memory. - obj[key] = null; + GBLog.verbose(`Ignoring duplicated field in flatten operation to storage: ${key}.`); } + } else { + // Create a temporary reference to the nested object to prevent memory leaks. + const tempObj = obj[key]; + this.flattenJSON(tempObj, res, separator, `${parent ? parent + separator : ''}${key}`); + // Clear the reference to avoid holding unnecessary objects in memory. + obj[key] = null; + } } return res; -} + } public async getCustomToken({ pid, tokenName }) { const { min } = await DialogKeywords.getProcessInfo(pid); @@ -2349,7 +2368,7 @@ export class SystemKeywords { valueFound = found[e]; } }); - + const equals = typeof value === 'string' && typeof valueFound === 'string' ? value?.toLowerCase() != valueFound?.toLowerCase() @@ -2446,31 +2465,24 @@ export class SystemKeywords { } /** - * Publishs a tweet to X. + * Publishs a post to BlueSky . * - * TWEET "My tweet text" + * BlueSky "My BlueSky text" */ - public async tweet({ pid, text }) { + public async postToBlueSky({ pid, text }) { const { min, user } = await DialogKeywords.getProcessInfo(pid); - const consumer_key = min.core.getParam(min.instance, 'Twitter Consumer Key', null); - const consumer_secret = min.core.getParam(min.instance, 'Twitter Consumer Key Secret', null); - const access_token_key = min.core.getParam(min.instance, 'Twitter Access Token', null); - const access_token_secret = min.core.getParam(min.instance, 'Twitter Access Token Secret', null); + const consumer_key = min.core.getParam(min.instance, 'BlueSky Consumer Key', null); + const consumer_secret = min.core.getParam(min.instance, 'BlueSky Consumer Key Secret', null); + const access_token_key = min.core.getParam(min.instance, 'BlueSky Access Token', null); + const access_token_secret = min.core.getParam(min.instance, 'BlueSky Access Token Secret', null); if (!consumer_key || !consumer_secret || !access_token_key || !access_token_secret) { - GBLogEx.info(min, 'Twitter not configured in .gbot.'); + GBLogEx.info(min, 'BlueSky not configured in .gbot.'); } + throw new Error('Not implemented yet.'); - const client = new TwitterApi({ - appKey: consumer_key, - appSecret: consumer_secret, - accessToken: access_token_key, - accessSecret: access_token_secret - }); - - await client.v2.tweet(text); - GBLogEx.info(min, `Twitter Automation: ${text}.`); + GBLogEx.info(min, `BlueSky Automation: ${text}.`); } /** diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index 1ef7651a..e04a1bab 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -424,10 +424,12 @@ export class GBMinService { GBServer.globals.server .all(`/${min.instance.botId}/whatsapp`, async (req, res) => { + if (req.query['hub.mode'] === 'subscribe') { const val = req.query['hub.verify_token']; + const challenge = min.core.getParam(min.instance, `Meta Challenge`, null); - if (val === process.env.META_CHALLENGE) { + if (challenge && val === challenge) { res.send(req.query['hub.challenge']); res.status(200); GBLogEx.info(min, `Meta callback OK. ${JSON.stringify(req.query)}`); diff --git a/packages/llm.gblib/services/ChatServices.ts b/packages/llm.gblib/services/ChatServices.ts index 59da3b27..b234733a 100644 --- a/packages/llm.gblib/services/ChatServices.ts +++ b/packages/llm.gblib/services/ChatServices.ts @@ -621,8 +621,17 @@ export class ChatServices { private static getToolsAsText(tools) { return Object.keys(tools) - .map(toolname => `- ${tools[toolname].name}: ${tools[toolname].description}`) - .join('\n'); + .map(toolname => { + const tool = tools[toolname]; + const properties = tool.lc_kwargs.parameters.properties; + const params = Object.keys(properties).map(param => { + const { description, type } = properties[param]; + return `${param} (${type}): ${description}`; + }).join(', '); + + return `- ${tool.name}: ${tool.description}\n Parameters: ${params?? 'No parameters'}`; + }) + .join('\n'); } private static async getTools(min: GBMinInstance) { diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/describe-image-tool.bas b/templates/default.gbai/default.gbdialog/describe-image-tool.bas similarity index 100% rename from templates/llm-tools.gbai/llm-tools.gbdialog/describe-image-tool.bas rename to templates/default.gbai/default.gbdialog/describe-image-tool.bas diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/generate-image-tool.bas b/templates/default.gbai/default.gbdialog/generate-image-tool.bas similarity index 100% rename from templates/llm-tools.gbai/llm-tools.gbdialog/generate-image-tool.bas rename to templates/default.gbai/default.gbdialog/generate-image-tool.bas diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/search-tool.bas b/templates/default.gbai/default.gbdialog/search-tool.bas similarity index 100% rename from templates/llm-tools.gbai/llm-tools.gbdialog/search-tool.bas rename to templates/default.gbai/default.gbdialog/search-tool.bas diff --git a/templates/llm-tools.gbai/llm-tools.gbdata/products.csv b/templates/llm-tools.gbai/llm-tools.gbdata/products.csv index f882d14a..51e981f9 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdata/products.csv +++ b/templates/llm-tools.gbai/llm-tools.gbdata/products.csv @@ -1,7 +1,5 @@ name,price -Product A, 230 -Product B, 120 -Product C, 180 -Product D, 250 -Product E, 200 -Product F, 150 \ No newline at end of file +fax, 500 +TV, 1200 +mobile,200 +console, 250 diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas b/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas index 30f1363c..9ec29e6f 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas +++ b/templates/llm-tools.gbai/llm-tools.gbdialog/get-price.bas @@ -1,6 +1,6 @@ -PARAM product AS string LIKE "Product A" -DESCRIPTION "Returns the price of the given product." +PARAM product AS string LIKE telephone DESCRIPTION The name of the product to have the price retrieved. +DESCRIPTION Returns the price of the given product. -product = FIND "products.csv", "name LIKE ${product}" +product = FIND "products.csv", "name = ${product}" price = product.price -RETURN price +RETURN price \ No newline at end of file diff --git a/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas b/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas index c33da72d..bf81f7aa 100644 --- a/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas +++ b/templates/llm-tools.gbai/llm-tools.gbdialog/start.bas @@ -4,6 +4,5 @@ There exist some helpful predefined internal tools which can help me by extending my functionalities or get me helpful information. These tools **should** be abstracted away from the user. These tools can be invoked only by me before I respond to a user. -Here is the list of my internal tools: END SYSTEM PROMPT \ No newline at end of file diff --git a/templates/whatsapp.gbai/whatsapp.gbot/config.csv b/templates/whatsapp.gbai/whatsapp.gbot/config.csv new file mode 100644 index 00000000..fa9652b1 --- /dev/null +++ b/templates/whatsapp.gbai/whatsapp.gbot/config.csv @@ -0,0 +1 @@ +Meta Challenge,