0

My bot processes incoming user messages and takes action based on the intent. For simple, one shot answers that do not require multiple passes between users and the bot, it works well. Now when I want to implement a dialog, it's getting tough. Once the user sends the message and the Dialog gets triggered, the bot asks the user for input. Once the user provides the input, this input is being again processed by LUIS to understand the intent. However, I do not want this to happen as this abandons the ongoing Dialog.

I want to understand if there some way I can detect if a Dialog is under progress and what dialog is it. If I can know this, I can skip the LUIS intent check part and direct the logic to Dialog. I've used the Multi-turn Prompt as a basis for this. Below is what I expect.

from botbuilder.core import ActivityHandler, TurnContext, MessageFactory, IntentScore, ConversationState, UserState
from botbuilder.schema import ChannelAccount,Attachment
from botbuilder.ai.luis import LuisApplication,LuisPredictionOptions,LuisRecognizer
from botbuilder.ai.qna import QnAMakerEndpoint,QnAMakerOptions,QnAMaker
from typing import List
import os
import json
import os.path
from usecases import SimpleUseCase
import requests

from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus
from helpers.dialog_helper import DialogHelper
from dialogs import UserProfileDialog



class Bot(ActivityHandler):

    def __init__(
        self,
        conversation_state: ConversationState,
        user_state: UserState
        # dialog: Dialog,
    ):
        if conversation_state is None:
            raise TypeError(
                "[DialogBot]: Missing parameter. conversation_state is required but None was given"
            )
        if user_state is None:
            raise TypeError(
                "[DialogBot]: Missing parameter. user_state is required but None was given"
            )
        # if dialog is None:
        #     raise Exception("[DialogBot]: Missing parameter. dialog is required")

        self.conversation_state = conversation_state
        self.user_state = user_state
        # self.dialog = dialog###Change based on dialog

        luisApp = LuisApplication("","","https://abcd.cognitiveservices.azure.com/")
        luisOptions = LuisPredictionOptions(include_all_intents=True,include_instance_data=True)
        self.LuisRecog = LuisRecognizer(luisApp,luisOptions)
        self.qnaMaker = QnAMaker(QnAMakerEndpoint("","",""))        
    

    async def on_members_added_activity(
        self, members_added: List[ChannelAccount], turn_context: TurnContext
    ):
        for member in members_added:
            # Greet anyone that was not the target (recipient) of this message.
            # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
            if member.id != turn_context.activity.recipient.id:
                welcome_card = self.create_adaptive_card_attachment()
                response = MessageFactory.attachment(welcome_card)
                await turn_context.send_activity(response)

    # Load attachment from file.
    def create_adaptive_card_attachment(self):
        relative_path = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(relative_path, "cards/welcomeCard.json")
        with open(path) as in_file:
            card = json.load(in_file)

        return Attachment(
            content_type="application/vnd.microsoft.card.adaptive", content=card
        )

    async def greetings(self,turn_context):
        await turn_context.send_activity(MessageFactory.text("from inside greetings"))

    async def on_turn(self, turn_context: TurnContext):
        await super().on_turn(turn_context)

        # Save any state changes that might have ocurred during the turn.
        await self.conversation_state.save_changes(turn_context)
        await self.user_state.save_changes(turn_context)


    async def on_message_activity(self, turn_context: TurnContext):
        luisResult = await self.LuisRecog.recognize(turn_context) 
        print(luisResult.get_top_scoring_intent())
        intent = LuisRecognizer.top_intent(luisResult,min_score=0.40)

        if turn_context.activity.text.lower() == 'hi':
            await self.greetings(turn_context)
            await turn_context.send_activity(MessageFactory.text("message sent from send greetings.."))
        else:
### Here I want to be able to understand if there is any dialog that is already present on the dialog stack and 
### the dialog id. If we can get that details, I can then call the run_dialog method with the  dialog id 
### instead of routing the request based on the intent. Without knowing if a dialog is already 
### running, every input from user is again processed which is not desired.
            if intent == 'UserProfileIntent':
                await DialogHelper.run_dialog(UserProfileDialog(self.user_state),turn_context,self.conversation_state.create_property("DialogState"))
            else:
                answers = await self..get_answers(turn_context)                
                await turn_context.send_activity(MessageFactory.text(answers[0].answer))

                

