Packages
llm_db
2026.4.3
2026.7.3
2026.7.2
2026.7.1
2026.7.0
2026.6.4
2026.6.3
2026.6.2
2026.6.1
2026.6.0
2026.5.2
2026.5.1
2026.5.0
2026.4.8
2026.4.7
2026.4.6
2026.4.5
2026.4.4
2026.4.3
2026.4.2
2026.4.1
2026.4.0
2026.3.3
2026.3.2
2026.3.1
2026.3.0
2026.2.9
2026.2.8
2026.2.7
2026.2.6
2026.2.5
2026.2.4
2026.2.3
2026.2.2
2026.2.1
2026.2.0
2026.1.5
2026.1.4
2026.1.3
2026.1.2
2026.1.1
2026.1.0
2025.12.4
2025.12.3
2025.12.2
2025.12.1
2025.11.18-preview
2025.11.14-preview
2025.11.7-preview
LLM model metadata catalog with fast, capability-aware lookups.
Current section
Files
Jump to
Current section
Files
lib/llm_db/provider.ex
defmodule LLMDB.Provider do
@moduledoc """
Provider struct with Zoi schema validation.
Represents an LLM provider with metadata including identity, base URL,
environment variables, documentation, and pricing defaults.
## Fields
- `:id` - Unique provider identifier atom (e.g., `:openai`)
- `:name` - Display name
- `:base_url` - Base API URL (supports template variables like `{region}`)
- `:env` - List of environment variable names for credentials
- `:config_schema` - Runtime configuration field definitions
- `:doc` - Documentation URL
- `:pricing_defaults` - Default pricing components applied to all models (see below)
- `:exclude_models` - Model IDs to exclude from upstream sources
- `:extra` - Additional provider-specific data
- `:alias_of` - Primary provider ID if this is an alias
## Pricing Defaults
The `:pricing_defaults` field defines default pricing for tools and features
that apply to all models from this provider. This avoids duplicating tool
pricing across every model definition.
%{
currency: "USD",
components: [
%{id: "tool.web_search", kind: "tool", tool: "web_search", unit: "call", per: 1000, rate: 10.0},
%{id: "storage.vectors", kind: "storage", unit: "gb_day", per: 1, rate: 0.10}
]
}
Provider defaults are merged with model-specific pricing at load time.
See `LLMDB.Pricing` and the [Pricing and Billing guide](pricing-and-billing.md).
"""
@config_field_schema Zoi.object(%{
name: Zoi.string(),
type: Zoi.string(),
required: Zoi.boolean() |> Zoi.default(false),
default: Zoi.any() |> Zoi.nullish(),
doc: Zoi.string() |> Zoi.nullish()
})
@pricing_component_schema Zoi.object(%{
id: Zoi.string(),
kind:
Zoi.enum([
"token",
"tool",
"image",
"storage",
"request",
"other"
])
|> Zoi.nullish(),
unit:
Zoi.enum([
"token",
"call",
"query",
"session",
"gb_day",
"image",
"source",
"other"
])
|> Zoi.nullish(),
per: Zoi.integer() |> Zoi.min(1) |> Zoi.nullish(),
rate: Zoi.number() |> Zoi.nullish(),
meter: Zoi.string() |> Zoi.nullish(),
tool: Zoi.union([Zoi.atom(), Zoi.string()]) |> Zoi.nullish(),
size_class: Zoi.string() |> Zoi.nullish(),
notes: Zoi.string() |> Zoi.nullish()
})
@pricing_defaults_schema Zoi.object(%{
currency: Zoi.string() |> Zoi.nullish(),
components: Zoi.array(@pricing_component_schema) |> Zoi.default([])
})
@runtime_auth_header_schema Zoi.object(%{
name: Zoi.string(),
env: Zoi.string() |> Zoi.nullish(),
value: Zoi.string() |> Zoi.nullish()
})
@runtime_auth_schema Zoi.object(%{
type:
Zoi.enum(["bearer", "x_api_key", "header", "query", "multi_header"])
|> Zoi.nullish(),
env: Zoi.array(Zoi.string()) |> Zoi.default([]),
header_name: Zoi.string() |> Zoi.nullish(),
query_name: Zoi.string() |> Zoi.nullish(),
headers: Zoi.array(@runtime_auth_header_schema) |> Zoi.default([])
})
@runtime_schema Zoi.object(%{
base_url: Zoi.string() |> Zoi.nullish(),
auth: @runtime_auth_schema |> Zoi.nullish(),
default_headers: Zoi.map() |> Zoi.default(%{}),
default_query: Zoi.map() |> Zoi.default(%{}),
config_schema: Zoi.array(@config_field_schema) |> Zoi.nullish(),
doc_url: Zoi.string() |> Zoi.nullish()
})
@schema Zoi.struct(
__MODULE__,
%{
id: Zoi.atom(),
name: Zoi.string() |> Zoi.nullish(),
base_url: Zoi.string() |> Zoi.nullish(),
env: Zoi.array(Zoi.string()) |> Zoi.nullish(),
config_schema: Zoi.array(@config_field_schema) |> Zoi.nullish(),
doc: Zoi.string() |> Zoi.nullish(),
exclude_models: Zoi.array(Zoi.string()) |> Zoi.default([]) |> Zoi.nullish(),
pricing_defaults: @pricing_defaults_schema |> Zoi.nullish(),
runtime: @runtime_schema |> Zoi.nullish(),
catalog_only: Zoi.boolean() |> Zoi.default(false),
extra: Zoi.map() |> Zoi.nullish(),
alias_of: Zoi.atom() |> Zoi.nullish()
},
coerce: true
)
@type t :: unquote(Zoi.type_spec(@schema))
@enforce_keys Zoi.Struct.enforce_keys(@schema)
defstruct Zoi.Struct.struct_fields(@schema)
@doc "Returns the Zoi schema for Provider"
def schema, do: @schema
@doc """
Creates a new Provider struct from a map, validating with Zoi schema.
## Examples
iex> LLMDB.Provider.new(%{id: :openai, name: "OpenAI"})
{:ok, %LLMDB.Provider{id: :openai, name: "OpenAI"}}
iex> LLMDB.Provider.new(%{})
{:error, _validation_errors}
"""
@spec new(map()) :: {:ok, t()} | {:error, term()}
def new(attrs) when is_map(attrs) do
attrs =
attrs
|> normalize_runtime_attrs()
Zoi.parse(@schema, attrs)
end
@doc """
Creates a new Provider struct from a map, raising on validation errors.
## Examples
iex> LLMDB.Provider.new!(%{id: :openai, name: "OpenAI"})
%LLMDB.Provider{id: :openai, name: "OpenAI"}
"""
@spec new!(map()) :: t()
def new!(attrs) when is_map(attrs) do
case new(attrs) do
{:ok, provider} -> provider
{:error, reason} -> raise ArgumentError, "Invalid provider: #{inspect(reason)}"
end
end
defp normalize_runtime_attrs(attrs) do
update_in_nested_map(attrs, :runtime, fn runtime ->
update_in_nested_map(runtime, :auth, fn auth ->
normalize_string_enum(auth, :type)
end)
end)
end
defp update_in_nested_map(attrs, key, fun) when is_map(attrs) do
case Map.get(attrs, key) || Map.get(attrs, to_string(key)) do
nested when is_map(nested) ->
normalized = fun.(nested)
attrs
|> Map.put(key, normalized)
|> Map.put(to_string(key), normalized)
_other ->
attrs
end
end
defp normalize_string_enum(map, key) when is_map(map) do
value = Map.get(map, key) || Map.get(map, to_string(key))
normalized =
case value do
atom when is_atom(atom) -> Atom.to_string(atom)
other -> other
end
map
|> Map.put(key, normalized)
|> Map.put(to_string(key), normalized)
end
end
defimpl DeepMerge.Resolver, for: LLMDB.Provider do
@moduledoc false
def resolve(original, override = %LLMDB.Provider{}, resolver) do
cleaned_override =
override
|> Map.from_struct()
|> Enum.reject(fn {_key, value} -> is_nil(value) end)
|> Map.new()
Map.merge(original, cleaned_override, resolver)
end
def resolve(original, override, resolver) when is_map(override) do
Map.merge(original, override, resolver)
end
def resolve(_original, override, _resolver), do: override
end