# `Dsxir.LM`

Behaviour for LM providers.

Predictors and adapters issue LM requests through this contract; they never reach
into a specific provider SDK directly. The active impl plus its config live in
`Dsxir.Settings` under `:lm` as a `{impl_module, config :: keyword()}` tuple. The
dispatcher functions on this module read `:lm` from settings, merge per-call opts
on top of the config, and invoke the impl.

The behaviour declares the callbacks predictors and adapters call. New callbacks
(structured output for the Json adapter, streaming for the Chat adapter,
embeddings for the in-memory retriever) extend the behaviour when their consumers
land — old impls keep working while their new-callback path raises until
implemented.

In tests, stub impl callbacks with `mimic` rather than building bespoke fakes.

# `config`

```elixir
@type config() :: keyword()
```

# `messages`

```elixir
@type messages() :: [map()]
```

# `opts`

```elixir
@type opts() :: keyword()
```

# `usage`

```elixir
@type usage() :: %{
  tokens_in: nil | non_neg_integer(),
  tokens_out: nil | non_neg_integer(),
  cost: nil | float()
}
```

# `embed`
*optional* 

```elixir
@callback embed(config(), [String.t()], opts()) ::
  {:ok, [[float()]], usage()} | {:error, term()}
```

# `generate_object`
*optional* 

```elixir
@callback generate_object(config(), messages(), Zoi.schema(), opts()) ::
  {:ok, map(), usage()} | {:error, term()}
```

# `generate_text`

```elixir
@callback generate_text(config(), messages(), opts()) ::
  {:ok, String.t(), usage()} | {:error, term()}
```

# `embed`

```elixir
@spec embed([String.t()], opts()) :: {:ok, [[float()]], usage()} | {:error, term()}
```

Dispatch an `embed` call to the impl module currently active in
`Dsxir.Settings`. Per-call `opts` are merged on top of the config. Raises
`Dsxir.Errors.Invalid.Configuration` when `:lm` is unset, malformed, or the
configured impl does not implement the optional `embed/3` callback.

# `empty_usage`

```elixir
@spec empty_usage() :: usage()
```

Empty usage map used by impls when the upstream LM did not report token
counts or cost. The shape is always present: `tokens_in`, `tokens_out`, and
`cost` are all `nil`.

# `generate_object`

```elixir
@spec generate_object(messages(), Zoi.schema(), opts()) ::
  {:ok, map(), usage()} | {:error, term()}
```

Dispatch a `generate_object` call to the impl module currently active in
`Dsxir.Settings`, requesting a structured object validated against `schema`.

Per-call `opts` are merged on top of the config from settings. Raises
`Dsxir.Errors.Invalid.Configuration` when `:lm` is unset, malformed, or when
the configured impl does not implement the optional `generate_object/4`
callback.

# `generate_text`

```elixir
@spec generate_text(messages(), opts()) ::
  {:ok, String.t(), usage()} | {:error, term()}
```

Dispatch a `generate_text` call to the impl module currently active in
`Dsxir.Settings`. Per-call `opts` are merged on top of the config from settings.
Raises `Dsxir.Errors.Invalid.Configuration` when `:lm` is unset or malformed.

---

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