Working with Subflows
A frequently used pattern in Flow XO is to organize your conversations into multiple flows. This approach can simplify your life tremendously by letting your group related logic into individual flows and then delegating to those flows based on how users answer certain questions, eliminating the need for complicated filters in each step of your flow, which tends to happen when you try to cram a complex conversational workflow into a single flow. For example, you may have a complex onboarding procedure for your users where you ask them some contact information, get their preferences, and then save those values into user attributes. You may want to run this onboarding procedure from multiple places, even at the beginning of all of your other flows, if they try to run a flow without completing the onboarding first.
Another example may be interacting with a database or external service, such as Google Sheets. You may read profile data from a Google Sheet for your user and store it in user attributes for later use. instead of configuring the Google Sheets actions everywhere you need this profile data, you can move that functionality into its own flow and call it from anywhere.
The main advantage to this approach is that you can isolate reusable or complex functionality into a single place, rather than spreading it out and repeating it in many different places, or trying to cram it all into one huge, difficult to understand master flow.
NOTE: If you find yourself wanting to use sub-flows to create and manage menus to navigate the different areas of functionality your bot offers, you should consider using our Menu feature instead
What we will be building in this guide
In this walkthrough, we will be building a simple chatbot that simulates an app that requires a user to be registered before using the bot. If the user attempts to use the bot before having registered, the bot will ask them to register with an email and a preferred name. Once the bot receives an e-mail and before proceeding to ask for additional information, it will confirm with a simulated account service that the account is valid. If a valid account is found, the user will proceed with the conversation.
While it would definitely be possible to build this entire interaction using a single Flow, it can be much better to separate the flow into subflows. In this simple example, it may make sense to combine all these steps into one flow, but in a real bot you will have much more complicated logic in each phase. Additionally, you will probably want to trigger the onboarding flow from multiple places, as well as look up account information from different spots during the user journey. In that case, having these processes separated into subflows is essential.
Before we begin, if you haven't already, you may want to familiarize yourself on the concept of subflows in this brief overview.
Here are the steps we'll be taking:
- Create an 'Account Lookup' flow
- Create the 'Onboarding' flow that will make use of the 'Account Lookup' flow
- Create the main workflow, that will rely on the 'Onboarding' flow
You may have noticed that we're building our flows in reverse order from what you might expect. This is optional, and frequently you'll do it the other way around if your designing your flows as you build them since you might not even know you want to use a subflow until you need it. If you have the luxury of designing your flows first, however, as we have done, you can save some time by preparing the subflows before they needed.
1. Account Lookup Flow
You can install this sample into your own account to follow along here.
The account lookup flow is what we consider a "component" flow. It doesn't have any interaction with the user (unless the account is not found), and it relies on certain inputs to be provided as metadata. It's main purpose in life is ONLY to query the account management system (which we are just pretending exists in our example) to fetch an account number for a given email and set the result as a user attribute. Here's what it looks like:
Let's look at each aspect of this flow one at a time.
1. New Trigger
This flow uses a New Trigger type of trigger. Since we will be triggering this flow directly, rather than dynamically, you can use any kind of trigger you want. For flows that should not be triggered by user messages, such as this one, we like to use New Trigger triggers because they can't be accidentally activated by a user message.
In this case, the "Word or Phrase" can be anything you want, because we will be triggering this flow by name, not by keyword. We suggest using something descriptive, but it can be anything, it won't affect the functioning of the flow.
2. Lookup the Account
For our sample, we're just sending a message instead of querying a database, such as Google Sheets or Airtable. Obviously in a real chatbot, you would replace this step with an actual call to an actual integration or use an HTTP step to query your own back end services. So the actual details of this step aren't important, except that we need to show where to get the email address we want to query. This flow will require that whichever flows calls it must pass in a metadata field named 'email' containing the email of the user to search. We can grab that value from the trigger metadata, as shown here:
In our sample, we will assume the account lookup was successful, UNLESS the email provided is 'firstname.lastname@example.org', in which case we'll pretend the lookup returned no valid account.
In this case, we'll send the user a message and stop all processing. Your chatbot may want to be more flexible, and ask the user to try again, but for now we're just calling it quits:
3: Send error message
This is just a simple message to let the user know their account wasn't found.
4. Cancel sending flow
This step demonstrates something you may occasionally need when working with subflows, and that is asking a subflow to cancel the flow that called it. If a subflow fails, and the flow that called it is waiting on a response, the calling flow will automatically fail. There isn't anything you have to do to make that happen. However, you may sometimes want to intentionally cancel the calling flow even if there wasn't an actual error, as in this case. To do that, you can use the Cancel Flow step from the Flow XO Utils integration.
When a subflow is triggered, some information about the flow that triggered it is available as part of the trigger data. One of those pieces of data is the Request ID of the triggering flow, which you can use to cancel the calling flow because of a validation error or any other reason, like we are doing here. One thing to note about this step is that we are using a filter so that it only runs if the account was missing, and it also stops the current flow after it executes. That is because no more processing is required - we could not find the user account, we notified them, and we are done.
To accomplish that, we tell this step to stop the flow any time it runs:
5. Generate Fake Account Number
This is just part of the simulation. We are using the Flow XO - Utils actions to create a pretend account identifier. In your real applications, you would return the account number you retrieved from the external service.
6. Set outputs
This is a critical part of subflows that need to return data to the flow that called them. When a subflow completes, if the flow that triggered it is using the 'wait' behavior to wait for the subflow to finish, then the output of the LAST step to run will be sent back to the calling flow as the output of the Trigger a Flow step. This can be useful in some cases. However in many other cases you can just use User Attributes to help flows communicate with one another, as you usually want the data to be stored along with the user anyway. That's what we're doing here.
Note that we're setting a new attribute called 'account_number' on the user, which can be used by the parent flow.
That's it for the fake account service! Now on to the Onboarding flow.
2. Onboarding Flow
You can install this sample into your own account to follow along here.
The onboarding flow is responsible for collecting any information about the end user that is needed to validate they have an account and complete any interactions we will need to have with them. You won't always have a flow like this if you are interacting with anonymous users and don't need to communicate with them outside of the messaging channel your bot is hosted on. However many times you will want to collect some basic info about your users, or validate they have an account with your company, and a dedicated onboarding flow is a good place to do that kind of thing. Here is what the flow looks like:
Let's take a look at this in detail as well.
1. New Trigger
This is set up exactly like the Account Lookup flow, for the exact same reasons. No need to rehash that, just see #1 in the walkthrough above for the Account Lookup flow.
2. Ask the user for their email
We want the user's email, so we use an Ask a Question task to get it. Easy peasy.
3. Lookup Account NumberFinally! We get to the place where are triggering a subflow. As soon as we have an e-mail, we can try to fetch an account number from our account service, which we have wrapped up in our Account Lookup flow. So to do that, we need a Trigger a Flow task. Here is what it looks like:
There are a few interesting things going on here. First, we are selecting the specific flow to trigger, which is why we built that flow FIRST. It now appears in our pick list of flows.
Note that we also set a metadata field, named 'email' and we passed in the email the user provided us. If you recall, we used that metadata field in the 'Lookup Account' step in the previous flow.
Finally, and importantly, we set the trigger behavior to ' Wait'. This will cause our Onboarding flow to pause until the Account Lookup flow completely finishes and has set the account_id as a User Attribute.
And that's it for the Account Lookup. Once this step completes, the Account Lookup subflow will have finished running or failed (and cancelled our onboarding flow in the process).
4. Get Preferred Name
This is just a simple Ask a Question to ask the user what to call them.
5. Set Outputs
As the last step in the sub flow, we need to store the user gathered so far in User Attributes. This data will be used by the main flow.
That's it for the Onboarding flow. When this flow has completed, the Main flow can be confident that the user is registered and has a valid email, name and account number.
3. Main Flow
You can install this sample into your own account to follow along here.
The onboarding flow is responsible for implementing the main functionality of your chatbot. In this sample, it isn't doing very much, as the main point is to demonstrate subflows, but it's also short and sweet because we split the onboarding and account lookup functions into separate subflows. Here is what the flow looks like:
There really isn't a need to go through every step of this flow like we did with the others, because it's just more of the same. Install the sample flow and take a look if you have any questions. I will point out a few interesting bits though around triggering the onboarding flow.
1. We trigger the onboarding flow exactly as we did with the Account Lookup flow. However, we added a filter so that we only even trigger the onboarding flow if we haven't already onboarded the user:
Because the onboarding flow sets the "email" attribute when it completes, if we don't have that attribute at the start of the flow, we need to run the onboarding. If we DO have the email, then we know the onboarding is complete and we can skip it entirely.
That brings us to the next step, to reload the users attributes:
This step is necessary immediately after the Onboarding trigger, because the onboarding step may or may not have just run. If it DID run, we need to make sure that the email, preferred_name and account_number attributes that it set for us behind the scene get loaded up so we can use them later. If it did NOT run, then this step will load the attributes that were already there. It doesn't hurt anything, and it guarantees that we will have the data we need.
After that, the rest of this flow is just boilerplate. We send a message with the attributes from the onboarding process to prove that they were fetched correctly, and then we ask the user if they want us to remember them. If they say NO, then we clear their attributes using the Flow XO - Utils action.
You won't usually have this step in a live flow, but when you are developing your flows it can be very handy. The reason for this is that once you capture the onboarding information, the main flow doesn't ask for it again. Why would it? It already knows what it needs to move forward.
But that also means that if you make changes to the onboarding flow you want to test, you will have a hard time triggering the onboarding flow to test the changes - because it's always being skipped.
So giving you or your QA team the option to clear the attributes simplifies the process of testing all the possible paths through the bot because you can start with a fresh slate on every run if you want to.
The techniques in the guide above should cover 90% of your subflow triggering needs. However, you may have noticed, we ONLY used the "wait" behavior when triggering our subflows - meaning we always paused one flow to wait for the subflow to continue.
Parallel Subflows - Triggering with Continue
There is another mode, 'continue', that lets the main flow keep running while the subflow runs in the background. If we tried to set up the sample above this way, it would be a disaster. It wouldn't work at all, because there would be multiple flows asking questions at the same time, and the main flow would not have the data it needed.
But there are cases where it makes sense. One of those might be when you want to hand off complete control of the conversation to another flow and end the current flow. In this case, you should use continue - why keep the main flow alive waiting for a response it will never use.
Another interesting use case is to deal with long running tasks. In a conversational environment like a chatbot, users are generally used to getting responses within a few seconds. It won't always be possible, however, to provide a response that quickly.
A great example that has become very relevant recently is when you are using a large language model (AI) like ChatGPT or GPT-4. These AI APIs are extremely powerful, but they can also be slooooow...sometimes taking 30 seconds to answer a question or even several minutes (especially with GPT-4 and its larger context sizes).
What's worse, how quickly they respond can be somewhat unpredictable. So you can tell your user "Please be patient, a response may take up to 30 seconds" but sometimes, maybe rarely maybe not, the response takes 90 seconds! This will cause your users to start banging on the keyboard in many cases.
It would be great if you could send the user a message when things are taking too long, to let them know the AI is still thinking and hasn't given up on them.
But how to do this can be a real challenge. Once you begin the API request with the AI, the flow that made the request will stop and wait for the response. So there's not a way to send a message from the main flow.
The solution is to trigger a subflow just before you make the API request, and use the 'continue' behavior instead of the 'wait' behavior. Then, the subflow will start running, and your main flow will move on and call the API.
The subflow will be responsible for sending messages to the user at strategically timed intervals to keep them engaged and let them know the bot isn't broken. For example, if you know that the response from Open AI typically takes about 20 seconds, you can send a message at 10 seconds that says 'We're almost done'. At 25 seconds, 5 seconds after the AI usually replies, you can send another message like 'Sorry, this is taking longer than usual, but I'm still working on getting your answer? Just doing a little more research...'
That's easy enough. There is one more important element though, which is that you don't want the 'Sorry this is taking too long' message to show up when the response was received earlier. For that, you need to cancel the subflow as soon as you get a response.
1. The Waiting Flow
First we'll build the "waiting" flow. All this flow does is send 'I'm still here' type messages in intervals that you should design to match your historical experience with the response time of the long running process you are waiting on (like an OpenAi call). You should consider adding "wait" -> "message" combinations for as long as you think the long running process can realistically last. You will only know this through experimentation. Here's the waiting flow:
Here's how the wait tasks are configured. We are using "Wait until a Date" actions to wait for time periods < 1 minute.
After each wait, we send a message. This is repeated. If you wanted to get really fancy, you could use the techniques we showed in the first part of this walkthrough to combine the "Wait" and "Message" tasks into a subflow that could take the amount of time to wait and the message as inputs via metadata. Then your "wait" flow would just be a series of Trigger a Flow calls. In our example though, we just duplicated the "wait" + "message" pairs a few times in a row.
That's all there is to the wait flow - it's really that simple, all it should do is send messages at certain intervals.
2. The "Long Running Task" Flow
This flow does the actual work. It looks like this:
This flow is mostly self explanatory, but let's go over the triggering and cancelling aspects. First, as we said we would do, we're triggering our "wait notification" flow just before run the task that will take a long time:
Next, we run our long running task. In our sample, we're just picking a random amount of seconds between 5 and 30 and pausing the flow for that amount of time. This is simulating an API call that could take between 5-30 seconds to complete.
After that is done, we have to cancel the "wait notification" flow. This is extremely important, otherwise we'd be sending our users notifications that their task was not yet finished even after we gave them a response!
The thing to notice here is that the Trigger a Flow task has an output variable called "Triggered Request IDs" that will have the identifier of the flow it triggers. You can use this in a Cancel Flow task to stop that triggered flow before it completes.
We do that here once we receive a result from our service so that no further "wait notifications" are sent after the user gets their response.
That's All Folks
That is it for our Subflow tutorial! I hope you will find many uses for these powerful techniques to create lean, mean conversational flows that amaze your users and make you rich!
As always, please reach out with question or feedback, email@example.com