Appointment Bot

Appointment Bot Tutorial

This tutorial will walk you through the design and build process of a Bot that allows the user to manage appointments.

Third-party components

To begin with, let’s look at some parts of the Bot experience that will need to be handled outside of the Bot itself, i.e. the third-party components. These are:

  • Google Calendar
  • AWS Lambda
  • DialogFlow

To invoke the AWS Lambda function we will create a BaaS connector of type aws-sdk.

The Bot will use DialogFlow for intent detection. This tutorial assumes that a DialogFlow project with a trained Agent is available for you to use.

The DialogFlow agent should have the following intents: - WhenMyAppointment - MakeAppointment - AvailableSlots

AWS Lambda will be invoked with a JSON payload, which will contain the intent name and supplementary data needed to complete that intent.

This is a simplified flow of the Bot experience:

  1. User inputs an utterance
  2. IntentDetection node detects the user’s intent from the utterance
  3. BaaS node invokes the AWS Lambda function with an event (e.g. “make a new appointment”)
  4. AWS Lambda processes the event and returns the result via the BaaS node

Calendar Lambda

This function will consist of a main file with functions handling different intents and two supporting utilities - date and calendar.

AppointmentBotFunction
| calendar
| | index.js
|
| date
| | index.js
|
| app.js
| package.json
 app.js
 1|  
 2|  ...
 3|  
 4|  const WhenMyAppointment = async (currentIntent, googleCalendar) => {
 5|   ...
 6|   return fulfilledResponse;
 7| }
 8|
 9|  const MakeAppointment = async (currentIntent, googleCalendar) => {
10|   ...
11|   return response;
12| }
13|
14| const AvailableSlots = async (currentIntent, googleCalendar) => {
15|   ...
16|   return events;
17| }
18|
19| exports.handler = async (event) => {
20|   const googleCalendar = new GCalendar(google);
21|   const currentIntent = event.currentIntent || event;
22|
23|   switch(currentIntent.name){
24|     case 'WhenMyAppointment':
25|       return WhenMyAppointment(currentIntent, googleCalendar);
26|     case 'MakeAppointment':
27|       return MakeAppointment(currentIntent, googleCalendar);
28|     case 'AvailableSlots':
29|       return AvailableSlots(currentIntent, googleCalendar)
30|   }
31| };

The code above will run different functions based on the payload’s name property (WhenMyAppointment, MakeAppointment, or AvailableSlots).

Deployment

In your AWS account create a new AWS Lambda Function using the code provided above. Use test events in Lambda console to verify behavior.
Note the function’s name as it will be required for the BaaS.

BaaS