This is in a way related to this question and though there was a discussion on Github here, I think my question/requirement is in a way different. I tried to understand if there's a way to get the current active dialog in other SDK but couldn't find any useful information.

The author of the above question has answered his approach. Though it's a good one, it only partially suits my requirements. The answer is useful when there are small number of intents and also where only Luis is used. My use cases has more than 50 intents and the list will keep increasing. I'll need to keep adding dialogs inside the maindialog. If I'm able to identify the active dialog, I'll be able to dynamically call the run_dialog method.

Also, I use QnA Maker to answer the user when the intent of the user input is None. When I tried to implement the QnA Maker also into a dialog, I'm ending up with certain errors below.

main_dialog.py

from botbuilder.dialogs import (
    ComponentDialog,
    WaterfallDialog,
    WaterfallStepContext,
    DialogTurnResult,
)
from botbuilder.dialogs.prompts import (
    TextPrompt,
    NumberPrompt,
    ChoicePrompt,
    ConfirmPrompt,
    AttachmentPrompt,
    PromptOptions,
    PromptValidatorContext,
)
from botbuilder.dialogs.choices import Choice
from botbuilder.core import MessageFactory, UserState
from dialogs import NameDialog,UserProfileDialog,QADialog
from botbuilder.ai.qna import QnAMaker,QnAMakerEndpoint
import json

class MainDialog(ComponentDialog):
    def __init__(self, luis, intent, recognizer_result,user_state, turn_context):
        super(MainDialog, self).__init__(MainDialog.__name__)

        self.luis = luis
        self.intent = intent
        self.recognizer_result = recognizer_result
        self.user_state = user_state
        self.turn_context = turn_context

        self.add_dialog(NameDialog(self.user_state))
        self.add_dialog(UserProfileDialog(self.user_state))
        self.add_dialog(QADialog(self.user_state))
        self.add_dialog(
            WaterfallDialog(
                "main_dialog_id", [self.main_step]
            )
        )

        self.initial_dialog_id = "main_dialog_id"

    async def main_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:
        # dialog_detail = self.luis.get_entities(self.intent, self.recognizer_result)
        if self.intent == "name":
            return await step_context.begin_dialog(NameDialog.__name__)
        elif self.intent == "place":
            return await step_context.begin_dialog(UserProfileDialog.__name__)
        elif self.intent == "AGE":
            entities = None
            if self.recognizer_result.entities is not None:
                entities = json.dumps(self.recognizer_result.entities.get("number"))
            return await step_context.begin_dialog(NameDialog.__name__,entities[0])
            # await self.turn_context.send_activity(MessageFactory.text("age intent"))
        elif self.intent == "None":                                    
     ###This part ends up in error even though I close the dialog immediately after sending the answer and I'm not sure if it's the right way to do it.        
            answers = await QnAMaker(QnAMakerEndpoint("","","")).get_answers(self.turn_context)
            if answers and len(answers) > 0:
                await self.turn_context.send_activity(MessageFactory.text(answers[0].answer))
            else:
                await self.turn_context.send_activity(MessageFactory.text("Sorry I cant help you with that !!"))

            await step_context.end_dialog()

bot.py

from botbuilder.core import ActivityHandler, TurnContext, MessageFactory, IntentScore, ConversationState, UserState, StatePropertyAccessor
from botbuilder.schema import ChannelAccount,Attachment
from botbuilder.ai.luis import LuisApplication,LuisPredictionOptions,LuisRecognizer
from botbuilder.ai.qna import QnAMakerEndpoint,QnAMakerOptions,QnAMaker
from typing import List
import os
import json
import os.path
from usecases import SimpleUseCase
import requests

from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus, DialogContext
from helpers.dialog_helper import DialogHelper
from dialogs import UserProfileDialog,NameDialog,MainDialog



