Skip to main content

Functions

Within cards you will make use of functions and expressions to describe the logic of your impact service.

Cards allow for various functions: replying with multi-media or interactive buttons, updating contact fields, integrating with 3rd party services or doing various calculations.

Text messages

Send a text message

This function sends a text message to the user.

Syntax

text("lorem ipsum")

Example

The following example creates a journey that sends a single text message to a user. The content of the message says "Hello this is my first text.".

A simple message example

card TextCard do
text("Hello this is my first text.")
end

Common errors

  • Not wrapping the message in double quotes.
  • text() is misspelt as test().

Ask a question

This function sends a text message and waits for the user’s response.

Syntax

ask("lorem ipsum?")

Example

The following example creates a journey that asks the user "What is your name?" and then waits for the user’s response. Once the user replies a thank you message is sent.

Example of a question message

card QuestionCard do
ask("What is your name?")
text("Thanks for answering")
end

Key things to note

  • The chat service will only execute further steps when a response is received or, else, when the journey times out.

Common errors

  • Not wrapping the question in double quotes.

Interactive messages

WhatsApp supports two types of quick replies: button replies and list replies.

Buttons replies are limited to 3 buttons. Each button has a limit of 20 characters.

List replies are limited to 10 options. Each option has a limit of 24 characters.

The body text, the header, and the footer of an interactive message can be placed into the buttons or list blocks so it can be composed as a single message.

Send a button message

This function sends a text message with buttons to the user. Buttons make it easy for the user to reply. You can add up to 3 buttons per message.

Syntax

buttons([Button1, Button2, Button3]) do
text("Select an option!")
end

You can also use labels for the buttons like so:

buttons([Button1: "1️⃣", Button2: "2️⃣", Button3: "3️⃣"]) do
text("Select an option!")
end

Example

The following example creates a journey that ask the user "What would you like to drink?" and provide two options to select from: Tea or Coffee.

If the user selects Tea they receive a message saying "Thanks for choosing tea". If the user selects Coffee they receive a response saying "Thanks for choosing coffee".

Example of a button message

card ButtonsCard do
buttons([Tea, Coffee]) do
text("What would you like to drink?")
header("This is the header!")
footer("This is the footer!")
end
end

card Tea do
text("Thanks for choosing tea")
end

card Coffee do
text("Thanks for choosing coffee")
end

You can provide labels for the buttons (e.g. Tea 🫖), while keeping a simple card name (e.g. Tea).

card ButtonsCard do
buttons([Tea: "Tea 🫖", Coffee: "Coffee ☕️"]) do
text("What would you like to drink?")
header("This is the header!")
footer("This is the footer!")
end
end

Sometimes, one wants to provide multiple buttons with different labels and but have them go to the same destination card. It's possible to do so by using the same destination card name, with different labels.

card Card do
var =
buttons(Destination: "🧁", Destination: "🎂", Destination: "🍰") do
text("Choose your favourite cake!")
end
end

card Destination do
text("You chose @var using @var.label ")
end

This will present 3 buttons, each with a different label but the response is all handled by the same card with the name Destination. Keep in mind that even if the Destination card may have a display label assigned, any label set in the button will take priority over display labels on the card.

Example of a button message with labels

Key things to note

  • You need to use both the buttons() and the text() functions.
  • The buttons() function describes the available options. Buttons can have up to 3 options. Button options have a limit of 20 characters.
  • The text() function describes the instruction to the user.
  • The header and footer are optional.
  • If you want to act on the user selection you need to add new cards to the journey to define the logic of each selected option.

Common errors

  • Forgetting to add the text() function into the buttons() function.
  • Forgetting to add cards for each button option.

Send a list message

This function sends a list of items to the user to select from. You can add up to 10 options in a list.

Syntax

list("lorem ipsum", [Option1, Option2,, Option10]) do
text("lorem ipsum")
end

Example

The following example creates a journey that sends the user a list message and allows them to choose between four options.

The user is instructed to "Pick an option!". Once they click the "Call to action" text the list expands and they see 4 options: Option1, Option2, Option3, Option4. After the user makes a selection, they get a message confirming their choice.

Example of a list message

card List do
list("Call to action", [Option1, Option2, Option3, Option4]) do
text("Pick an option!")
header("This is the header!")
footer("This is the footer!")
end
end

