-
Notifications
You must be signed in to change notification settings - Fork 247
/
evaluateConditionalChaining.js
128 lines (120 loc) · 6.43 KB
/
evaluateConditionalChaining.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/*********************************************************************************************************************
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. *
* *
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance *
* with the License. A copy of the License is located at *
* *
* http://www.apache.org/licenses/ *
* *
* or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES *
* OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions *
* and limitations under the License. *
*********************************************************************************************************************/
const _ = require('lodash');
const qnabot = require('qnabot/logging');
const staticEval = require('static-eval');
const { invokeLambda } = require('./invokeLambda');
const { mergeNext } = require('./mergeNext');
const { encryptor } = require('./encryptor');
const { getHit } = require('./getHit');
const esprimaParse = require('esprima').parse;
/**
* Central location to evaluate conditional chaining. Chaining can take place either when an elicitResponse is
* complete or during the normal course of question processing. A question can be chained even if it is not
* involved in an elicitResponse.
* @param req
* @param res
* @param hit - the original hit found through a query. note this may be a "fakeHit" in the case of elicitResponse processing.
* @param conditionalChaining
* @returns {Promise<*>}
*/
async function evaluateConditionalChaining(req, res, hit, conditionalChaining) {
qnabot.log('evaluateConditionalChaining req: ', JSON.stringify(req, null, 2));
qnabot.log('evaluateConditionalChaining res: ', JSON.stringify(res, null, 2));
qnabot.log('evaluateConditionalChaining hit: ', JSON.stringify(hit, null, 2));
// decrypt conditionalChaining
conditionalChaining = encryptor.decrypt(conditionalChaining);
qnabot.log('Decrypted Chained document rule specified:', conditionalChaining);
let next_q;
let errors = [];
// If chaining rule a lambda, or an expression?
if (conditionalChaining.toLowerCase().startsWith('lambda::')) {
// Chaining rule is a Lambda function
const lambdaName = conditionalChaining.split('::')[1];
let payload;
[req, res, payload] = await invokeLambda(lambdaName, req, res);
qnabot.log('Chaining Rule Lambda response payload: ', payload);
try {
payload = JSON.parse(payload);
} catch (e) {
const message = `Parsing Lambda response payload returned from ${lambdaName} failed.`;
qnabot.log(message);
const error = {
message,
};
errors.push(error);
}
if (_.get(payload, 'req') && _.get(payload, 'res')) {
next_q = _.get(payload, 'req.question');
} else {
qnabot.log('Chaining Rules Lambda did not return session event in response.');
qnabot.log('assume response is a simple string containing next_q value');
next_q = payload;
}
} else {
// create chaining rule safeEval context, aligned with Handlebars context
const sandbox = {
LexOrAlexa: req._type,
UserInfo: req._userInfo,
SessionAttributes: res.session,
Slots: req.slots,
Settings: req._settings,
Question: req.question,
OrigQuestion: _.get(req, '_event.origQuestion', req.question),
PreviousQuestion: _.get(req, 'session.qnabotcontext.previous.q', false),
Sentiment: req.sentiment,
};
qnabot.log('Evaluating:', conditionalChaining);
qnabot.debug('Sandbox:', JSON.stringify(sandbox, null, 2));
// safely evaluate conditionalChaining expression.. throws an exception if there is a syntax error
const ast = esprimaParse(conditionalChaining).body[0].expression;
try {
next_q = staticEval(ast, sandbox);
} catch (e) {
const message = `Syntax Error evaluating conditional chaining rule: ${conditionalChaining}`
qnabot.log(message);
const error = {
message,
};
errors.push(error);
}
}
qnabot.log('Chained document rule evaluated to:', next_q);
let hit2;
if (next_q) {
req.question = next_q;
[req, res, hit2, errors] = await getHit(req, res);
}
// if the question we are chaining to, also has conditional chaining, be sure to navigate set up
// next user input to elicitResponse from this lex Bot.
if (hit2) {
const responsebot_hook = _.get(hit2, 'elicitResponse.responsebot_hook', undefined);
const responsebot_session_namespace = _.get(hit2, 'elicitResponse.response_sessionattr_namespace', undefined);
const chaining_configuration = _.get(hit2, 'conditionalChaining', undefined);
const elicitResponse = {};
elicitResponse.responsebot = undefined;
elicitResponse.namespace = undefined;
elicitResponse.chainingConfig = chaining_configuration;
if (responsebot_hook && responsebot_session_namespace) {
elicitResponse.responsebot = responsebot_hook;
elicitResponse.namespace = responsebot_session_namespace;
_.set(res.session, `${res.session.elicitResponseNamespace}.boterror`, undefined);
}
_.set(res.session, 'qnabotcontext.elicitResponse', elicitResponse);
const mergedhit = mergeNext(hit, hit2);
return [req, res, mergedhit, errors];
}
qnabot.log('WARNING: No documents found for evaluated chaining rule:', next_q);
return [req, res, hit, errors];
}
exports.evaluateConditionalChaining = evaluateConditionalChaining;