Before you can make a working BaaS you need to create a secret with credentials to invoke your AWS Lambda function.

  • Using the ServisBot CLI run sb-cli secret create-interactive .
  • Select 'aws-cross-account-role'.
  • Follow instructions and make sure to assign an IAM policy that allow access to the AWS Lambda you created before
  • Once finished, the CLI will print the SRN (ServisBot Resource Name) of the new Secret
  • Create the BaaS JSON file as below json { "Method": "invoke", "Endpoint": "Lambda", "Alias": "MakeAppointmentLambda", "RequestMapping": { "payload": { "type": "requestBody", "requestBodyPath": "$.Payload", "inputPath": "$.payload" } }, "ResponseMapping": {}, "Type": "aws-sdk", "Body": { "FunctionName": "Your function name here" }, "Region": "eu-west-1", "Credentials": "<secret SRN here>, }

make sure to add the SRN of the Secret you created in the Credentials field.

Test the BaaS

To make sure that your BaaS connector is working as expected, create the following file:

MakeAppointmentLambda.AvailableSlots.exec.json
{
  "Alias": "MakeAppointmentLambda",
  "payload": "{ \"name\": \"AvailableSlots\", \"date\": \"Wed, 26 Jun 2019 14:01:33 GMT\"}"
}

Execute the file above using the sb-cli baas execute command.

Run
sb-cli baas execute MakeAppointmentLambda.AvailableSlots.exec.json. If everything worked correctly you should see the following output:

Starting Execute BAAS API command
{
  "response": {
    "StatusCode": 200,
    "ExecutedVersion": "$LATEST",
    "Payload": "[{\"time\":\"9:00AM\"},{\"time\":\"10:00AM\"},{\"time\":\"11:00AM\"},{\"time\":\"12:00PM\"},{\"time\":\"1:00PM\"},{\"time\":\"2:00PM\"},{\"time\":\"3:00PM\"},{\"time\":\"4:00PM\"},{\"time\":\"5:00PM\"},{\"time\":\"6:00PM\"}]"
  },
  "statusCode": 200,
  "headers": {}
}
Finished Execute BAAS API command

Sample exec files for the other two supported intents:

MakeAppointmentLambda.MakeAppointment.exec.json
{
  "Alias": "MakeAppointmentLambda",
  "payload": "{\"name\":\"MakeAppointment\",\"slots\":{\"Date\":\"Thu Jun 20 2019 00:00:00 GMT+0100 (Irish Standard Time)\",\"Email\":\"tutorial@expert.com\",\"Time\":\"12:00PM\"}}"
}
MakeAppointmentLambda.WhenMyAppointment.exec.json
{
  "Alias": "MakeAppointmentLambda",
  "payload": "{\"name\":\"WhenMyAppointment\",\"slots\":{\"Email\":\"tutorial@expert.com\"}}"
}

Now that we have the third-party components set up, let’s focus on creating our Bot.

The Bot

To create the Bot, log into your account on https://portal.servisbot.com and select Create New Bot in the Bot Army section. Select the Classic Flow Bot blueprint, and give it a memorable name.

Navigate to your Bot in the Bot Army section. Click on the Test button and a new tab with the ServisBot Messenger will open up. This is how you can test your Bot.

Go back to the Bot page, and click on the green Designer button - you will be presented with a flow diagram representing your Bot’s main worker.

Double-click on the leftmost Dialogue node, note value in the Message box.

Go back to the Messenger tab in your browser and note the Bot’s first message.

Hello World example

Feel free to skip this step if you have built a Bot before. Otherwise, follow these steps:
1. Switch back to the flow tab, remove all nodes 2. Select and delete all nodes 3. Add Start node 4. Add Dialogue node and connect it to the Start node. 5. Edit message in the Dialogue node. 6. Switch back to the bot messenger. 7. Click reset and wait for your message.

Detect Intent node debug

This step will teach you a quick and helpful debugging technique. 1. Add and connect nodes in this order Start > Dialogue > Input > Intent Detection > Dialogue 2. First dialogue should have a message ‘hi’, mainly so you know when the bot started and is expecting input. 3. Configure Intent Detection node with your Secret and Project Id 4. Second Dialogue node should have a message msg.payload.result

Now open your bot in the messenger and input ‘book an appointment’ you should see a similar response. asd

Using dialogue node to print contents of msg.* is very helpful and often used during Bot development.

BaaS Node

Add BaaS Node, from the dropdown select alias of your BaaS endpoint created in BaaS section, context part should be set in relation to BaaS definition:

  "RequestMapping": {
    "payload": {
      "type": "requestBody",
      "requestBodyPath": "$.Payload",
      "inputPath": "$.payload"
    }
  },

The BaaS is expecting to have payload object, as you recall from BaaS Exec section - value of payload is stringified JSON object, you will use Function node to prepare the payload.

  1. Add Change node after Intent detection configure it to: Set msg.payload.intentName to payload.result.intent. This will make current intent available at msg.payload.intentName
  2. Add Function node between BaaS node and Change node. (you can remove second Dialogue) node.
  3. Insert this code js msg.payload.lambdaPayload = JSON.stringify({ "name": msg.payload.intentName, "slots": { "Date": "Thu Jun 20 2019 00:00:00 GMT+0100 (Irish Standard Time)", "Email": "some@email.com", "Time": "12:00PM" } }); return msg;
  4. Open the BaaS node and set context to:
    [msg.] [payload.lambdaPayload] [payload]

Now you are ready to test the bot, it now integrates Intent Detection and your BAAS endpoint. The last Dialogue node will print contents of the BAAS response BaaS

No more hard-coded data

The bot is expecting just a few different inputs - text, email, suggestion prompts, date and time. Text and email can be captured with input node, for suggestions prompts and DateTime inputs you will use Markup node.
Markup syntax for ‘main menu’ suggestion prompt.

<TimelineMessage>
  <SuggestionPrompt>
    <Option title="Book an appointment" id="1" />
    <Option title="When is my appointment?" id="2" />
  </SuggestionPrompt>
</TimelineMessage>

DateTime input syntax

<TimelineMessage>
 <DateTime componentType="dateOnly" />
</TimelineMessage>

Full markup documentation available at http://docs.servisbot.com/

Markup nodes that are inputs need to be followed by an Input node.

Full Design, node breakdown

Full flow Note the use of Link In and Link Out to remove pathways clutter.

Node configurations

Notice use of Set Nodes - setIntentName, setDate, setTime and setEmail - each of these is setting msg.payload.{PROPERTY} such that it is available within the bot. These nodes follow input nodes. Message sent to an input node is set on msg.payload.user.message, only last message is available on that porperty - hence the need to save it to another place on msg.payload setTime example
settime example
The two function nodes prepare BaaS payloads witch will trigger different route within the lambda function: AvailableSlots:

msg.payload.lambdaPayload = JSON.stringify({
    "name": "AvailableSlots",
    "date": msg.payload.date
});
return msg;

WhenMyAppointment and MakeAppointment

msg.payload.lambdaPayload = JSON.stringify({
    "name": msg.payload.intentName,
    "slots": {
        "Date": msg.payload.date,
        "Email": msg.payload.email,
        "Time": msg.payload.time
    }
});
return msg;

Other nodes config

get available times:

msg.payload.times = JSON.parse(msg.baas.MakeAppointmentLambda.response.Payload)
return msg;

unpack lambdaResponse:

msg.payload.lambdaResponse = JSON.parse(msg.baas.MakeAppointmentLambda.response.Payload)
return msg;

[template]-[markup]:

<TimelineMessage>
    <Dropdown description="When?">
       {{# payload.times }}
          <DropdownItem id="{{time}}" title="{{time}}" value="{{time}}" />
        {{ / payload.times }}
    </Dropdown>
</TimelineMessage>