This tutorial will walk you through the design and build process of a Bot that allows the user to manage appointments.
To begin, let’s look at some elements of the Bot experience that will need to be handled outside of the Bot itself, i.e. the third-party components. These are:
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:
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:
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
).
In your AWS account create a new AWS Lambda Function using the code provided above.
Use test events in the Lambda console to verify behavior.
Note the function’s name as it will be required for the BaaS.
Before you can make a working BaaS you need to create a secret with credentials to invoke your AWS Lambda function.
sb-cli secret create-interactive
.'aws-cross-account-role'
.{
"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.
To verify 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.
To create the Bot, log into your account on https://portal.servisbot.com and select Create 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 launch. 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.
Feel free to skip this step if you have built a Bot before. Otherwise, follow these steps:
Start
nodeDialogue
node and connect it to the Start node.This step will teach you a quick and helpful debugging technique.
Start
> Dialogue
> Input
> Intent Detection
> DialogueSecret
and Project Id
msg.payload.result
Now open your bot in the messenger and input ‘book an appointment’ you should see a similar response.
Using dialogue node to print contents of msg.*
is very helpful and often used during Bot development.
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.
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
Change
node. (you can remove second Dialogue) node. 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;
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
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.
Note the use of
Link In
and Link Out
to remove pathways clutter.
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 property - hence the need to save it to another place on msg.payload
setTime
example
The two function nodes prepare BaaS payloads which will trigger different routes 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;
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>