Variable Interpolation
Variable interpolation allows you to dynamically insert values into step content, request bodies, and assertions. This is essential for building realistic test flows that depend on runtime data.
Syntax Overview
The test runner supports two interpolation syntaxes:
| Syntax | Example | Use Case |
|---|---|---|
${path} | ${bag.var.accountId} | Standard interpolation with braces |
$path | $bag.var.accountId | Shorthand (no braces needed) |
Both syntaxes support nested paths with dots and array brackets.
Variable Bag Structure
All variables are stored in a “bag” object that’s available throughout test execution:
{
bag: {
var: {}, // Variables from saveAs/assign
steps: {}, // Outputs by step ID
last: {
assistant: "", // Last assistant response
user: "", // Last user message
request: {} // Last request result
},
transcript: [] // Full conversation history
}
}Accessing Variables
From saveAs / assign
When a step uses saveAs or assign, the result is stored in bag.var:
// Step 1: Save request result
{
"type": "request",
"requestId": "accounts.create_test",
"saveAs": "newAccount"
}
// Step 2: Access the saved value
{
"type": "message",
"content": "Show account ${bag.var.newAccount.accountId}"
}From Step Outputs ($steps)
Access outputs by step ID using the $steps prefix:
// Step with ID
{
"type": "request",
"id": "create_acct",
"requestId": "accounts.create_test"
}
// Access by step ID
{
"type": "message",
"content": "Created account: $steps.create_acct.output.accountId"
}Last Response ($bag.last)
Access the most recent values:
{
"type": "message",
"content": "You said: ${bag.last.assistant}"
}Available in bag.last:
assistant— Last assistant response textuser— Last user message textrequest— Last request response payload
Path Syntax
Dot Notation
Access nested properties with dots:
bag.var.account.details.balanceArray Access
Access array elements with brackets:
bag.var.accounts[0].id
bag.transcript[2].contentMixed Paths
Combine both:
bag.steps.get_users.output.users[0].nameExamples
In Message Content
{
"type": "message",
"content": "Transfer ${bag.var.amount} from account ${bag.var.sourceAccount.id} to ${bag.var.targetAccount.id}"
}In Request Input Mappings
{
"type": "request",
"requestId": "transfers.create",
"inputMappings": {
"amount": "${bag.var.transferAmount}",
"fromAccount": "$steps.create_source.output.accountId",
"toAccount": "$steps.create_target.output.accountId"
}
}In Rubrics
{
"type": "assistant_check",
"mode": "judge",
"rubric": "The assistant should mention the account balance of ${bag.var.expectedBalance}"
}Request Body Templates
In request definitions (requests/*.json), use {key} syntax in the body template:
{
"requests": [
{
"id": "accounts.create_test",
"transport": {
"http": {
"method": "POST",
"body": {
"account_id": "{accountId}",
"balance": "{balance}",
"status": "{status}"
}
}
}
}
]
}When executing this request from a step:
{
"type": "request",
"requestId": "accounts.create_test",
"inputMappings": {
"accountId": "acct-12345",
"balance": 5000,
"status": "active"
}
}The {accountId}, {balance}, and {status} placeholders are replaced with the values from inputMappings.
Environment Variables
Environment variables can be interpolated in configuration files:
{
"env": {
"baseUrl": "${BOT_BASE_URL}",
"headers": {
"x-api-key": "${BOT_API_KEY}"
}
}
}Set these in your environment:
export BOT_BASE_URL="https://api.example.com"
export BOT_API_KEY="your-secret-key"Special Values
JSON Serialization
When a variable value is an object, it’s automatically JSON-serialized in string contexts:
{
"content": "Account details: ${bag.var.account}"
}
// Result: "Account details: {\"id\":\"123\",\"balance\":1000}"Missing Values
${path}— Returns empty string if path doesn’t exist$path— Keeps original text if path doesn’t exist (useful for debugging)
Complete Example
{
"id": "transfer-flow",
"name": "Money Transfer Test",
"steps": [
{
"type": "request",
"id": "source",
"requestId": "accounts.create_test",
"inputMappings": { "balance": 10000, "name": "Source Account" },
"saveAs": "sourceAccount"
},
{
"type": "request",
"id": "target",
"requestId": "accounts.create_test",
"inputMappings": { "balance": 0, "name": "Target Account" },
"saveAs": "targetAccount"
},
{
"type": "message",
"content": "I want to transfer $500 from ${bag.var.sourceAccount.name} to ${bag.var.targetAccount.name}"
},
{
"type": "assistant_check",
"mode": "judge",
"rubric": "The assistant should confirm the transfer between the accounts with IDs $steps.source.output.id and $steps.target.output.id"
},
{
"type": "message",
"content": "Yes, please proceed with the transfer"
},
{
"type": "extract",
"variableName": "transferId",
"description": "The transfer confirmation number"
},
{
"type": "request",
"id": "verify_source",
"requestId": "accounts.get",
"inputMappings": { "accountId": "$steps.source.output.id" }
},
{
"type": "assistant_check",
"mode": "variable_check",
"variablePath": "bag.steps.verify_source.output.balance",
"expectEquals": "9500"
}
]
}Debugging Tips
- Check step IDs — Ensure the
idproperty is set on steps you want to reference - Use
saveAs— Explicitly save values for clearer access viabag.var - Log intermediate values — Add a message step to echo variables for debugging
- Test paths — Use
$pathsyntax to see if values are missing (it preserves the original text)
Next Steps
- Test Steps — Step type reference
- Requests & Auth — Configure API requests
- Test Runner — Execution engine overview