# `LLMDB.Model`
[🔗](https://github.com/agentjido/llm_db/blob/main/lib/llm_db/model.ex#L1)

Model struct with Zoi schema validation.

Represents an LLM model with complete metadata including identity, provider,
dates, limits, costs, pricing, modalities, capabilities, tags, lifecycle status, and aliases.

## Lifecycle

Models have a lifecycle with three possible states: `"active"`, `"deprecated"`, and `"retired"`.

The lifecycle is represented in two ways for flexibility and backward compatibility:

- `:deprecated` and `:retired` - Boolean flags for quick checks
- `:lifecycle` - Structured map with status, dates, and replacement info

These are automatically synchronized:
- If `lifecycle.status` is set, the boolean flags are derived from it
- If only boolean flags are set, `lifecycle.status` is derived from them

### Lifecycle Fields

- `lifecycle.status` - One of `"active"`, `"deprecated"`, `"retired"`
- `lifecycle.deprecated_at` - ISO8601 date when deprecation occurred/will occur
- `lifecycle.retires_at` - ISO8601 date when retirement is planned
- `lifecycle.replacement` - Suggested replacement model ID

### Temporal Lifecycle

Use `effective_status/2`, `deprecated?/2`, and `retired?/2` to get status
that respects `deprecated_at` and `retires_at` dates:

    model = LLMDB.Model.new!(%{
      id: "old-model",
      provider: :openai,
      lifecycle: %{status: "active", deprecated_at: "2025-01-01", retires_at: "2025-06-01"}
    })

    LLMDB.Model.effective_status(model, ~U[2025-03-01 00:00:00Z])
    # => "deprecated"

    LLMDB.Model.retired?(model, ~U[2025-07-01 00:00:00Z])
    # => true

## Pricing Fields

Models have two pricing-related fields:

- `:cost` - Legacy simple pricing (per-million-token rates for input/output/cache/reasoning)
- `:pricing` - Flexible component-based pricing with support for tokens, tools, images, storage

The `:cost` field is automatically converted to `:pricing.components` at load time
for backward compatibility. See `LLMDB.Pricing` and the [Pricing and Billing guide](pricing-and-billing.md).

# `t`

```elixir
@type t() :: %LLMDB.Model{
  aliases: [binary()],
  base_url: nil | nil | binary(),
  capabilities:
    nil
    | nil
    | %{
        :json =&gt; %{
          optional(:native) =&gt; nil | boolean(),
          optional(:strict) =&gt; nil | boolean(),
          optional(:schema) =&gt; nil | boolean()
        },
        :tools =&gt; %{
          optional(:enabled) =&gt; nil | boolean(),
          optional(:strict) =&gt; nil | boolean(),
          optional(:parallel) =&gt; nil | boolean(),
          optional(:streaming) =&gt; nil | boolean(),
          optional(:forced_choice) =&gt; nil | boolean()
        },
        :reasoning =&gt; %{
          optional(:enabled) =&gt; nil | boolean(),
          optional(:token_budget) =&gt; nil | integer()
        },
        :streaming =&gt; %{
          optional(:text) =&gt; nil | boolean(),
          optional(:tool_calls) =&gt; nil | boolean()
        },
        :chat =&gt; boolean(),
        :embeddings =&gt;
          boolean()
          | %{
              optional(:min_dimensions) =&gt; nil | integer(),
              optional(:max_dimensions) =&gt; nil | integer(),
              optional(:default_dimensions) =&gt; nil | integer()
            },
        optional(:caching) =&gt; nil | %{optional(:type) =&gt; nil | binary()}
      },
  catalog_only: boolean(),
  cost:
    nil
    | nil
    | %{
        optional(:input) =&gt; nil | number(),
        optional(:output) =&gt; nil | number(),
        optional(:request) =&gt; nil | number(),
        optional(:image) =&gt; nil | number(),
        optional(:cache_read) =&gt; nil | number(),
        optional(:cache_write) =&gt; nil | number(),
        optional(:training) =&gt; nil | number(),
        optional(:reasoning) =&gt; nil | number(),
        optional(:audio) =&gt; nil | number(),
        optional(:input_audio) =&gt; nil | number(),
        optional(:output_audio) =&gt; nil | number(),
        optional(:input_video) =&gt; nil | number(),
        optional(:output_video) =&gt; nil | number()
      },
  deprecated: boolean(),
  doc_url: nil | nil | binary(),
  execution:
    nil
    | nil
    | %{
        optional(:text) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:image) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:embed) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:object) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:transcription) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:speech) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            },
        optional(:realtime) =&gt;
          nil
          | %{
              optional(:path) =&gt; nil | binary(),
              optional(:family) =&gt; nil | binary(),
              :supported =&gt; boolean(),
              optional(:wire_protocol) =&gt; nil | binary(),
              optional(:transport) =&gt; nil | binary(),
              optional(:provider_model_id) =&gt; nil | binary(),
              optional(:base_url) =&gt; nil | binary()
            }
      },
  extra: nil | nil | map(),
  family: nil | nil | binary(),
  id: binary(),
  knowledge: nil | nil | binary(),
  last_updated: nil | nil | binary(),
  lifecycle:
    nil
    | nil
    | %{
        optional(:status) =&gt; nil | binary(),
        optional(:deprecated_at) =&gt; nil | binary(),
        optional(:retires_at) =&gt; nil | binary(),
        optional(:replacement) =&gt; nil | binary()
      },
  limits:
    nil
    | nil
    | %{
        optional(:output) =&gt; nil | integer(),
        optional(:context) =&gt; nil | integer()
      },
  modalities:
    nil
    | nil
    | %{optional(:input) =&gt; nil | [atom()], optional(:output) =&gt; nil | [atom()]},
  model: nil | nil | binary(),
  name: nil | nil | binary(),
  pricing:
    nil
    | nil
    | %{
        :merge =&gt; binary(),
        optional(:currency) =&gt; nil | binary(),
        components: [
          %{
            :id =&gt; binary(),
            optional(:unit) =&gt; nil | binary(),
            optional(:kind) =&gt; nil | binary(),
            optional(:tool) =&gt; nil | atom() | binary(),
            optional(:per) =&gt; nil | integer(),
            optional(:rate) =&gt; nil | number(),
            optional(:meter) =&gt; nil | binary(),
            optional(:size_class) =&gt; nil | binary(),
            optional(:notes) =&gt; nil | binary()
          }
        ]
      },
  provider: atom(),
  provider_model_id: nil | nil | binary(),
  release_date: nil | nil | binary(),
  retired: boolean(),
  tags: nil | nil | [binary()]
}
```

# `deprecated?`

```elixir
@spec deprecated?(t(), DateTime.t()) :: boolean()
```

Returns true if the model is deprecated or retired (effective status).

Considers declared status, date-based lifecycle transitions, and boolean flags.

## Examples

    iex> model = %LLMDB.Model{id: "gpt-4", provider: :openai, deprecated: true}
    iex> LLMDB.Model.deprecated?(model)
    true

# `effective_status`

```elixir
@spec effective_status(t(), DateTime.t()) :: String.t()
```

Returns the effective lifecycle status, considering dates, declared status, and boolean flags.

This function determines status using this precedence:
1. Declared `lifecycle.status` (if it indicates an advanced stage)
2. Date-based auto-advancement (`deprecated_at`, `retires_at`)
3. Boolean flags (`retired`, `deprecated`) as fallback
4. Default: `"active"`

## Examples

    iex> model = %LLMDB.Model{
    ...>   id: "gpt-4",
    ...>   provider: :openai,
    ...>   lifecycle: %{status: "active", deprecated_at: "2025-01-01T00:00:00Z"}
    ...> }
    iex> LLMDB.Model.effective_status(model, ~U[2025-06-01 00:00:00Z])
    "deprecated"

# `lifecycle_status`

```elixir
@spec lifecycle_status(t()) :: String.t() | nil
```

Returns the declared lifecycle status from the model's lifecycle field.

## Examples

    iex> model = %LLMDB.Model{id: "gpt-4", provider: :openai, lifecycle: %{status: "deprecated"}}
    iex> LLMDB.Model.lifecycle_status(model)
    "deprecated"

    iex> model = %LLMDB.Model{id: "gpt-4", provider: :openai}
    iex> LLMDB.Model.lifecycle_status(model)
    nil

# `new`

```elixir
@spec new(map()) :: {:ok, t()} | {:error, term()}
```

Creates a new Model struct from a map, validating with Zoi schema.

## Examples

    iex> LLMDB.Model.new(%{id: "gpt-4", provider: :openai})
    {:ok, %LLMDB.Model{id: "gpt-4", model: "gpt-4", provider: :openai}}

    iex> LLMDB.Model.new(%{})
    {:error, _validation_errors}

# `new!`

```elixir
@spec new!(map()) :: t()
```

Creates a new Model struct from a map, raising on validation errors.

## Examples

    iex> LLMDB.Model.new!(%{id: "gpt-4", provider: :openai})
    %LLMDB.Model{id: "gpt-4", model: "gpt-4", provider: :openai}

# `retired?`

```elixir
@spec retired?(t(), DateTime.t()) :: boolean()
```

Returns true if the model is retired (effective status).

Considers declared status, date-based lifecycle transitions, and boolean flags.

## Examples

    iex> model = %LLMDB.Model{id: "gpt-4", provider: :openai, retired: true}
    iex> LLMDB.Model.retired?(model)
    true

# `schema`

Returns the Zoi schema for Model

# `spec`

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

Formats a model as a spec string in the given format.

Delegates to `LLMDB.Spec.format_spec/2` with the model's provider and ID.
If no format is specified, uses the application config `:llm_db, :model_spec_format`
(default: `:provider_colon_model`).

## Parameters

- `model` - The model struct
- `format` - Optional format override (`:provider_colon_model`, `:model_at_provider`, `:filename_safe`)

## Examples

    iex> model = %LLMDB.Model{provider: :openai, id: "gpt-4"}
    iex> LLMDB.Model.spec(model)
    "openai:gpt-4"

    iex> LLMDB.Model.spec(model, :model_at_provider)
    "gpt-4@openai"

    iex> LLMDB.Model.spec(model, :filename_safe)
    "gpt-4@openai"

---

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