Yalo Standard Libraries
This section of the Flow Builder guide covers advanced and developer-centric tools used to create complex conversational Flows.
Introduction
Developers and Advanced users of Flow Builder use three primary toolkits to create complex Flows:
- Yalo's Standard Libraries: a set of methods that simplify the most common scripting use cases
- Handlebars are used in messages for easy variable recall: https://handlebarsjs.com/guide/
- Lua: a lightweight and easy-to-learn scripting language. https://www.lua.org/
The Standard Libraries
Yalo has developed a set of custom functions in the Lua environment to make common operations within Flow Builder simple and efficient to implement.
Studio supports the following Lua data types
- nil
- boolean
- number
- string
- table
More information.
In the following sections, each method of the standard library will be described in the following way:
HTTP.post(url: string, body?: table, headers?: table) -> HttpResponse
How to interpret this function:
HTTP the namespace of the Standard Library
post the method to be used
url: string url is a required parameter and the expected data type is string
body?: table body is an optional parameter -- as indicated by the '?' suffix -- and not required
for the method call to be valid
HttpResponse the data type returned by the method. In the case of HTTP.post, an object
(Lua table) that contains multiple properties inside it. (See the HTTP Standard Library for more information on this data type)
HTTP.post(url: string, body?: table, headers?: table) -> HttpResponse
How we can interpret the function.
HTTP The namemespace of the Standard Library.
post The method name.
url: string url is the expected input and string the datatype.
body?: table if the input has a ? suffix it means it is an optional argument
meaning that HTTP.post(url) would be a valid call too.
HttpResponse This is the data type that the function returns in this case is an
object (lua table) that contains multiple properties inside (See HTTP
Standard Library for information on this data type)
Debug Library
Using the debug library, you can send messages to WhatsApp from inside Lua code. Debug messages should be used to debug/troubleshoot issues in the execution of your Flow. Use the messages section of Studio to deliver messages to your end-user as it offers more flexibility for handling messages.
Methods
Debug.print(text: string) -> nil
-- send a variable value to whatsapp
local name = "Studio Core"
Debug.print(name)
-- you can also send a Lua table which will be formatted as a JSON string
local user = {
name = "Studio Core"
email = "[email protected]"
}
Debug.print(user)
-- Whatsapp output:
-- {"name":"Studio Core", "email":"[email protected]"
Flow Library
The Flow library is the way we can move between steps in a Flow in Flow Builder. If you want to move to a new step during the execution of a Lua block you can use this library.
Warning
Use of the Flow function will halt the execution of the Lua code so any code below this function will not be executed.
Methods
Workflow.branch(stepId: number) -> nil
Examples
local age = 20
-- We want to move to a Step 1 if the age is equals or greater than 18
-- else we want to move to a Step 2 if its lower than 18.
if age >= 18 then
Workflow.branch(1)
else
Workflow.branch(2)
end
JSON Library
The JSON library helps transform between JSON strings and Lua Tables
Methods
JSON.encode(object: table) -> string
JSON.decode(jsonString: string) -> table
Examples
local user = {
name = "Studio Core",
email = "[email protected]"
}
local userJsonString = JSON.encode(user)
-- userJsonString
-- {"name":"Studio Core","email":"[email protected]"}
local userJsonString = '{"name":"Studio Core","email":"[email protected]"}'
local user = JSON.decode(userJsonString)
Debug.print(user.name) -- Would send "Studio Core"
HTTP Library
The HTTP library is a Lua container for the Axios JavaScript Library". Use the HTTP library to connect to an external http service from your Flow.
Methods
HTTP.get(url: string, headers?: table) -> HttpResponse
HTTP.post(url: string, body?: table, headers?: table) -> HttpResponse
HTTP.put(url: string, body?: table, headers?: table -> HttpResponse)
HTTP.patch(url: string, body?: table, headers?: table) -> HttpResponse
HTTP.delete(url: string, headers?: table) -> HttpResponse
-- sample response
HttpResponse
{
data: table,
status: number -- 200
statusText: string -- OK
config: table { url: string, method: string, data: table, headers: table }
}
Examples
-- API that returns a random generated user
url = "https://randomuser.me/api/"
-- Make the HTTP Get request
response = HTTP.get(url)
-- Notice Lua first index is 1 not 0.
user = response.data.result[1]
-- We can send the name to whatsapp
Debug.print(user.name.first .. " " .. user.name.last) -- Would send "First Last"
-- Fictional REST API to handle users
url = "https://external-api-service.net/v1/users"
body = {
name = "Studio Core",
email = "[email protected]"
}
headers = {
authorization = "Bearer securitytoken",
[content-type] = "application/json" -- Notice the use of [ ] is the variable has
} -- invalid characters like ( - )
-- Make the HTTP Post request with body and headers
response = HTTP.post(url, body, headers)
if response.status == 201 then
Debug.print("The user has been created")
else
Debug.print("There was a problem creating the user")
-- You could inspect the contents of response.data which
-- could contain the error message that caused it to fail
Invoke Library
You can use AWS lambdas by using the INVOKE library.
Notice
Only lambdas inside Yalo's AWS are currently supported.
Methods
-- Fictional REST API to handle users
url = "https://external-api-service.net/v1/users"
body = {
name = "Studio Core",
email = "[email protected]"
}
headers = {
authorization = "Bearer securitytoken",
[content-type] = "application/json" -- Notice the use of [ ] is the variable has
} -- invalid characters like ( - )
-- Make the HTTP Post request with body and headers
response = HTTP.post(url, body, headers)
if response.status == 201 then
Debug.print("The user has been created")
else
Debug.print("There was a problem creating the user")
-- You could inspect the contents of response.data which
-- could contain the error message that caused it to fail
Examples
-- Name of the Lambda
lambda = 'add-product-shopping-cart'
-- Body to the Lambda
body = {
name = "Studio NG",
price = 9999
}
-- Since the body of the lambda has to be a string we
-- need to convert our body to a JSON string.
result = Invoke.cloudFunction(lambda, JSON.encode(body))
if result.success then
Debug.print(result.payload) -- This would send the lambda response to whatsapp.
else
Debug.print("There was a problem executing the lambda")
end
Input Library
The Input library provides methods for matching strings using things the ability to pass a list of values to match against or using custom RegEx. Input also provides methods for retrieving the end-user profile and the latest end-user message details.
Methods
Input.match(regex: string, expression: string,
value?: string, options?: table) -> boolean
Input.intent(domain: string, message: string) -> IntentResponse
Input.getMessage() -> ContextMessage
Input.getProfile() -> ContextProfile
-- Optional Configuration Options for match
options
{
toLower = true,
removeDiacritics = true, -- e.g. hĂślĂĄ --> hola
reduceRepeatedAlphanumerics = true -- If you have 3 or more repeated chars, it reduces them to 2 repeated chars. e.g. aaaa -> aa
}
IntentResponse
{
status: string
message: string
domain?: string
categories?: table
confidence?: table
}
ContextMessage
{
message: string
type: string -- text, interactive, image, video, file, location, audio
step: string -- current step name
}
ContextProfile
{
userId: string
name: string
}
Examples
-- Check if a value exists in a list of keywords
match = Input.match('contains', 'blue|red', 'blue')
-- match would be True.
-- Check if a value matches to a custom regex
-- Using a phone regex which accepts only numbers, () and -
match = Input.match('regex', '^[0-9()-]+$', '(656) 123-1234')
-- match would be True.
-- Check if the value is equal to the other value
match = Input.match('isEqualTo', 'Studio NG', 'Other Product')
-- match would be False.
-- Explicitly state the value to compare
Input.intent(domain: string, value: string)
-- Implicitly infer the value to compare from the last user message
Input.intent(domain: string)
-- Example the explicit way
-- We use generic-en domain for the intent.
intent = Input.intent('generic-en', 'hello!')
if intent.confidence.greeting > .025 then
Debug.print("We understand you are doing a greeting intent")
else
Debug.print("Not a greeting intent")
end
input = Input.getMessage()
Debug.print(input.message) -- Would send the latest user message to whatsapp.
profile = Input.getProfile()
Debug.print("Hello " .. profile.name .. " your phone is " .. profile.userId)
Context Library
The context library gives us access to the ephemeral context of the current user (brain) in which we can store information that is going to live through the lifespan of the Flow session which is defined in the Flow configuration in Flow Builder. The information is stored in a key:value datastore in which we can store JSON serialized objects too.
Methods
Context.set(key: string, value: string|table) -> nil
Context.get(key: string) -> string
Context.delete(key: string) -> nil
Context.check(key: string) -> boolean
Examples
local email = "[email protected]"
Context.set("email", email)
email = Context.get("email")
Debug.print(email) -- Would send [email protected] to whatsapp
Context.delete("email") -- Would delete the value from the user context
Context.set("email", "[email protected]")
if Context.check("email") then
Debug.print("Email exists in the Context")
end
-- Retrieving JSON string from the Context
-- and convert it back to a Lua table
-- Context (user) = '{"name":"Studio Core","email":"[email protected]"}'
userJsonString = Context.get("user")
-- Convert to Lua table using JSON (See JSON Documentation above)
user = JSON.decode(userJsonString)
-- This would send "Studio Core"
Debug.print(user.name)
Profile Library
The profile library is very similar to the context library. The main difference is that the information stored with the profile library does not expire* and is encrypted** in the datastore. The main use for this is information that would span across multiple workflow sessions like a user address.
Methods
Profile.set(key: string, value: string) -> nil
Profile.get(key: string) -> string
Profile.delete(key: string) -> nil
Profile.check(key: string) -> boolean
Examples
address = "Studio Avenue 123"
Profile.set("address", address)
address = Profile.get("address")
Debug.print(address) -- Would send "Studio Avenue 123"
Profile.delete("address") -- Would delete the value from the user profile
Profile.set("addresss", "Studio Avenue 123")
if Profile.check("address") then
Debug.print("Address exists in the Context")
end
FAQs Library
The FAQ library uses the Sheets feature of Flow Builder to provide easy access to the questions and the result from querying a question against the multiple available questions.
Methods
FAQ.searchQuestion(question: string, sheetId: string, config?: table) -> FAQResponse
config
{
outputAttr: string -- Name of the key in which the response is stored
numAnswers: number -- Number of answers returned from the service
minSingleSimilarity: number -- The threshold for single matching
minMultipleSimilarity: number -- The threshold for multiple matching
lambdaScript: string -- The AWS lambda to process the FAQ matching
}
FAQResponse
{
success: boolean -- True if the lambda execution for FAQs was successful
singleMatch: boolean -- True if there was an exact match for the question
multipleMatch: boolean -- True if multiple questions were matched
noMatch: boolean -- True if there was no match for the question
searchId: string -- The search id returned by the serverles semantic search
documentId: string -- The document id from the search results
faqs: FAQsQuestions -- The list of all the matching questions
matchQuestion: string -- If the response is singleMatch this contains the matched question. Or N/A if no answers where found.
matchAnswer: string -- If the response is singleMatch this contains the matched answer. Or N/A if no answers where found.
}
FAQsQuestions
{
index: number
indexEmoji: string
title: string
}
Examples
sheetId = '61258430ca635768d3e07932'
-- This will send the latest user message as a question to the FAQs
-- table specified in the second argument.
result = FAQ.searchQuestion("What do you sell?", sheetId)
-- If the question has a single match
if result.singleMatch then Workflow.branch(13) end
-- If there were mutliple questions matched
if result.multipleMatch then Workflow.branch(14) end
-- If there was no match for the question.
if result.noMatch then Workflow.branch(15) end
-- If multiple questions matched the question asked then a context variable
-- FAQsQuestions will be available which will contain all the matched questions
-- which can be displayed to the user using handlebars with the following snippet
-- Message (with handlebars)
{{#each FAQsQuestions}}
{{index}}) {{title}}
{{/each}}
-- On the Actions following that message you can check the user response with the
-- index of the question he wants to see. Using the selectQuestion if the index is valid
-- it would set that question as the single match and you can display it to the user
-- in a another step message.
isValid = FAQ.selectQuestion(Context.get('userMessage'))
if isValid then Workflow.branch(13) end
-- Step 13 (Message)
Q: {{FAQsMatchQuestion}}
A: {{FAQsMatchAnswer}}
Location Library
The location library provides methods to search for and display locations near the end-user.
Methods
Location.stores(keyword: string, radius: number, placeType: string, address: string) -> LocationResult
LocationResult
{
success: boolean -- True if there was something matched
places: LocationDetail
}
LocationDetail
{
address_components: AddressComponent[]
adr_address: string
business_status: string
formatted_address: string
formatted_phone_number: string
geometry: Geometry
icon: string
international_phone_number: string
name: string
opening_hours: ExtendedBusinesHours[]
photos: Photo[]
place_id: string
plus_code: Plus
price_level: number
rating: number
reference: string
reviews: Review[]
types: PlaceType[]
url: string
user_ratings_total: number
utc_offset: number
vicinity: string
website: string
}
AddressComponent
{
long_name: string
short_name: string
types: string
}
ExtendedBusinessHours
{
weekday_text: string[]
periods {
close {
day: number
time: string
}
open {
day: number
time: string
}
}
}
Examples
-- Explicitly state the address you want to nearby search
stores = Location.stores(keyword, radius, PlaceType, address)
-- Implicitly infer the address value from user's last message
stores = Location.stores(keyword, radius, PlaceType)
if stores.success then
-- Storing the results in storesFound context
Context.set("storesFound", stores.places)
else
Debug.print("We did not find any location/store")
end
-- You can use handlebars in another step to display this information
-- Step Message
{{#each storesFound}}
đ{{name}}
đ{{url}}
đşď¸{{formatted_address}}
đ{{formatted_phone_number}}
{{/each}}
String Library
Easily manipulate string type data
Methods
String.cleanText(text: string) -> string
String.onlyNumbers(text: string) -> number
String.split(text: string) -> table
String.encodeURI(text: string) -> string
Examples
clean = String.cleanText(string)
-- e.g. "äDiós"
-- returns "aDios"
number = String.onlyNumbers(string)
-- e.g. "my age is 25"
-- returns "25"
-- e.g. "my age is 25 next friday the 20th"
-- returns "2520"
message = 'Apple iPhone 13 Pro Max'
words = String.split(message)
-- words = {'Apple', 'iPhone', '13', 'Pro', 'Max'}
message = 'Apple iPhone 13 Pro Max'
urlString = String.encodeURI(message)
-- urlString = Apple%20iPhone%2013%20Pro%20Max
Updated about 2 years ago