card Option1 do
text("You selected option 1")
end

card Option2 do
text("You selected option 2")
end

card Option3 do
text("You selected option 3")
end

card Option4 do
text("You selected option 4")
end

Key things to note

  • You need to use both the list() and the text() functions.
  • The list() function describes the copy for the selection button, as well as all the available options. Lists can have up to 10 options. List options have a limit of 24 characters.
  • The text() function describes the instruction to the user.
  • The header and the footer are optional.
  • If you want to act on the user selection you need to add new cards to the journey to define the logic of each selected option.

Common errors

  • Forgetting to add text for the selection button.
  • Forgetting to add the text() function after the list() function.
  • Forgetting a comma between the selection text button and the options.
  • Forgetting to add cards for each list option.

Labels for buttons and list items

Often times on an impact service you will re-use things, e.g. your menu. When you need to make updates to your copy, ideally you want to update it once and have that change apply everywhere that is applicable. This is possible for button and list options.

Syntax

When defining cards you can add an optional label for the card. The label will be used for buttons and list items the card is part of.

card ButtonCard, "lorem ipsum" do
buttons([Button1, Button3, Button3]) do
text("lorem ipsum")
end
end

Example

Let's create a basic menu selection with 3 options: About, Contact and Terms of service (ToS). Once the user clicks any of the buttons, they get a short message with a single button that takes them back to the Start.

Example of buttons being used for a menu

card Start do
buttons([About, Contact, ToS]) do
text("This our menu. Select an option.")
end
end

card About do
buttons([Start]) do
text("This is about us, we are truly wonderful!")
end
end

card Contact do
buttons([Start]) do
text("You can contact us via pigeon post, glhf")
end
end

card ToS do
buttons([Start]) do
text("We provide zero guarantees")
end
end

Now, let's say we want to change the copy of the Start button. We use it three times but only want to update it once. You can do that by specifying the copy you want as a string in that button option card.

By adding the "Menu" label to the definition of the Start card, that label will now be used in any button or list item where the Start card is referenced.

Example of buttons with label

card Start, "Menu" do
buttons([About, Contact, ToS])
text("This our menu. Select an option.")
end

card About do
buttons([Start]) do
text("This is about us, we are truly wonderful!")
end
end

card Contact do
buttons([Start]) do
text("You can contact us via pigeon post, glhf")
end
end

card ToS do
buttons([Start]) do
text("We provide zero guarantees")
end
end

The best part about the string is that you can also use dynamic fields, which is great for personalisation. Let's update the Start button to include the user's profile name on WhatsApp.

Updated example of buttons with labels

card Start, "Head home @contact.whatsapp_profile_name" do
buttons([About, Contact, ToS])
text("This our menu. Select an option.")
end

card About do
buttons([Start]) do
text("This is about us, we are truly wonderful!")
end
end

card Contact do
buttons([Start]) do
text("You can contact us via pigeon post, glhf")
end
end

card ToS do
buttons([Start]) do
text("We provide zero guarantees")
end
end

Media messages

Making use of media improves the user experience and can overcome literacy gaps. WhatsApp allows five types of media: image, video, audio, document and sticker.

The media you intend to send should ideally be hosted on Turn itself; however, a publicly accessible URL is also a valid input for these functions.

See how to upload media on Turn for usage in code-based journeys.

tip

A simple way to host media on a publicly-accessible URL is to upload your file to Google Drive and then obtain a download link using this site. Make sure the sharing permissions is set to Any one with the link can view.

caution

It is recommended that you upload the media to Turn, since media uploads on third-party platforms, including but not limited to Google Drive and AWS, often do not ensure that the request is serviced within an adequate time. This can lead to the request timing out and ultimately the user not receiving a response.

Send an image

This function sends an image to the user.

Syntax

image("<URL of your image>")

Example

The following example creates a journey that sends the user an image with a caption saying "hello there!".

Example of a message with an image

card MyCard do
image("https://upload.wikimedia.org/wikipedia/commons/thumb/d/db/Golden-crowned_kinglet_at_JBWR_%2811835%29.jpg/1000px-Golden-crowned_kinglet_at_JBWR_%2811835%29.jpg")
text("hello there!")
end

