Packages
llm_db
2026.7.1
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/snapshot/release_store.ex
defmodule LLMDB.Snapshot.ReleaseStore do
@moduledoc """
GitHub Releases-backed snapshot artifact store.
Runtime fetching resolves immutable snapshot and history releases via the
GitHub Releases API and downloads public assets via Req. Publishing is
handled with the `gh` CLI, intended for local maintainer workflows and
GitHub Actions.
"""
alias LLMDB.Snapshot
@default_repo "agentjido/llm_db"
@default_index_tag "catalog-index"
@default_cache_dir Path.join(["tmp", "llm_db", "snapshot_cache"])
@github_api_version "2022-11-28"
@release_page_size 100
@max_release_pages 10
@type config :: %{
repo: String.t(),
index_tag: String.t(),
cache_dir: String.t()
}
@spec config(keyword() | map()) :: config()
def config(overrides \\ []) do
app_config =
Application.get_env(:llm_db, :snapshot_store, [])
|> Enum.into(%{})
override_map =
cond do
is_map(overrides) -> overrides
Keyword.keyword?(overrides) -> Enum.into(overrides, %{})
true -> %{}
end
merged = Map.merge(app_config, override_map)
%{
repo: Map.get(merged, :repo, Map.get(merged, "repo", @default_repo)),
index_tag: Map.get(merged, :index_tag, Map.get(merged, "index_tag", @default_index_tag)),
cache_dir:
Map.get(merged, :cache_dir, Map.get(merged, "cache_dir", @default_cache_dir))
|> expand_path()
}
end
@spec snapshot_tag(String.t()) :: String.t()
def snapshot_tag(snapshot_id), do: release_tag("snapshot", snapshot_id)
@spec history_tag(String.t()) :: String.t()
def history_tag(snapshot_id), do: release_tag("history", snapshot_id)
@spec asset_url(String.t(), String.t(), keyword() | map()) :: String.t()
def asset_url(tag, filename, overrides \\ %{}) do
cfg = config(overrides)
release_asset_url(cfg.repo, tag, filename)
end
@spec snapshot_asset_url(String.t(), keyword() | map()) :: String.t() | nil
def snapshot_asset_url(snapshot_id, overrides \\ %{}) do
case find_snapshot_entry(snapshot_id, overrides) do
{:ok, entry} -> entry["snapshot_url"]
_ -> nil
end
end
@spec snapshot_meta_asset_url(String.t(), keyword() | map()) :: String.t() | nil
def snapshot_meta_asset_url(snapshot_id, overrides \\ %{}) do
case find_snapshot_entry(snapshot_id, overrides) do
{:ok, entry} -> entry["snapshot_meta_url"]
_ -> nil
end
end
@spec history_archive_asset_url(String.t(), keyword() | map()) :: String.t() | nil
def history_archive_asset_url(snapshot_id, overrides \\ %{}) do
case find_history_entry(snapshot_id, overrides) do
{:ok, entry} -> entry["history_url"]
_ -> nil
end
end
@spec history_meta_asset_url(String.t(), keyword() | map()) :: String.t() | nil
def history_meta_asset_url(snapshot_id, overrides \\ %{}) do
case find_history_entry(snapshot_id, overrides) do
{:ok, entry} -> entry["history_meta_url"]
_ -> nil
end
end
@spec fetch_latest(keyword() | map()) :: {:ok, map()} | {:error, term()}
def fetch_latest(overrides \\ %{}) do
with {:ok, snapshots} <- fetch_snapshot_index(overrides),
latest when is_map(latest) <- List.last(snapshots) do
{:ok, latest}
else
nil -> {:error, :not_found}
error -> error
end
end
@spec fetch_snapshot_index(keyword() | map()) :: {:ok, [map()]} | {:error, term()}
def fetch_snapshot_index(overrides \\ %{}) do
with {:ok, releases} <- list_releases(config(overrides).repo) do
releases
|> Enum.filter(&snapshot_release?/1)
|> build_entries(&snapshot_entry_from_release/1)
|> case do
{:ok, entries} ->
sorted =
entries
|> sort_by_identity(&snapshot_identity/1)
|> dedupe_by(& &1["snapshot_id"])
|> sort_by_identity(&snapshot_identity/1)
{:ok, sorted}
error ->
error
end
end
end
@spec fetch_history_meta(keyword() | map()) :: {:ok, map()} | {:error, term()}
def fetch_history_meta(overrides \\ %{}) do
with {:ok, latest} <- fetch_latest(overrides),
snapshot_id when is_binary(snapshot_id) <- latest["snapshot_id"],
{:ok, entry} <- find_history_entry(snapshot_id, overrides),
meta_url when is_binary(meta_url) <- entry["history_meta_url"] do
fetch_json(meta_url)
else
nil -> {:error, :not_found}
error -> error
end
end
@spec fetch_snapshot(:latest | String.t(), keyword() | map()) ::
{:ok, %{snapshot: map(), snapshot_id: String.t(), path: String.t()}} | {:error, term()}
def fetch_snapshot(ref, overrides \\ %{})
def fetch_snapshot(:latest, overrides) do
with {:ok, latest} <- fetch_latest(overrides),
snapshot_id when is_binary(snapshot_id) <- latest["snapshot_id"] do
fetch_snapshot(snapshot_id, overrides)
else
{:error, _reason} = error -> error
_ -> {:error, :invalid_latest_snapshot}
end
end
def fetch_snapshot(snapshot_id, overrides) when is_binary(snapshot_id) do
cfg = config(overrides)
path = cached_snapshot_path(snapshot_id, cfg)
with :ok <- File.mkdir_p(Path.dirname(path)),
{:ok, snapshot} <- maybe_read_cached_snapshot(path, snapshot_id) do
{:ok, %{snapshot: snapshot, snapshot_id: snapshot_id, path: path}}
else
{:error, :cache_miss} ->
with {:ok, entry} <- find_snapshot_entry(snapshot_id, overrides),
snapshot_url when is_binary(snapshot_url) <- entry["snapshot_url"],
{:ok, content} <- download(snapshot_url),
{:ok, snapshot} <- Snapshot.decode(content),
^snapshot_id <- snapshot["snapshot_id"] do
File.write!(path, Snapshot.encode(snapshot))
{:ok, %{snapshot: snapshot, snapshot_id: snapshot_id, path: path}}
else
nil ->
{:error, :not_found}
mismatch when is_binary(mismatch) ->
{:error, {:snapshot_id_mismatch, expected: snapshot_id, got: mismatch}}
error ->
error
end
error ->
error
end
end
@spec download_history_archive(String.t(), keyword() | map()) :: :ok | {:error, term()}
def download_history_archive(destination, overrides \\ %{}) when is_binary(destination) do
with {:ok, latest} <- fetch_latest(overrides),
snapshot_id when is_binary(snapshot_id) <- latest["snapshot_id"],
{:ok, entry} <- find_history_entry(snapshot_id, overrides),
history_url when is_binary(history_url) <- entry["history_url"],
{:ok, content} <- download(history_url) do
destination
|> Path.dirname()
|> File.mkdir_p!()
File.write!(destination, content)
:ok
else
nil -> {:error, :not_found}
error -> error
end
end
@spec ensure_snapshot_release(String.t(), String.t(), String.t(), keyword() | map()) ::
{:ok, String.t()} | {:error, term()}
def ensure_snapshot_release(snapshot_path, meta_path, snapshot_id, overrides \\ %{}) do
with :ok <- ensure_gh_available(),
{:ok, asset_paths} <- validate_asset_paths([snapshot_path, meta_path]) do
case find_snapshot_entry(snapshot_id, overrides) do
{:ok, entry} ->
{:ok, entry["tag"]}
{:error, :not_found} ->
create_release(
snapshot_tag(snapshot_id),
config(overrides).repo,
"Snapshot #{snapshot_id}",
asset_paths
)
{:error, reason} ->
{:error, reason}
end
end
end
@spec publish_history_release([String.t()], String.t(), keyword() | map()) ::
{:ok, String.t()} | {:error, term()}
def publish_history_release(asset_paths, snapshot_id, overrides \\ %{})
when is_list(asset_paths) and is_binary(snapshot_id) do
with :ok <- ensure_gh_available(),
{:ok, asset_paths} <- validate_asset_paths(asset_paths) do
case find_history_entry(snapshot_id, overrides) do
{:ok, entry} ->
{:ok, entry["tag"]}
{:error, :not_found} ->
create_release(
history_tag(snapshot_id),
config(overrides).repo,
"History #{snapshot_id}",
asset_paths
)
{:error, reason} ->
{:error, reason}
end
end
end
defp maybe_read_cached_snapshot(path, expected_snapshot_id) do
case Snapshot.read(path) do
{:ok, %{"snapshot_id" => ^expected_snapshot_id} = snapshot} -> {:ok, snapshot}
_ -> {:error, :cache_miss}
end
end
defp cached_snapshot_path(snapshot_id, %{cache_dir: cache_dir}) do
Path.join([cache_dir, "snapshots", "#{snapshot_id}.json"])
end
defp find_snapshot_entry(snapshot_id, overrides) do
with {:ok, entries} <- snapshot_entries(overrides),
%{} = entry <- Enum.find(entries, &(&1["snapshot_id"] == snapshot_id)) do
{:ok, entry}
else
nil -> {:error, :not_found}
error -> error
end
end
defp snapshot_entries(overrides) do
case override_entries(overrides, :snapshot_index) do
{:ok, entries} -> {:ok, entries}
:none -> fetch_snapshot_index(overrides)
end
end
defp find_history_entry(snapshot_id, overrides) do
with {:ok, entries} <- history_entries(overrides),
%{} = entry <- Enum.find(entries, &(&1["to_snapshot_id"] == snapshot_id)) do
{:ok, entry}
else
nil -> {:error, :not_found}
error -> error
end
end
defp history_entries(overrides) do
case override_entries(overrides, :history_entries) do
{:ok, entries} ->
{:ok, entries}
:none ->
with {:ok, releases} <- list_releases(config(overrides).repo) do
releases
|> Enum.filter(&history_release?/1)
|> build_entries(&history_entry_from_release/1)
|> case do
{:ok, entries} ->
sorted =
entries
|> sort_by_identity(&history_identity/1)
|> dedupe_by(& &1["to_snapshot_id"])
|> sort_by_identity(&history_identity/1)
{:ok, sorted}
error ->
error
end
end
end
end
defp override_entries(overrides, key) do
override_map =
cond do
is_map(overrides) -> overrides
Keyword.keyword?(overrides) -> Enum.into(overrides, %{})
true -> %{}
end
case Map.fetch(override_map, key) do
{:ok, entries} when is_list(entries) -> {:ok, entries}
:error -> :none
{:ok, _other} -> {:error, {:invalid_override_entries, key}}
end
end
defp build_entries(releases, loader) do
Enum.reduce_while(releases, {:ok, []}, fn release, {:ok, acc} ->
case loader.(release) do
{:ok, nil} ->
{:cont, {:ok, acc}}
{:ok, entry} ->
{:cont, {:ok, [entry | acc]}}
{:error, reason} ->
{:halt, {:error, reason}}
end
end)
end
defp sort_by_identity(entries, identity_fun) do
Enum.sort_by(entries, identity_fun)
end
defp dedupe_by(entries, key_fun) do
entries
|> Enum.reduce(%{}, fn entry, acc ->
Map.put(acc, key_fun.(entry), entry)
end)
|> Map.values()
end
defp snapshot_identity(entry) do
{
entry["captured_at"] || "",
entry["published_at"] || "",
entry["snapshot_id"] || "",
entry["tag"] || ""
}
end
defp history_identity(entry) do
{
entry["generated_at"] || "",
entry["published_at"] || "",
entry["to_snapshot_id"] || "",
entry["tag"] || ""
}
end
defp snapshot_release?(%{"tag_name" => "snapshot-" <> _rest}), do: true
defp snapshot_release?(%{tag_name: "snapshot-" <> _rest}), do: true
defp snapshot_release?(_release), do: false
defp history_release?(%{"tag_name" => "history-" <> _rest}), do: true
defp history_release?(%{tag_name: "history-" <> _rest}), do: true
defp history_release?(_release), do: false
defp snapshot_entry_from_release(release) do
snapshot_url = release_asset_download_url(release, Snapshot.snapshot_filename())
meta_url = release_asset_download_url(release, Snapshot.snapshot_meta_filename())
cond do
is_nil(snapshot_url) or is_nil(meta_url) ->
{:ok, nil}
true ->
with {:ok, meta} <- fetch_json(meta_url),
snapshot_id when is_binary(snapshot_id) <- meta["snapshot_id"] do
entry =
meta
|> Map.put_new("published_at", release_published_at(release))
|> Map.put("snapshot_url", snapshot_url)
|> Map.put("snapshot_meta_url", meta_url)
|> Map.put("tag", release_tag_name(release))
{:ok, entry}
else
_ -> {:error, {:invalid_snapshot_release, release_tag_name(release)}}
end
end
end
defp history_entry_from_release(release) do
archive_url = release_asset_download_url(release, Snapshot.history_archive_filename())
meta_url = release_asset_download_url(release, Snapshot.history_meta_filename())
cond do
is_nil(archive_url) or is_nil(meta_url) ->
{:ok, nil}
true ->
with {:ok, meta} <- fetch_json(meta_url),
snapshot_id when is_binary(snapshot_id) <- meta["to_snapshot_id"] do
entry =
meta
|> Map.put_new("published_at", release_published_at(release))
|> Map.put("history_url", archive_url)
|> Map.put("history_meta_url", meta_url)
|> Map.put("tag", release_tag_name(release))
{:ok, entry}
else
_ -> {:error, {:invalid_history_release, release_tag_name(release)}}
end
end
end
defp release_asset_download_url(%{"assets" => assets}, filename) when is_list(assets) do
Enum.find_value(assets, fn asset ->
case asset do
%{"name" => ^filename, "browser_download_url" => url} -> url
%{"name" => ^filename, "url" => url} -> url
_ -> nil
end
end)
end
defp release_asset_download_url(_release, _filename), do: nil
defp release_tag_name(%{"tag_name" => tag}), do: tag
defp release_tag_name(%{tag_name: tag}), do: tag
defp release_published_at(%{"published_at" => published_at}), do: published_at
defp release_published_at(%{published_at: published_at}), do: published_at
defp release_published_at(_release), do: nil
@spec fetch_json(String.t()) :: {:ok, term()} | {:error, term()}
defp fetch_json(url) do
with {:ok, content} <- download(url),
{:ok, decoded} <- Jason.decode(content) do
{:ok, decoded}
end
end
@spec download(String.t()) :: {:ok, binary()} | {:error, term()}
defp download(url) do
:ok = ensure_http_started()
case Req.get(url) do
{:ok, %{status: status, body: body}} when status in 200..299 -> {:ok, body}
{:ok, %{status: 404}} -> {:error, :not_found}
{:ok, %{status: status, body: body}} -> {:error, {:http_status, status, body}}
{:error, reason} -> {:error, reason}
end
end
defp ensure_gh_available do
case System.find_executable("gh") do
nil -> {:error, "gh CLI is required to publish snapshot artifacts"}
_ -> :ok
end
end
defp validate_asset_paths(paths) do
case Enum.filter(paths, &File.exists?/1) do
[] -> {:error, :no_release_assets}
existing_paths -> {:ok, existing_paths}
end
end
defp create_release(tag, repo, title, asset_paths) do
args =
["release", "create", tag]
|> Kernel.++(asset_paths)
|> Kernel.++(["--repo", repo, "--title", title, "--notes", ""])
case run_gh(args) do
{:ok, _output} -> {:ok, tag}
{:error, reason} -> {:error, reason}
end
end
defp list_releases(repo) do
do_list_releases(repo, 1, [])
end
defp do_list_releases(repo, page, acc) when page <= @max_release_pages do
url = "https://api.github.com/repos/#{repo}/releases"
case api_get_json(url, params: [per_page: @release_page_size, page: page]) do
{:ok, releases} when is_list(releases) ->
next_acc = acc ++ releases
if length(releases) < @release_page_size do
{:ok, next_acc}
else
do_list_releases(repo, page + 1, next_acc)
end
{:ok, other} ->
{:error, {:invalid_release_list, other}}
error ->
error
end
end
defp do_list_releases(_repo, _page, acc), do: {:ok, acc}
defp api_get_json(url, opts) do
:ok = ensure_http_started()
req_opts = api_request_options(opts)
case Req.get(url, req_opts) do
{:ok, %{status: status, body: body}} when status in 200..299 -> {:ok, body}
{:ok, %{status: 404}} -> {:error, :not_found}
{:ok, %{status: status, body: body}} -> {:error, {:http_status, status, body}}
{:error, reason} -> {:error, reason}
end
end
defp api_request_options(opts) do
headers =
[
{"accept", "application/vnd.github+json"},
{"x-github-api-version", @github_api_version},
{"user-agent", "llm_db"}
]
|> maybe_add_auth_header()
opts
|> Keyword.put(:headers, headers)
|> Keyword.put_new(:decode_body, true)
end
defp maybe_add_auth_header(headers) do
case System.get_env("GH_TOKEN") || System.get_env("GITHUB_TOKEN") do
nil -> headers
token -> [{"authorization", "Bearer #{token}"} | headers]
end
end
defp run_gh(args) do
case System.cmd("gh", args, stderr_to_stdout: true) do
{output, 0} -> {:ok, output}
{output, _code} -> {:error, String.trim(output)}
end
end
@spec ensure_http_started() :: :ok
defp ensure_http_started do
case Application.ensure_all_started(:req) do
{:ok, _apps} -> :ok
{:error, {:already_started, _app}} -> :ok
{:error, reason} -> raise "failed to start req application: #{inspect(reason)}"
end
end
defp release_asset_url(repo, tag, filename) do
"https://github.com/#{repo}/releases/download/#{tag}/#{filename}"
end
defp release_tag(kind, snapshot_id) do
short_id = snapshot_id |> String.slice(0, 12)
suffix = "#{System.system_time(:millisecond)}-#{System.unique_integer([:positive])}"
"#{kind}-#{short_id}-#{suffix}"
end
defp expand_path(path) when is_binary(path) do
if Path.type(path) == :absolute do
path
else
Path.expand(path)
end
end
end