Current section
Files
Jump to
Current section
Files
lib/gemini/live/models.ex
defmodule Gemini.Live.Models do
@moduledoc """
Live API model selection helpers.
Live API model availability can vary by rollout. This module provides
a consistent way to choose a suitable model, preferring newer models
when available while falling back to stable defaults.
"""
require Logger
alias Gemini.APIs.Coordinator
alias Gemini.Config
@type modality :: :text | :audio | :image
@default_models %{
text: :flash_2_0_exp,
audio: :flash_2_5_native_audio_preview_12_2025,
image: :flash_2_0_exp_image_generation
}
@candidate_models %{
text: [
:live_2_5_flash_preview,
:flash_2_0_live_001,
:flash_2_0_exp
],
audio: [
:flash_2_5_native_audio_preview_12_2025,
:flash_2_5_preview_native_audio_dialog
],
image: [
:flash_2_0_exp_image_generation,
:flash_2_0_preview_image_generation,
:flash_2_5_image
]
}
@doc """
Returns the default Live API model for a modality.
"""
@spec default(modality()) :: String.t()
def default(modality) do
model_key = Map.fetch!(@default_models, modality)
Config.get_model(model_key)
end
@doc """
Returns the candidate Live API models for a modality in preference order.
"""
@spec candidates(modality()) :: [String.t()]
def candidates(modality) do
@candidate_models
|> Map.fetch!(modality)
|> Enum.map(&Config.get_model/1)
end
@doc """
Resolve the most appropriate Live API model for a modality.
Uses the Gemini API `list_models` response when available, then falls back
to the default model if no candidates are listed.
## Options
- `:auth` - Auth strategy passed to `Coordinator.list_models/1` (default: `:gemini`)
- `:available_models` - Explicit list of available models (bypass API call)
- `:candidates` - Override candidate list (strings)
- `:require_method` - Supported generation method to filter on (default: `"bidiGenerateContent"`)
"""
@spec resolve(modality(), keyword()) :: String.t()
def resolve(modality, opts \\ []) do
candidate_models = Keyword.get(opts, :candidates, candidates(modality))
available_models = Keyword.get(opts, :available_models)
require_method = Keyword.get(opts, :require_method, "bidiGenerateContent")
selection =
if is_list(available_models) do
pick_from_available(candidate_models, available_models)
else
fetch_and_pick_model(candidate_models, require_method, opts)
end
case selection do
{:ok, model} ->
model
:none ->
default_model = default(modality)
Logger.warning(
"[Gemini.Live.Models] No candidate models matched availability; using #{default_model}."
)
default_model
end
end
defp fetch_and_pick_model(candidate_models, require_method, opts) do
case Coordinator.list_models(auth: Keyword.get(opts, :auth, :gemini)) do
{:ok, response} ->
available = filter_models_by_method(response.models, require_method)
pick_from_available(candidate_models, available)
{:error, reason} ->
Logger.warning(
"[Gemini.Live.Models] Could not list models (#{inspect(reason)}); using default."
)
:none
end
end
defp filter_models_by_method(models, require_method) do
models
|> Enum.filter(&supports_method?(&1, require_method))
|> Enum.map(&Map.get(&1, :name))
end
defp supports_method?(model, method) do
model
|> Map.get(:supported_generation_methods, [])
|> Enum.member?(method)
end
@doc """
Select the first candidate present in an available model list.
Returns `{:ok, model}` or `:none` if no candidates match.
"""
@spec pick_from_available([String.t()], [String.t()]) :: {:ok, String.t()} | :none
def pick_from_available(candidates, available_models)
when is_list(candidates) and is_list(available_models) do
available =
available_models
|> Enum.map(&normalize_model_name/1)
|> MapSet.new()
Enum.find_value(candidates, :none, fn candidate ->
normalized = normalize_model_name(candidate)
if MapSet.member?(available, normalized) do
{:ok, candidate}
end
end)
end
defp normalize_model_name(name) when is_binary(name) do
String.replace_prefix(name, "models/", "")
end
end