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

A content-moderation response — Layer A serializable data.

    iex> result = ALLM.ModerationResult.new(flagged: true, categories: %{"violence" => true})
    iex> resp = ALLM.ModerationResponse.new(results: [result])
    iex> ALLM.ModerationResponse.flagged?(resp)
    true

## Cardinality

`length(response.results) == length(request.input)` when
`ALLM.ModerationRequest.multimodal?/1` is `false`, and `== 1` when it is
`true`. The rule is stated in full on `ALLM.ModerationRequest`.

## No `:usage`

Unlike `ALLM.EmbeddingResponse` and `ALLM.ImageResponse`, this struct has
**no `:usage` field**. Moderation endpoints are free and return no usage
object, so there are no counters to carry and synthesizing empty ones
would be noise. `ALLM.moderate/3`'s `[:allm, :moderate, :stop]` telemetry
metadata still carries a `:usage` key (always `nil`) so that a metrics
handler written against one capability does not `KeyError` when pointed at
another; the absence is on the struct only.

## Fields

`:provider` is the provider atom (`:openai`), populated by the adapter. It
is present here and not on `ALLM.EmbeddingResponse` because a moderation
verdict is routinely persisted alongside the content it judged, and
"which policy engine said this" is the field an auditor asks for six
months later.

`:id` is the provider's own identifier for the call, and there is exactly
one per HTTP round-trip. `:raw` carries the same caller-responsibility
contract as `ALLM.Response.raw`: a non-JSON-encodable `:raw` raises at
encode time.

# `t`

```elixir
@type t() :: %ALLM.ModerationResponse{
  id: String.t() | nil,
  metadata: map(),
  model: String.t() | nil,
  provider: atom() | nil,
  raw: term(),
  request_id: String.t() | nil,
  results: [ALLM.ModerationResult.t()]
}
```

# `flagged?`

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

True when **any** result is flagged.

This is the one-call answer to "should I stop here?" for the common case
of moderating a single string.

## Examples

    iex> clean = ALLM.ModerationResult.new(flagged: false)
    iex> flagged = ALLM.ModerationResult.new(flagged: true)
    iex> ALLM.ModerationResponse.flagged?(ALLM.ModerationResponse.new(results: [clean, flagged]))
    true

    iex> ALLM.ModerationResponse.flagged?(ALLM.ModerationResponse.new())
    false

# `flagged_categories`

```elixir
@spec flagged_categories(t()) :: [String.t()]
```

Union of every result's flagged categories, deduplicated and sorted.

Same meaning as `ALLM.ModerationResult.flagged_categories/1`, one scope
up: use this to report *what* tripped anywhere in a batch, and the
per-result function to report *which item* tripped it.

## Examples

    iex> a = ALLM.ModerationResult.new(flagged: true, categories: %{"violence" => true})
    iex> b = ALLM.ModerationResult.new(flagged: true, categories: %{"violence" => true, "hate" => true})
    iex> ALLM.ModerationResponse.flagged_categories(ALLM.ModerationResponse.new(results: [a, b]))
    ["hate", "violence"]

    iex> ALLM.ModerationResponse.flagged_categories(ALLM.ModerationResponse.new())
    []

# `new`

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

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

Unknown keys raise `KeyError` via `struct!/2`.

## Examples

    iex> resp = ALLM.ModerationResponse.new(model: "omni-moderation-latest")
    iex> resp.results
    []
    iex> resp.provider
    nil

---

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