diff --git a/packages/basic.gblib/index.ts b/packages/basic.gblib/index.ts index 6abf1215..47e17021 100644 --- a/packages/basic.gblib/index.ts +++ b/packages/basic.gblib/index.ts @@ -1,3 +1,4 @@ + /*****************************************************************************\ | █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® | | ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ | diff --git a/packages/basic.gblib/services/DialogKeywords.ts b/packages/basic.gblib/services/DialogKeywords.ts index a6dc28b2..5e630f83 100644 --- a/packages/basic.gblib/services/DialogKeywords.ts +++ b/packages/basic.gblib/services/DialogKeywords.ts @@ -1249,43 +1249,60 @@ export class DialogKeywords { const { min, user } = await DialogKeywords.getProcessInfo(pid); GBLogEx.info(min,`MESSAGE BOT: ${text}.`); - const conversation = min['apiConversations'][pid]; + const { conversation, client} = min['apiConversations'][pid]; - conversation.client.apis.Conversations.Conversations_PostActivity({ + await client.apis.Conversations.Conversations_PostActivity({ conversationId: conversation.conversationId, activity: { textFormat: 'plain', text: text, type: 'message', from: { - id: 'word', - name: 'word' + id: user.userSystemId, + name: user.userName } } }); - const watermarkMap = conversation.watermarkMap; + let messages = []; - const response = await conversation.client.apis.Conversations.Conversations_GetActivities({ - conversationId: conversation.conversationId, - watermark: conversation.watermark - }); - conversation.watermarkMap = response.obj.watermark; - let activities = response.obj.activites; + GBLog.info(`MessageBot: Starting message polling ${conversation.conversationId}).`); - if (activities && activities.length) { - activities = activities.filter(m => m.from.id === min.botId && m.type === 'message'); - if (activities.length) { - activities.forEach(activity => { - messages.push({ text: activity.text }); - GBLogEx.info(min, `MESSAGE BOT answer from bot: ${activity.text}`); + + + const worker = async () => { + try { + + const response = await client.apis.Conversations.Conversations_GetActivities({ + conversationId: conversation.conversationId, + watermark: conversation.watermark }); + conversation.watermarkMap = response.obj.watermark; + let activities = response.obj.activites; + + + if (activities && activities.length) { + activities = activities.filter(m => m.from.id === min.botId && m.type === 'message'); + if (activities.length) { + activities.forEach(activity => { + messages.push({ text: activity.text }); + GBLogEx.info(min, `MESSAGE BOT answer from bot: ${activity.text}`); + }); + } + } + + return messages.join('\n'); + + } catch (err) { + GBLog.error( + `Error calling printMessages API ${err.data === undefined ? err : err.data} ${err.errObj ? err.errObj.message : '' + }` + ); } - } - - return messages.join('\n'); + }; + setInterval(worker, DEFAULT_HEAR_POLL_INTERVAL); } diff --git a/packages/basic.gblib/services/GBVMService.ts b/packages/basic.gblib/services/GBVMService.ts index 3d6b899c..38a5c7e3 100644 --- a/packages/basic.gblib/services/GBVMService.ts +++ b/packages/basic.gblib/services/GBVMService.ts @@ -645,6 +645,8 @@ export class GBVMService extends GBService { } catch(e){ console.log(e); + + reject ({message: e.message, name: e.name}); } finally{ @@ -652,6 +654,8 @@ export class GBVMService extends GBService { await wa.closeHandles({pid: pid}); await sys.closeHandles({pid: pid}); + + resolve(true); } })(); `; @@ -1085,8 +1089,9 @@ export class GBVMService extends GBService { try { if (GBConfigService.get('GBVM') === 'false') { return await (async () => { - return await new Promise(resolve => { + return await new Promise((resolve, reject) => { sandbox['resolve'] = resolve; + sandbox['reject'] = reject; const vm1 = new NodeVM({ allowAsync: true, sandbox: sandbox, diff --git a/packages/basic.gblib/services/KeywordsExpressions.ts b/packages/basic.gblib/services/KeywordsExpressions.ts index e900a6ee..b92c7e30 100644 --- a/packages/basic.gblib/services/KeywordsExpressions.ts +++ b/packages/basic.gblib/services/KeywordsExpressions.ts @@ -714,7 +714,7 @@ export class KeywordsExpressions { keywords[i++] = [ /\= NEW OBJECT/gim, ($0, $1, $2, $3) => { - return ` = {pid: pid}`; + return ` = {}`; } ]; diff --git a/packages/basic.gblib/services/SystemKeywords.ts b/packages/basic.gblib/services/SystemKeywords.ts index f34a762e..994b227d 100644 --- a/packages/basic.gblib/services/SystemKeywords.ts +++ b/packages/basic.gblib/services/SystemKeywords.ts @@ -1440,16 +1440,19 @@ export class SystemKeywords { return ret; } - public static async setSystemPrompt({ pid, systemPrompt }) { + public async setSystemPrompt({ pid, text }) { let { min, user } = await DialogKeywords.getProcessInfo(pid); const sec = new SecService(); + + if (user) { - user['systemPrompt'] = systemPrompt; + ChatServices.userSystemPrompt[user.userSystemId] = text; + const path = DialogKeywords.getGBAIPath(min.botId); const systemPromptFile = urlJoin(process.cwd(), 'work', path, 'users',user.userSystemId, 'systemPrompt.txt'); - Fs.writeFileSync(systemPromptFile, systemPrompt); + Fs.writeFileSync(systemPromptFile, text); } } diff --git a/packages/core.gbapp/services/GBMinService.ts b/packages/core.gbapp/services/GBMinService.ts index f9df8743..8c71086e 100644 --- a/packages/core.gbapp/services/GBMinService.ts +++ b/packages/core.gbapp/services/GBMinService.ts @@ -455,8 +455,7 @@ export class GBMinService { if (!res) { return 'GeneralBots'; } - if (req.body?.AccountSid) - { + if (req.body?.AccountSid) { return 'official'; } return req.body.phone_id ? 'maytapi' : 'chatapi'; @@ -842,8 +841,8 @@ export class GBMinService { await min.whatsAppDirectLine.setup(true); } else { - if (min !== minBoot && minBoot.instance.whatsappServiceKey - && min.instance.webchatKey) { + if (min !== minBoot && minBoot.instance.whatsappServiceKey + && min.instance.webchatKey) { min.whatsAppDirectLine = new WhatsappDirectLine( min, min.botId, @@ -957,7 +956,7 @@ export class GBMinService { req.body.channelId = req.body.from.channelIdEx === 'whatsapp' ? 'omnichannel' : req.body.channelId; req.body.group = req.body.from.group; - + // Default activity processing and handler. const handler = async context => { @@ -1027,7 +1026,7 @@ export class GBMinService { } let pid = step.context.activity['pid']; - if (!pid){ + if (!pid) { pid = GBVMService.createProcessInfo(user, min, step.context.activity.channelId, null); } step.context.activity['pid'] = pid; @@ -1086,7 +1085,7 @@ export class GBMinService { const startDialog = min.core.getParam(min.instance, 'Start Dialog', null); - + if (context.activity.type === 'installationUpdate') { GBLog.info(`Bot installed on Teams.`); } else if (context.activity.type === 'conversationUpdate' && context.activity.membersAdded.length > 0) { @@ -1421,7 +1420,7 @@ export class GBMinService { GBLog.info( `Auto start (whatsapp) dialog is now being called: ${startDialog} for ${min.instance.instanceId}...` ); - await GBVMService.callVM(startDialog.toLowerCase(), min, step,pid); + await GBVMService.callVM(startDialog.toLowerCase(), min, step, pid); return; } @@ -1455,7 +1454,7 @@ export class GBMinService { } else if (cmdOrDialogName === '/callsch') { await GBVMService.callVM(args, min, null, pid); } else if (cmdOrDialogName === '/calldbg') { - await GBVMService.callVM(args, min, step, pid, true); + await GBVMService.callVM(args, min, step, pid, true); } else { await step.beginDialog(cmdOrDialogName, { args: args }); } @@ -1613,8 +1612,40 @@ export class GBMinService { await CollectionUtil.asyncForEach(Object.values(min.scriptMap), async script => { dialogs[script] = async (data) => { - let params = JSON.parse(data); - return await GBVMService.callVM(script, min, null, params.pid, false, params); + let sec = new SecService(); + const user = await sec.ensureUser( + min, + data.userSystemId, + data.userSystemId, + '', + 'api', + data.userSystemId, + null + ); + + let pid = data?.pid; + if (script === 'start'){ + pid = GBVMService.createProcessInfo(user, min, 'api', null); + + + const client = await new SwaggerClient({ + spec: JSON.parse(Fs.readFileSync('directline-3.0.json', 'utf8')), + requestInterceptor: req => { + req.headers['Authorization'] = `Bearer ${min.instance.webchatKey}`; + } + }); + const response = await client.apis.Conversations.Conversations_StartConversation(); + + min['apiConversations'][pid] = {conversation: response.obj, client: client}; + } + + let ret = await GBVMService.callVM(script, min, null, pid, false, data); + + if (script === 'start') + { + ret = pid; + } + return ret; } }); diff --git a/packages/gpt.gblib/services/ChatServices.ts b/packages/gpt.gblib/services/ChatServices.ts index 87141a44..3ebc77a5 100644 --- a/packages/gpt.gblib/services/ChatServices.ts +++ b/packages/gpt.gblib/services/ChatServices.ts @@ -69,7 +69,7 @@ export class CustomLLMOutputParser extends BaseLLMOutputParser { private toolChain: RunnableSequence private documentChain: RunnableSequence; - constructor( toolChain: RunnableSequence, documentChain: RunnableSequence) { + constructor(toolChain: RunnableSequence, documentChain: RunnableSequence) { super(); this.toolChain = toolChain; this.documentChain = documentChain; @@ -83,19 +83,19 @@ export class CustomLLMOutputParser extends BaseLLMOutputParser { "Output parser did not receive any generations." ); } - let parsedOutput; + let result; if (llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls) { - this.toolChain.invoke({func: llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls}); + return this.toolChain.invoke({ func: llmOutputs[0]['message'].lc_kwargs.additional_kwargs.tool_calls }); } if (isChatGeneration(llmOutputs[0])) { - parsedOutput = llmOutputs[0].message.content; + result = llmOutputs[0].message.content; } else { - parsedOutput = llmOutputs[0].text; + result = llmOutputs[0].text; } - return this.documentChain.invoke(parsedOutput); + return this.documentChain ? this.documentChain.invoke(result) : result; } } @@ -131,6 +131,8 @@ export class ChatServices { } + private static memoryMap = {}; + public static userSystemPrompt = {}; public static async answerByGPT(min: GBMinInstance, user, pid, question: string, @@ -142,22 +144,24 @@ export class ChatServices { return { answer: undefined, questionId: 0 }; } - const contentLocale = min.core.getParam( min.instance, 'Default Content Language', GBConfigService.get('DEFAULT_CONTENT_LANGUAGE') ); + const docsContext = min['vectorStore']; - const context = min['vectorStore']; - - const memory = new BufferWindowMemory({ - returnMessages: true, - memoryKey: 'chat_history', - inputKey: 'input', - k: 2, - }); + if (!this.memoryMap[user.userSystemId]) { + this.memoryMap[user.userSystemId] = new BufferWindowMemory({ + returnMessages: true, + memoryKey: 'chat_history', + inputKey: 'input', + k: 2, + }) + } + const memory = this.memoryMap[user.userSystemId]; + const systemPrompt = this.userSystemPrompt[user.userSystemId]; const model = new ChatOpenAI({ openAIApiKey: process.env.OPENAI_API_KEY, @@ -168,15 +172,19 @@ export class ChatServices { let tools = await ChatServices.getTools(min); let toolsAsText = ChatServices.getToolsAsText(tools); + const modelWithTools = model.bind({ tools: tools.map(convertToOpenAITool) }); const questionGeneratorTemplate = ChatPromptTemplate.fromMessages([ AIMessagePromptTemplate.fromTemplate( - `Answer the question without calling any tool, but if there is a need to call: + ` + Answer the question without calling any tool, but if there is a need to call: You have access to the following set of tools. Here are the names and descriptions for each tool: ${toolsAsText} + + Do not use any previous tools output in the chat_history. ` ), new MessagesPlaceholder("chat_history"), @@ -184,10 +192,22 @@ export class ChatServices { Standalone question:`), ]); + const toolsResultPrompt = ChatPromptTemplate.fromMessages([ + AIMessagePromptTemplate.fromTemplate( + `The tool just returned value in last call. Using {chat_history} + rephrase the answer to the user using this tool output. + ` + ), + new MessagesPlaceholder("chat_history"), + AIMessagePromptTemplate.fromTemplate(`Tool output: {tool_output} + Standalone question:`), + ]); + const combineDocumentsPrompt = ChatPromptTemplate.fromMessages([ AIMessagePromptTemplate.fromTemplate( - `Use the following pieces of context to answer the question at the end. + ` If you don't know the answer, just say that you don't know, don't try to make up an answer. + Use the following pieces, if any, of context to answer the question at the end. \n\n{context}\n\n ` ), @@ -197,13 +217,11 @@ export class ChatServices { const callToolChain = RunnableSequence.from([ { - func: async (output: object) =>{ + tool_output: async (output: object) => { const name = output['func'][0].function.name; const args = JSON.parse(output['func'][0].function.arguments); - GBLog.info(`Running .gbdialog '${name}' as GPT tool...`); - const pid = GBVMService.createProcessInfo(null, min, 'gpt', null); return await GBVMService.callVM(name, min, false, pid, false, args); @@ -212,12 +230,13 @@ export class ChatServices { const { chat_history } = await memory.loadMemoryVariables({}); return chat_history; }, + }, + toolsResultPrompt, + model, new StringOutputParser() ]); - - const combineDocumentsChain = RunnableSequence.from([ { question: (output: string) => output, @@ -226,7 +245,8 @@ export class ChatServices { return chat_history; }, context: async (output: string) => { - return await ChatServices.getRelevantContext(context, output, 1); + const c = await ChatServices.getRelevantContext(docsContext, output, 1); + return c ?? 'answer just with user question.'; }, }, @@ -245,34 +265,63 @@ export class ChatServices { }, questionGeneratorTemplate, modelWithTools, - new CustomLLMOutputParser(callToolChain, combineDocumentsChain) + new CustomLLMOutputParser(callToolChain, docsContext?.docstore?._docs.length > 0 ? combineDocumentsChain : null), + new StringOutputParser() ]); - const systemPrompt = user['systemPrompt']; + const directChain = RunnableSequence.from([ + { + question: (i: { question: string }) => { + return ` + ${systemPrompt} + + ${i.question}` + }, + chat_history: async () => { + const { chat_history } = await memory.loadMemoryVariables({}); + return chat_history; + }, + }, + modelWithTools, + new CustomLLMOutputParser(callToolChain, docsContext?.docstore?._docs.length > 0 ? combineDocumentsChain : null), + new StringOutputParser() + ]); - let result = await conversationalQaChain.invoke({ - question, - }); + const direct = true; + let result; - if (result['name']) { - const func = result['func']; - await func.func(min, result['args']); + if (direct) { + result = await (tools.length > 0 ? modelWithTools : model).invoke(` + ${systemPrompt} + + ${question}`); - } else { + result = result.content; + } + else { - // await memory.saveContext( - // { - // input: query, - // }, - // { - // output: result, - // } - // ); - - GBLog.info(`GPT Result: ${result.toString()}`); - return { answer: result.toString(), questionId: 0 }; + result = await (directChain ?? conversationalQaChain).invoke({ + question, + }); } + + + + + await memory.saveContext( + { + input: question, + }, + { + output: result, + } + ); + + GBLog.info(`GPT Result: ${result.toString()}`); + return { answer: result.toString(), questionId: 0 }; + + } private static getToolsAsText(tools) { @@ -286,39 +335,27 @@ export class ChatServices { // Adds .gbdialog as functions if any to GPT Functions. await CollectionUtil.asyncForEach(Object.keys(min.scriptMap), async (script) => { + + const path = DialogKeywords.getGBAIPath(min.botId, "gbdialog", null); const jsonFile = Path.join('work', path, `${script}.json`); - if (Fs.existsSync(jsonFile)) { + if (Fs.existsSync(jsonFile) && script.toLowerCase() !== 'start.vbs') { + const funcJSON = JSON.parse(Fs.readFileSync(jsonFile, 'utf8')); const funcObj = funcJSON?.function; - if (funcObj){ - + if (funcObj) { + // TODO: Use ajv. funcObj.schema = eval(jsonSchemaToZod(funcObj.parameters)); functions.push(new DynamicStructuredTool(funcObj)); } - + } }); - const multiplyTool = new DynamicStructuredTool({ - name: "multiply", - description: "Multiply two integers together.", - schema: z.object({ - firstInt: z.number(), - secondInt: z.number(), - }), - func: async ({ firstInt, secondInt }) => { - return (firstInt * secondInt).toString(); - }, - }); - - functions.push(multiplyTool); - - return functions; } }