From 27d7474354b12ca031bbe1f0c4de5635d4bd2a64 Mon Sep 17 00:00:00 2001 From: "Peter.Morton" Date: Thu, 7 Aug 2025 00:48:43 -0500 Subject: [PATCH] proactive agent proxy now runs models for responses --- .../proactive_agent_settings.json | 9 + .../ProxyScript/proactive_agent_messenger.js | 289 ++++++++++++++++++ .../snippets/DigitalWelcomeEvent.js | 25 ++ proactiveAgent/snippets/join.js | 17 ++ 4 files changed, 340 insertions(+) create mode 100644 proactiveAgent/_studio_dependencies/GlobalVariable/proactive_agent_settings.json create mode 100644 proactiveAgent/_studio_dependencies/ProxyScript/proactive_agent_messenger.js create mode 100644 proactiveAgent/snippets/DigitalWelcomeEvent.js create mode 100644 proactiveAgent/snippets/join.js diff --git a/proactiveAgent/_studio_dependencies/GlobalVariable/proactive_agent_settings.json b/proactiveAgent/_studio_dependencies/GlobalVariable/proactive_agent_settings.json new file mode 100644 index 0000000..f78a69b --- /dev/null +++ b/proactiveAgent/_studio_dependencies/GlobalVariable/proactive_agent_settings.json @@ -0,0 +1,9 @@ +{ + "user": { + "name": "Proactive Agent", + "avatar": "https://storage.googleapis.com/speakeasyai-agent-desktop/img/verint-logo.png", + "userId": "d4de56b5-3fac-4a42-892f-ebec914b6aa3", + "type": "bot" + }, + "modelName": "proactiveAgent" +} diff --git a/proactiveAgent/_studio_dependencies/ProxyScript/proactive_agent_messenger.js b/proactiveAgent/_studio_dependencies/ProxyScript/proactive_agent_messenger.js new file mode 100644 index 0000000..b2eb079 --- /dev/null +++ b/proactiveAgent/_studio_dependencies/ProxyScript/proactive_agent_messenger.js @@ -0,0 +1,289 @@ +console.log({ query: req.query, params: req.params, body: req.body }); + +const msgrSettings = settings_667ef98d3ee8930a5debcbdb; +const paSettings = proactive_agent_settings; +const studioToken = msgrSettings + ? { + Authorization: `Bearer ${msgrSettings.messenger.token}`, + } + : {}; + +let bot_user = { + ...paSettings.user, +}; + +const app = { + routes: [], + route: function (regexp, fn) { + this.routes.push({ regexp: new RegExp(regexp), function: fn }); + }, + run: function (req, res) { + const path = + req.params[0].split( + `/ProxyScript/run/${req.params.workspaceId}/${req.params.branch}/${req.params.route}` + )[1] || "/"; + + for (var route of this.routes) { + console.log(`Checking route: ${route.regexp}`); + console.log(`Against path: ${path}`); + if (route.regexp.test(path)) { + console.log(`Running route: ${route.regexp}`); + console.log(`Path: ${path}`); + console.log(`Function: ${route.function.name}`); + route.function(req, res, route.regexp.exec(path)); + return; + } + } + // If no route matches, return a 404 status + const status = { path: req.params[0], status: 404 }; + res.status(404).send(status); + }, +}; + +const redisKey = (userId) => { + return `user-map-${userId}`; +}; + +const axioshelper = (message, config, callback, retries = 3, delay = 100) => { + return new Promise(async (resolve, reject) => { + for (let i = 0; i < retries; i++) { + try { + const response = await axios(config); + console.log({ response: response }); + if (response.status < 200 || response.status > 299) { + if (callback) { + await callback(); + } else { + throw new Error( + `Axios Request failed! ${JSON.stringify(response)}` + ); + } + } + resolve(response); + break; + } catch (error) { + if (error.response) { + // The request was made and the server responded with a status code + // that falls out of the range of 2xx + console.log(error.response.data); + console.log(error.response.status); + console.log(error.response.headers); + } else if (error.request) { + // The request was made but no response was received + // `error.request` is an instance of XMLHttpRequest in the browser and an instance of + // http.ClientRequest in node.js + console.log(error.request); + } else { + // Something happened in setting up the request that triggered an Error + console.log("Error", error.message); + } + console.log(error.config); + if (i === retries - 1) { + if (callback) { + await callback(); + } + console.log({ + retry: i + 1, + error: "out of retries", + message, + url: config.url, + }); + reject(error); + } else { + console.log({ + level: "warn", + message: `Attempt ${i + 1} failed.`, + url: config.url, + }); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + } + }); +}; + +app.route("^(?:/userId/([^/]+))(?:/$)?$", async (req, res, match) => { + try { + console.log("Received request for userId:", match[1]); + const modelName = + req.params?.modelName || proactive_agent_settings.modelName; + if (match.length > 1) { + const userId = match[1]; + const smStr = redisKey(userId); + const sessionMap = await redis.hGetAll(smStr); + const metadata = { + userId: bot_user.userId, + }; + + const recognizedData = await axios + .post( + `${settings_667ef98d3ee8930a5debcbdb.nlu.apiBaseURL}Model/run/${req.params.workspaceId}/${req.params.branch}/${modelName}`, + { + input: req.body.input, + conversationId: sessionMap.conversationId, + settings: settings_667ef98d3ee8930a5debcbdb.nlu.settings, + metadata: metadata, + } + ) + .then((response) => { + return response.data; + }) + .catch((error) => { + //If we have error handling turned off, don't record the error + if (settings_667ef98d3ee8930a5debcbdb.errorHandling === false) { + throw error; + } else { + // Conversation ID is generated by the NLU + // Must make sure Conversation ID is created properly for an error transaction + // Get it from session map if it exists, or generate it if we got the error at the very first transaction. + const conversationId = sessionMap.conversationId || uuidv4(); + let errorPayload = { + id: uuidv4(), + workspaceId: req.params.workspaceId, + errorInfo: { + name: error.name, + message: error.message, + stack: error.stack, + }, + conversationId, + input: req.body.input, + answers: [settings_667ef98d3ee8930a5debcbdb.responses.modelError], + req: { + params: req.params, + query: req.query, + body: req.body, + headers: req.headers, + }, + metadata, + classificationResults: [ + { + label: "Model Error", + value: 0.0, + }, + ], + nerResults: { + entities: [], + sourceEntities: [], + }, + // Reporting tags to match GlobalSupport function reporting + tag: "ERROR", + reporting: { + tag: "ERROR", + modelResponse: "ERROR", + }, + }; + + // Add model name + errorPayload.req.params.route = modelName; + + //add any other details you wish to see in transaction data + + return errorPayload; + } + }); + // console.log(recognizedData); + if (recognizedData.conversationId) { + await redis.hSet( + smStr, + "conversationId", + recognizedData.conversationId + ); + await redis.expire(smStr, 3600); + if (sessionMap.conversationId) { + } else { + db.analytics.addConversation({ + id: recognizedData.conversationId, + }); + } + } + recognizedData._id = ( + await db.analytics.addTransaction(recognizedData) + ).insertedId; + + // Send Answers to Messenger + new Promise(async (resolve) => { + for (const [index, answer] of recognizedData.answers.entries()) { + try { + await axios.post( + `${settings_667ef98d3ee8930a5debcbdb.messenger.apiBaseURL}Event/create`, + { + conversationId: sessionMap.sessionId, + metadata: { + channel: "web", + }, + input: answer, + sentBy: bot_user, + ping: nanoid(), + sentAt: Date.now(), + options: recognizedData?.options, + metadata: { + outputs: + recognizedData.answers.length == index + 1 + ? recognizedData?.outputs + : {}, + transactionId: recognizedData._id, + feedbackable: false, + }, + }, + { + headers: { + Authorization: `Bearer ${settings_667ef98d3ee8930a5debcbdb.messenger.token}`, + }, + } + ); + } catch (e) {} + } + resolve(true); + }); + res.send(recognizedData); + } else { + res.status(400).send({ error: "Bad Request" }); + } + } catch (error) { + console.error(error); + return res.status(500).send({ + status: 500, + }); + } +}); + +app.route("/", async (req, res) => { + const sessionId = req.body.event?.conversationId; + const params = req.body.event?.params || {}; + const modelName = + params?.modelName || settings_667ef98d3ee8930a5debcbdb.nlu.modelName; + const env = params?.env; + const postBack = req.body.event?.postBack; + const metadata = { + ...req.body.event?.metadata, + userId: req.body.event?.sentBy?.userId, + }; + const configuration = req.body.event?.configuration; + + try { + const umStr = redisKey(metadata.userId); + + await redis.hSet(umStr, "sessionId", sessionId); + await redis.expire(umStr, 3600); + + await axioshelper("messenger join", { + method: "post", + url: `${msgrSettings.messenger.apiBaseURL}Conversation/join`, + headers: studioToken, + data: { + _id: sessionId, + participant: bot_user, + }, + }); + + res.send(); + } catch (error) { + console.log(error.message); + res.send({ + answers: ["Something went wrong. Please try again."], + outputs: {}, + }); + } +}); + +app.run(req, res); diff --git a/proactiveAgent/snippets/DigitalWelcomeEvent.js b/proactiveAgent/snippets/DigitalWelcomeEvent.js new file mode 100644 index 0000000..16c7c0b --- /dev/null +++ b/proactiveAgent/snippets/DigitalWelcomeEvent.js @@ -0,0 +1,25 @@ +// Add this code to the Messenger's 'On Engagement Load' block +window.askIVA = (input, postBack, metadata, configuration) => { + // store.main.showMessenger(); + // store.main.setView("chat"); + // store.main.setNcPing(crypto.randomUUID()); + store.conversationEvent.sendMessage({ + input, + postBack, + metadata, + configuration, + }); +}; + +// Set form when messenger is opened +vm.$watch( + () => store.main.newConversation, + async (value) => { + if (value) { + console.log("Starting New Conversation..."); + setTimeout(() => { + window.askIVA("Digital Wecome Event"); + }, 100); + } + } +); diff --git a/proactiveAgent/snippets/join.js b/proactiveAgent/snippets/join.js new file mode 100644 index 0000000..4fe0823 --- /dev/null +++ b/proactiveAgent/snippets/join.js @@ -0,0 +1,17 @@ +(async () => { + proactive_agent.joinConversation(conversationData.id); +})() + .catch((error) => { + console.log(error.message); + recognizedObject.answers.push(""); + recognizedObject.errorInfo = { + ...recognizedObject.errorInfo, + label: { + data: error.toJSON ? error.toJSON() : {}, + message: error.message, + }, + }; + }) + .finally(() => { + next(); + });