From e520c9a9f980ac7a96ff2bff96f5900767c45a8a Mon Sep 17 00:00:00 2001 From: Him188 Date: Thu, 8 Oct 2026 16:11:29 +0900 Subject: [PATCH 1/2] docs: dynamic variable default values, unresolved placeholders render as empty text Co-Authored-By: Claude Fable 5.1 --- agents/build/configuration.mdx | 1 + agents/build/dynamic-variables.mdx | 28 +++++++++++++++++++++++++++- agents/monitor/webhooks.mdx | 10 +++++----- agents/telephony/inbound-calls.mdx | 2 +- 4 files changed, 34 insertions(+), 7 deletions(-) diff --git a/agents/build/configuration.mdx b/agents/build/configuration.mdx index ba2f1cd2..c86558f7 100644 --- a/agents/build/configuration.mdx +++ b/agents/build/configuration.mdx @@ -156,6 +156,7 @@ The response includes the draft's new `config_hash`. Values outside the document | `knowledge_base` | Attached knowledge sources | [Knowledge base](/agents/build/knowledge-base) | | `analysis` | Post-call summary, data fields, and success criteria | [Post-call analysis](/agents/monitor/post-call-analysis) | | `webhooks` | Post-call webhook delivery and the inbound call webhook | [Webhooks](/agents/monitor/webhooks) | +| `variables` | Default values for the `{{placeholders}}` in the configured text and what a placeholder without a value does | [Dynamic variables](/agents/build/dynamic-variables#default-values) | | `llm` | Conversation model settings, including a custom LLM endpoint | [Custom LLM](/agents/build/custom-llm) | ## Going further diff --git a/agents/build/dynamic-variables.mdx b/agents/build/dynamic-variables.mdx index 31abeb33..2cf6c826 100644 --- a/agents/build/dynamic-variables.mdx +++ b/agents/build/dynamic-variables.mdx @@ -61,8 +61,34 @@ const session = await AgentSession.start({ ``` +### Default values + +A placeholder is resolved in this order: the value supplied for the session, then the default configured on the agent. What happens when neither exists is set by `on_missing`: + +| `on_missing` | A placeholder with no value | +| ------------------ | ------------------------------------------------------------------------------------------- | +| `keep` (default) | Stays in the text as written, visible to the model and spoken on a fixed first message | +| `empty` | Renders as empty text | +| `reject` | Session creation fails with `422 Missing dynamic variables: ...` and an inbound call is not connected | + +Set both in the **Dynamic variables** sheet of the agent's Configuration page (the **Default values** link under the system prompt), or through the `variables` section of the config API. The defaults map replaces as a whole on every update, and names follow the same rules as `dynamic_variables`: + +```bash +curl --request PATCH https://api.fish.audio/v1/agent/agents/YOUR_AGENT_ID/config \ + --header "Authorization: Bearer $FISH_API_KEY" \ + --header "Content-Type: application/json" \ + --data '{ + "variables": { + "defaults": { "customer_name": "there", "plan": "Free" }, + "on_missing": "empty" + } + }' +``` + +With these defaults, a session that sends only `{"plan": "Pro"}` renders the first message `Hi {{customer_name}}, welcome to the {{plan}} plan.` as `Hi there, welcome to the Pro plan.` + -Placeholders with no matching variable are **not** removed: the literal `{{variable_name}}` text stays in the prompt, visible to the model. Make sure every placeholder in your configuration has a value at session creation. +By default a placeholder with no session value and no default stays as written, so `Hi {{customer_name}}, welcome.` is spoken braces and all on a fixed first message. For text that is spoken word for word, give every placeholder that can go unfilled a default that reads well on its own, or set `on_missing` to `empty` (`Hi , welcome.`) or `reject`. A fixed first message that renders to nothing but whitespace is dropped and the agent opens with its generic greeting instead. Variables fill placeholders in the configured text. To replace whole configuration fields for a session (the prompt itself, the opener, voice, language), use [overrides](/agents/deploy/authenticated-sessions#overrides) on the same request; `{{placeholders}}` render inside overridden text too. diff --git a/agents/monitor/webhooks.mdx b/agents/monitor/webhooks.mdx index c98b9cbd..0e143ea4 100644 --- a/agents/monitor/webhooks.mdx +++ b/agents/monitor/webhooks.mdx @@ -357,7 +357,7 @@ Respond with a `2xx` status and a JSON object whose `dynamic_variables` follow t } ``` -An entry that breaks a rule is dropped on its own; the call still connects with the rest. Other fields in the response are ignored, so the contract can grow without breaking deployed endpoints. Placeholders that receive no value stay in the text as written, as described under [dynamic variables](/agents/build/dynamic-variables#custom-variables). +An entry that breaks a rule is dropped on its own; the call still connects with the rest. Other fields in the response are ignored, so the contract can grow without breaking deployed endpoints. A placeholder that receives no value takes the agent's configured [default value](/agents/build/dynamic-variables#default-values), and follows the agent's `on_missing` setting when there is none. ```text System prompt You are the support line for Acme. The caller is {{customer_name}} on the {{plan}} plan @@ -366,14 +366,14 @@ with {{open_orders}} open orders. Greet them by name. ### Failure behavior -The endpoint can personalize a call but never block one. The caller is waiting while the request runs, so keep the handler fast and the timeout short. +The endpoint can personalize a call but never block one. The caller is waiting while the request runs, so keep the handler fast and the timeout short. Whenever the call connects without your variables, every placeholder falls back to its configured [default value](/agents/build/dynamic-variables#default-values), so give each one a default that reads well on its own. | Outcome | Effect | | ---------------------------------------- | ------------------------------------------------------------- | | Response within the timeout | Variables are rendered into the prompt, first message, and webhook tool requests | -| Timeout | The call connects without variables; the request is not retried | -| Connection error or non-`2xx` response | One immediate retry, then the call connects without variables | -| Body is not a JSON object | The call connects without variables | +| Timeout | The call connects on the defaults; the request is not retried | +| Connection error or non-`2xx` response | One immediate retry, then the call connects on the defaults | +| Body is not a JSON object | The call connects on the defaults | ## Auto-ticket unresolved calls diff --git a/agents/telephony/inbound-calls.mdx b/agents/telephony/inbound-calls.mdx index 5d49de42..00c62eb9 100644 --- a/agents/telephony/inbound-calls.mdx +++ b/agents/telephony/inbound-calls.mdx @@ -119,7 +119,7 @@ Inbound calls have no session-creation request to carry [dynamic variables](/age Hi {{customer_name}}, thanks for calling Acme. Are you calling about order {{last_order}}? ``` -Set it on the agent's **Webhooks** page in the console (**Inbound call webhook**) or through `webhooks.conversation_init` in the config API, then publish. A slow or failing endpoint never blocks the call: after the configured timeout (up to 8 seconds, default 5) the call connects without variables. +Set it on the agent's **Webhooks** page in the console (**Inbound call webhook**) or through `webhooks.conversation_init` in the config API, then publish. A slow or failing endpoint never blocks the call: after the configured timeout (up to 8 seconds, default 5) the call connects and every placeholder takes its configured [default value](/agents/build/dynamic-variables#default-values) (`Hi there, thanks for calling Acme.`). Set a default for each placeholder the first message depends on, or set the agent's `on_missing` to `reject` if the call must not connect without them. ## Phone sessions behave like any session From b26770582e4dc7a4331ab552245f5115f810b21d Mon Sep 17 00:00:00 2001 From: Him188 Date: Sat, 10 Oct 2026 02:26:54 +0900 Subject: [PATCH 2/2] chore: update OpenAPI schema Co-Authored-By: Claude Fable 5.1 --- api-reference/openapi.json | 122 +++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/api-reference/openapi.json b/api-reference/openapi.json index d799de24..42a760d8 100644 --- a/api-reference/openapi.json +++ b/api-reference/openapi.json @@ -3724,6 +3724,9 @@ "llm": { "$ref": "#/components/schemas/AgentLLMConfigRedacted" }, + "variables": { + "$ref": "#/components/schemas/AgentVariablesConfig" + }, "updated_at": { "format": "date-time", "title": "Updated At", @@ -3944,6 +3947,9 @@ "llm": { "$ref": "#/components/schemas/AgentLLMConfigRedacted" }, + "variables": { + "$ref": "#/components/schemas/AgentVariablesConfig" + }, "updated_at": { "format": "date-time", "title": "Updated At", @@ -19492,6 +19498,12 @@ "title": "Warm Confirm Timeout Seconds", "type": "integer" }, + "warm_human_detection": { + "default": false, + "description": "Applies to warm transfers whose warm_connect is `confirm` or `briefing`, and is ignored for cold transfers and `direct`. When true, once the destination answers, the agent stays silent until the other side speaks, then classifies the pickup. A person is briefed as usual. Voicemail or an IVR (phone menu) ends the transfer as failed. A hold queue or silence keeps the agent waiting until `warm_confirm_timeout_seconds` ends the attempt as unreachable. A failed transfer follows `on_failure`. Leave it off for destinations whose people are only reachable through a phone menu, because the menu stops the transfer.", + "title": "Warm Human Detection", + "type": "boolean" + }, "on_failure": { "$ref": "#/components/schemas/AgentTransferOnFailurePatch" }, @@ -19914,6 +19926,18 @@ } ], "default": null + }, + "variables": { + "anyOf": [ + { + "$ref": "#/components/schemas/PublicAgentVariablesPatch" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Default values for the {{placeholders}} in the configured text, used when the session supplies none (the map replaces as a whole), and on_missing for a placeholder with neither: keep leaves it as written (default), empty renders it as empty text, reject refuses the session." } }, "title": "PublicAgentConfigPatchPayload", @@ -20434,6 +20458,58 @@ "title": "PublicAgentToolsPatch", "type": "object" }, + "PublicAgentVariablesPatch": { + "additionalProperties": false, + "properties": { + "defaults": { + "anyOf": [ + { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "type": "object" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Defaults" + }, + "on_missing": { + "anyOf": [ + { + "enum": [ + "keep", + "empty", + "reject" + ], + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "title": "On Missing" + } + }, + "title": "PublicAgentVariablesPatch", + "type": "object" + }, "PublicAgentVoicePatch": { "additionalProperties": false, "properties": { @@ -21414,6 +21490,12 @@ "title": "Warm Confirm Timeout Seconds", "type": "integer" }, + "warm_human_detection": { + "default": false, + "description": "Applies to warm transfers whose warm_connect is `confirm` or `briefing`, and is ignored for cold transfers and `direct`. When true, once the destination answers, the agent stays silent until the other side speaks, then classifies the pickup. A person is briefed as usual. Voicemail or an IVR (phone menu) ends the transfer as failed. A hold queue or silence keeps the agent waiting until `warm_confirm_timeout_seconds` ends the attempt as unreachable. A failed transfer follows `on_failure`. Leave it off for destinations whose people are only reachable through a phone menu, because the menu stops the transfer.", + "title": "Warm Human Detection", + "type": "boolean" + }, "on_failure": { "$ref": "#/components/schemas/AgentTransferOnFailure" }, @@ -21456,6 +21538,43 @@ "title": "AgentTransferOnFailure", "type": "object" }, + "AgentVariablesConfig": { + "description": "Defaults for `{{name}}` placeholders and what a placeholder without a value does.", + "properties": { + "defaults": { + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "integer" + }, + { + "type": "number" + }, + { + "type": "boolean" + } + ] + }, + "title": "Defaults", + "type": "object" + }, + "on_missing": { + "default": "keep", + "enum": [ + "keep", + "empty", + "reject" + ], + "title": "On Missing", + "type": "string" + } + }, + "title": "AgentVariablesConfig", + "type": "object" + }, "AgentVoiceConfigView": { "description": "Read shape of the voice section. The ASR fields that moved to the asr\nsection stay mirrored here, deprecated, so clients written before the split\nkeep reading them.", "properties": { @@ -21741,6 +21860,9 @@ "llm": { "$ref": "#/components/schemas/AgentLLMConfigRedacted" }, + "variables": { + "$ref": "#/components/schemas/AgentVariablesConfig" + }, "updated_at": { "format": "date-time", "title": "Updated At",