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

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