0
votes

I am using python sdk of botframework for my bot design. I am using waterfall style of dialogs for my conversation design.

My bot starts with a dialog by asking user: "I can show documents for topic A, B, C. Of what topic you would like to see documents?" To verify if the user has submitted right topic, I use custom Validator and using luis I verify if the user has entered correct topic.

In waterfall step of dialog, I use the topic entered by user to show him the respective topics. But here also I have to hit luis service again to extract the topic from the user message and then using that entity filter from the list of topics.

My question is: Is it possible to pass on the values from the promptValidatorContext to current step context or the next dialog in the waterfall dialog set.

As you can see with following sample code, I am hitting the luis app twice with the same user message, if it's possible to share values between promptValidatorContext and dialogContext, this would me help me avoid hitting luis service twice and could do the same job with one time hitting.

Sample code:

class MainDialog(ComponentDialog):
    def __init__(self, dialog_id, luis_app):
        self.dialog_id = dialog_id
        self.luis_app = luis_app
        self.add_dialog(TextPrompt('topic', self.TopicValidator))
        self.add_dialog(WaterFallDialog('wf_dialog', [self.Welcome, self.Topic, self.FinalStep])

    async def Welcome(self, step_context):
        return await step_context.prompt(
        'topic', 
        options = PromptOptions(
            prompt = MessageFactory.text('Welcome to the bot, I can show you documents of topic Math, English, Science'), 
            retry_prompt = MessageFactory.text("I am sorry I didn't understand please try again with different wording")
            )
        )

    async def TopicValidator(self, prompt_context: PromptValidatorContext):
        for_luis = prompt_context.recognized.value

        #hit the luis app to get the topic name
        topic_name = self.luis_app(for_luis)

        if topic_name in ['Math', 'Science', 'English']:
            return True
        else:
            return False

    async def Topic(self, step_context):
        topic_name = self.luis_app(step_context.context.activity.text) #using the same user message as used in Validator function 

        #filter documents based on topics with custom function filter_doc
        docs = filter_doc(topic_name) 

        return await step_context.prompt('docs', options = PromptOptions(prompt = docs))

    async def FinalStep(self, step_context):
        #code for final step
1
Now that I've added the Python tag, you can see that your code is malformed. What editor did you use to write that code where you couldn't see that the string was faulty? Does the code still work correctly despite the bad string? Would you please edit the fixed string into your question?Kyle Delaney
Thanks, the string was faulty, I have edited it. Although this was just a sample code, through which I wanted to highlight the implementation of my dialog set. As you can see in the sample code, at two separate instances I am making calls to luis with same input text, once in custom validator and once in next waterfall dialog to filter out the result. My question was is there a way to pass on any validation result from validator to subsequent dialogs. This way I can avoid two luis calls, and do away with just one. Apologies if the question wasn't clear.monte
So I'm guessing luis_app is a function that calls your LUIS endpoint and is not an actual LuisApplication object. Is that correct? If so, I can see that you are needlessly calling the endpoint twice and you'd rather just call it once. I can think of several ways to answer your question as you've asked it, but I suspect there's a better question that you didn't ask. Is your text prompt just trying to get the user to pick one of three possible options? If so, you should be using a choice prompt instead. You asked how to call LUIS only once, but maybe you can call it zero times.Kyle Delaney
Yes, luis_app in the sample code is just a proxy function to call the luis endpoint. Yes my text prompt is trying to get user to pick one of three options and yes I have tried choice prompts. Actually those were my first choice to go with. But later on I felt calling LUIS is a more robust choice as what if the user didn't select the option, rather typed "show me documents for Math". Choice prompt won't be able to recognize this and would reprompt again, but with luis I can handle such variations. Would really appreciate if you could share the approach of calling luis only once.monte
Is my answer acceptable?Kyle Delaney

1 Answers

1
votes

I really want to reiterate that I think you should use a choice prompt. Choice prompts are a lot more flexible than you seem to think. The choice recognition is really quite advanced and it is able to recognize a choice in the middle of a phrase and you can even provide synonyms just like in a LUIS list entity. There's an entire folder in the dialogs library dedicated to choice recognition. You think you're giving the user a better experience by using LUIS, but you're actually giving them a worse experience because they can't type an ordinal like 1 or 2 as their choice. Based on what you've told me, I'm certain that a choice prompt is the best option for you.

That being said, here's the information you asked for.

It's common practice to only call any given LUIS endpoint once per turn. If your LUIS model is global for your bot then it would make more sense to call it outside of that dialog, so I'll assume this LUIS model is specific to this dialog.

The most obvious solution would be to store the LUIS result in the turn state. You could store it like this:

prompt_context.context.turn_state[MY_KEY] = self.luis_app(for_luis);

And you could retrieve the value like this:

topic_name = step_context.context.turn_state[MY_KEY];

Another idea would to make luis_app a memoized function (a function that caches its own results). Since the best place for its cache would be the turn state, this isn't so different from the first idea. The main difference is that you'd be changing the luis_app code and leaving your dialog code as it is.

Another idea would be to put your LUIS result in the prompt result. You are free to modify the recognized value from within the validator as you please.

prompt_context.recognized.value = self.luis_app(prompt_context.recognized.value);

That value is accessed in the next step of the waterfall using step_context.result. I should mention that you can also modify the text property of the incoming activity, but that might be considered bad practice.

Finally, you could create your own prompt class that automatically uses a LUIS entity as its recognized value. Perhaps you could call it EntityPrompt or something.