Tutorial : Weather Bot

This tutorial will show you how to create a simple bot which gives the weather forecast in a given city.

Prerequisites

We recommend that you read the Interface - Knowledge & Skills and Training section pages before going on with this tutorial. You should also consult the Athena documentation as much as possible while reading this tutorial.

Intents creation and training

There are 2 ways to understand the intent of a user: Keyword Matching and Intent Detection. These are known as Patterns.

Keyword Matching is mostly used for small tests and are not very useful in a real bot. This is why we will look into Intent Detection.

To start, let’s define some intents we will use for our bot.

In this use case, the primary intent will be Weather. But we also want our bot to be able to handle basic conversation. To do that we have to define a few additional intents, like so:

  • Weather
  • Greetings
  • Goodbye
  • Name

The steps to create the intents are described in the Training section of the documentation.

For the sake of this tutorial we will just create these 4 intents, name them as described above, and add a few sentences in it. Then we will be able to launch a training which will allow us to start chattting with our bot.

Knowledge & Skills

Now we can go to the Knowledge & Skills page to start creating our dialog scenario.

Domains

The first thing to do is add a new domain. Domains are simply a way to organize your dialogs in different files. When you start having a lot of scenarios, it is really useful to clearly organize them in different files. However in this use case the scenario is very short so we only need to create one domain.

Dialog scenario

Now that our domain is created, we can start writing the dialog scenario.

?! Greetings
    - Hello, nice to see you

Let’s see what we just did here. The “?!” is used to detect an intent. If this intent is detected in the user’s input, the bot will go ahead and play what is in the next indentation block.

Now lets do the same for the other intents :

[DEFAULT]
    - Sorry I did not understand you.

?! Greetings
    - Hello, nice to see you

?! Goodbye
    - Take care
    -& Hope to see you soon

?! Name
    - My name is WeatherBot, nice to meet you.
    - People call me WeatherBot.

?! Weather
    - I do not know :'(

Try to trigger all these scenarios. If some are not triggered when they should, then make sure that you trained your classifier correctly and that all your intents have sentences.

Note: We used a few different functionalities here :

  • In the Name scenario, we defined two answers. They will be picked randomly. You are not limited in the number of different answers.
  • The “-&” notation allows us to answer several sentences sequentially.
  • [DEFAULT] is a special label that will be rendered if no scenario is triggered. We will show you more about the labels later in this tutorial.

You will find more examples in the Templates documentation.

We recommend that you spend some time trying to combine those symbols and see what happens. This tutorial is the most useful when you write your own examples and try different cases instead of copying the code we give you as examples. Practice makes perfecT!

Weather API

The way to retrieve the weather information is still missing. So we will use one of the many weather API available such as OpenWeatherMap.

Getting the API key

OpenWeatherMap requires to use an API key to be able to make requests. Fortunately this is free and pretty easy to do.

1) Sign up OpenWeatherMap. 2) Sign in OpenWeatherMap. 3) From the welcome page, select the “API keys” tab.

Retrieve the weather

If we look into the OpenWeatherMap’s Documentation we can see several ways to retrieve the weather :

  • By city name
  • By city id
  • By coordinates
  • By ZIP code

City id and the coordinates are usually not easy to get from a user, so we will go with the city name.

Using the @rest addin

All we have to do now is call the OpenWeatherMap’s API from our Athena scenario. In order to do so we will use the @rest addin.

?! Weather
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=San Francisco&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239")
    - %rest_response.data

Note: The APPID parameter is the API key we got from OpenWeatherMap’s website.

Important : The new syntax we introduced here : “-:” This is called a Template statement. Template statements are executed before templates and before conditions (we will see about conditions later in this tutorial).

What we just did here was requesting the weather for San Francisco, and then display the whole response. Here is the result :

{ "coord": { "lon": -122.42, "lat": 37.77 },
  "weather": [ { "id": 802, "main": "Clouds", "description": "scattered clouds", "icon": "03n" } ],
  "base": "stations",
  "main": { "temp": 288.6, "pressure": 1020, "humidity": 47, "temp_min": 287.15, "temp_max": 290.15 },
  "visibility": 16093,
  "wind": { "speed": 6.2, "deg": 280 },
  "clouds": { "all": 40 },
  "dt": 1510707360,
  "sys": { "type": 1, "id": 478, "message": 0.0323, "country": "US", "sunrise": 1510757420, "sunset": 1510793879 },
  "id": 5391959,
  "name": "San Francisco",
  "cod": 200 }

