From 46b31998ae76eadda7d921dba9ed48e4cf607ed7 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Fri, 25 Oct 2019 16:21:08 -0500 Subject: [PATCH 1/4] Added 03.welcome-user sample --- samples/03.welcome-user/README.md | 51 +++++++ samples/03.welcome-user/app.py | 84 +++++++++++ samples/03.welcome-user/bots/__init__.py | 6 + .../03.welcome-user/bots/welcome_user_bot.py | 132 ++++++++++++++++++ samples/03.welcome-user/config.py | 19 +++ .../03.welcome-user/data_models/__init__.py | 6 + .../data_models/welcome_user_state.py | 7 + samples/03.welcome-user/requirements.txt | 1 + 8 files changed, 306 insertions(+) create mode 100644 samples/03.welcome-user/README.md create mode 100644 samples/03.welcome-user/app.py create mode 100644 samples/03.welcome-user/bots/__init__.py create mode 100644 samples/03.welcome-user/bots/welcome_user_bot.py create mode 100644 samples/03.welcome-user/config.py create mode 100644 samples/03.welcome-user/data_models/__init__.py create mode 100644 samples/03.welcome-user/data_models/welcome_user_state.py create mode 100644 samples/03.welcome-user/requirements.txt diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md new file mode 100644 index 000000000..5d7ae28ba --- /dev/null +++ b/samples/03.welcome-user/README.md @@ -0,0 +1,51 @@ +# welcome users + + +Bot Framework v4 welcome users bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to welcome users when they join the conversation. + +## Running the sample +- Clone the repository +```bash +git clone https://github.com/Microsoft/botbuilder-python.git +``` +- Run `pip install -r requirements.txt` to install all dependencies +- Run `python app.py` +- Alternatively to the last command, you can set the file in an environment variable with `set FLASK_APP=app.py` in windows (`export FLASK_APP=app.py` in mac/linux) and then run `flask run --host=127.0.0.1 --port=3978` + + +### Visual studio code +- Activate your desired virtual environment +- Open `botbuilder-python\samples\03.welcome-user` folder +- Bring up a terminal, navigate to `botbuilder-python\samples\03.welcome-user` folder +- In the terminal, type `pip install -r requirements.txt` +- In the terminal, type `python app.py` + +## Testing the bot using Bot Framework Emulator +[Microsoft Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to bot using Bot Framework Emulator +- Launch Bot Framework Emulator +- Paste this URL in the emulator window - http://localhost:3978/api/messages + + +## Welcoming Users + +The primary goal when creating any bot is to engage your user in a meaningful conversation. One of the best ways to achieve this goal is to ensure that from the moment a user first connects, they understand your bot’s main purpose and capabilities, the reason your bot was created. See [Send welcome message to users](https://aka.ms/botframework-welcome-instructions) for additional information on how a bot can welcome uers to a conversation. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/03.welcome-user/app.py b/samples/03.welcome-user/app.py new file mode 100644 index 000000000..9b8e1fc08 --- /dev/null +++ b/samples/03.welcome-user/app.py @@ -0,0 +1,84 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +This sample shows how to manage state in a bot. +""" +import asyncio +import sys + +from flask import Flask, request, Response +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + ConversationState, + MemoryStorage, + TurnContext, + UserState, +) +from botbuilder.schema import Activity + +from bots import WelcomeUserBot + +LOOP = asyncio.get_event_loop() +APP = Flask(__name__, instance_relative_config=True) +APP.config.from_object("config.DefaultConfig") + +SETTINGS = BotFrameworkAdapterSettings( + APP.config["APP_ID"], APP.config["APP_PASSWORD"] +) +ADAPTER = BotFrameworkAdapter(SETTINGS) + +# Catch-all for errors. +async def on_error(context: TurnContext, error: Exception): + # This check writes out errors to console log + # NOTE: In production environment, you should consider logging this to Azure + # application insights. + print(f"\n [on_turn_error]: { error }", file=sys.stderr) + # Send a message to the user + await context.send_activity("Oops. Something went wrong!") + + +ADAPTER.on_turn_error = on_error + +# Create MemoryStorage, UserState and ConversationState +MEMORY = MemoryStorage() + +# Commented out user_state because it's not being used. +USER_STATE = UserState(MEMORY) + + +BOT = WelcomeUserBot(USER_STATE) + + +@APP.route("/api/messages", methods=["POST"]) +def messages(): + """Main bot message handler.""" + if "application/json" in request.headers["Content-Type"]: + body = request.json + else: + return Response(status=415) + + activity = Activity().deserialize(body) + auth_header = ( + request.headers["Authorization"] if "Authorization" in request.headers else "" + ) + + async def aux_func(turn_context): + await BOT.on_turn(turn_context) + + try: + task = LOOP.create_task( + ADAPTER.process_activity(activity, auth_header, aux_func) + ) + LOOP.run_until_complete(task) + return Response(status=201) + except Exception as exception: + raise exception + + +if __name__ == "__main__": + try: + APP.run(debug=False, port=APP.config["PORT"]) # nosec debug + except Exception as exception: + raise exception diff --git a/samples/03.welcome-user/bots/__init__.py b/samples/03.welcome-user/bots/__init__.py new file mode 100644 index 000000000..4f3e70d59 --- /dev/null +++ b/samples/03.welcome-user/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .welcome_user_bot import WelcomeUserBot + +__all__ = ["WelcomeUserBot"] diff --git a/samples/03.welcome-user/bots/welcome_user_bot.py b/samples/03.welcome-user/bots/welcome_user_bot.py new file mode 100644 index 000000000..67d97e4a6 --- /dev/null +++ b/samples/03.welcome-user/bots/welcome_user_bot.py @@ -0,0 +1,132 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, TurnContext, UserState, CardFactory, MessageFactory +from botbuilder.schema import ChannelAccount, HeroCard, CardImage, CardAction, ActionTypes + +from data_models import WelcomeUserState + + +class WelcomeUserBot(ActivityHandler): + def __init__(self, user_state: UserState): + if user_state is None: + raise TypeError( + "[WelcomeUserBot]: Missing parameter. user_state is required but None was given" + ) + + self.user_state = user_state + + self.user_state_accessor = self.user_state.create_property("WelcomeUserState") + + self.WELCOME_MESSAGE = """This is a simple Welcome Bot sample. This bot will introduce you + to welcoming and greeting users. You can say 'intro' to see the + introduction card. If you are running this bot in the Bot Framework + Emulator, press the 'Restart Conversation' button to simulate user joining + a bot or a channel""" + + async def on_turn(self, turn_context: TurnContext): + await super().on_turn(turn_context) + + # save changes to WelcomeUserState after each turn + await self.user_state.save_changes(turn_context) + + """ + Greet when users are added to the conversation. + Note that all channels do not send the conversation update activity. + If you find that this bot works in the emulator, but does not in + another channel the reason is most likely that the channel does not + send this activity. + """ + + async def on_members_added_activity( + self, members_added: [ChannelAccount], turn_context: TurnContext + ): + for member in members_added: + if member.id != turn_context.activity.recipient.id: + await turn_context.send_activity( + f"Hi there { member.name }. " + self.WELCOME_MESSAGE + ) + + await turn_context.send_activity("""You are seeing this message because the bot received at least one + 'ConversationUpdate' event, indicating you (and possibly others) + joined the conversation. If you are using the emulator, pressing + the 'Start Over' button to trigger this event again. The specifics + of the 'ConversationUpdate' event depends on the channel. You can + read more information at: https://aka.ms/about-botframework-welcome-user""" + ) + + await turn_context.send_activity("""It is a good pattern to use this event to send general greeting + to user, explaining what your bot can do. In this example, the bot + handles 'hello', 'hi', 'help' and 'intro'. Try it now, type 'hi'""" + ) + + """ + Respond to messages sent from the user. + """ + + async def on_message_activity(self, turn_context: TurnContext): + # Get the state properties from the turn context. + welcome_user_state = await self.user_state_accessor.get(turn_context, WelcomeUserState) + + if not welcome_user_state.did_welcome_user: + welcome_user_state.did_welcome_user = True + + await turn_context.send_activity( + "You are seeing this message because this was your first message ever to this bot." + ) + + name = turn_context.activity.from_property.name + await turn_context.send_activity( + f"It is a good practice to welcome the user and provide personal greeting. For example: Welcome { name }" + ) + + else: + # This example hardcodes specific utterances. You should use LUIS or QnA for more advance language understanding. + text = turn_context.activity.text.lower() + if text in ("hello", "hi"): + await turn_context.send_activity( + f"You said { text }" + ) + elif text in ("intro", "help"): + await self.__send_intro_card(turn_context) + else: + await turn_context.send_activity(self.WELCOME_MESSAGE) + + async def __send_intro_card(self, turn_context: TurnContext): + card = HeroCard( + title="Welcome to Bot Framework!", + text="Welcome to Welcome Users bot sample! This Introduction card " + "is a great way to introduce your Bot to the user and suggest " + "some things to get them started. We use this opportunity to " + "recommend a few next steps for learning more creating and deploying bots.", + images=[ + CardImage( + url="https://aka.ms/bf-welcome-card-image" + ) + ], + buttons=[ + CardAction( + type=ActionTypes.open_url, + title="Get an overview", + text="Get an overview", + display_text="Get an overview", + value="https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + ), + CardAction( + type=ActionTypes.open_url, + title="Ask a question", + text="Ask a question", + display_text="Ask a question", + value="https://stackoverflow.com/questions/tagged/botframework" + ), + CardAction( + type=ActionTypes.open_url, + title="Learn how to deploy", + text="Learn how to deploy", + display_text="Learn how to deploy", + value="https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + ) + ] + ) + + return await turn_context.send_activity(MessageFactory.attachment(CardFactory.hero_card(card))) diff --git a/samples/03.welcome-user/config.py b/samples/03.welcome-user/config.py new file mode 100644 index 000000000..c8c926f07 --- /dev/null +++ b/samples/03.welcome-user/config.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import os + +""" Bot Configuration """ + + +class DefaultConfig(object): + """ Bot Configuration """ + + PORT = 3978 + APP_ID = os.environ.get("MicrosoftAppId", "") + APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") + LUIS_APP_ID = os.environ.get("LuisAppId", "") + LUIS_API_KEY = os.environ.get("LuisAPIKey", "") + # LUIS endpoint host name, ie "westus.api.cognitive.microsoft.com" + LUIS_API_HOST_NAME = os.environ.get("LuisAPIHostName", "") diff --git a/samples/03.welcome-user/data_models/__init__.py b/samples/03.welcome-user/data_models/__init__.py new file mode 100644 index 000000000..a7cd0686a --- /dev/null +++ b/samples/03.welcome-user/data_models/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .welcome_user_state import WelcomeUserState + +__all__ = ["WelcomeUserState"] diff --git a/samples/03.welcome-user/data_models/welcome_user_state.py b/samples/03.welcome-user/data_models/welcome_user_state.py new file mode 100644 index 000000000..7470d4378 --- /dev/null +++ b/samples/03.welcome-user/data_models/welcome_user_state.py @@ -0,0 +1,7 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + + +class WelcomeUserState: + def __init__(self, did_welcome: bool = False): + self.did_welcome_user = did_welcome diff --git a/samples/03.welcome-user/requirements.txt b/samples/03.welcome-user/requirements.txt new file mode 100644 index 000000000..0c4745525 --- /dev/null +++ b/samples/03.welcome-user/requirements.txt @@ -0,0 +1 @@ +botbuilder-core>=4.4.0b1 \ No newline at end of file From 6b5a603a031e25c9c0ab95d73de2ad45350ade13 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 28 Oct 2019 16:34:34 -0500 Subject: [PATCH 2/4] Removed LUIS keys from settings. --- samples/03.welcome-user/config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/03.welcome-user/config.py b/samples/03.welcome-user/config.py index c8c926f07..c46adb612 100644 --- a/samples/03.welcome-user/config.py +++ b/samples/03.welcome-user/config.py @@ -13,7 +13,3 @@ class DefaultConfig(object): PORT = 3978 APP_ID = os.environ.get("MicrosoftAppId", "") APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "") - LUIS_APP_ID = os.environ.get("LuisAppId", "") - LUIS_API_KEY = os.environ.get("LuisAPIKey", "") - # LUIS endpoint host name, ie "westus.api.cognitive.microsoft.com" - LUIS_API_HOST_NAME = os.environ.get("LuisAPIHostName", "") From 5c3d372cd7586050d266e1ff69e70c28b55b6cab Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 29 Oct 2019 08:34:55 -0500 Subject: [PATCH 3/4] Updated on_error messages, standardized app.py --- samples/03.welcome-user/README.md | 10 +--- samples/03.welcome-user/app.py | 57 +++++++++++-------- .../03.welcome-user/bots/welcome_user_bot.py | 3 +- samples/03.welcome-user/config.py | 2 +- samples/03.welcome-user/requirements.txt | 3 +- 5 files changed, 39 insertions(+), 36 deletions(-) diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index 5d7ae28ba..160415370 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -34,18 +34,10 @@ git clone https://github.com/Microsoft/botbuilder-python.git ## Welcoming Users -The primary goal when creating any bot is to engage your user in a meaningful conversation. One of the best ways to achieve this goal is to ensure that from the moment a user first connects, they understand your bot’s main purpose and capabilities, the reason your bot was created. See [Send welcome message to users](https://aka.ms/botframework-welcome-instructions) for additional information on how a bot can welcome uers to a conversation. - +The primary goal when creating any bot is to engage your user in a meaningful conversation. One of the best ways to achieve this goal is to ensure that from the moment a user first connects, they understand your bot’s main purpose and capabilities, the reason your bot was created. See [Send welcome message to users](https://aka.ms/botframework-welcome-instructions) for additional information on how a bot can welcome users to a conversation. ## Further reading - [Bot Framework Documentation](https://docs.botframework.com) - [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) - [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/03.welcome-user/app.py b/samples/03.welcome-user/app.py index 9b8e1fc08..7a771763d 100644 --- a/samples/03.welcome-user/app.py +++ b/samples/03.welcome-user/app.py @@ -1,59 +1,71 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -""" -This sample shows how to manage state in a bot. -""" import asyncio import sys +from datetime import datetime +from types import MethodType from flask import Flask, request, Response from botbuilder.core import ( BotFrameworkAdapter, BotFrameworkAdapterSettings, - ConversationState, MemoryStorage, TurnContext, UserState, ) -from botbuilder.schema import Activity +from botbuilder.schema import Activity, ActivityTypes from bots import WelcomeUserBot +# Create the loop and Flask app LOOP = asyncio.get_event_loop() APP = Flask(__name__, instance_relative_config=True) APP.config.from_object("config.DefaultConfig") -SETTINGS = BotFrameworkAdapterSettings( - APP.config["APP_ID"], APP.config["APP_PASSWORD"] -) +# Create adapter. +# See https://aka.ms/about-bot-adapter to learn more about how bots work. +SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"]) ADAPTER = BotFrameworkAdapter(SETTINGS) + # Catch-all for errors. -async def on_error(context: TurnContext, error: Exception): - # This check writes out errors to console log +async def on_error(self, context: TurnContext, error: Exception): + # This check writes out errors to console log .vs. app insights. # NOTE: In production environment, you should consider logging this to Azure # application insights. - print(f"\n [on_turn_error]: { error }", file=sys.stderr) - # Send a message to the user - await context.send_activity("Oops. Something went wrong!") + print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr) + # Send a message to the user + await context.send_activity("The bot encountered an error or bug.") + await context.send_activity("To continue to run this bot, please fix the bot source code.") + # Send a trace activity if we're talking to the Bot Framework Emulator + if context.activity.channel_id == 'emulator': + # Create a trace activity that contains the error object + trace_activity = Activity( + label="TurnError", + name="on_turn_error Trace", + timestamp=datetime.utcnow(), + type=ActivityTypes.trace, + value=f"{error}", + value_type="https://www.botframework.com/schemas/error" + ) + # Send a trace activity, which will be displayed in Bot Framework Emulator + await context.send_activity(trace_activity) -ADAPTER.on_turn_error = on_error +ADAPTER.on_turn_error = MethodType(on_error, ADAPTER) -# Create MemoryStorage, UserState and ConversationState +# Create MemoryStorage, UserState MEMORY = MemoryStorage() - -# Commented out user_state because it's not being used. USER_STATE = UserState(MEMORY) - +# Create the Bot BOT = WelcomeUserBot(USER_STATE) - +# Listen for incoming requests on /api/messages. @APP.route("/api/messages", methods=["POST"]) def messages(): - """Main bot message handler.""" + # Main bot message handler. if "application/json" in request.headers["Content-Type"]: body = request.json else: @@ -64,12 +76,9 @@ def messages(): request.headers["Authorization"] if "Authorization" in request.headers else "" ) - async def aux_func(turn_context): - await BOT.on_turn(turn_context) - try: task = LOOP.create_task( - ADAPTER.process_activity(activity, auth_header, aux_func) + ADAPTER.process_activity(activity, auth_header, BOT.on_turn) ) LOOP.run_until_complete(task) return Response(status=201) diff --git a/samples/03.welcome-user/bots/welcome_user_bot.py b/samples/03.welcome-user/bots/welcome_user_bot.py index 67d97e4a6..8fca0919f 100644 --- a/samples/03.welcome-user/bots/welcome_user_bot.py +++ b/samples/03.welcome-user/bots/welcome_user_bot.py @@ -81,7 +81,8 @@ async def on_message_activity(self, turn_context: TurnContext): ) else: - # This example hardcodes specific utterances. You should use LUIS or QnA for more advance language understanding. + # This example hardcodes specific utterances. You should use LUIS or QnA for more advance language + # understanding. text = turn_context.activity.text.lower() if text in ("hello", "hi"): await turn_context.send_activity( diff --git a/samples/03.welcome-user/config.py b/samples/03.welcome-user/config.py index c46adb612..e007d0fa9 100644 --- a/samples/03.welcome-user/config.py +++ b/samples/03.welcome-user/config.py @@ -7,7 +7,7 @@ """ Bot Configuration """ -class DefaultConfig(object): +class DefaultConfig: """ Bot Configuration """ PORT = 3978 diff --git a/samples/03.welcome-user/requirements.txt b/samples/03.welcome-user/requirements.txt index 0c4745525..7e54b62ec 100644 --- a/samples/03.welcome-user/requirements.txt +++ b/samples/03.welcome-user/requirements.txt @@ -1 +1,2 @@ -botbuilder-core>=4.4.0b1 \ No newline at end of file +botbuilder-core>=4.4.0b1 +flask>=1.0.3 From f68657062fbfaadf2bea865081c89c01c2c6f596 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 29 Oct 2019 12:50:41 -0500 Subject: [PATCH 4/4] Corrected README --- samples/03.welcome-user/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index 160415370..ac6c37553 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -10,14 +10,7 @@ This bot has been created using [Bot Framework](https://dev.botframework.com), i ```bash git clone https://github.com/Microsoft/botbuilder-python.git ``` -- Run `pip install -r requirements.txt` to install all dependencies -- Run `python app.py` -- Alternatively to the last command, you can set the file in an environment variable with `set FLASK_APP=app.py` in windows (`export FLASK_APP=app.py` in mac/linux) and then run `flask run --host=127.0.0.1 --port=3978` - - -### Visual studio code - Activate your desired virtual environment -- Open `botbuilder-python\samples\03.welcome-user` folder - Bring up a terminal, navigate to `botbuilder-python\samples\03.welcome-user` folder - In the terminal, type `pip install -r requirements.txt` - In the terminal, type `python app.py`