What are Structured Tests?
Structured Tests is a specialized evaluator type that allows you to create dynamic, rule-based test scenarios. Unlike traditional evaluators that follow a linear script, Structured Tests evaluators can adapt their behavior based on what the agent says during the conversation. This makes them ideal for testing complex workflows where the conversation can take multiple paths depending on the agent’s responses.When to Use Structured Tests
Use Structured Tests evaluators when you need to:- Test branching conversations: Handle different conversation paths based on agent responses
- Simulate decision trees: Test scenarios where user responses depend on what the agent asks
- Create adaptive evaluators: Build evaluators that can handle unexpected agent responses
- Test IVR (Interactive Voice Response) flows: Simulate interactive voice response systems with menu options
- Validate error handling: Test how agents handle various user inputs and edge cases
Example Use Case
Structure
A Structured Tests evaluator consists of two main components:1. Role
Therole defines who the testing agent is pretending to be.
2. Conditions
Theconditions array defines the rules and actions for the evaluator. Each condition specifies:
- When a certain situation occurs (the condition)
- What the testing agent should do (the action)
Condition Fields
Each condition object has the following fields:id: 0. Each ID must be unique — duplicate IDs are rejected.- When
idis 0, the condition must be the string"FIRST_MESSAGE"(represents the first message) - When
typeisaction_followup, the condition should be an integer referencing the previous condition ID - For standard conditions, the condition is a string describing the trigger
fixed_message field:- When
fixed_messageisfalse: Contains instructions for the testing agent to interpret - When
fixed_messageistrue: Contains the exact message to send word-for-word
FIRST_MESSAGE condition (id: 0) may have an empty action when the main agent speaks first.standard: Normal condition triggered by the conversation contextaction_followup: Fires on the next turn after the referenced condition — the testing agent sends the referenced condition’s message, waits for the main agent to reply, then sends this message
true or false on every condition — omitting it returns a validation error.true: Theactionfield contains the exact message to send word-for-wordfalse: Theactionfield contains instructions for the testing agent to interpret naturally
fixed_message: true.Validation Rules
The backend enforces the following constraints on all Structured Tests evaluators. Violations return a validation error.1. FIRST_MESSAGE required
1. FIRST_MESSAGE required
conditions array must satisfy all three requirements:conditionmust equal"FIRST_MESSAGE"(not empty, not any other string)idmust be0fixed_messagemust betrue
2. Non-empty actions
2. Non-empty actions
action field cannot be empty or whitespace-only. This rule applies to all conditions except FIRST_MESSAGE (id: 0), which may have an empty action when the main agent speaks first.3. type is required
3. type is required
type field. There is no default — omitting type returns a validation error.4. fixed_message is required
4. fixed_message is required
fixed_message field set to true or false. Omitting it returns a validation error.5. Unique condition IDs
5. Unique condition IDs
id. Duplicate IDs are rejected.6. Language required
6. Language required
scenario_language. Set it by assigning a personality with a language configured. If you provide a personality, the language is inferred automatically; if no personality is set, scenario_language must be explicitly provided.This also applies when changing an existing evaluator’s type to Structured Tests.First Message (id: 0)
The condition withid: 0 is special—it represents the first message the testing agent sends to start the conversation. Three rules apply to this condition:
conditionmust be the string"FIRST_MESSAGE"fixed_messagemust betrueactionmay be empty if the main agent speaks first
action to an empty string:
<silence> always gives way when the main agent starts talking, so a greeting that arrives during a leading <silence> drops the whole line; spoken text gives way according to the personality’s interruption settings. The conversation then continues through the other conditions as usual. To let the main agent greet first and then say an exact line, leave id: 0 empty and put the line on a standard condition with id: 1 that matches the greeting:Condition Types
Standard Conditions
Standard conditions are evaluated based on the conversation context. They trigger when the specified condition is met.Action Followup Conditions
Anaction_followup fires on the next turn after the condition it references. It does not fire immediately. The sequence is:
- Testing agent sends the message from condition X
- Main agent receives it and replies
action_followup(withcondition: X) fires — testing agent sends this message
action_followup has two key uses:
- Multi-part responses: send several messages across consecutive turns after a single trigger
- Scripted sequences: chain followups to deliver an exact sequence of messages from the testing agent, turn by turn, without needing any conditions to match — the sequence fires automatically regardless of what the main agent says
condition field must be an integer referencing the id of the previous condition.
action_followup does not fire immediately after condition X — it fires on the next turn, after the main agent has replied to condition X. The condition field holds the id (integer) of the condition whose turn triggers this followup.Action vs Fixed Message
Theaction field can be used in two ways, controlled by the fixed_message boolean:
Using action with fixed_message: false
When fixed_message is false, the action field provides instructions that the testing agent interprets to generate a natural response.
Using action with fixed_message: true
When fixed_message is true, the action field contains the exact text that the testing agent will say, word-for-word.
When to Use Each
Use fixed_message: false
- Natural, varied responses
- Testing agent adaptability
- When exact wording doesn’t matter
Use fixed_message: true
- Exact phrases required
- Testing specific keywords
- Compliance testing
- Reproducible test cases
- First message (id: 0) — always required
- Actions containing XML tags
fixed_message is required on every condition and must be explicitly set to true or false. It determines how action is interpreted: as instructions (false) or as the exact message to send (true).Supported Tags
Conditional Actions supports a variety of special tags in theaction field to control conversation flow and behavior.
Tags can be combined with text and other sibling tags. They run from left to right; do not nest tags inside one another, except inside a regional <voice ...>...</voice> block. <ivr> and <voicemail> remain whole-action tags.
Communication Tags
<ivr> - Interactive Voice Response
<ivr> - Interactive Voice Response
<ivr>. When a scenario contains the <ivr> tag, any DTMF digits pressed by the main agent (the agent being tested) are captured and appear in the transcript. This lets you write conditions that detect the exact digit pressed — for example, "The main agent pressed 1" — rather than relying on speech detection alone. Both <ivr> and <dtmf> can appear in any condition, not just id: 0.text: The IVR message to play (required)
<ignore_interruptions> - Protected Playback Span
<ignore_interruptions> - Protected Playback Span
<audio> clips, <hold>/<silence> pauses, or any mix. The typical use is a multi-part IVR menu of clips and pauses.<ivr>, this is a block tag scoped to a span: content goes between the opening and closing tags, it can appear more than once per action, and normal text or tags can come before and after it.What the main agent’s speech does during the span:- It does not interrupt playback or abort the remaining clips.
- It does not trigger a reply from the testing agent — during the span or after it ends.
- It is still transcribed and included in evaluation, so metrics can judge what the agent said over the menu.
<voicemail> - Voicemail Greeting
<voicemail> - Voicemail Greeting
text: Voicemail greeting message (optional) - if omitted, only plays beep; if provided, cannot be empty
<endcall> - End Call
<endcall> - End Call
Speech Control Tags
<silence> vs <hold> — both add pauses, but behave differently:<silence> when you want the conversation to feel natural and allow the main agent to jump in. Use <hold> when you need a guaranteed delay before the next message.<silence> - Add Pauses (interruptible)
<silence> - Add Pauses (interruptible)
time: Duration of silence (required) - format: “Xs” where X is a number (e.g., “1s”, “2.5s”)
<hold> - Hold Between Messages (not interruptible)
<hold> - Hold Between Messages (not interruptible)
time: Duration to wait before next message (required) - format: “Xs” where X is a number (e.g., “5s”, ”10s”)
<spell> - Spell Out Text
<spell> - Spell Out Text
<speed> - Control Speech Speed
<speed> - Control Speech Speed
<speed> tag or the end of the action. Add text to scope it to just those
words instead — the previous speed resumes afterwards:<speed>...</speed> block form.Attributes:ratio: Speed multiplier (required) - Valid range: 0.1 to 2.0
0.8–1.2 keeps speech natural; beyond that it becomes a stress test.text: Words to scope the ratio to (optional)
Cannot contain other tags — use the point form above if you need tags inside.
<volume> - Control Volume
<volume> - Control Volume
<speed>, it may appear anywhere and holds until the next <volume> tag or
the end of the action, and text scopes it to just those words:<volume>...</volume> block form.Attributes:ratio: Volume multiplier (required) - Valid range: 0 to 2
Examples:"1.5"for 50% louder,"0.5"for 50% quieter. Above1.0the output is clipped against the TTS signal, so prefer<= 1.0unless distortion is what you are testing.text: Words to scope the ratio to (optional)
<voice> - Switch the Testing Agent's Voice
<voice> - Switch the Testing Agent's Voice
text changes
every later utterance — including later conditions — until another persistent <voice> tag
changes it. This is the persistent form.This is how you put more than one speaker in a single simulated call: a caller handing the
phone to a family member, a supervisor taking over, or a different person picking up.text to speak only that text in the selected voice; the previous
voice resumes immediately afterward.<silence>, use an opening and closing
block instead. Nested inline tags are allowed only inside this regional voice block.text with the opening/closing form. All <voice> tags require
fixed_message: true.Attributes:-
provider: Voice provider (required) -cartesiaor11labs. Must be the same provider the evaluator already runs on. -
id: Voice ID (required) - must belong to that provider: -
model: TTS model (optional) - omit it to use the provider’s default:sonic-3.5for Cartesia,eleven_turbo_v2_5for ElevenLabs.
Interaction Tags
<dtmf> - Send DTMF Tones
<dtmf> - Send DTMF Tones
digits: DTMF digits to send (required) - e.g., “123”, “456#”, “*9”
<dtmf digits="{{test_profile.customer_number}}#" />.
The value is resolved per run and any formatting in it is stripped before the
tones are sent. See DTMF testing.<send_sms> - Send SMS
<send_sms> - Send SMS
text: SMS message content to send (required)
<client_message> - Send an RTVI Client Message
<client_message> - Send an RTVI Client Message
t: App-defined RTVI message type (required). Use the exact type your agent expects; Cekura does not invent or validate application-specific types.d: Optional app-defined payload. JSON is decoded before it is sent. Use single quotes around the attribute when the JSON contains double quotes, as in the example above.
<interruption> - Interrupt After Timeout
<interruption> - Interrupt After Timeout
time: Duration to wait before interrupting (required) - format: “Xs” where X is a number (e.g., “3s”, “5.5s”)
Environmental Tags
<background_noise> - Background Sounds
<background_noise> - Background Sounds
sound: Sound name (required) - see supported sounds belowvolume: Volume level (optional) - between 0 and 1.0; default applies if not specified
<background_noise> tag in evaluator actions. To use a custom audio file URL as background noise (e.g., https://your-domain.com/noise.mp3), use the Custom URL option in the Background Noise dropdown when creating or editing a personality — no tag required.<noise> - Play Sound Effects
<noise> - Play Sound Effects
sound: Sound name (required) - a name from the sound library below, or a directhttps://URL to an audio filevolume: Volume level (optional) - between 0 and 1.0time: Duration in ms (optional)
office, beep, cough1, cough2, female-crying, male-crying, ringback. The library grows over time - newly added sounds work with the same syntax as soon as they are available.ringback is ~6 seconds of a phone ringback tone (one ring, the gap, and the start of the next). The crying sounds are ~10 seconds of a person sobbing. Use them to check whether your agent notices a caller in distress and asks if everything is okay; shorten with time if needed.<network_simulation> - Simulate Network Issues
<network_simulation> - Simulate Network Issues
packet_loss: Packet loss percentage (required) - e.g.,"5"= 5% loss
Custom Functions
Functions let the testing agent fetch live data from your systems during the call and use it in what it says — look up an order, pull an account record, then respond with the real values instead of invented ones. Declare functions once in a top-levelfunctions array (a sibling of role and conditions), then use them from any condition’s action.
Function fields
_, -; up to 64 characters.rest_api — an HTTP call to your API.true (the default), the function runs once automatically at the start of the call, in the background — its values are ready before the conversation needs them. Set false to run the function only when a condition invokes it with a <function> tag.GET or POST.{{test_profile.*}} substitution. Must be an http(s) URL that is publicly reachable — internal and loopback addresses are refused at call time.{ "Authorization": "Bearer ..." }. Values must be strings or numbers.POST only). Objects and arrays are sent as JSON; strings are sent as-is.When a function runs
id: 0), on both standard and action_followup conditions, fixed or non-fixed.
Using the response
Two ways to get fetched values into the testing agent’s replies:-
Value placeholder —
{{function.lookup_order.status}}(fixed messages only). Substitutes one mapped output into the exact spoken text: -
Non-fixed actions — no placeholder needed. When
fixed_messageisfalse, the testing agent is automatically given the function’s results (your named outputs plus the response) and phrases its reply from them:
"Let me check. <function name=\"lookup_order\"/> It shows as {{function.lookup_order.status}}."Response mapping (JSONPath)
Eachresponse_mapping entry gives a short output name to a value in the JSON response. An entry is either a path string, or an object with path and a default:
Chaining functions
A function’sconfig can reference an earlier function’s outputs, so one call can feed the next — for example, look up a customer, then verify with the fetched name:
auto_run or a tag on an earlier condition.
If the call fails
A failed function never breaks the conversation. On a timeout, an error response, or an unreachable URL:- Mapped outputs fall back to their declared
default. - A placeholder with no default is spoken as-is (
"...status is {{function.lookup_order.status}}") — declare defaults for anything a fixed message references. - On non-fixed actions, the testing agent is told the lookup failed, so it doesn’t invent values.
Rules
- Function
names must be unique;<function>tags and{{function.*}}placeholders must reference a declared function. - Placeholders require
fixed_message: true, and the key must be an output declared in that function’sresponse_mapping. - Neither tags nor placeholders may appear on the First Message (
id: 0) — useauto_runand reference the values from a later condition.
auto_run function — see Example 5 below.
Attached Audio
Attach a pre-recorded audio clip to a scenario and the testing agent plays the recording instead of speaking the text with TTS when a referencing condition fires. A single clip can be referenced from any number of conditions and even multiple times within one action. Use it when the exact voice, accent, tone, or non-speech sound matters — reproducing a real caller’s recording, a specific IVR prompt, background audio a synthetic voice can’t recreate, or a regression case captured from a live call.fixed_message: true). It is not available on instruction-based (fixed_message: false) conditions — those are meant to vary, and a recording is fixed by definition.How it works
- You upload an audio file to the scenario, optionally attaching it to a condition (from the editor UI or the API). Each clip gets a human-readable name that becomes its id — for example,
greetingorhold-music. - Cekura transcribes it automatically in the background — this is why a clip is briefly processing before it’s ready to use.
- Reference the clip from any number of fixed-message actions by pasting its tag:
<audio id="greeting"/>. You can write this tag by hand, or let the upload endpoint insert it for you when you passcondition_id. - When the condition fires, text, recordings, and tags run from left to right. The same clip plays wherever it is referenced.
Attaching audio (UI)
In the Conditional Actions editor, each fixed-message condition has an Attach audio at cursor control:- Place the cursor where the recording should play, click Attach audio at cursor, and pick a file. You can attach more than one recording to the same action.
- When processing finishes, each recording shows its read-only transcript and an audio player. Replace or Remove affects only that recording; surrounding text and tags stay in place.
- While any attached clip is still processing (or has failed), runs are blocked for that scenario until it resolves — see Statuses & run gating.
Attaching audio (API)
Four endpoints on the scenario manage attached audio. Clips are uploaded asmultipart/form-data.
status: "pending"; poll the list endpoint until it becomes ready. Each entry looks like:
condition_audio summary with every clip’s tag and referencing conditions, so an author editing instructions directly can discover which clip ids exist.
<audio> tag and as its stored object name. Letters, digits, underscores and hyphens, max 64 characters; must be unique within the scenario (case-insensitive). Omit to derive a readable name from the filename (lowercased, hyphenated, trimmed to 32 characters).id of the condition to attach the clip to. The condition must already exist and have fixed_message: true. Omit to add the clip to the scenario without touching any action — then reference it yourself with <audio id="..."/> from as many actions as needed.wav, mp3, m4a, ogg, webm, flac.condition_id. The complete action with one <audio /> marker where the new recording should be inserted. Existing managed audio tags must remain in the template.condition_id. Points that condition’s existing tag at the new clip. To swap a clip’s audio while keeping every reference to it, PUT to condition-audio/{audio_id}/ instead.Statuses & run gating
Each attached clip moves through these statuses:ready — the run is rejected with a clear message ("still transcribing — wait for it to finish" for pending/processing, "failed to transcribe — retry or remove it" for failed).
Complete Examples
Example 1: Appointment Cancellation
Example 2: IVR Navigation
- Inbound (main agent IS the IVR): Use
<dtmf>to navigate the IVR menu played by the main agent.<dtmf>can appear in any condition. - Outbound (testing agent simulates an external IVR): Use
<ivr text="..." />to play the IVR menu.<ivr>can appear in any condition. When the scenario contains<ivr>, DTMF digits pressed by the main agent appear in the transcript — write your conditions using the specific digit (e.g.,"The main agent pressed 1").
Example 3: Multi-Part Response with Followup
- Testing agent sends condition 0: “Hi, I need help with my order”
- Main agent replies
- Action followup (condition: 0) fires — testing agent: “Also mention you haven’t received a confirmation email”
- Main agent replies
- Action followup (condition: 1) fires — testing agent: “And ask if you can get expedited shipping”
- Main agent replies — condition 3 (standard) matches and fires
Example 4: Using Advanced Tags
Example 5: Live Data via a Custom Function
Oneauto_run function feeding both flows: condition 1 drops a fetched value into a fixed message with a {{function.*}} placeholder, and condition 2 lets the testing agent phrase a non-fixed reply from the same response — no placeholder needed.
- As the call connects,
lookupruns once in the background (auto_run) — say it returns{"status": "shipped", "customer_name": "Jane Doe"} - Testing agent sends condition 0: “Hi, I’d like to check on my order.”
- The agent asks for the name — condition 1 (fixed) fires with the placeholder substituted verbatim: “It’s under Jane Doe.”
- The agent replies — condition 2 (non-fixed) fires; the testing agent is given the lookup results automatically and phrases them naturally: e.g. “On my side it shows as shipped — is that what you see?”
- The agent confirms — condition 3 closes the call
defaults take over: condition 1 speaks “It’s under unknown.” and the testing agent in condition 2 is told the lookup failed rather than inventing a status.
On-demand variant (auto_run: false): to run the lookup only at a specific point in the flow instead of at call start, turn off auto_run and invoke it with a tag — the function completes before the rest of the action is spoken:
Best Practices
Clear Conditions
- ✅ “The agent asks for your email”
- ❌ “Email”
Specific Actions
- ✅ “Provide your email address”
- ❌ “Answer the question”
Logical Flow
id: 0 (FIRST_MESSAGE), then follow natural conversation progression.Common Patterns
Pattern 1: Conditional Branching
Handle different paths based on agent response:Pattern 2: Information Gathering
Progressively provide information as requested:Pattern 3: Scripted Sequence with action_followup
Chainaction_followup conditions to deliver an exact sequence of messages across turns, with no conditions to match — each followup fires automatically on the next turn after the main agent replies, regardless of what the main agent says:
Tips
Troubleshooting
Validation error: first condition must have condition='FIRST_MESSAGE'
Validation error: first condition must have condition='FIRST_MESSAGE'
conditions array has all three:condition was left empty (""); it must now be the exact string "FIRST_MESSAGE".Validation error: type is required
Validation error: type is required
type field.Solution: Add type explicitly to every condition. There is no default:Validation error: fixed_message is required
Validation error: fixed_message is required
fixed_message field.Solution: Add fixed_message explicitly to every condition set to true or false:Validation error: duplicate condition ID
Validation error: duplicate condition ID
id.Solution: Ensure every condition has a unique integer id. Use sequential numbering (0, 1, 2, …) to avoid collisions.Validation error: scenario_language is required
Validation error: scenario_language is required
scenario_language explicitly in the request.Condition Not Triggering
Condition Not Triggering
- Make the condition more specific
- Check if a previous condition is matching instead
- Verify the condition describes what the agent says, not what the evaluator should do
action_followup firing unexpectedly or not at all
action_followup firing unexpectedly or not at all
action_followup fires at the wrong time or doesn’t fire.Explanation: action_followup fires on the next turn after the referenced condition — after the testing agent sends condition X and the main agent replies. It does not fire in the same turn as condition X.Solutions:- Ensure
conditionpoints to the correct ID of the preceding condition - If chaining multiple followups, each one references the ID of the previous followup (not the original condition)
First Message Not Sending
First Message Not Sending
- Ensure you have a condition with
id: 0andcondition: "FIRST_MESSAGE" - Verify
fixed_message: trueis set on the first condition - Check that the role is defined
- If the agent speaks first, set
actionto""(empty string is allowed for FIRST_MESSAGE only)
Can't run — an attached audio clip is still transcribing / failed
Can't run — an attached audio clip is still transcribing / failed
ready. Wait for transcription to finish (pending/processing), or if a clip failed, re-upload it or remove it. Check clip statuses via GET scenarios/{id}/condition-audio/.Attach audio option is missing on a condition
Attach audio option is missing on a condition
fixed_message: true on the condition first.