Overview
The transform system bridges rigid API specifications and LLM-friendly interfaces. You define schemas that match the API documentation exactly, and the LLM gets semantic, human-readable types.Core principle: LLMs should interact with data like humans, not like computers dealing with wire formats.Transforms compose with the mode system, which controls which fields the LLM sees; transforms control what type those fields have.
The pipeline
- APIs speak in rigid formats — base64, RFC822, protobuf JSON.
- You annotate schema fields with
Formatmarkers matching the API docs. - Charter generates the LLM-facing view, where those fields carry semantic types.
- The LLM provides natural input (
to/subject/body, not raw MIME). - The transform engine converts semantic input to the exact wire format.
- The API receives the payload it expects. No glue code.
Components
1. Transform registry
One place where every transformation is declared, viaTransformRegistry:
rfc822_transform.py
- Declarative registration
- Semantic type recorded alongside the function, so schema generation knows what to show the LLM
- LLM-friendly description attached to the field
2. Format markers
Annotate the fields that need conversion withFormat:
format_marker.py
3. LLM schema generation
send_email_request.py
- detects
Formatmarkers, - replaces the wire type with the registered semantic type,
- swaps in the transform’s LLM-facing description,
- preserves the
Path/Query/Body/Case/Modemarkers.
Built-in transforms
bytes and base64url encode identically. The separate name exists for
fidelity with Google API specs that annotate a field as bytes.
Nesting
Transforms apply at any depth. The runtime walks nested models and lists, applying everyFormat marker it finds, then routes the transformed value to the
HTTP location its Path/Query/Body marker names.
Registering your own
Callregister_transform with the
semantic type, the conversion, and the description the LLM will see:
custom_transform.py
Annotated[str, Query(), Format("latlon_string")] shows the LLM a
Coordinates object and sends "48.8584,2.2945".
The last argument replaces the description of every field the transform is
applied to. Give one when the field’s own description is about the wire value
the transform hides, as a vendor’s “a base64url encoded string” is: a model told
that encodes the value itself, and the API receives it encoded twice. Leave it
out and each field keeps its own description, which is what a transform reused
across fields wants:
postgrest_filter.py
ai-safety; the request carries
topic=eq.ai-safety.
Failure
An unknown transform name, a payload that will not validate as the semantic type, or a transform function that raises all produce aTransformError naming the transform and the
field. Transform failures are never swallowed — a silently untransformed value
reaches the API as a confusing 400.
Related
TransformSpec,TransformRegistry,register_transform— the registry API in fullFormat— the marker that names a transform- Semantic types —
EmailContent,CalendarEvent,DocumentContent,FileContent TransformError— what a failed conversion raises