class Bot(ActivityHandler):

    def __init__(
        self,
        conversation_state: ConversationState,
        user_state: UserState
        # dialog: Dialog,
    ):
        if conversation_state is None:
            raise TypeError(
                "[DialogBot]: Missing parameter. conversation_state is required but None was given"
            )
        if user_state is None:
            raise TypeError(
                "[DialogBot]: Missing parameter. user_state is required but None was given"
            )
        # if dialog is None:
        #     raise Exception("[DialogBot]: Missing parameter. dialog is required")

        self.conversation_state = conversation_state
        self.user_state = user_state
        # self.dialog = dialog###Change based on dialog

        luisApp = LuisApplication("","","")
        luisOptions = LuisPredictionOptions(include_all_intents=True,include_instance_data=True)
        self.LuisRecog = LuisRecognizer(luisApp,luisOptions)
      

    async def on_members_added_activity(
        self, members_added: List[ChannelAccount], turn_context: TurnContext
    ):
        for member in members_added:
            # Greet anyone that was not the target (recipient) of this message.
            # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
            if member.id != turn_context.activity.recipient.id:
                welcome_card = self.create_adaptive_card_attachment()
                response = MessageFactory.attachment(welcome_card)
                await turn_context.send_activity(response)

    # Load attachment from file.
    def create_adaptive_card_attachment(self):
        relative_path = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(relative_path, "cards/welcomeCard.json")
        with open(path) as in_file:
            card = json.load(in_file)

        return Attachment(
            content_type="application/vnd.microsoft.card.adaptive", content=card
        )

    async def greetings(self,turn_context):
        await turn_context.send_activity(MessageFactory.text("from inside greetings"))

    async def on_turn(self, turn_context: TurnContext):
        await super().on_turn(turn_context)

        # Save any state changes that might have ocurred during the turn.
        await self.conversation_state.save_changes(turn_context)
        await self.user_state.save_changes(turn_context)


    async def on_message_activity(self, turn_context: TurnContext):
        luisResult = await self.LuisRecog.recognize(turn_context) 
        print(luisResult.get_top_scoring_intent())
        intent = LuisRecognizer.top_intent(luisResult,min_score=0.40)


        if turn_context.activity.text.lower() == 'hi':
            await self.greetings(turn_context)
            await turn_context.send_activity(MessageFactory.text("message sent from send greetings.."))
        else:
            # if intent != "None":
            dialog = MainDialog(self.LuisRecog, intent, luisResult,self.user_state,turn_context)
            await DialogHelper.run_dialog(dialog,turn_context,self.conversation_state.create_property("DialogState"))

                

Error in case of None intent and Q&A Maker processes the user question. Also I've not tried the QnA Dialogs yet to see if they work in this setup.

It would be great if we could somehow access the current active dialog id so that we can switch the dialog easily based on the id by creating a map of intent to dialog.

enter image description here

Thanks to the author of this question for sharing his solution.

Approach with Dispatch

import os
from typing import List
import json

from azure.cognitiveservices.language.luis.runtime.models import LuisResult, IntentModel

from botbuilder.ai.luis import LuisApplication, LuisRecognizer, LuisPredictionOptions
from botbuilder.ai.qna import QnAMaker, QnAMakerEndpoint
from botbuilder.core import ActivityHandler, TurnContext, RecognizerResult, MessageFactory, UserState, ConversationState
from botbuilder.schema import ChannelAccount, Attachment

from config import DefaultConfig

from helpers.luis_helper import LuisDispatchResult
from helpers.dialog_helper import DialogHelper
from dialogs import NameDialog,UserProfileDialog,QADialog

