Schemas

An autoform schema is a pytree whose leaves are schema specs. It is instance-first: the schema is a value shape, not a separate output class definition. lm_schema_call returns the same pytree structure, with each schema leaf replaced by a parsed value.

answer_schema = {"text": af.Str(min=1), "score": af.Float(min=0, max=1)}

Leaf Types

Python

Args

Meaning

Str

min=None, max=None, pattern=None

A string, optionally constrained by length or regex.

Int

min=None, max=None

An integer, optionally range constrained.

Float

min=None, max=None

A number, optionally range constrained.

Bool

none

A boolean.

Enum

*values

One of a non-empty set of JSON scalar values of the same type.

Descriptions

Use Doc with the @ operator to attach descriptions:

schema = {
    "kind": af.Enum("summary", "definition") @ af.Doc("Kind."),
    "text": af.Str() @ af.Doc("Text."),
}

The descriptions become JSON Schema descriptions in the provider request.

Pytree Shapes

A schema can have any pytree shape that autoform can walk. It does not need a special schema class. Dictionaries, tuples, lists, and registered dataclasses all work.

schema = {"route": (af.Enum("search", "done"), af.Str()), "answer": af.Str()}

lm_schema_call returns the same shape with schema leaves replaced by parsed values. The example above returns a dictionary whose "route" value is a tuple and whose "answer" value is a string.

Fixed-size repeated fields are just list-shaped schemas:

score_schema = [af.Float(min=0, max=1)] * 4
schema = {"scores": score_schema}

This is a fixed-size schema. The parsed result has the same list shape:

result = af.lm_schema_call(messages, model="gpt-5.5", schema=schema)
assert isinstance(result["scores"], list)
assert len(result["scores"]) == 4

For variable-length output, choose a bounded representation in the schema, such as a fixed number of slots plus a count or status field.

Define a Custom Pytree

Schema trees can use any registered custom pytree. For dataclasses, use Optree’s dataclass integration:

import optree
import autoform as af


@optree.dataclasses.dataclass(namespace=af.PYTREE_NAMESPACE)
class Decision:
    tool: str
    answer: str


decision_schema = Decision(tool=af.Enum("search", "done"), answer=af.Str())

The schema is the instance decision_schema, not the class Decision.

Transform Schema Trees

Because schemas are pytrees, project code can build one base schema and derive call-specific variants with pytree utilities. tree_map changes the schema value before tracing or execution; it is not post-processing model output. Use the autoform namespace when mapping over registered dataclasses.

import optree
import autoform as af


base_schema = {"answer": af.Str(), "confidence": af.Float(min=0, max=1)}

extract_schema = optree.tree_map(
    lambda leaf: leaf @ af.Doc("Extract only facts explicitly present in the input."),
    base_schema,
    namespace=af.PYTREE_NAMESPACE,
)

critique_schema = optree.tree_map(
    lambda leaf: leaf @ af.Doc("Critique the draft and report uncertainty conservatively."),
    base_schema,
    namespace=af.PYTREE_NAMESPACE,
)

Both calls return the same shape, so downstream code still reads result["answer"] and result["confidence"]. Only the schema guidance changes.

The same pattern works for dataclass-shaped schemas:

plain_decision_schema = Decision(tool=af.Enum("search", "done"), answer=af.Str())

documented_decision_schema = optree.tree_map(
    lambda leaf: leaf @ af.Doc("Decision field."),
    plain_decision_schema,
    namespace=af.PYTREE_NAMESPACE,
)

This is useful when several calls share the same shape but differ by descriptions, ranges, or other schema leaf metadata.

The same shape-preserving rule is what makes schemas compose with autoform transforms:

  • batch returns a batched version of the schema-shaped output.

  • pullback accepts feedback with the same schema shape.

  • tree_map can derive schema variants before the IR is traced or executed.

Schema Calls

Pass the schema value to lm_schema_call. The result has the same pytree shape, with schema leaves replaced by parsed provider output.[1]

import autoform as af


schema = {"text": af.Str(), "score": af.Float(min=0, max=1)}

messages = [dict(role="user", content="Explain recursion.")]
result = af.lm_schema_call(messages, model="gpt-5.5", schema=schema)

print(result["text"], result["score"])

For provider routing, retries, aliases, or fallback chains, use lm_client. See Configure LiteLLM Routing.

Schema Pullback

When a schema call is used inside pullback, feedback is still text. The output cotangent should match the schema shape, with text feedback at leaves. For example, feedback might be {"text": "too terse", "score": "overconfident"}.

If the provider returns malformed structured output, lm_schema_call raises while parsing the response.