Packages
llm_db
2026.2.4
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/mix/tasks/llm_db.build.ex
defmodule Mix.Tasks.LlmDb.Build do
use Mix.Task
@shortdoc "Build snapshot.json from sources using the ETL pipeline"
@moduledoc """
Builds snapshot.json from configured sources using the Engine ETL pipeline.
Runs the complete ETL pipeline (Ingest → Normalize → Validate → Merge →
Enrich → Filter → Index) on configured sources to generate a fresh
snapshot.json file.
## Usage
mix llm_db.build
## Configuration
Configure sources in your application config:
config :llm_db,
sources: [
{LLMDB.Sources.Packaged, %{}},
{LLMDB.Sources.ModelsDev, %{url: "https://models.dev/api.json"}},
{LLMDB.Sources.JSONFile, %{paths: ["priv/custom.json"]}}
],
allow: :all,
deny: %{},
prefer: [:openai, :anthropic]
"""
@manifest_path "priv/llm_db/manifest.json"
@providers_dir "priv/llm_db/providers"
@impl Mix.Task
def run(_args) do
ensure_llm_db_project!()
Mix.Task.run("app.start")
Mix.shell().info("Building snapshot from configured sources...\n")
{:ok, snapshot} = build_snapshot()
save_snapshot(snapshot)
print_summary(snapshot)
end
defp build_snapshot do
config = LLMDB.Config.get()
sources = LLMDB.Config.sources!()
if sources == [] do
Mix.shell().info("Warning: No sources configured - snapshot will be empty\n")
end
LLMDB.Engine.run(
sources: sources,
allow: config.allow,
deny: config.deny,
prefer: config.prefer
)
end
defp save_snapshot(snapshot) do
# Create providers directory
File.mkdir_p!(@providers_dir)
# Write each provider to its own file
provider_ids =
snapshot.providers
|> Enum.map(fn {provider_id, provider_data} ->
filename = "#{provider_id}.json"
path = Path.join(@providers_dir, filename)
provider_output = map_with_string_keys(provider_data)
json = Jason.encode!(provider_output, pretty: true)
File.write!(path, json)
Atom.to_string(provider_id)
end)
|> Enum.sort()
# Write manifest with metadata
File.mkdir_p!(Path.dirname(@manifest_path))
manifest = %{
"version" => snapshot.version,
"generated_at" => snapshot.generated_at,
"providers" => provider_ids
}
json = Jason.encode!(manifest, pretty: true)
File.write!(@manifest_path, json)
Mix.shell().info("✓ Manifest written to #{@manifest_path} (v#{snapshot.version})")
Mix.shell().info("✓ #{length(provider_ids)} provider files written to #{@providers_dir}/")
# Generate ValidProviders module from normalized snapshot data
generate_valid_providers(snapshot)
end
defp print_summary(snapshot) do
provider_count = map_size(snapshot.providers)
model_count =
snapshot.providers
|> Map.values()
|> Enum.map(fn provider -> map_size(provider.models) end)
|> Enum.sum()
Mix.shell().info("")
Mix.shell().info("Summary:")
Mix.shell().info(" Providers: #{provider_count}")
Mix.shell().info(" Models: #{model_count}")
end
defp map_with_string_keys(map) when is_map(map) do
# Convert struct to plain map first
plain_map =
if Map.has_key?(map, :__struct__) do
Map.from_struct(map)
else
map
end
# Convert to sorted keyword list for deterministic JSON output
plain_map
|> Enum.map(fn
{k, v} when is_atom(k) -> {Atom.to_string(k), map_with_string_keys(v)}
{k, v} -> {to_string(k), map_with_string_keys(v)}
end)
|> Enum.sort_by(fn {k, _v} -> k end)
|> Jason.OrderedObject.new()
end
defp map_with_string_keys(list) when is_list(list) do
Enum.map(list, &map_with_string_keys/1)
end
defp map_with_string_keys(value), do: value
# Generate ValidProviders module from normalized snapshot data
defp generate_valid_providers(snapshot) do
# Extract provider atoms from nested providers map
provider_atoms =
snapshot.providers
|> Map.keys()
|> Enum.sort()
|> Enum.uniq()
write_valid_providers_module(provider_atoms)
Mix.shell().info("✓ Generated valid_providers.ex with #{length(provider_atoms)} providers")
end
# Write the ValidProviders module to disk
defp write_valid_providers_module(provider_atoms) do
module_code = """
defmodule LLMDB.Generated.ValidProviders do
@moduledoc \"\"\"
Auto-generated module containing all valid provider atoms.
This module is generated by `mix llm_db.build` to prevent atom leaking.
By pre-generating all provider atoms at build time, we ensure that runtime
code can only use existing atoms via `String.to_existing_atom/1`.
DO NOT EDIT THIS FILE MANUALLY - it will be overwritten.
\"\"\"
@providers #{inspect(provider_atoms, limit: :infinity)}
@doc \"\"\"
Returns the list of all valid provider atoms.
\"\"\"
@spec list() :: [atom()]
def list, do: @providers
@doc \"\"\"
Checks if the given atom is a valid provider.
\"\"\"
@spec member?(atom()) :: boolean()
def member?(atom), do: atom in @providers
end
"""
module_path = "lib/llm_db/generated/valid_providers.ex"
File.mkdir_p!(Path.dirname(module_path))
formatted = Code.format_string!(module_code) |> IO.iodata_to_binary()
# Ensure file ends with newline (Elixir convention)
content = if String.ends_with?(formatted, "\n"), do: formatted, else: formatted <> "\n"
File.write!(module_path, content)
end
defp ensure_llm_db_project! do
app = Mix.Project.config()[:app]
if app != :llm_db do
Mix.raise("""
mix llm_db.build can only be run inside the llm_db project itself.
This task generates lib/llm_db/generated/valid_providers.ex. Running it from
a downstream application would create a duplicate LLMDB.Generated.ValidProviders
module that conflicts with the one shipped in the :llm_db Hex package.
If you need to regenerate the snapshot (maintainers only):
cd path/to/llm_db
mix llm_db.build
For downstream applications, use the data and modules shipped with :llm_db.
""")
end
end
end