# `PhoenixKitAI.StructuredOutput`
[🔗](https://github.com/BeamLabEU/phoenix_kit_ai/blob/0.23.0/lib/phoenix_kit_ai/structured_output.ex#L1)

Machine-readable answers from chat models, shared by `PhoenixKitAI.ask/3`,
`PhoenixKitAI.complete/3` and `PhoenixKitAI.describe_image/3`.

Two options turn it on:

  * `schema:` — a JSON Schema map; the answer is requested as that object
    (`response_format: json_schema`, strict) and parsed.
  * `json: true` — any JSON object (`response_format: json_object`).

The schema also rides in the prompt, so a model that rejects
`response_format` (the request is retried once without it on 400/422 —
any 400, so an unrelated bad request costs one extra call) still knows
the shape. The parsed object comes back under `"json"` on a chat
response and `:json` on a vision result; an answer that does not parse
is `{:error, {:no_json_in_response, text}}` and is never cached, though
the provider call is logged with its usage. Nothing validates the
object against the schema locally: `strict` schemas are enforced by the
provider on the first attempt and advisory on the retry, so check the
keys you depend on.

# `format`

```elixir
@type format() :: %{required(String.t()) =&gt; term()} | nil
```

# `attach`

```elixir
@spec attach(list(), String.t() | nil) :: list()
```

Appends `suffix` to the last user message (string content or content
parts). Messages with atom or string keys are both handled.

# `parse`

```elixir
@spec parse(String.t() | nil, boolean()) ::
  {:ok, map() | list() | nil}
  | {:error, {:no_json_in_response, String.t() | nil}}
```

Parses the model's text as a JSON object (or array): the whole text
first, then a fenced block anywhere in it, then the outermost `{…}` —
models wrap answers in prose and fences however they were told. With
`requested?` false returns `{:ok, nil}`.

# `request`

```elixir
@spec request(keyword()) :: {String.t() | nil, format()}
```

The sentence to append to the user's prompt and the `response_format`
to send, or `{nil, nil}` when no JSON was asked for.

# `requested?`

```elixir
@spec requested?(keyword()) :: boolean()
```

Whether the caller asked for a JSON answer.

# `with_fallback`

```elixir
@spec with_fallback(format(), (format() -&gt; {:ok, term()} | {:error, term()})) ::
  {:ok, term()} | {:error, term()}
```

Runs `fun.(response_format)`; when a JSON answer was requested and the
provider answers 400/422 (a model that takes no `response_format`), runs
it once more with `nil`.

---

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