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 the following:

  • 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. Simple functionality in your Bixby capsule might be possible entirely within action models. More complicated work, such as making API requests and performing computations, requires you to write a JavaScript function. Action models are connected to an implementation using an endpoint declaration (endpoints.bxb). A local endpoint connects the action to a JavaScript function in your capsule that will be executed within Bixby; a remote endpoint connects the action to an API on an external server that will accept the action inputs and return the expected output.

As you start to build out your actions, make sure that you have read Bixby's Design Guidelines so that your models follow a consistent user experience.

Note

For an example of how to write simple functions entirely with models and no JavaScript, see the No Code Design Patterns sample capsule.

Action Definitions

Actions have one or more input concepts specified by name, an output concept, and a type. Actions can, at certain points, also define dialog for Bixby.

An action's type, such as Calculation, Constructor, or Search, tells Bixby about the action's purpose, and is important in planning the execution graph. The Side Effects section, below, discusses some action types; read the type reference documentation for a complete discussion of the various types available. If you don't specify a type for your action, it defaults to Search, the most general type with the fewest limitations.

Note

An action can have more than one input, but it cannot have more than one input of the same concept type. If you were building an action for searching flights, for example, you could not specify the departure and arrival airport inputs as both having types of Airport; you would need a DepartureAirport input type and an ArrivalAirport input type, which could both be assigned roles of Airport.

Cardinality

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 (NumDiceConcept)
min (Required)
max (One)
}

input (numSides) {
type (NumSidesConcept)
min (Required)
max (One)
}
}
output (RollResultConcept)
type (Calculation)
}

View master on GitHub

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.

Action outputs do not enforce cardinality, and can return any number of values for the specified concept type. If the output of an action is consumed by the input of another action, the cardinality is enforced by that input. An output of a single value, or zero values, can match either a single or multi-cardinal input. An output of two or more values, if matched with a single cardinality input, will cause Bixby to prompt the user to choose only one of the values.

Side Effects

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. Certain types are for specific features, so choose the type that best fits your action's purpose. For more information and examples of each action type, see the following linked descriptions.

  • 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 these types in the Transactional Workflows Developers' Guide.

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

action (FindShoe) {
type (Search)
collect {
input (name) {
type (Name)
min (Optional)
}
input (type) {
type (Type)
min (Optional)
}
input (minPrice) {
type (money.MinPrice)
min (Optional)
max (One)
}
input (maxPrice) {
type (money.MaxPrice)
min (Optional)
max (One)
}
}
output (Shoe)
}

View master on GitHub

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 size for creating a shirt Item. It uses an expression in select-min to determine a default size.

action (CreateItem) {
type (Constructor)
collect {
input (shirt) {
type (Shirt)
min (Required)

default-init {
intent {
goal: FindShirt
}
}
}
input-group (itemProperties) {
requires (OneOrMoreOf)
collect {
input (size) {
type (Size)
min (Optional)

//Lists all available options - this is used by default-select
default-init {
intent {
goal: Size
value-set: Size { Size(ExtraSmall) Size(Small) Size(Medium) Size(Large) Size(ExtraLarge) }
}
}
default-select {
//picks Medium, otherwise pick first item from the list of candidates
with-rule {
select-min {
expression (exists(size) && size == 'Medium' ? 0 : 1)
}
}
with-learning {
}
}
}

input (quantity) {
type (Quantity)
min (Optional)

default-init {
intent {
goal: Quantity(1)
}
}
}
}
}
}

output (Item)
}

View master on GitHub

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, in the Space Resorts sample capsule, if a user wants to find a resort, you can direct them to be specific about the type of resort they want. With input groups, the user can ask for resorts on Mars, resorts with spas, or even resorts on Mars with spas, but they must specify at least one of the constraints:

