8000 Merge branch 'master' into trboehre-43.complex-dialog · guidotorresmx/botbuilder-python@50950d1 · GitHub
[go: up one dir, main page]

Skip to content

Commit 50950d1

Browse files
authored
Merge branch 'master' into trboehre-43.complex-dialog
2 parents 7e8104a + e3708f9 commit 50950d1

File tree

23 files changed

+747
-15
lines changed

23 files changed

+747
-15
lines changed

samples/05.multi-turn-prompt/app.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import asyncio
55
import sys
66
from datetime import datetime
7-
from types import MethodType
87

98
from flask import Flask, request, Response
109
from botbuilder.core import (
@@ -59,7 +58,9 @@ async def on_error(context: TurnContext, error: Exception):
5958
# Clear out state
6059
await CONVERSATION_STATE.delete(context)
6160

62-
ADAPTER.on_turn_error = MethodType(on_error, ADAPTER)
61+
# Set the error handler on the Adapter.
62+
# In this case, we want an unbound method, so MethodType is not needed.
63+
ADAPTER.on_turn_error = on_error
6364

6465
# Create MemoryStorage, UserState and ConversationState
6566
MEMORY = MemoryStorage()

samples/06.using-cards/app.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
import asyncio
88
import sys
99
from datetime import datetime
10-
from types import MethodType
1110

1211
from flask import Flask, request, Response
1312
from botbuilder.core import (
@@ -35,7 +34,7 @@
3534

3635

3736
# Catch-all for errors.
38-
async def on_error(self, context: TurnContext, error: Exception):
37+
async def on_error(context: TurnContext, error: Exception):
3938
# This check writes out errors to console log .vs. app insights.
4039
# NOTE: In production environment, you should consider logging this to Azure
4140
# application insights.
@@ -61,7 +60,9 @@ async def on_error(self, context: TurnContext, error: Exception):
6160
# Clear out state
6261
await CONVERSATION_STATE.delete(context)
6362

64-
ADAPTER.on_turn_error = MethodType(on_error, ADAPTER)
63+
# Set the error handler on the Adapter.
64+
# In this case, we want an unbound method, so MethodType is not needed.
65+
ADAPTER.on_turn_error = on_error
6566

6667
# Create MemoryStorage, UserState and ConversationState
6768
MEMORY = MemoryStorage()
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Proactive messages
2+
3+
Bot Framework v4 proactive messages bot sample
4+
5+
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to send proactive messages to users by capturing a conversation reference, then using it later to initialize outbound messages.
6+
7+
## Concepts introduced in this sample
8+
9+
Typically, each message that a bot sends to the user directly relates to the user's prior input. In some cases, a bot may need to send the user a message that is not directly related to the current topic of conversation. These types of messages are called proactive messages.
10+
11+
Proactive messages can be useful in a variety of scenarios. If a bot sets a timer or reminder, it will need to notify the user when the time arrives. Or, if a bot receives a notification from an external system, it may need to communicate that information to the user immediately. For example, if the user has previously asked the bot to monitor the price of a product, the bot can alert the user if the price of the product has dropped by 20%. Or, if a bot requires some time to compile a response to the user's question, it may inform the user of the delay and allow the conversation to continue in the meantime. When the bot finishes compiling the response to the question, it will share that information with the user.
12+
13+
This project has a notify endpoint that will trigger the proactive messages to be sent to
14+
all users who have previously messaged the bot.
15+
16+
## Running the sample
17+
- Clone the repository
18+
```bash
19+
git clone https://github.com/Microsoft/botbuilder-python.git
20+
```
21+
- Activate your desired virtual environment
22+
- Bring up a terminal, navigate to `botbuilder-python\samples\16.proactive-messages` folder
23+
- In the terminal, type `pip install -r requirements.txt`
24+
- In the terminal, type `python app.py`
25+
26+
## Testing the bot using Bot Framework Emulator
27+
[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.
28+
29+
- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
30+
31+
### Connect to bot using Bot Framework Emulator
32+
- Launch Bot Framework Emulator
33+
- File -> Open Bot
34+
- Paste this URL in the emulator window - http://localhost:3978/api/messages
35+
36+
With the B 10000 ot Framework Emulator connected to your running bot, the sample will now respond to an HTTP GET that will trigger a proactive message. The proactive message can be triggered from the command line using `curl` or similar tooling, or can be triggered by opening a browser windows and nagivating to `http://localhost:3978/api/notify`.
37+
38+
### Using curl
39+
40+
- Send a get request to `http://localhost:3978/api/notify` to proactively message users from the bot.
41+
42+
```bash
43+
curl get http://localhost:3978/api/notify
44+
```
45+
46+
- Using the Bot Framework Emulator, notice a message was proactively sent to the user from the bot.
47+
48+
### Using the Browser
49+
50+
- Launch a web browser
51+
- Navigate to `http://localhost:3978/api/notify`
52+
- Using the Bot Framework Emulator, notice a message was proactively sent to the user from the bot.
53+
54+
## Proactive Messages
55+
56+
In addition to responding to incoming messages, bots are frequently called on to send "proactive" messages based on activity, scheduled tasks, or external events.
57+
58+
In order to send a proactive message using Bot Framework, the bot must first capture a conversation reference from an incoming message using `TurnContext.get_conversation_reference()`. This reference can be stored for later use.
59+
60+
To send proactive messages, acquire a conversation reference, then use `adapter.continue_conversation()` to create a TurnContext object that will allow the bot to deliver the new outgoing message.
61+
62+
## Further reading
63+
64+
- [Bot Framework Documentation](https://docs.botframework.com)
65+
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
66+
- [Send proactive messages](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-proactive-message?view=azure-bot-service-4.0&tabs=js)

samples/16.proactive-messages/app.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import asyncio
5+
import sys
6+
import uuid
7+
from datetime import datetime
8+
from types import MethodType
9+
from typing import Dict
10+
11+
from flask import Flask, request, Response
12+
from botbuilder.core import BotFrameworkAdapterSettings, TurnContext, BotFrameworkAdapter
13+
from botbuilder.schema import Activity, ActivityTypes, ConversationReference
14+
15+
from bots import ProactiveBot
16+
17+
# Create the loop and Flask app
18+
LOOP = asyncio.get_event_loop()
19+
APP = Flask(__name__, instance_relative_config=True)
20+
APP.config.from_object("config.DefaultConfig")
21+
22+
# Create adapter.
23+
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
24+
SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"])
25+
ADAPTER = BotFrameworkAdapter(SETTINGS)
26+
27+
28+
# Catch-all for errors.
29+
async def on_error(self, context: TurnContext, error: Exception):
30+
# This check writes out errors to console log .vs. app insights.
31+
# NOTE: In production environment, you should consider logging this to Azure
32+
# application insights.
33+
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
34+
35+
# Send a message to the user
36+
await context.send_activity("The bot encountered an error or bug.")
37+
await context.send_activity("To continue to run this bot, please fix the bot source code.")
38+
# Send a trace activity if we're talking to the Bot Framework Emulator
39+
if context.activity.channel_id == 'emulator':
40+
# Create a trace activity that contains the error object
41+
trace_activity = Activity(
42+
label="TurnError",
43+
name="on_turn_error Trace",
44+
timestamp=datetime.utcnow(),
45+
type=ActivityTypes.trace,
46+
value=f"{error}",
47+
value_type="https://www.botframework.com/schemas/error"
48+
)
49+
# Send a trace activity, which will be displayed in Bot Framework Emulator
50+
await context.send_activity(trace_activity)
51+
52+
ADAPTER.on_turn_error = MethodType(on_error, ADAPTER)
53+
54+
# Create a shared dictionary. The Bot will add conversation references when users
55+
# join the conversation and send messages.
56+
CONVERSATION_REFERENCES: Dict[str, ConversationReference] = dict()
57+
58+
# If the channel is the Emulator, and authentication is not in use, the AppId will be null.
59+
# We generate a random AppId for this case only. This is not required for production, since
60+
# the AppId will have a value.
61+
APP_ID = SETTINGS.app_id if SETTINGS.app_id else uuid.uuid4()
62+
63+
# Create the Bot
64+
BOT = ProactiveBot(CONVERSATION_REFERENCES)
65+
66+
# Listen for incoming requests on /api/messages.
67+
@APP.route("/api/messages", methods=["POST"])
68+
def messages():
69+
# Main bot message handler.
70+
if "application/json" in request.headers["Content-Type"]:
71+
body = request.json
72+
else:
73+
return Response(status=415)
74+
75+
activity = Activity().deserialize(body)
76+
auth_header = (
77+
request.headers["Authorization"] if "Authorization" in request.headers else ""
78+
)
79+
80+
try:
81+
task = LOOP.create_task(
82+
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
83+
)
84+
LOOP.run_until_complete(task)
85+
return Response(status=201)
86+
except Exception as exception:
87+
raise exception
88+
89+
90+
# Listen for requests on /api/notify, and send a messages to all conversation members.
91+
@APP.route("/api/notify")
92+
def notify():
93+
try:
94+
task = LOOP.create_task(
95+
_send_proactive_message()
96+
)
97+
LOOP.run_until_complete(task)
98+
99+
return Response(status=201, response="Proactive messages have been sent")
100+
except Exception as exception:
101+
raise exception
102+
103+
104+
# Send a message to all conversation members.
105+
# This uses the shared Dictionary that the Bot adds conversation references to.
106+
async def _send_proactive_message():
107+
for conversation_reference in CONVERSATION_REFERENCES.values():
108+
return await ADAPTER.continue_conversation(
109+
APP_ID,
110+
conversation_reference,
111+
lambda turn_context: turn_context.send_activity("proactive hello")
112+
)
113+
114+
115+
if __name__ == "__main__":
116+
try:
117+
APP.run(debug=False, port=APP.config["PORT"]) # nosec debug
118+
except Exception as exception:
119+
raise exception
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from .proactive_bot import ProactiveBot
5+
6+
__all__ = ["ProactiveBot"]
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from typing import Dict
5+
6+
from botbuilder.core import ActivityHandler, TurnContext
7+
from botbuilder.schema import ChannelAccount, ConversationReference, Activity
8+
9+
10+
class ProactiveBot(ActivityHandler):
11+
def __init__(
12+
self, conversation_references: Dict[str, ConversationReference]
13+
):
14+
self.conversation_references = conversation_references
15+
16+
async def on_conversation_update_activity(self, turn_context: TurnContext):
17+
self._add_conversation_reference(turn_context.activity)
18+
return await super().on_conversation_update_activity(turn_context)
19+
20+
async def on_members_added_activity(
21+
self, members_added: [ChannelAccount], turn_context: TurnContext
22+
):
23+
for member in members_added:
24+
if member.id != turn_context.activity.recipient.id:
25+
await turn_context.send_activity("Welcome to the Proactive Bot sample. Navigate to "
26+
"http://localhost:3978/api/notify to proactively message everyone "
27+
"who has previously messaged this bot.")
28+
29+
async def on_message_activity(self, turn_context: TurnContext):
30+
self._add_conversation_reference(turn_context.activity)
31+
return await turn_context.send_activity(f"You sent: {turn_context.activity.text}")
32+
33+
def _add_conversation_reference(self, activity: Activity):
34+
"""
35+
This populates the shared Dictionary that holds conversation references. In this sample,
36+
this dictionary is used to send a message to members when /api/notify is hit.
37+
:param activity:
38+
:return:
39+
"""
40+
conversation_reference = TurnContext.get_conversation_reference(activity)
41+
self.conversation_references[conversation_reference.user.id] = conversation_reference
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env python3
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License.
4+
5+
import os
6+
7+
""" Bot Configuration """
8+
9+
10+
class DefaultConfig:
11+
""" Bot Configuration """
12+
13+
PORT = 3978
14+
APP_ID = os.environ.get("MicrosoftAppId", "")
15+
APP_PASSWORD = os.environ.get("MicrosoftAppPassword", "")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
botbuilder-core>=4.4.0b1
2+
flask>=1.0.3

samples/19.custom-dialogs/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Custom Dialogs
2+
3+
Bot Framework v4 custom dialogs bot sample
4+
5+
This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to sub-class the `Dialog` class to create different bot control mechanism like simple slot filling.
6+
7+
BotFramework provides a built-in base class called `Dialog`. By subclassing `Dialog`, developers can create new ways to define and control dialog flows used by the bot.
8+
9+
## Running the sample
10+
- Clone the repository
11+
```bash
12+
git clone https://github.com/Microsoft/botbuilder-python.git
13+
```
14+
- Activate your desired virtual environment
15+
- Bring up a terminal, navigate to `botbuilder-python\samples\19.custom-dialogs` folder
16+
- In the terminal, type `pip install -r requirements.txt`
17+
- In the terminal, type `python app.py`
18+
19+
## Testing the bot using Bot Framework Emulator
20+
[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.
21+
22+
- Install the Bot Framework emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
23+
24+
### Connect to bot using Bot Framework Emulator
25+
- Launch Bot Framework Emulator
26+
- File -> Open Bot
27+
- Paste this URL in the emulator window - http://localhost:3978/api/messages
28+
29+
## Custom Dialogs
30+
31+
BotFramework provides a built-in base class called `Dialog`. By subclassing Dialog, developers
32+
can create new ways to define and control dialog flows used by the bot. By adhering to the
33+
features of this class, developers will create custom dialogs that can be used side-by-side
34+
with other dialog types, as well as built-in or custom prompts.
35+
36+
This example demonstrates a custom Dialog class called `SlotFillingDialog`, which takes a
37+
series of "slots" which define a value the bot needs to collect from the user, as well
38+
as the prompt it should use. The bot will iterate through all of the slots until they are
39+
all full, at which point the dialog completes.
40+
41+
# Further reading
42+
43+
- [Bot Framework Documentation](https://docs.botframework.com)
44+
- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0)
45+
- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0)
46+
- [Dialog class reference](https://docs.microsoft.com/en-us/javascript/api/botbuilder-dialogs/dialog)
47+
- [Manage complex conversation flows with dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0)
48+
- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0)

0 commit comments

Comments
 (0)
0