Key things to note

  • You need to use both the image() and the text() functions.
  • The image() function only holds the URL. Images can be up to 5MB.
  • The text() function sends a text caption.
  • WhatsApp only supports specific file types.

Common errors

  • Forgetting to add the text() function after the image() function.

Send a video

This function sends an image to the user.

Syntax

video("<URL of your video>")

Example

The following example creates a journey that sends the user a (cat) video with a caption saying "hello there!".

Example of a message with a video

card MyCard do
video("https://drive.google.com/uc?export=download&id=1LCTe5S6DT1mNnYQhaoeYsk3jqSAEglVE")
text("hello there!")
end

Key things to note

  • You need to use both the video() and the text() functions.
  • The video() function only holds the URL. Videos can be up to 16MB.
  • The text() function sends a text caption.
  • WhatsApp only supports specific file types.

Send an audio

This function sends an audio to the user.

Syntax

audio("<URL of your audio>")

Example

The following example creates a journey that sends the user an audio file and a text message saying "hello there!".

Example of a message with an audio

card MyCard do
audio("https://drive.google.com/uc?export=download&id=1hpkdDN9noqIqvwDX0EhS2z7K2W_-khXS")
text("hello there!")
end

Key things to note

  • You need to use both the audio() and the text() functions.
  • The audio() function only holds the URL. Audio files can be up to 16MB.
  • The text() function sends a separate text message.
  • WhatsApp only supports specific file types.

Send a document

This function sends an document to the user.

Syntax

document("<URL of your document>", filename: "<Title of your document>")

Example

The following example creates a journey that sends the user a document named "Example" with a caption saying "hello there!".

Example of a message with a document

card MyCard do
document("https://drive.google.com/uc?export=download&id=17-O2zjqaI-eQdqVJ-febYBxr0E9YT_HU", filename: "Example")
text("hello there!")
end

Key things to note

  • You need to use both the document() and the text() functions.
  • The document() function holds the URL and an optional filename parameter. Document files can be up to 100MB.
  • The text() function describes the caption of the media message.
  • WhatsApp only supports specific file types.

Template messages

You can send message templates from journeys provided that the template you want to send already exists and has been approved.

A WhatsApp message template is identified by its name and language, so you'll need both of those to send a template from journeys.

Syntax

send_message_template(
"template-name",
"template-language",
["body-placeholder1", "body-placeholder2", ...],
header: ["header-placeholder"],
video: "https://link.to/video.mp4",
image: "https://link.to/image.mp4",
document: "https://link.to/document.mp4",
buttons: [Card1, Card2, ...]
)

The header, video, image, document and buttons parameters are optional.

Template with placeholders in the body

If the template contains placeholders in its body, you can provide values for those placeholders in a list after the template name and language. In case the template doesn't have body placeholders, you can pass an empty list [].

Example

Let's assume you have created the following template:

  • name: simple-reminder
  • language: en
  • body: Hi {{1}}, this a reminder to {{2}}

Here is how you could send that template:

card SendTemplate do
send_message_template(
"simple-reminder",
"en",
["Mark", "exercise"]
)
end

Template with placeholders in the header

If the template contains placeholders in its header, you can provide parameters for them passing a list of values to the optional header argument.

Example

Let's assume you have created the following template:

  • name: simple-reminder
  • language: en
  • header: {{1}} reminder
  • body: Hi {{1}}, this a reminder to {{2}}

Here is how you could send that template:

card SendTemplate do
send_message_template(
"simple-reminder",
"en",
["Mark", "exercise"],
header: ["Training"]
)
end

Template with a media header

If the template has a media header, you can use the optional video, image, document arguments to provide a link to the desired media.

Example

Let's assume you have created the following template:

  • name: simple-reminder
  • language: en
  • header media type: VIDEO
  • body: Hi {{1}}, this a reminder to {{2}}

Here is how you could send that template:

card SendTemplate do
send_message_template(
"simple-reminder",
"en",
["Mark", "exercise"],
video: "https://link.to/the/video.mp4"
)
end

Let's assume you have created the following template:

  • name: simple-reminder
  • language: en
  • header media type: IMAGE
  • body: Hi {{1}}, this a reminder to {{2}}

Here is how you could send that template:

