Imagine that you are developing an Action. You’ve completed the design for the voice-only experience, and now you’re moving on to how the Action will work on screens. While writing sample dialogs, you realize that some paths through the Action will be more likely than others. Or that your Action has several branches from the same root level. Our job as developers is to make these paths easier for the user to work through. Besides writing great dialog for your Action, you can also take advantage of visual elements on display surfaces to help guide the user too.

Suggestion chips are small visual elements that appear at the bottom of the Assistant user interface that users can tap on, instead of writing out the phrase to continue the conversation. This makes the interaction smoother for users, by reducing their cognitive load and manual typing effort.

When incorporating this type of rich response, many developers start by doing the following:

Create a list of suggestion chips and randomly select two or three from that list to provide to the user

Hardcode suggestion chips per intent

A better approach is to have the Action display only suggestion chips for new interactions, and remove any chips that the user selected that no longer apply. In other words, your suggestion chips would be smart!

The GDG Sample

The GDG Sample Action provides information about the GDG (Google Developer Group) group — GDGs are community groups that host events where they share information about Google Technologies. The Action integrates with the Meetup API to provide information about a group, details about the next and prior events, the organizer name and photo, as well as how many members are in the meetup group.

After writing my sample dialogs, I created the following conversation design:

When I initially built the Action, I programmed my suggestion chips to be randomly selected. I asked my colleague, Cathy, who’s a Conversational Design expert, to test out the Action. She commented that it was strange to see a suggestion chip of something that she just asked for and asked if that could be removed. Seeing that I have all my intents available at the root level (all intents are available after the welcome intent is triggered), we revisited my sample dialogs to see if there was a common usage pattern. And indeed there was!

Every conversation starts with a welcome intent. After that, there are two types of users: first time users and returning users. The most common thing a first time user would ask for is to learn more about the GDG and then about events. For the returning user, it would be to learn about the next event. With that, the most common path would be:

Welcome → About GDG → Next Event → Last Event

That leaves the number of members and about the organizer intents. But based on the sample dialogs I had, knowing how many members came up earlier in the conversation more often than the organizer. So we ended up with this ordered list:

With my ordered intents, I can provide my user with suggestion chips that:

Guide the user through accessing the different data points

Don’t show an already seen suggestion chip/Intent. In other words, if in the beginning of the conversation the user already asks for the number of members, the Action won’t show the suggestion chip “Member” for the rest of that conversation.

Smarter Suggestion Chips

To present suggestion chips dynamically, I used an array of 6 integers — this could also be booleans. I set the values to zero, indicating that the user has not interacted with this intent. This data point needs to stay persistent within the conversation, so I added it to the ConversationToken, which is the data object that’s exchanged between the Action and the Google Assistant. Because I am using the Node.js client library, I can do that by setting the intentFulfilled property in conv.data .

conv.data.intentFulfilled = [0, 0, 0, 0, 0, 0];

Next, add this to the welcome intent so it can be initialized at the beginning of the conversation.

/** Handles the welcome intent. */

app.intent(‘Default Welcome Intent’, (conv) =>

conv.data.intentFulfilled = [0, 0, 0, 0, 0, 0];

// code }

Note: If you have a deep-link/invocation phrase, make sure that those intents check if the intentFulfilled property exists. If it doesn’t, your fulfillment should initialize that property.

To list out the actual suggestion chips, I created an object to represent my ordered suggestion chips in my response.json file:

“SUGGESTIONS”: {

“MENU”: [

“About GDG”,

“Next event”,

“Last event”,

“# of Members”,

“Organizer’s Name”,

“Bye”

]

},

I structured my suggestion chip object in this way so that I can still add additional lists of suggestions grouped by navigation paths, as needed.That is, if I add account linking and other options that are only available for registered users, then I may have an alternative path that I can add to the SUGGESTIONS object.

The following conversational design shows the behavior I want:

the suggestion chips are in bold

To implement this, I need a function that updates the conv.data.intentFulfilled to mark a suggestion chip as seen, when its corresponding intent executes.

I added recordsIntentFulfillment function which takes in the conv object and the intent name.

So now my intent handler looks like this:

/**

* Handles the Dialogflow intent named ‘next event’.

*/

app.intent(‘next event’, async (conv) => {

recordsIntentFulfillment(conv, ‘next event’);

// logic of the message

}

Next, I implement selecting only unvisited suggestion chip. To do that, I iterate over the conv.data.intentFulfilled and append just two of the unvisited suggestion chips to be returned and used.

Now include selectSuggestionChips to these intents:

/**

* Handles the Dialogflow intent named ‘next event’.

*/

app.intent(‘next event’, async (conv) => {

recordsIntentFulfillment(conv, ‘next event’);

// logic of the message

conv.ask(new Suggestions(selectSuggestionChips(conv)));

As the user interacts with the Action, each intent is updating the conv.data.intentFulfilled , to note that it has been seen. The selectSuggestionChips function makes sure that users will see only unused intents. Below you can see how the conv.data.intentFulfilled changes during the conversation that follows: