Tutorial: Flow design patterns
Level 3 – Lesson 3 of 5 – Learn common flow patterns for collecting, validating, and acting on user input.
Most flows follow a small number of repeatable patterns: collect a value, validate its format, then verify it against known data. For each pattern, decide whether to let the model lead (flexible, natural) or write deterministic logic (predictable, reliable).
The three patterns
When a flow collects information from a user, there are three distinct operations:
These are separate concerns. A value can be collected but invalid. A value can be valid but not match any record. Handling them separately makes your flows more predictable.
Collection pattern
The simplest flow pattern: ask for a value, save it, move on.
Validation pattern
After collecting a value, check whether it matches the expected format before using it.
When validation fails, the step prompt is still active – the model will ask the user again. This is the persistent prompt at work.
In voice, validation failures are often caused by transcription errors, not user mistakes. Avoid phrasing that blames the user.
Instead of: “That’s not a valid tracking number.”
Prefer: “Sorry, I didn’t quite catch that – could you repeat your tracking number?”
Check your understanding
A user gives a tracking number that's correctly formatted but doesn't exist in the system. Which pattern handles this?
- Collection – re-ask for the number
- Validation – the format check failed
- Verification – the value is valid but doesn’t match known data
- None – this is an API error
Answer: Verification – the value is valid but doesn’t match known data
Validation checks format. Verification checks against known data. A correctly formatted number that doesn’t exist in the system is a verification failure, not a validation failure.
Verification pattern
After validation passes, check the value against your backend.
LLM-led vs deterministic control
When something goes wrong in a flow (validation fails, API returns no match), you have two approaches:
LLM-led
Deterministic
Let the model decide how to handle retries and rephrasing.
Pros: Flexible, handles edge cases naturally, less code to write.
Cons: Less predictable. The model might retry indefinitely or phrase things inconsistently.
When to use each approach
A good rule: let the model do what it is good at (natural language, extraction, phrasing) and use code where precision matters (validation, retry limits, escalation).
Check your understanding
You need to collect a date of birth and the client requires exactly 3 attempts before handoff. Which approach should you use?
- LLM-led – the model can count retries itself
- Deterministic – use a counter in conv.state to enforce the limit
- Either approach works equally well
- Neither – use a managed topic instead
Answer: Deterministic – use a counter in conv.state to enforce the limit
When a client requires a strict retry limit, use deterministic control with a counter. The LLM may not count retries accurately or consistently, and you cannot guarantee it will stop at exactly 3 attempts.
Handoff from a flow
When the user fails repeatedly or the flow cannot continue, hand off to a live agent.
Parameter: utterance – The message to say to the user before transferring
By passing the utterance as a function argument, the model generates a contextually appropriate goodbye message, but the handoff is handled deterministically.
If you return a hard-coded utterance with handoff, no second LLM request occurs. The utterance is played and the call is transferred immediately. This is efficient but means the model does not get a chance to add anything – make sure your utterance provides proper closure.
Design principles
Try it yourself
Challenge: Design a tracking number lookup flow
Design a 3-step flow that:
- Collects a tracking number
- Validates the format (3 uppercase letters + 5 digits)
- Looks up the order and reports the status
Include:
- What happens if validation fails
- What happens if the order is not found
- A retry limit of 3 attempts before handoff
Hint
Use conv.state to store the tracking number and an attempt counter. Each step should have a clear prompt and one flow function. The validation step should check the counter before re-prompting.
Example solution
Step 1 – Collect
Prompt: “Ask the user for their tracking number. Once they provide it, call save_tracking.”
Step 2 – Validate
Prompt: “The tracking number has been collected. Call check_format to validate it.”
Step 3 – Lookup
Prompt: “The tracking number is validated. Call lookup_order to check the status.”
Check your understanding
You want the model to generate a natural goodbye message before handing off, but you also need the handoff to happen reliably. What pattern should you use?
- Return content telling the model to say goodbye, then hope it calls the handoff function
- Hard-code a generic goodbye utterance with handoff: True
- Pass the utterance as a function argument so the model generates it, then return it with handoff: True
- Use two separate functions – one for goodbye, one for handoff
Answer: Pass the utterance as a function argument so the model generates it, then return it with handoff: True
By passing the utterance as a function argument, the model generates contextually appropriate phrasing. The function then returns it as a hard-coded utterance with handoff: True, so the transfer happens deterministically.

