A registry is an immutable value, not a mutable global: withProvider and withModule return a new registry, and an application that wants a different set constructs one with ProviderRegistry.of and passes it in. Core code resolves a registry through a using clause, so the default is ProviderRegistry.default — every provider on the classpath — unless the caller supplies another.
llm4s-core ships no provider: every one comes from a provider module (llm4s-openai, llm4s-anthropic, llm4s-gemini, llm4s-ollama, llm4s-openai-compatible, llm4s-voyage, or a third party's), found through its META-INF/services entry. Where discovery cannot run - a shaded fat jar whose services files were merged away - ProviderRegistry.ofModules builds the registry from the modules named explicitly.
Chat and embedding providers are held in separate namespaces, because the two sets overlap without either containing the other - OpenAI and Ollama supply both, Voyage only embeddings, Anthropic only chat. The same id may therefore appear in both, and does: ollama names a chat client and an embedding provider that share nothing but a base URL.
Lookup is by ProviderId and never throws: an id nothing handles produces a Left naming the ids that are registered, which is the difference between a usable error and "provider openai not registered".
Value parameters
descriptors
the registered chat providers, in registration order.
embeddingDescriptors
the registered embedding providers, in registration order.
Folds an alias onto the id that owns it, leaving unknown strings alone.
Folds an alias onto the id that owns it, leaving unknown strings alone.
"google" becomes "gemini" because the Gemini descriptor (llm4s-gemini) declares that alias. An id no provider claims is returned canonicalised but unchanged — parsing config must not depend on what happens to be on the classpath; only resolution does.
The registered descriptor for id, or an error naming what is registered.
The registered descriptor for id, or an error naming what is registered.
The error carries the discovery summary as well as the registered ids, because the two most common causes of a missing provider are invisible otherwise: a dependency that was never added, and a fat jar whose META-INF/services entries were dropped or overwritten during shading.
Value parameters
configPath
where the id came from, e.g. "llm4s.providers.my-bedrock.provider". Included in the error so the user knows which entry to fix.
The registered embedding descriptor for id, or an error naming what is registered.
The registered embedding descriptor for id, or an error naming what is registered.
The error names the '''embedding''' providers specifically. A user who set EMBEDDING_MODEL=anthropic/... needs to be told that Anthropic supplies no embedding provider, not handed the chat list and left to infer it.
Value parameters
configPath
where the id came from, e.g. "llm4s.embeddings.model".
This registry plus descriptor; a later registration of the same id wins.
This registry plus descriptor; a later registration of the same id wins.
This is the escape hatch for a classpath where discovery cannot work — a shaded fat jar, most often. The discovery report is carried through unchanged, so the diagnostics still say what the scan saw.