# Model Specs

Model specs are how ReqLLM knows which model to call and what metadata is needed to route the request correctly.

That can be as simple as a registry lookup like `"openai:gpt-4o"`, or as explicit as a full `%LLMDB.Model{}` that carries the provider, model ID, base URL, and any extra routing metadata needed for a model that is not in the registry yet.

This guide covers both paths.

## Start with LLMDB

ReqLLM uses [`llm_db`](https://hex.pm/packages/llm_db) as its model registry. The easiest human-readable reference for that registry is [LLM Catalog](https://llmcatalog.dev).

Use [LLM Catalog](https://llmcatalog.dev) when you want to:

- look up the exact provider and model ID to pass to ReqLLM
- inspect current model variants and versioned releases
- confirm whether a date-stamped release ID already exists in the registry
- copy an exact `provider:model` spec instead of guessing

ReqLLM and LLMDB intentionally lean into exact, versioned model IDs when providers publish them. That is why you will often see date-based or version-stamped IDs such as:

- `anthropic:claude-3-5-sonnet-20240620`
- `openai:gpt-4o-mini-2024-07-18`
- `google_vertex:claude-sonnet-4-5@20250929`

That strategy matters for developer experience:

- exact IDs are reproducible and easier to debug
- aliases can still resolve to a current canonical model through LLMDB
- moving from one dated release to another is an explicit choice instead of an accidental drift

If the model is already on [LLM Catalog](https://llmcatalog.dev), prefer using that exact spec first.

## What A Model Spec Is

A model spec is the complete input ReqLLM uses to resolve a model.

In practice, ReqLLM supports four forms:

### 1. String Specs

The most common path. Strings resolve through LLMDB.

```elixir
"anthropic:claude-haiku-4-5"
"openai:gpt-4o-mini-2024-07-18"
"google_vertex:claude-sonnet-4-5@20250929"
```

### 2. Tuple Specs

Tuples also resolve through LLMDB, but let you keep the provider and model ID split.

```elixir
{:anthropic, "claude-haiku-4-5", max_tokens: 512}
{:openai, id: "gpt-4o"}
```

When a three-element tuple is passed directly to an operation, its keyword list supplies
low-precedence defaults for options documented by that operation:

```elixir
model = {:anthropic, "claude-haiku-4-5", max_tokens: 512, temperature: 0.3}

ReqLLM.generate_text(model, "Hello", temperature: 0.7)
```

Here `max_tokens: 512` is used and the explicit `temperature: 0.7` wins. An explicit
`provider_options` value replaces a tuple's provider-options default as one option. Options
that do not belong to the selected operation, invalid values, duplicate defaults, and
operation-control or request-input keys are ignored with a warning rather than applied to
the wrong request. Pass `on_unsupported: :ignore` on the call to suppress that warning.

`ReqLLM.model/1` resolves only model identity. It returns the same `%LLMDB.Model{}` for
equivalent two- and three-element tuples. The two-element keyword tuple remains an identity
form; only the documented three-element tuple supplies operation defaults.

### 3. `%LLMDB.Model{}`

This is the canonical explicit model contract in ReqLLM.

If you already have a `%LLMDB.Model{}`, ReqLLM uses it directly instead of looking up the model in the registry.

```elixir
model =
  LLMDB.Model.new!(%{
    provider: :openai,
    id: "gpt-6-mini",
    base_url: "http://localhost:8000/v1"
  })

ReqLLM.generate_text!(model, "Hello")
```

### 4. Plain Maps

ReqLLM accepts plain maps for backwards compatibility and convenience.

They are treated as full model specs and normalized into an enriched `%LLMDB.Model{}`.

```elixir
ReqLLM.generate_text!(
  %{provider: :openai, id: "gpt-6-mini", base_url: "http://localhost:8000/v1"},
  "Hello"
)
```

The clearer path is to normalize first with `ReqLLM.model!/1`.

## How ReqLLM Resolves Model Specs

ReqLLM has two distinct resolution paths:

### Registry Path

Strings and tuples resolve through LLMDB.

```elixir
{:ok, model} = ReqLLM.model("openai:gpt-4o")
{:ok, model} = ReqLLM.model({:anthropic, "claude-haiku-4-5"})
```

This path is best when:

- the model already exists in LLMDB
- you want aliases and canonical version resolution
- you want shared metadata like pricing, capabilities, and limits

### Full Model Specification Path

`%LLMDB.Model{}` values and plain maps bypass registry lookup. They are self-contained model specs.

This path is best when:

- a new model exists but is not in LLMDB yet
- you are testing a local or proxied deployment
- you need per-model `base_url` metadata
- you are working with a private or experimental model ID

This is the key point: you do not need the model to exist in LLMDB before ReqLLM can use it, as long as you provide a complete enough model spec.

## Inspect Request Routing Without Executing

`ReqLLM.plan/3` provides an experimental, redacted view of text-request routing before
ReqLLM encodes a request or resolves credentials:

```elixir
{:ok, diagnostic} =
  ReqLLM.plan("openai:gpt-4o-mini", :chat,
    max_tokens: 256,
    stream: true
  )

diagnostic.surface
#=> :openai_responses

diagnostic.transport
#=> :finch

diagnostic.route
#=> %{method: :post, path: "/responses"}
```

The result contains the resolved provider/model ID, operation, named surface, transport,
relative route template, canonical and provider-translated option names, fallbacks, and
warnings. It does not contain model metadata, internal modules, option values, hosts,
credentials, prompt/message/tool/file values, or encoded request bodies. ReqLLM 1.x does
not silently retry another provider surface, so `fallbacks` is currently empty.

Planning currently covers the production surfaces migrated to the shared planner: OpenAI
Chat Completions, OpenAI Responses, and Anthropic Messages. Unsupported providers and
surface/transport combinations return normal ReqLLM structured errors without sending a
request.

## Recommended Workflow

### Use a string when the model is in LLMDB

```elixir
ReqLLM.generate_text!("openai:gpt-4o", "Hello")
```

### Pin an exact release when reproducibility matters

```elixir
ReqLLM.generate_text!("anthropic:claude-3-5-sonnet-20240620", "Hello")
```

### Use `ReqLLM.model!/1` when the model is not in LLMDB yet

```elixir
model =
  ReqLLM.model!(%{
    provider: :openai,
    id: "gpt-6-mini",
    base_url: "http://localhost:8000/v1"
  })

ReqLLM.generate_text!(model, "Hello")
```

### Keep the resulting `%LLMDB.Model{}` if you are going to reuse it

```elixir
model =
  ReqLLM.model!(%{
    provider: :openai,
    id: "gpt-6-mini",
    base_url: "http://localhost:8000/v1"
  })

ReqLLM.generate_text!(model, "Write a haiku")
ReqLLM.stream_text!(model, "Write a poem")
```

## Minimum Required Fields

For the full model specification path, the minimum required fields are:

- `provider`
- `id` or `model`

ReqLLM then enriches and normalizes the spec where possible.

## Common Fields

These are the fields you will use most often when building a full model spec.

### `provider`

The provider atom, such as `:openai`, `:anthropic`, `:google`, `:google_vertex`, `:azure`, or `:openrouter`.

This is required.

### `id`

The ReqLLM model ID.

For most cases this is also the API model ID. This is required unless you provide `model` instead.

### `model`

An alternate way to provide the model ID. ReqLLM normalizes `id` and `model` so either can seed the spec.

### `provider_model_id`

The provider-facing model ID when it should differ from `id`.

This is useful when:

- you want a friendlier local `id`
- you want to preserve an alias in `id` but send a different provider model ID
- a provider needs a specific wire ID that differs from your local identifier

If it can be derived, ReqLLM and LLMDB will fill it in for you.

### `base_url`

Per-model endpoint metadata.

This is especially useful for:

- local OpenAI-compatible servers
- proxies
- Azure resource endpoints
- provider-compatible gateways

### `capabilities`, `limits`, `modalities`, `pricing`, `cost`

Optional metadata that can improve validation, usage reporting, and capability checks.

You do not need to provide all of these just to make a request.

### `capabilities`

Use this map to state the features that the model supports. This metadata is important
for operations such as embeddings because ReqLLM checks the capability before it sends
the request. For example, a local Ollama embedding model whose ID does not contain
`"embedding"` needs an explicit capability:

```elixir
model =
  ReqLLM.model!(%{
    provider: :ollama,
    id: "nomic-embed-text",
    capabilities: %{embeddings: true}
  })

ReqLLM.embed(model, "Hello")
```

The complete capabilities map supports these fields:

- `chat` - A boolean that states if the model supports chat.
- `embeddings` - A boolean, or a map with `min_dimensions`, `max_dimensions`, and
  `default_dimensions`.
- `rerank` - A boolean that states if the model supports reranking.
- `reasoning` - A map with `enabled`, `effort`, `thinking`, and `token_budget`.
  - `effort` supports `supported`, `values`, and `default`.
  - `thinking` supports `supported`, `types`, `default_type`, `disable_supported`,
    `raw_output_supported`, `summary_supported`, and `encrypted_supported`.
  - `token_budget` can be a non-negative integer or a map with `min`, `max`, and
    `default`.
- `tools` - A map with `enabled`, `streaming`, `strict`, `parallel`, and
  `forced_choice`.
- `json` - A map with `native`, `schema`, and `strict`.
- `caching` - A map with `type` set to `"implicit"` or `"explicit"`.
- `streaming` - A map with `text` and `tool_calls`.
- `batch`, `citations`, `code_execution`, and `context_management` - Maps with
  `supported` and an optional list of `features`.

The nested support fields are booleans unless the list above gives another type. Only
set fields that you know. LLMDB adds defaults when it normalizes the model spec.

### `extra`

Provider-specific metadata that does not belong in the common top-level fields.

The main advanced cases today are:

- `extra.family` for certain Google Vertex MaaS models
- `extra.wire.protocol` when you need to force a specific OpenAI-compatible wire protocol

## Examples

### Standard Catalog Model

```elixir
ReqLLM.generate_text!("openai:gpt-4o", "Hello")
```

### Exact Dated Release

```elixir
ReqLLM.generate_text!("openai:gpt-4o-mini-2024-07-18", "Hello")
```

### Local OpenAI-Compatible Server

```elixir
model =
  ReqLLM.model!(%{
    provider: :openai,
    id: "qwen3-32b",
    base_url: "http://localhost:8000/v1"
  })

ReqLLM.generate_text!(model, "Explain supervision trees")
```

### Azure

Azure often benefits from the full model specification path because the Azure resource URL is model metadata.

`deployment` is still a request option.

```elixir
model =
  ReqLLM.model!(%{
    provider: :azure,
    id: "gpt-4o",
    base_url: "https://my-resource.openai.azure.com/openai"
  })

ReqLLM.generate_text!(
  model,
  "Hello",
  deployment: "my-gpt4-deployment"
)
```

### Google Vertex MaaS

Some Google Vertex MaaS and OpenAI-compatible model IDs need an explicit family hint if the family cannot be inferred from the ID alone.

```elixir
model =
  ReqLLM.model!(%{
    provider: :google_vertex,
    id: "zai-org/glm-4.7-maas",
    extra: %{family: "glm"}
  })

ReqLLM.generate_text!(model, "Hello")
```

## Advanced: Local `%LLMDB.Model{}`

The canonical advanced path is an explicit `%LLMDB.Model{}`.

If you want full control, build it directly with `LLMDB.Model.new!/1`.

```elixir
model =
  LLMDB.Model.new!(%{
    provider: :openai,
    id: "gpt-6-mini",
    provider_model_id: "gpt-6-mini",
    base_url: "http://localhost:8000/v1",
    capabilities: %{chat: true},
    limits: %{context: 200_000, output: 8_192}
  })

ReqLLM.generate_text!(model, "Hello")
```

This is useful when:

- you want to construct and store reusable model definitions yourself
- you want a fully explicit contract with no ambiguity about the final struct
- you are integrating ReqLLM into a system that already manages model metadata

Plain maps are still accepted, but conceptually they are convenience input for constructing `%LLMDB.Model{}` values.

## Validation And Errors

ReqLLM intentionally hard-fails early for malformed full model specs.

Common failures include:

- missing `provider`
- missing `id` or `model`
- provider string that does not correspond to a registered provider
- provider-specific routing metadata that cannot be inferred

Provider-specific examples:

- Azure still needs `base_url`
- Google Vertex MaaS models may need `extra.family`

This is an advanced workflow, so explicit errors are preferred over silent fallback behavior.

## Useful Helpers

### Resolve a model spec

```elixir
{:ok, model} = ReqLLM.model("openai:gpt-4o")
model = ReqLLM.model!(%{provider: :openai, id: "gpt-6-mini"})
```

### Browse models in the registry

```elixir
models = LLMDB.models(:openai)
specs = Enum.map(models, &LLMDB.Model.spec/1)
```

### Resolve directly through LLMDB

```elixir
{:ok, model} = LLMDB.model("openai:gpt-4o")
```

## When To Update LLMDB Instead

The full model specification path is the fastest way to use a model that is missing from the registry.

You should still update LLMDB or add registry metadata when you want:

- the model to be discoverable on [LLM Catalog](https://llmcatalog.dev)
- shared, reusable metadata for the team
- compatibility tooling such as `mix mc`
- richer cost, capability, and limit metadata everywhere

Use the registry for shared catalog quality.

Use full model specs when you need to move immediately.