action (SelectResort) {
description (Select a HabitatProd from a list by name)
type (Calculation)

collect {

input (resorts) {
type (SpaceResort)
min (Required)
max (Many)
default-init {
//get the candidates from the input-view (prompt context)
intent {
goal-set: SpaceResort {$expr("getPreviousPage('SpaceResort')")}
}
}
}

input-group(search) {
requires (OneOrMoreOf)
collect {
input (name) {
description (space resort name)
type (Name)
min (Optional)
}
input (planet) {
description (space resort name)
type (Planet)
min (Optional)
}
input(searchCriteria) {
description (search criteria)
type (SearchCriteria)
min (Optional)
max (Many)
}
}
}
}
output (SpaceResort) {
on-empty {
// TODO
}
throws {
error (MultipleMatches) {
property (matches) {
type (SpaceResort)
min (Required)
max (Many)
}
on-catch {
replan {
intent {
goal { SpaceResort @prompt-behavior(AlwaysSelection) }
value { $expr(matches) }
}
}
}
}
}
}
}

View master on GitHub

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:

"Find me a space resort on Mars that's good for kids and has a spa."

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 shirt order. During cancellation, you can ensure that the shirt order exists. You can implement this using action input validation:

action (CancelCommittedOrder) {

type (CancelActivity)

confirm {
by (core.Confirmation)
}

collect {
input(receipt){
type(Receipt)
min (Optional)

default-init {
intent {
goal: FindLastReceipt
}
}

validate {
if (!exists(receipt)) {
halt {
dialog {
template("Not sure what to cancel. I didn't find any recent shirt orders.")
}
}
}
if (exists(receipt) && receipt.orderState != 'Ordered') {
halt {
dialog{
template("This order is already #{value (receipt.orderState)}!")
}
}
}
}
}
}

output(Receipt)
}

View master on GitHub

Declaring input validation in the action model is often the best solution because the validation rules become part of the model. This is often the cleanest way to perform validation. Alternatively, you can move the validation logic into your action JavaScript and use checked errors by Throwing Exceptions, which can be more flexible.

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.

You can see an example of Input Validation in the Input Validation and Error Handling sample capsule.

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 UnsupportedSearchCriteria error, which is developer defined:

action (FindSpaceResorts) {
description (Find space resorts)
type (Search)
collect {
input (name) {
type (Name)
}
input (planet) {
type (Planet)
}
input (searchCriteria) {
type (SearchCriteria)
max (Many)
}
}
output (SpaceResort) {
throws {
error (UnsupportedSearchCriteria) {
on-catch {
// TODO: drop the unsupported criterion with a message and continue
halt {
dialog {
macro (UNSUPPORTED_SEARCH_OPTION)
}
}
}
}
unknown-error {
on-catch {
halt {
dialog {
macro (UNKNOWN_ERROR)
}
}
}
}
}
}
}

View master on GitHub

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 might 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.

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:

  output (SelectedItem) {
throws {

error(MultipleMatches) {
property (matches) {
type (Item)
max (Many)
}
on-catch {
//prompt for matched items, clear searchTerm
prompt (items) {
required-value (true)
single-value (true)
candidates (matches)
}
}
}
}
on-empty {
//prompt for items, clear searchTerm
ordered-effects {
drop (searchTerm)
prompt (items) {
required-value (true)
single-value (true)
candidates (items)
}
}
}
}

View 0690617 on GitHub

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

  output (HabitatPod) {
throws {
error (MultipleMatches) {
property (matches) {
type (HabitatPod)
min (Required)
max (Many)
}
on-catch {
replan {
intent {
goal { HabitatPod @prompt-behavior(AlwaysSelection) }
value { $expr(matches) }
}
}
}
}
}
}

View f766695 on GitHub

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:

  output (SpaceResort) {
throws {
error (UnsupportedSearchCriteria) {
on-catch {
// TODO: drop the unsupported criterion with a message and continue
halt {
dialog {
macro (UNSUPPORTED_SEARCH_OPTION)
}
}
}
}
unknown-error {
on-catch {
halt {
dialog {
macro (UNKNOWN_ERROR)
}
}
}
}
}
}

View 84cc015 on GitHub

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

If you need to change the effects of your error handling during runtime, you can also override behavior with runtime flags.

You can see more examples of error handling in the Input Validation and Error Handling sample capsule.