Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
Showing results for 
Search instead for 
Did you mean: 
Have you ever wanted to build your own chatbot? We made it super easy for you with our end-to-end platform. By reading this article, you’ll understand and master the different steps of bot building with SAP Conversational AI, and you’ll have a concrete result at the end… ready to go live! Let’s start building!


For the purposes of the tutorial, we’re going to imagine we’re building a bot for an e-commerce company that provides its customers with information on shipment rates as well as a parcel tracker, which we’ll connect to UPS.

To deliver these services, we need to enable our bot to do the following:

  1. Perform simple interactions like greetings and goodbyes

  2. Understand that the user is asking either for the package tracking service or the package delivery price service

  3. Answer with the appropriate action


  1. To begin, create an account on SAP Conversational AI! It’s free.

  2. Once you’re logged in, it’s time to build your first bot. Click on the button + NEW BOT in the header section, at the top right of the screen.

  3. You can choose one or more predefined skills for you bot. This will help you get started faster. Just select Greetings for now, but I encourage you to check out the others later.

  4. Enter a name for your bot: “parcel-bot” would be appropriate

  5. Add a description.

  6. You can select up to six topics to improve your bot training. Topics are keywords that can define your bot like “fashion”, “games” or “jobs”.

  7. Depending on how to the data will be used, choose the appropriate Data Policy option. Note that we don’t currently allow health bots (in other words, bots that ask your users to provide some personal health information).

  8. Set English as the default language.

  9. You can keep your bot public since there’s no private info involved, but you can change this setting later.

You’re now ready to start buildind your bot!


There are four phases in the life of your bot. On our platform, these are represented by four tabs:

  1. Train – Teach your bot what it needs to understand

  2. Build – Create your conversation flow with our Bot Builder tool

  3. Connect – Link your bot to one or several messaging platforms

  4. Monitor – Train your bot to make it sharper and get insights into its usage!


Entities are designed to extract key information from sentences that the user types into the chatbot.

For our package bot, and specifically for the tracking service, we need to extract the tracking number from the sentence.


To achieve this, we’ll create a custom entity to give to the bot an idea of the information it should find within conversations. As with intents, training is very important: The more examples you add to your bot, the more accurate it gets. You can train your entities using multiple intents. Entities are independent of intents.

Go to the Entities section and create a new free entity called parcel-. Once you have created this, you can start entering synonyms, which are simply examples of possible instance of your entity. The UPS API provides some examples of tracking numbers to test your applications. You can use these as a reference or even write your own tracking numbers, if you’re a regular UPS user.

  • 1Z2220060291994175

  • 1Z2220060292002190

  • 1Z2220060292634221

  • 1Z2220060292690189

  • 1ZISDE016691676846

  • 1ZY756Y60380128446


This is the brain of your bot, where all its understanding is stored in the form of intents.

An intent is a collection of sentences that all have the same meaning, even though they can be worded very differently. When a user sends some text to your bot, our algorithm compares it to the phrases in your intents. Then it checks whether the text is close enough to one of them and decides what the intention of the message is.

Take the following sentences, for example:

  • Are you a bot?

  • You reply so fast, I’m sure you must be some kind of robot.

  • Am I speaking to a human or not?

These are all different, but they all ask the same question, which we can sum up as: “Are you a bot?” Well, that would make a great intent. If your bot can recognize this question, you can prepare a smart response, like “I’m a robot and I’m proud of it.”


All bots should understand basic things such as greetings, agreement, disagreement, or when a user is asking for help.

If you chose the predefined Greetings skill when you set up your bot, you’ll already have two intents: @goodbye and @greetings.

As SAP Conversational AI is collaborative, you don’t have to recreate each intent every time you want to use it. You can fork an intent that someone has already created to clone it right into your bot. At a certain point in the conversation, since we need to understand whether your user agrees or not, we can try to search and fork two intents: @yes and @no.

Type yes in the SEARCH field.

You can click the intent names if you want to check their full content.


If you want a custom intent, you can build it from scratch. Here, we want the bot to understand when someone is asking to track a parcel.

Click + CREATE to the right of the search field and choose a name for your intent. Let’s call it track-parcel.


Now that we have created two intents, we need to populate them with various expressions. An expression is a sentence added to an intent.

A golden rule would be to add at least 30 expressions to an intent and ideally more than 50.

Click an intent and add sentences you want your bot to understand.

When adding expressions, put yourself in the shoes of the people talking to your bot. What could they possibly ask? Enter a new expression by typing it into the Add an expression field. Above are some examples.

