Packages

Protocol-based AI adapter foundation for Elixir. Provides unified abstractions for gemini_ex, claude_agent_sdk, and codex_sdk with automatic fallback support, runtime capability detection, and built-in telemetry.

Current section

Files

Jump to
altar_ai lib altar ai adapters gemini.ex
Raw

lib/altar/ai/adapters/gemini.ex

defmodule Altar.AI.Adapters.Gemini do
@moduledoc """
Gemini adapter wrapping gemini_ex.
Provides protocol implementations for Gemini AI capabilities including
text generation and embeddings.
"""
defstruct opts: []
@type t :: %__MODULE__{opts: keyword()}
@doc """
Create a new Gemini adapter.
## Options
- `:api_key` - Gemini API key (defaults to GEMINI_API_KEY env var)
- `:model` - Default model to use (e.g., "gemini-pro")
- Other options passed through to Gemini SDK
## Examples
iex> Altar.AI.Adapters.Gemini.new(model: "gemini-pro")
%Altar.AI.Adapters.Gemini{opts: [model: "gemini-pro"]}
"""
def new(opts \\ []), do: %__MODULE__{opts: opts}
# Check if SDK available at compile time
@gemini_available Code.ensure_loaded?(Gemini)
@doc """
Check if the Gemini SDK is available.
"""
def available?, do: @gemini_available
end
# Only implement protocols if Gemini SDK is available
if Code.ensure_loaded?(Gemini) do
defimpl Altar.AI.Generator, for: Altar.AI.Adapters.Gemini do
alias Altar.AI.{Response, Error, Telemetry}
def generate(%{opts: opts}, prompt, call_opts) do
merged_opts = Keyword.merge(opts, call_opts)
Telemetry.span(:generate, %{provider: :gemini, prompt_length: String.length(prompt)}, fn ->
model = merged_opts[:model] || "gemini-pro"
case Gemini.generate_content(model, prompt, merged_opts) do
{:ok, response} ->
{:ok,
%Response{
content: extract_content(response),
model: model,
provider: :gemini,
finish_reason: :stop,
tokens: extract_tokens(response)
}}
{:error, error} ->
{:error, Error.from_gemini_error(error)}
end
end)
end
def stream(%{opts: opts}, prompt, call_opts) do
merged_opts = Keyword.merge(opts, call_opts)
Telemetry.span(:stream, %{provider: :gemini, prompt_length: String.length(prompt)}, fn ->
model = merged_opts[:model] || "gemini-pro"
case Gemini.stream_generate_content(model, prompt, merged_opts) do
{:ok, stream} ->
{:ok, stream}
{:error, error} ->
{:error, Error.from_gemini_error(error)}
end
end)
end
defp extract_content(response) do
case response do
%{candidates: [%{content: %{parts: [%{text: text} | _]}} | _]} -> text
%{text: text} -> text
_ -> ""
end
end
defp extract_tokens(response) do
case response do
%{usage_metadata: usage} ->
%{
prompt: Map.get(usage, :prompt_token_count, 0),
completion: Map.get(usage, :candidates_token_count, 0),
total: Map.get(usage, :total_token_count, 0)
}
_ ->
%{prompt: 0, completion: 0, total: 0}
end
end
end
defimpl Altar.AI.Embedder, for: Altar.AI.Adapters.Gemini do
alias Altar.AI.{Error, Telemetry}
def embed(%{opts: opts}, text, call_opts) do
merged_opts = Keyword.merge(opts, call_opts)
Telemetry.span(:embed, %{provider: :gemini}, fn ->
model = merged_opts[:model] || "text-embedding-004"
case Gemini.embed_content(model, text, merged_opts) do
{:ok, response} ->
extract_vector(response)
{:error, error} ->
{:error, Error.from_gemini_error(error)}
end
end)
end
def batch_embed(%{opts: opts}, texts, call_opts) do
merged_opts = Keyword.merge(opts, call_opts)
Telemetry.span(:batch_embed, %{provider: :gemini, count: length(texts)}, fn ->
model = merged_opts[:model] || "text-embedding-004"
case Gemini.batch_embed_contents(model, texts, merged_opts) do
{:ok, response} ->
extract_vectors(response)
{:error, error} ->
{:error, Error.from_gemini_error(error)}
end
end)
end
# Extract embedding vector from Gemini response
defp extract_vector(%{embedding: %{values: values}}), do: {:ok, values}
defp extract_vector(%{embedding: values}) when is_list(values), do: {:ok, values}
defp extract_vector(_),
do: {:error, Error.new(:invalid_request, "Invalid embedding response", provider: :gemini)}
# Extract multiple embedding vectors from batch response
defp extract_vectors(%{embeddings: embeddings}) when is_list(embeddings) do
vectors =
Enum.map(embeddings, fn
%{values: values} -> values
values when is_list(values) -> values
_ -> []
end)
{:ok, vectors}
end
defp extract_vectors(_),
do:
{:error,
Error.new(:invalid_request, "Invalid batch embedding response", provider: :gemini)}
end
end