Seventeen providers, one code path, and the credentials that refuse to be uniform
September 21, 2026
The promise of a gem like ruby_llm is that providers become interchangeable. In the chat call they
are: the same ask reaches Anthropic, OpenAI, Gemini, Bedrock, Vertex AI or a model running on your
laptop, and the reply comes back in the same shape.
The seam is one layer down, in what each provider wants before it will answer at all.
Asking the gem instead of guessing
ruby_llm providers are not a list you have to keep: every provider class declares what it needs, and
the registry is readable at runtime. That matters more than it sounds, because the set changes
between releases and a hardcoded copy of it goes stale without anything failing. Reading it is four
lines in a console and the answer is current by construction:
Seventeen entries come back. Twelve of them say exactly what you expect, one API key, which is why
ruby_llm configuration looks trivial until it is not:
Azure OpenAI wants an endpoint, because the model lives at a URL you deployed rather than at a
vendor's. Its azure_api_key is optional, sitting alongside azure_ai_auth_token, so a key is one
of two ways in rather than the way in.
Ollama and GPUStack want an address and no key at all, which is the whole point of running a model
yourself. A settings form with a required password field for these is a form that asks you to invent
a secret.
Bedrock and Vertex AI are the interesting pair, and they are interesting for the same reason. Both
require the least of any provider in the list, and neither requires a secret.
Why the two big clouds ask for the least
Bedrock requires a region. Not a key, not a secret, not a session token, all three of which are
declared optional next to bedrock_credential_provider.
Vertex AI requires a project id and a location. Its vertexai_service_account_key is optional in
exactly the same way.
The reason is that both platforms already have an answer to "who are you" that does not involve
pasting a secret into an application. An EC2 instance or an ECS task has a role, a GKE workload has
a service account, and a developer's laptop has whatever aws configure and gcloud auth left
behind. Requiring a key would mean refusing to work in the environment these services were designed
for.
What they cannot infer is where. A region and a project are not credentials, they are addresses,
and no ambient credential chain can guess which one you meant.
That distinction is worth holding onto when you build the form. A region and a project id belong in
plain text inputs. Masking them, storing them encrypted, and never echoing them back is effort spent
protecting values that appear in every log line and every ARN.
Local providers skip the registry
A provider class declares one more thing, and a Rails Ollama setup is the reason to care:
local? is true for Ollama and GPUStack, and it changes behaviour rather than labelling. Model
resolution checks the registry to reject a typo in a model id, and a local provider has no registry
to check because the models are whatever you installed. So the gem assumes a model on a local
provider exists.
The same escape hatch is available explicitly, which is what makes a local provider usable as a test
harness for everything else:
Point that at a small server of your own speaking the OpenAI chat-completions dialect, and the whole
Rails LLM provider path runs with no key, no network and no bill:
failing over to a second provider is easier to test that way
than against a real provider, because you can make the failure happen on demand.
The mistake this invites
Given a list like the one above, the tempting move is to copy it into your own application as a
constant, so the settings screen knows which fields to render. That is what we did, and the copy
was wrong within one release: our hardcoded Bedrock entry lists an access key id, a secret access
key, a region and a session token, and the gem's own option names for three of those are
bedrock_api_key, bedrock_secret_key and bedrock_session_token.
The list is readable at runtime, from configuration_requirements and configuration_options, and
reading it is the version that cannot drift. A copy is a second source of truth for something the
gem changes on its own schedule, and the failure mode is a form that collects the wrong fields and
a provider that stays unconfigured for a reason nobody can see in the UI.
What this page does not cover
Which provider to choose. Latency, price and model quality are the real inputs to that decision,
they move monthly, and a page that ranked them would be wrong by the time it was indexed.
Nor does it cover the non-chat providers in the list. Deepgram is speech to text and ElevenLabs is
text to speech, so both appear in the seventeen and neither answers a chat call. Counting every
entry in the registry as a chat provider is the easiest mistake to make from the number alone.
The promise of a gem like
ruby_llmis that providers become interchangeable. In the chat call they are: the sameaskreaches Anthropic, OpenAI, Gemini, Bedrock, Vertex AI or a model running on your laptop, and the reply comes back in the same shape.The seam is one layer down, in what each provider wants before it will answer at all.
Asking the gem instead of guessing
ruby_llm providers are not a list you have to keep: every provider class declares what it needs, and the registry is readable at runtime. That matters more than it sounds, because the set changes between releases and a hardcoded copy of it goes stale without anything failing. Reading it is four lines in a console and the answer is current by construction:
Seventeen entries come back. Twelve of them say exactly what you expect, one API key, which is why ruby_llm configuration looks trivial until it is not:
The other five are the ones that break a settings screen built on the assumption above.
The five that want something else
Azure OpenAI wants an endpoint, because the model lives at a URL you deployed rather than at a vendor's. Its
azure_api_keyis optional, sitting alongsideazure_ai_auth_token, so a key is one of two ways in rather than the way in.Ollama and GPUStack want an address and no key at all, which is the whole point of running a model yourself. A settings form with a required password field for these is a form that asks you to invent a secret.
Bedrock and Vertex AI are the interesting pair, and they are interesting for the same reason. Both require the least of any provider in the list, and neither requires a secret.
Why the two big clouds ask for the least
Bedrock requires a region. Not a key, not a secret, not a session token, all three of which are declared optional next to
bedrock_credential_provider.Vertex AI requires a project id and a location. Its
vertexai_service_account_keyis optional in exactly the same way.The reason is that both platforms already have an answer to "who are you" that does not involve pasting a secret into an application. An EC2 instance or an ECS task has a role, a GKE workload has a service account, and a developer's laptop has whatever
aws configureandgcloud authleft behind. Requiring a key would mean refusing to work in the environment these services were designed for.What they cannot infer is where. A region and a project are not credentials, they are addresses, and no ambient credential chain can guess which one you meant.
That distinction is worth holding onto when you build the form. A region and a project id belong in plain text inputs. Masking them, storing them encrypted, and never echoing them back is effort spent protecting values that appear in every log line and every ARN.
Local providers skip the registry
A provider class declares one more thing, and a Rails Ollama setup is the reason to care:
local?is true for Ollama and GPUStack, and it changes behaviour rather than labelling. Model resolution checks the registry to reject a typo in a model id, and a local provider has no registry to check because the models are whatever you installed. So the gem assumes a model on a local provider exists.The same escape hatch is available explicitly, which is what makes a local provider usable as a test harness for everything else:
Point that at a small server of your own speaking the OpenAI chat-completions dialect, and the whole Rails LLM provider path runs with no key, no network and no bill: failing over to a second provider is easier to test that way than against a real provider, because you can make the failure happen on demand.
The mistake this invites
Given a list like the one above, the tempting move is to copy it into your own application as a constant, so the settings screen knows which fields to render. That is what we did, and the copy was wrong within one release: our hardcoded Bedrock entry lists an access key id, a secret access key, a region and a session token, and the gem's own option names for three of those are
bedrock_api_key,bedrock_secret_keyandbedrock_session_token.The list is readable at runtime, from
configuration_requirementsandconfiguration_options, and reading it is the version that cannot drift. A copy is a second source of truth for something the gem changes on its own schedule, and the failure mode is a form that collects the wrong fields and a provider that stays unconfigured for a reason nobody can see in the UI.What this page does not cover
Which provider to choose. Latency, price and model quality are the real inputs to that decision, they move monthly, and a page that ranked them would be wrong by the time it was indexed.
Nor does it cover the non-chat providers in the list. Deepgram is speech to text and ElevenLabs is text to speech, so both appear in the seventeen and neither answers a chat call. Counting every entry in the registry as a chat provider is the easiest mistake to make from the number alone.