class DispatchBot(ActivityHandler):
    def __init__(self, config: DefaultConfig,
                 conversation_state: ConversationState,
                 user_state: UserState):
        self.qna_maker = QnAMaker(
            QnAMakerEndpoint(
                knowledge_base_id=config.QNA_KNOWLEDGEBASE_ID,
                endpoint_key=config.QNA_ENDPOINT_KEY,
                host=config.QNA_ENDPOINT_HOST,
            )
        )

        # If the includeApiResults parameter is set to true, as shown below, the full response
        # from the LUIS api will be made available in the properties  of the RecognizerResult
        self.dialog_started = False
        self.app_id = config.LUIS_APP_ID
        self.api_key = config.LUIS_API_KEY
        self.host = "https://" + config.LUIS_API_HOST_NAME
        self.user_state = user_state
        self.conversation_state = conversation_state

        luis_application = LuisApplication(
            config.LUIS_APP_ID,
            config.LUIS_API_KEY,
            "https://" + config.LUIS_API_HOST_NAME,
        )
        luis_options = LuisPredictionOptions(
            include_all_intents=True, include_instance_data=True
        )
        self.recognizer = LuisRecognizer(luis_application, luis_options, True)

    async def on_members_added_activity(
        self, members_added: List[ChannelAccount], turn_context: TurnContext
    ):
        for member in members_added:
            # Greet anyone that was not the target (recipient) of this message.
            # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
            if member.id != turn_context.activity.recipient.id:
                welcome_card = self.create_adaptive_card_attachment()
                response = MessageFactory.attachment(welcome_card)
                await turn_context.send_activity(response)

    # Load attachment from file.
    def create_adaptive_card_attachment(self):
        relative_path = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(relative_path, "cards/welcomeCard.json")
        with open(path) as in_file:
            card = json.load(in_file)

        return Attachment(
            content_type="application/vnd.microsoft.card.adaptive", content=card
        )



    async def on_message_activity(self, turn_context: TurnContext):
        # First, we use the dispatch model to determine which cognitive service (LUIS or QnA) to use.
        # recognizer_result = await self.recognizer.recognize(turn_context)
        # Top intent tell us which cognitive service to use.
        # intent = LuisRecognizer.top_intent(recognizer_result)
        topIntent,childIntent,childEntities = LuisDispatchResult().getLuisDispatchResult(self.app_id,self.api_key,self.host,turn_context.activity.text)

        # Next, we call the dispatcher with the top intent.
        await self._dispatch_to_top_intent(turn_context, topIntent,childIntent,childEntities)

    async def _dispatch_to_top_intent(
        self, turn_context: TurnContext, topIntent,childIntent,childEntities
    ):
        if topIntent == "l_myluisapp":
            await self._process_luis_queries(
                turn_context, childIntent,childEntities
            )
        elif topIntent == "q_mybotqna":
            await self._process_sample_qna(turn_context)
        else:
            await turn_context.send_activity(f"Dispatch unrecognized intent: {topIntent}.")

    async def _process_luis_queries(
        self, turn_context: TurnContext, childIntent,childEntities
    ):
        await turn_context.send_activity(
            f"LUIS intent selected {childIntent}."
        )

        await turn_context.send_activity(
            f"LUIS entities: {childEntities}."
        )
        
            
        if childIntent == "name":
            self.dialog_started = True
            dialog = NameDialog(self.user_state)
            await DialogHelper.run_dialog(dialog,turn_context,self.conversation_state.create_property("DialogState"))
        elif childIntent == "place":
            self.dialog_started = True
            dialog = UserProfileDialog(self.user_state)
            await DialogHelper.run_dialog(dialog,turn_context,self.conversation_state.create_property("DialogState"))
        elif childIntent == "AGE":
            self.dialog_started = True
            dialog = NameDialog(self.user_state)
            await DialogHelper.run_dialog(dialog,turn_context,self.conversation_state.create_property("DialogState"))
        else:
            await turn_context.send_activity(MessageFactory.text("No suitable intent detected"))


    async def _process_sample_qna(self, turn_context: TurnContext):
        results = await self.qna_maker.get_answers(turn_context)
        if results:
            await turn_context.send_activity(results[0].answer)
        else:
            await turn_context.send_activity(
                "Sorry, could not find an answer in the Q and A system."
            )

Whenever an intent and there by child intent is detected from Dispatch App, the dialog is getting triggered. However, as soon as the user provides input to the question from first dialog, which results in a None intent, the conversation ends with Dispatch unrecognized intent: None. The dialog is not supposed to end until an end_dialog is called. I tried doing the same by passing the child dialog into a Main Dialog like above, but it also results in the same. What should I be doing in order to skip the intent check or what am I doing wrong here ?

