Uh oh! We couldn’t find any match.

Please try other search keywords.

Bixby Developer Center

Guides

Modeling Actions

An action defines an operation that Bixby can perform on behalf of a user. If concepts are nouns, actions are verbs. Examples of actions include:

  • FindRestaurants: search for restaurants.
  • ConvertTemperature: perform a temperature conversion computation.
  • BookHotel: perform a transactional operation that books a hotel room.

In programming terms, you can think of an action model as an interface specification, describing the action inputs and action output. You also need to implement a JavaScript function to do the real work of actions, such as making API requests and computations.

Defining an Action

An action has a type, an output, and some input child keys all defined in the reference guide.

As with concept properties, action inputs can have cardinality constraints specified. The min constraint can define if an input is optional or required, and max can define if an input is single or multi-cardinal.

action (RollDice) {
collect {
input (numDice) {
type (NumDice)
min (Required)
max (One)
}
input (numSides) {
type (NumSides)
min (Required)
max (One)
}
}
output (RollResult)
type (Calculation)
}

Keep in mind that, if a user request doesn't fulfill the cardinality for inputs, execution halts. Bixby communicates this requirement to the user via a prompt.

If an input is required, but no value is specified, the system provides an elicitation prompt, eliciting a value from the user. This is a violation of the min constraint. If an input is single cardinal, but you get more than one value, the system prompts with a selection prompt. This is a violation of the max constraint.

Some actions have side effects, while others do not. You would expect FindRestaurants or ConvertTemperature to run without side effects, while BookHotel models an action that changes state in another system. When defining an action, you should choose an appropriate action type to tell Bixby whether the action will have side effects.

The following non-transactional action types indicate that the action has no side-effects on external systems. When choosing an action type, select the narrowest possible value.

  • Calculation: isolated computation on the inputs, guaranteed to return a result.
  • Constructor: isolated construction of the output using only the provided inputs, and no external services.
  • Fetch: simple lookup of additional data from a single input.
  • Search: search action, using the inputs as constraints.

If your action will have side effects on external systems, use a transactional action type. You can learn more about Transactional Workflows in Advanced Topics.

The following action definition, FindMovie, takes multiple inputs as search constraints and uses the Search type:

action (FindMovie) {
type (Search)
description (Find a movie by optional director, genre, rating, actor(s), and/or title.)
collect {
input (movieActor) {
type (ActorName)
min (Optional)
max (Many)
}
input (movieDirector) {
type (DirectorName)
min (Optional)
}
input (movieGenre) {
type (MovieGenre)
min (Optional)
}
input (movieRating) {
type (MovieRating)
min (Optional)
}
input (title) {
type (MovieTitle)
min (Optional)
}
}
output (Movie) {
}
}

Selection Rules

To give you more control over selections made on the user’s behalf, you can use selection rules as a way for you to better control the selection of ambiguous input values instead of prompting the user. These selection rules are invoked whenever a maximum cardinality check is violated, so if max is set to Many, selection rules cannot be used.

Within the default-select key, you enable selection learning using the with-learning key. You then add rules using the new with-rule key.

You can also add other selection learning behaviors (like NoPreferences) as options.

Within selection rules, you can use select-first and select-min to choose the first candidate value or the lowest scoring value, respectively. You can optionally use sort-key within select-first to re-sort a list of inputs. If sort-key is absent, Bixby selects the first input as-is without sorting. You can use multiple sort-key’s in order to perform a compound sort and further refine select-first rules.

Below is an example of choosing a learnedTipPercent for calculating a tip. It uses an expression in select-min to determine the lowest option.

action (ConvertLearnedTipPercent) {
type (Calculation)
collect {
input (learnedTipPercent) {
type (TipPercentEnum)
min (Required)
max (One)

default-select {
with-learning{
option(NoRanking)
}
with-rule {
select-min {
expression(learnedTipPercent eq '15%' ? 0 : 1)
}
}
}
}
}
output (TipPercent)
}

For information on the difference between Selection Rules and Selection Learning, see the Selection Learning topic.

To understand how selection rules and selection learning behave, see Selection Behavior.

Input Groups

When defining actions, you can specify cardinality on individual inputs.

Extending this, you can use input-group keys to apply this requirement across multiple inputs.

For example, if a user wants to find recipes, you can direct them to be specific about the kinds of recipe they want. With input groups, the user can ask for Italian recipes, or chicken recipes, or even Italian chicken recipes, but they must specify at least one of the constraints:

action (FindRecipes) {
type (Search)
description (Looks up recipes by dish name, cuisine, ingredient, diet, course, or food allergy)
collect {
input-group (atLeastOne) {
requires (OneOrMoreOf)
collect {
input (meal) {
max (Many)
type (MealName)
}
input (dish) {
type (FoodName)
}
input (ingredientToInclude) {
max (Many)
type (IngredientName)
}
input (ingredientToExclude) {
max (Many)
type (IngredientNameToExclude)
}
input (allergy) {
max (Many)
type (Allergy)
}
input (diet) {
max (Many)
type (Diet)
}
input (cuisine) {
max( Many)
type (CuisineStyle)
}
}
}
}
output (Recipe) {
}
}

Notice that the cardinality has a slightly different meaning for input-groups. With input-group keys, the min value determines whether an input is required and the max value determines the maximum number of input declarations within an input-group to allow in a query.

In this example, users can specify multiple inputs:

"Give me chicken and broccoli Italian pasta recipes with no peanuts or soy because of my allergy."

