Current section
Files
Jump to
Current section
Files
lib/gemini/config.ex
defmodule Gemini.Config do
@moduledoc """
Unified configuration management for both Gemini and Vertex AI authentication.
Supports multiple authentication strategies:
- Gemini API (AI Studio): API key authentication
- Vertex AI: OAuth2 or Service Account authentication
## Model Registry
Models are organized by API compatibility:
- **Universal models**: Work identically in both Gemini API and Vertex AI
- **Gemini API models**: Only available in AI Studio (convenience aliases like `-latest`)
- **Vertex AI models**: Only available in Vertex AI (e.g., EmbeddingGemma)
Use `models_for/1` to discover available models for your auth type.
## Auth-Aware Defaults
Default models are automatically selected based on detected authentication:
- Gemini API: `gemini-flash-lite-latest` (convenience alias)
- Vertex AI: `gemini-2.5-flash-lite` (universal name)
For embeddings:
- Gemini API: `gemini-embedding-001` (3072 dimensions)
- Vertex AI: `embeddinggemma` (768 dimensions)
## Examples
# Auto-detects auth and uses appropriate default
Gemini.generate("Hello")
# Get models available for specific API
Config.models_for(:vertex_ai)
# Check if a model works with an API
Config.model_available?(:flash_lite_latest, :vertex_ai)
#=> false
# Get auth-aware embedding model
Config.default_embedding_model()
"""
require Logger
import Gemini.Utils.MapHelpers, only: [maybe_put: 3]
alias Gemini.ModelRegistry
@type auth_config :: %{
type: :gemini | :vertex_ai,
credentials: map()
}
@type api_type :: :gemini | :vertex_ai | :both
@type model_category :: :generation | :embedding | :thinking | :image | :live | :tts
# ===========================================================================
# Model Registry - Organized by API Compatibility
# ===========================================================================
# Universal models - work identically in both Gemini API and Vertex AI
@universal_models %{
# Gemini 3 models (preview)
pro_3_1_preview: "gemini-3.1-pro-preview",
pro_3_1_preview_customtools: "gemini-3.1-pro-preview-customtools",
pro_3_preview: "gemini-3-pro-preview",
flash_3_1_lite_preview: "gemini-3.1-flash-lite-preview",
flash_3_1_image_preview: "gemini-3.1-flash-image-preview",
flash_3_1_live_preview: "gemini-3.1-flash-live-preview",
pro_3_image_preview: "gemini-3-pro-image-preview",
flash_3_preview: "gemini-3-flash-preview",
# Gemini 2.5 models (GA)
pro_2_5: "gemini-2.5-pro",
flash_2_5: "gemini-2.5-flash",
flash_2_5_lite: "gemini-2.5-flash-lite",
# Gemini 2.5 Live API models in current model catalog
flash_2_5_native_audio_latest: "gemini-2.5-flash-native-audio-latest",
flash_2_5_native_audio_preview_12_2025: "gemini-2.5-flash-native-audio-preview-12-2025",
flash_2_5_native_audio_preview_09_2025: "gemini-2.5-flash-native-audio-preview-09-2025",
# Gemini 2.5 preview/specialized
flash_2_5_preview_native_audio_dialog: "gemini-2.5-flash-preview-native-audio-dialog",
flash_2_5_exp_native_audio_thinking_dialog:
"gemini-2.5-flash-exp-native-audio-thinking-dialog",
flash_2_5_preview_tts: "gemini-2.5-flash-preview-tts",
pro_2_5_preview_tts: "gemini-2.5-pro-preview-tts",
flash_2_5_preview_09_2025: "gemini-2.5-flash-preview-09-2025",
flash_2_5_lite_preview_09_2025: "gemini-2.5-flash-lite-preview-09-2025",
flash_2_5_image: "gemini-2.5-flash-image",
flash_2_5_image_preview: "gemini-2.5-flash-image-preview",
# Gemini 2.0 models
flash_2_0: "gemini-2.0-flash",
flash_2_0_001: "gemini-2.0-flash-001",
flash_2_0_exp: "gemini-2.0-flash-exp",
flash_2_0_preview_image_generation: "gemini-2.0-flash-preview-image-generation",
flash_2_0_lite: "gemini-2.0-flash-lite",
flash_2_0_lite_001: "gemini-2.0-flash-lite-001",
flash_2_0_live_001: "gemini-2.0-flash-live-001",
# Universal aliases (use these for cross-platform compatibility)
default_universal: "gemini-2.5-flash-lite",
latest: "gemini-3.1-pro-preview",
stable: "gemini-2.5-pro"
}
# Gemini API (AI Studio) only models - convenience aliases that don't work on Vertex AI
@gemini_api_models %{
# Convenience aliases (AI Studio only - Vertex AI doesn't support -latest suffix)
flash_lite_latest: "gemini-flash-lite-latest",
flash_latest: "gemini-flash-latest",
pro_latest: "gemini-pro-latest",
# Embedding model for AI Studio
embedding: "gemini-embedding-001",
embedding_exp: "gemini-embedding-exp-03-07",
# Legacy Gemini Live aliases (not listed in current model catalog pages)
live_2_5_flash: "gemini-live-2.5-flash",
live_2_5_flash_preview: "gemini-live-2.5-flash-preview",
live_2_5_flash_native_audio: "gemini-live-2.5-flash-native-audio",
live_2_5_flash_preview_native_audio: "gemini-live-2.5-flash-preview-native-audio",
live_2_5_flash_preview_native_audio_09_2025:
"gemini-live-2.5-flash-preview-native-audio-09-2025",
# Specialized model family pages
deep_research_pro_preview_12_2025: "deep-research-pro-preview-12-2025",
computer_use_2_5_preview_10_2025: "gemini-2.5-computer-use-preview-10-2025",
robotics_er_1_5_preview: "gemini-robotics-er-1.5-preview",
lyria_realtime_exp: "lyria-realtime-exp",
veo_3_1_generate_preview: "veo-3.1-generate-preview",
veo_3_1_fast_generate_preview: "veo-3.1-fast-generate-preview",
imagen_4_generate_001: "imagen-4.0-generate-001",
imagen_4_ultra_generate_001: "imagen-4.0-ultra-generate-001",
imagen_4_fast_generate_001: "imagen-4.0-fast-generate-001",
# Legacy image rollout fallback (Gemini API only)
flash_2_0_exp_image_generation: "gemini-2.0-flash-exp-image-generation",
# Legacy default alias (AI Studio only)
default: "gemini-flash-lite-latest"
}
# Vertex AI only models - not available in Gemini API (AI Studio)
@vertex_ai_models %{
# EmbeddingGemma - Vertex AI's embedding model
# 300M parameters, 768 dimensions (supports MRL: 128, 256, 512, 768)
embedding_gemma: "embeddinggemma",
embedding_gemma_300m: "embeddinggemma-300m",
# Vertex AI default alias
default: "gemini-2.5-flash-lite"
}
# Default models per API type
@default_generation_models %{
gemini: "gemini-flash-lite-latest",
vertex_ai: "gemini-2.5-flash-lite"
}
# Use-case aliases with recommended token budgets
# These provide consistent model selection across the codebase
@use_case_models %{
cache_context: %{model_key: :flash_2_5, recommended_tokens: 32_000},
report_section: %{model_key: :pro_2_5, recommended_tokens: 16_000},
fast_path: %{model_key: :flash_2_5_lite, recommended_tokens: 8_000}
}
@default_embedding_models %{
gemini: "gemini-embedding-001",
vertex_ai: "embeddinggemma"
}
# Embedding configuration per model
@embedding_config %{
"gemini-embedding-001" => %{
default_dimensions: 3072,
supported_dimensions: [128, 256, 512, 768, 1536, 3072],
recommended_dimensions: [768, 1536, 3072],
uses_task_type_param: true,
requires_normalization_below: 3072
},
"gemini-embedding-exp-03-07" => %{
default_dimensions: 3072,
supported_dimensions: [128, 256, 512, 768, 1536, 3072],
recommended_dimensions: [768, 1536, 3072],
uses_task_type_param: true,
requires_normalization_below: 3072
},
"embeddinggemma" => %{
default_dimensions: 768,
supported_dimensions: [128, 256, 512, 768],
recommended_dimensions: [768],
uses_task_type_param: false,
uses_prompt_prefix: true,
# All dimensions are normalized
requires_normalization_below: nil
},
"embeddinggemma-300m" => %{
default_dimensions: 768,
supported_dimensions: [128, 256, 512, 768],
recommended_dimensions: [768],
uses_task_type_param: false,
uses_prompt_prefix: true,
requires_normalization_below: nil
}
}
# EmbeddingGemma task type to prompt prefix mapping
@embedding_gemma_prompts %{
retrieval_query: "task: search result | query: ",
retrieval_document: "title: {title} | text: ",
question_answering: "task: question answering | query: ",
fact_verification: "task: fact checking | query: ",
classification: "task: classification | query: ",
clustering: "task: clustering | query: ",
semantic_similarity: "task: sentence similarity | query: ",
code_retrieval_query: "task: code retrieval | query: "
}
@doc """
Get configuration based on environment variables and application config.
Returns a structured configuration map.
"""
def get do
auth_type = detect_auth_type()
case auth_type do
:gemini ->
%{
auth_type: :gemini,
api_key: api_key(),
model: default_model()
}
:vertex ->
%{
auth_type: :vertex,
project_id: vertex_project_id(),
location: vertex_location(),
model: default_model()
}
end
end
@doc """
Get configuration with overrides.
"""
def get(overrides) when is_list(overrides) do
base_config = get()
override_map = Enum.into(overrides, %{})
Map.merge(base_config, override_map)
end
@doc """
Detect authentication type based on configured Gemini or Vertex credentials.
"""
def detect_auth_type do
cond do
api_key() -> :gemini
vertex_project_id() && vertex_project_id() != "" -> :vertex
# default
true -> :gemini
end
end
@doc """
Detect authentication type based on configuration map.
"""
def detect_auth_type(%{api_key: api_key, project_id: _project_id}) when not is_nil(api_key) do
# gemini takes priority
:gemini
end
def detect_auth_type(%{project_id: project_id}) when not is_nil(project_id) do
:vertex
end
def detect_auth_type(%{api_key: api_key}) when not is_nil(api_key) do
:gemini
end
def detect_auth_type(%{}) do
# default
:gemini
end
@doc """
Get the authentication configuration.
Returns a map with the authentication type and credentials.
Priority order:
1. Application configuration
2. Environment variables
"""
@spec auth_config() :: auth_config() | nil
def auth_config do
auth_config_from_app() ||
gemini_auth_config_from_env() ||
vertex_access_token_config_from_env() ||
vertex_service_account_config_from_env()
end
@doc """
Get the API key from application config or environment.
(Legacy function for backward compatibility)
"""
def api_key do
Application.get_env(:gemini_ex, :api_key) || gemini_api_key()
end
defp gemini_auth_config_from_env do
case gemini_api_key() do
nil ->
nil
api_key ->
%{
type: :gemini,
credentials: %{api_key: api_key}
}
end
end
defp vertex_access_token_config_from_env do
if vertex_access_token() && vertex_project_id() do
%{
type: :vertex_ai,
credentials: %{
access_token: vertex_access_token(),
project_id: vertex_project_id(),
location: vertex_location()
}
}
else
nil
end
end
defp vertex_service_account_config_from_env do
service_account_path = vertex_service_account()
if service_account_path do
project_id = project_id_from_env_or_service_account(service_account_path)
if project_id do
%{
type: :vertex_ai,
credentials: %{
service_account_key: service_account_path,
project_id: project_id,
location: vertex_location()
}
}
else
nil
end
end
end
defp project_id_from_env_or_service_account(service_account_path) do
case vertex_project_id() do
nil ->
case load_project_from_service_account(service_account_path) do
{:ok, project} -> project
_ -> nil
end
project ->
project
end
end
defp auth_config_from_app do
app_auth = Application.get_env(:gemini, :auth) || Application.get_env(:gemini_ex, :auth)
case app_auth do
nil ->
case Application.get_env(:gemini_ex, :api_key) ||
Application.get_env(:gemini, :api_key) do
nil -> nil
api_key -> %{type: :gemini, credentials: %{api_key: api_key}}
end
config ->
config
end
end
# ===========================================================================
# Auth-Aware Default Model Functions
# ===========================================================================
@doc """
Get the default generation model for the current authentication type.
Returns different defaults based on detected auth:
- Gemini API (AI Studio): `"gemini-flash-lite-latest"` (convenience alias)
- Vertex AI: `"gemini-2.0-flash-lite"` (universal name)
Can be overridden via application config:
config :gemini_ex, :default_model, "your-model"
## Examples
# With GEMINI_API_KEY set
Config.default_model()
#=> "gemini-flash-lite-latest"
# With VERTEX_PROJECT_ID set
Config.default_model()
#=> "gemini-2.5-flash-lite"
"""
@spec default_model() :: String.t()
def default_model do
case Application.get_env(:gemini_ex, :default_model) do
nil -> default_model_for_auth()
model -> model
end
end
@doc """
Get the default model for a specific API type.
## Parameters
- `api_type`: `:gemini` or `:vertex_ai`
## Examples
Config.default_model_for(:gemini)
#=> "gemini-flash-lite-latest"
Config.default_model_for(:vertex_ai)
#=> "gemini-2.5-flash-lite"
"""
@spec default_model_for(api_type()) :: String.t()
def default_model_for(:gemini), do: @default_generation_models[:gemini]
def default_model_for(:vertex_ai), do: @default_generation_models[:vertex_ai]
def default_model_for(:both), do: @default_generation_models[:vertex_ai]
@doc """
Get the default embedding model for the current authentication type.
Returns different defaults based on detected auth:
- Gemini API (AI Studio): `"gemini-embedding-001"` (3072 dimensions)
- Vertex AI: `"embeddinggemma"` (768 dimensions)
Can be overridden via application config:
config :gemini_ex, :default_embedding_model, "your-model"
## Examples
# With GEMINI_API_KEY set
Config.default_embedding_model()
#=> "gemini-embedding-001"
# With VERTEX_PROJECT_ID set
Config.default_embedding_model()
#=> "embeddinggemma"
"""
@spec default_embedding_model() :: String.t()
def default_embedding_model do
case Application.get_env(:gemini_ex, :default_embedding_model) do
nil -> default_embedding_model_for_auth()
model -> model
end
end
@doc """
Get the default embedding model for a specific API type.
## Parameters
- `api_type`: `:gemini` or `:vertex_ai`
## Examples
Config.default_embedding_model_for(:gemini)
#=> "gemini-embedding-001"
Config.default_embedding_model_for(:vertex_ai)
#=> "embeddinggemma"
"""
@spec default_embedding_model_for(api_type()) :: String.t()
def default_embedding_model_for(:gemini), do: @default_embedding_models[:gemini]
def default_embedding_model_for(:vertex_ai), do: @default_embedding_models[:vertex_ai]
def default_embedding_model_for(:both), do: @default_embedding_models[:gemini]
# Private helpers for auth-aware defaults
defp default_model_for_auth do
case current_api_type() do
:vertex_ai -> @default_generation_models[:vertex_ai]
:gemini -> @default_generation_models[:gemini]
end
end
defp default_embedding_model_for_auth do
case current_api_type() do
:vertex_ai -> @default_embedding_models[:vertex_ai]
:gemini -> @default_embedding_models[:gemini]
end
end
@doc """
Get the current API type based on detected authentication.
Returns `:gemini` or `:vertex_ai` based on which credentials are configured.
"""
@spec current_api_type() :: :gemini | :vertex_ai
def current_api_type do
case detect_auth_type() do
:vertex -> :vertex_ai
:gemini -> :gemini
end
end
# ===========================================================================
# Model Lookup and Validation
# ===========================================================================
@doc """
Get a model name by its key or return the string if it's already a model name.
Optionally validates that the model is available for a specific API.
## Parameters
- `model_key`: Atom key or string model name
- `opts`: Optional keyword list
- `:api` - Validate model works with `:gemini` or `:vertex_ai`
- `:strict` - If true, raise on incompatible model (default: false, warns)
## Examples
iex> Gemini.Config.get_model(:flash_2_0)
"gemini-2.0-flash"
iex> Gemini.Config.get_model("gemini-2.5-flash")
"gemini-2.5-flash"
iex> Gemini.Config.get_model(:flash_lite_latest, api: :vertex_ai)
# Logs warning: Model flash_lite_latest (gemini-flash-lite-latest) may not be available on vertex_ai
"gemini-flash-lite-latest"
iex> Gemini.Config.get_model(:flash_lite_latest, api: :vertex_ai, strict: true)
# ** (ArgumentError) Model :flash_lite_latest not available on vertex_ai
"""
@spec get_model(atom() | String.t(), keyword()) :: String.t()
def get_model(model_key, opts \\ [])
def get_model(model_name, _opts) when is_binary(model_name), do: model_name
def get_model(model_key, opts) when is_atom(model_key) do
case lookup_model(model_key) do
{model_name, api_compat} ->
if api = Keyword.get(opts, :api) do
validate_model_compat(model_key, model_name, api_compat, api, opts)
end
model_name
:not_found ->
all_keys =
Map.keys(@universal_models) ++
Map.keys(@gemini_api_models) ++ Map.keys(@vertex_ai_models)
raise ArgumentError,
"Unknown model key: #{model_key}. Available keys: #{inspect(Enum.uniq(all_keys))}"
end
end
@doc """
List all models available for a specific API type.
## Parameters
- `api_type`: `:gemini`, `:vertex_ai`, or `:both` (universal only)
## Examples
Config.models_for(:gemini)
#=> %{flash_lite_latest: "gemini-flash-lite-latest", flash_2_5: "gemini-2.5-flash", ...}
Config.models_for(:vertex_ai)
#=> %{embedding_gemma: "embeddinggemma", flash_2_5: "gemini-2.5-flash", ...}
Config.models_for(:both)
#=> %{flash_2_5: "gemini-2.5-flash", ...} # Only universal models
"""
@spec models_for(api_type()) :: map()
def models_for(:gemini), do: Map.merge(@universal_models, @gemini_api_models)
def models_for(:vertex_ai), do: Map.merge(@universal_models, @vertex_ai_models)
def models_for(:both), do: @universal_models
@doc """
Check if a model key is available for a specific API type.
## Parameters
- `model_key`: Atom model key to check
- `api_type`: `:gemini` or `:vertex_ai`
## Examples
Config.model_available?(:flash_2_0, :vertex_ai)
#=> true
Config.model_available?(:flash_lite_latest, :vertex_ai)
#=> false
Config.model_available?(:embedding_gemma, :gemini)
#=> false
"""
@spec model_available?(atom(), api_type()) :: boolean()
def model_available?(model_key, api_type) when is_atom(model_key) do
models_for(api_type) |> Map.has_key?(model_key)
end
@doc """
Check if a model key exists in the combined model registry.
## Examples
iex> Gemini.Config.has_model?(:flash_2_0)
true
iex> Gemini.Config.has_model?(:unknown)
false
"""
@spec has_model?(atom()) :: boolean()
def has_model?(model_key) when is_atom(model_key) do
Map.has_key?(@universal_models, model_key) or
Map.has_key?(@gemini_api_models, model_key) or
Map.has_key?(@vertex_ai_models, model_key)
end
@doc """
Get the API compatibility of a model key.
## Returns
- `:both` - Model works in both Gemini API and Vertex AI
- `:gemini` - Model only works in Gemini API (AI Studio)
- `:vertex_ai` - Model only works in Vertex AI
## Examples
Config.model_api(:flash_2_0)
#=> :both
Config.model_api(:flash_lite_latest)
#=> :gemini
Config.model_api(:embedding_gemma)
#=> :vertex_ai
"""
@spec model_api(atom()) :: api_type() | nil
def model_api(model_key) when is_atom(model_key) do
cond do
Map.has_key?(@universal_models, model_key) -> :both
Map.has_key?(@gemini_api_models, model_key) -> :gemini
Map.has_key?(@vertex_ai_models, model_key) -> :vertex_ai
true -> nil
end
end
@doc """
Return model metadata from `Gemini.ModelRegistry`.
Accepts either a registry key atom (resolved through `get_model/1`) or a
concrete model string.
"""
@spec model_info(atom() | String.t()) :: Gemini.ModelRegistry.entry() | nil
def model_info(model_or_key) when is_atom(model_or_key) or is_binary(model_or_key) do
case resolve_model_name(model_or_key) do
nil -> nil
model_name -> ModelRegistry.get(model_name)
end
end
@doc """
Check model capability support state from `Gemini.ModelRegistry`.
Defaults to checking for `:supported`.
"""
@spec model_supports?(
atom() | String.t(),
Gemini.ModelRegistry.capability(),
Gemini.ModelRegistry.support_state()
) :: boolean()
def model_supports?(model_or_key, capability, expected \\ :supported)
when (is_atom(model_or_key) or is_binary(model_or_key)) and is_atom(capability) and
is_atom(expected) do
case resolve_model_name(model_or_key) do
nil -> false
model_name -> ModelRegistry.supports?(model_name, capability, expected)
end
end
@doc """
List model codes that match a capability support state.
"""
@spec models_with_capability(
Gemini.ModelRegistry.capability(),
Gemini.ModelRegistry.support_state()
) :: [String.t()]
def models_with_capability(capability, expected \\ :supported)
when is_atom(capability) and is_atom(expected) do
ModelRegistry.with_capability(capability, expected)
end
@doc """
Return registry-backed Live API candidate model codes for a modality.
"""
@spec live_registry_candidates(:text | :audio) :: [String.t()]
def live_registry_candidates(modality) when modality in [:text, :audio] do
ModelRegistry.live_candidates(modality)
end
# ===========================================================================
# Use-Case Model Aliases
# ===========================================================================
@doc """
Get the model string for a use-case alias.
Use-case aliases provide consistent model selection with recommended token budgets.
This avoids scattering raw model strings throughout the codebase.
## Parameters
- `use_case`: Use-case atom (`:cache_context`, `:report_section`, `:fast_path`)
- `api_type`: Optional API type for validation (`:gemini` or `:vertex_ai`)
## Available Use Cases
| Use Case | Model | Recommended Token Budget |
|----------|-------|--------------------------|
| `:cache_context` | gemini-2.5-flash | 32,000 |
| `:report_section` | gemini-2.5-pro | 16,000 |
| `:fast_path` | gemini-2.5-flash-lite | 8,000 |
## Examples
iex> Gemini.Config.model_for_use_case(:cache_context)
"gemini-2.5-flash"
iex> Gemini.Config.model_for_use_case(:fast_path)
"gemini-2.5-flash-lite"
iex> Gemini.Config.model_for_use_case(:cache_context, :vertex_ai)
"gemini-2.5-flash"
"""
@spec model_for_use_case(atom(), api_type() | nil) :: String.t()
def model_for_use_case(use_case, api_type \\ nil)
def model_for_use_case(use_case, api_type) when is_atom(use_case) do
case Map.get(@use_case_models, use_case) do
%{model_key: model_key} ->
opts = if api_type, do: [api: api_type], else: []
get_model(model_key, opts)
nil ->
available = Map.keys(@use_case_models)
raise ArgumentError,
"Unknown use case: #{use_case}. Available use cases: #{inspect(available)}"
end
end
@doc """
Get the recommended token budget for a use-case alias.
## Parameters
- `use_case`: Use-case atom
## Examples
iex> Gemini.Config.use_case_token_budget(:cache_context)
32000
iex> Gemini.Config.use_case_token_budget(:fast_path)
8000
"""
@spec use_case_token_budget(atom()) :: pos_integer()
def use_case_token_budget(use_case) when is_atom(use_case) do
case Map.get(@use_case_models, use_case) do
%{recommended_tokens: tokens} ->
tokens
nil ->
available = Map.keys(@use_case_models)
raise ArgumentError,
"Unknown use case: #{use_case}. Available use cases: #{inspect(available)}"
end
end
@doc """
Get all resolved use-case model mappings.
Returns a map of use-case atoms to their resolved model strings.
## Examples
iex> Gemini.Config.resolved_use_case_models()
%{
cache_context: "gemini-2.5-flash",
report_section: "gemini-2.5-pro",
fast_path: "gemini-2.5-flash-lite"
}
"""
@spec resolved_use_case_models() :: %{atom() => String.t()}
def resolved_use_case_models do
Map.new(@use_case_models, fn {use_case, %{model_key: model_key}} ->
{use_case, get_model(model_key)}
end)
end
@doc """
List available use-case aliases.
## Examples
iex> Gemini.Config.available_use_cases()
[:cache_context, :fast_path, :report_section]
"""
@spec available_use_cases() :: [atom()]
def available_use_cases do
@use_case_models |> Map.keys() |> Enum.sort()
end
# ===========================================================================
# Embedding Configuration
# ===========================================================================
@doc """
Get embedding configuration for a specific model.
Returns configuration including supported dimensions, task type handling, etc.
## Parameters
- `model`: Model name string
## Returns
Map with embedding configuration or nil if not an embedding model.
## Examples
Config.embedding_config("gemini-embedding-001")
#=> %{
#=> default_dimensions: 3072,
#=> supported_dimensions: [128, 256, 512, 768, 1536, 3072],
#=> recommended_dimensions: [768, 1536, 3072],
#=> uses_task_type_param: true,
#=> requires_normalization_below: 3072
#=> }
Config.embedding_config("embeddinggemma")
#=> %{
#=> default_dimensions: 768,
#=> supported_dimensions: [128, 256, 512, 768],
#=> uses_task_type_param: false,
#=> uses_prompt_prefix: true,
#=> ...
#=> }
"""
@spec embedding_config(String.t()) :: map() | nil
def embedding_config(model) when is_binary(model) do
Map.get(@embedding_config, model)
end
@doc """
Check if an embedding model uses prompt prefixes for task types.
EmbeddingGemma uses prompt prefixes like "task: search result | query: "
while Gemini embedding models use a taskType parameter.
## Examples
Config.uses_prompt_prefix?("embeddinggemma")
#=> true
Config.uses_prompt_prefix?("gemini-embedding-001")
#=> false
"""
@spec uses_prompt_prefix?(String.t()) :: boolean()
def uses_prompt_prefix?(model) when is_binary(model) do
case embedding_config(model) do
%{uses_prompt_prefix: true} -> true
_ -> false
end
end
@doc """
Get the prompt prefix for an EmbeddingGemma task type.
## Parameters
- `task_type`: Task type atom (e.g., `:retrieval_query`, `:semantic_similarity`)
- `opts`: Optional keyword list
- `:title` - Document title for `:retrieval_document` task type
## Examples
Config.embedding_prompt_prefix(:retrieval_query)
#=> "task: search result | query: "
Config.embedding_prompt_prefix(:retrieval_document, title: "My Document")
#=> "title: My Document | text: "
Config.embedding_prompt_prefix(:retrieval_document)
#=> "title: none | text: "
"""
@spec embedding_prompt_prefix(atom(), keyword()) :: String.t()
def embedding_prompt_prefix(task_type, opts \\ []) do
case Map.get(@embedding_gemma_prompts, task_type) do
nil ->
# Default to retrieval query if unknown task type
"task: search result | query: "
prefix when task_type == :retrieval_document ->
title = Keyword.get(opts, :title, "none")
String.replace(prefix, "{title}", title)
prefix ->
prefix
end
end
@doc """
Get the default output dimensionality for an embedding model.
## Examples
Config.default_embedding_dimensions("gemini-embedding-001")
#=> 3072
Config.default_embedding_dimensions("embeddinggemma")
#=> 768
"""
@spec default_embedding_dimensions(String.t()) :: pos_integer() | nil
def default_embedding_dimensions(model) when is_binary(model) do
case embedding_config(model) do
%{default_dimensions: dims} -> dims
_ -> nil
end
end
@doc """
Check if an embedding needs normalization for a given dimensionality.
Gemini embedding models only return normalized embeddings at full dimensionality.
Lower dimensions need manual normalization. EmbeddingGemma is always normalized.
## Examples
Config.needs_normalization?("gemini-embedding-001", 768)
#=> true
Config.needs_normalization?("gemini-embedding-001", 3072)
#=> false
Config.needs_normalization?("embeddinggemma", 256)
#=> false # EmbeddingGemma is always normalized
"""
@spec needs_normalization?(String.t(), pos_integer()) :: boolean()
def needs_normalization?(model, dimensions) when is_binary(model) and is_integer(dimensions) do
case embedding_config(model) do
%{requires_normalization_below: nil} -> false
%{requires_normalization_below: threshold} -> dimensions < threshold
_ -> false
end
end
defp resolve_model_name(model_name) when is_binary(model_name), do: model_name
defp resolve_model_name(model_key) when is_atom(model_key) do
case lookup_model(model_key) do
{model_name, _api_compat} -> model_name
:not_found -> nil
end
end
# Private helper to look up model with its API compatibility
defp lookup_model(key) do
cond do
model = Map.get(@universal_models, key) -> {model, :both}
model = Map.get(@gemini_api_models, key) -> {model, :gemini}
model = Map.get(@vertex_ai_models, key) -> {model, :vertex_ai}
true -> :not_found
end
end
defp validate_model_compat(key, name, model_api, requested_api, opts) do
compatible? = model_api == :both or model_api == requested_api
if compatible? do
:ok
else
handle_incompatible_model(key, name, requested_api, opts)
end
end
defp handle_incompatible_model(key, name, requested_api, opts) do
msg = "Model #{key} (#{name}) may not be available on #{requested_api}"
if Keyword.get(opts, :strict, false) do
suggestion = suggest_alternative(key, requested_api)
raise ArgumentError,
msg <>
". Use a universal model or one specific to #{requested_api}." <>
if(suggestion, do: " Suggested alternative: #{suggestion}", else: "")
else
Logger.warning("[Gemini.Config] #{msg}")
end
end
# Suggest alternative models when using incompatible model
defp suggest_alternative(:flash_lite_latest, :vertex_ai), do: ":flash_2_0_lite"
defp suggest_alternative(:flash_latest, :vertex_ai), do: ":flash_2_0"
defp suggest_alternative(:embedding, :vertex_ai), do: ":embedding_gemma"
defp suggest_alternative(:embedding_gemma, :gemini), do: ":embedding"
defp suggest_alternative(_, _), do: nil
@doc """
Get HTTP timeout in milliseconds.
"""
def timeout do
Application.get_env(:gemini_ex, :timeout, 120_000)
end
@doc """
Get the base URL for the current authentication type.
(Legacy function - now determined by auth strategy)
"""
def base_url do
case auth_config() do
%{type: :gemini, credentials: credentials} ->
Gemini.Auth.get_base_url(:gemini, credentials)
%{type: :vertex_ai, credentials: credentials} ->
Gemini.Auth.get_base_url(:vertex_ai, credentials)
_ ->
Application.get_env(
:gemini,
:base_url,
"https://generativelanguage.googleapis.com/v1beta"
)
end
end
@doc """
Validate that required configuration is present.
"""
def validate! do
case auth_config() do
nil ->
raise """
No authentication configured. Please set one of:
For Gemini API:
- Environment variable: GEMINI_API_KEY
- Application config: config :gemini, api_key: "your_api_key"
For Vertex AI:
- Environment variables: VERTEX_ACCESS_TOKEN, VERTEX_PROJECT_ID, VERTEX_LOCATION
- Environment variables: VERTEX_SERVICE_ACCOUNT, VERTEX_PROJECT_ID, VERTEX_LOCATION
- Application config: config :gemini, auth: %{type: :vertex_ai, credentials: %{...}}
"""
%{type: :gemini, credentials: %{api_key: nil}} ->
raise "Gemini API key is nil"
%{type: :vertex_ai, credentials: credentials} ->
validate_vertex_config!(credentials)
%{type: :gemini} ->
:ok
end
end
@doc """
Check if telemetry is enabled.
Determines whether telemetry events should be emitted based on the
application configuration. Telemetry is enabled by default unless
explicitly disabled.
## Configuration
Set `:telemetry_enabled` to `false` in your application config to disable:
config :gemini, telemetry_enabled: false
## Returns
- `true` - Telemetry is enabled (default)
- `false` - Telemetry is explicitly disabled
## Examples
iex> # Default behavior (telemetry enabled)
iex> Gemini.Config.telemetry_enabled?()
true
iex> # Explicitly disabled
iex> Application.put_env(:gemini, :telemetry_enabled, false)
iex> Gemini.Config.telemetry_enabled?()
false
iex> # Any other value defaults to enabled
iex> Application.put_env(:gemini, :telemetry_enabled, :maybe)
iex> Gemini.Config.telemetry_enabled?()
true
"""
@spec telemetry_enabled? :: boolean()
def telemetry_enabled? do
case Application.get_env(:gemini_ex, :telemetry_enabled) do
false -> false
_ -> true
end
end
@doc """
Get authentication configuration for a specific strategy.
## Parameters
- `strategy`: The authentication strategy (`:gemini` or `:vertex_ai`)
## Returns
- A map containing configuration for the specified strategy
- Returns empty map if no configuration found
## Examples
iex> Gemini.Config.get_auth_config(:gemini)
%{api_key: "your_api_key"}
iex> Gemini.Config.get_auth_config(:vertex_ai)
%{project_id: "your-project", location: "us-central1"}
"""
@spec get_auth_config(:gemini | :vertex_ai) :: map()
def get_auth_config(:gemini) do
app_auth = Application.get_env(:gemini, :auth)
legacy_app_auth = Application.get_env(:gemini_ex, :auth)
cond do
# App env via configure/2
match?(%{type: :gemini, credentials: %{api_key: _}}, app_auth) ->
%{credentials: %{api_key: api_key}} = app_auth
%{api_key: api_key}
match?(%{type: :gemini, credentials: %{api_key: _}}, legacy_app_auth) ->
%{credentials: %{api_key: api_key}} = legacy_app_auth
%{api_key: api_key}
# Legacy app env
is_binary(Application.get_env(:gemini_ex, :api_key)) ->
%{api_key: Application.get_env(:gemini_ex, :api_key)}
is_binary(Application.get_env(:gemini, :api_key)) ->
%{api_key: Application.get_env(:gemini, :api_key)}
# Direct env var
is_binary(gemini_api_key()) ->
%{api_key: gemini_api_key()}
true ->
%{}
end
end
def get_auth_config(:vertex_ai) do
env_config =
%{}
|> maybe_put(:project_id, vertex_project_id())
|> maybe_put(:location, vertex_location())
|> maybe_put(:access_token, vertex_access_token())
|> maybe_put(:service_account_key, vertex_service_account())
|> maybe_put(:quota_project_id, vertex_quota_project_id())
app_auth = Application.get_env(:gemini, :auth)
legacy_app_auth = Application.get_env(:gemini_ex, :auth)
legacy_vertex = Application.get_env(:gemini_ex, :vertex_ai, %{})
creds_from_app =
cond do
match?(%{type: :vertex_ai, credentials: %{}}, app_auth) ->
app_auth.credentials
match?(%{type: :vertex_ai, credentials: %{}}, legacy_app_auth) ->
legacy_app_auth.credentials
true ->
legacy_vertex
end
# Application material is the explicit account selection. Environment
# values may fill missing fields for standalone calls but cannot overwrite
# an explicitly configured account or token.
Map.merge(env_config, creds_from_app || %{})
end
def get_auth_config(_strategy) do
%{}
end
# Private functions for environment variable access
defp gemini_api_key do
get_env_non_empty("GEMINI_API_KEY")
end
defp vertex_access_token do
get_env_non_empty("VERTEX_ACCESS_TOKEN")
end
defp vertex_service_account do
get_env_non_empty("VERTEX_SERVICE_ACCOUNT") || get_env_non_empty("VERTEX_JSON_FILE")
end
defp vertex_quota_project_id do
get_env_non_empty("VERTEX_QUOTA_PROJECT_ID") ||
get_env_non_empty("GOOGLE_CLOUD_QUOTA_PROJECT")
end
defp vertex_project_id do
get_env_non_empty("VERTEX_PROJECT_ID") || get_env_non_empty("GOOGLE_CLOUD_PROJECT")
end
defp vertex_location do
get_env_non_empty("VERTEX_LOCATION") || get_env_non_empty("GOOGLE_CLOUD_LOCATION") ||
"us-central1"
end
# Returns nil for empty strings, so "" is treated as "not set"
defp get_env_non_empty(var) do
case Gemini.Env.get(var) do
nil -> nil
"" -> nil
value -> value
end
end
defp validate_vertex_config!(%{access_token: token, project_id: project, location: location})
when is_binary(token) and is_binary(project) and is_binary(location) do
:ok
end
defp validate_vertex_config!(%{
service_account_key: key,
project_id: project,
location: location
})
when is_binary(key) and is_binary(project) and is_binary(location) do
:ok
end
defp validate_vertex_config!(credentials) do
raise "Invalid Vertex AI configuration fields: #{inspect(Map.keys(credentials))}"
end
defp load_project_from_service_account(file_path) do
case File.read(file_path) do
{:ok, content} ->
case Jason.decode(content) do
{:ok, %{"project_id" => project_id}} -> {:ok, project_id}
{:ok, _} -> {:error, "No project_id found in service account file"}
{:error, reason} -> {:error, "Failed to parse JSON: #{reason}"}
end
{:error, reason} ->
{:error, "Failed to read file: #{reason}"}
end
end
end