Skip to main content

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

Stated plainly:
  1. APIs speak in rigid formats — base64, RFC822, protobuf JSON.
  2. You annotate schema fields with Format markers matching the API docs.
  3. Charter generates the LLM-facing view, where those fields carry semantic types.
  4. The LLM provides natural input (to/subject/body, not raw MIME).
  5. The transform engine converts semantic input to the exact wire format.
  6. The API receives the payload it expects. No glue code.

Components

1. Transform registry

One place where every transformation is declared, via TransformRegistry:
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 with Format:
format_marker.py
The schema still says exactly what the API says. The marker records where the bridge goes.

3. LLM schema generation

send_email_request.py
The generator:
  1. detects Format markers,
  2. replaces the wire type with the registered semantic type,
  3. swaps in the transform’s LLM-facing description,
  4. preserves the Path/Query/Body/Case/Mode markers.

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 every Format marker it finds, then routes the transformed value to the HTTP location its Path/Query/Body marker names.

Registering your own

Call register_transform with the semantic type, the conversion, and the description the LLM will see:
custom_transform.py
Then Annotated[str, Query(), Format("latlon_string")] shows the LLM a Coordinates object and sends "48.8584,2.2945".

Failure

An unknown transform name, a payload that will not validate as the semantic type, or a transform function that raises all produce a TransformError naming the transform and the field. Transform failures are never swallowed — a silently untransformed value reaches the API as a confusing 400.