Vijay
  • 1,030
  • 11
  • 34
  • I"m looking into this, but before I dig too deep, have you looked into dispatch? It's like an umbella LUIS, where you can detect a LUIS intent vs a QnA. Once you've detected that initial intent, you can run a method to start the component dialog from there, and it won't come back to your parent dialog until end_dialog is triggered. – JJ_Wailes Jul 01 '20 at 17:14
  • I tried to run the Python Dispatch sample, but it always ends up with Dispatch unrecognized intent - I've configured both the LUIS and QnA Models and updated the config.py. The intents that are used in the branching logic l_HomeAutomation/l_Weather/q_sample-qna are not present in the LUIS model. I assume they must be present bcz, the intent is being derived from LUIS Recognizer. Do I need to install and configure Dispatch CLI tool as well for this to work ? – Vijay Jul 01 '20 at 17:35
  • 1
    Was able to setup dispatch! I'll try it out and let you if I face any issues. – Vijay Jul 01 '20 at 18:29
  • I was able to setup Dispatch and run the bot. I have a question though. Dispatch just splits the question between LUIS and QnA and does not actually give the LUIS Intent. Once it splits, we're supposed run the recognizer again to find the intent from LUIS. Am I right ? – Vijay Jul 02 '20 at 14:23
  • not necessarily. That's just what the dispatch samples does to show an example of what it's capable of. – JJ_Wailes Jul 02 '20 at 15:59
  • Yes. You're right. I was looking if Dispatch gets the intent from the child apps as well and it seems it does. I found the info here - https://stackoverflow.com/questions/54930061/dispatch-cli-not-passing-entities-from-luis-app. I tried the JSON View on LUIS in the dispatch luis app and it shows the intents from the child app as well in addition to showing the app or QnA itself. However, I'm struggling to extract the child app intent from the recognizer result. Let me know if you've any inputs. – Vijay Jul 02 '20 at 16:12
  • I managed to extract the child app intent from dispatch results via REST API. However, when I enter dialog based on child app intent, the dialog breaks after the first input!! Not sure what went wrong! – Vijay Jul 02 '20 at 18:40
  • Any inputs on this?? @JJ_Wailes – Vijay Jul 06 '20 at 15:26

1 Answers1

0

Finally, I'm able to do exactly what I want. The Python SDK and the community around is not as mature as the .net one which is why it took a lot more time than it actually should. I followed a .Net sample on YouTube Github Repo. Checking if a dialog is active or seemed so straightforward after watching this sample. Below is the Python implementation. What the below code does is that, it creates a DialogSet when the bot is initiated and then a DialogContext. It checks if there is any existing dialog that can be run in the on_message_activity method, which if true, continues the old dialog or else, sends the message to LuisHelper Class to detect the intent and initiate the dialog based on intent.

Bot

import os
import json

from botbuilder.core import ActivityHandler, ConversationState, UserState, TurnContext, StatePropertyAccessor, MessageFactory
from botbuilder.dialogs import Dialog, DialogSet, DialogTurnStatus
from botbuilder.schema import Attachment,ChannelAccount
from helpers.luis_helper import LuisDispatchResult
from helpers.qna_helper import QnAHelper,QnAMakerEndpoint
from dialogs import NameDialog, UserProfileDialog
from typing import List
from botbuilder.ai.qna import QnAMaker,QnAMakerEndpoint, QnAMakerOptions

from config import DefaultConfig

CONFIG = DefaultConfig()

