From 681d20c5fe3e43048b9902e927f9d7debb27d62f Mon Sep 17 00:00:00 2001 From: Rodrigo Rodriguez Date: Mon, 5 Feb 2024 12:36:20 -0300 Subject: [PATCH] new(basic.gblib): GPT replacing ALLEN NLP Reading Comp. --- packages/admin.gbapp/dialogs/AdminDialog.ts | 2 +- .../basic.gblib/services/DialogKeywords.ts | 2 +- packages/core.gbapp/services/GBMinService.ts | 8 +- .../dialogs/FeedbackDialog.ts | 2 +- .../services/GoogleChatDirectLine.ts | 2 +- packages/gpt.gblib/services/ChatServices.ts | 104 +++++++++++++++++- packages/kb.gbapp/dialogs/AskDialog.ts | 6 +- packages/kb.gbapp/services/KBService.ts | 92 +--------------- .../security.gbapp/services/SecService.ts | 17 ++- .../services/WhatsappDirectLine.ts | 6 +- 10 files changed, 138 insertions(+), 103 deletions(-) diff --git a/packages/admin.gbapp/dialogs/AdminDialog.ts b/packages/admin.gbapp/dialogs/AdminDialog.ts index f125a9e6..53b1204b 100644 --- a/packages/admin.gbapp/dialogs/AdminDialog.ts +++ b/packages/admin.gbapp/dialogs/AdminDialog.ts @@ -327,7 +327,7 @@ export class AdminDialog extends IGBDialog { let sec = new SecService(); const member = step.context.activity.from; const user = await sec.ensureUser( - min.instance.instanceId, + min, member.id, member.name, '', diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index 044af62f..b9fd03e9 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -1220,7 +1220,7 @@ export class DialogKeywords { let user = await sec.getUserFromSystemId(fromOrDialogName); if (!user) { user = await sec.ensureUser( - min.instance.instanceId, + min, fromOrDialogName, fromOrDialogName, null, diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index cae0099c..cb04bb71 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -328,6 +328,10 @@ export class GBMinService { if (!Fs.existsSync(dir)) { mkdirp.sync(dir); } + dir = `work/${gbai}/users`; + if (!Fs.existsSync(dir)) { + mkdirp.sync(dir); + } // Loads Named Entity data for this bot. @@ -956,7 +960,7 @@ export class GBMinService { const member = context.activity.from; const sec = new SecService(); - const user = await sec.ensureUser(instance.instanceId, member.id, member.name, '', 'web', member.name, null); + const user = await sec.ensureUser(min, member.id, member.name, '', 'web', member.name, null); const userId = user.userId; const params = user.params ? JSON.parse(user.params) : {}; @@ -1351,7 +1355,7 @@ export class GBMinService { memberId = member.id; } - let user = await sec.ensureUser(min.instance.instanceId, memberId, member.name, '', 'web', member.name, email); + let user = await sec.ensureUser(min, memberId, member.name, '', 'web', member.name, email); const userId = user.userId; const params = user.params ? JSON.parse(user.params) : {}; diff --git a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts index a44e4732..f77095af 100644 --- a/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts +++ b/packages/customer-satisfaction.gbapp/dialogs/FeedbackDialog.ts @@ -88,7 +88,7 @@ export class FeedbackDialog extends IGBDialog { if (args && args.to) { // An user from Teams willing to transfer to a WhatsApp user. - await sec.ensureUser(min.instance.instanceId, args.to, 'Name', '', 'whatsapp', 'Name', null); + await sec.ensureUser(min, args.to, 'Name', '', 'whatsapp', 'Name', null); await sec.assignHumanAgent(min, args.to, profile.userSystemId); await min.conversationalService.sendText( diff --git a/packages/google-chat.gblib/services/GoogleChatDirectLine.ts b/packages/google-chat.gblib/services/GoogleChatDirectLine.ts index 65b339c1..f070b69d 100644 --- a/packages/google-chat.gblib/services/GoogleChatDirectLine.ts +++ b/packages/google-chat.gblib/services/GoogleChatDirectLine.ts @@ -137,7 +137,7 @@ export class GoogleChatDirectLine extends GBService { message.ack(); const sec = new SecService(); - const user = await sec.ensureUser(this.min.instance.instanceId, from, from, '', 'googlechat', fromName, from); + const user = await sec.ensureUser(this.min, from, from, '', 'googlechat', fromName, from); await sec.updateConversationReferenceById(user.userId, threadName); diff --git a/packages/gpt.gblib/services/ChatServices.ts b/packages/gpt.gblib/services/ChatServices.ts index 4c032e71..c0cbb672 100644 --- a/packages/gpt.gblib/services/ChatServices.ts +++ b/packages/gpt.gblib/services/ChatServices.ts @@ -32,14 +32,35 @@ import { GBMinInstance } from 'botlib'; import OpenAI from "openai"; -import { ChatGPTAPIBrowser, getOpenAIAuth } from 'chatgpt' +import { OpenAIChat } from 'langchain/llms/openai'; +import { CallbackManager } from 'langchain/callbacks'; +import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts'; +import { LLMChain } from 'langchain/chains'; +import { BufferWindowMemory } from 'langchain/memory'; import { CollectionUtil } from 'pragmatismo-io-framework'; import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js'; import Path from 'path'; import * as Fs from 'fs'; +import { HNSWLib } from 'langchain/vectorstores/hnswlib'; +import { GuaribasSubject } from '../../kb.gbapp/models/index.js'; +import { GBConfigService } from '../../core.gbapp/services/GBConfigService.js'; export class ChatServices { + private static async getRelevantContext( + vectorStore: HNSWLib, + sanitizedQuestion: string, + numDocuments: number + ): Promise { + const documents = await vectorStore.similaritySearch(sanitizedQuestion, numDocuments); + return documents + .map((doc) => doc.pageContent) + .join(', ') + .trim() + .replaceAll('\n', ' '); + } + + public static async sendMessage(min: GBMinInstance, text: string) { let key; if (process.env.OPENAI_KEY) { @@ -112,4 +133,85 @@ export class ChatServices { // }); // return chatCompletion.choices[0].message.content; } + + public static async answerByGPT(min: GBMinInstance, + query: string, + searchScore: number, + subjects: GuaribasSubject[] + ) { + + if (!process.env.OPENAI_KEY) { + return { answer: undefined, questionId: 0 }; + } + + + const contextVectorStore = min['vectorStore']; + const question = query.trim().replaceAll('\n', ' '); + const context = await this.getRelevantContext(contextVectorStore, question, 1); + + const systemPrompt = SystemMessagePromptTemplate.fromTemplate( + `You are $${min.botId}`); + + const contentLocale = min.core.getParam( + min.instance, + 'Default Content Language', + GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') + ); + + const chatPrompt = ChatPromptTemplate.fromPromptMessages([ + systemPrompt, + HumanMessagePromptTemplate.fromTemplate(`Answer in ${contentLocale}. + You have access to the context (RELEVANTDOCS) provided by the user. + + When answering think about whether the question in RELEVANTDOCS, but never mention + to user about the source. + Don’t justify your answers. Don't refer to yourself in any of the created content. + Don´t prefix RESPONSE: when answering the user. + RELEVANTDOCS: {context} + + QUESTION: """{input}""" + + `), + ]); + const windowMemory = new BufferWindowMemory({ + returnMessages: false, + memoryKey: 'immediate_history', + inputKey: 'input', + k: 2, + }); + + const callbackManager = CallbackManager.fromHandlers({ + // This function is called when the LLM generates a new token (i.e., a prediction for the next word) + async handleLLMNewToken(token: string) { + + }, + }); + + const llm = new OpenAIChat({ + streaming: true, + callbackManager, + modelName: 'gpt-3.5-turbo', + }); + + const chain = new LLMChain({ + prompt: chatPrompt, + memory: windowMemory, + llm, + }); + + const response = await chain.call({ + input: question, + context, + history: '', + immediate_history: '', + }); + if (response) { + + return { answer: response.text, questionId: 0 }; + } + + return { answer: undefined, questionId: 0 }; + } + + } diff --git a/packages/kb.gbapp/dialogs/AskDialog.ts b/packages/kb.gbapp/dialogs/AskDialog.ts index fb0ed804..63d50a0e 100644 --- a/packages/kb.gbapp/dialogs/AskDialog.ts +++ b/packages/kb.gbapp/dialogs/AskDialog.ts @@ -132,7 +132,7 @@ export class AskDialog extends IGBDialog { let sec = new SecService(); const member = step.context.activity.from; const user = await sec.ensureUser( - min.instance.instanceId, + min, member.id, member.name, '', @@ -187,7 +187,7 @@ export class AskDialog extends IGBDialog { let answer: GuaribasAnswer = null; const member = step.context.activity.from; const sec = new SecService(); - let user = await sec.ensureUser(min.instance.instanceId, member.id, member.name, '', 'web', member.name, null); + let user = await sec.ensureUser(min, member.id, member.name, '', 'web', member.name, null); const minBoot = GBServer.globals.minBoot as any; @@ -436,7 +436,7 @@ export class AskDialog extends IGBDialog { let sec = new SecService(); const member = step.context.activity.from; const user = await sec.ensureUser( - min.instance.instanceId, + min, member.id, member.name, '', diff --git a/packages/kb.gbapp/services/KBService.ts b/packages/kb.gbapp/services/KBService.ts index 49c2f0f7..9bb9e18a 100644 --- a/packages/kb.gbapp/services/KBService.ts +++ b/packages/kb.gbapp/services/KBService.ts @@ -34,28 +34,22 @@ import Path from 'path'; import Fs from 'fs'; -import { OpenAIChat } from 'langchain/llms/openai'; -import { CallbackManager } from 'langchain/callbacks'; import urlJoin from 'url-join'; import asyncPromise from 'async-promises'; import walkPromise from 'walk-promise'; import { SearchClient } from '@azure/search-documents'; import Excel from 'exceljs'; import getSlug from 'speakingurl'; -import { ChatPromptTemplate, HumanMessagePromptTemplate, SystemMessagePromptTemplate } from 'langchain/prompts'; -import { LLMChain } from 'langchain/chains'; import { GBServer } from '../../../src/app.js'; -import { HNSWLib } from 'langchain/vectorstores/hnswlib'; import { JSONLoader } from 'langchain/document_loaders/fs/json'; import { TextLoader } from 'langchain/document_loaders/fs/text'; import { PDFLoader } from 'langchain/document_loaders/fs/pdf'; import { DocxLoader } from 'langchain/document_loaders/fs/docx'; import { EPubLoader } from 'langchain/document_loaders/fs/epub'; import { CSVLoader } from 'langchain/document_loaders/fs/csv'; -import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'; -import { BufferWindowMemory } from 'langchain/memory'; -import { Document } from 'langchain/document'; import path from 'path'; +import { RecursiveCharacterTextSplitter } from 'langchain/text_splitter'; +import { Document } from 'langchain/document'; import { GBDialogStep, @@ -85,6 +79,8 @@ import { GBAdminService } from '../../admin.gbapp/services/GBAdminService.js'; import { GBVMService } from '../../basic.gblib/services/GBVMService.js'; import { DialogKeywords } from '../../basic.gblib/services/DialogKeywords.js'; import { GBMinService } from '../../core.gbapp/services/GBMinService.js'; +import { ChatServices } from '../../gpt.gblib/services/ChatServices.js'; + /** * Result for quey on KB data. @@ -365,7 +361,7 @@ export class KBService implements IGBKBService { returnedScore: ${returnedScore} < required (searchScore): ${searchScore}` ); - return await this.answerByGPT(min, + return await ChatServices.answerByGPT(min, query, searchScore, subjects @@ -374,86 +370,8 @@ export class KBService implements IGBKBService { } } - private async getRelevantContext( - vectorStore: HNSWLib, - sanitizedQuestion: string, - numDocuments: number - ): Promise { - const documents = await vectorStore.similaritySearch(sanitizedQuestion, numDocuments); - return documents - .map((doc) => doc.pageContent) - .join(', ') - .trim() - .replaceAll('\n', ' '); - } - public async answerByGPT(min: GBMinInstance, - query: string, - searchScore: number, - subjects: GuaribasSubject[] - ) { - const contextVectorStore = min['vectorStore']; - const question = query.trim().replaceAll('\n', ' '); - const context = await this.getRelevantContext(contextVectorStore, question, 1); - - const systemPrompt = SystemMessagePromptTemplate.fromTemplate( - "You are General Bots"); - - const chatPrompt = ChatPromptTemplate.fromPromptMessages([ - systemPrompt, - HumanMessagePromptTemplate.fromTemplate(`Answer in pt-br. - You have access to the context (RELEVANTDOCS) provided by the user. - - When answering think about whether the question in RELEVANTDOCS, but never mention - to user about the source. - Don’t justify your answers. Don't refer to yourself in any of the created content. - Don´t prefix RESPONSE: when answering the user. - RELEVANTDOCS: {context} - - QUESTION: """{input}""" - - `), - ]); - const windowMemory = new BufferWindowMemory({ - returnMessages: false, - memoryKey: 'immediate_history', - inputKey: 'input', - k: 2, - }); - - const callbackManager = CallbackManager.fromHandlers({ - // This function is called when the LLM generates a new token (i.e., a prediction for the next word) - async handleLLMNewToken(token: string) { - - }, - }); - - const llm = new OpenAIChat({ - streaming: true, - callbackManager, - modelName: 'gpt-3.5-turbo', - }); - - const chain = new LLMChain({ - prompt: chatPrompt, - memory: windowMemory, - llm, - }); - - const response = await chain.call({ - input: question, - context, - history: '', - immediate_history: '', - }); - if (response) { - - return { answer: response.text, questionId: 0 }; - } - - return { answer: undefined, questionId: 0 }; - } public async getSubjectItems(instanceId: number, parentId: number): Promise { diff --git a/packages/security.gbapp/services/SecService.ts b/packages/security.gbapp/services/SecService.ts index d4c40eb2..e34c351f 100644 --- a/packages/security.gbapp/services/SecService.ts +++ b/packages/security.gbapp/services/SecService.ts @@ -1,16 +1,18 @@ -import { GBServer } from '../../../src/app.js'; import { ConversationReference } from 'botbuilder'; import { GBLog, GBMinInstance, GBService, IGBInstance } from 'botlib'; import { CollectionUtil } from 'pragmatismo-io-framework'; import { GuaribasUser } from '../models/index.js'; import { FindOptions } from 'sequelize'; +import { DialogKeywords } from '../../../packages/basic.gblib/services/DialogKeywords.js'; +import * as Fs from 'fs'; +import mkdirp from 'mkdirp'; /** * Security service layer. */ export class SecService extends GBService { public async ensureUser( - instanceId: number, + min: GBMinInstance, userSystemId: string, userName: string, address: string, @@ -26,9 +28,18 @@ export class SecService extends GBService { if (!user) { user = GuaribasUser.build(); + const gbaiPath = DialogKeywords.getGBAIPath(min.botId); + + const dir = `work/${gbaiPath}/users/${userSystemId}`; + if (!Fs.existsSync(dir)) { + mkdirp.sync(dir); + } + + + } - user.instanceId = instanceId; + user.instanceId = min.instance.instanceId; user.userSystemId = userSystemId; user.userName = userName; user.displayName = displayName; diff --git a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts index 87da513c..f5ceb6ce 100644 --- a/packages/whatsapp.gblib/services/WhatsappDirectLine.ts +++ b/packages/whatsapp.gblib/services/WhatsappDirectLine.ts @@ -472,7 +472,7 @@ export class WhatsappDirectLine extends GBService { }); const sec = new SecService(); - const user = await sec.ensureUser(this.min.instance.instanceId, from, fromName, '', 'whatsapp', fromName, null); + const user = await sec.ensureUser(this.min, from, fromName, '', 'whatsapp', fromName, null); const locale = user.locale ? user.locale : 'pt'; if (answerText) { @@ -1083,7 +1083,7 @@ export class WhatsappDirectLine extends GBService { } const botId = getKeyByValue(WhatsappDirectLine.botGroups, group); if ((botId && user.instanceId !== this.min.instance.instanceId) || !user) { - user = await sec.ensureUser(this.min.instance.instanceId, id, senderName, '', 'whatsApp', senderName, null); + user = await sec.ensureUser(this.min, id, senderName, '', 'whatsApp', senderName, null); } if (botId) { activeMin = GBServer.globals.minInstances.filter(p => p.instance.botId === botId)[0]; @@ -1122,7 +1122,7 @@ export class WhatsappDirectLine extends GBService { // start dialog if any is specified in Config.xlsx. if (user === null || user.hearOnDialog) { - user = await sec.ensureUser(activeMin.instance.instanceId, id, senderName, '', 'whatsapp', senderName, null); + user = await sec.ensureUser(activeMin, id, senderName, '', 'whatsapp', senderName, null); const startDialog = user.hearOnDialog ? user.hearOnDialog