Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LexV2 specialtybotrouting fixes and enhancements. #575

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions BotRoutingREADME.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Bot Routing - Preview Mode
(version 1.1 - November 2021)
# Bot Routing
(version 1.2 - November 2022)

Bots exist to perform a variety of automation tasks. Usually they take
as input a message from a human and respond performing
Expand Down Expand Up @@ -45,7 +45,7 @@ enterprise level can direct users to answers from any of their Bots.

#### Configuration

Configuration is simple. Each question in QnAbot now contains an optional section which
Configuration is simple. Each question in QnABot now contains an optional section which
allows configuration of a BotRouter.

**Note: This is optional. Please leave empty and QnABot will not act as a
Expand Down Expand Up @@ -73,9 +73,18 @@ list of session attribute names can be specified. The session attributes
will be passed on each request to the specialty bot. They will override
any session attributes which might have been returned from the specialty bot
on the prior request.

The example image shows an integration we've developed which communicates
with the Nutritionix Bot.

* The initial utterance to send to the target bot. You can specify this as ${relay} in which
case QnABot will send the input utterance matching this qid to the target bot or specify
a different string to send on startup. Leave this field blank to not send an utterance
to the specialty bot on startup.

* Lex session attributes to return from the specialty bot. A comma separated list
of session attribute names can be specified that will be returned on each interaction
with the specialty bot.

* If Lex session attributes are being returned from the specialty bot, a namespace with
which to scope the returned attributes must be specified.

