Model Configuration

Copy Markdown View Source

The SDK uses a config-driven model registry so that new Claude models can be added without recompiling application code. All model validation, listing, and suggestion logic reads from Application config at runtime.

Default Configuration

The built-in defaults are set in config/config.exs:

config :claude_agent_sdk, :models, %{
  short_forms: %{
    "opus"       => "opus",
    "sonnet"     => "sonnet",
    "haiku"      => "haiku",
    "opus[1m]"   => "opus[1m]",
    "sonnet[1m]" => "sonnet[1m]"
  },
  full_ids: %{
    "claude-opus-4-6"                => "claude-opus-4-6",
    "claude-sonnet-4-6"              => "claude-sonnet-4-6",
    "claude-haiku-4-5-20251001"      => "claude-haiku-4-5-20251001",
    "claude-opus-4-6[1m]"            => "claude-opus-4-6[1m]",
    "claude-sonnet-4-6[1m]"          => "claude-sonnet-4-6[1m]"
  },
  default: "opus"
}
KeyDescription
short_formsCLI-friendly aliases ("opus", "sonnet", etc.)
full_idsVersioned model identifiers as published by Anthropic
defaultThe model used when none is specified

Both maps use name => name identity mappings because the Claude CLI accepts these strings directly and handles any internal resolution.

Using Models

In Options

# Short form
options = %ClaudeAgentSDK.Options{model: "opus"}

# Full ID
options = %ClaudeAgentSDK.Options{model: "claude-opus-4-6"}

In Agents

agent = ClaudeAgentSDK.Agent.new(
  description: "Research specialist",
  prompt: "You excel at research",
  model: "sonnet"
)

Validation

{:ok, "opus"} = ClaudeAgentSDK.Model.validate("opus")
{:error, :invalid_model} = ClaudeAgentSDK.Model.validate("gpt-5")

Listing

ClaudeAgentSDK.Model.list_models()
# => ["claude-haiku-4-5-20251001", "claude-opus-4-6", "haiku", "opus", ...]

ClaudeAgentSDK.Model.short_forms()
# => ["haiku", "opus", "sonnet", "sonnet[1m]"]

ClaudeAgentSDK.Model.full_ids()
# => ["claude-haiku-4-5-20251001", "claude-opus-4-6", ...]

Suggestions

When a user provides an invalid model name the SDK can suggest corrections:

ClaudeAgentSDK.Model.suggest("opuss")
# => ["opus"]

Adding Custom Models at Runtime

If Anthropic ships a new model before the SDK publishes an update, add it at boot time without waiting for a library release:

# In config/runtime.exs or Application.start callback:
config = Application.get_env(:claude_agent_sdk, :models)

updated =
  config
  |> Map.update!(:full_ids, &Map.put(&1, "claude-opus-5-20260301", "claude-opus-5-20260301"))
  |> Map.update!(:short_forms, &Map.put(&1, "opus5", "opus5"))

Application.put_env(:claude_agent_sdk, :models, updated)

After this, Model.validate("opus5") and Model.validate("claude-opus-5-20260301") will both return {:ok, ...}.

Overriding the Default Model

config = Application.get_env(:claude_agent_sdk, :models)
Application.put_env(:claude_agent_sdk, :models, %{config | default: "opus"})

Effort Level

Control how much reasoning effort Claude applies:

# Standard effort levels (all Opus and Sonnet models)
options = %ClaudeAgentSDK.Options{
  model: "sonnet",
  effort: :high  # :low | :medium | :high | :max
}

# Maximum effort (Opus only)
options = %ClaudeAgentSDK.Options{
  model: "opus",
  effort: :max
}

This emits --effort <level> to the CLI.

Effort Levels

LevelModelsDescription
:lowOpus, SonnetMinimal thinking, fewer tool calls, terse output
:mediumOpus, SonnetBalanced cost/performance (recommended default for Sonnet)
:highOpus, SonnetDeep thinking, detailed responses (API default)
:maxOpus onlyAbsolute maximum capability, no token constraints

Model Gating

  • Haiku: Effort is not supported. The SDK logs a warning and silently omits the --effort flag.
  • Sonnet: Supports :low, :medium, :high. The SDK treats :max as unsupported, logs a warning, and omits the --effort flag.
  • Opus: Supports all four levels including :max.
  • nil model: The SDK resolves the configured default model first, then applies the same gating rules.
  • Invalid effort values raise ArgumentError when options are built or converted to CLI arguments.

See examples/effort_gating_live.exs for a runnable example covering supported and gated effort cases. See examples/max_effort_opus_live.exs for a standalone :max effort demo with Opus and Opus[1m] (not in run_all.sh — expensive).

Thinking Configuration

The thinking option provides structured control over extended thinking:

# Adaptive (default budget 32000 tokens)
options = %ClaudeAgentSDK.Options{thinking: %{type: :adaptive}}

# Explicit budget
options = %ClaudeAgentSDK.Options{thinking: %{type: :enabled, budget_tokens: 16_000}}

# Disabled
options = %ClaudeAgentSDK.Options{thinking: %{type: :disabled}}

Resolution rules:

  • thinking takes precedence over max_thinking_tokens
  • :adaptive uses max_thinking_tokens as fallback, or 32000 if nil
  • :enabled uses budget_tokens directly
  • :disabled emits --max-thinking-tokens 0
  • nil falls back to raw max_thinking_tokens

The max_thinking_tokens field is still supported for backward compatibility:

options = %ClaudeAgentSDK.Options{
  model: "sonnet",
  max_thinking_tokens: 8192
}

Testing

Test code uses ClaudeAgentSDK.Test.ModelFixtures to avoid hardcoding model strings in assertions:

import ClaudeAgentSDK.Test.ModelFixtures

test "handles message start" do
  event = %{"type" => "message_start", "message" => %{"model" => test_model()}}
  # ...
end
HelperValuePurpose
test_model()"test-model-alpha"Primary fixture model
test_model_alt()"test-model-beta"When two distinct models are needed
real_default_model()Reads live configTests that need an actual registry entry

Architecture

graph LR
  Config["config/config.exs<br/>:models map"] --> Model["Model module<br/>known_models/0"]
  Runtime["runtime.exs /<br/>Application.put_env"] --> Model
  Model --> Validate["validate/1"]
  Model --> List["list_models/0"]
  Model --> Suggest["suggest/1"]
  Model --> Default["default_model/0"]
  Mock["Mock module"] --> Default
  Options["Options struct"] --> Validate

The Model module never holds state of its own. Every call reads from Application.get_env(:claude_agent_sdk, :models), making the registry fully dynamic at runtime.