From feb244b6776f7d820333e588aaa1bd1d07a4e2eb Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 28 Oct 2019 14:44:34 -0500 Subject: [PATCH 1/5] Added 02.echo-bot --- samples/02.echo-bot/README.md | 44 ++++++++++++++ .../02.echo-bot/adapter_with_error_handler.py | 31 ++++++++++ samples/02.echo-bot/app.py | 58 +++++++++++++++++++ samples/02.echo-bot/bots/__init__.py | 6 ++ samples/02.echo-bot/bots/echo_bot.py | 17 ++++++ samples/02.echo-bot/config.py | 19 ++++++ samples/02.echo-bot/requirements.txt | 2 + 7 files changed, 177 insertions(+) create mode 100644 samples/02.echo-bot/README.md create mode 100644 samples/02.echo-bot/adapter_with_error_handler.py create mode 100644 samples/02.echo-bot/app.py create mode 100644 samples/02.echo-bot/bots/__init__.py create mode 100644 samples/02.echo-bot/bots/echo_bot.py create mode 100644 samples/02.echo-bot/config.py create mode 100644 samples/02.echo-bot/requirements.txt diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md new file mode 100644 index 000000000..5423f955d --- /dev/null +++ b/samples/02.echo-bot/README.md @@ -0,0 +1,44 @@ +# EchoBot + +Bot Framework v4 echo bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. + +## 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\45.state-management` folder +- Bring up a terminal, navigate to `botbuilder-python\samples\02.echo-bot` 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 + + +## 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) +- [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/02.echo-bot/adapter_with_error_handler.py b/samples/02.echo-bot/adapter_with_error_handler.py new file mode 100644 index 000000000..c41264829 --- /dev/null +++ b/samples/02.echo-bot/adapter_with_error_handler.py @@ -0,0 +1,31 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +import sys +from botbuilder.core import ( + BotFrameworkAdapter, + BotFrameworkAdapterSettings, + MessageFactory, + TurnContext +) + + +class AdapterWithErrorHandler(BotFrameworkAdapter): + def __init__( + self, + settings: BotFrameworkAdapterSettings + ): + super().__init__(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 + error_message_text = "Sorry, it looks like something went wrong." + await context.send_activity(MessageFactory.text(error_message_text)) + + self.on_turn_error = on_error diff --git a/samples/02.echo-bot/app.py b/samples/02.echo-bot/app.py new file mode 100644 index 000000000..8cd106a64 --- /dev/null +++ b/samples/02.echo-bot/app.py @@ -0,0 +1,58 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +""" +This sample shows a simple Bot that echos messages back to the user. +""" +import asyncio + +from flask import Flask, request, Response +from botbuilder.core import BotFrameworkAdapterSettings +from botbuilder.schema import Activity + +from bots import EchoBot +from adapter_with_error_handler import AdapterWithErrorHandler + +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 = AdapterWithErrorHandler(SETTINGS) + +BOT = EchoBot() + + +@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/02.echo-bot/bots/__init__.py b/samples/02.echo-bot/bots/__init__.py new file mode 100644 index 000000000..f95fbbbad --- /dev/null +++ b/samples/02.echo-bot/bots/__init__.py @@ -0,0 +1,6 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from .echo_bot import EchoBot + +__all__ = ["EchoBot"] diff --git a/samples/02.echo-bot/bots/echo_bot.py b/samples/02.echo-bot/bots/echo_bot.py new file mode 100644 index 000000000..985c0694c --- /dev/null +++ b/samples/02.echo-bot/bots/echo_bot.py @@ -0,0 +1,17 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +from botbuilder.core import ActivityHandler, MessageFactory, TurnContext +from botbuilder.schema import ChannelAccount + + +class EchoBot(ActivityHandler): + 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("Hello and welcome!") + + async def on_message_activity(self, turn_context: TurnContext): + return await turn_context.send_activity(MessageFactory.text(f"Echo: {turn_context.activity.text}")) diff --git a/samples/02.echo-bot/config.py b/samples/02.echo-bot/config.py new file mode 100644 index 000000000..c8c926f07 --- /dev/null +++ b/samples/02.echo-bot/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/02.echo-bot/requirements.txt b/samples/02.echo-bot/requirements.txt new file mode 100644 index 000000000..7e54b62ec --- /dev/null +++ b/samples/02.echo-bot/requirements.txt @@ -0,0 +1,2 @@ +botbuilder-core>=4.4.0b1 +flask>=1.0.3 From 52fe260d9a53632867c8698ab122d5f5befb6d70 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Mon, 28 Oct 2019 16:33:06 -0500 Subject: [PATCH 2/5] Removed LUIS keys from settings. --- samples/02.echo-bot/config.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/samples/02.echo-bot/config.py b/samples/02.echo-bot/config.py index c8c926f07..c46adb612 100644 --- a/samples/02.echo-bot/config.py +++ b/samples/02.echo-bot/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 57f4e2fdfd82cb8be38d2286c3b56295e725460e Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 29 Oct 2019 08:16:20 -0500 Subject: [PATCH 3/5] Added new on_error messages, standardized app.py --- samples/02.echo-bot/README.md | 7 ------- .../02.echo-bot/adapter_with_error_handler.py | 21 ++++++++++++++++--- samples/02.echo-bot/app.py | 19 +++++++---------- samples/02.echo-bot/config.py | 2 +- 4 files changed, 26 insertions(+), 23 deletions(-) diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index 5423f955d..dec66759d 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -30,15 +30,8 @@ git clone https://github.com/Microsoft/botbuilder-python.git - Launch Bot Framework Emulator - Paste this URL in the emulator window - http://localhost:3978/api/messages - ## 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) -- [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/02.echo-bot/adapter_with_error_handler.py b/samples/02.echo-bot/adapter_with_error_handler.py index c41264829..599326aca 100644 --- a/samples/02.echo-bot/adapter_with_error_handler.py +++ b/samples/02.echo-bot/adapter_with_error_handler.py @@ -2,12 +2,14 @@ # Licensed under the MIT License. import sys +from datetime import datetime + from botbuilder.core import ( BotFrameworkAdapter, BotFrameworkAdapterSettings, - MessageFactory, TurnContext ) +from botbuilder.schema import Activity, ActivityTypes class AdapterWithErrorHandler(BotFrameworkAdapter): @@ -25,7 +27,20 @@ async def on_error(context: TurnContext, error: Exception): print(f"\n [on_turn_error]: {error}", file=sys.stderr) # Send a message to the user - error_message_text = "Sorry, it looks like something went wrong." - await context.send_activity(MessageFactory.text(error_message_text)) + 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) self.on_turn_error = on_error diff --git a/samples/02.echo-bot/app.py b/samples/02.echo-bot/app.py index 8cd106a64..b5b6351f3 100644 --- a/samples/02.echo-bot/app.py +++ b/samples/02.echo-bot/app.py @@ -1,9 +1,6 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -""" -This sample shows a simple Bot that echos messages back to the user. -""" import asyncio from flask import Flask, request, Response @@ -13,21 +10,22 @@ from bots import EchoBot from adapter_with_error_handler import AdapterWithErrorHandler +# 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 = AdapterWithErrorHandler(SETTINGS) BOT = EchoBot() - +# Listen for incoming requests on /api/messages.s @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: @@ -38,12 +36,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/02.echo-bot/config.py b/samples/02.echo-bot/config.py index c46adb612..e007d0fa9 100644 --- a/samples/02.echo-bot/config.py +++ b/samples/02.echo-bot/config.py @@ -7,7 +7,7 @@ """ Bot Configuration """ -class DefaultConfig(object): +class DefaultConfig: """ Bot Configuration """ PORT = 3978 From 12298de91cfc9fd1779404d91bf94ef68fb3b2b1 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 29 Oct 2019 12:22:37 -0500 Subject: [PATCH 4/5] Corrected 02.echo-bot README --- samples/02.echo-bot/README.md | 7 ------- 1 file changed, 7 deletions(-) diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index dec66759d..40e84f525 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -9,14 +9,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\45.state-management` folder - Bring up a terminal, navigate to `botbuilder-python\samples\02.echo-bot` folder - In the terminal, type `pip install -r requirements.txt` - In the terminal, type `python app.py` From b04cd18d15b933d868fe1eb5b3da3ec926ce0934 Mon Sep 17 00:00:00 2001 From: Tracy Boehrer Date: Tue, 29 Oct 2019 12:40:17 -0500 Subject: [PATCH 5/5] Removed adapter_with_error_handler.py (migrated to app.py) --- .../02.echo-bot/adapter_with_error_handler.py | 46 ------------------- samples/02.echo-bot/app.py | 38 +++++++++++++-- 2 files changed, 34 insertions(+), 50 deletions(-) delete mode 100644 samples/02.echo-bot/adapter_with_error_handler.py diff --git a/samples/02.echo-bot/adapter_with_error_handler.py b/samples/02.echo-bot/adapter_with_error_handler.py deleted file mode 100644 index 599326aca..000000000 --- a/samples/02.echo-bot/adapter_with_error_handler.py +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. - -import sys -from datetime import datetime - -from botbuilder.core import ( - BotFrameworkAdapter, - BotFrameworkAdapterSettings, - TurnContext -) -from botbuilder.schema import Activity, ActivityTypes - - -class AdapterWithErrorHandler(BotFrameworkAdapter): - def __init__( - self, - settings: BotFrameworkAdapterSettings - ): - super().__init__(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("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) - - self.on_turn_error = on_error diff --git a/samples/02.echo-bot/app.py b/samples/02.echo-bot/app.py index b5b6351f3..9762bafb9 100644 --- a/samples/02.echo-bot/app.py +++ b/samples/02.echo-bot/app.py @@ -2,13 +2,15 @@ # Licensed under the MIT License. import asyncio +import sys +from datetime import datetime +from types import MethodType from flask import Flask, request, Response -from botbuilder.core import BotFrameworkAdapterSettings -from botbuilder.schema import Activity +from botbuilder.core import BotFrameworkAdapterSettings, TurnContext, BotFrameworkAdapter +from botbuilder.schema import Activity, ActivityTypes from bots import EchoBot -from adapter_with_error_handler import AdapterWithErrorHandler # Create the loop and Flask app LOOP = asyncio.get_event_loop() @@ -18,8 +20,36 @@ # 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 = AdapterWithErrorHandler(SETTINGS) +ADAPTER = BotFrameworkAdapter(SETTINGS) + +# Catch-all for errors. +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] 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 = MethodType(on_error, ADAPTER) + +# Create the Bot BOT = EchoBot() # Listen for incoming requests on /api/messages.s