Iterable Inputs

Marking an input as iterable rather than marking it as multi-cardinal will result in the action being called again for each input of that type which presents itself. For example, FindRestaurants has the searchRegion set as iterable:

input (searchRegion) {
iterable
type (SearchRegion)
min (Required)
}

An utterance such as "Find restaurants in San Jose and San Francisco" calls the action FindRestaurants twice, once for each city.

Note that iterable should be used with care because, if there are many inputs, it will call the action many times. There cannot be more than one iterable input at a time because of a large number of combinations are possible. The results of this query are merged into a single list.

Input Validation

Action declarations also support simple validation logic. A validation consists of validate and a condition that can trigger a halt, prompt, or replan.

For example, you could implement an action that cancels a rideShare reservation. During cancellation, you can ensure that ride isn't already canceled or complete. You can implement this using action input validation:

collect {
input (activity) {
type (Activity)
min (Optional)

default-init {
intent {
goal: viv.rideShare.FindLastActivity
}
}

validate {
if (!exists(activity)) {
halt {
dialog {
template ("Not sure what to cancel. I didn't find any recent rides.")
}
}
}
else-if (activity.status == 'Canceled' || activity.status == 'Completed' || activity.status == 'InProgress') {
halt {
dialog {
if (activity.status == 'InProgress') {
template ("This ride is already in progress!")
}
template ("This ride is already ${value (activity.status)}!")
}
}
}
}
}
}

Declaring input validation in the action model is often the best solution because the validation rules become part of the model. Another way to validate inputs is through action implementation preconditions, which is not part of the model and can access special $id identifiers. However, these validation rules only have access to action inputs, and can't validate API responses. Through error handling, you can move the validation logic into your action JavaScript and use checked errors by Throwing Exceptions.

Preconditions and exception throwing are both ways of doing validation, but validating inputs closest to where they are defined is the cleanest way, if not the most flexible way, to do validation. For more flexibility, you can use preconditions and exceptions.

Input validation happens after default-init blocks are evaluated, and after cardinality and requires constraints are checked. Where the validate key appears in the input block doesn't matter.

Error Handling

Checked errors allow you to throw a named error through JavaScript. You can then catch and handle the error in the action. The first part of error handling, involves handling the error through your action model, which we cover here. The second part involves throwing the error, which is handled through JavaScript in your action implementation.

Actions can declare handling of specific checked errors through the throws key in the action's output. Here's an example that declares an UnsupportedCondition error, which is developer defined:

action (QuantifyWeatherCondition) {
type (Calculation)
collect {
input (condition) {
type (WeatherCondition)
min (Required)
max (One)
}
input (weather) {
type (Weather)
min (Required)
max (One)
}
}
output (WeatherConditionQuantity) {
throws {
error (UnsupportedCondition) {
on-catch {
halt {
dialog { template ("#{event(weather, 'Result')}") }
display (weather)
}
}
}
}
}
}

Each checked error declaration specifies an error code. We recommend that you implement error handling that addresses the error gracefully. For example, if the error involves authorization, you may want to take the user through an authorization flow instead of simply presenting an authorization error.

In addition to dialog, you can use other effects such as replace, halt, drop, and replan. In the example above, the error also displays one of the inputs to the action, weather. Even though the action wasn't able to answer a specific question about the weather, the user might still like to see the relevant weather information.

Another type of checked error might mean that the action needs more clarification or additional input from the user. You can handle these situations with a prompt:

...
error (MultipleItemsMatched) {
property (candidates) {
type (FoodName)
min (Required)
max (Many)
}
on-catch {
prompt (menuItemName) {
min (1)
max (1)
mode (Replace)
candidates (candidates)
}
}
}
...

In other cases, you can offer the user a replacement intent, using replan:

...
error (PriceChange) {
property(newRequest) {
type (viv.hotel.ReservationRequest)
min(Required) max (One)
}

on-catch {
replan {
dialog {
template ("The price has changed from #{value(request.roomRateInfo.rateSummary.total)} to
#{value(newRequest.roomRateInfo.rateSummary.total)}.
Continue booking with this new price, or search for another hotel.")
}
intent {
goal: viv.hotel.BookHotelRoom
value-set: viv.hotel.ReservationRequest { $expr(newRequest) }
}
}
}
}
...

A checked error can also declare that it expects a property as part of the error message and uses the property in the error dialog. You first declare the concept type that you are expecting. Then, in the JavaScript action implementation where you throw an error, you can pass along the data for the concept.

For example, this RequiredOptionNotSpecified error expects a property optionCategory:

output (Order) {
throws {

error (RequiredOptionNotSpecified) {
property (optionCategory) {
type (ProductOptionCategory)
description (The optionCategory to prompt for!)
}
on-catch {
prompt (options) {
min (optionCategory.minCardinality)
max (optionCategory.maxCardinality)
mode (Add)
candidates (optionCategory.options)
}
}
}
}
}

You can also catch unnamed or unknown errors. You should do this directly in the action that might produce them.

Here is an example of how you can use unknown-error within an action:

action (MyAction) {
...
output (MyOutput) {
throws {
error (Error1) {
on-catch {
halt {
dialog { template ("Error 1 handled") }
}
}
}
unknown-error {
on-catch {
halt {
dialog { template ("An unexpected error occurred - please try again.") }
}
}
}
}
}
}

To learn more about the JavaScript implementation of error handling, read about Throwing Exceptions or read the reference documentation on throws.