Skip to content

Commit

Permalink
Merge branch 'develop'
Browse files Browse the repository at this point in the history
  • Loading branch information
Bob Potterveld committed Aug 24, 2020
2 parents 1e5245d + b7d20a5 commit c4f3069
Show file tree
Hide file tree
Showing 113 changed files with 4,794 additions and 472 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
## [4.2.0]
- New Kendra FAQ support (Beta version) using the setting KENDRA_FAQ_INDEX. New menu item in Designer UI to export Questions as a Kendra FAQ. See revised Blog Post for details.
- New GetSessionAttribute Handlebars helper to obtain session attribute. Works similar to lodash get(). Will not through exception and will return a default value.
- Enhanced handlebars to support string concatenation including handlevar 'variables' like Session Attributes and UserInfo, etc. Use case, e.g. to build a url containing a users email, eg a google calendar URL. Example of syntax now supported - in this case to dynamically build a personalized URL based on user info. {{setSessionAttr 'link' 'https://calendar.google.com/calendar/embed?src=' UserInfo.Email '&ctz=America%2FNew_York'}}
- Moved 'previous' and 'navigation' session attributes under a new 'qnabotcontext' session attribute so that Connect (and other) clients have fewer session attributes to preserve.
- Allows Chaining rule Lambda function to return a modified session object in addition to the string for chaining.
- Allows Chaining of up to 10 documents. Each document's Lambda hooks will also be invoked in sequence if defined.
- Added a new Repeat QID in the QNAUtility example package. Allows QnABot to easily repeat the last answer.
- Allow the chaining rule to specify a specific QID rather than an answer. A QID can be specified in the chaining rule by using string such as QID::<qid> e.g. QID::Admin.001. Note, the new QID::<qid> syntax can also be used from the webUI, say as button values if/when you prefer to target a specific QID (exact query) rather than rely on question matching.
- Fixed a defect to allow conditional chaining to be invoked after an elicit response bot failure.
- Upgrades to and installs ElasticSearch 7.7.

## [4.1.0]
- Install / Upgrade now supports the option to configure S3 Buckets and Elastic Search cluster using encryption at rest
- Install / Upgrade now supports the option to require Cognito based user authorization to access the built-in full screen web UI (Public/Private parameter in template) - Public is the default
Expand Down
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ This repository contains code for the QnABot, described in the AWS AI blog post

See the "Getting Started" to launch your own QnABot

