You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 8669d7f
Browse filesBrowse the repository at this point in the historyBrowse files
TEST-201 docs: bring Simulations pages up to date with Composer test generation and the run API (#1342)
## Description
Linear: [TEST-201](https://linear.app/vapi/issue/TEST-201/bring-simulations-docs-up-to-date-with-composer-onboarding-and-the-run). Companion in-repo docs: VapiAI/vapi#23428.
- **Composer**
- New section **Generate tests for an assistant** (`/composer#generate-tests-for-an-assistant`): requirements draft → **Generate coverage** → suite card with mocked tools and not-tested gaps → **Run suite**, plus edits and a tip to run five or more iterations.
- Limitations: "Cannot test your agent" is replaced by "Cannot test every behavior".
- Safety › Deletion no longer claims Composer can delete resources. It has no delete tools; only `discard-draft`, for your own unpublished draft.
- **Advanced**
- "Mock tool responses" (anchor unchanged) says generated suites come pre-mocked and explains how to review those mocks.
- New sections: "Handoffs and transfers in simulations", covering the static-handoff refusal and dynamic handoffs, with a gaps table; and "Hooks and call endings", covering the removed `customer.speech.timeout` and the tester hang-up ending as `customer-ended-call`.
- Four troubleshooting rows.
- **Manage (API tabs)**
- The `Idempotency-Key` header on `POST /eval/simulation/run`.
- Run, item and suite filters (`suiteId`, `targetType`, `targetId`, `status`, `simulationId`, `assistantId`, `squadId`, `name`).
- A note that any query parameter switches these lists to `{ metadata, results }`.
- **Quickstart:** the unmocked-tools warning now applies to hand-built suites only.
- **Best practices / Run and maintain:** one iteration while setting up, then five or more before trusting a pass rate.
- **Overview, Manage, Voice testing:** the handoff mentions link to the new limits section.
## Evidence
Every behavior was checked against VapiAI/vapi `main`:
- Dashboard labels: `features/simulationsOnboarding/constants.ts`.
- Idempotency: `simulationRunIdempotencyKeyParse.ts` and `simulationRunIdempotentReplayGet.ts`.
- Filters: `simulationRun.types.ts` and `GetSimulationSuitesDTO.ts`.
- Handoffs and hooks: `handoffSimulationRefusalGet.ts` and `simulationTargetReengagementHooks/`.
## Testing Steps
- [x] `fern check` (5.151.1) passes: 0 errors. The 15 warnings match the untouched main baseline.
- [ ] Check the preview deployment for the changed pages
Copy file name to clipboardExpand all lines: fern/composer.mdx
+42-3Lines changed: 42 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -54,15 +54,16 @@ Composer understands voice agent architecture and Vapi's capabilities. It can cr
54
54
- Configure integrations and webhooks
55
55
- Update agent prompts and settings
56
56
- Troubleshoot technical issues
57
+
- Create and run [simulations](/observability/simulations-overview), and [generate a test suite](#generate-tests-for-an-assistant) for an assistant
57
58
- Answer questions about Vapi features
58
59
- Recommend best practices for your use case
59
60
60
61
### Limitations
61
62
62
-
-**Cannot delete resources**: Composer cannot delete assistants, tools, phone numbers, or any other resources. This is an intentional safety measure. Use the dashboard sidebar to delete resources manually.
63
+
-**Cannot delete resources**: Composer cannot delete assistants, tools, phone numbers, or any other resources. This is an intentional safety measure. Use the dashboard sidebar to delete resources manually. Composer can discard your own unpublished assistant or tool draft, which never changes the live resource.
63
64
-**Cannot access external systems**: Composer operates within Vapi and cannot reach your CRM, database, or other third-party systems directly.
64
65
-**Cannot make business logic decisions**: You provide the domain knowledge; Composer handles the technical configuration.
65
-
-**Cannot test your agent**: You need to test the agent yourself and provide feedback.
66
+
-**Cannot test every behavior**: Generated suites run in chat, so they can't check voice-only behavior such as pronunciation. They also skip [some handoffs and transfers](/observability/simulations-advanced#handoffs-and-transfers-in-simulations). Test these with real calls.
66
67
-**Cannot deploy to production automatically**: You control when and how changes go live.
67
68
68
69
## Common use cases
@@ -171,6 +172,44 @@ Composer understands voice agent architecture and Vapi's capabilities. It can cr
171
172
</Accordion>
172
173
</AccordionGroup>
173
174
175
+
## Generate tests for an assistant
176
+
177
+
Ask Composer to generate tests for an assistant, for example "Generate a test suite for my dental office agent." Composer reads the assistant's prompt and tools and drafts a list of testable requirements. Once you agree on them, it builds a [simulation suite](/observability/simulations-overview) that covers them.
178
+
179
+
<Steps>
180
+
<Steptitle="Review the requirements">
181
+
Composer drafts a numbered list of **Test requirements**. Each one is a behavior a simulated caller could trigger. Composer tags a requirement **Assumption** when the prompt implies it without stating it. It tags a requirement **Can't be simulated** when a chat simulation can't check it, such as pronunciation or the exact wording of the first message. It also asks up to three open questions when an answer would change a requirement.
182
+
</Step>
183
+
<Steptitle="Accept or change them">
184
+
Select **Generate coverage** to accept the draft, or describe the changes you want in the chat. Composer revises the draft and asks you to review it again. It never generates a suite from requirements you haven't agreed to.
185
+
</Step>
186
+
<Steptitle="Review the suite">
187
+
Composer generates the suite and shows it on a card. The card shows:
188
+
189
+
- Each simulation. A simulation covers one caller goal, and the requirements it covers become its success criteria.
190
+
- How many requirements are covered, and why any aren't.
191
+
- Handoffs and transfers the suite doesn't test, marked **Not tested**, with the reason.
192
+
- The tools each simulation mocks.
193
+
194
+
Every tool in the assistant's tool list that would reach an external system is [mocked by default](/observability/simulations-advanced#mock-tool-responses). Tools that only a hook calls aren't mocked.
195
+
</Step>
196
+
<Steptitle="Run the suite">
197
+
Select **Run suite** on the card, or ask Composer to run it. The suite runs once, in chat, against the assistant it was generated for. Composer doesn't wait for the run. Follow progress and results on the [runs page](https://dashboard.vapi.ai/simulations/runs). When the run ends, ask Composer about the results.
198
+
</Step>
199
+
</Steps>
200
+
201
+
After generating a suite, ask Composer to change it in the same conversation:
202
+
203
+
-**Remove simulations**: Removed simulations are unlinked from the suite, not deleted.
204
+
-**Rewrite a simulation**: Describe the change, for example "make the caller refuse to give a date of birth."
205
+
-**Retry failed simulations**: When some simulations couldn't be generated, Composer offers to try them again.
206
+
207
+
Composer saves the agreed requirements with the suite, and every edit works from them. If you change the requirements later, Composer builds a new suite. The existing suite doesn't change. It remains in your account until you delete it from the dashboard.
208
+
209
+
<Tip>
210
+
One passing run shows the assistant **can** succeed, not that it will every time. Run five or more iterations before you trust the pass rate. Ask Composer to run more, or [rerun the suite](/observability/simulations-manage#rerun-a-suite) with more **Iterations**. See [testing best practices](/test/simulations-best-practices).
211
+
</Tip>
212
+
174
213
## Safety features
175
214
176
215
Composer includes safeguards to prevent accidental or irreversible changes to your account. You control how much Composer can do on its own with per-conversation approval modes.
@@ -188,7 +227,7 @@ Approval enforcement always happens on the server, and read operations never req
188
227
189
228
### Deletion
190
229
191
-
Composer can delete resources such as assistants, tools, phone numbers, squads, and files. Deletion follows your approval mode: unless you have selected Bypass, Composer asks for your explicit approval before it deletes anything. Because deletion is permanent, keep an approval mode on if you want a confirmation step before a resource is removed.
230
+
Composer does not delete resources. Delete assistants, tools, phone numbers, squads, and files from the dashboard yourself. When Composer removes a simulation from a suite it generated, the simulation is unlinked from the suite, not deleted.
Copy file name to clipboardExpand all lines: fern/observability/simulations-advanced.mdx
+43Lines changed: 43 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -41,6 +41,17 @@ Open a suite and select **Edit**, then **Next**. The review step contains the **
41
41
42
42
During a simulation, the [**assistant**](/assistants) or [**squad**](/squads) under test runs its real [**tools**](/tools). Mock a tool to return fixed text instead, so that tool never calls its live service or API. A mock intercepts only the tool it names; every other tool the assistant or squad calls still runs for real.
43
43
44
+
<Note>
45
+
Suites that [Composer generates](/composer#generate-tests-for-an-assistant) come with mocks already set. Every tool in the assistant's tool list that would reach an external system is mocked, including handoffs and transfers that can't run safely. Tools that only end or route the conversation run normally, such as end call, voicemail, DTMF, and static handoffs.
46
+
47
+
To change a generated mock, edit the suite and open **Tool mocks & webhooks**. Change the **Mock result**, or turn off **Enabled** to run the tool live. Suites you build yourself start with no mocks, so every tool runs for real until you add one.
48
+
</Note>
49
+
50
+
<Warning>
51
+
52
+
Generated mocks don't cover everything. Tools that only an assistant [**hook**](/assistants/assistant-hooks) calls aren't mocked. Explicit [scenario webhooks](#send-webhooks-on-run-start-and-end), unmocked tools, and destination requests can still contact your server. Simulation lifecycle [server messages](/server-url), including the end-of-call report, are suppressed; that doesn't disable these other requests. Check them before running against production systems.
53
+
</Warning>
54
+
44
55
<Tabs>
45
56
<Tabtitle="Dashboard">
46
57
@@ -126,6 +137,34 @@ Return plain text to test how the assistant handles a service that is unavailabl
126
137
127
138
These strings replace the selected tool's output. They do not produce an HTTP error, add network latency, or cause a network timeout.
128
139
140
+
## Handoffs and transfers in simulations
141
+
142
+
In a simulation, a [**handoff**](/squads/handoff) with a fixed list of destinations runs only when the model picks one of them. If the model names no destination, asks for `dynamic`, or names an assistant that isn't on the list, the handoff is refused. The assistant doesn't switch, and the model is asked to try again with a listed destination. Your server isn't asked for a destination. On a live call, a missing or `dynamic` destination can instead trigger a request to your server.
143
+
144
+
A [**dynamic handoff**](/squads/handoff/destinations#dynamic-handoffs) runs as it does on a live call, so it asks your server for a destination unless you [mock it](#mock-tool-responses).
145
+
146
+
In a suite you build yourself, `transferCall` isn't mocked automatically. In a voice simulation, an external transfer ends the simulation without dialing the phone number or SIP destination, so it doesn't test that destination. If the tool asks your server to choose a destination, that lookup can still run. Mock the tool to avoid that request, and use a real test call to test the external transfer itself.
147
+
148
+
### Gaps in generated suites
149
+
150
+
When [Composer generates a suite](/composer#generate-tests-for-an-assistant), it lists each handoff or transfer the suite doesn't exercise as **Not tested**, with the reason:
151
+
152
+
| Reason | What the suite does |
153
+
| --- | --- |
154
+
| Your server chooses the destination (a dynamic handoff) | Mocks the handoff. |
155
+
| It transfers to a phone number or your server | Mocks the transfer. |
156
+
| The destination assistant couldn't be checked. It uses MCP tools or a pinned version, it didn't load, or Composer reached its limit on how many assistants it reads. | Mocks the handoff into it. |
157
+
| It hands off by assistant name outside a [**squad**](/squads)| Doesn't mock it. A name-based handoff can't resolve outside a squad, so the simulation never reaches it. |
158
+
| A built-in tool, such as `endCall`, has the same name as a mocked tool | The mock answers both. |
159
+
160
+
Other supported handoffs and assistant-to-assistant transfers run normally. Cover the paths above with test calls against sandbox systems.
161
+
162
+
## Hooks and call endings
163
+
164
+
Simulations run the [**hooks**](/assistants/assistant-hooks) on the assistant or squad under test, just like a real call. For example, a `call.ending` hook runs in a voice simulation. The exception is `customer.speech.timeout`. Those hooks are removed from the assistant and every squad member so the assistant doesn't prompt the AI tester when the tester pauses. To test re-engagement, set `targetOverrides.hooks` on the scenario. Those hooks are used exactly as set.
165
+
166
+
If the AI tester hangs up first in a voice simulation, the call ends with `customer-ended-call`, just like a real caller hanging up. Anything that depends on a caller hang-up runs normally.
167
+
129
168
## Send webhooks on run start and end
130
169
131
170
Turn on webhooks to notify your server when a simulation iteration starts or ends. Each webhook sends a `POST` request to the URL you configure.
@@ -351,6 +390,10 @@ For a transient scenario, set the same `targetOverrides` on the inline `scenario
351
390
| There is no audio or recording | Check whether the run used chat mode. Use voice mode to test or record audio. |
352
391
| Start or end webhooks do not trigger | Confirm the corresponding hook and its URL are configured on the scenario. |
353
392
| A tool mock does not apply | Confirm that `toolName` matches the configured tool name exactly and that `enabled` is `true`. |
393
+
| A handoff is refused in the transcript | The model gave no destination, `dynamic`, or an assistant the handoff doesn't list. Check the handoff's destinations and the prompt's routing instructions. See [**Handoffs and transfers in simulations**](#handoffs-and-transfers-in-simulations). |
394
+
| A generated suite lists a handoff or transfer as **Not tested**| That path couldn't run safely, so Composer mocked it, or the simulation can't reach it. Cover it with a test call. See [**Gaps in generated suites**](#gaps-in-generated-suites). |
395
+
| The assistant never asks whether the AI tester is still there | Simulations remove `customer.speech.timeout` hooks. To test them, set `targetOverrides.hooks` on the scenario. See [**Hooks and call endings**](#hooks-and-call-endings). |
396
+
| A voice simulation's assistant call ends as `customer-ended-call`| The AI tester hung up first, which matches a real caller hanging up. |
354
397
| A tool looks up the wrong caller | Set `customer.number` in the scenario's variable values. See [**Simulate a call from a specific phone number**](#simulate-a-call-from-a-specific-phone-number). |
355
398
| A latency limit fails without a measured value | No turn in the call measured that metric. Confirm the run used voice mode and that the conversation completed at least one exchange. |
356
399
| Latency limits show as skipped | Check whether the run used chat mode or the assistant under test uses GPT-Live. Both skip latency limits. |
Copy file name to clipboardExpand all lines: fern/observability/simulations-manage.mdx
+47-1Lines changed: 47 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -92,6 +92,31 @@ curl -X GET "https://api.vapi.ai/eval/simulation/run/<run-id>" \
92
92
93
93
The run includes `status` and `itemCounts` (`total`, `passed`, `failed`, and so on). Fetch the result for each simulation iteration with `GET /eval/simulation/run/<run-id>/item`. Each item's `results` holds its `evaluations` and, when the scenario sets latency limits, its `latencyEvaluations`. See [**Review latency results**](/observability/simulations-advanced#review-latency-results).
94
94
95
+
Filter the lists with query parameters:
96
+
97
+
| Endpoint | Filters |
98
+
| --- | --- |
99
+
|`GET /eval/simulation/run`|`suiteId` (repeat for up to 100 suites), `targetType` (`assistant` or `squad`), `targetId`, and `status` (`queued`, `running`, or `ended`). You can send `targetType` or `targetId` without the other. |
100
+
|`GET /eval/simulation/run/<run-id>/item`|`status` (`queued`, `running`, `evaluating`, `passed`, `failed`, or `canceled`) and `simulationId`. |
101
+
|`GET /eval/simulation/suite`|`assistantId` and `squadId` (repeat each for up to 100 IDs; if you send both, a suite must match both) and `name`, which matches part of the suite name. |
102
+
103
+
```bash
104
+
curl -X GET "https://api.vapi.ai/eval/simulation/run?suiteId=<suite-id>&status=ended" \
105
+
-H "Authorization: Bearer $VAPI_API_KEY"
106
+
```
107
+
108
+
Each list also accepts `limit` (default 100, maximum 1,000) and `page`.
109
+
110
+
<Note>
111
+
Adding query parameters changes the response shape:
112
+
113
+
- With no query parameters, these endpoints return a plain array.
114
+
- The run and run item lists return `{ "metadata": { ... }, "results": [ ... ] }` when you send any query parameter.
115
+
- The suite list returns the same object when you send `limit`, `page`, `name`, `assistantId`, or `squadId`.
116
+
117
+
If your code expects an array, read `results` whenever you send a query parameter.
118
+
</Note>
119
+
95
120
</Tab>
96
121
</Tabs>
97
122
@@ -121,6 +146,27 @@ curl -X POST "https://api.vapi.ai/eval/simulation/run" \
121
146
122
147
Use `"provider": "vapi.websocket"` for voice.
123
148
149
+
To retry a run request without starting a second run, send an `Idempotency-Key` header with a value you generate, such as a UUID:
150
+
151
+
```bash
152
+
curl -X POST "https://api.vapi.ai/eval/simulation/run" \
- A retry with the same key and the same body returns the run the first request created, with `reused: true`. It doesn't start or bill the run again.
165
+
- Reusing a key with a different body returns `409`. Use a new key for a different run.
166
+
- If a request returns `503` with the code `SIMULATION_WORK_DISABLED`, simulations are paused across Vapi. Retry later. For any other `503`, retry with the same key.
167
+
- A key is up to 255 printable ASCII characters and is scoped to your organization. An empty or invalid key returns `400`.
168
+
- Keys don't expire. A key stays tied to its run for as long as the run exists.
169
+
124
170
</Tab>
125
171
</Tabs>
126
172
@@ -138,7 +184,7 @@ Create a regression test when you fix a defect or find an unexpected response. R
138
184
139
185
### Edge-case tests
140
186
141
-
Test realistic variations such as an ambiguous request, an impatient customer, an unavailable appointment, a tool error, an interruption, or a handoff. Base AI tester personalities on the customer types the [**assistant**](/assistants) or [**squad**](/squads) handles. Synthetic callers don't reliably reproduce every interruption, silence, or background-noise condition. Keep controlled real-call checks for problems the simulation can't reproduce.
187
+
Test realistic variations such as an ambiguous request, an impatient customer, an unavailable appointment, a tool error, an interruption, or a handoff. Base AI tester personalities on the customer types the [**assistant**](/assistants) or [**squad**](/squads) handles. Synthetic callers don't reliably reproduce every interruption, silence, or background-noise condition. Keep controlled real-call checks for problems the simulation can't reproduce. Some handoffs and transfers [behave differently in simulations](/observability/simulations-advanced#handoffs-and-transfers-in-simulations).
Copy file name to clipboardExpand all lines: fern/observability/simulations-overview.mdx
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -89,7 +89,7 @@ Simulations test live interactions. An AI tester with a defined personality and
89
89
90
90
Choose Evals when you want to lock down a specific response or verify a tool call's arguments with fast, rerunnable checks.
91
91
92
-
Choose Simulations when you want to see how your assistant or squad performs against an AI tester with different personalities, multi-turn flows, squad handoffs, or voice.
92
+
Choose Simulations when you want to see how your assistant or squad performs against an AI tester with different personalities, multi-turn flows, squad handoffs, or voice. Some handoffs and transfers [behave differently in simulations](/observability/simulations-advanced#handoffs-and-transfers-in-simulations).
93
93
94
94
See [testing voice agents](/test/voice-testing) for guidance on combining both
Copy file name to clipboardExpand all lines: fern/observability/simulations-quickstart.mdx
+4-1Lines changed: 4 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -18,7 +18,7 @@ A reusable **simulation suite** that asks an AI tester to book an appointment wi
18
18
You create Riley and the structured output during this quickstart.
19
19
20
20
<Warning>
21
-
Simulations run unmocked tools for real. Before running, inspect Riley's tools and use sandbox integrations or [mock every tool](/observability/simulations-advanced#mock-tool-responses) that could create a booking, send a message, or affect a real customer.
21
+
In a suite you build yourself, every tool without a [mock](/observability/simulations-advanced#mock-tool-responses) runs for real. Before running, inspect Riley's tools and use sandbox integrations or mock every tool that could create a booking, send a message, or affect a real customer. Suites that [Composer generates](/composer#generate-tests-for-an-assistant) mock tools that reach external systems by default.
22
22
</Warning>
23
23
24
24
## Build and run a simulation
@@ -232,10 +232,13 @@ The response returns the created suite, including its `id`. Save the `id` to pas
232
232
233
233
This request starts a suite run against your [**assistant**](/assistants) or [**squad**](/squads). Pass the suite, the assistant or squad to test, the transport provider (`vapi.websocket` for voice or `vapi.webchat` for chat), and the number of iterations.
234
234
235
+
Send an `Idempotency-Key` header with a unique value for this run. Reuse the key and the same body when retrying, so a lost response cannot start and bill a second run. This applies to the first run and every rerun. See [**Run request idempotency**](/observability/simulations-manage#rerun-a-suite) for replay and error behavior.
236
+
235
237
```bash
236
238
curl -X POST "https://api.vapi.ai/eval/simulation/run" \
0 commit comments