Alberto Antenangeli June 27, 2023
Think back to the last time you spoke to a customer support representative. They probably asked you questions and steered the conversation based on your responses. If you weren’t prepared to answer or asked them to repeat themselves, they didn’t get confused or go silent; they knew exactly what to say to get the conversation back on track. This is pretty easy for a live agent – but is that also true for virtual agents?
In a recent blog post, we shared a design pattern for FAQ-style Agents using Google Cloud’s Dialogflow, where we follow the single responsibility principle – each flow is responsible for one, and only one, thing. Questions are broken down into categories, with each category being handled by a flow; in addition, control flows that handle routing, redirects, etc. tie the Agent together.
While a basic FAQ-style Agent is a great starting point for automating your contact center, real-world interactions are not always purely transactional questions & answers. Eventually your virtual agents may have to gather information from the user – perhaps even several data points – to carry out more complex tasks.
This flips the FAQ paradigm around: instead of a user asking a question and the Agent responding, the Agent is now requesting information from the user. We call this a “feature-rich conversation,” and it’s a critical aspect of the customer experience (as well as a sign of your virtual agent’s maturity).
In this post, we’ll share some important requirements to keep in mind when you’re training your virtual agents to handle feature-rich conversations, along with design patterns that we developed at Nuvalence to help implement those requirements in a simple and reusable way.
Defining Foundational Small-Talk Intents for Virtual Agents
Before we dive into the technical details, it’s important to understand the distinction between conversation design and Agent design. Conversation design is the practice of creating human-like, engaging, and user-friendly conversations between users and conversational AI systems; if you’re new to conversation design, check out our recent post to learn how to get it right. By contrast, Agent design focuses on how to organize the Agent (entities, flows, pages, events, transitions, etc.) to support a conversation design.
An important aspect of conversation design is “Small Talk,” which is defined as a collection of reusable intents that bring consistency to the user experience for common prompts such as repeat, help, go back, more time, non-responsive caller, etc.
During conversation design, your team (likely led by a product manager) will need to identify and define the expected behaviors and validations that will apply to the Small Talk intents that make up the feature-rich conversation.
In addition to the wide variety of sophisticated interactions virtual agents need to support, when asking for user input, you need to handle Small Talk Intents well. Small Talk Intents are at the core of virtually every user input flow, and must be handled consistently and (ideally) in a reusable way.
We captured those types of interactions as five foundational Small Talk Intents:
|Repeat the last prompt.
|End the interaction and offer the option to exit.
|Return to the previous collection point for new input.
|I need more time
|Pause the interaction for a set period of time, giving the customer time to respond (ex. locate their driver’s license number).
|Allow further clarification.
You may also want to designate inputs as mandatory or optional, giving the user more opportunities to provide the correct information for mandatory fields. Think of this as similar to a form, where certain fields are mandatory, while others are not.
TIP: This is different from the Dialogflow “Required” flag, which focuses on whether the Agent should attempt to capture the information before moving to another page.
Two categories of validation that may apply:
- Input type: Whether the user is supplying the correct type (for example, ensuring that a date is well-formed). This validation does not depend on the context where the information is collected – the format of a date must be validated regardless of who wants that date.
- Input context: Whether the data validated as part of 1 above makes sense in the context where it is required. For example, if you are collecting a date of birth, besides making sure you have a well-formed date, you also want it to be in the past. On the other hand, if you are scheduling an appointment, you want that date to be in the future.
Design Patterns for Foundational Small-Talk Intents
While it is possible to implement all these requirements at each data collection point, this translates to a substantial amount of copy and paste, which is far from ideal for obvious reasons. When confronted with this problem, we knew we needed to abstract these requirements in a reusable way that would enable us to “call” the reusable component when we needed to collect information from the user. Conceptually, it’s equivalent to encapsulating these behaviors in a function that can then be called from multiple points in the code.
To do this, we developed two design patterns: one to implement a generic data collection flow that addresses the requirements above, and one to invoke that flow from other flows.
A link to where you can download a working copy of this example can be found at the end of this post.
Pattern 1: The Data Collection Flow
This is the “callable” flow – the function, if you wish – responsible for collecting information from the user.
To make it more flexible, this flow uses a session parameter called data-collection-type that, as the name suggests, defines which data type we want to collect. In our example, we support phone numbers, gender, yes/no responses, and amounts. It is easy to add support for other data types by following the pattern we describe below.
Before we deconstruct its elements, this is what the flow looks like:
The Start Page
The Start Page has routes to the page responsible for collecting a specific data type, based on the data-collection-type parameter:
The “collect” pages look the same, gathering the corresponding parameter from the user:
They also include the rg-behaviors route group, which implements the behavioral requirements we described earlier in this post:
Using a route group enables us to eliminate the need to copy and paste those routes into every page where they are required. All we need to do is to define the behavior we want in the route group, and add it where it is needed – all the “collect” pages, to be more specific.
Using this design pattern, you can easily add support for a new data type by using one of the existing pages and transitions as a model. Simply add another route to the Start Page that directs the flow to a “collect” page like the ones above based on a new data-collection-type parameter value.
The Mechanics of the Call
Two session parameters must be set before transferring control to the Data Collection flow:
- data-collection-type: defines what you want to collect (phone, date, etc.)
- data-collection-mandatory: true if this is a mandatory field (from the caller’s perspective), offering more opportunities to get the data input right.
The Data Collection flow returns a state to the caller in the data-collection-outcome session parameter:
- success: the information was successfully collected.
- error: there was an error collecting the information.
- cancel: the user canceled the process.
- repeat: the Caller flow should re-prompt the user.
- help: the user needs guidance to enter the information.
Note that the Data Collection flow relies on the Caller flow to re-prompt or help the user, since those are specific to the Caller context. Using phone numbers as an example, sometimes we may want to ask for the user’s cell phone number, but sometimes we may want their work phone number. Each has a different prompt, and may be associated with different help messages. Only the Caller knows what those are. We will cover how to handle this in detail when discussing the Caller flow later in this post.
Handling no-match, no-input, and “I Need More Time”
Each “collect” page handles no-match and no-input the same way:
The event-routing page uses a more-time-count session parameter to determine whether the event was triggered because it failed data collection (e.g., the user said something that did not match the input parameter) or because the user requested more time. Why are we using the No-match instead of the Invalid Parameter event? There is a simple explanation – it’s how Dialogflow CX works. For example, if we expect a date and the input is something unrelated, we get No-match, rather than an Invalid Parameter.
For the I need more time use case, we leverage the sys.no-input timeout. The default is five seconds; when we set the more-time-count session parameter to 10 (as in our example flow), that means we are giving the user 10 * 5 = 50 additional seconds to provide the required information. You can set it to other values, depending on the timeout you have configured and how much additional time you want to give the user.
The more-time-handler page simply decrements the counter every time the no-input event is triggered after the user requested more time; when it reaches 3, it warns the user that they are running out of time, so they can ask for more time if they want. Once it reaches 0, we end the session. Alternatively, the data-collection-outcome could also be set to time-out and pass control back to the Caller flow – if that is your requirement, it’s a simple change.
To test this behavior, you can key “I need more time” while testing the Agent from the console, hit enter without typing anything to trigger the no-input event, and watch the Agent reply and the count go down.
The attempt-router page checks the data-collection-mandatory session parameter to determine whether to give the user three or five attempts to enter the required information, handing control to the mandatory-router or the non-mandatory-router page. Both use a session parameter (data-collection-attempt-count) to keep track of how many times an attempt to collect the input failed.
The first time we land on this page, this parameter is null, and we set it to 3 or 5, depending on the contents of the required session parameter set by the Caller. After this parameter is set, revisiting these pages decrements the data-collection-attempt-count; once it reaches 0, the data-collection-outcome parameter is set to error, indicating that the information was not collected. We use different pages with different messages for each unsuccessful attempt, to add variety to the conversation with the Agent.
Pattern 2: The Caller Flow
When using the Data Collection flow, the Caller flow also follows a well-defined pattern. There are two ways to go about that: implementing specific pages to handle the different data collection outcomes, or simply leveraging routes and their fulfillments.
Let’s look at the more complicated case first – specific pages. This is what the Caller flow looks like:
Phone, gender, date, yes/no, and an amount are collected in this order. Note that there is a pattern to call the data collection flow – for each data element we want to collect, there are four associated pages:
- <element>-collect contains the prompt for that particular element (e.g., “What is your phone number?”)
- <element>-help contains the associated help message.
- <element>-error handles error conditions; in our example, error and cancel. TIP: you can handle errors and cancellations in different pages.
- <element>-collected handles the element successful collection; at minimum you should copy the user input to a local parameter. This is also where you can implement additional consistency checks.
In our example, we keep going even when there is an error; your use case may be different.
Each <element>-collect page has the following routes (using gender as an example):
The assumption is that the incoming transition to the <element>-collect page sets the data-collection-outcome parameter to null to indicate that we want to collect the data. All we do is set the data-collection-type session parameter to the type we want to gather, and transition to flow.data-collection.
When we return from that flow, we check the data-collection-outcome parameter for the outcome, and transition to the appropriate page depending on its contents, as shown in the routes above. As a side note, for help and repeat, the data-collection-outcome parameter is reset to null to indicate that we want to keep trying to collect the data. Failure to do so will make the Agent enter an infinite loop.
Leveraging Routes and Fulfillments
Now for the simple implementation, relying on transition routes:
The parameter checking/setting is similar to what we described for the more complex solution – the only difference is that the fulfillments now are part of the transition routes.
And that’s it! Just follow the one of the patterns above, and your data collection points can benefit from the rich requirements we described earlier in this post.
Adapting the Patterns
A big advantage to using these patterns is that they can be adapted to meet your unique needs. When you do so, keep the following in mind:
- It is possible to route all “collected” pages to a single one if consistency can be consolidated – for example, via a webhook call. You will need a parameter to indicate the pages you should go to when consistency passes or fails.
- If Caller-specific help is not required for each input field, help for each data type can be implemented directly in the Data Collection flow.
- The concept of “mandatory” may not be needed, in which case the flows can be simplified. Note that we leave the decision to move on after a parameter collection failure to the Caller flow, not the Data Collection flow.
- As long as the behavior is consistent, you can consolidate the handling of repeat, error, and cancel to a route group, and apply that to the Start Page of your Caller flow. Repeat always transitions to Current Page; if error and cancel are handled the same way (e.g., saying goodbye to the user and ending the session), a route group will do just fine.
Training virtual agents to conduct feature-rich conversations can be complex – but we’ve learned how to simplify the process by developing the right reusable design patterns. In this post, we presented two Dialogflow patterns that have worked well for us:
- A Caller pattern that calls another flow to perform a reusable task.
- A Data Collection pattern that implements a feature-rich way of gathering data from the user.
If you would like to take this Agent for a spin, you can find the zip file in Github.