**New features in 4.2.0** [Beta Kendra FAQ Support, Bug fixes, Multiple document chaining, Repeat question, Elastic Search 7.7 upgrade](#new-features)

**New features in 4.1.0** [Encryption at rest for S3 and Elastic Search Cluster, option to require Cognito user pool authorization to access embedded web UI, enhanced Kendra integration, enhanced Connect integration, and others](#new-features)

**New features in 4.0.0** [Update to Elasticsearch 7.4, improved question matching accuracy, fuzzy matching, new multi-language support debug setting, SSML for Amazon Connect, improved Kendra integration, full upgrade support](#new-features)
Expand Down Expand Up @@ -152,6 +154,18 @@ See the [LICENSE.md](LICENSE.md) file for details

## New features

### Version 4.2.0
- New Kendra FAQ support (Beta version) using the setting KENDRA_FAQ_INDEX. New menu item in Designer UI to export Questions as a Kendra FAQ. See revised Blog Post for details.
- New GetSessionAttribute Handlebars helper to obtain session attribute. Works similar to lodash get(). Will not through exception and will return a default value.
- Enhanced handlebars to support string concatenation including handlevar 'variables' like Session Attributes and UserInfo, etc. Use case, e.g. to build a url containing a users email, eg a google calendar URL. Example of syntax now supported - in this case to dynamically build a personalized URL based on user info. {{setSessionAttr 'link' 'https://calendar.google.com/calendar/embed?src=' UserInfo.Email '&ctz=America%2FNew_York'}}
- Moved 'previous' and 'navigation' session attributes under a new 'qnabotcontext' session attribute so that Connect (and other) clients have fewer session attributes to preserve.
- Allows Chaining rule Lambda function to return a modified session object in addition to the string for chaining.
- Allows Chaining of up to 10 documents. Each document's Lambda hooks will also be invoked in sequence if defined.
- Added a new Repeat QID in the QNAUtility example package. Allows QnABot to easily repeat the last answer.
- Allow the chaining rule to specify a specific QID rather than an answer. A QID can be specified in the chaining rule by using string such as QID::<qid> e.g. QID::Admin.001. Note, the new QID::<qid> syntax can also be used from the webUI, say as button values if/when you prefer to target a specific QID (exact query) rather than rely on question matching.
- Fixed a defect to allow conditional chaining to be invoked after an elicit response bot failure.
- Upgrades to and installs ElasticSearch 7.7.

### Version 4.1.0
- Install / Upgrade now supports the option to configure S3 Buckets and Elastic Search cluster using encryption at rest
- Install / Upgrade now supports the option to require Cognito based user authorization to access the built-in full screen web UI (Public/Private parameter in template) - Public is the default
Expand Down
3 changes: 2 additions & 1 deletion bin/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module.exports={
"devEncryption": "UNENCRYPTED",
"devPublicOrPrivate": "PUBLIC",
"namespace":"dev",
"stackNamePrefix":"QNA"
"stackNamePrefix":"QNA",
"KendraFAQIndex":""
}

if (require.main === module) {
Expand Down
4 changes: 4 additions & 0 deletions lambda/cfn/lib/S3Lambda.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,9 @@ module.exports=class S3Lambda extends base{
Delete(ID,params,reply){
reply(null);
}

Update(ID,params,oldparams,reply){
this.Create(params,reply)
}
}

6 changes: 3 additions & 3 deletions lambda/cfn/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion lambda/cfn/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
"json-stringify-pretty-compact": "^1.0.4",
"jsonwebtoken": "^8.0.1",
"jszip": "^3.1.4",
"lodash": "^4.17.15",
"lodash": "^4.17.19",
"multer": "^1.3.0",
"stack-utils": "^1.0.1"
},
Expand Down
239 changes: 239 additions & 0 deletions lambda/export/createFAQ.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
// createFAQ.js


const AWSKendra = require('aws-sdk/clients/kendra');
const AWSS3 = require('aws-sdk/clients/s3');
const sleep = require('util').promisify(setTimeout)


/**
* Function to upload CSV to S3 bucket, return Promise
* @param s3Client
* @param params
* @returns {*}
*/
function s3Uploader(s3Client,params) {
return new Promise(function(resolve, reject) {
s3Client.putObject(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
reject(err);
}
else {
console.log('Uploaded CSV to S3 successfully:');
console.log(data); // successful response
resolve(data);
}
});
});
}


/**
* Function to convert uploaded CSV into Kendra FAQ, return Promise
* @param kendraClient
* @param params
* @returns {*}
*/
function faqConverter(kendraClient,params) {
return new Promise(function(resolve, reject) {
kendraClient.createFaq(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
reject(err);
}
else {
console.log('Converted CSV to FAQ successfully:');
console.log(data); // successful response
resolve(data);
}
});
});
}


/**
* Function to delete old FAQ from Kendra index, return Promise
* @param kendraClient
* @param params
* @returns {*}
*/
function faqDeleter(kendraClient,params) {
return new Promise(function(resolve, reject) {
kendraClient.deleteFaq(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
reject(err);
}
else {
console.log('Deleted old FAQ successfully. New list of FAQs in index ' + params.IndexId + ':');
console.log(data); // successful response
resolve(data);
}
});
});
}


/**
* Function to list existing FAQs in a Kendra index, return Promise
* @param kendraClient
* @param params
* @returns {*}
*/
function faqLister(kendraClient,params) {
return new Promise(function(resolve, reject) {
kendraClient.listFaqs(params, function(err, data) {
if (err) {
console.log(err, err.stack); // an error occurred
reject(err);
}
else {
console.log('Checked for pre-existing FAQ successfully. List of FAQs for index ' + params.IndexId + ':');
console.log(data); // successful response
resolve(data);
}
});
});
}




