async function routeKendraRequest()

in lambda/es-proxy-layer/lib/kendraQuery.js [74:219]


async function routeKendraRequest(request_params) {
    
    AWS.config.update({
      maxRetries: request_params.maxRetries,
      retryDelayOptions: {
        base: request_params.retryDelay
      },
    });
    
    var kendraClient = (process.env.REGION ?
            new AWS.Kendra({apiVersion: '2019-02-03', region: process.env.REGION}) :
            new AWS.Kendra({apiVersion: '2019-02-03'})
        );
        
    
    let promises = [];
    let resArray = [];
    let kendraIndexes = undefined;
    let kendraFaqIndex = request_params.kendra_faq_index;
    if (kendraFaqIndex != "" && kendraFaqIndex != undefined) {
        kendraIndexes = [kendraFaqIndex];
    } else {
        throw new Error("Undefined KendraFAQIndex: " + kendraFaqIndex);
    }
    // Iterate through this area and perform queries against Kendra.
    kendraIndexes.forEach(function (index, i) {
        const params = {
            IndexId: index, /* required */
            QueryText: request_params.question
        };
        let p = kendraRequester(kendraClient,params,resArray);
        promises.push(p);
    });
    await Promise.all(promises);


    // ----- process kendra query responses and update answer content -----

    /* default message text - can be overridden using QnABot SSM Parameter Store Custom Property */
    let foundAnswerCount = 0;
    let kendraQueryId;
    let kendraIndexId;
    let kendraResultId;
    let json_struct = [];
    
    
    // note that this outside for loop will only execute once (one FAQ index) but the structure was kept due to its elegance
    //resArray.forEach(async function (res) {
    await asyncForEach(resArray, async function (res) {
        if (res && res.ResultItems && res.ResultItems.length > 0) {
            
            var i, element;
            for (i=0; i<res.ResultItems.length; i++) {
                element = res.ResultItems[i];

                if(!confidence_filter(request_params.minimum_score,element))
                    continue;

                /* Note - only FAQ format will be provided back to the requester */
                if (element.Type === 'QUESTION_ANSWER' && foundAnswerCount < request_params.size && element.AdditionalAttributes &&
                    element.AdditionalAttributes.length > 1) {

                    if (!open_es.hasJsonStructure(element.DocumentURI)) {
                        break;
                    }
                    var hit = JSON.parse(element.DocumentURI);
                    if (_.get(hit,"_source_qid")) {
                        let qid = hit._source_qid ;
                        // FAQ only references the QID but doesn't contain the full docunment.. retrieve it from ES
                        qnabot.log("Kendra matched qid: ", qid, ". Retrieving full document from Elasticsearch.");
                        let es_response = await open_es.run_qid_query_es(request_params, qid) ;
                        qnabot.log("Qid document from Kendra: ", JSON.stringify(hit));
                        hit = _.get(es_response, "hits.hits[0]._source"); //todo fix if null -- test from content designer
                        if(hit == null){
                            qnabot.log("WARNING: An answer was found in Kendrs FAQ, but a corresponding answer was not found in ElasticSearch for "+ hit)
                            continue;

                        }
                        if(_.get(hit,"QNAClientFilter")){
                            qnabot.log("Found an answer with a clientFilterValue set...skipping")
                            continue;
                        }
                    }
                    
                    qnabot.log(`hit is ${JSON.stringify(hit)}`);
                    json_struct.push(hit);

                    kendraQueryId = res.QueryId; // store off the QueryId to use as a session attribute for feedback
                    kendraIndexId = res.originalKendraIndexId; // store off the Kendra IndexId to use as a session attribute for feedback
                    kendraResultId = element.Id; // store off resultId to use as a session attribute for feedback
                    foundAnswerCount++;
                }
            }
        }
    });
    
    // return query response structure to make Kendra results look like ES results so we don't have to change the UI
    var hits_struct = {
        // "took": 104,
        "timed_out": false,
        "hits": {
            "total": {
                "value": foundAnswerCount,  // if no answers found, total hits # is 0 and hits list is empty
                "relation": "eq"
            },
            "max_score": json_struct.length,
            "hits": [],
        },
    }
    if (kendraQueryId) {
        hits_struct.kendra_context = {
            "kendraQueryId":kendraQueryId,
            "kendraIndexId":kendraIndexId,
            "kendraResultId":kendraResultId,
            "kendraResponsibleQid":"KendraFAQ"
        }
    }

    
    let ans = {};
    var j, faq_struct;
    var num=json_struct.length;
    if (request_params.size) {
        num = Math.min(num, request_params.size);
    }
    for (j=0; j<num; j++) {
        faq_struct = json_struct[j];
        
        ans = {
            "_index": request_params.kendra_faq_index,
            "_type": "_faq",
            "_id": faq_struct.qid,
            "_score": json_struct.length-j, // score is inverse ranking of returned results
            "_source": faq_struct
        }
        hits_struct.hits.hits.push(ans);
    }
    
    // cache kendra results to optimize fallback engine
    if (request_params.same_index && resArray.length>0) {
        hits_struct['kendraResultsCached'] = resArray[0];
    }
    
    qnabot.log("RETURN: " + JSON.stringify(hits_struct));
    return hits_struct;
}