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
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.