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.".
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.
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".
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.
Key things to note
- You need to use both the
buttons()
and thetext()
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 thebuttons()
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.
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 thetext()
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
.
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.
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.
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.
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.
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!".
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 thetext()
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!".
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 thetext()
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!".
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 thetext()
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!".
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 thetext()
functions. - The
document()
function holds the URL and an optionalfilename
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.
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.
The ID of your flow and the available screens are supplied by Facebook Business Manager
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:
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:
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.
When a schedule journey executes, it interrupts any other journey that is running at that time for the user.
Assistant AI functions
We provide a comprehensive set of assistant AI 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 AI vendors 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 AI related functions.
Choose openai
and meta_ai
as AI vendors.
Vendor | Vendor key |
---|---|
OpenAI | openai |
Meta Llama | meta_ai |
card Start do
# connect using an assistant configured in your account
ai = ai_assistant("the-assistant-id")
# or connect directly with a vendor ('openai' or 'meta_ai'), a model identifier and a token
ai = ai_connect("openai", "gpt-4o", "the-token")
end
Finding the assistant ID
To find the assistant ID go to the assistant detail page and click the button 'Copy ID'.
Adding Context
Add a single message, with an accompanying role to the context with ai_add_message()
. This context will be included in chat completions.
ai = ai_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.
Images are only supported for an assistant with "openai" as a vendor. See OpenAI Adding Images below.
Chat Completion
Chat Completion is the function for using AI to generate text based on the context supplied
completion = ai_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."
}
Text classification
Text Classification is the function for using AI to choose one of the given options.
ai_text_classification(ai, "user-input", "system-prompt", ["option 1", "option 2", "option 3"])
This returns a complex object where response is an object with a classification value. The value is the zero-based index of the option. If no option matches the classification will be -1.
%{
"error" => false,
"status" => 200,
"response" => %{
"sentences" => ["{\"classification\": 1} "],
"__value__" => 1,
"functions" => [],
"combined" => [
%{"sentence" => "{\"classification\": 1} ", "type" => "sentence"}
]
}
}
Transcribing Speech
Transcribing is only supported for an assistant with "openai" as a vendor. See OpenAI Transcribing Speech below.
Creating Speech
Speech is only supported for an assistant with "openai" as a vendor. See OpenAI Speech below.
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.
OpenAI: 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
OpenAI: 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")
OpenAI: Adding images
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)
OpenAI: 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."
}
OpenAI: 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.
OpenAI: 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.
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
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
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
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.
card LogData do
text("@params.items.greeting!")
log("@params")
log("@contact")
end
Will result in the following:
-
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 uselog("@contact")
, notlog(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.
The user took longer than 10 seconds to reply and journey expired.
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).