Skip to content

Commit f2958da

Browse files
tracyboehrerVirtual-Josh
authored andcommitted
Added 47.inspection (#381)
* Added 47.inspection, corrected README in 45.state-management * Changed the on_error function to be unbound for consistency.
1 parent 177b5a3 commit f2958da

File tree

9 files changed

+247
-7
lines changed

9 files changed

+247
-7
lines changed

samples/45.state-management/README.md

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,7 @@ The bot maintains user state to track the user's answers.
99
```bash
1010
git clone https://github.com/Microsoft/botbuilder-python.git
1111
```
12-
- Run `pip install -r requirements.txt` to install all dependencies
13-
- Run `python app.py`
14-
- 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`
15-
16-
17-
### Visual studio code
1812
- Activate your desired virtual environment
19-
- Open `botbuilder-python\samples\45.state-management` folder
2013
- Bring up a terminal, navigate to `botbuilder-python\samples\45.state-management` folder
2114
- In the terminal, type `pip install -r requirements.txt`
2215
- In the terminal, type `python app.py`

samples/47.inspection/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Inspection Bot
2+
3+
Bot Framework v4 Inspection Middleware sample.
4+
5+
This bot demonstrates a feature called Inspection. This feature allows the Bot Framework Emulator to debug traffic into and out of the bot in addition to looking at the current state of the bot. This is done by having this data sent to the emulator using trace messages.
6+
7+
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. Included in this sample are two counters maintained in User and Conversation state to demonstrate the ability to look at state.
8+
9+
This runtime behavior is achieved by simply adding a middleware to the Adapter. In this sample you can find that being done in `app.py`.
10+
11+
More details are available [here](https://github.com/microsoft/BotFramework-Emulator/blob/master/content/CHANNELS.md)
12+
13+
## Running the sample
14+
- Clone the repository
15+
```bash
16+
git clone https://github.com/Microsoft/botbuilder-python.git
17+
```
18+
- Bring up a terminal, navigate to `botbuilder-python\samples\47.inspection` folder
19+
- In the terminal, type `pip install -r requirements.txt`
20+
- In the terminal, type `python app.py`
21+
22+
## Testing the bot using Bot Framework Emulator
23+
[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.
24+
25+
- Install the Bot Framework Emulator version 4.5.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases)
26+
27+
### Connect to the bot using Bot Framework Emulator
28+
29+
- Launch Bot Framework Emulator
30+
- File -> Open Bot
31+
- Enter a Bot URL of `http://localhost:3978/api/messages`
32+
33+
### Special Instructions for Running Inspection
34+
35+
- In the emulator, select Debug -> Start Debugging.
36+
- Enter the endpoint url (http://localhost:8080)/api/messages, and select Connect.
37+
- The result is a trace activity which contains a statement that looks like /INSPECT attach < identifier >
38+
- Right click and copy that response.
39+
- In the original Live Chat session paste the value.
40+
- Now all the traffic will be replicated (as trace activities) to the Emulator Debug tab.
41+
42+
# Further reading
43+
44+
- [Getting started with the Bot Inspector](https://github.com/microsoft/BotFramework-Emulator/blob/master/content/CHANNELS.md)
45+
- [Azure Bot Service Introduction](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0)
46+
- [Bot State](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-storage-concept?view=azure-bot-service-4.0)

samples/47.inspection/app.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
import asyncio
5+
import sys
6+
from datetime import datetime
7+
from types import MethodType
8+
9+
from botbuilder.core.inspection import InspectionMiddleware, InspectionState
10+
from botframework.connector.auth import MicrosoftAppCredentials
11+
from flask import Flask, request, Response
12+
from botbuilder.core import (
13+
BotFrameworkAdapter,
14+
BotFrameworkAdapterSettings,
15+
ConversationState,
16+
MemoryStorage,
17+
TurnContext,
18+
UserState,
19+
)
20+
from botbuilder.schema import Activity, ActivityTypes
21+
22+
from bots import EchoBot
23+
24+
# Create the loop and Flask app
25+
LOOP = asyncio.get_event_loop()
26+
APP = Flask(__name__, instance_relative_config=True)
27+
APP.config.from_object("config.DefaultConfig")
28+
29+
# Create adapter.
30+
# See https://aka.ms/about-bot-adapter to learn more about how bots work.
31+
SETTINGS = BotFrameworkAdapterSettings(APP.config["APP_ID"], APP.config["APP_PASSWORD"])
32+
ADAPTER = BotFrameworkAdapter(SETTINGS)
33+
34+
35+
# Catch-all for errors.
36+
async def on_error(context: TurnContext, error: Exception):
37+
# This check writes out errors to console log .vs. app insights.
38+
# NOTE: In production environment, you should consider logging this to Azure
39+
# application insights.
40+
print(f"\n [on_turn_error] unhandled error: {error}", file=sys.stderr)
41+
42+
# Send a message to the user
43+
await context.send_activity("The bot encountered an error or bug.")
44+
await context.send_activity("To continue to run this bot, please fix the bot source code.")
45+
# Send a trace activity if we're talking to the Bot Framework Emulator
46+
if context.activity.channel_id == 'emulator':
47+
# Create a trace activity that contains the error object
48+
trace_activity = Activity(
49+
label="TurnError",
50+
name="on_turn_error Trace",
51+
timestamp=datetime.utcnow(),
52+
type=ActivityTypes.trace,
53+
value=f"{error}",
54+
value_type="https://www.botframework.com/schemas/error"
55+
)
56+
# Send a trace activity, which will be displayed in Bot Framework Emulator
57+
await context.send_activity(trace_activity)
58+
59+
# Clear out state
60+
await CONVERSATION_STATE.delete(context)
61+
62+
# Set the error handler on the Adapter.
63+
# In this case, we want an unbound method, so MethodType is not needed.
64+
ADAPTER.on_turn_error = on_error
65+
66+
# Create MemoryStorage and state
67+
MEMORY = MemoryStorage()
68+
USER_STATE = UserState(MEMORY)
69+
CONVERSATION_STATE = ConversationState(MEMORY)
70+
71+
# Create InspectionMiddleware
72+
INSPECTION_MIDDLEWARE = InspectionMiddleware(
73+
inspection_state=InspectionState(MEMORY),
74+
user_state=USER_STATE,
75+
conversation_state=CONVERSATION_STATE,
76+
credentials=MicrosoftAppCredentials(
77+
app_id=APP.config["APP_ID"],
78+
password=APP.config["APP_PASSWORD"]
79+
)
80+
)
81+
ADAPTER.use(INSPECTION_MIDDLEWARE)
82+
83+
# Create Bot
84+
BOT = EchoBot(CONVERSATION_STATE, USER_STATE)
85+
86+
87+
# Listen for incoming requests on /api/messages.
88+
@APP.route("/api/messages", methods=["POST"])
89+
def messages():
90+
# Main bot message handler.
91+
if "application/json" in request.headers["Content-Type"]:
92+
body = request.json
93+
else:
94+
return Response(status=415)
95+
96+
activity = Activity().deserialize(body)
97+
auth_header = (
98+
request.headers["Authorization"] if "Authorization" in request.headers else ""
99+
)
100+
101+
try:
102+
task = LOOP.create_task(
103+
ADAPTER.process_activity(activity, auth_header, BOT.on_turn)
104+
)
105+
LOOP.run_until_complete(task)
106+
return Response(status=201)
107+
except Exception as exception:
108+
raise exception
109+
110+
111+
if __name__ == "__main__":
112+
try:
113+
APP.run(debug=False, port=APP.config["PORT"]) # nosec debug
114+
except Exception as exception:
115+
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 .echo_bot import EchoBot
5+
6+
__all__ = ["EchoBot"]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
from botbuilder.core import ActivityHandler, ConversationState, TurnContext, UserState, MessageFactory
5+
from botbuilder.schema import ChannelAccount
6+
7+
from data_models import CustomState
8+
9+
10+
class EchoBot(ActivityHandler):
11+
def __init__(self, conversation_state: ConversationState, user_state: UserState):
12+
if conversation_state is None:
13+
raise TypeError(
14+
"[EchoBot]: Missing parameter. conversation_state is required but None was given"
15+
)
16+
if user_state is None:
17+
raise TypeError(
18+
"[EchoBot]: Missing parameter. user_state is required but None was given"
19+
)
20+
21+
self.conversation_state = conversation_state
22+
self.user_state = user_state
23+
24+
self.conversation_state_accessor = self.conversation_state.create_property("CustomState")
25+
self.user_state_accessor = self.user_state.create_property("CustomState")
26+
27+
async def on_turn(self, turn_context: TurnContext):
28+
await super().on_turn(turn_context)
29+
30+
await self.conversation_state.save_changes(turn_context)
31+
await self.user_state.save_changes(turn_context)
32+
33+
async def on_members_added_activity(self, members_added: [ChannelAccount], turn_context: TurnContext):
34+
for member in members_added:
35+
if member.id != turn_context.activity.recipient.id:
36+
await turn_context.send_activity("Hello and welcome!")
37+
38+
async def on_message_activity(self, turn_context: TurnContext):
39+
# Get the state properties from the turn context.
40+
user_data = await self.user_state_accessor.get(turn_context, CustomState)
41+
conversation_data = await self.conversation_state_accessor.get(turn_context, CustomState)
42+
43+
await turn_context.send_activity(MessageFactory.text(
44+
f"Echo: {turn_context.activity.text}, "
45+
f"conversation state: {conversation_data.value}, "
46+
f"user state: {user_data.value}"))
47+
48+
user_data.value = user_data.value + 1
49+
conversation_data.value = conversation_data.value + 1
50+

samples/47.inspection/config.py

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: 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 .custom_state import CustomState
5+
6+
__all__ = ["CustomState"]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Copyright (c) Microsoft Corporation. All rights reserved.
2+
# Licensed under the MIT License.
3+
4+
5+
class CustomState:
6+
def __init__(self, value: int = 0):
7+
self.value = value
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

0 commit comments

Comments
 (0)