guides/model-specs.md

# 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 [LLMDB.xyz](https://llmdb.xyz).

Use [LLMDB.xyz](https://llmdb.xyz) 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 [LLMDB.xyz](https://llmdb.xyz), 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"}
```

### 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.

## 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.

### `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 [LLMDB.xyz](https://llmdb.xyz)
- 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.