class Bot(ActivityHandler):
    def __init__(
        self,
        conversation_state: ConversationState,
        user_state: UserState
    ):
        if conversation_state is None:
            raise Exception(
                "[DialogBot]: Missing parameter. conversation_state is required"
            )
        if user_state is None:
            raise Exception("[DialogBot]: Missing parameter. user_state is required")


        self.conversation_state = conversation_state
        self.user_state = user_state

        self.accessor = self.conversation_state.create_property("DialogState")
        self.dialog_set = DialogSet(self.accessor)
        self.dialog_context = None

        self.app_id = CONFIG.LUIS_APP_ID
        self.api_key = CONFIG.LUIS_API_KEY
        self.host = "https://" + CONFIG.LUIS_API_HOST_NAME

    async def on_turn(self, turn_context: TurnContext):
        await super().on_turn(turn_context)

        # Save any state changes that might have occurred during the turn.
        await self.conversation_state.save_changes(turn_context, False)
        await self.user_state.save_changes(turn_context, False)


    async def on_members_added_activity(
        self, members_added: List[ChannelAccount], turn_context: TurnContext
    ):
        for member in members_added:
            # Greet anyone that was not the target (recipient) of this message.
            # To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details.
            if member.id != turn_context.activity.recipient.id:
                welcome_card = self.create_adaptive_card_attachment()
                response = MessageFactory.attachment(welcome_card)
                await turn_context.send_activity(response)

    # Load attachment from file.
    def create_adaptive_card_attachment(self):
        relative_path = os.path.abspath(os.path.dirname(__file__))
        path = os.path.join(relative_path, "cards/welcomeCard.json")
        with open(path) as in_file:
            card = json.load(in_file)

        return Attachment(
            content_type="application/vnd.microsoft.card.adaptive", content=card
        )

    async def on_message_activity(self, turn_context: TurnContext):        
        
        self.dialog_context = await self.dialog_set.create_context(turn_context)
        results = await self.dialog_context.continue_dialog()

        if results.status == DialogTurnStatus.Empty:
            topIntent,childIntent,childEntities = LuisDispatchResult().getLuisDispatchResult(self.app_id,self.api_key,self.host,turn_context.activity.text)
            print(topIntent,childIntent,childEntities )
            await self._dispatch_to_top_intent(turn_context, topIntent,childIntent,childEntities)

    async def _dispatch_to_top_intent(
        self, turn_context: TurnContext, topIntent,childIntent,childEntities
    ):
        if topIntent == "l_myluisapp":
            await self._process_luis_queries(
                turn_context, childIntent,childEntities
            )
        elif topIntent == "q_mybotqna":
            await self._process_sample_qna(turn_context)
        else:
            await turn_context.send_activity(f"Dispatch unrecognized intent: {topIntent}.")

    async def _process_luis_queries(
        self, turn_context: TurnContext, childIntent,childEntities
    ):

        if childIntent == "name":
            dialog = NameDialog(self.user_state)
            if not await self.dialog_set.find(dialog.id):
                self.dialog_set.add(dialog)            
            await self.dialog_context.begin_dialog(dialog.id)
        elif childIntent == "place":
            dialog = UserProfileDialog(self.user_state)
            if not await self.dialog_set.find(dialog.id):
                self.dialog_set.add(dialog)            
            await self.dialog_context.begin_dialog(dialog.id)
        elif childIntent == "AGE":
            dialog = NameDialog(self.user_state)
            if not await self.dialog_set.find(dialog.id):
                self.dialog_set.add(dialog)            
            await self.dialog_context.begin_dialog(dialog.id)
        else:
            await turn_context.send_activity(MessageFactory.text("No suitable intent detected, Checking QnA..."))
            await self._process_sample_qna(turn_context)
            

    async def _process_sample_qna(self, turn_context: TurnContext):

        results = await QnAMaker(QnAMakerEndpoint(CONFIG.QNA_KNOWLEDGEBASE_ID,CONFIG.QNA_ENDPOINT_KEY,CONFIG.QNA_ENDPOINT_HOST),QnAMakerOptions(score_threshold=CONFIG.QNA_THRESHOLD)).get_answers(turn_context)

        # results = await self.qna_maker.get_answers(turn_context)

        if results:
            await turn_context.send_activity(results[0].answer)
        else:
            await turn_context.send_activity(
                "Sorry, could not find an answer in the Q and A system."
            )

app.py

from quart import Quart, request, Response
from botbuilder.core import (
    BotFrameworkAdapterSettings,
    ConversationState,
    MemoryStorage,
    UserState,
)
from botbuilder.schema import Activity

from bot import Bot

from adapter_with_error_handler import AdapterWithErrorHandler

app = Quart(__name__, instance_relative_config=True)
# app.config.from_object("config.DefaultConfig")

from config import DefaultConfig