Track parcel:

  • Please track parcel

  • Track parcel 1Z2220060291994175

  • Where is my packet

  • I want to check my parcel position

  • When will I receive my packet with the number 1ZISDE016691676846 ?

Tip: Click the message “You have X expressions suggested to enrich your intent” to see examples of phrases you can add to your bot. This is a good way to speed up your training.

As you can see, some expressions of the @track-parcel intent contain a tracking number. These numbers are intended to train your bot and help it recognize what a tracking number looks like. That’s why we recommend making a mix of expressions both with and without tracking numbers. In the expressions containing a tracking number, click the expression to make sure the corresponding entity is recognized (or tagged). If the bot doesn’t recognize the entity automatically, you can highlight the tracking number and select the #parcel-number entity. This is pure training.

If you want to see the finale version of the @track-parcel intent, click here.
The @price-parcel intent is available here.


At this stage, your chatbot should now be able to recognize when a user is asking to track their package and extract the tracking number, if it exists.

Let’s test the bot with the console.

Click the TEST icon near the top right of the screen. Type a sentence you haven’t used to train your bot – for example: Check parcel 1Z2220060291994175.

You’ll see which intent was detected under User is referring to, and you will see the recognized entity under In the expression there is.
This means your bot knows that your user is asking for tracking and which parcel number they want to track. If the algorithm did not detect an intent or detected an invalid intent, you will need to train your bot with more expressions. Go back to your intents and add or modify expressions. Then, test again. This might sound a bit repetitive, but it’s the best way to ensure your bot will detect users’ sentences correctly.

Once you’re happy with your bot’s intent detection, it’s time to move to the next phase: building your conversation flow.


Now that you’ve filled the brain of your bot with the knowledge it needs, click the Build tab.

The Build tab is where you find the Bot Builder, which helps you construct the conversation flow of your bot using skills.


Each skill represents one thing that your bot knows how to do. Skills can interact with each other and can be either complicated (such as managing payment by credit) or simple (such as answering basic questions).

Every time you build a bot, forking skills you’ve already made will make your new bots increasingly powerful. And because you can also fork skills created by other people on the platform, you don’t have to reinvent the wheel. For example, if you chose the predefined when creating your bot, it will already be on your interface.

Click the name of the skill to check the associated details:

A skill has four parts:

  • Readme: Where you explain the purpose of your skill

  • Triggers: Where you decide when the skill should be activated

  • Requirements: Where you specify which information this skill has to collect, and what questions the user needs to ask to fulfill the requirements

  • Actions: Where you define what to do once the requirements are fulfilled

If you navigate through the tabs, you’ll see that a skill is structured as follows:

  • It is triggered if the @greetings intent or the @goodbye intent is matched.

  • It has no requirements, because it does not need to collect additional information. This means it will execute actions directly after a trigger.

  • It has two possible actions: If the intent matched is @greetings, it sends a random welcoming message chosen from a list, and if the intent is @goodbye, it does the same, but picks the message from a different list.

It’s time to create our own skill with the same structure.

Note: you can fork the already built skills here : track-parcel and price-parcel.


Go back to the Build tab and click on + Create skill on the left of the screen.

You have three different types of skills: business, floating, and fallback.

  • Business and floating have no structural differences. However, differentiating these two types helps when you have a lot of skills.

  • Fallback skills trigger when no other skill is triggered by the user’s input. Most of the time, you’ll have only one skill of this kind, where you can remind the user what your bot can do and ask them to rephrase.

Our skill will be of the floating type. Give it the name you want. I’ve chosen track-parcel.


Click your newly created skill, then go to the Triggers tab. We want to activate our skill when an expression contained in the @track-parcel intent is present.


Next, go to the Requirements tab and create a first requirementHere, we’ll ask for some information that is necessary for the action to work properly. In our case, what we need is the parcel tracking number. Without it, of course, the bot can’t retrieve the packet location. Remember that when creating the entities and intents (parts 1, 2, and 3 of this tutorial), we created a #parcel-number entity and some intents that might contain it. The Requirements tab will extract the entity and save it to the bot’s memory.
Let’s create a requirement that asks the bot to save the recognized #parcel-number entity into a memory variable called parcel-number.

We’ll also create a condition if #parcel-number is missing. Let’s enter a sentence like Could you give me your tracking number?
Once we’ve done this, there are now the following two solutions:

  • The user already included a tracking number in their initial sentence – for example, Check parcel 1Z2220060291994175.

  • The user wrote something like Where is my parcel?, to which the bot replies by asking them to provide the tracking number.

Each case results in the tracking number being stored in memory.

