Created
April 5, 2020 11:18
-
-
Save peternann/41b68c561a955f858ec08285cd54ef25 to your computer and use it in GitHub Desktop.
Cognigy integration with Amazon Connect/Lex via Cognigy.AI Transformers
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
The code herein relates to the Cognigy blog post here: | |
https:// | |
Be sure to read the blog post to understand the context of this code. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USE THIS CODE, COMPLETE, IN A COGNIGY.AI 'NLU CONNECTOR' OF TYPE 'COGNIGY', FOR THE 'TRANSFORMER FUNCTIONS': | |
// BE SURE TO 'ENABLE POST NLU TRANSFORMER' IN THE NLU CONNECTOR CONFIG, ABOVE THE SOURCE CODE: | |
createNluTransformer({ | |
/** | |
* This transformer is executed when receiving the output from the | |
* NLU engine, before executing the Flow. | |
* | |
* @param text The text from the message. | |
* @param data The data object from the message. | |
* @param nluResult Intent, type, slots and intentscore, | |
* after executing the NLU engine. | |
* @param connectorOutput The raw output from the NLU engine. | |
* | |
* @returns nluResult and/or data, as changed in the transformer. | |
*/ | |
postNlu: async ({ text, data, nluResult, connectorOutput }) => { | |
// Since we are behind Lex, we received Lex NLU result data in the endpoint Transformer. | |
// All we have to do is borrow the intent details from Lex, and pretend it is the Cognigy result: | |
if ( data.lex && data.lex.currentIntent && data.lex.currentIntent.name) { | |
nluResult.intent = data.lex.currentIntent.name; | |
// Merge Lex slots in with any found by Cognigy: | |
Object.assign(nluResult.slots, data.lex.currentIntent.slots); | |
} | |
console.log( "######## nluResult transformed to: " + JSON.stringify(nluResult) ); | |
return { data, nluResult }; | |
}, | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USE THIS CODE, COMPLETE, IN A COGNIGY.AI 'REST ENDPOINT', FOR THE 'TRANSFORMER FUNCTIONS': | |
// BE SURE TO 'ENABLE INPUT TRANSFORMER' AND 'ENABLE EXECUTION FINISHED TRANSFORMER' IN THE | |
// ENDPOINT CONFIG, ABOVE THE SOURCE CODE | |
// Lex request/response reference: https://docs.aws.amazon.com/lex/latest/dg/lambda-input-response-format.html | |
// Note that Cognigy.AI Transformers are actually Typescript code. | |
// Define Typescript types for Lex request/response formats: | |
/** There is more to this format, but this is what we use for now: */ | |
interface RequestFromLex { | |
inputTranscript: string, | |
currentIntent: { | |
name: string, | |
slots:any | |
}, | |
sessionAttributes: { [key: string]: string } | |
}; | |
/** There is more to this format, but this is what we use for now: */ | |
interface ResponseToLex { | |
dialogAction: { | |
type: string, | |
fulfillmentState?: string, | |
message?: { | |
contentType: string, | |
content: string | |
}, | |
}, | |
sessionAttributes?: { [key: string]: string } | |
} | |
createRestTransformer({ | |
/** | |
* This transformer is executed when receiving a message | |
* from the user, before executing the Flow. | |
* | |
* @param endpoint The configuration object for the used Endpoint. | |
* @param request The Express request object with a JSON parsed body. | |
* @param response The Express response object. | |
* | |
* @returns A valid userId, sessionId, as well as text and/or data, | |
* which has been extracted from the request body. | |
*/ | |
handleInput: async ({ endpoint, request, response }) => { | |
console.log( "######## Lex event: " + JSON.stringify(request.body)); | |
const lex = <RequestFromLex>request.body; | |
const userId = lex.sessionAttributes.CustomerNumber || `ANON_${uuid.v4()}`; | |
const sessionId = lex.sessionAttributes.ContactId || `CGY_${uuid.v4()}`; | |
// The two items above are circulated back to Lex as SessionAttributes in handleExecutionFinished() below. | |
// In this way they become 'sticky', as Lex circulates all SessionAttributes each call. | |
const text = lex.inputTranscript; | |
const data = { lex: lex }; | |
return { userId, sessionId, text, data }; | |
}, | |
/** | |
* This transformer is executed on every output from the Flow. | |
* | |
* @param output The raw output from the Flow. It is possible to manipulate | |
* and return every distinct output before they get formatted in the 'handleExecutionFinished' | |
* transformer. | |
* | |
* @param endpoint The configuration object for the used Endpoint. | |
* @param userId The unique ID of the user. | |
* @param sessionId The unique ID for this session. Can be used together with the userId | |
* to retrieve the sessionStorage object. | |
* | |
* @returns The output that will be formatted into the final response in the 'handleExecutionFinished' transformer. | |
*/ | |
handleOutput: async ({ output, endpoint, userId, sessionId }) => { | |
return output; | |
}, | |
/** | |
* This transformer is executed when the Flow execution has finished. | |
* For REST based transformers, the final output will be sent to | |
* the user. | |
* | |
* @param processedOutput This is the final object that will be sent to the user. | |
* It is therefore structured according to the Endpoint channel of the transformer. | |
* | |
* @param outputs This is an array of all of the outputs that were output by the Flow. | |
* These will be merged to create the 'processedOutput' object. | |
* | |
* @param userId The unique ID of the user. | |
* @param sessionId The unique ID for this session. Can be used together with the userId | |
* to retrieve the sessionStorage object. | |
* @param endpoint The configuration object for the used Endpoint. | |
* @param response The express response object that can be used to send a custom response back to the user. | |
* | |
* @returns An object that will be sent to the user, unchanged. It therefore has to have the | |
* correct format according to the documentation of the specific Endpoint channel. | |
*/ | |
handleExecutionFinished: async ({ processedOutput, outputs, userId, sessionId, endpoint, response }) => { | |
const lex: ResponseToLex = { | |
// Default action, until proven otherwise: | |
dialogAction: { | |
type: "Close", | |
fulfillmentState: "Failed", // Or 'Fulfilled' | |
} | |
}; | |
if (processedOutput.text === '') { | |
console.log( `Lex endpoint transformer - Null text returned from Flows - Treat as FAILED!`); | |
} else { | |
lex.dialogAction = { | |
type: "ElicitIntent", | |
message: { | |
// "contentType": "PlainText or SSML or CustomPayload", | |
contentType: "PlainText", | |
content: processedOutput.text, | |
}, | |
}; | |
} | |
// These MAY have been made, on the fly, at Cognigy session start, if they were not supplied from Lex/Connect: | |
// By returning them to Lex as Session Attributes, they should circulate back in the next request: | |
lex.sessionAttributes = { | |
CustomerNumber: userId, | |
ContactId: sessionId, | |
} | |
console.log( "######## Response to Lex: " + JSON.stringify(lex)); | |
return lex; | |
} | |
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// USE THIS CODE, COMPLETE, IN AN AWS LAMBDA FUNCTION OF YOUR OWN CREATION. | |
// ENSURE THAT YOUR Amazon Connect INSTANCE WILL BE ABLE TO GET PERMISSION TO | |
// ACCESS THE LAMBDA FUNCTION. SEE YOUR Connect/AWS ADMINISTRATOR. | |
// !!!! ADJUST THE URL SUPPLIED TO doPOST(), AS DESCRIBED BELOW !!!! | |
// As mentioned in the blog post, the full function includes more code for logging, | |
// error handling and https POST boilerplate: | |
exports.handler = async(event) => { | |
console.info( '######## Lex Lambda event:\n' + JSON.stringify(event,null,2) ); | |
try { | |
return await doPOST( | |
// ADJUST THIS URL TO BE THE 'Endpoint URL' IN THE COGNIGY.AU REST ENDPOINT: | |
'https://endpoint-x.cognigy.ai/a73b381c09 ... 3e402d5294', | |
event); | |
} | |
catch (e) { | |
console.error( 'POST into Cognigy REST endpoint FAILED: ' + JSON.stringigy(e) ); | |
return e; | |
} | |
} | |
const https = require('https'); | |
// Helper function to do https JSON POST request-response using Node.js primitives: | |
// Of course, if you go beyond the inbuilt Lambda editor, you could use your | |
// preferred npm module to carry out the request more succinctly. | |
async function doPOST(url, postObject) { | |
return new Promise((resolve, reject) => { | |
// Use RegEx matcher to extract host and path from full URL: | |
const m = url.match(/https:\/\/([^\/]+)(\/.+)/); | |
const postData = JSON.stringify(postObject); | |
const options = { | |
hostname: m[1], | |
port: 443, | |
path: m[2], | |
method: 'POST', | |
headers: { | |
'Content-Type': 'application/json', | |
'Content-Length': postData.length, | |
} | |
}; | |
const request = https.request(options, handleResponse); | |
request.on('error', (error) => { | |
console.error( `Error doing http POST to ${url} : ${error}`); | |
reject(error); | |
}); | |
request.write(postData); | |
request.end(); | |
function handleResponse(response) { | |
let data = ''; | |
response.on('data', (d) => { | |
data += d.toString(); | |
}); | |
response.on('end', () => { | |
// Resolve with javascript object of JSON response: | |
resolve(JSON.parse(data)); | |
}); | |
} | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment