# `ALLM.ModerationRequest`
[🔗](https://github.com/cykod/ALLM/blob/v0.6.1/lib/allm/moderation_request.ex#L1)

A content-moderation request — Layer A serializable data.

    iex> req = ALLM.ModerationRequest.new(input: ["is this ok?"])
    iex> ALLM.ModerationRequest.multimodal?(req)
    false

## `:input` cardinality is type-dependent

This is the one surprising thing about the moderation wire, and it is a
property of the provider endpoint rather than an ALLM choice. `:input` is
a list of items, where an item is either a `t:String.t/0` or an
`t:ALLM.ImagePart.t/0`:

  * **All strings** — the request is a *batch* of `length(input)`
    independent items. A conforming adapter returns exactly that many
    results, with `:index` values `0..length-1`.
  * **Any `%ALLM.ImagePart{}` present** — the entire `:input` list is
    **one** multimodal item (text plus its images, judged together). A
    conforming adapter returns exactly **one** result, at `index: 0`.

`multimodal?/1` reports which shape a request is in, so the result count
is derivable *before* the call. There is no way to send several
multimodal items in one call, which is why the two shapes share one
field rather than being modelled separately.

## Construction

`new/1` is a bare `struct!/2` pass-through: unknown keys raise
`KeyError`, and nothing is enforced. `input: []` is deliberately
*constructible* so that `ALLM.Validate.moderation_request/1` — not
`struct!/2` — is what rejects it.

## Other fields

`:model` is `nil` by default and late-resolved; an adapter that receives
a `nil` model omits the field from the wire body rather than pinning a
name. `:options` is the documented home for provider-specific opaque
opts, and `:metadata` is caller-owned and round-tripped onto the
response unchanged.

# `item`

```elixir
@type item() :: String.t() | ALLM.ImagePart.t()
```

One moderation input item: a string, or an image content part.

The union is what makes the cardinality rule above type-dependent.

# `t`

```elixir
@type t() :: %ALLM.ModerationRequest{
  input: [item()],
  metadata: map(),
  model: String.t() | nil,
  options: map()
}
```

# `multimodal?`

```elixir
@spec multimodal?(t()) :: boolean()
```

True when any element of `:input` is an `t:ALLM.ImagePart.t/0` — see the
cardinality rule in the module docs.

A `%ModerationRequest{}` whose `:input` is not a list returns `false`
rather than raising. That total contract is load-bearing: this function
is read for telemetry metadata *before* validation runs, so it must have
one behaviour for the whole "not a list" class rather than raising on
`42` and returning `false` on `%{}`.

## Examples

    iex> ALLM.ModerationRequest.multimodal?(ALLM.ModerationRequest.new(input: ["text"]))
    false

    iex> part = ALLM.ImagePart.new(ALLM.Image.from_url("https://example.com/cat.png"))
    iex> req = ALLM.ModerationRequest.new(input: ["look at this", part])
    iex> ALLM.ModerationRequest.multimodal?(req)
    true

# `new`

```elixir
@spec new(keyword()) :: t()
```

Build a `%ModerationRequest{}` from keyword opts.

Unknown keys raise `KeyError` via `struct!/2`. No validation — call
`ALLM.Validate.moderation_request/1` to check the field rules.

## Examples

    iex> req = ALLM.ModerationRequest.new()
    iex> req.input
    []
    iex> req.model
    nil

    iex> req = ALLM.ModerationRequest.new(input: ["a", "b"], model: "omni-moderation-latest")
    iex> length(req.input)
    2
    iex> req.model
    "omni-moderation-latest"

---

*Consult [api-reference.md](api-reference.md) for complete listing*