card SendTemplate do
send_message_template(
"simple-reminder",
"en",
["Mark", "exercise"],
image: "https://link.to/the/image.jpg"
)
end

Let's assume you have created the following template:

  • name: simple-reminder
  • language: en
  • header media type: DOCUMENT
  • body: Hi {{1}}, this a reminder to {{2}}

Here is how you could send that template:

card SendTemplate do
send_message_template(
"simple-reminder",
"en",
["Mark", "exercise"],
document: "https://link.to/the/document.pdf",
filename: "Document title"
)
end

Template with buttons

If the template contains buttons, you can use the optional buttons argument to direct the flow of the journey to different cards depending on the button the user clicks.

Example

Let's assume you have created the following template:

  • name: updates
  • language: en
  • body: Hi {{1}}, would you like to receive updates?
  • buttons: Yes, I would love to!, No, I would not.

Here is how you could send that template and direct the flow of the journey depending on the button the user clicks:

card SendTemplate do
send_message_template(
"updates",
"en",
["Mark"],
buttons: [Yes, No]
)
end

card Yes do
text("Great! You will receive updates from now on.")
end

card No do
text("Ok, you will not receive updates.")
end

It's important to note that the text of the buttons displayed in the simulator will likely be different from the ones displayed in WhatsApp. This is because the simulator uses the names of the cards you defined as options to simulate the display text for the buttons, while the real message uses the text you provided in the template.

Key things to note

  • The template you want to send should already exists and be approved.

Receiving GPS coordinates

You can extract GPS coordinates, such as latitude and longitude, from a user's shared location pin in the WhatsApp mobile application.

Syntax

  latitude = event.message.location.latitude
longitude = event.message.location.longitude

Example

The following example creates a journey that asks the user to share a GPS pin and extracts the GPS coordinates from it.

Example of a message with a document

card AskForGpsPin, then: ParseLocation do
ask("Please send your GPS Pin using WhatsApp")
end

card ParseLocation when event.message.type == "location" do
latitude = event.message.location.latitude
longitude = event.message.location.longitude

text("You have shared the following GPS coordinates: [@latitude, @longitude]")
end

card ParseLocation do
text("You have not shared a valid GPS pin")
end

Key things to note

  • You need to ensure that the message type being shared is "location" before you try to extract the latitude and longitude.
  • Having a default condition is also necessary to ensure that cases where the user does not send in a GPS pin do not break the conversation flow.

WhatsApp Flows

WhatsApp Flows are phone based forms that are great for data collection. You can integrate these into your workflows and use the data collected to continue in your canvas or journey.

Syntax

flow = whatsapp_flow("the call to action", "the flow-id", "screen") do
text("the body of the message")
end

The header() and footer() functions are optional in the whatsapp_flow() block. If supplied they can have the text you want available in the header and footer of the WhatsApp Flow message.

Here's an example of using a WhatsApp Flow in a card and then using the data collected.

card Flow do
flow =
whatsapp_flow("click!", "123456789", "SIGN_UP") do
header("this is the header")
text("this is the body")
footer("this is the footer")
end

then(ThankYou when has_text(flow.firstname))
then(Other)
end

card ThankYou do
text("Thank you! @flow.firstname @flow.lastname")
end

card Other do
text("You did not submit the flow.")
end

WhatsApp Flows are current best designed and tested in your Facebook Business Manager account. Meta's Getting Started section for WhatsApp Flows is a great place to start.

info

The ID of your flow and the available screens are supplied by Facebook Business Manager

note

