Cooee A Bot Building Framework for the JVM

Prompts

When prompting a user for a particular response, it would be useful to be able to parse and validate that response. For example, when expecting a time input, which may be received in a natural language format such as “yesterday” or “after noon next Tuesday”, have the system parse the response to extract structured data such as :value "2013-02-12T15:00:00.000-02:00", :grain :hour.

This requires that user responses be processed in stages - as a pipeline.

To implement response processing pipelines, Cooee considered three options:

  1. Stacks
  2. Using the Receive Pipeline Pattern within Akka
  3. Akka Streams

The second option was chosen.

Stacks

The Microsoft Bot Framework has an elegant approach, using a stack, which is used more generally by all ‘dialogs’ - the Microsoft framework’s term for a conversation flow specification. A ‘prompt’ is a special form of dialog. Multiple dialogs for a user interaction can be pushed onto a stack. A response is processed by the dialog popped off the top of the stack (FIFO order). If there is another dialog in the stack, then the result of the current dialog is passed to the next one ready to be popped. If there are no more dialogs, then the result is sent back to the user.

Akka Streams

The Akka Streams API offers a graph DSL, which can implement a staged pipeline for processing event streams. This option may be reconsidered if there is a requirement to implement a pipeline with junctions (fan-out). It is possible to build reusable, encapsulated components of arbitrary input and output ports using the graph DSL.

Simple graph example

Receive Pipeline Pattern

The Receive Pipeline Pattern lets us define general interceptors for our messages, and plug an arbitrary amount of them into our Actors. It’s useful for defining cross aspects to be applied to all or many of our Actors.

Multiple interceptors can be added to actors that ‘mixin’ the ReceivePipeline trait. These interceptors internally define layers of decorators around the actor’s behavior. The first interceptor defines an outer decorator which delegates to a decorator corresponding to the second interceptor and so on, until the last interceptor, which defines a decorator for the actor’s Receive function.

For each message received by an interceptor, the interceptor will typically perform some processing based on the message and decide whether or not to pass the received message (or some other message) to the next interceptor.

A PromptInterceptor can be ‘mixin’ed to’ a conversation actor to preprocess response messages, in this case to parse and validate an expected response defined by the ‘prompt’ set when the message was sent.

Prompt Types

The Cooee framework supports the following types of prompts:

  • time, e.g. “today” or “Monday, Feb 18” - both points in time and intervals
  • temperature, e.g. “65 degrees”
  • number, e.g. “100K” or “four”
  • ordinal, e.g. “first”
  • distance, e.g. “7 km” or “2 inches” and conversion to a normalized unit (metre)
  • volume, e.g. “250ml” or “1 gallon” and conversion to a normalized unit (litre)
  • money, e.g. “ten dollars” or “$20.30”
  • duration, e.g. “2 hours” or “3 min” and conversion to a normalized unit (seconds)
  • email
  • URL
  • phone number, e.g. “415-123-3444” or “(650)-283-4757 ext 897”
  • selection from a defined list, either by ordinal number of the item’s index in the list, or nearest textual match (using Levenshtein distance)

The PromptInterceptor will add the parsed structured data as additional context to the message, which can be used by the conversation logic.

case class TextResponse(platform: Platform,
                        sender: String,
                        text: String,
                        context: Option[Map[String, Any]] = None)

The PromptInterceptor uses the excellent Duckling library.