Search code examples
node.jsbotframeworkazure-language-understandingnock

Mocking LUIS response with LuisRecognizer not working


I am trying to mock calls to LUIS via nock, which uses the LuisRecognizer from botbuilder-ai. Here is the relevant information.

The bot itself is calling LUIS and getting the result via const recognizerResult = await this.dispatchRecognizer.recognize(context);. I grabbed the actual result as below:

{"text":"I want to look up my order","intents":{"viewOrder":{"score":0.996454835},"srStatus":{"score":0.0172454268},"expediteOrder":{"score":0.0108480565},"escalate":{"score":0.007967358},"qna":{"score":0.00694736559},"Utilities_Cancel":{"score":0.005627355},"manageProfile":{"score":0.004953466},"getPricing":{"score":0.001781322},"Utilities_Help":{"score":0.0007197641},"getAvailability":{"score":0.0005667514},"None":{"score":0.000321137835}},"entities":{"$instance":{}},"sentiment":{"label":"negative","score":0.171873689},"luisResult":{"query":"I want to look up my order","topScoringIntent":{"intent":"viewOrder","score":0.996454835},"intents":[{"intent":"viewOrder","score":0.996454835},{"intent":"srStatus","score":0.0172454268},{"intent":"expediteOrder","score":0.0108480565},{"intent":"escalate","score":0.007967358},{"intent":"qna","score":0.00694736559},{"intent":"Utilities.Cancel","score":0.005627355},{"intent":"manageProfile","score":0.004953466},{"intent":"getPricing","score":0.001781322},{"intent":"Utilities.Help","score":0.0007197641},{"intent":"getAvailability","score":0.0005667514},{"intent":"None","score":0.000321137835}],"entities":[],"sentimentAnalysis":{"label":"negative","score":0.171873689}}}

For the sake of brevity, I'll just call this "recognizerResult" in the below. I'm successfully intercepting the API call in my test file with nock, with the configuration as follows:

nock('https://westus.api.cognitive.microsoft.com')
.post(/.*/)
.reply(200,{recognizerResult});

I've tried returning both as a JSON object and a string, though I'm almost certain this needs to be JSON object as shown (I'm mocking a call to QnA maker with the same approach that is working). When I run this test via mocha, I get the following error:

TypeError: Cannot read property 'replace' of undefined
    at LuisRecognizerV2.normalizeName (node_modules\botbuilder-ai\src\luisRecognizerOptionsV2.ts:96:21)
    at luisResult.intents.reduce (node_modules\botbuilder-ai\src\luisRecognizerOptionsV2.ts:104:31)
    at Array.reduce (<anonymous>)
    at LuisRecognizerV2.getIntents (node_modules\botbuilder-ai\src\luisRecognizerOptionsV2.ts:102:32)
    at LuisRecognizerV2.<anonymous> (node_modules\botbuilder-ai\src\luisRecognizerOptionsV2.ts:81:27)
    at Generator.next (<anonymous>)
    at fulfilled (node_modules\botbuilder-ai\lib\luisRecognizerOptionsV2.js:11:58)
    at process._tickCallback (internal/process/next_tick.js:68:7)

I've looked at the code in question within the luisRecognizerOptionsV2.ts file, but can't see where there's an issue. The replace is part of normalizing the intent name, which is there to replace unsupported characters with an "_". The bot runs normally when deployed to Azure (and locally), and the tests work without mocking the call. However, I really want to be able to test this without making actual LUIS calls. Any ideas why I am getting this error and how to fix?

For reference, here is the mock to QnA Maker that is working, though note that I'm using a simple REST call for that instead of the recognizer.

nock('https://myqnaservicename.azurewebsites.net')
.post(/.*/)
.reply(200, {"answers": [{"questions": ["I need an unrecognized utterance for testing"], "answer": "I can hear you now!", "score": 28.48, "id": 1234}]});

Solution

  • The issue is that your {recognizerResult} is what gets saved to const recognizerResult, but is not what gets returned by that API call.

    It takes a lot of digging to find it all, but a V2 LUIS client gets the API response, then converts it into recognizerResult.

    You've got a few options for "fixing" this:

    1. Set a breakpoint in that node_modules\botbuilder-ai\src\luisRecognizerOptionsV2 file on that const result = line and grab luisResult.
    2. Use something like Fiddler to record the actual API response and use that
    3. Write it manually

    For reference, you can see how we do this in our tests:

    You can see that our nock() returns response.v2, which does not contain .topScoringIntent, which is what it's looking for, which is why the error is throwing.

    Specifically, the mock response needs to be just the v2/luisResults attributes. In other words, when using the luisRecognizer, the response set in nock needs to be

    .reply(200,{ "query": "Sample query", "topScoringIntent": { "intent": "desiredIntent", "score":1}, "entities":[]});

    If you look at the test data linked above, there are other attributes in the actual response. But this is the minimum required response if you are just trying to get topIntent to test routing. If you needed other attributes you could add them, e.g. you could add everything within v2 as in this file or some of the more involved files with things like multiple intents.