-
Notifications
You must be signed in to change notification settings - Fork 306
Description
Version
What package version of the SDK are you using.
I am using version 4.16.2 of the packages botbuilder-core, botbuilder-integration-aiohttp, botbuilder-schema, botframework-connector, and botframework-streaming.
Describe the bug
I am attempting to simultaneously do two things:
- Send the user a "Bot is typing" activity.
- Open a connection to OpenAI, send them the prompt and current conversation state, and allow them to start processing the user's request.
When using TurnContext.send_activity(), it is mostly implemented using an async http library. However, in the circumstance where it is attempting to authenticate for the first time, it uses the requests library to fetch the OIDC configuration and keys. Specifically, it fetches https://login.microsoftonline.com/botframework.com/v2.0/.well-known/openid-configuration and then https://login.microsoftonline.com/<uuid>/oauth2/v2.0/token.
Here is the specific code within botframework-connector that is at issue:
async def _refresh(self):
response = requests.get(self.url)
response.raise_for_status()
keys_url = response.json()["jwks_uri"]
response_keys = requests.get(keys_url)
response_keys.raise_for_status()
self.last_updated = datetime.now()
self.keys = response_keys.json()["keys"]
This fetch can take hundreds of milliseconds, so it would be nice if it allowed other coroutines to run while waiting for the HTTP request to come back. The current behavior means that sometimes TurnContext.send_activity will use async IO, and sometimes use sync IO, and there is no way to predict which one.
To Reproduce
I am attempting to achieve this parallelism by using asyncio.gather, like this:
import asyncio
from botbuilder.core.teams import TeamsActivityHandler
from botbuilder.schema import Activity, ActivityTypes
async def get_message_from_ai(message: str):
# Get past messages in this conversation, open connection to OpenAI, etc.
pass
class BotHandler(TeamsActivityHandler):
async def on_message_activity(self, turn_context: TurnContext) -> None:
# ... snipped for clarity ...
# Send a typing indicator to Teams
typing_activity = Activity(type=ActivityTypes.typing)
typing_activity_coroutine = turn_context.send_activity(typing_activity)
# Get AI reply
response_data_coroutine = get_message_from_ai(message)
# Wait for typing activity and AI reply to finish
(_, response_data) = await asyncio.gather(typing_activity_coroutine, response_data_coroutine)
This works if the OpenID key has already been requested, but it requires synchronous IO if not.
Expected behavior
TurnContext.send_activity() should always use async IO.
Screenshots
N/A
Additional context
We are using a Django-based bot, using the guide here to integrate it with the rest of Django.