Model Resolution
How RubyLLM turns a model name into a concrete model and provider, step by step, covering aliases, the registry, provider preference, and unlisted models.
After reading this guide, you will know:
- The exact steps RubyLLM follows to turn a model name into a model and a provider.
- How aliases map a friendly name to a provider’s versioned ID.
- How RubyLLM picks a provider when you don’t name one.
- How provider-specific resolution works, including Bedrock regions and inference profiles.
- How to use a model the registry doesn’t list, and what happens when resolution fails.
Overview
Every entry point that takes a model: argument runs the same resolution. RubyLLM.chat, RubyLLM.embed, and RubyLLM.paint all hand the name to the registry, which returns two things: a RubyLLM::Model describing the model, and the provider instance that will serve it.
chat = RubyLLM.chat(model: "claude-haiku-4-5", provider: :bedrock)
chat.model.id # => "anthropic.claude-haiku-4-5-20251001-v1:0" (resolved from the alias)
chat.model.provider # => "bedrock"
You gave a name; RubyLLM resolved it to a concrete model and chose a provider. The rest of this guide is the procedure behind that.
The Resolution Procedure
Resolution takes a name and an optional provider, and runs in this order:
- Did you name a provider? If you passed
provider:, resolution is scoped to that provider. If not, RubyLLM searches every provider and chooses one by preference. - Resolve aliases. The name is looked up in the alias table and rewritten to the provider’s versioned ID when a match exists.
- Look up the registry. RubyLLM finds the model whose ID (or pre-alias name) matches in the model registry.
- Bypass the registry for unlisted models. With
assume_model_exists: true(or a local provider), RubyLLM skips the lookup and trusts the name you gave. - Resolve the provider. The provider comes from the matched model’s
providerfield, or from theprovider:you named.
If no model matches and you didn’t ask RubyLLM to assume it exists, resolution raises RubyLLM::ModelNotFoundError.
Aliases
An alias is a stable, friendly name that maps to a provider’s exact, versioned model ID. Aliases live in aliases.json and are shaped as friendly-name => { provider => versioned-id }:
{
"claude-haiku-4-5": {
"anthropic": "claude-haiku-4-5-20251001",
"openrouter": "anthropic/claude-haiku-4.5",
"bedrock": "anthropic.claude-haiku-4-5-20251001-v1:0",
"vertexai": "claude-haiku-4-5",
"azure": "claude-haiku-4-5-20251001"
}
}
When you name a provider, RubyLLM uses that provider’s entry. Without a provider, it uses the first entry listed for the alias:
RubyLLM.chat(model: "claude-haiku-4-5") # resolves to anthropic
RubyLLM.chat(model: "claude-haiku-4-5", provider: :bedrock) # resolves to anthropic.claude-haiku-4-5-20251001-v1:0
A name that isn’t in the alias table is passed through unchanged, so exact model IDs always work.
Resolving Without a Provider
When you don’t name a provider, the same name can exist across several services, so RubyLLM has to choose one. It collects every registry model whose ID matches either the name you gave or its alias-resolved ID, then picks the most preferred provider.
Provider preference puts first-party providers ahead of the aggregators that resell their models:
| Rank | Providers |
|---|---|
| First-party | openai, anthropic, gemini, deepseek, mistral, cohere, perplexity, xai |
| Cloud platforms | vertexai, bedrock |
| Aggregators and local | openrouter, azure, ollama_cloud, ollama, gpustack |
Preference decides the winner when several providers carry the model. Anthropic, Vertex AI, Bedrock, and OpenRouter all serve claude-haiku-4-5, and Anthropic outranks the rest, so the bare name resolves to Anthropic:
RubyLLM.chat(model: "claude-haiku-4-5").model.provider # => "anthropic"
To pin a different provider, name it:
RubyLLM.chat(model: "claude-opus-4", provider: :vertexai)
Provider preference only breaks ties between providers that both carry the model. It never makes RubyLLM use a provider you haven’t configured a key for, because a chat still needs that provider’s credentials to run.
Resolving With a Provider
When you pass provider:, RubyLLM resolves the alias for that provider, then finds the model whose ID matches the resolved ID (falling back to the raw name) for that provider only.
RubyLLM.embed("hello", model: "text-embedding-3-small", provider: :openai)
Bedrock Regions and Inference Profiles
Bedrock adds one more step. Its model IDs carry a region prefix and may require an inference profile, so before the registry lookup RubyLLM rewrites the ID for your configured bedrock_region:
RubyLLM.configure { |config| config.bedrock_region = "us-east-1" }
chat = RubyLLM.chat(model: "claude-haiku-4-5", provider: :bedrock)
chat.model.id # => "us.anthropic.claude-haiku-4-5-20251001-v1:0" (region prefix applied)
RubyLLM only applies the prefix when a matching regional model exists in the registry, and normalizes the inference-profile form from the model’s metadata. See Custom Endpoints and Unlisted Models for routing the same model through a different provider.
Bedrock Converse and Mantle Endpoints
Bedrock serves models through two endpoints, and the model ID decides which one RubyLLM uses:
| Model ID | Endpoint | Wire format |
|---|---|---|
anthropic.claude-sonnet-5 |
bedrock-mantle |
Anthropic Messages |
openai.gpt-oss-20b, google.gemma-4-31b |
bedrock-mantle |
OpenAI Responses |
qwen.qwen3-coder-next, zai.glm-5 |
bedrock-mantle |
OpenAI Chat Completions |
anthropic.claude-haiku-4-5-20251001-v1:0 |
bedrock-runtime |
Converse |
us.anthropic.claude-sonnet-5, us.amazon.nova-2-lite-v1:0 |
bedrock-runtime |
Converse |
RubyLLM refreshes both catalogs, so the registry records which endpoint serves each model and routing reads that record rather than guessing from the ID. The two catalogs overlap and disagree: mantle spells some Converse models differently (qwen.qwen3-next-80b-a3b-instruct against Converse’s qwen.qwen3-next-80b-a3b) and lists models Converse has never heard of.
Claude models speak the Anthropic Messages API on mantle. The rest of the mantle catalog speaks one of the two OpenAI surfaces: the Gemma 4 and GPT-OSS models answer on Responses, and everything else answers on Chat Completions. Both endpoints use the same AWS credentials and are signed with SigV4, so nothing changes in your configuration.
For a model the registry doesn’t list, RubyLLM falls back to reading the ID: an un-versioned vendor.model ID with no : version suffix, no date stamp, and no region prefix goes to mantle. A bare anthropic. ID always goes to mantle, because Converse only ever serves Claude under a dated and versioned ID.
Point bedrock_mantle_api_base at a different host to override the mantle endpoint, the same way bedrock_api_base overrides the Converse one.
RubyLLM.chat(model: 'openai.gpt-oss-20b', provider: :bedrock).ask('Hello')
Models the Registry Doesn’t List
New releases, custom fine-tunes, and private deployments won’t be in the registry. Set assume_model_exists: true to skip the registry lookup and use the name as-is. You must name a provider, since there is no registry entry to infer one from:
chat = RubyLLM.chat(
model: ENV.fetch("CUSTOM_CHAT_MODEL"),
provider: :openai,
assume_model_exists: true
)
RubyLLM then builds a synthetic RubyLLM::Model with assumed capabilities (function_calling, streaming, vision, structured_output) and a metadata warning, because it can’t know the real ones:
chat.model.capabilities # => ["function_calling", "streaming", "vision", "structured_output"]
chat.model.metadata # => { warning: "Assuming model exists, capabilities may not be accurate" }
You are responsible for using only the features the model actually supports. The same flag works on RubyLLM.embed, RubyLLM.paint, and with_model.
Local providers like Ollama and GPUStack assume models exist automatically. You can pull and run any model name without registering it first, and you don’t need to pass the flag. Ollama Cloud does the same, because its catalog changes faster than the registry. So does Azure, because you name your own deployments.
When Resolution Fails
If nothing matches and you didn’t assume existence, RubyLLM raises RubyLLM::ModelNotFoundError with guidance to refresh the registry:
RubyLLM.chat(model: unknown_model_id)
# => RubyLLM::ModelNotFoundError: Unknown model: ...
# If the model exists at the provider, refresh the registry with
# `RubyLLM.models.refresh`.
A real but newly released model usually means your registry is stale. Refresh it from the published catalog and your configured providers:
RubyLLM.models.refresh
See Model Registry for refreshing in applications and Rails. If the model genuinely isn’t in any catalog, use assume_model_exists: true instead.
Next Steps
- Model Registry - explore, filter, and inspect the registry.
- Custom Endpoints and Unlisted Models - point a provider at a custom host and use unlisted models.
- Custom Providers and Protocols - teach RubyLLM about a service it doesn’t know.