Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 79 additions & 5 deletions docs/docs/reference/resources/variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,57 @@ Each attribute includes:

| Field | Description |
|---|---|
| `name` | Attribute identifier, ideally in snake_case |
| `values` | Map from variant name to string value |
| `name` | Attribute identifier. Must be a valid Python identifier, since it is read in code as `conv.variant.<name>` |
| `kind` | Optional. The attribute's type: `string` (default), `number`, `boolean`, `enum`, or `object` |
| `config` | Optional. Type-specific configuration. Only `enum` takes one: `config.values` lists the allowed values |
| `values` | Map from variant name to value |

Every attribute must provide a value for every defined variant, even if that value is an empty string.
Every attribute must provide a value for every defined variant, even if that value is blank.

## Attribute types

An attribute with no `kind` is a string attribute, which is how every attribute behaved before types existed — existing files need no change.

Declaring a `kind` does two things: values are checked against it when you push, so a typo is caught at authoring time rather than on a live call, and the agent receives the value in its real type. A `number` attribute arrives as `3`, not `"3"`, and a `boolean` as `true`, not `"True"`.

| `kind` | Written in `values` as | Example |
|---|---|---|
| `string` | Text | `London Office` |
| `number` | A number | `3`, `2.5` |
| `boolean` | `true` or `false` | `true` |
| `enum` | One of `config.values` | `premium` |
| `object` | A map or list | `{currency: USD}` |

Values are written in their declared type. A quoted value is still read correctly — `max_retries: 3` and `max_retries: "3"` are the same attribute, and neither shows as a change against the other — but `poly pull` writes the native form.

A blank value is allowed for every kind and means the attribute is not set for that variant yet.

!!! info "`config` is per-kind"

Only `enum` takes a `config` today. It is nested rather than a top-level `enum_values` so a future kind can carry its own settings without every other kind growing a field it ignores — and because `values` at the top level is already the per-variant value map.

Changing an attribute's `kind` does not convert its existing values. Update the values in the same change, or the push is rejected with the variants whose values no longer fit.

### Blank values at runtime

A blank value behaves differently depending on the kind, and the difference only shows up on a live call:

- A blank **`string`** attribute substitutes an empty string, so `{{attr:name}}` resolves to nothing.
- A blank attribute of **any other kind** is left out of the deployed agent entirely, so `{{attr:name}}` stays unresolved and is reported as a configuration gap.

If a prompt depends on a typed attribute, give it a value for every variant rather than leaving one blank.

### Types that Agent Studio shows but does not store

Agent Studio's type picker offers three types that have no equivalent here, because the platform stores them as one of the five kinds above:

| Agent Studio type | Stored as | Value shape in `values` |
|---|---|---|
| Date & time | `string` | `2026-09-03 14:30` |
| Opening hours | `string` | `Mon: 09:00-17:00; Tue: 09:00-17:00; Wed: Closed; Thu: ...` — all seven days, `Mon` to `Sun`, separated by `; ` |
| Voice | `enum` | A voice ID, from `config.values` |

They are editor choices, not stored types. Agent Studio re-detects "Date & time" and "Opening hours" by matching the value's exact shape, so editing one of those values here into a different shape silently drops the attribute back to a plain text editor in the UI. The value itself still works. "Voice" is indistinguishable from any other `enum` once saved.

## Example

Expand Down Expand Up @@ -86,6 +133,29 @@ attributes:
london: |-
This call may be recorded in accordance with UK regulations.
tokyo: ""

- name: max_retries
kind: number
values:
new_york: 3
london: 5
tokyo: 3

- name: serves_alcohol
kind: boolean
values:
new_york: true
london: true
tokyo: false

- name: tier
kind: enum
config:
values: [basic, premium]
Comment on lines +153 to +154

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would there be anything else in this field potentially in the future? Just wondering if values should be top level?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No plans to extend it for this feature. enum is the only kind with metadata today. It's nested so a future kind can carry its own settings without every other kind growing a field it ignores.

values:
new_york: premium
london: premium
tokyo: basic
~~~

## Why variants are useful
Expand Down Expand Up @@ -163,7 +233,7 @@ Common uses include:
| Branding | greeting name, company name |
| Contact | phone numbers, addresses, office hours |
| IDs | location ID, region code |
| Feature flags | `"True"` / `"False"` strings, checked in Python |
| Feature flags | `kind: boolean` (`true` / `false`) |
| URLs | portal links, payment links |
| Environment | timezone, `is_live` |

Expand All @@ -176,12 +246,16 @@ Common uses include:

- Exactly one variant must have `is_default: true` — validation fails if zero or more than one variant is marked default.
- Every variant must have a value in every attribute's `values` map — a missing variant fails validation.
- Every attribute name must be a valid Python identifier — a letter or underscore followed by letters, digits or underscores — and not a Python reserved word. `customer-name` and `class` both fail.
- Every value must match its attribute's declared `kind` — a value of the wrong type fails validation, naming the variant it came from.
- An `enum` attribute must declare a non-empty `config.values` list, with no duplicates.

## Best practices

- keep variant names stable over time
- set exactly one default variant
- provide a value or `""` for every variant in every attribute
- provide a value, or a blank one, for every variant in every attribute
- declare a `kind` whenever the value is not text, so mistakes surface at push time
- prefer `{{attr:...}}` over hard-coded strings when values vary by location or environment
- use multi-line YAML for disclaimers, instructions, or longer text values

