Packages
llm_db
2026.4.6
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/runtime.ex
defmodule LLMDB.Runtime do
@moduledoc """
Runtime configuration compilation for consumer applications.
Phase 2 of LLMDB: Compile runtime configuration by merging application
environment config with per-call options, enabling consumers to:
- Filter models by provider/model patterns (allow/deny)
- Define provider preferences
- Add custom providers/models
This module handles the consumer-facing runtime configuration that gets
applied when loading the packaged snapshot into the Store.
## Example
# Compile runtime config from app env + per-call opts
runtime = LLMDB.Runtime.compile(
allow: [:openai, :anthropic],
custom: %{
providers: [%{id: :myprov, name: "My Provider"}],
models: [%{provider: :myprov, id: "my-model", capabilities: %{chat: true}}]
}
)
# Runtime config can then be used to filter and customize the catalog
"""
alias LLMDB.Config
require Logger
@doc """
Compiles runtime configuration by merging app env and per-call options.
Merges application environment configuration (from `config :llm_db, ...`) with
options passed at load time, normalizes the configuration, and compiles filters.
## Parameters
- `opts` - Keyword list of per-call options that override app env:
- `:allow` - `:all`, list of providers `[:openai]`, or map `%{openai: :all | [patterns]}`
- `:deny` - List of providers `[:provider]` or map `%{provider: [patterns]}`
- `:prefer` - List of provider atoms in preference order
- `:custom` - Map with provider IDs as keys, provider configs (with models) as values
- `:provider_ids` - Optional list of known provider IDs for validation
## Returns
Map with compiled runtime configuration:
- `:filters` - Compiled allow/deny patterns
- `:prefer` - Provider preference list
- `:custom` - Normalized custom providers/models (%{providers: [...], models: [...]})
- `:unknown` - List of unknown providers in filters (for warnings)
## Examples
# Simple provider allow list
runtime = Runtime.compile(allow: [:openai, :anthropic])
runtime.filters.allow
#=> %{openai: :all, anthropic: :all}
# Provider allow list with model patterns
runtime = Runtime.compile(
allow: %{openai: ["gpt-4*"], anthropic: :all},
deny: %{openai: ["gpt-4-0613"]}
)
# With custom providers
runtime = Runtime.compile(
custom: %{
vllm: [
name: "Local vLLM Provider",
models: %{
"llama-3" => %{capabilities: %{chat: true}}
}
]
}
)
"""
@spec compile(keyword()) :: map()
def compile(opts \\ []) do
# Get base config from app env
base = Config.get()
# Normalize and merge options
allow = normalize_allow(Keyword.get(opts, :allow, base.allow))
deny = normalize_deny(Keyword.get(opts, :deny, base.deny))
prefer = Keyword.get(opts, :prefer, base.prefer) || []
custom = normalize_custom(Keyword.get(opts, :custom, base.custom))
provider_ids = Keyword.get(opts, :provider_ids)
# Compile filters (deferred if provider_ids not provided)
{filters, unknown: unknown} =
if provider_ids do
Config.compile_filters(allow, deny, provider_ids)
else
# Compile without validation, will recompile later with known providers
Config.compile_filters(allow, deny, nil)
end
%{
filters: filters,
prefer: prefer,
custom: custom,
unknown: unknown,
# Keep raw patterns for digest calculation
raw_allow: allow,
raw_deny: deny
}
end
@doc """
Applies runtime overrides to an existing snapshot.
## Parameters
- `snapshot` - The current snapshot map
- `overrides` - Map with optional `:filter` and `:prefer` keys
## Override Options
- `:filter` - %{allow: patterns, deny: patterns} to recompile and reapply
- `:prefer` - List of provider atoms to update preference order
## Returns
- `{:ok, updated_snapshot}` - Success with updated snapshot
- `{:error, reason}` - Validation or processing error
"""
@spec apply(map(), map() | nil) :: {:ok, map()} | {:error, term()}
def apply(snapshot, overrides) when is_map(snapshot) do
case validate_and_prepare_overrides(overrides) do
{:ok, prepared} ->
apply_overrides(snapshot, prepared)
{:error, reason} ->
{:error, reason}
end
end
# Private helpers
# Normalize :allow from various formats to canonical form
defp normalize_allow(:all), do: :all
defp normalize_allow(allow) when is_list(allow) do
# Convert [:openai, :anthropic] to %{openai: :all, anthropic: :all}
Map.new(allow, fn provider -> {provider, :all} end)
end
defp normalize_allow(allow) when is_map(allow), do: allow
defp normalize_allow(nil), do: :all
# Normalize :deny from various formats to canonical form
defp normalize_deny(deny) when is_list(deny) do
# Convert [:openai] to %{openai: :all}
Map.new(deny, fn provider -> {provider, :all} end)
end
defp normalize_deny(deny) when is_map(deny), do: deny
defp normalize_deny(nil), do: %{}
# Normalize custom overlay from new format to internal format
# New format: %{provider_id: [name: "...", models: %{id => config}]}
# Internal format: %{providers: [...], models: [...]}
defp normalize_custom(custom) when is_map(custom) and map_size(custom) > 0 do
{providers, models} =
Enum.reduce(custom, {[], []}, fn {provider_id, provider_config},
{acc_providers, acc_models} ->
# Normalize provider_id to atom
provider_atom =
case provider_id do
id when is_atom(id) -> id
id when is_binary(id) -> String.to_atom(id)
end
# Extract provider fields (only include non-nil values)
provider_map =
%{id: provider_atom}
|> maybe_put(:name, Keyword.get(provider_config, :name))
|> maybe_put(:base_url, Keyword.get(provider_config, :base_url))
|> maybe_put(:env, Keyword.get(provider_config, :env))
|> maybe_put(:config_schema, Keyword.get(provider_config, :config_schema))
|> maybe_put(:doc, Keyword.get(provider_config, :doc))
|> maybe_put(:pricing_defaults, Keyword.get(provider_config, :pricing_defaults))
|> maybe_put(:extra, Keyword.get(provider_config, :extra))
# Extract models
provider_models =
case Keyword.get(provider_config, :models) do
models when is_map(models) ->
Enum.map(models, fn {model_id, model_config} ->
Map.merge(model_config, %{
id: model_id,
provider: provider_atom
})
end)
_ ->
[]
end
{[provider_map | acc_providers], provider_models ++ acc_models}
end)
%{providers: Enum.reverse(providers), models: Enum.reverse(models)}
end
defp normalize_custom(_), do: %{providers: [], models: []}
# Helper to conditionally add non-nil values to a map
defp maybe_put(map, _key, nil), do: map
defp maybe_put(map, key, value), do: Map.put(map, key, value)
defp validate_and_prepare_overrides(nil), do: {:ok, %{}}
defp validate_and_prepare_overrides(overrides) when overrides == %{}, do: {:ok, %{}}
defp validate_and_prepare_overrides(overrides) when is_map(overrides) do
with :ok <- validate_filter(overrides[:filter]),
:ok <- validate_prefer(overrides[:prefer]) do
{:ok, overrides}
end
end
defp validate_filter(nil), do: :ok
defp validate_filter(%{} = filter) when map_size(filter) == 0, do: :ok
defp validate_filter(%{allow: allow, deny: deny}) do
allow_ok = allow in [:all, nil] or is_map(allow)
deny_ok = deny == nil or is_map(deny)
if allow_ok and deny_ok do
:ok
else
{:error, "filter.allow must be :all or map; filter.deny must be map"}
end
end
defp validate_filter(_), do: {:error, "filter must be %{allow: ..., deny: ...}"}
defp validate_prefer(nil), do: :ok
defp validate_prefer([]), do: :ok
defp validate_prefer(prefer) when is_list(prefer) do
if Enum.all?(prefer, &is_atom/1) do
:ok
else
{:error, "prefer must be a list of atoms"}
end
end
defp validate_prefer(_), do: {:error, "prefer must be a list of atoms"}
defp apply_overrides(snapshot, overrides) do
snapshot
|> maybe_update_filter(overrides[:filter])
|> maybe_update_prefer(overrides[:prefer])
|> wrap_ok()
end
defp maybe_update_filter(snapshot, nil), do: {:ok, snapshot}
defp maybe_update_filter(snapshot, filter) when map_size(filter) == 0, do: {:ok, snapshot}
defp maybe_update_filter(snapshot, filter) do
alias LLMDB.{Config, Engine}
require Logger
# Get known provider IDs for validation
provider_ids = Map.keys(snapshot.providers_by_id)
# Compile filters with provider validation
{compiled_filters, unknown: unknown_providers} =
Config.compile_filters(
Map.get(filter, :allow, :all),
Map.get(filter, :deny, %{}),
provider_ids
)
# Warn on unknown providers in runtime overrides
if unknown_providers != [] do
provider_ids_set = MapSet.new(provider_ids)
Logger.warning(
"llm_db: unknown provider(s) in runtime filter: #{inspect(unknown_providers)}. " <>
"Known providers: #{inspect(MapSet.to_list(provider_ids_set))}. " <>
"Check spelling or remove unknown providers from runtime overrides."
)
end
# Use base_models to enable filter widening, fall back to current models
all_models = Map.get(snapshot, :base_models, Map.values(snapshot.models) |> List.flatten())
filtered_models = Engine.apply_filters(all_models, compiled_filters)
# Fail fast if filters eliminate all models - return error instead of raise
if compiled_filters.allow != :all and filtered_models == [] do
allow_summary = summarize_runtime_filter(Map.get(filter, :allow, :all))
deny_summary = summarize_runtime_filter(Map.get(filter, :deny, %{}))
{:error,
"llm_db: runtime filters eliminated all models " <>
"(allow: #{allow_summary}, deny: #{deny_summary}). " <>
"Use allow: :all to widen filters or remove deny patterns."}
else
updated_snapshot = %{
snapshot
| filters: compiled_filters,
models_by_key: index_models(filtered_models),
models: Enum.group_by(filtered_models, & &1.provider),
aliases_by_key: index_aliases(filtered_models)
}
{:ok, updated_snapshot}
end
end
defp index_models(models), do: Map.new(models, &{{&1.provider, &1.id}, &1})
defp index_aliases(models) do
models
|> Enum.flat_map(fn model ->
provider = model.provider
canonical_id = model.id
aliases = Map.get(model, :aliases, [])
Enum.map(aliases, fn alias_name ->
{{provider, alias_name}, canonical_id}
end)
end)
|> Map.new()
end
defp maybe_update_prefer({:ok, snapshot}, nil), do: {:ok, snapshot}
defp maybe_update_prefer({:ok, snapshot}, []), do: {:ok, snapshot}
defp maybe_update_prefer({:ok, snapshot}, prefer) when is_list(prefer) do
{:ok, %{snapshot | prefer: prefer}}
end
defp maybe_update_prefer({:error, _} = error, _prefer), do: error
defp wrap_ok({:ok, _} = result), do: result
defp wrap_ok({:error, _} = error), do: error
defp summarize_runtime_filter(:all), do: ":all"
defp summarize_runtime_filter(filter) when is_map(filter) and map_size(filter) == 0 do
"%{}"
end
defp summarize_runtime_filter(filter) when is_map(filter) do
# Summarize large filter maps to avoid huge error messages
keys = Map.keys(filter) |> Enum.take(5)
if map_size(filter) > 5 do
"#{inspect(keys)} ... (#{map_size(filter)} providers total)"
else
inspect(filter)
end
end
defp summarize_runtime_filter(other), do: inspect(other)
end