Adding a conversational UI to “Zonebot” with the Microsoft bot framework
Following on from his introduction of Zonebot to the world, our head of .net Andy Butland has decided it’s time to take his creation to the next level…
I recently blogged about the development of “Zonebot” — a chat-bot built using the Microsoft Bot Framework for internal agency use within Slack. In this article I’m going to discuss some further enhancements I have made to it to allow for the collection of information by the bot via a conversational interface.
There’s also a little side-story about what’s likely to be rule number one in software development — always talk to your users — that I’ll come back to at the end…
The features already built and discussed previously involved the detection of various requests and subjects (known as “intents” and “entities”) via an analysis of natural language to generate appropriate responses. Specifically we linked up with Luis for its language parsing capabilities and the API of Timezone, our internal agency system used for various processes such as scheduling and timesheeting. You could ask questions like “What is Ali Taheri working on this week?” and it would query the schedule to give you an answer.
As enhancements we’ve provided for some means of a conversation by having the bot remember details of the previous statement from the user. So if you followed up that question with “What about Rob Purcell?” it would know you were still talking about schedules and respond accordingly. That was limited to a single step of conversation though and required some custom coding to implement.
The next feature I wanted to look at was to provide a prompted means of collecting a set of related information about a given request, much like the pizza bot demonstrated when the Microsoft bot framework was launched. The task I picked was room booking. Like most companies meeting rooms are often at a premium so a means of requesting one via Slack seemed a good idea. It was also something real-world yet not too complicated in order to act as a proof of concept for this type of user interface.
With the bot framework the method used for creating these types of information collection conversations is known as FormFlow. Rather than coding up each and every question and answer, it’s possible to define a class with various attributes that the bot builder will render in step format to collect the information and then pass off to a function to process the request once it’s complete.
This class for room booking looks as follows and key points will be described below the code listing:
The first thing to note is that the options for the various selections are defined as enums, with `[Description]` attributes to provide the more friendly names the user will see. Enums worked just fine for my purposes as all the options were known at build time, however it’s also possible to implement these as dynamic fields which are pulled from a data source at runtime.
You then define a serializable class with properties for each of the pieces of information that need to be requested. These may be numbers, dates, strings or single or multiple values from one of the enum types defined for the lists of options. Finally there’s a BuildForm() method that puts together the interface in the correct order and allows for other customisations like a confirmation message (though I have to note, I couldn’t get the confirmation message to appear).
If working with a bot that only carries out this function there’s not much more that needs to be done. I had an additional complication here though as it needed to be part of a larger bot, integrated with the same one that provided the answers to the scheduling questions etc.
This was set up by training the Luis model as described in the previous post for another intent for “room booking”. With that in place a new method could be called when requests of that nature were detected:
This builds the form for the room booking, calls it so for the user it forms an extension of the existing conversation and defines a callback function that is processed when all the information is collected.
One nice point to note here is that you can pass in the list of entities detected in the last request from the user, and if matched with fields in the form, they will be pre-selected and not requested again from the user. This manifests itself in that if the request is “Book a meeting room” it will ask me all the questions, the first one being the office location. On the other hand if “Book a meeting room in Bristol” is requested, and “Bristol” is identified as a valid office location, that question no longer needs to be asked, and won’t be.
The callback function looks like this and again I’ll describe key points below:
Firstly we have to extract the form results as an instance of RoomBookingDialog.RoomBookingRequest. There’s a try/catch around this, partly to handle cancelled bookings (if you type “quit” at any point the request in process will be exited), and partly to work around an issue I was having with the framework.
The next step is to extract who the message has come from. This is available via the instance of IMessageActivity passed in when a message is received, but it doesn’t look to be accessible from this callback function. To get around that I saved the last message from a user into the bot conversation data and then could extract it here in the callback function. Like this:
Finally I then call out to a couple of service components. The first queries our Timezone API to extract the details of the user from their Slack user name (which is stored as a user profile field). It then calls off to a second component responsible for booking the room for the user (which just sends an email to the person responsible). I’ve not show the code for these as they are quite specific to this particular scenario.
And with that we can create a conversational UI that looks like this (in the emulator):
To hook up with Slack it’s just necessary to register it as before, though important to follow the steps to set up “interactive buttons” on Slack (enable the option and provide a verification token). And then in there, it looks like this:
So that all worked quite nicely — however I do need to come back and confess on the “always talk to your users” point above. Just before releasing this I spoke to the reception staff who book rooms, just to check the questions I was asking were the right ones. And their answer? They are, but actually they didn’t book rooms anymore, everyone did it via their Outlook calendars. “Hang on though, if I need a room I just send you an email”, I said. “Yes we know, we just help you out by booking it for you!”. Lesson learned (well, reminded anyway) — don’t get too excited about the tech, check there’s a real problem to solve first!
So, given that, it’s likely that this new Zonebot feature isn’t going to see too much real-life use. Never mind though, was a fun project and was good to focus on a real-world scenario develop what at least is a proof of concept for similar features.