diff --git a/fern/assistants/structured-outputs-examples.mdx b/fern/assistants/structured-outputs-examples.mdx index 26560fc24..36472a2f5 100644 --- a/fern/assistants/structured-outputs-examples.mdx +++ b/fern/assistants/structured-outputs-examples.mdx @@ -1122,6 +1122,67 @@ Process loan or credit applications with financial information. ``` +## Conditional extraction + +Attach [conditions](/assistants/structured-outputs#conditional-generation) to a structured output so it only runs when a call is worth analyzing. This keeps noisy, incomplete calls out of your data and makes the reason for a skip visible in the assistant preview, call logs, and sessions. + +This example only extracts a post-call outcome when the customer actually reached and ended the conversation (at least 6 messages, 15 seconds, and a customer-ended call): + + +```typescript title="TypeScript (Server SDK)" +const outcome = await vapi.structuredOutputs.create({ + name: "Call Outcome", + type: "ai", + description: "Capture the outcome and any follow-up requested by the customer", + schema: { + type: "object", + properties: { + outcome: { + type: "string", + enum: ["resolved", "callback-requested", "escalated", "no-resolution"] + }, + followUpNotes: { type: "string" } + }, + required: ["outcome"] + }, + conditions: [ + { type: "minMessages", count: 6 }, + { type: "minCallDuration", seconds: 15 }, + { type: "endedReason", operator: "oneOf", values: ["customer-ended-call"] } + ] +}); +``` + +```bash title="cURL" +curl -X POST https://api.vapi.ai/structured-output \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Call Outcome", + "type": "ai", + "description": "Capture the outcome and any follow-up requested by the customer", + "schema": { + "type": "object", + "properties": { + "outcome": { + "type": "string", + "enum": ["resolved", "callback-requested", "escalated", "no-resolution"] + }, + "followUpNotes": { "type": "string" } + }, + "required": ["outcome"] + }, + "conditions": [ + { "type": "minMessages", "count": 6 }, + { "type": "minCallDuration", "seconds": 15 }, + { "type": "endedReason", "operator": "oneOf", "values": ["customer-ended-call"] } + ] + }' +``` + + +On a call that ends before those thresholds are met — a wrong number, a hang-up, a voicemail — the output is skipped instead of producing an empty or misleading result. + ## Best practices for complex schemas diff --git a/fern/assistants/structured-outputs.mdx b/fern/assistants/structured-outputs.mdx index d93d43cb9..ad7e04f59 100644 --- a/fern/assistants/structured-outputs.mdx +++ b/fern/assistants/structured-outputs.mdx @@ -38,9 +38,9 @@ Structured outputs enable automatic extraction of specific information from voic ```typescript title="TypeScript (Server SDK)" -import { Vapi } from '@vapi-ai/server-sdk'; +import { VapiClient } from '@vapi-ai/server-sdk'; -const vapi = new Vapi({ apiKey: process.env.VAPI_API_KEY }); +const vapi = new VapiClient({ token: process.env.VAPI_API_KEY }); const structuredOutput = await vapi.structuredOutputs.create({ name: "Customer Info", @@ -76,9 +76,10 @@ console.log('Created structured output:', structuredOutput.id); ``` ```python title="Python (Server SDK)" -from vapi_python import Vapi +import os +from vapi import Vapi -vapi = Vapi(api_key=os.environ['VAPI_API_KEY']) +vapi = Vapi(token=os.environ['VAPI_API_KEY']) structured_output = vapi.structured_outputs.create( name="Customer Info", @@ -237,10 +238,10 @@ for output_id, data in outputs.items(): ```javascript title="Webhook Response" // In your webhook handler app.post('/vapi/webhook', (req, res) => { - const { type, call } = req.body; + const { message } = req.body; - if (type === 'call.ended') { - const outputs = call.artifact?.structuredOutputs; + if (message.type === 'end-of-call-report') { + const outputs = message.artifact?.structuredOutputs; if (outputs) { Object.entries(outputs).forEach(([outputId, data]) => { @@ -440,9 +441,100 @@ Use `if/then/else` for conditional requirements: } ``` +## Conditional generation + +By default, every linked structured output runs after each call. Attach **conditions** to a structured output so it only generates when the call meets your criteria — for example, skip extraction on calls that barely started, or only run an output when the call ended a certain way. + + +Conditions gate **whether the output runs at all**. This is different from the [`if/then/else` schema logic](#conditional-logic) above, which shapes the data *within* a single extraction. + + +### How conditions work + +- Add a `conditions` array to a structured output. +- **Every condition must pass** for the output to run (AND semantics). +- When `conditions` is omitted or empty, no user-defined conditions gate the output (runtime defaults still apply). +- On update (`PATCH`), send `conditions: null` to clear a previously saved gate. + +When a condition isn't met, the output is **skipped** rather than failed. Skipped outputs are surfaced in the **assistant preview**, **call logs**, and **sessions**, so you can see which outputs ran and which were gated out. + +### Condition types + +| Type | Fields | Output runs when | +|------|--------|------------------| +| `minMessages` | `count` (integer ≥ 0) | The conversation has at least `count` messages. `count: 0` removes the runtime default minimum. | +| `minCallDuration` | `seconds` (integer ≥ 0) | The call lasted at least `seconds` seconds. | +| `endedReason` | `operator` (`oneOf` or `notOneOf`), `values` (array of strings) | The call's [ended reason](/calls/call-ended-reason) passes the membership test against `values`. `oneOf` runs the output only if the ended reason is in `values`; `notOneOf` runs it only if the ended reason is not in `values`. | + +### Example + +Only extract a call summary when the call had a real conversation (at least 4 messages and 10 seconds) and the customer ended it: + + +```typescript title="TypeScript (Server SDK)" +const structuredOutput = await vapi.structuredOutputs.create({ + name: "Call Summary", + type: "ai", + description: "Summarize the conversation", + schema: { + type: "object", + properties: { + summary: { type: "string" } + } + }, + conditions: [ + { type: "minMessages", count: 4 }, + { type: "minCallDuration", seconds: 10 }, + { type: "endedReason", operator: "oneOf", values: ["customer-ended-call"] } + ] +}); +``` + +```python title="Python (Server SDK)" +structured_output = vapi.structured_outputs.create( + name="Call Summary", + type="ai", + description="Summarize the conversation", + schema={ + "type": "object", + "properties": { + "summary": {"type": "string"} + } + }, + conditions=[ + {"type": "minMessages", "count": 4}, + {"type": "minCallDuration", "seconds": 10}, + {"type": "endedReason", "operator": "oneOf", "values": ["customer-ended-call"]} + ] +) +``` + +```bash title="cURL" +curl -X POST https://api.vapi.ai/structured-output \ + -H "Authorization: Bearer $VAPI_API_KEY" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Call Summary", + "type": "ai", + "description": "Summarize the conversation", + "schema": { + "type": "object", + "properties": { + "summary": { "type": "string" } + } + }, + "conditions": [ + { "type": "minMessages", "count": 4 }, + { "type": "minCallDuration", "seconds": 10 }, + { "type": "endedReason", "operator": "oneOf", "values": ["customer-ended-call"] } + ] + }' +``` + + ## Custom models -Configure which AI model performs the extraction: +By default, structured outputs are extracted with GPT-4.1. Configure the `model` to use a different provider or model, or to supply your own extraction prompts: ```typescript title="TypeScript" @@ -465,7 +557,7 @@ const structuredOutput = await vapi.structuredOutputs.create({ }, model: { provider: "openai", - model: "gpt-4-turbo-preview", + model: "gpt-4.1", temperature: 0.1, messages: [ { @@ -474,7 +566,7 @@ const structuredOutput = await vapi.structuredOutputs.create({ }, { role: "user", - content: "Analyze the sentiment of this conversation:\n{{transcript}}" + content: "Extract {{structuredOutput.name}} using this schema:\n{{structuredOutput.schema}}\n\nAnalyze the sentiment of this conversation:\n{{transcript}}" } ] } @@ -501,7 +593,7 @@ structured_output = vapi.structured_outputs.create( }, model={ "provider": "openai", - "model": "gpt-4-turbo-preview", + "model": "gpt-4.1", "temperature": 0.1, "messages": [ { @@ -510,7 +602,7 @@ structured_output = vapi.structured_outputs.create( }, { "role": "user", - "content": "Analyze the sentiment of this conversation:\n{{transcript}}" + "content": "Extract {{structuredOutput.name}} using this schema:\n{{structuredOutput.schema}}\n\nAnalyze the sentiment of this conversation:\n{{transcript}}" } ] } @@ -523,57 +615,44 @@ structured_output = vapi.structured_outputs.create( Use these variables in custom prompts: - `{{transcript}}` - Full conversation transcript -- `{{messages}}` - Conversation messages array -- `{{callEndedReason}}` - How the call ended +- `{{messages}}` - Conversation messages array (JSON) +- `{{endedReason}}` - How the call ended +- `{{duration}}` - Call duration in seconds +- `{{startedAt}}` - Call start time (ISO 8601) +- `{{endedAt}}` - Call end time (ISO 8601) +- `{{systemPrompt}}` - The assistant's system prompt +- `{{structuredOutput}}` - The full structured output definition - `{{structuredOutput.name}}` - Output name - `{{structuredOutput.description}}` - Output description - `{{structuredOutput.schema}}` - Schema definition + +When you supply custom `messages`, reference either `{{transcript}}` or `{{messages}}` for the conversation, and a variation of `{{structuredOutput}}` so the model has the schema definition. + + ## API reference - +The full set of request fields, response types, and query parameters lives in the API reference. Refer there for all possible values rather than duplicating them here. ### Create structured output - - Display name for the structured output (max 40 characters) - - - - Must be set to "ai" - - - - Description of what data to extract - - - - JSON Schema defining the structure of data to extract - - - - Array of assistant IDs to link this output to - + - - Custom model configuration for extraction - +See [Create structured output](/api-reference/structured-outputs/structured-output-controller-create) for every request field, including `type`, `conditions`, `model`, and `assistantIds`. ### Update structured output - -To update the top level schema type after creation, you must include `?schemaOverride=true` as a query parameter in the URL - + +Updating the top-level schema type after creation requires the `?schemaOverride=true` query parameter. See [Update structured output](/api-reference/structured-outputs/structured-output-controller-update). + ### List structured outputs -Query parameters: -- `page` - Page number (default: 1) -- `limit` - Results per page (default: 20, max: 100) +See [List structured outputs](/api-reference/structured-outputs/structured-output-controller-find-all) for all query parameters, including filtering, sorting, and pagination. ### Delete structured output @@ -713,7 +792,7 @@ Query parameters: ### Performance tips - **Keep schemas focused**: Extract only what you need to minimize processing time -- **Use appropriate models**: GPT-4 for complex schemas, GPT-3.5 for simple ones +- **Use appropriate models**: use a capable model (for example, GPT-4.1) for complex schemas; lighter models can handle simpler ones - **Set low temperature**: Use 0.1 or lower for consistent extraction - **Monitor success rates**: Track extraction failures and adjust schemas accordingly @@ -759,14 +838,6 @@ if (data.result === null) { - Make fields optional if they might not be mentioned - Verify data types match expected values -## HIPAA compliance - - -**Important for HIPAA-enabled organizations:** When HIPAA mode is enabled, structured outputs are generated normally but **not stored** by default, to protect PHI. You can still receive them via webhooks during the call. - -To store a specific structured output that doesn't contain PHI, see [HIPAA Compliance & Storage Settings](/assistants/structured-outputs-quickstart#hipaa-compliance-storage-settings) for the full walkthrough and safe/unsafe use cases. - - ## Limitations diff --git a/fern/docs.yml b/fern/docs.yml index 0216e7724..c1e620ac1 100644 --- a/fern/docs.yml +++ b/fern/docs.yml @@ -563,6 +563,7 @@ navigation: icon: fa-light fa-chart-line - section: Structured outputs icon: fa-light fa-database + path: assistants/structured-outputs.mdx contents: - page: Quickstart path: assistants/structured-outputs-quickstart.mdx