Turn only guarantees the working of WhatsApp Flows for the following devices as per [Meta's policies] (https://developers.facebook.com/docs/whatsapp/flows/introduction/):

  • Android running OS 6.0 and newer
  • iPhone running iOS 12 and newer

Personalisation

Journeys allows you to build highly personalised impact services. You can store responses from users momentarily in variables while the conversation happens, or more permanently against the user's contact profile.

Save a response and use it in conversation

Any response from a user can be stored momentarily in variables and used in future interactions. This provides a more personalised user experience.

Syntax

# Save a result into a variable
variable_name = some_function_call()

# Use an expression to put the variable's value in a string
text("The value is: @variable_name")

Example

Let's revisit the button example. This time will will store the selected options and reference it in future responses. You will notice we refer to the stored value in strings by using the @ symbol. This makes use of expressions to include the button picked in the response.

card Card do
button_picked = buttons([Tea, Coffee]) do
text("What do you like to have?")
end
end

card Tea, "Tea 🫖" do
text("You selected @button_picked")
end

card Coffee, "Coffee ☕️" do
text("You selected @button_picked")
end

Update a contact profile field

This function updates a contact profile field with a defined value. Storing a value against the user's contact profile means it is available when the journey interaction ends and can be retrieved in future interactions.

The value can be any data captured while the user interacts with the Journey. All standard and custom fields can be updated with this function.

Syntax

update_contact(profileFieldName: "value")

Example

The following example creates a journey that asks the user what their name is, saves the response to a variable called name, and updates the contact profile name field with that value.

card One, then: Two do
name = ask("What is your name?")
end

card Two do
update_contact(name: "@name")
text("Your name has been updated to @contact.name")
end

Key things to note

  • The contact profile field needs to exist already.

Running another journey

A journey can start the execution of another journey. This can be useful to break large services into multiple journeys or to extract common logic into a separate journey.

Syntax

run_stack("<uuid-of-the-journey-to-run>")

Retrieving the UUID of a journey

To run a journey you need its UUID. You can retrieve the UUID of the journey to run by clicking the "Copy ID" button next to it in the list of journeys:

Copy ID button in the journey list

Example

This example shows you how to call a journey B from withing journey A. The run_stack() function requires the UUID of the journey you want to run.

This would be the code for Journey A:

card MyCardA do
text("Hello from Journey A")
run_stack("d58b0319-eb3f-4884-b43b-4ef0b7c37e1d")
text("Hello again from Journey A")
end

This the code for Journey B. Let's assume its UUID is d58b0319-eb3f-4884-b43b-4ef0b7c37e1d:

card MyCardB do
text("Hello from Journey B")
end

Key things to note

  • The flow goes back to the parent journey once the child journey ends.
  • There is currently no way to pass parameters or expect a return value from a child journey, however you can update fields on the contact profile, which is available both to the parent and child journey.

Scheduling

A journey can schedule the execution of another journey (or itself) at some time in the future. This can be useful to program messages that will be sent to the user in some time from now (e.g. 2 days from now).

Syntax

The journey can be scheduled using either the in argument or the at argument.

The in argument requires a relative time expressed in seconds. The scheduled journey will execute after the number of seconds specified.

schedule_stack("<uuid-of-the-journey-to-schedule>", in: seconds)

The at argument requires an exact date, in a datetime format. This date can be expressed using one of our expressions. The scheduled journey will execute at the date and time that has been defined.

If you wanted to schedule a journey exactly two days from now you could do:

schedule_stack("<uuid-of-the-journey-to-schedule>", at: datetime_add(now(), 2, "D"))

Or if you wanted to schedule a journey to run at 9am, the next monday after a certain date you could do:

base_date = date(2023, 2, 08))
schedule_stack("<uuid-of-the-journey-to-schedule>", at: datetime_next("monday", "09:00", base_date))

A journey that was previously scheduled can also be canceled, using our cancel_scheduled_stacks function:

card TextCard, then: MaybeCancel do
schedule_stack("<uuid-of-the-journey-to-schedule>", at: datetime_next("monday", "09:00", base_date))
schedule_stack("<uuid-of-the-journey-to-schedule>", at: datetime_next("tuesday", "09:00", base_date))
keep_going = ask("Do you want to keep receiving these messages? Type STOP to cancel them")
end

card MaybeCancel when keep_going == "STOP" do
cancel_scheduled_stacks("<uuid-of-the-journey-to-cancel>")
end

card MaybeCancel do
schedule_stack("40ac549a-a883-4c26-b3a2-6eaf8f799edb", at: datetime_next("friday", "10:00"))
end

Please note that this function cancels all scheduled journeys with the given UUID for that recipient, so in the example above, both scheduled journeys would be canceled.

Retrieving the UUID of a journey

To schedule a journey you need its UUID. You can retrieve the UUID of the journey to schedule by clicking the "Copy ID" button next to it in the list of journeys:

Copy ID button in the journey list

A basic reminder message