We now need to create a second requirement that the bot will request when it asks for final confirmation.
We’ll require either an @yes or @no intent, both of which we initially forked in step 2 of this tutorial. The reason we need our user to say “Yes” or “No” is simply when the chatbot will ask “Do you want to track the parcel . As you can see, this is a confirmation process.

Select @yes as yes and @no as no and put an OR condition between both by clicking the AND button, which switches between AND and OR.
Then, click the EDIT REPLIES box. What we’ll do now is set up the text that the chatbot will display if no “Yes” or “No” sentences were selected. Click Quick replies, enter the confirmation message and create two quick replies “Yes” and “No”.

Let’s take some time to analyze the placeholder in the sentence “Do you want to track the parcel XXX”.


  • memory: This is the way to enter the memory of the conversation (which we can access and modify whenever we want during the conversation)

  • parcel-number: This is the variable that contains the entity we previously created

  • raw: This is the exact way the parcel-number was typed

For more information about memory management, check out our documentation.


Now that the condition as been trigerred and the requirments fullfilled, the chatbot can now start the action.
If our use-case, there will be actually two actions that will depend on what the user will answer to the requirement “Do you want to track the parcel {{memory.parcel-number.raw}}?”

1) The user answers “No

We start by creating a new message group and apply the condition to it – in other words, if the “no” variable (containing one of the @no expressions) is in the memory.
Then we choose SEND MESSAGE and add a text message.

Finally, we click UPDATE CONVERSATION and EDIT MEMORY, where we will unset the “no” variable in the memory.
As you can see, we start over, and the user passes through the requirement process again.

Quick tip: You can also set a delay (optional) of up to five seconds between two messages. This can be useful when the messages your bot sends are quite long and the user needs time to read them.

2) The user answers “Yes

This is where we’ll connect to the UPS API. Here, some development skills are required. But don’t worry: We’ll explain everything.
Let’s create a new message group and apply the other condition

What’s happening here? We’re connecting to a back-end system (in our case, coded in Python) that will make the connection between the bot and UPS, using the parcel number as a reference. Let’s dive deeper into this.


As we saw before, we have to connect to our custom-built webhook, which then connects to the back-end APIs.
For the purpose of this demo, we coded our webhook in Python, but you could also do it in Node.js or any language you want.

Let’s first connect our webhook.

Go to Settings, select Versions from the menu on the left of the screen and then click Expand (arrow icon) to the right of v1.

Paste the following URL into the Bot Builder field:

This is where we host the code. Of course, you can also host the code on your own server and change the URL accordingly.

What about the code itself? Let’s decrypt it.
import os
from flask import Flask, request, jsonify, send_file
import json
import requests
import datetime

app = Flask(__name__)
port = int(os.environ.get('PORT', 3000))

@app.route('/check_number', methods=['POST'])
def check_number_validation():

bot_data = json.loads(request.get_data())
parcel_number = bot_data['conversation']['memory']['parcel-number']['raw']
memory = bot_data['conversation']['memory']

if len(parcel_number) == 18:
validation = 1
validation = 0

memory['validation'] = validation

