Packages
llm_db
2026.6.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/sources/google.ex
defmodule LLMDB.Sources.Google do
@moduledoc """
Remote source for Google Gemini models (https://generativelanguage.googleapis.com/v1beta/models).
- `pull/1` fetches data from Google Gemini API and caches locally
- `load/1` reads from cached file (no network call)
## Options
- `:url` - API endpoint (default: "https://generativelanguage.googleapis.com/v1beta/models")
- `:api_key` - Google API key (required, or set `GOOGLE_API_KEY` or `GEMINI_API_KEY` env var)
- `:page_size` - Items per page (1-1000, default: 1000 to fetch all)
- `:req_opts` - Additional Req options for testing
## Configuration
Cache directory can be configured in application config:
config :llm_db,
google_cache_dir: "priv/llm_db/remote"
Default: `"priv/llm_db/remote"`
## Usage
# Pull remote data and cache (requires API key)
mix llm_db.pull --source google
# Load from cache
{:ok, data} = Google.load(%{})
"""
@behaviour LLMDB.Source
@default_url "https://generativelanguage.googleapis.com/v1beta/models"
@default_cache_dir "priv/llm_db/remote"
@impl true
def pull(opts) do
api_key = get_api_key(opts)
if is_nil(api_key) or api_key == "" do
{:error, :no_api_key}
else
do_pull(opts, api_key)
end
end
defp do_pull(opts, api_key) do
url = Map.get(opts, :url, @default_url)
cache_dir = get_cache_dir()
cache_path = cache_path(url, cache_dir)
manifest_path = manifest_path(url, cache_dir)
req_opts = Map.get(opts, :req_opts, [])
headers = build_headers(api_key)
headers = headers ++ Keyword.get(req_opts, :headers, [])
req_opts = Keyword.put(req_opts, :headers, headers)
page_size = Map.get(opts, :page_size, 1000)
all_models = fetch_all_pages(url, req_opts, page_size, [])
case all_models do
{:ok, models} ->
response = %{"models" => models}
bin = Jason.encode!(response, pretty: true)
write_cache(cache_path, manifest_path, bin, url, [])
{:ok, cache_path}
{:error, reason} ->
{:error, reason}
end
end
@impl true
def load(opts) do
url = Map.get(opts, :url, @default_url)
cache_dir = get_cache_dir()
cache_path = cache_path(url, cache_dir)
case File.read(cache_path) do
{:ok, bin} ->
case Jason.decode(bin) do
{:ok, decoded} -> {:ok, transform(decoded)}
{:error, err} -> {:error, {:json_error, err}}
end
{:error, :enoent} ->
{:error, :no_cache}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Transforms Google Gemini API response to canonical Zoi format.
## Input Format (Google)
```json
{
"models": [
{
"name": "models/gemini-2.0-flash-exp",
"baseModelId": "models/gemini-2.0-flash",
"version": "001",
"displayName": "Gemini 2.0 Flash",
"description": "...",
"inputTokenLimit": 1048576,
"outputTokenLimit": 8192,
"supportedGenerationMethods": ["generateContent"],
"thinking": false
}
]
}
```
## Output Format (Canonical Zoi)
```elixir
%{
"google" => %{
id: :google,
name: "Google",
models: [
%{
id: "gemini-2.0-flash-exp",
provider: :google,
name: "Gemini 2.0 Flash",
limits: %{
context: 1048576,
output: 8192
},
extra: %{
base_model_id: "models/gemini-2.0-flash",
version: "001",
description: "...",
supported_generation_methods: ["generateContent"],
thinking: false
}
}
]
}
}
```
"""
def transform(content) when is_map(content) do
models_list =
content
|> Map.get("models", [])
|> Enum.map(&transform_model/1)
%{
"google" => %{
id: :google,
name: "Google",
models: models_list
}
}
end
defp transform_model(model) do
model_id = extract_model_id(model["name"])
base = %{
id: model_id,
provider: :google
}
base =
if display_name = model["displayName"] do
Map.put(base, :name, display_name)
else
base
end
base = map_limits(base, model)
base = map_extra(base, model)
base
end
defp extract_model_id(name) when is_binary(name) do
case String.split(name, "/") do
["models", model_id] -> model_id
_ -> name
end
end
defp extract_model_id(name), do: name
defp map_limits(model, source) do
limits =
%{}
|> put_if_present(:context, source["inputTokenLimit"])
|> put_if_present(:output, source["outputTokenLimit"])
if map_size(limits) > 0 do
Map.put(model, :limits, limits)
else
model
end
end
defp map_extra(model, source) do
extra =
source
|> Map.drop([
"name",
"displayName",
"inputTokenLimit",
"outputTokenLimit"
])
|> Enum.reduce(%{}, fn {k, v}, acc ->
atom_key =
k
|> Macro.underscore()
|> String.to_atom()
Map.put(acc, atom_key, v)
end)
if map_size(extra) > 0 do
Map.put(model, :extra, extra)
else
model
end
end
defp fetch_all_pages(url, req_opts, page_size, acc) do
params = [pageSize: page_size]
req_opts = Keyword.put(req_opts, :params, params)
case Req.get(url, req_opts) do
{:ok, %Req.Response{status: 200, body: body}} ->
models = Map.get(body, "models", [])
next_page_token = Map.get(body, "nextPageToken")
new_acc = acc ++ models
if next_page_token && not Enum.empty?(models) do
req_opts =
Keyword.put(req_opts, :params, pageSize: page_size, pageToken: next_page_token)
fetch_all_pages(url, req_opts, page_size, new_acc)
else
{:ok, new_acc}
end
{:ok, %Req.Response{status: status}} when status >= 400 ->
{:error, {:http_status, status}}
{:error, reason} ->
{:error, reason}
end
end
defp put_if_present(map, _key, nil), do: map
defp put_if_present(map, key, value), do: Map.put(map, key, value)
defp get_api_key(opts) do
Map.get(opts, :api_key) ||
System.get_env("GOOGLE_API_KEY") ||
System.get_env("GEMINI_API_KEY")
end
defp get_cache_dir do
Application.get_env(:llm_db, :google_cache_dir, @default_cache_dir)
end
defp cache_path(url, cache_dir) do
hash = :crypto.hash(:sha256, url) |> Base.encode16(case: :lower) |> binary_part(0, 8)
Path.join(cache_dir, "google-#{hash}.json")
end
defp manifest_path(url, cache_dir) do
hash = :crypto.hash(:sha256, url) |> Base.encode16(case: :lower) |> binary_part(0, 8)
Path.join(cache_dir, "google-#{hash}.manifest.json")
end
defp build_headers(api_key) do
[{"x-goog-api-key", api_key}]
end
defp write_cache(cache_path, manifest_path, content, url, _headers) do
File.mkdir_p!(Path.dirname(cache_path))
File.write!(cache_path, content)
manifest = %{
source_url: url,
sha256: :crypto.hash(:sha256, content) |> Base.encode16(case: :lower),
size_bytes: byte_size(content),
downloaded_at: DateTime.utc_now() |> DateTime.to_iso8601()
}
File.write!(manifest_path, Jason.encode!(manifest, pretty: true))
end
end