This example shows you how to schedule an individual reminder that will be sent to the user in 2 hours. You need to create two journeys. The schedule_stack() function requires the UUID of the journey you want to schedule.

This journey schedules a second journey to run in 2 hours from now. It refers to the other journey using its UUID:

card ScheduleReminder do
text("We'll send you a reminder message in 2 hours")
schedule_stack("d58b0319-eb3f-4884-b43b-4ef0b7c37e1d", in: 2 * 60 * 60)
end

This is the journey that will send the reminder message. Let's assume its UUID is d58b0319-eb3f-4884-b43b-4ef0b7c37e1d:

card SendReminder do
text("Here is your reminder")
end

A more complete use case

In this example we allow the user to decide when they want to receive a reminder message. Once again two journeys are required.

This is the first journey the user interacts with. It schedules the execution of the second journey referencing it by UUID:

card FirstCard, then: NoChoice do
buttons([OneHour, TwoDays, NextSaturday])
text("When would you like to be reminded?")
end

card NoChoice do
text("Please click on one of the buttons.")
end

card OneHour, "In 1 hour" do
text("Ok, we'll remind you in 1 hour.")
schedule_stack("5ac7f564-a158-4519-9e41-faac610d59ec", in: 60 * 60)
end

card TwoDays, "In 2 days" do
text("Ok, we'll remind you in 2 days.")
schedule_stack("5ac7f564-a158-4519-9e41-faac610d59ec", in: 2 * 24 * 60 * 60)
end

card NextSaturday, "Next Saturday at noon" do
text("Ok, we'll remind you next Saturday at noon.")
schedule_stack("5ac7f564-a158-4519-9e41-faac610d59ec", at: datetime_next("saturday", "12:00"))
end

This is the journey that will send the reminder message, Let's assume its UUID is 5ac7f564-a158-4519-9e41-faac610d59ec:

card SendReminder do
# Send a template in order to support reminders longer than 24 hours
send_message_template("reminder_template", "en", ["Time is up!"])
end

Key things to note

  • The first argument needs to be a valid UUID of an existing journey on the same number.
  • If a journey is scheduled to run after more than 24 hours, it should start by sending a message template. WhatsApp requires you to start a conversation with a template if the last message from the user is older than 24 hours.
info

When a schedule journey executes, it interrupts any other journey that is running at that time for the user.

OpenAI functions

We provide a comprehensive set of OpenAI functions. These functions power the AI blocks in the Journey Canvas but they can be used directly from code blocks as well.

Connecting

You can either use the built-in AI Assistant to connect to OpenAI as an Assistant or you can connect directly with a token. The connection details are stored in the variable returned and should be passed as an argument to any of the other OpenAI related functions.

card Start do
# connect using an assistant configured in your account
ai = openai_assistant()
# or connect directly with a token and model identifier
ai = openai_connect("the-token", "gpt-4o")
end

Adding Context

Add a single message, with an accompanying role to the context with openai_add_message(). This context will be included in chat completions.

ai = openai_add_message(ai, "user", "the message")

Add an image for when you want to ask a question about a user submitted image and limit the response size of the answer to 150 characters.

image_url = attachment_url(event.message.image.id)
ai = openai_add_image(ai, "user", "the message", image_url, 150)

Chat Completion

Chat Completion is the function for using OpenAI to generate text based on the context supplied

completion = openai_chat_completion(ai)

This returns a complex object with a default value:

