Skip to main content
Both Structured Output schema fields and Variables share a single type system. A VariableDefinition extends the same SchemaFieldDefinition base that structured output fields use — that’s why a type string written for a schema field and a type string written for a variable follow identical rules, are parsed by the same type parser, and are coerced by the same logic. This page is the single reference for that notation.
Type strings are parsed with Python’s ast module (no eval), then handed to Pydantic for runtime validation. Only the forms documented below are recognized — anything else raises a parse error.

Scalars

type
Text data.
type
Integer numbers.
type
Decimal numbers.
type
Boolean values (true/false).
type
Accepts any value, no validation. Both Any and any are accepted spellings.
Enum types are not supported. Use str with a clear description, and steer allowed values through the system prompt.

Collections

type
A list of items of type T, e.g. list[str], list[int].
type
A mapping from keys of type K to values of type V, e.g. dict[str, int].
Bare list and dict (with no type arguments) are also valid. Collections nest to arbitrary depth — the parser recurses through subscripts, so dict[str, list[int]], list[dict[str, float]], and deeper combinations all parse correctly:

Nullability

type
Optional/nullable value of type T. None | T (reversed order) works identically.
type
Equivalent alternate spelling for T | None.
A third spelling is also accepted because the parser maps Union directly: Union[T, None] parses to the same nullable type as T | None and Optional[T].
Capitalization matters. Optional and Union must be capitalized exactly as shown — optional[str] or union[str, None] are rejected as unknown types. Likewise, the generic container keywords are lowercase: list and dict, not List or Dict. Typing-module-style capitalized generics (List[str], Dict[str, int]) are not supported — the parser only knows the lowercase builtins.
General, non-nullable unions are also accepted (e.g. str | int), though in practice nullable forms (T | None) are by far the most common use.

Coercion rules

Coercion only applies to variables — specifically when a value is assigned to a variable via variable_assignments, a runtime input, or a checkpoint-restored value. Structured output fields are produced by the model directly and validated by BAML; they are not run through this coercion step.
If coercion raises (only possible for int/float/bool conversions), the system logs:
and keeps the original, uncoerced value rather than failing the run.

Nullability interaction: variables vs. structured output

This is the one interaction worth understanding before you rely on null values flowing between agents.
1

A null field does not overwrite a non-nullable variable

When a variable_assignments entry reads a structured output field that the model set to null, and the variable’s declared type is not nullable (no None/Optional[...] in its type), the assignment is skipped entirely — the variable keeps whatever value it already had. The system logs:
This exists because a None value assigned into a non-nullable variable would otherwise be rejected the next time variables are assembled (e.g. on the following execution, once it’s been through a checkpoint), failing the whole session.
2

The structured output layer tolerates null on any field, regardless of notation

Internally, every structured output field is marked optional at the BAML schema-building layer, even if you declared it as a plain non-nullable type like str. This is deliberate: without it, a model returning null for a field typed str would hard-fail the generation call (Expected string, got null) for the entire agent turn. So declaring a structured output field as T | None is not required merely to keep BAML from rejecting the model’s null — the runtime already tolerates that for every field.
3

Declare both sides nullable anyway, if null is meaningful

To actually store and use a null result, declare the variable’s type nullable (float | None, not float) — that’s what determines whether the assignment in Step 1 is skipped or allowed through. Declaring the structured output field as T | None too keeps the schema’s intent explicit and matches the variable it feeds, even though the BAML layer would tolerate null either way.

Common errors

The type string isn’t valid Python expression syntax at all. Exact message:
The base name isn’t one of the recognized keywords (str, int, float, bool, list, dict, any/Any, Optional, Union). This is the error you’ll hit for things like List[str], enum[...], or a typo. Exact message:
The string parsed into a type, but Pydantic couldn’t build a validator for it. Exact message: Invalid type '{type_str}': {error}.
Raised at config-validation time for a variable whose type doesn’t parse. Exact message:
The hint text in this message is misleading — str[]/int[] are not valid notation. Use list[str]/list[int] instead.
Raised when a variable’s current value doesn’t match its declared type after coercion (for example, a non-nullable variable ended up holding None, or a static assignment can’t be coerced into the declared type).

Used by

Structured Output

Define typed fields the agent must extract.

Variables

Define typed values threaded through prompts and assignments.