Expand Down
71 changes: 67 additions & 4 deletions src/poly/docs/variants.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,28 @@ The file has two top-level keys:
- **is_default** (optional): Exactly one variant must have `is_default: true`. Used when no variant is resolved at runtime.

### `attributes` - List of attributes
- **name**: Attribute identifier (snake_case recommended), e.g. `greeting_name`, `support_phone_number`.
- **values**: Map from **variant name** to string value. Must have one entry per variant. Values can be `""`, a single line, or multi-line (`|-`).
- **name**: Attribute identifier. Must be a valid Python identifier (a letter or underscore followed by letters, digits or underscores) and not a Python reserved word, since it is read in code as `conv.variant.<name>`. e.g. `greeting_name`, `support_phone_number`.
- **kind** (optional): The attribute's type — one of `string`, `number`, `boolean`, `enum`, `object`. Defaults to `string` when omitted.
- **config** (optional): Type-specific configuration. Only `enum` takes one: `config.values` lists the allowed values.
- **values**: Map from **variant name** to value. Must have one entry per variant. Leave a value blank for any kind to mean "not set yet".

Typed values reach the agent as their real type: a `number` attribute arrives as `3`, not `"3"`. A `string` attribute behaves exactly as it always has, which is why untyped attributes need no change.

Values are written in their declared type. A quoted value is still read correctly — `max_retries: 3` and `max_retries: "3"` are the same attribute — but `poly pull` writes the native form. Only `enum` takes a `config`; it is nested rather than a top-level `enum_values` so a future kind can carry its own settings, and because `values` at the top level is already the per-variant value map.

### Blank values at runtime
A blank `string` attribute substitutes an empty string, so `{{attr:name}}` resolves to nothing. A blank attribute of any other kind is left out of the deployed agent entirely, so `{{attr:name}}` stays unresolved and is reported as a configuration gap. Give typed attributes a value for every variant if a prompt depends on them.

### Types Agent Studio shows but does not store
Agent Studio's type picker offers three types with no equivalent here, because the platform stores them as one of the five kinds:

| Agent Studio type | Stored as | Value shape |
|---|---|---|
| Date & time | `string` | `2026-09-03 14:30` |
| Opening hours | `string` | `Mon: 09:00-17:00; Tue: Closed; ...` — all seven days, `Mon` to `Sun`, separated by `; ` |
| Voice | `enum` | A voice ID, from `config.values` |

Agent Studio re-detects "Date & time" and "Opening hours" by matching the value's exact shape, so editing one into a different shape drops the attribute back to a plain text editor in the UI. The value still works. "Voice" is indistinguishable from any other `enum` once saved.

## Example
```yaml
Expand Down Expand Up @@ -54,6 +74,46 @@ attributes:
tokyo: ""
```

### Typed attributes
```yaml
attributes:
- name: max_retries
kind: number
values:
new_york: 3
london: 5
tokyo: 3

- name: serves_alcohol
kind: boolean
values:
new_york: true
london: true
tokyo: false

- name: tier
kind: enum
config:
values: [basic, premium]
values:
new_york: premium
london: premium
tokyo: basic

- name: menu_config
kind: object
values:
new_york:
categories: [pizza, pasta]
currency: USD
london:
categories: [pizza]
currency: GBP
tokyo:
categories: [pasta]
currency: JPY
```

Ensure the YAML is formatted correctly, for example variant names with special characters (e.g. `&`, parentheses) must be quoted.

## Usage
Expand Down Expand Up @@ -84,13 +144,16 @@ Use the same attribute names as defined in `variant_attributes.yaml`.
- **Branding**: greeting name, company name
- **Contact**: phone numbers, addresses, office hours
- **IDs**: location_id, region code
- **Feature flags**: `"True"` / `"False"` strings (check in Python)
- **Feature flags**: `kind: boolean` (`true` / `false`)
- **URLs**: portal link, payment link
- **Environment**: timezone, is_live

## Best practices
- Keep variant names stable; quote them when they contain special characters.
- Set exactly **one** `is_default` variant.
- Provide a value (or `""`) for every variant in each attribute's `values` map. Validation will fail if a variant is missing.
- Provide a value (or a blank one) for every variant in each attribute's `values` map. Validation will fail if a variant is missing.
- Name attributes as Python identifiers — `customer_name`, not `customer-name`. The platform rejects anything else.
- Declare a `kind` when the value is not text. A `boolean` attribute is checked at push time and reaches the agent as a real boolean, where a `"True"` string is neither.
- Changing an attribute's `kind` does not convert its existing values — update them in the same change, or the push is rejected.
- Prefer `{{attr:...}}` over hard-coded strings for anything that varies by location/environment.
- Use `|-` for multi-line values (disclaimers, hours, instructions).
2 changes: 1 addition & 1 deletion src/poly/resources/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,4 +83,4 @@
from poly.resources.transcript_correction import RegularExpressionRule, TranscriptCorrection
from poly.resources.translations import Translation
from poly.resources.variable import Variable
from poly.resources.variant_attributes import Variant, VariantAttribute
from poly.resources.variant_attributes import AttributeKind, Variant, VariantAttribute
Loading
Loading