Current section
Files
Jump to
Current section
Files
lib/gemini/apis/context_cache.ex
defmodule Gemini.APIs.ContextCache do
@moduledoc """
Context caching API for improved performance with long context.
Context caching allows you to cache large amounts of content (code, documents)
for reuse across multiple requests, reducing latency and cost.
## Usage
# Create a cached context
{:ok, cache} = Gemini.APIs.ContextCache.create(
[%Gemini.Types.Content{role: "user", parts: [%{text: large_content}]}],
display_name: "My Codebase",
model: "gemini-2.0-flash"
)
# Use cached context in requests
{:ok, response} = Gemini.generate("Analyze this code",
cached_content: cache.name
)
# Delete when done
:ok = Gemini.APIs.ContextCache.delete(cache.name)
## API Endpoints
- `POST /cachedContents` - Create cached content
- `GET /cachedContents` - List cached contents
- `GET /cachedContents/{name}` - Get specific cache
- `PATCH /cachedContents/{name}` - Update cache TTL
- `DELETE /cachedContents/{name}` - Delete cache
"""
alias Gemini.Client.HTTP
alias Gemini.Types.Content
@type cache_opts :: [
display_name: String.t(),
model: String.t(),
ttl: non_neg_integer(),
expire_time: DateTime.t()
]
@type cached_content :: %{
name: String.t(),
display_name: String.t() | nil,
model: String.t(),
create_time: String.t() | nil,
update_time: String.t() | nil,
expire_time: String.t() | nil,
usage_metadata: map() | nil
}
@doc """
Create a new cached content.
## Parameters
- `contents`: List of Content structs to cache
- `opts`: Options including:
- `:display_name` - Human-readable name (required)
- `:model` - Model to use (default: default model)
- `:ttl` - Time to live in seconds (default: 3600)
- `:expire_time` - Specific expiration DateTime
## Returns
- `{:ok, cached_content}` - Created cache metadata
- `{:error, reason}` - Failed to create cache
## Examples
{:ok, cache} = ContextCache.create(
[Content.text("Large document content...")],
display_name: "My Document",
model: "gemini-2.0-flash",
ttl: 7200
)
"""
@spec create([Content.t()] | [map()], cache_opts()) ::
{:ok, cached_content()} | {:error, term()}
def create(contents, opts \\ []) when is_list(contents) do
display_name =
Keyword.get(opts, :display_name) ||
raise ArgumentError, "display_name is required for cached content"
model = Keyword.get(opts, :model, Gemini.Config.default_model())
full_model_name = "models/#{model}"
# Build TTL specification
ttl_spec = build_ttl_spec(opts)
# Format contents for API
formatted_contents = format_contents(contents)
request_body =
%{
model: full_model_name,
displayName: display_name,
contents: formatted_contents
}
|> Map.merge(ttl_spec)
case HTTP.post("cachedContents", request_body, opts) do
{:ok, response} ->
{:ok, normalize_cache_response(response)}
{:error, reason} ->
{:error, reason}
end
end
@doc """
List all cached contents.
## Parameters
- `opts`: Options including:
- `:page_size` - Number of results per page
- `:page_token` - Pagination token
## Returns
- `{:ok, %{cached_contents: [cached_content()], next_page_token: String.t() | nil}}`
- `{:error, reason}`
"""
@spec list(keyword()) :: {:ok, map()} | {:error, term()}
def list(opts \\ []) do
query_params = []
query_params =
if opts[:page_size], do: [{:pageSize, opts[:page_size]} | query_params], else: query_params
query_params =
if opts[:page_token],
do: [{:pageToken, opts[:page_token]} | query_params],
else: query_params
path =
if query_params == [] do
"cachedContents"
else
query_string = URI.encode_query(query_params)
"cachedContents?#{query_string}"
end
case HTTP.get(path, opts) do
{:ok, response} ->
cached_contents =
response
|> Map.get("cachedContents", [])
|> Enum.map(&normalize_cache_response/1)
result = %{
cached_contents: cached_contents,
next_page_token: Map.get(response, "nextPageToken")
}
{:ok, result}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Get a specific cached content by name.
## Parameters
- `name`: Cache name (format: "cachedContents/{id}")
- `opts`: Request options
## Returns
- `{:ok, cached_content}` - Cache metadata
- `{:error, reason}` - Failed to get cache
"""
@spec get(String.t(), keyword()) :: {:ok, cached_content()} | {:error, term()}
def get(name, opts \\ []) when is_binary(name) do
case HTTP.get(name, opts) do
{:ok, response} ->
{:ok, normalize_cache_response(response)}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Update cache TTL.
## Parameters
- `name`: Cache name
- `opts`: Options including:
- `:ttl` - New TTL in seconds
- `:expire_time` - New expiration DateTime
## Returns
- `{:ok, cached_content}` - Updated cache metadata
- `{:error, reason}` - Failed to update
"""
@spec update(String.t(), keyword()) :: {:ok, cached_content()} | {:error, term()}
def update(name, opts \\ []) when is_binary(name) do
ttl_spec = build_ttl_spec(opts)
if map_size(ttl_spec) == 0 do
{:error, "Must specify either :ttl or :expire_time"}
else
# PATCH request
case HTTP.patch(name, ttl_spec, opts) do
{:ok, response} ->
{:ok, normalize_cache_response(response)}
{:error, reason} ->
{:error, reason}
end
end
end
@doc """
Delete a cached content.
## Parameters
- `name`: Cache name to delete
- `opts`: Request options
## Returns
- `:ok` - Successfully deleted
- `{:error, reason}` - Failed to delete
"""
@spec delete(String.t(), keyword()) :: :ok | {:error, term()}
def delete(name, opts \\ []) when is_binary(name) do
case HTTP.delete(name, opts) do
{:ok, _response} ->
:ok
{:error, reason} ->
{:error, reason}
end
end
# Private helpers
defp build_ttl_spec(opts) do
cond do
opts[:expire_time] ->
expire_str = DateTime.to_iso8601(opts[:expire_time])
%{expireTime: expire_str}
opts[:ttl] ->
%{ttl: "#{opts[:ttl]}s"}
true ->
# Default 1 hour TTL
%{ttl: "3600s"}
end
end
defp format_contents(contents) do
Enum.map(contents, fn
%Content{role: role, parts: parts} ->
%{
role: role,
parts: format_parts(parts)
}
%{role: role, parts: parts} ->
%{
role: role,
parts: format_parts(parts)
}
%{text: text} ->
%{
role: "user",
parts: [%{text: text}]
}
text when is_binary(text) ->
%{
role: "user",
parts: [%{text: text}]
}
end)
end
defp format_parts(parts) when is_list(parts) do
Enum.map(parts, fn
%Gemini.Types.Part{text: text} when is_binary(text) ->
%{text: text}
%{text: text} when is_binary(text) ->
%{text: text}
%Gemini.Types.Part{inline_data: %{data: data, mime_type: mime}} ->
%{inlineData: %{data: data, mimeType: mime}}
%{inline_data: %{data: data, mime_type: mime}} ->
%{inlineData: %{data: data, mimeType: mime}}
other ->
other
end)
end
defp normalize_cache_response(response) when is_map(response) do
%{
name: Map.get(response, "name"),
display_name: Map.get(response, "displayName"),
model: Map.get(response, "model"),
create_time: Map.get(response, "createTime"),
update_time: Map.get(response, "updateTime"),
expire_time: Map.get(response, "expireTime"),
usage_metadata: normalize_usage_metadata(Map.get(response, "usageMetadata"))
}
end
defp normalize_usage_metadata(nil), do: nil
defp normalize_usage_metadata(metadata) when is_map(metadata) do
%{
total_token_count: Map.get(metadata, "totalTokenCount"),
cached_content_token_count: Map.get(metadata, "cachedContentTokenCount")
}
end
end