Canonical "provider:model" spec parsing and resolution.
This module provides functions to parse and resolve model specifications in various formats, including "provider:model" strings, "model@provider" strings (filename-safe), tuples, and bare model IDs with provider scope.
String Formats
Two string formats are supported:
"provider:model"- Traditional colon separator (default)"model@provider"- Email-like format, filesystem-safe for filenames
Both formats parse to the same internal representation and can be used interchangeably. The @ format is recommended when model specs are used in filenames, CI artifact names, or other filesystem contexts.
Amazon Bedrock Inference Profiles
For Amazon Bedrock models, inference profile IDs with region prefixes (us., eu., ap., apac., ca., au., jp., us-gov., global.) are supported. A regional catalog entry supplies its own metadata and pricing. If no regional entry exists, lookup can use an alias or the base model, while preserving the region prefix in the returned model ID. For example:
iex> LLMDB.Spec.resolve("amazon_bedrock:us.anthropic.claude-opus-4-1-20250805-v1:0")
{:ok, {:amazon_bedrock, "us.anthropic.claude-opus-4-1-20250805-v1:0", %LLMDB.Model{}}}When the catalog has only the base entry "anthropic.claude-opus-4-1-20250805-v1:0", the lookup uses its metadata, but the returned model ID retains the "us." prefix for API routing purposes.
Summary
Functions
Builds a model specification string from various inputs.
Formats a model specification as a string.
Normalizes a model specification to tuple format.
Parses and validates a provider identifier.
Parses a model specification string in either "provider:model" or "model@provider" format.
Parses a model specification string, raising on error.
Resolves a model specification to a canonical model record.
Splits a declared provider prefix from a model ID.
Functions
Builds a model specification string from various inputs.
Accepts strings (in any supported format) or tuples and outputs a string in the desired format.
Parameters
input- Model spec as string or tupleopts- Keyword list with optional:formatfor output format
Examples
iex> LLMDB.Spec.build_spec("openai:gpt-4", format: :filename_safe)
"gpt-4@openai"
iex> LLMDB.Spec.build_spec({:openai, "gpt-4"}, format: :model_at_provider)
"gpt-4@openai"
Formats a model specification as a string.
Converts a {provider, model_id} tuple to string format. The output format can be
controlled via the format parameter or falls back to the application config
:llm_db, :model_spec_format (default: :provider_colon_model).
Parameters
spec- {provider_atom, model_id} tupleformat- Optional format override (atom)
Supported Formats
:provider_colon_model- "provider:model" (default):model_at_provider- "model@provider" (filename-safe):filename_safe- alias for:model_at_provider
Examples
iex> LLMDB.Spec.format_spec({:openai, "gpt-4"})
"openai:gpt-4"
iex> LLMDB.Spec.format_spec({:openai, "gpt-4"}, :model_at_provider)
"gpt-4@openai"
iex> LLMDB.Spec.format_spec({:openai, "gpt-4o-mini"}, :filename_safe)
"gpt-4o-mini@openai"
Normalizes a model specification to tuple format.
Accepts either a string (in any supported format) or a tuple and returns a normalized {provider, model_id} tuple.
Examples
iex> LLMDB.Spec.normalize_spec("openai:gpt-4")
{:openai, "gpt-4"}
iex> LLMDB.Spec.normalize_spec("gpt-4@openai")
{:openai, "gpt-4"}
iex> LLMDB.Spec.normalize_spec({:openai, "gpt-4"})
{:openai, "gpt-4"}
@spec parse_provider(atom() | binary()) :: {:ok, atom()} | {:error, :unknown_provider | :bad_provider}
Parses and validates a provider identifier.
Accepts atom or binary input, normalizes to atom, and verifies the provider exists in the current catalog.
Parameters
input- Provider identifier as atom or binary
Returns
{:ok, atom}- Normalized provider atom if valid and exists in catalog{:error, :unknown_provider}- Provider not found in catalog{:error, :bad_provider}- Invalid provider format
Examples
iex> LLMDB.Spec.parse_provider(:openai)
{:ok, :openai}
iex> LLMDB.Spec.parse_provider("google-vertex")
{:ok, :google_vertex}
iex> LLMDB.Spec.parse_provider("nonexistent")
{:error, :unknown_provider}
@spec parse_spec(String.t() | {atom() | String.t(), String.t()}, keyword()) :: {:ok, {atom(), String.t()}} | {:error, :invalid_format | :ambiguous_format | :unknown_provider | :bad_provider | :invalid_chars | :empty_segment}
Parses a model specification string in either "provider:model" or "model@provider" format.
Automatically detects the format based on separators present. Validates the provider exists in the catalog and checks for reserved characters in segments.
Parameters
spec- String in "provider:model" or "model@provider" format, or a{provider, model_id}tuple whose provider is an atom or stringopts- Keyword list with optional:formatto explicitly specify format
Options
:format- Explicitly specify the format as:colonor:at. Required when both separators present.
Returns
{:ok, {provider_atom, model_id}}- Parsed and normalized spec{:error, :invalid_format}- No valid separator found{:error, :ambiguous_format}- Both separators present without explicit format{:error, :unknown_provider}- Provider not found in catalog{:error, :bad_provider}- Invalid provider format{:error, :invalid_chars}- Reserved characters in provider or model segments{:error, :empty_segment}- Provider or model segment is empty
Examples
iex> LLMDB.Spec.parse_spec("openai:gpt-4")
{:ok, {:openai, "gpt-4"}}
iex> LLMDB.Spec.parse_spec("gpt-4@openai")
{:ok, {:openai, "gpt-4"}}
iex> LLMDB.Spec.parse_spec("google-vertex:gemini-pro")
{:ok, {:google_vertex, "gemini-pro"}}
iex> LLMDB.Spec.parse_spec("provider:model@ambiguous", format: :colon)
{:ok, {:provider, "model@ambiguous"}}
iex> LLMDB.Spec.parse_spec("gpt-4")
{:error, :invalid_format}
Parses a model specification string, raising on error.
Same as parse_spec/2 but raises ArgumentError instead of returning error tuple.
Examples
iex> LLMDB.Spec.parse_spec!("openai:gpt-4")
{:openai, "gpt-4"}
iex> LLMDB.Spec.parse_spec!("gpt-4@openai")
{:openai, "gpt-4"}
@spec resolve(String.t() | {atom() | String.t(), String.t()}, keyword()) :: {:ok, {atom(), String.t(), LLMDB.Model.t()}} | {:error, term()}
Resolves a model specification to a canonical model record.
Accepts multiple input formats:
- "provider:model" string
- "model@provider" string
- {provider, model_id} tuple with an atom or string provider
- Bare "model" string with opts[:scope] = provider_atom
Handles alias resolution and validates the model exists in the catalog.
Parameters
input- Model specification in one of the supported formatsopts- Keyword list with optional:scopefor bare model resolution
Returns
{:ok, {provider, canonical_id, Model.t()}}- Resolved model{:error, :not_found}- Model doesn't exist{:error, :ambiguous}- Bare model ID exists under multiple providers without scope{:error, :invalid_format}- Malformed input{:error, term}- Other parsing errors
Examples
iex> LLMDB.Spec.resolve("openai:gpt-4")
{:ok, {:openai, "gpt-4", %LLMDB.Model{}}}
iex> LLMDB.Spec.resolve({:openai, "gpt-4"})
{:ok, {:openai, "gpt-4", %LLMDB.Model{}}}
iex> LLMDB.Spec.resolve("gpt-4@openai")
{:ok, {:openai, "gpt-4", %LLMDB.Model{}}}
iex> LLMDB.Spec.resolve("gpt-4", scope: :openai)
{:ok, {:openai, "gpt-4", %LLMDB.Model{}}}
iex> LLMDB.Spec.resolve("gpt-4")
{:error, :ambiguous}
Splits a declared provider prefix from a model ID.
Uses the provider's extra.model_id_prefixes metadata from the loaded catalog.
Amazon Bedrock retains its compatibility defaults when no override is declared.
Returns {base_id, prefix} using the longest matching prefix. If the provider
has no prefix rules or none matches, returns {model_id, nil} unchanged.