// ************************************************************* // Genesys Intelligent Automation // Custom Natural Language Menu // // Script: Perform natural language interpretation // Supported Channels: Web Chat(Rich Media), Text based channels // NLU: Goggle Dialogflow CX Integration // Version: 3.0 // Date: 01.04.2021 // Author: Andreas Schneider // // POSTing utterances to the configured Dialogflow CX agent // Parsing results - extracting slots and intent // ************************************************************* import com.speechstorm.http.FishHttpException; import com.speechstorm.fish.scripting.RemoteHttpDataResult; import com.speechstorm.fish.runtime.RecognitionResults; // // POSTing utterances to the configured Dialogflow CX agent // // Parameters // Retrieve from IA session token and session ID generated in the previous script and get other CX path parameters. // sSessionID // sToken // NOTE: only valid for 60 min, then needs to be requested again. // Get token generated in -Start dialog engine session- script def sToken = context.getVariable("token"); // Get sessionID generated in -Start dialog engine session- script def sSessionID = context.getVariable("sSessionID"); // ************************************************************* // Get CNLM module parmameters to construct endpoint path // These parameters, especially language, location(region), agent and environment id should be made available as module parameters. // Best would be to have the product sync them using the API // projects/ghostbusterscxagent/locations/europe-west1/agents/eb14b3b3-1915-42e2-9fa6-decd09606d8d/environments/f7b88ecf-bbf5-415a-8fac-19e7eb2ad72c // ************************************************************ def slanguageCode = context.getVariable("LanguageCode"); def sAPIVersion = context.getVariable("GoogleCXAPIVersion"); def sGoogleCXProjectID = context.getVariable("GoogleCXProjectID"); def sLocations = context.getVariable("GoogleCXRegion"); def sAgentID =context.getVariable("GoogleCXAgentID"); def sEnvironmentID = context.getVariable("GoogleCXEnvironmentID"); context.log(" ***** Retrieved information required for API request"); // ************************************************************ // Get FirstName, LastName and EmailAddress (from Genesys Widget Chat Registration Form) // to be passed to the CX Agent // Please note: // To make FirstName, LastName and EmailAddress available to the IA session you need // to add them in the start block - read KVP // ************************************************************ def sCustomerContextFirstName =context.getVariable("FirstName"); def sCustomerContextLastName =context.getVariable("LastName"); def sCustomerContextEmail =context.getVariable("EmailAddress"); def sIntentHistory = context.getVariable("NLIntentHistory"); // ************************************************************ // Construct the JSON query for intent // ************************************************************ // Headers def headers = []; headers.add("Authorization: Bearer "+ sToken); headers.add("Accept: application/json"); // QueryInput - detectIntent // For more information please check Google's documentation: // https://cloud.google.com/dialogflow/cx/docs/reference/rest/v3/projects.locations.agents.sessions/detectIntent // // Get the recognition result from the "Ask natural language question" menu block and add it to the query // Get the ASR language as well if needed def response = context.getRecognitionResults("Ask natural language question"); context.log(" ***** Recognition Result from Ask natural language question : " + response.getResult()) def sQueryInput = " {\r\n"+ " \"queryInput\" : {\r\n" + " \"text\" : {\r\n" + " \"text\":\"" + response.getResult() + "\",\r\n" + " },\r\n" + " \"languageCode\":\""+ slanguageCode+"\",\r\n" + " },\r\n" + " \"queryParams\": {\r\n" + " \"parameters\" : {\r\n" + " \"first-name\":[\"" + sCustomerContextFirstName + "\"],\r\n" + " \"last-name\":[\"" + sCustomerContextLastName +" \"],\r\n" + " \"email\":[\"" + sCustomerContextEmail + " \"],\r\n" + " }\r\n" + " }\r\n" + " }"; context.log(" ***** Created Query Input : "+ sQueryInput); // *********************************************** // Enter BASE_API_URL here to make a POST request with the token to the Google's DF API in the format: // For example, the following endpoint uses the 6db409d7-57ac-41d7-83bd-89b8768e2745 environment ID: // https://dialogflow.googleapis.com/v3beta1/projects/my-project-id/locations/us/agents/my-agent-id/environments/6db409d7-57ac-41d7-83bd-89b8768e2745/sessions/my-session-id:detectIntent // PLEASE NOTE: If other region than default US is used, you need a region prefix: // https://europe-west1-dialogflow.googleapis.com/ // We should have a dropdown selection with the new product. // Creating Google Dialogflow CX BASE URL // *********************************************** def sBASE_API_URL = "https://europe-west1-dialogflow.googleapis.com/" + sAPIVersion + "/projects/" + sGoogleCXProjectID + "/locations/" + sLocations + "/agents/" + sAgentID + "/environments/" + sEnvironmentID+ "/sessions/" + sSessionID + ":detectIntent?access_token="+sToken; context.log(" ***** Dialogflow CX BASE_API_URL: " + sBASE_API_URL); // ******************************************************************** // Start API Request and parsing results // ******************************************************************** try { context.log("Making a POST request to DF CX API detect intent"); RemoteHttpDataResult intentData = context.getRemoteHttpData(sBASE_API_URL, "POST", headers, sQueryInput, "application/json", 5000); context.log(" ***** POST COMPLETED *****"); def JSONResponseBody = intentData.getResponseJson(); context.log("DF Response : "+ intentData.getResponseText()); // ******************************************************************** // Get all text responseMessages concatenate text response string // ******************************************************************** def sResponseMessages = JSONResponseBody.queryResult.responseMessages; context.log(" ***** Here the response messages we got from CX Agent" + sResponseMessages); def sMessageText =""; def mt= 0; sResponseMessages.each { messageRespomnseEntry -> context.log( " ***** Start parsing messages *****") try { context.log(" ***** Response Message :" + mt ); sResponseMessage = JSONResponseBody.queryResult.responseMessages[mt]; context.log(" ***** Response Message :" + mt + " :" + sResponseMessage); sText = JSONResponseBody.queryResult.responseMessages[mt].text.text[0] context.log(" ***** Response Message Text extracted for entry :" + mt + " :" + sText ); sMessageText = [sMessageText, sText].join("\r\n") } catch(e){ context.log("No text message returned or not a type text"); }; context.setVariable("FulfillmentText",sMessageText); mt++ }; context.log(" ***** The complete response to customer is " + sMessageText ); context.setVariable("NLMenuPrompt",sMessageText); // ******************************************************************* // Check queryResult for custom JSON payload - e.g. Genesys Rich Media // Need to check if Genesys Widget is able to render if there is more // than one page returning a custom payload - join or concatenate ? // ******************************************************************* context.log( " ***** Start parsing queryResults for custom payload *****"); pl=0; sResponseMessages.each { messageRespomseEntryPayload -> context.log( " ***** Start parsing custom payload of response message:: " +pl ) try { context.log(" ***** Response Message :" + pl ); sDialogflowCXCustomJSONPayload = JSONResponseBody.queryResult.responseMessages[pl].payload if (sDialogflowCXCustomJSONPayload) { sGenesysRMJSON = context.toJsonString(sDialogflowCXCustomJSONPayload); context.log(" ****** Dialogflow CX queryResult has custom JSON paylod *****" ); context.log(" ****** Custom payload is :" + sDialogflowCXCustomJSONPayload ); sCustomRMprompt = context.createNativePrompt(sMessageText, sGenesysRMJSON, "PureEngageStructuredMessage", "genesys-chat"); context.log("NATIVE PROMPT ${sCustomRMprompt}"); context.setVariable("NLMenuPrompt", sCustomRMprompt); } else { context.log("****** No custom payload *****"); } } catch(e){ context.log("****** Exception at Parsing Custom Payload"); context.log("Exception: ${e}"); }; pl++ }; // ********************************************************************* // Try to get Intent - Some CX pages may not return an intent but parameters only -filling slots only // ********************************************************************* try { context.log(" ***** Now getting returned intent - you may have to change JSON path in future"); def sNLIntent = JSONResponseBody.queryResult.match.intent.displayName context.log("NLIntent : " + sNLIntent); context.setVariable("NLIntent",sNLIntent, true); if (!sIntentHistory?.trim()){ sIntentHistory = sNLIntent } else { sIntentHistory = sIntentHistory+ " -- " + sNLIntent }; context.log("NLIntentHistory : " + sIntentHistory); context.setVariable("NLIntentHistory",sIntentHistory, true); } catch(Exception e) { context.log("Exception: ${e}"); context.log("Response does not have an intent, may be a page returning parameters only"); }; // ********************************************************************* // Get all slots/entities and make them available to GIA session // Any IA Session variables will be attached before interaction is returned to // routing with -Attach all IA session variables for desktop- script // ********************************************************************* context.log(" ***** Now extracting all entities returned ***** "); def sGDFEntities = JSONResponseBody.queryResult.parameters context.log("GDF Entities : "+ sGDFEntities ); sGDFEntities.each { entry -> sSlotName = entry.key context.log("SlotName : "+ sSlotName ); sSlotValue = entry.value context.log("SlotValue: "+sSlotValue ); context.setVariable(sSlotName,sSlotValue); } context.log(" ***** End extracting entities ***** "); // ********************************************************************* // Check if any of the Dialogflow CX Pages returns a liveAgentHandoff to genesys agent // Any Dialogflow CX Page you want to trigger aliveAgentHandoff, you will need to add // a dialog option with a JSON payload // { // "genesys": "agent" // } // You may add additional keys and values, extract the information in the script below and use // it for segmentation in routing and reporting // ********************************************************************* context.log(" ***** Check liveAgentHandoff in ResponseMessage *****"); context.log(" ***** sResponseMessages contains :" + sResponseMessages); def sliveAgentHandoff = false lah = 0; sResponseMessages.each { messageRespomnseEntryliveAgentHandoff -> context.log( " ***** Start parsing custom payload of response message liveAgentHandoff: " +lah ) try { context.log(" ***** Response Message :" + lah ); sDialogflowCXliveAgentHandoff = JSONResponseBody.queryResult.responseMessages[lah].liveAgentHandoff.metadata.genesys if (sDialogflowCXliveAgentHandoff) { context.log (" ***** liveAgentHandoff is type genesys:" + sDialogflowCXliveAgentHandoff); sliveAgentHandoff = true } else { context.log("****** No liveAgentHandoff *****"); } } catch(e){ context.log("****** responseMessage: " + lah + "is not a type liveAgentHandoff"); }; lah++ }; if (sliveAgentHandoff) { return "agent_transfer"; } // ********************************************************************* // Get current page - check for agent transfer or session end or breakout for IA Fulfillment based on page displayed name // ********************************************************************* sCurrentPage = JSONResponseBody.queryResult.currentPage.displayName sResponseMessages = JSONResponseBody.queryResult.responseMessages context.log(" ***** Current Page is : " + sCurrentPage); // ********************************************************************* // If page is end session - end interaction // ********************************************************************* if (sCurrentPage=="End Session") { return "end_session" } // ********************************************************************* // If no breakout - go to ask another question // ********************************************************************* } else { return "ask another question"; } } catch(FishHttpException e){ //NOTE: check here for e.getStatusCode() == 401 for token expiration and re-request context.log("Error while retrieving the intent data: "+ e.getResponseBody()); return "error"; };