ALLM.Providers.Support.ImageMime (allm v0.3.0)

Copy Markdown View Source

Per-provider MIME and size validation for ALLM.ImagePart content.

Layer B helper used by the OpenAI and (future) Anthropic vision pre-flight to gate %ALLM.ImagePart{} content against per-provider accept-sets and a shared 20-MB byte-size ceiling. See spec §35.6 and Phase 17 design §3.1.

Per-image validation (validate/2)

Resolves the part's %ALLM.Image{} to bytes via ALLM.Image.to_binary/1 and applies two checks:

  • MIME membership against the supplied accept-set.
  • byte_size/1 of the resolved bytes against @max_bytes (20 MB).

URL sources ({:url, _}) skip the size check — the adapter does NOT fetch the URL during pre-flight (Decision #1); size validation is deferred to the provider. MIME enforcement still applies (the URL's :mime_type is always nil per ALLM.Image.from_url/1, so a URL-source %ImagePart{} is accepted regardless of accept-set today; future divergence is captured by the per-provider arg).

Request-level pre-flight (validate_request/2)

Walks every %ALLM.ImagePart{} in request.messages, calls validate/2 for each, accumulates {[:content, msg_idx, part_idx], reason} field errors. Returns :ok when no part fails, otherwise an %ALLM.Error.ValidationError{reason: :invalid_message} with all per-image errors in :errors. Adapters call this once in pre-flight; per-provider divergence is isolated to accept_mimes/1.

Provider accept-sets

iex> ALLM.Providers.Support.ImageMime.accept_mimes(:openai)
["image/png", "image/jpeg", "image/webp", "image/gif"]

iex> ALLM.Providers.Support.ImageMime.accept_mimes(:anthropic)
["image/png", "image/jpeg", "image/webp", "image/gif"]

Both providers' published 2026-04 accept-sets match; the per-provider arg keeps the seam open for future divergence.

Summary

Types

Per-image validation result.

Functions

Provider accept-set for image/* MIME types.

Validate a single %ImagePart{} against an accept-set and the 20-MB size ceiling.

Walk every %ImagePart{} in request.messages, validate, and return either :ok or a %ValidationError{reason: :invalid_message} carrying per-image field errors.

Types

validate_result()

@type validate_result() ::
  :ok
  | {:error,
     {:unsupported_image_format, mime :: String.t() | nil}
     | {:image_too_large, byte_size :: non_neg_integer()}
     | :missing_mime_type}

Per-image validation result.

Functions

accept_mimes(atom)

@spec accept_mimes(:openai | :anthropic) :: [String.t()]

Provider accept-set for image/* MIME types.

Examples

iex> ALLM.Providers.Support.ImageMime.accept_mimes(:openai)
["image/png", "image/jpeg", "image/webp", "image/gif"]

validate(image_part, accept_mimes)

@spec validate(ALLM.ImagePart.t(), [String.t()]) :: validate_result()

Validate a single %ImagePart{} against an accept-set and the 20-MB size ceiling.

Examples

iex> img = ALLM.Image.from_binary("hi", "image/png")
iex> part = ALLM.ImagePart.new(img)
iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
:ok

iex> img = ALLM.Image.from_binary("hi", "image/svg+xml")
iex> part = ALLM.ImagePart.new(img)
iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
{:error, {:unsupported_image_format, "image/svg+xml"}}

iex> img = ALLM.Image.from_url("https://example.com/x.png")
iex> part = ALLM.ImagePart.new(img)
iex> ALLM.Providers.Support.ImageMime.validate(part, ["image/png"])
:ok

validate_request(request, provider)

@spec validate_request(ALLM.Request.t(), :openai | :anthropic) ::
  :ok | {:error, ALLM.Error.ValidationError.t()}

Walk every %ImagePart{} in request.messages, validate, and return either :ok or a %ValidationError{reason: :invalid_message} carrying per-image field errors.

Field paths: [:content, msg_idx, part_idx] per spec §35.6 / Phase 17 design §7.

Examples

iex> img = ALLM.Image.from_binary("hi", "image/png")
iex> req = ALLM.Request.new([%ALLM.Message{role: :user, content: [%ALLM.ImagePart{image: img}]}])
iex> ALLM.Providers.Support.ImageMime.validate_request(req, :openai)
:ok