ExAgent.Schema (ExAgent v0.4.1)

Copy Markdown View Source

Turns an Ecto embedded schema into a JSON Schema, and provider JSON back into a struct.

There is no ExAgent-specific schema language. You describe the shape you want with an ordinary Ecto.Schema and pass the module:

defmodule Invoice do
  use Ecto.Schema

  @primary_key false
  embedded_schema do
    field :total, :float
    field :currency, Ecto.Enum, values: [:EUR, :USD, :GBP]
    embeds_many :lines, Invoice.Line
  end
end

{:ok, response} = ExAgent.chat(agent, "Extract the invoice", schema: Invoice)
response.structured   #=> %Invoice{total: 128.4, currency: :EUR, lines: [...]}

Ecto is an optional dependency of ExAgent, needed only for this.

The rules, and where they come from

Each was verified against a live API rather than inferred:

  • Every field is required. OpenAI's strict mode rejects a schema whose required list omits any property, and Ecto has no field-level required anyway, so the two agree. Optional fields cannot be expressed.
  • A root list is wrapped. OpenAI refuses a schema whose root is type: "array", so {:list, Invoice} becomes an object with a single "items" property, unwrapped again by cast/2. Callers never see it.
  • additionalProperties: false is required by OpenAI and rejected by Gemini, hence :dialect.
  • Primary keys are excluded. embedded_schema carries a binary_id primary key by default, and a primary key is identity the application assigns, not a fact a model extracts. Asking for one invites a plausible hallucinated id.

Summary

Functions

Casts decoded provider JSON into the schema's struct.

Builds a JSON Schema from an Ecto embedded schema.

Types

dialect()

@type dialect() :: :openai | :gemini

schema()

@type schema() :: module() | {:list, module()}

Functions

cast(module, params)

@spec cast(schema(), term()) ::
  {:ok, struct() | [struct()]} | {:error, ExAgent.Error.t()}

Casts decoded provider JSON into the schema's struct.

Returns {:error, %ExAgent.Error{type: :invalid_response}} when the JSON does not fit: a wrong type, a value outside an Ecto.Enum, or a missing field. A missing field is an error rather than a nil because an endpoint that ignored the schema would otherwise hand back a half-built struct with no signal.

to_json_schema(schema, opts \\ [])

@spec to_json_schema(
  schema(),
  keyword()
) :: {:ok, map()} | {:error, ExAgent.Error.t()}

Builds a JSON Schema from an Ecto embedded schema.

Options

  • :description - becomes the schema root's "description", which both OpenAI and Gemini pass to the model
  • :dialect - :openai (default) emits additionalProperties: false; :gemini omits it, because Gemini rejects the key outright

Examples

{:ok, json} = ExAgent.Schema.to_json_schema(Invoice, description: "One invoice")

json["required"]              #=> ["total", "currency", "issued_on", "lines"]
json["additionalProperties"]  #=> false