This data is formated in JSON. For this tutorial, we will only retrieve the current temperature, which is done pretty easily as follows:

?! Weather
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=San Francisco&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239")
    - %rest_response.data.main.temp

The output is now 288.6, meaning the unit is Kelvin (hopefully). A quick look at OpenWeatherMap’s documentation tells us that we can specify the unit in the request, “metric” for Celcius and “imperial” for Fahrenheit. For our example we will use Fahrenheit :

?! Weather
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=San Francisco&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    - %rest_response.data.main.temp

We now have 59.02 in output, which seems much more reasonable!

Ok now you must be asking yourself what happens if the user does not live in San Francisco? Well we would say that he/she is missing out, but not to worry we are just getting started.

Getting the city from the user

Athena gives us the possibility to extract some information from the user’s input using the @entities addin.

And good news for us, there is a city extractor. Let’s try it shall we?

?! Weather
    -: @entities.extract("city")
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=%city&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    - The current temperature in %city is %rest_response.data.main.temp

Now let’s do a few tests:

1) What is the weather in Fremont? -> The current temperature in Fremont is 62.43 2) Temperature in London -> The current temperature in London is 45.31 3) Weather -> The current temperature in %city is %rest_response.data.main.temp

As you can see in the last test we did not mention the city name. As a result, our output is not so nice anymore. With Athena, a variable with no value will be displayed with its raw name, which is a very easy way to detect things that can go wrong with a scenario.

Checking the user input

We want to check if the user provided the city name or not, and act accordingly.

Athena allows us to use conditions to select which Templates will be rendered.

This part gets a little tricky because it requires a good understanding of the Athena Workflow and terms. So pay attention!

Here is how the Athena workflow works :

1) A scenario is elected using the user’s input 2) The template statements (lines starting with “-: “) of the next indentation block are executed 3) The conditions (lines starting with “-| “) are checked 4) The templates (lines starting with “- “) are rendered

Knowing that, let us check what happens with the following code :

?! Weather
    -: @entities.extract("city")
    - No city provided
    -| @contextScenario.has("city")
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=%city&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    - The current temperature in %city is %rest_response.data.main.temp

As a reminder, we are trying to check if the user provided us with the city name. If he/she did then the API request can be executed.

Athena will process the code as follows:

User imput : "What is the weather?"

1) election

?! Weather --> election of the scenario Weather

2) extraction

    -: @entities.extract("city") --> extraction of the city

3) api call

    -: @rest.get("http://api.openweathermap.org... --> API call

4) check

    -| @contextScenario.has("city") --> Checking the condition

5) incomplete query handler

    - No city provided --> Excecution of the default template as the condition was not fulfilled

As you can see the API call is done whether or not the city name has been provided by the user, which is not what we want to achieve.

Exercise : How to make the @rest call only when a city name has been provided, using solely what we have seen since the beginning of this tutorial?

Answer : If you go back to the workflow, you see that the only instructions excecuted after the conditions are the templates.

So to make it work check out the following code. We are calling the rest API from a template. This way it will be executed only if the condition is fulfilled, and then the result of the request will be used in the template continuation.

?! Weather
    -: @entities.extract("city")
    - No city provided
    -| @contextScenario.has("city")
    - @rest.get("http://api.openweathermap.org/data/2.5/weather?q=%city&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    -& The current temperature in %city is %rest_response.data.main.temp @contextScenario.transferToSession("city")

Even though this is working, you can see this is not the best solution. The potential issue raised by putting this API call directly in a template, is that we cannot verify the API response, as conditions are executed before templates. This is why we usually want to check the @rest_response.status before deciding what to do with @rest_response.data.

So the best solution is to use Labels.

Using Labels

Let us first define our label. We will do a label that displays the weather for a given city name:

[GET_WEATHER(city)]
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=%city&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    - Error: Code: %rest_response.statusCode, Message: %rest_response.data
    -| %rest_response.statusCode == 200
    - The current temperature in %city is %rest_response.data.main.temp @contextScenario.transferToSession("city")

So what happens when this label is executed?

1) The template statements are executed. In this case there is only one, the @rest call. 2) The conditions are checked. Meaning we check if the REST call was successful. 3) The templates are rendered. Here it is the last line as the condition that is verified.