/**
* Function to upload CSV into S3 bucket and convert into Kendra FAQ, return Promise
* @returns {*}
*/
async function createFAQ(params) {

// create kendra and s3 clients
let kendraClient = (process.env.REGION ?
new AWSKendra({apiVersion:'2019-02-03', region:process.env.REGION}) :
new AWSKendra({apiVersion:'2019-02-03', region:params.region})
);
let s3Client = (process.env.REGION ?
new AWSS3({apiVersion:'2006-03-01', region:process.env.REGION}) :
new AWSS3({apiVersion:'2006-03-01', region:params.region}));
console.log('clients created');


// read in CSV and upload to S3 bucket
var fs = require('fs');
var s3_params = {
Bucket: params.s3_bucket,
Key: params.s3_key,
ACL: 'bucket-owner-read', // TODO: should this param be public?
Body: fs.createReadStream(params.csv_path), // use read stream option in case file is large
};

let count=0;
while (count<1) {
try {
var s3_response = await s3Uploader(s3Client, s3_params);
count++;
} catch (error) {
if (error.code=='ThrottlingException') {
console.log(`Throttling exception: trying upload CSV again in 10 seconds`);
await sleep(10000);
continue;
} else {
throw error;
}
}
}
await sleep(10000);


// if FAQ exists already, delete the old one and update it
var index_params = {
IndexId: params.faq_index_id,
MaxResults: '30' // default max number of FAQs in developer edition
};

count=0;
while (count<1) {
try {
var list_faq_response = await faqLister(kendraClient, index_params);
count++;
} catch (error) {
if (error.code=='ThrottlingException') {
console.log(`Throttling exception: trying list FAQs again in 10 seconds`);
await sleep(10000);
continue;
} else {
throw error;
}
}
}
await sleep(10000);


var j, elem, index=null;
for (j=0; j<list_faq_response.FaqSummaryItems.length; j++) {
elem = list_faq_response.FaqSummaryItems[j];
if (elem.Name == params.faq_name) {
index = elem.Id;
break;
}
}
if (index != null) {
var delete_faq_params = {
Id: index,
IndexId: params.faq_index_id
}
count=0;
while (count<1) {
try {
var del_faq_response = await faqDeleter(kendraClient, delete_faq_params);
count++;
} catch (error) {
if (error.code=='ThrottlingException') {
console.log(`Throttling exception: trying delete FAQ again in 10 seconds`);
await sleep(10000);
continue;
} else {
throw error;
}
}
}
} else {
console.log("No old FAQ to delete");
}
await sleep(10000);

// create the FAQ
var faq_params = {
IndexId: params.faq_index_id,
Name: params.faq_name,
RoleArn: params.kendra_s3_access_role,
S3Path: {
Bucket: params.s3_bucket,
Key: params.s3_key
},
Description: 'Exported FAQ of questions from QnABot designer console'
// if no tags, delete parameter because empty arrays cause throttling exceptions
};

count=0;
while (count<1) {
try {
var faq_response = await faqConverter(kendraClient, faq_params);
count++;
} catch (error) {
if (error.code=='ThrottlingException') {
console.log(`Throttling exception: trying convert to FAQ again in 10 seconds`);
await sleep(10000);
continue;
} else {
throw error;
}
}
}
await sleep(10000);
return faq_response;
}


exports.handler = async(params) => {
return await createFAQ(params);
}
3 changes: 2 additions & 1 deletion lambda/export/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ exports.step=function(event,context,cb){
.then(()=>s3.getObject({Bucket,Key,VersionId}).promise())
.then(x=>JSON.parse(x.Body.toString()))
.then(function(config){
if(config.status!=="Error" && config.status!=="Completed"){
var step_status_ignore = ['Error', 'Completed', 'Sync Complete', 'Parsing content JSON', 'Creating FAQ']
if (step_status_ignore.includes(config.status)===false) {
return Promise.try(function(){
console.log("Config:",JSON.stringify(config,null,2))
switch(config.status){
Expand Down
Loading

0 comments on commit c4f3069

Please sign in to comment.