*Note: when integrating with other Lex Bots or Lambdas, the permission to
communicate with the target Lex bot or with a new BotRouter (Lambda) need to
Expand Down
Binary file modified docs/botroutingconfig.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 4 additions & 2 deletions lambda/es-proxy-layer/lib/query.js
Original file line number Diff line number Diff line change
Expand Up @@ -631,9 +631,11 @@ async function processFulfillmentEvent(req,res) {
if (hit) {
// found a document in elastic search.
var c=0;
while (_.get(hit, 'conditionalChaining') && _.get(hit, 'elicitResponse.responsebot_hook', '') === '' ) {
while (_.get(hit, 'conditionalChaining')
&& _.get(hit, 'elicitResponse.responsebot_hook', '') === ''
&& _.get(hit, 'botRouting.specialty_bot', '') === '') {
c++;
// ElicitResonse is not involved and this document has conditionalChaining defined. Process the
// ElicitResponse and SpecialtyBot is not involved and this document has conditionalChaining defined. Process the
// conditionalChaining in this case.
[req, res, hit] = await evaluateConditionalChaining(req, res, hit, hit.conditionalChaining);
qnabot.log('Chained doc count: ', c);
Expand Down
78 changes: 55 additions & 23 deletions lambda/fulfillment/lib/middleware/3_query.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,36 @@ const qnabot = require("qnabot/logging")
* @param res
* @returns {Promise<any>}
*/

async function specialtyBotInvocation(req, res) {
let specialtyBot = _.get(req,"session.qnabotcontext.specialtyBot", undefined);
let specialtyBotAlias = _.get(req,"session.qnabotcontext.specialtyBotAlias", undefined);
let specialtyBotChainingConfig = _.get(req,"session.qnabotcontext.sBChainingConfig", undefined);
qnabot.log('Handling specialtyBot');
let resp = await specialtyBotRouter.routeRequest(req, res, specialtyBot, specialtyBotAlias);
qnabot.log("SpecialtyBotRouterResp: " + JSON.stringify(resp, null, 2));
let isSpecialtyBotComplete = _.get(resp, "res.session.qnabotcontext.specialtyBot", "") === "";
if (isSpecialtyBotComplete) {
// Specialty bot has completed. See if we need, to using chaining to go to another question
if (specialtyBotChainingConfig) {
qnabot.log("Conditional chaining: " + specialtyBotChainingConfig);

// Set session response bot attributes to force chaining to work properly in query.js
_.set(res,"session.qnabotcontext.elicitResponse.progress", "Fulfilled");
_.set(res, "session.qnabotcontext.elicitResponse.chainingConfig", specialtyBotChainingConfig);
// chainingConfig will be used in Query Lambda function
// const arn = util.getLambdaArn(process.env.LAMBDA_DEFAULT_QUERY);
const postQuery = await esquery(req, res)
_.set(postQuery,'res.session.qnabotcontext.elicitResponse.progress',undefined);
_.set(postQuery,'res.session.qnabotcontext.elicitResponse.chainingConfig',undefined);
qnabot.log("After chaining the following response is being made: " + JSON.stringify(postQuery,null,2));
return postQuery;
}
}
qnabot.log("No chaining. The following response is being made: " + JSON.stringify(resp,null,2));
return resp;
}

module.exports=async function query(req,res) {
qnabot.debug("Entry REQ:", JSON.stringify(req, null, 2));
qnabot.debug("Entry RES:", JSON.stringify(res, null, 2));
Expand All @@ -57,31 +87,10 @@ module.exports=async function query(req,res) {
let chainingConfig = _.get(req,"session.qnabotcontext.elicitResponse.chainingConfig", undefined);

if (specialtyBot) {
qnabot.log('Handling specialtyBot');
let resp = await specialtyBotRouter.routeRequest(req, res, specialtyBot, specialtyBotAlias);
if (resp.res.session.specialtyBotProgress === 'Complete' ||
resp.res.session.specialtyBotProgress === 'Failed') {
// Specialty bot has completed. See if we need to using chaining to go to another question
if (chainingConfig) {
qnabot.log("Conditional chaining: " + chainingConfig);
// chainingConfig will be used in Query Lambda function
const arn = util.getLambdaArn(process.env.LAMBDA_DEFAULT_QUERY);
const postQuery = await esquery(req,res)

// specialtyBot processing is done. Remove the flag for now.
_.set(postQuery, 'res.session.qnabotcontext.specialtyBotProgress', undefined);
qnabot.log("After chaining the following response is being made: " + JSON.stringify(postQuery,null,2));
return postQuery;
} else {
// no chaining. continue on with response from standard fulfillment path.
_set(res,'session.qnabotcontext.specialtyBotProgress', undefined);
}
}
qnabot.log("No chaining. The following response is being made: " + JSON.stringify(resp,null,2));
return resp;
return await specialtyBotInvocation(req, res);
} else if (elicitResponse) {
qnabot.log('Handling elicitResponse');
let resp = await lexRouter.elicitResponse(req,res, elicitResponse);
let resp = await lexRouter.elicitResponse(req, res, elicitResponse);
let progress = _.get(resp,"res.session.qnabotcontext.elicitResponse.progress", undefined);
if (progress === 'Fulfilled' || progress === 'ReadyForFulfillment' || progress === 'Close' || progress === 'Failed') {
qnabot.log("Bot was fulfilled");
Expand Down Expand Up @@ -153,6 +162,10 @@ module.exports=async function query(req,res) {
const specialtybot_name = _.get(postQuery.res,"result.botRouting.specialty_bot_name", undefined);
const specialtybot_alias = _.get(postQuery.res,"result.botRouting.specialty_bot_alias", undefined);
const specialtybot_attributes_to_merge = _.get(postQuery.res,"result.botRouting.specialty_bot_session_attributes_to_merge", undefined);
const specialtybot_start_up_text = _.get(postQuery.res, "result.botRouting.specialty_bot_start_up_text", undefined);
const specialtybot_attributes_to_receive = _.get(postQuery.res, "result.botRouting.specialty_bot_session_attributes_to_receive", undefined);
const specialtybot_receive_namespace = _.get(postQuery.res, "result.botRouting.specialty_bot_session_attributes_to_receive_namespace", undefined);

if (responsebot_hook && responsebot_session_namespace) {
if (_.get(postQuery,'res.session.qnabotcontext.elicitResponse.loopCount')) {
_.set(postQuery,'res.session.qnabotcontext.elicitResponse.loopCount',0)
Expand All @@ -167,6 +180,25 @@ module.exports=async function query(req,res) {
_.set(postQuery,'res.session.qnabotcontext.specialtyBotName', specialtybot_name);
_.set(postQuery,'res.session.qnabotcontext.specialtyBotAlias', specialtybot_alias);
_.set(postQuery,'res.session.qnabotcontext.specialtyBotMergeAttributes', specialtybot_attributes_to_merge);
_.set(postQuery,'res.session.qnabotcontext.sBChainingConfig',chaining_configuration);
_.set(postQuery,'res.session.qnabotcontext.sBAttributesToReceive',specialtybot_attributes_to_receive);
_.set(postQuery,'res.session.qnabotcontext.sBAttributesToReceiveNamespace',specialtybot_receive_namespace);

if (specialtybot_start_up_text) {
_.set(postQuery,'req.session.qnabotcontext.specialtyBot', specialtybot_hook);
_.set(postQuery,'req.session.qnabotcontext.specialtyBotName', specialtybot_name);
_.set(postQuery,'req.session.qnabotcontext.specialtyBotAlias', specialtybot_alias);
_.set(postQuery,'req.session.qnabotcontext.sBMergeAttributes', specialtybot_attributes_to_merge);
_.set(postQuery,'req.session.qnabotcontext.sBChainingConfig',chaining_configuration);
_.set(postQuery,'req.session.qnabotcontext.sBAttributesToReceive',specialtybot_attributes_to_receive);
_.set(postQuery,'req.session.qnabotcontext.sBAttributesToReceiveNamespace',specialtybot_receive_namespace);
if (specialtybot_start_up_text === "${relay}") {
_.set(postQuery,'req.question', _.get(req, "question"));
} else {
_.set(postQuery,'req.question', specialtybot_start_up_text);
}
return await specialtyBotInvocation(postQuery.req, postQuery.res);
}
}

qnabot.log("Standard path return from 3_query: " + JSON.stringify(postQuery, null, 2));
Expand Down
21 changes: 21 additions & 0 deletions lambda/schema/qna.js
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,27 @@ module.exports={
type:"string",
maxLength:100,
propertyOrder:3
},
specialty_bot_start_up_text:{
title:"Send initial utterance to bot",
description:"An optional string to send to the bot for startup. Use ${relay} to send the user's current input text. Default is an empty string for no initial interaction.",
type:"string",
maxLength:100,
propertyOrder:4
},
specialty_bot_session_attributes_to_receive:{
title:"Session attributes to receive and merge from the Lex specialty bot",
description:"An optional comma separated list of session attributes to receive from a Lex specialty bot. Default is an empty string.",
type:"string",
maxLength:100,
propertyOrder:5
},
specialty_bot_session_attributes_to_receive_namespace:{
title:"Namespace to use for session attributes being received",
description:"An string specifying the namespace to use for received attributes. Default is an empty string. However, this must be specified if receiving session attributes.",
type:"string",
maxLength:100,
propertyOrder:6
}
}
},
Expand Down
5 changes: 3 additions & 2 deletions package-lock.json

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