You can actually check what happens if you remove the APPID from the API URL. You should be getting the “Error” template displayed instead.

Requesting the city name

Now let’s see how we ask the user to provide the city name if it was not provided.

We are going to create a sub-scenario that will ask for the city name, and put that sub-scenario inside a label so we can call it recursively until the user provides the requested information.

[REQUEST_CITY]
    - For which city?
        ??
            -: @entities.extract("city")
            - {REQUEST_CITY}
            -| @contextScenario.has("city")
            - {GET_WEATHER("%city")}

Let us look at what this code does :

1) We ask the user for the city name. 2) We use the Pattern fallback ?? to process the user’s answer 3) We check if the user provided a city name. 4) If he/she did we give him the weather for that city 5) If he/she did not, we repeat that label.

Note: As we used the pattern fallback “??”, the user can still exit this scenario. You can easily test it by triggering any other intent. If you want to force your user to give a mandatory information, then you can use “???” which will keep him/her in the sub-scenario.

We now need to call this label from the scenario :

?! Weather
    -: @entities.extract("city")
    - {REQUEST_CITY}
    -| @contextScenario.has("city")
    - {GET_WEATHER("%city")}

We can see that we actually have the same 4 lines in the “Weather” scenario and in the REQUEST_CITY label. So once again we are going to create a label to avoid unecessary duplicated code:

?! Weather
    - {WEATHER}

[WEATHER]
    -: @entities.extract("city")
    - {REQUEST_CITY}
    -| @contextScenario.has("city")
    - {GET_WEATHER("%city")}

[REQUEST_CITY]
    - For which city?
        ??
            - {WEATHER}

[GET_WEATHER(city)]
    -: @rest.get("http://api.openweathermap.org/data/2.5/weather?q=%city&APPID=7dd3269a7fb905e9e0d62ec4b7a5d239&units=imperial")
    - Error: Code: %rest_response.statusCode, Message: %rest_response.data
    -| %rest_response.statusCode == 200
    - The current temperature in %city is %rest_response.data.main.temp @contextScenario.transferToSession("city")

Context

It is sometimes useful to remember the context of a conversation. It allows the bot to have custom reactions according to what has been said previously.

In this case for example we could save the fact that the user asked for the weather. So the next time he/she inputs a city name only the bot can give him the weather for that city. Here is how it works:

?! Weather
    -: @contextSession.set("conversationContext", "weather")
    - {WEATHER}

?% city
    - What about %city?
    -| @contextSession.has("conversationContext")
    -| "weather" == @contextSession.get("conversationContext")
    - {GET_WEATHER("%city")}

First we are using the @contextSession addin. When the user asks for the weather, you save this in the context.

Then if the user inputs a city name only, we check if he/she previously asked for the weather and act accordingly.

Note : The syntax “?%” allows us to call an extractor as a scenario elector.

If you want to unset the context, you can just use :

@contextSession.remove("conversationContext")

BBCode

Some connectors will allow you to use BBCode in your dialog.

Let us do a simple use case for our WeatherBot. We can change the REQUEST_CITY so it will display a list of cities the user can select from:

[REQUEST_CITY]
    - For which city? :[list][*] [button title="San Francisco"]SF[/button][*] [button title="Oakland"]OAK[/button][/list]
        ?# SF
            - {GET_WEATHER("San Francisco")}
        ?# OAK
            - {GET_WEATHER("Oakland")}
        ??
            - {WEATHER}

Now the user simply clicks on either San Francisco or Oakland to get the weather of his/her chosing. The user can still input any other city names Which will work just like before.

Conclusion

This tutorial comes to an end. You learned about most of Athena’s functionalities, and you should now have solid basis to create your own bot. Do not forget to refer to the documentation if some parts still seem unclear to you!