%{
"error" => false,
"status" => 200,
"response" => %{
"sentences" => ["the generated.", "response."],
"__value__" => "the generated. response.",
"combined" => "the generated. response."
}

Transcribing Speech

transcription = openai_transcribe(ai, "https://example.org/file.mp3", "context", "en")

This returns the transcribed text of the supplied audio file. To transcribe received voice notes use the attachment_url() function to create a temporary URL and supply that value to this function's URL parameter.

Creating Speech

Convert text to audio speech in the given voice and using the given model. Refer to OpenAI's documentation on valid values for these parameters.

attachment = openai_create_speech(ai, "text to speak", "voice", "model")

This returns an attachment which can be used with audio("@attachment.external_id") to send over WhatsApp to an end user.

Lelapa.ai functions

We provide a comprehensive set of Lelapa.ai functions. These functions can be used directly from code blocks.

info

To use these functions you need to have an account at https://lelapa.ai/ and generate a token there.

Connecting

You can connect directly with a token from the Lelapa API. The connection details are stored in the variable returned and should be passed as an argument to any of the other OpenAI related functions.

card Start do
conn = lelapa_connect("the-token")
end

Intent Classification

This connects to Lelapa's Intent Classification API.

Allows users to classify inputs into specified intents with minimal training data across multiple languages.

result =
lelapa_intent_classification(
conn,
"Hey, how are you?",
[
["greeting", "Hello!"],
["greeting", "Hi there!"],
# how are you?
["greeting", "Habari yako?"],
["goodbye", "Goodbye!"],
["goodbye", "See you later!"],
# bye
["goodbye", "Kwaheri"],
# see you later ]
["goodbye", "Tutaonana baadaye"]
]
)

This returns the highest matched intent and the score:

%{
"__value__" => "greeting",
"score" => 0.74386334,
"intent" => "greeting"
}

Entity Recognition

This exposes the Entity Recognition API.

result =
lelapa_entity_recognition(
conn,
"President Ramaphosa gaan loop by Emfuleni Municipality."
)

and it returns the list of entities extracted:

[
%{
"entity" => "person",
"word" => "Ramaphosa",
"start" => 10,
"end" => 19
},
%{
"entity" => "location",
"word" => "Emfuleni Municipality",
"start" => 33,
"end" => 54
}
]

Sentiment Analysis

This exposes Sentiment Analysis API.

result =
lelapa_sentiment_analysis(
conn,
"am happy. i am sad. hello!"
)

And returns the following value:

%{
"__value__" => "positive",
"score" => 0.9991829991340637,
"text" => "am happy",
"label" => "positive",
"sentiments" => [
%{
"sentiment" => [%{"label" => "positive", "score" => 0.9991829991340637}],
"text" => "am happy"
},
%{
"sentiment" => [%{"label" => "negative", "score" => 0.960746169090271}],
"text" => " i am sad"
},
%{
"sentiment" => [%{"label" => "neutral", "score" => 0.8527688384056091}],
"text" => " hello"
}
]
}

The highgest scoring sentiment is the default value.

Translate

Exposes the Translation API.

result =
lelapa_translate(
conn,
"Lo musho ubhalwe ngesiZulu.",
"zul_Latn",
"eng_Latn"
)

Which returns the translation of the supplied text:

%{
"__value__" => "This sentence is written in Zulu.",
"translation" => [%{"translation_text" => "This sentence is written in Zulu."}]
}

Integrating with ML models hosted on Huggingface.co

Often a user experience can be improved by using a machine learning model to infer the meaning of the submitted request. A convenient way to do this is to use models hosted on Huggingface.co.

Here is an example using the hf_connect and the hf_infer functions to interact with Huggingface APIs.

Setting up a connection to a model

In the following card we setup a connection to a model hosted on Huggingface and store the reference to it in a variable called connection.

card Start, then: HFCard do
connection =
hf_connect(
"michellejieli/NSFW_text_classifier",
"<- insert your Hugggingface API token here ->"
)

query = ask("What is your question?")
end

In the example above we are using an freely available NSFW classification model called michellejieli/NSFW_text_classifier. This can be replaced with a model of your liking. If you have a model running on a custom Huggingface domain you can refer to it by URL:

hf_connect(
"https://my-custom.endpoints.huggingface.cloud/the-model",
"<- insert your Hugggingface API token here ->"
)

Calling the Inference API with the model

In the following card we use the hf_infer() function to submit the query to the model being referred to in the connection variable.

As per the model's documentation at Huggingface the response from the model has the following shape:

[
[
{
"label": "SFW",
"score": 0.9408659934997559
},
{
"label": "NSFW",
"score": 0.05913396179676056
}
]
]

Processing the model's results

The model in this example returns a list of results and we need to pick the result with the higest score. The model may already always return the highest scoring label as the first value in the list but given we're not sure about that we apply sorting in the journey anyway.

card HFCard, then: HFResult do
# This model returns a list with lists, we're wanting to read the first list item
# as that has the list of scorings for the given query
all_responses = hf_infer(connection, query)[0]
# Sort with descending score
sorted_responses = sort_by(all_responses, &(&1.score * -1))
# Get the first item from the sorted responses, this is the one with the highest score
top_response = sorted_responses[0]
end
note

Turn calls the Huggingface API with the wait_for_model parameter set to true. If the model in question takes some time to startup then the first interactions may hit a timeout. Note that the default timeout for Huggingface models is 5 seconds. This is currently not configurable.

Using the results to make an informed decision

Next we use the result stored in the top_response variable to determine what to do with the original query. In this case, because this is a demo, we're just returning the label and the score. In your applications you'll likely want to modify this to do something that suits your usecase and requirements.

card HFResult when top_response.label == "NSFW" do
text("This was classified as NSFW with a score of @top_response.score")
end

card HFResult when top_response.label == "SFW" do
text("This was classified as SFW with a score of @top_response.score")
end

card HFResult do
text("Huggingface returned something unexpected: @all_responses ")
end

Using the Huggingface Infer API from Journeys

Assigning labels to messages

When handling a pivotal moment in a journey, say a question received a specific response, it can be useful to add a label to a message. This allows it to be included in collections set up specifically to track messages with one or more labels applied.

Adding a label can be achieved with the add_label() function.

card AddLabel do
add_label("a plain label")
add_label("a label with metadata", extra: "label assigned at @now()")
end

If the label doesn't exist yet, it will be created. In some use cases it can useful to allocate extra metadata about the label. These can be added as key-value pairs as the second argument to the add_label() function. This metadata is stored on the assignment between the message and the label. If one retrieves the labels assigned to a message via the API, the metadata will be available there.

The label is always applied to the last message received from the user.

Assigning chats to an operator

Even in the most automated services, some questions simply need the attention of a human being. Journeys allow a chat to be assigned to an account within the Turn organisation the number is part of. Like labels, this assignment can be used to create collections that require the attention of a specific operator.

Assigning a chat to an operator can be achieved with the assign_chat_to() function.

card AssignChat do
assign_chat_to("user@example.org")
end

This will only work if the email address is of a known account within the number's organisation. If the email address is not recognised, it will clear any chat assignment.

To explicitly remove an operator assignment from a chat use the unassign_chat() function:

unassign_chat()

Logging in the Journeys Simulator

Sometimes we want to be able to understand and debug the behavior of our journeys without adding messages to the chat. The Journeys Simulator allows us to do this by logging messages to the simulator with the log function.

A simple example where we just want to add text.

card TextAndLogMessage do
text("Hello this is my first text.")
log("a simple message")
text("Hello this is my second text.")
end

log_simple_text_example.png

However, you can also use it to log more complicated data structures, such as the dictionary below. You could also log structures such as @contact, made available within the chat.

log_data_structure_dict_example.png

card LogData do
text("@params.items.greeting!")
log("@params")
log("@contact")
end

Will result in the following:

log_data_structure_example.png

info
  • You need to pass in a string. If you want to evaluate a more complicated structure like contacts, you need to use an expression - so use log("@contact"), not log(contact). The Journey will highlight this issue as an error if you attempt to do this.

  • log currently only works within the Journeys simulator. They will not affect interactions when using a Stack directly via WhatsApp.

  • Logs are temporary. They will disappear each time you start a new session with the Journeys simulator

Advanced functions

Interaction timeout

Journeys have a timeout value. The default is 300 seconds (equating to 5 minutes). If a user takes longer that 5 minutes to respond to a request for input (for example a question from ask()) the journey will expire. This means that it won't respond as expected if the user comes back after more than 5 minutes.

You can change the timeout to any value that makes sense to you. Timeout values need to be specified in seconds.

Syntax

The timeout is defined with the interaction_timeout optional journey parameter.

stack MyJourney, interaction_timeout: 20 do
...
end

Example

The following example gives a user 10 seconds to reply in order to win. If the user takes longer than 10 seconds the journey has expired and the user did not win.

The user replied within 10 seconds. Example showing an interaction before timeout

The user took longer than 10 seconds to reply and journey expired. Example showing an interaction timeout

interaction_timeout(10)

card Card, then: Win do
ask("You have 10 seconds to reply to win!")
end

card Win do
text("congrats! you replied on time")
end

Key things to note

  • Timeout values need to be specified in seconds.
  • The default timeout is 300 seconds (equating to 5 minutes).