CONFIG = DefaultConfig()

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
SETTINGS = BotFrameworkAdapterSettings(CONFIG.APP_ID, CONFIG.APP_PASSWORD)

# Create MemoryStorage, UserState and ConversationState
MEMORY = MemoryStorage()
USER_STATE = UserState(MEMORY)
CONVERSATION_STATE = ConversationState(MEMORY)

# Create adapter.
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
ADAPTER = AdapterWithErrorHandler(SETTINGS, CONVERSATION_STATE)


# Create dialogs and Bot
BOT = Bot(CONVERSATION_STATE, USER_STATE)



# Listen for incoming requests on /api/messages
@app.route("/api/messages", methods=["POST"])
async def messages():
    # Main bot message handler.
    if "application/json" in request.headers["Content-Type"]:
        body = await request.json
    else:
        return Response("", status=415)

    activity = Activity().deserialize(body)
    auth_header = (
        request.headers["Authorization"] if "Authorization" in request.headers else ""
    )

    try:
        await ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
        return Response("", status=201)
    except Exception as exception:
        raise exception


@app.route("/", methods=["GET"])
async def homepage():
    try:
        return "Yes I'm working brother."
    except Exception as exception:
        raise exception
    
if __name__ == "__main__":
    try:
        app.run()  # nosec debug
    except Exception as exception:
        raise exception

A Sample Dialog - Name Dialog

from botbuilder.dialogs import (
    ComponentDialog,
    WaterfallDialog,
    WaterfallStepContext,
    DialogTurnResult,
)
from botbuilder.dialogs.prompts import (
    TextPrompt,
    NumberPrompt,
    ChoicePrompt,
    ConfirmPrompt,
    AttachmentPrompt,
    PromptOptions,
    PromptValidatorContext,
)
from botbuilder.dialogs.choices import Choice
from botbuilder.core import MessageFactory, UserState

from data_models import Name


class NameDialog(ComponentDialog):

    def __init__(self, user_state: UserState):
        super(NameDialog, self).__init__(NameDialog.__name__)

        self.name_accessor = user_state.create_property("Name")

        self.add_dialog(
            WaterfallDialog(
                WaterfallDialog.__name__,
                [
                    self.name_step,
                    self.summary_step
                ],
            )
        )
        self.add_dialog(
            TextPrompt(TextPrompt.__name__)
        )

        self.initial_dialog_id = WaterfallDialog.__name__


    async def name_step(self, step_context: WaterfallStepContext) -> DialogTurnResult:

        return await step_context.prompt(
            TextPrompt.__name__,
            PromptOptions(prompt=MessageFactory.text("My name is Hidimbi. What's yours ?")),
        )
        # User said "no" so we will skip the next step. Give -1 as the age.
        # return await step_context.next(-1)


    async def summary_step(
        self, step_context: WaterfallStepContext
    ) -> DialogTurnResult:
        if step_context.result:
            # Get the current profile object from user state.  Changes to it
            # will saved during Bot.on_turn.
            
            msg = f"Great name {step_context.result}."


            await step_context.context.send_activity(MessageFactory.text(msg))

        else:
            await step_context.context.send_activity(
                MessageFactory.text("Thanks. Your data is not saved")
            )

        # WaterfallStep always finishes with the end of the Waterfall or with another
        # dialog, here it is the end.
        return await step_context.end_dialog()

    @staticmethod
    async def material_prompt_validator(prompt_context: PromptValidatorContext) -> bool:
        # This condition is our validation rule. You can also change the value at this point.
        return (
            prompt_context.recognized.succeeded
            and 0 < prompt_context.recognized.value < 150
        )
Vijay
  • 1,030
  • 11
  • 34
  • This approach is letting me execute the dialog only once. The second time I try the same dialog, it throws me the DialogSet.add(): A dialog with an id of 'NameDialog' already added exception! – Vijay Jul 08 '20 at 15:13
  • Update the code to fix the above dialogset.add() issue. The key point is that, once the dialog is added to the dialog set, it's always available and simply be triggered by calling via its name which I was failing to do earlier. – Vijay Jul 09 '20 at 07:41