# `Alloy.ModelCatalog`
[🔗](https://github.com/alloy-ex/alloy/blob/v0.12.4/lib/alloy/model_catalog.ex#L1)

Behaviour for pluggable model-metadata catalogs.

Alloy needs one fact about a model to do context budgeting: its context
window. The built-in catalog (`Alloy.ModelMetadata`) ships a small,
hand-curated set of entries — enough to work out of the box, but it will
always lag the model release treadmill. This behaviour lets you swap in
your own source of truth instead.

Pass the catalog module per run (or per agent) via `:model_catalog`:

    Alloy.run("...",
      provider: {Alloy.Provider.Anthropic, api_key: key, model: model},
      model_catalog: MyApp.ModelCatalog
    )

Resolution order when deriving the token budget:

1. an explicit `:max_tokens` option always wins
2. `:model_metadata_overrides` (exact/family overrides, see
   `Alloy.ModelMetadata.context_window/2`)
3. the `:model_catalog` module's `c:context_window/1`
4. the catalog's `c:default_context_window/0` if exported, otherwise
   `Alloy.ModelMetadata.default_context_window/0`

## Example: static map

    defmodule MyApp.ModelCatalog do
      @behaviour Alloy.ModelCatalog

      @windows %{
        "claude-opus-4-8" => 1_000_000,
        "internal-finetune-v3" => 32_768
      }

      @impl true
      def context_window(model), do: Map.get(@windows, model)
    end

## Example: backed by `llm_db`

[`llm_db`](https://hex.pm/packages/llm_db) maintains a large model catalog
sourced from models.dev. An adapter is a few lines:

    defmodule MyApp.LlmDbCatalog do
      @behaviour Alloy.ModelCatalog

      @impl true
      def context_window(model) do
        case LLMDb.model(model) do
          %{context_window: limit} when is_integer(limit) -> limit
          _ -> nil
        end
      end
    end

Returning `nil` from `c:context_window/1` means "unknown model" and falls
through to the default window — it is not an error.

# `context_window`

```elixir
@callback context_window(model :: String.t()) :: pos_integer() | nil
```

Returns the context window (in tokens) for `model`, or `nil` if unknown.

# `default_context_window`
*optional* 

```elixir
@callback default_context_window() :: pos_integer()
```

Returns the fallback context window used when `c:context_window/1`
returns `nil`.

Optional — when not exported, Alloy falls back to
`Alloy.ModelMetadata.default_context_window/0`.

---

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