Reusable Dialogs in the Alexa Conversations Description Language

In the Alexa Conversations Description Language (ACDL), you can write reusable dialogs with predefined turns to carry out common tasks. Reusable dialogs enable you to invoke other dialogs by using a syntax similar to function calls. With reusable dialogs, you can write succinct, hierarchical dialogs instead of multiple flattened dialogs.

Reusable dialog components

The basic structure of a reusable dialog is as follows.

dialog Type Name(Parameters) {
  Samples
}

Reusable dialogs contain the following components:

The following is an example of a reusable dialog.

dialog TIME GetTime(APLA aplaResponse, Argument arg) {
  sample {
    response(aplaResponse, Request {argument = [arg]})
    timeRequest = expect(Inform, informTimeEvent)
    timeRequest.time
  }
}

The type of this reusable dialog is TIME, the name is GetTime, the parameters are aplaResponse and arg, and there is one sample in the dialog.

Type

The type of a reusable dialog adheres to the following rules:

  • The type of a reusable dialog is the return type of the sample, which is the return type of the final line in the sample. In the previous example, the final line, timeRequest.time, is of type TIME, so the return type of the entire sample is TIME.

  • If the reusable dialog has more than one sample, the return type of all samples must match, and must also match the type of the dialog. Therefore, in the previous example, additional samples must all have the return type TIME. In other words, each additional sample must have a final line of type TIME.

  • If a sample ends with an Alexa response, the response() action must be the final line of the sample. Because the response() action returns type Nothing, you don't have to provide the type of the dialog in the dialog declaration. The following two dialogs are equivalent.

    // The type of the dialog is explicitly declared to be Nothing.
    dialog Nothing OrderTaxi {
    sample {
      orderTaxiRequest = expect(Invoke, orderTaxiEvent)
      ensure(
        {arguments = [orderTaxi.arguments.date], response = datePrompt},
        {arguments = [orderTaxi.arguments.time], response = timePrompt},
        {arguments = [orderTaxi.arguments.address], response = addressPrompt}
      )
      confirmAction(confirm_TaxiOrder_apla, orderTaxi)
      orderTaxi(orderTaxiRequest.date, orderTaxiRequest.time, orderTaxiRequest.address, orderTaxiRequest.passengers)
    
      // This response line will return type Nothing, and this must match the type of the dialog.
      response(notify_api_response_OrderTaxi_apla, Notify {actionName = orderTaxi})
    }
    }
    
    // No type is declared for the dialog.
    dialog OrderTaxi {
    sample {
      orderTaxiRequest = expect(Invoke, orderTaxiEvent)
      ensure(
        {arguments = [orderTaxi.arguments.date], response = datePrompt},
        {arguments = [orderTaxi.arguments.time], response = timePrompt},
        {arguments = [orderTaxi.arguments.address], response = addressPrompt}
      )
      confirmAction(confirm_TaxiOrder_apla, orderTaxi)
      orderTaxi(orderTaxiRequest.date, orderTaxiRequest.time, orderTaxiRequest.address, orderTaxiRequest.passengers)
    
      // This response line will return type Nothing, and this must match the type of the dialog.
      response(notify_api_response_OrderTaxi_apla, Notify {actionName = orderTaxi})
    }
    }    
    

Name

The name of the reusable dialog enables you to reference the reusable dialog from other dialogs. For more information about names, see Names in the ACDL.

Parameters

The parameters of a reusable dialog are similar to the arguments of an action. The parameters are passed to the reusable dialog when the reusable dialog is called. The reusable dialog can then use the parameters.

In the following example, the reusable dialog passes its parameters, aplaResponse and arg, to the response action.

dialog TIME GetTime(APLA aplaResponse, Argument arg) {
  sample {
    response(aplaResponse, Request {argument = [arg]})
    timeRequest = expect(Inform, informTimeEvent)
    timeRequest.time
  }
}

Samples

As with all dialogs, reusable dialogs contain one or more sample sections. A sample brings together a set of events and actions that represent the conversational experience between the user and Alexa. When you build the model, the sample is input to the Alexa Conversations simulator. The sample conveys the relationship between input events and outgoing actions. The trained model predicts actions based on the observed events at runtime. For the syntax of events and actions, see Use Events in the ACDL and Use Actions in the ACDL.

Invoking reusable dialogs

All dialogs are reusable and therefore can be invoked by other dialogs. You invoke dialogs the same way you invoke actions. In the following example, the GetDateTime dialog calls the GetTime reusable dialog.

dialog GetDateTime {
  sample {
    ...
    time = GetTime(timePrompt, timeArgument)
    ...
  }
}

Deployable dialogs

There are two types of reusable dialogs: deployable and non-deployable.

Deployable dialogs can be standalone dialogs and are at the highest level of a dialog hierarchy. Because they can be standalone dialogs, they have the following requirements:

  • Deployable dialogs must start with a user turn and end with an Alexa response.
  • Deployable dialogs must not have any parameters.

The examples in Dialogs in the ACDL are deployable dialogs.

Some reusable dialogs don't meet these requirements, thereby rendering them non-deployable. Non-deployable dialogs are dialogs that have parameters and/or have a return type that isn't Nothing. For example, the dialogs in the DateTime.acdl file of the reusable dialog example are all non-deployable dialogs.

Using conditional expressions in reusable dialogs

You can use conditional expressions with reusable dialogs in the following ways:

  • You can use a conditional expression within a reusable dialog.
  • You can call a reusable dialog from within a conditional expression.
  • You can branch based on the return value of a call to a reusable dialog.

The following example shows how to use a conditional expression within a reusable dialog called Pay. The argument to Pay is the payment amount. Pay then passes the payment amount to an external API.

