Pages

Sunday, March 10, 2019

Building Proactive Messaging Bot using Bot framework SDK v4

I have been working with recently released bot framework from last few weeks and one of the interesting requirements I came across was to build a proactive bot which can send messages to users on its own.

Now this sounds like a very straight forward and quite common requirement as possibility of having this capability in bots opens a gateway to number of opportunities in an enterprise environment e.g. sharing live information analytics to power users, sharing caution / warnings to system administrators based on past data etc. however there is a serious limited documentation available on achieving these real-world scenarios specifically with Bot framework SDK v4.

In this post, I am going to share the experience I had while developing a proactive bot and challenges which I faced, approach I took by referring several links and will also share what I learned.

Hitting the road block


To start with, at the time of writing this article - the only documentation and an official sample available from Microsoft is as mentioned below. Please go through these links and understand the essentials involved in getting a proactive bot design working.

Coming back to the original requirement we had which was inline with what MSDN documentation says i.e.
"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%"
But the big question was - how exactly do you do that?

Understanding basics


Now to understand this, lets get into the technical details and understand what it essentially means to send a proactive message to the end user from bot perspective.

Proactive conversation is nothing but the event or a message that bot sends to the user and there is one fundamental need here that the user should have at least initiated a conversation with the bot first else it does not make sense that users starts receiving messages from bot which they do not know! 

Now coming to the next point – as we know that overall bot framework is built around the idea of web api and is stateless, hence all the conversations or dialogs or messages which we send to the bot are essentially listened by one web api endpoint (if you have noticed, there is always this default route and an endpoint https://localhost:1234/api/messages) – whenever this api receives any message – it is then processed and response is sent back to the user via specific channel. (channel is just a medium using which user is talking to the bot e.g. teams, direct line or skype). 

The requirement which I was working with needed some trigger from external system to the bot endpoint so that bot can process the incoming signal and then send out a conversation to a specific user.

Let’s understand this using a quick diagram


Note that - to send the conversation proactively to the user – the bot MUST know who the user is and what is his / her conversation reference otherwise it won’t be able to initiate the conversation with the user. 

Approach


Content below is heavily inspired by this github thread and a big shout out to all the contributors on that thread – you guys are awesome!

Bringing it all together:



These are the basic steps we integrated to get the proactive messaging working
  • Record conversation reference of each user / desired users
  • Used Azure table storage to store these records 
  • Introduce api to fetch stored record from storage and generate ProactiveMessage
  • Introduce / Integrate ProactiveController from this post
  • Trigger post call to ProactiveController’s method with payload generated from step 3 and send a message from bot to a specified user
Let’s see each step-in detail

Step 1 and 2:


Because the pre-requisite is bot should know the conversation reference before it can send the message by its own, it becomes essential to have the conversation references stored somewhere.

Since we do not really need a relational data structure here, so we can store it azure table storage, but you can choose to store in relational database table too. 

How do we store?

We can either write a custom middleware and detect if the entry for the user exists in the underlying storage, if it does not – meaning it is a new conversation and we record it. one big problem with this approach could be – since it is a middleware – it would be invoked every time for each message and might impact performance. 

Thankfully, in our bot since we have integrated Azure AD authentication – we knew exact place where we can write this storing logic i.e. whenever user gets authenticated. 

This is how my UserEntity look which I am storing in the table storage


Instance of this class needs to be initiated from the incoming first user request which you would find in the OnTurnAsync method


Step 3:


Because we have now stored the user entity (conversation reference) in the table storage – we will now need the api which can query the storage based on inputs provided.

E.g. if I am storing user name in FromId property, then I would need the api to generate Activity Payload for that specific user so that we can send it further to the ProactiveController which takes care of beginning the proactive conversation with that user.

I have added a controller in the same bot project which returns the collection of ProactiveMessages
Here my definition of ProactiveMessage


And ActivityGeneratorController in short


And here is the sample output it generates


IMPORTANT NOTE:
This took a while for me to understand and hence sharing. The botId and needs to be the Microsoft App Id of your bot and then only it works.

Step 4 and 5:


Finally, you should integrate the ProacticeController in your bot project. 
I am not going to share that source code here again because there is already a git project for it.

Validation:


Once this set up is completed, and if you have managed to generate the activity payload correctly by recording correct user conversation references, you can try sending the HttpPost request to the ProactiveController externally from any request generator like Postman and you should see the proactive message with your text appear in conversation of the user to which it was sent. Note that in above Json example, the message with text “Test Message “will appear in user’s team channel.

Update


The approach mentioned above stopped working in specific scenarios e.g. when the user to which message needs to be sent becomes idle on channel then proactive messaging started throwing exception below

Microsoft.Bot.Schema.ErrorResponseException: Operation returned an invalid status code 'Unauthorized'
   at Microsoft.Bot.Connector.Conversations.ReplyToActivityWithHttpMessagesAsync(String conversationId, String activityId, Activity activity, Dictionary`2 customHeaders, CancellationToken cancellationToken)
   at Microsoft.Bot.Connector.ConversationsExtensions.ReplyToActivityAsync(IConversations operations, String conversationId, String activityId, Activity activity, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.BotFrameworkAdapter.SendActivitiesAsync(ITurnContext turnContext, Activity[] activities, CancellationToken cancellationToken)
   at Microsoft.Bot.Builder.TurnContext.<>c__DisplayClass22_0.<<SendActivitiesAsync>g__SendActivitiesThroughAdapter|1>d.MoveNext()

and this puzzled us, again there is a lack of documentation and information is scattered around different links and blogs. We came across one helpful link which gave some hints and then we had to make changes in out ProactiveMessagingController logic which now looks like this



Note that we are now creating channel account and initialing an instance of MicrosoftAppCredentials with bot's app Id and secret (typically you would find this in .bot file in your solution, if not you can always get it from bot registration portal) and then with the help of ConnectorClient we are not beginning conversations with specific user.

Hope this helps someone looking to build the proactive messaging using bot framework SDK v4 and with this one can trigger targeted proactive messages externally to specific user with notifying them with relevant message.

No comments:

Post a Comment