return jsonify(
'memory': memory

@app.route('/track_parcel', methods=['POST'])
def get_track_parcel():

bot_data = json.loads(request.get_data())
parcel_number = bot_data['conversation']['memory']['parcel-number']['raw']

json_requests = {
"UPSSecurity": {
"UsernameToken": {
"Username": "ysu_deliverybot",
"Password": "190819&Jiqiren4TEST" },
"ServiceAccessToken": {
"AccessLicenseNumber": "0D6985C32BF33012"
} },
"TrackRequest": {
"Request": {
"RequestOption": "1",
"TransactionReference": {
"CustomerContext": "Your Test Case Summary Description" }
"InquiryNumber": parcel_number }
#1Z12345E6605272234 : une seule réponse
#1Z12345E0205271688 : plusieurs réponse
#1ZISDE016691609089 : pas de réponse
url = ""
r =, json=json_requests)

data = r.json()

if 'TrackResponse' in data.keys():
if type(data['TrackResponse']['Shipment']['Package']['Activity']) == list:
activity = data['TrackResponse']['Shipment']['Package']['Activity'][0]
activity = data['TrackResponse']['Shipment']['Package']['Activity']
date_str = activity['Date']
date_obj = datetime.datetime.strptime(date_str, '%Y%m%d')
RES = 'Latest status of your parcel: ' + activity['Status']['Description'] + ' on ' + date_obj.strftime('%B %-d, %Y')
RES = 'Sorry I could not find any information with the offered number. I met the error: ' + data['Fault']['detail']['Errors']['ErrorDetail']['PrimaryErrorCode']['Description']

return jsonify(
'type': 'text',
'content': RES,
#No tracking information available
#{"Fault":{"detail":{"Errors":{"ErrorDetail":{"PrimaryErrorCode":{"Code":"151044","Description":"No tracking information available"},"Severity":"Hard"}}},"faultcode":"Client","faultstring":"An exception has been raised as a result of client data."}}

@app.route('/save_size', methods=['POST'])
def save_parcel_size():

print("save parcel size")
bot_data = json.loads(request.get_data())
if 'parcel-size' in bot_data['nlp']['entities'].keys():
memory = bot_data['conversation']['memory']
memory['parcel-size'] = bot_data['nlp']['entities']['parcel-size'][0]
return jsonify(
'memory': memory
return jsonify(status=200)

@app.route('/get_location_thumbnail', methods=['POST'])
def get_location_thumbnail():
bot_data = json.loads(request.get_data())
geolocation = bot_data['conversation']['memory']['location']['formatted']
# Python program to get a google map
# image of specified location using
# Google Static Maps API

# Enter your Google Map api key here
api_key = "xxx"

# url variable store url
url = ""

# center defines the center of the map,
# equidistant from all edges of the map.
center = geolocation

# zoom defines the zoom
# level of the map
zoom = 14

# get method of requests module
# return response object
r = requests.get(url + "center=" + center + "&zoom=" +
str(zoom) + "&size=400x400&key=" +
api_key + "&sensor=false")

# wb mode is stand for write binary mode
filePath = "images/" + center + ".jpg"
f = open(filePath, 'wb')

# r.content gives content,
# in this case gives image

# close method of file object
# save and close the file

return jsonify(
'type': 'picture',
'content': '' + filePath,

def get_image(imageName):
return send_file('images/' + imageName, mimetype='image/jpg')

@app.route('/errors', methods=['POST'])
def errors():
return jsonify(status=200)

def hello():
return "UPS Chatbot running!"

if __name__ == '__main__':'', port=port)

What we do:

  • The app.route /check_number will check that the tracking number of the parcel is correctly formatted and contains 18 characters.

  • The app.route /track_parcel will collect the tracking number from the memory of the conversation. Accessing the memory of the bot is simple: bot_data[‘conversation’][‘memory’][‘parcel-number’][‘raw’]. Here, we take the raw number (that is, without any modification, exactly as the user wrote it). Then, the bot connects to the UPS system and uses parcel_number as the InquiryNumber. As you can see, parcel_number refers to the entity we created at the beginning of this tutorial. The tracking is processed, and we ultimately send the response back to the user in the chat as a text type containing the sentence (), which can be either the last status or an error – for example, the number doesn’t exist in the UPS database.

And that’s it. Through a simple piece of code, you can access entities, intents, messages, or any data contained in the conversation JSON and use them to connect to an external API.


At this point, your parcel tracking service is active. But we’ll go one step further and allow your customers to choose between parcel shipment options and check their delivery address with Google Maps.

8.1 – Let’s first create an entity called “parcel-size”.

This is a restricted entity because the values in it are predefined. We want the values to be the same in all cases, unlike for the parcel-number entity that we previously created, where there were unlimited tracking numbers.
Fill the parcel-size entity with the following values:

The values here all refer to sizes but in different ways.
Go to the Enrichments tab.

This is where we’re going to enrich our values; or, to put it another way, we’re going to group them and add dedicated information to them.

First, we click + Add New Pair. Here, we’ll enter a key-value pair representing the two enrichments we want for our package: the “official” size name and the price for it.

We’ll then create four groups, representing the four sizes our chatbot manages: >10 kg, 3–10 kg, 1–3 kg, 0–1kg.

We’ll give each of these groups a name (>10 kg will be called “very large”) and a price, (>10 kg will cost $40).
Also, we’ll gather all the entity values that refer to the same size. In the first case: >10 kg and xl will refer to the “very large” size. Of course, you can edit, add, or delete custom enrichments whenever you want.

8.2 – Let’s build an intent called @price-parcel

We have now created and enriched our entity. Next, we’ll create an @price-parcel intent to gather every sentence users are likely to write to get the price of their parcel.

Fill this intent with expressions like the following:

  • I want the M size

  • How much does your service cost?

  • I‘d like to send a package to 52 Place de la Madeleine, 75011 Paris

8.3 – Create a skill called price-parcel

Now we’ll create a price-parcel skill, which will be triggered if @price-parcel is present.

On the Requirements tab, we’ll need two important things: #parcel-size and #location. We’ll also need the “Yes” and “No” requirements, as we saw in the first part of this tutorial.

Let’s deep dive into the two entities first.


We’ll make a condition if parcel-size is missing and create a carousel.

In this carousel, we’ll create four cards, representing the four sizes available.

For each card, we put the official” name (small, medium, and so on), the size it refers to, and a button. In this case, Choose S is what is written on the button under the card, and “I choose is what will be written in the webchat once the user clicks on the button.

Once we’ve done this, when the user asks for the price of a parcel, there are two possible scenarios:

  • They already did it in their sentence – for example, by entering I want to ship .

  • They enter a generic sentence like I want to send a parcel, and because the requirement isn’t fulfilled, the bot answers with the carousel we just created. Remember: The action of the skill will run only if the user hasn’t chosen the size (understand: fulfill ALL the requirements).


Handling location is another cool part of our chatbot.
Let’s say we want the user to enter the delivery address, and we only want to allow French addresses – for example, an e-commerce website that wants to focus on a specific country.

You may well be wondering whether you need to create a dedicated entity to recognize a French address.
The answer is no: SAP Conversational AI does this for you. This is what we call gold entities. These are commonly used entities that have already been trained. And the best part is that they’re also already enriched.

In our case, let’s say the user enters the following French address: 3 rue Moncey, 75009 Paris.
The chatbot automatically recognizes this as a location:

Let’s have a look at the JSON:
 "location": [
"formatted": "3 Rue Moncey, 75009 Paris, France",
"lat": 48.8799922,
"lng": 2.330921,
"type": "premise",
"place": "ChIJAzwjbUlu5kcR9wj7Ox8O4M8",
"street_number": "3",
"street_name": "Rue Moncey",
"postal_code": "75009",
"city": "Paris",
"state": "IDF",
"country": "fr",
"raw": "3 rue moncey 75009 paris",
"confidence": 0.94

This shows that SAP Conversational AI recognizes not only the address, but also the city, the postal code, the country, the street number, and so on.
As a result, we can easily check whether the address is in France.

Let’s go back to our requirement and create a “missing” option.

And then let’s also create a “complete” option. The bot displays this “complete” option if the user writes a French address.

In this “complete” option, we call a webhook, specifically the /get_location_thumbnail.
This action is located in our Python code, which we saw in the coding part (step 7) of this tutorial. This app.route will connect to the Google Maps API, define a zoom/size option, and store (on the server) the screenshot image corresponding to the address written.

Finally, we’ll also create a “validator” to show whether the address is not in France. (Remember, the chatbot knows this thanks to the #location gold entity).
The condition will be if is-not fr. This means: if the “country” parameter of the #location gold entity is different from “fr”.

As a result of this complete set of requirements, the conversation will appear as follows:

As soon as the user clicks Yes, the action runs.

Let’s create an action. It the user clicks Yes,

In this text message, we access the memory of the bot and retrieve the price from the entity’s enrichments. Here’s an example of what the bot displays:

Make sure you click Reset all memory after the text message is displayed to ensure the bot doesn’t mix up information from the different parts of the conversation.

To cover all eventualities, we’ll also create a condition for cases in which the user clicks the No button immediately after the map screenshot.


We’re all set!

Let’s sum-up what we did to achieve the conversations on tracking and the parcel price.

Tracking conversation: 

  • Create an entity corresponding to the tracking number

  • Create an intent full of expressions that a user might write to track his parcel

  • Create a webhook to connect to the UPS API

  • Create a skill that sends the tracking number (through the webhook) and retrieve the location of the parcel

Parcel price:

  • Create an entity corresponding to the parcel type, enriched with size and price

  • Create an intent full of expressions that user might write to send a parcel and check whether the address is a French one

  • Create a webhook to connect to the Google Maps API

  • Create a skill that

    • Sends the address (through the webhook) and retrieves a map screenshot

    • Proposes different parcel sizes and tells the price of the selected one


How do we test it?

Simply click the blue CHAT WITH THE BOT button in the bottom right corner of the UI.


Are you happy with your bot? If so, it’s time to deploy it outside of the platform.

Go to the Connect tab, click Webchat, and follow the instructions.
In this easy step-by-step process, you’ll be able to do the following:

  • Choose the colors of the web client

  • Change the title and chatbot logo

  • Specify a welcome message, a user avatar, and so on

  • Enter a call-to-action text

  • Name your web client

The result will be a short script that you can copy/paste into the HTML code of your website. And that’s it!


Want to hone your bot building skills further? Then check out these other tutorials:


Feel free to ask your question on our official support platform SAP Answers or by leaving a message in the comments section below.

This blog post also appears at