dialog Boolean Pay(Amount amount) {
  sample {
    result = payApi(amount) 
    if (result.status != "no_payment_method") {
        // Please add a payment method.
        response(...)
        // Please add a credit card.
        expect(...)
        ...
    } else if {
    }
    ...
  }  
}

The following example shows how to call a reusable dialog, Pay, from within a conditional expression.

dialog Nothing FlightBot {
   sample {
     ..
     searchResult = searchFlights(...)
     if (searchResult.flights == 1) {
        // Reusable dialog call within a conditional expression.
        Pay(searchResult.flights[0].amount)
     }
   }
}

The following example shows how to branch based on the return value of a call to a reusable dialog. In this case, the return value of the reusable dialog call refers to the return value of an external action invocation.

dialog Nothing FlightBot {
   ...

   // Status refers to the return value of an external action 
   // invocation in the Pay dialog.   
   status = Pay(searchResult.flights[0].amount)

   if (status == false) {
   }
}

Reusable dialog example

This DateTime reusable dialog example involves the following two files:

DateTime.acdl

The following file is DateTime.acdl.

// DateTime.acdl
import com.amazon.ask.types.builtins.AMAZON.*

type InformTimeType {
  TIME time
}

informTimeEvent = utterances<InformTimeType>([
  "{time}",
  "at {time}"
])

dialog TIME GetTime(APLA response, Argument argument) {
  sample {
    response(response, Request {argument = [argument]})
    timeRequest = expect(Inform, informTimeEvent)
    timeRequest.time
  }
}

type InformDateType {
  DATE date
}

informDateEvent = utterances<InformDateType>([
  "{date}",
  "on {date}",
  "for {date}"
])

dialog DATE GetDate(APLA response, Argument argument) {
  sample {
    response(response, Request(arguments = [argument]})
    dateRequest = expect(Inform, informDateEvent)
    dateRequest.date
  }
}

type DateTimeArgs {
  Argument date
  Argument time
}

type DateTime {
  optional DATE date
  optional TIME time
}

informDateTimeEvent = utterances<DateTime>([
  "at {time} on {date}",
  "for {date} at {time}",
  "at {time}",
  "{time}",
  "on {date}",
  "{date"
])

dialog DateTime GetDateTime(APLA timePrompt, APLA datePrompt, DateTimeArgs args) {
// Request "date" and "time" separately with the reusable dialogs.
sample {
  dateArg = args.date
  date = GetDate(datePrompt, dateArg)

  timeArg = args.time
  time = GetTime(timePrompt, timeArg)

  DateTime {date = date, time = time}
  }
}

TaxiOrder.acdl

The following file, TaxiOrder.acdl, calls the reusable dialogs. Because neither this file nor DateTime.acdl has a namespace, they are compiled under the same namespace, and there is no need to import the names that the DateTime.acdl file initializes.

// TaxiOrder.acdl
import com.amazon.ask.types.builtins.AMAZON.*
import prompts.*

type TaxiOrder {
  optional DATE date
  optional TIME time
  optional NUMBER passengers
  StreetAddress address
}

orderTaxiEvent = utterances<TaxiOrder>([
  "order a taxi",
  "order a taxi for pickup from {address} for {passengers} passengers",
  "order a taxi for pickup at {time}",
  "order a taxi for {passengers} at {time}",
  "order a taxi for pickup at {time} from {address}",
  "order a taxi for pickup frono worm {address}",
  "order a taxi for pickup at {time} for {passengers} passengers from {address}"
])

type OrderTaxiResult {
  TIME time
  FirstName driverName
  Literal carMake
  Literal carModel
}

action OrderTaxiResult orderTaxi(DATE date, TIME time, StreetAddress address, NUMBER passengers)

dialog OrderTaxi {

  // Time and date not provided in the first invocation, so there is a call to the reusable GetDateTime dialog.
  sample {
    orderTaxiRequest = expect(Invoke, orderTaxiEvent)

    // Create an object that contains the arguments to be passed into the reusable dialogs.
    args = DateTimeArgs { time = orderTaxi.arguments.time, date = orderTaxi.arguments.date }

    // Call the reusable dialog.
    dateTime = GetDateTime(timePrompt, datePrompt, args)

    // The "date" and "time" arguments have request actions in the reusable dialogs, so the only remaining required arguments for the orderTaxi() action that need a request action are the "address" argument and the "passengers" argument.
    ensure(
      {arguments = [orderTaxi.arguments.address], response = addressPrompt},
      {arguments = [orderTaxi.arguments.passengers], response = passengersPrompt}
    )

    confirmAction(confirm_TaxiOrder_prompt, orderTaxi)
    orderTaxiResult = orderTaxi(dateTime.date, dateTime.time, orderTaxiRequest.address, orderTaxiRequest.passengers)
    response(orderTaxi_prompt, Notify {actionName = orderTaxi}, payload = {orderTaxiResult = orderTaxiResult})
  }

  // Time and date are provided in the first invocation, so there is no need to call the GetDateTime dialog.
  sample {
    orderTaxiRequest = expect(Invoke, orderTaxiEvent)
    ensure (
      {arguments = [orderTaxi.arguments.date], response = datePrompt},
      {arguments = [orderTaxi.arguments.time], response = timePrompt},
      {arguments = [orderTaxi.arguments.address], response = addressPrompt},
      {arguments = [orderTaxi.arguments.passengers], response = passengersPrompt}      
    )
    confirmAction(confirm_taxiOrder_prompt, orderTaxi)
    orderTaxiResult = orderTaxi(orderTaxiRequest.date, orderTaxiRequest.time, orderTaxiRequest.address, orderTaxiRequest.passengers)
    response(orderTaxi_prompt, Notify {actionName = orderTaxi}, payload = {orderTaxiResult = orderTaxiResult})
  }
}