{"id":32167827,"url":"https://github.com/nshkrdotcom/gemini_ex","last_synced_at":"2026-02-20T00:32:00.376Z","repository":{"id":297741950,"uuid":"997717307","full_name":"nshkrdotcom/gemini_ex","owner":"nshkrdotcom","description":"Elixir Interface / Adapter for Google Gemini LLM, for both AI Studio and Vertex AI","archived":false,"fork":false,"pushed_at":"2026-02-14T20:36:57.000Z","size":2087,"stargazers_count":25,"open_issues_count":1,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-15T04:31:48.424Z","etag":null,"topics":["ai","ai-integration","ai-studio","api-client","beam","elixir","erlang-vm","functional-programming","gemini","genai","generative-ai","google-gemini","llm","llm-client","nshkr-ai-sdk","otp","sdk","vertex-ai"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/nshkrdotcom.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-06-07T03:11:08.000Z","updated_at":"2026-02-14T20:37:00.000Z","dependencies_parsed_at":"2025-07-07T18:28:57.615Z","dependency_job_id":"5e3d1818-2e8f-4f18-b6fc-9974c2b9ff9a","html_url":"https://github.com/nshkrdotcom/gemini_ex","commit_stats":null,"previous_names":["nshkrdotcom/gemini_ex"],"tags_count":34,"template":false,"template_full_name":null,"purl":"pkg:github/nshkrdotcom/gemini_ex","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fgemini_ex","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fgemini_ex/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fgemini_ex/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fgemini_ex/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/nshkrdotcom","download_url":"https://codeload.github.com/nshkrdotcom/gemini_ex/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/nshkrdotcom%2Fgemini_ex/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29637410,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-19T22:32:43.237Z","status":"ssl_error","status_checked_at":"2026-02-19T22:32:38.330Z","response_time":117,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai","ai-integration","ai-studio","api-client","beam","elixir","erlang-vm","functional-programming","gemini","genai","generative-ai","google-gemini","llm","llm-client","nshkr-ai-sdk","otp","sdk","vertex-ai"],"created_at":"2025-10-21T15:38:38.722Z","updated_at":"2026-02-20T00:32:00.368Z","avatar_url":"https://github.com/nshkrdotcom.png","language":"Elixir","funding_links":[],"categories":["LLM Clients and APIs","Generative AI"],"sub_categories":["LLM Tools"],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/logo.svg\" alt=\"Gemini Elixir Client Logo\" width=\"200\" height=\"200\"\u003e\n\u003c/p\u003e\n\n# Gemini Elixir Client\n\n[![CI](https://github.com/nshkrdotcom/gemini_ex/actions/workflows/elixir.yaml/badge.svg)](https://github.com/nshkrdotcom/gemini_ex/actions/workflows/elixir.yaml)\n[![Elixir](https://img.shields.io/badge/elixir-1.18.3-purple.svg)](https://elixir-lang.org)\n[![OTP](https://img.shields.io/badge/otp-27.3.3-blue.svg)](https://www.erlang.org)\n[![Hex.pm](https://img.shields.io/hexpm/v/gemini_ex.svg)](https://hex.pm/packages/gemini_ex)\n[![Documentation](https://img.shields.io/badge/docs-hexdocs-purple.svg)](https://hexdocs.pm/gemini_ex)\n[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/nshkrdotcom/gemini_ex/blob/main/LICENSE)\n\nA comprehensive Elixir client for Google's Gemini AI API with dual authentication support, advanced streaming capabilities, type safety, and built-in telemetry.\n\n## Features\n\n- **Automatic Tool Calling**: A seamless, Python-SDK-like experience that automates the entire multi-turn tool-calling loop\n- **Built-in Tools (Gemini 3)**: Google Search, URL Context, and Code Execution via `tools:`\n- **Dual Authentication**: Seamless support for both Gemini API keys and Vertex AI OAuth/Service Accounts\n- **Application Default Credentials (ADC)**: Zero-config GCP auth with automatic discovery and token refresh (NEW in v0.8.x!)\n- **Advanced Streaming**: Production-grade Server-Sent Events streaming with real-time processing\n- **Interactions API**: Stateful interactions (CRUD), background execution, SSE streaming, and resumption\n- **Live API (WebSocket)**: Bidirectional, low-latency sessions with real-time input/output, native audio with affective dialog and proactivity (Enhanced in v0.9.0!)\n- **Automatic Rate Limiting**: Built-in rate limit handling with retries, concurrency gating, and adaptive backoff\n- **Files API**: Upload, manage, and use files with Gemini models for multimodal content (NEW in v0.7.0!)\n- **File Search Stores**: RAG store creation, ingestion, and semantic search (NEW in v0.8.x!)\n- **Documents API**: Manage indexed documents inside stores for RAG workflows (NEW in v0.7.0!)\n- **Batches API**: Submit large numbers of requests with 50% cost savings (NEW in v0.7.0!)\n- **Operations API**: Track long-running operations like video generation (NEW in v0.7.0!)\n- **Tunings (Fine-Tuning)**: Create, monitor, and manage tuned models (NEW in v0.8.x!)\n- **Image \u0026 Video Generation**: Imagen/Veo APIs for text-to-image, editing, upscaling, and video generation (NEW in v0.8.x!)\n- **Embeddings with MRL**: Text embeddings with Matryoshka Representation Learning, normalization, and distance metrics\n- **Async Batch Embeddings**: Production-scale embedding generation with 50% cost savings\n- **Type Safety**: Complete type definitions with runtime validation\n- **Built-in Telemetry**: Comprehensive observability and metrics out of the box\n- **Chat Sessions \u0026 System Instructions**: Multi-turn conversation management with persistent guardrails\n- **Flexible Multimodal Input**: Intuitive formats for images/text with automatic MIME detection\n- **Thinking Budget Control**: Optimize costs by controlling thinking token usage\n- **Gemini 3 Support**: `thinking_level` (`:minimal`, `:low`, `:medium`, `:high`), image generation, media resolution, thought signatures (NEW in v0.5.x!)\n- **Context Caching**: Cache large contexts once and reuse by ID (NEW in v0.6.0!)\n- **Complete Generation Config**: Full support for all generation config options including structured output\n- **Production Ready**: Robust error handling, retry logic, and performance optimizations\n- **Flexible Configuration**: Environment variables, application config, and per-request overrides\n\n## ALTAR Integration: The Path to Production\n\n`gemini_ex` is the first project to integrate with the **ALTAR Productivity Platform**, a system designed to bridge the gap between local AI development and enterprise-grade production deployment.\n\nWe've adopted ALTAR's `LATER` protocol to provide a best-in-class local tool-calling experience. This is the first step in a long-term vision to offer a seamless \"promotion path\" for your AI tools, from local testing to a secure, scalable, and governed production environment via ALTAR's `GRID` protocol.\n\n**[Learn the full story behind our integration in `ALTAR_INTEGRATION.md`](guides/ALTAR_INTEGRATION.md)**\n\n## Installation\n\nAdd `gemini` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:gemini_ex, \"~\u003e 0.9.1\"}\n  ]\nend\n```\n\n## Quick Start\n\n### Basic Configuration\n\nConfigure your API key in `config/runtime.exs`:\n\n```elixir\nimport Config\n\nconfig :gemini_ex,\n  api_key: System.get_env(\"GEMINI_API_KEY\")\n```\n\nOr set the environment variable:\n\n```bash\nexport GEMINI_API_KEY=\"your_api_key_here\"\n```\n\n### Simple Content Generation\n\n```elixir\n# Basic text generation\n{:ok, response} = Gemini.generate(\"Tell me about Elixir programming\")\n{:ok, text} = Gemini.extract_text(response)\nIO.puts(text)\n\n# With options\n{:ok, response} = Gemini.generate(\"Explain quantum computing\", [\n  model: \"gemini-flash-lite-latest\",\n  temperature: 0.7,\n  max_output_tokens: 1000\n])\n\n# Advanced generation config with structured output\n{:ok, response} = Gemini.generate(\"Analyze this topic and provide a summary\", [\n  response_json_schema: %{\n    \"type\" =\u003e \"object\",\n    \"properties\" =\u003e %{\n      \"summary\" =\u003e %{\"type\" =\u003e \"string\"},\n      \"key_points\" =\u003e %{\"type\" =\u003e \"array\", \"items\" =\u003e %{\"type\" =\u003e \"string\"}},\n      \"confidence\" =\u003e %{\"type\" =\u003e \"number\"}\n    }\n  },\n  response_mime_type: \"application/json\",\n  temperature: 0.3\n])\n```\n\n### System Instructions\n\nSet persistent guardrails that apply across an entire call or chat session without bloating your message history:\n\n```elixir\n{:ok, response} =\n  Gemini.generate(\"List three tips for interviewing junior engineers\",\n    system_instruction: \"Be concise, avoid markdown, and keep answers under 40 words.\"\n  )\n\n{:ok, text} = Gemini.extract_text(response)\n# Works the same with `Gemini.create_chat_session/1` and streaming calls via the `system_instruction:` option.\n```\n\n### Simple Tool Calling\n\n```elixir\n# Define a simple tool\ndefmodule WeatherTool do\n  def get_weather(%{\"location\" =\u003e location}) do\n    %{location: location, temperature: 22, condition: \"sunny\"}\n  end\nend\n\n# Create and register the tool\n{:ok, weather_declaration} = Altar.ADM.new_function_declaration(%{\n  name: \"get_weather\",\n  description: \"Gets weather for a location\",\n  parameters: %{\n    type: \"object\",\n    properties: %{location: %{type: \"string\", description: \"City name\"}},\n    required: [\"location\"]\n  }\n})\n\nGemini.Tools.register(weather_declaration, \u0026WeatherTool.get_weather/1)\n\n# Use the tool automatically - the model will call it as needed\n{:ok, response} = Gemini.generate_content_with_auto_tools(\n  \"What's the weather like in Tokyo?\",\n  tools: [weather_declaration]\n)\n\n{:ok, text} = Gemini.extract_text(response)\nIO.puts(text) # \"The weather in Tokyo is sunny with a temperature of 22°C.\"\n```\n\n### Advanced Streaming\n\n```elixir\n# Start a streaming session\n{:ok, stream_id} = Gemini.stream_generate(\"Write a long story about AI\", [\n  on_chunk: fn chunk -\u003e IO.write(chunk) end,\n  on_complete: fn -\u003e IO.puts(\"\\nStream complete!\") end,\n  on_error: fn error -\u003e IO.puts(\"Error: #{inspect(error)}\") end\n])\n\n# Stream management\nGemini.Streaming.pause_stream(stream_id)\nGemini.Streaming.resume_stream(stream_id)\nGemini.Streaming.stop_stream(stream_id)\n```\n\nStreaming knobs: pass `timeout:` (per attempt, default `config :gemini_ex, :timeout` = 120_000), `max_retries:` (default 3), `max_backoff_ms:` (default 10_000), and `connect_timeout:` (default 5_000). Manager cleanup delay can be tuned via `config :gemini_ex, :streaming, cleanup_delay_ms: ...`.\n\n### Interactions Quick Start\n\n```elixir\nalias Gemini.APIs.Interactions\nalias Gemini.Types.Interactions.Events.ContentDelta\nalias Gemini.Types.Interactions.DeltaTextDelta\n\n{:ok, stream} =\n  Interactions.create(\"Write a short poem about Elixir\",\n    model: \"gemini-2.5-flash\",\n    stream: true\n  )\n\nfor event \u003c- stream do\n  case event do\n    %ContentDelta{delta: %DeltaTextDelta{text: text}} when is_binary(text) -\u003e\n      IO.write(text)\n\n    _ -\u003e\n      :ok\n  end\nend\n```\n\nSee `docs/guides/interactions.md` for CRUD, resumption (`last_event_id`), and background/cancel/delete examples.\n\n### Live API (WebSocket)\n\nReal-time bidirectional streaming for voice, video, and text interactions. The v0.9.0 release upgrades to v1beta as the default API version while supporting v1alpha for advanced native audio features.\n\n#### Model Resolution\n\nLive API model availability varies by API key and regional rollout. Use `Gemini.Live.Models.resolve/1` to automatically select an available model:\n\n```elixir\nalias Gemini.Live.Models\n\n# Resolve best available model for your key\ntext_model = Models.resolve(:text)   # For text responses\naudio_model = Models.resolve(:audio) # For native audio responses\n```\n\n#### Basic Usage\n\n```elixir\nalias Gemini.Live.{Models, Session}\n\n{:ok, session} = Session.start_link(\n  model: Models.resolve(:text),\n  auth: :gemini,\n  generation_config: %{response_modalities: [\"TEXT\"]},\n  on_message: fn msg -\u003e IO.inspect(msg) end\n)\n\n:ok = Session.connect(session)\n:ok = Session.send_client_content(session, \"Hello!\")\n\n# Messages delivered via on_message callback\n# Close when done\nSession.close(session)\n```\n\n#### Audio Streaming with Native Audio Features\n\n```elixir\nalias Gemini.Live.{Models, Session}\n\n{:ok, session} = Session.start_link(\n  model: Models.resolve(:audio),\n  auth: :gemini,\n  api_version: \"v1alpha\",  # Required for affective dialog and proactivity\n  generation_config: %{response_modalities: [\"AUDIO\"]},\n  input_audio_transcription: %{},\n  output_audio_transcription: %{},\n  enable_affective_dialog: true,  # Emotion-aware responses\n  proactivity: %{proactive_audio: true},  # Model can choose not to respond\n  on_message: fn msg -\u003e handle_audio_response(msg) end\n)\n\n# Send audio chunks (16-bit PCM, 16kHz, mono)\nSession.send_realtime_input(session, audio: %{\n  data: pcm_data,\n  mime_type: \"audio/pcm;rate=16000\"\n})\n```\n\n#### Function Calling\n\n```elixir\nalias Gemini.Live.{Models, Session}\n\ntools = [\n  %{function_declarations: [\n    %{name: \"get_weather\", description: \"Get weather\", parameters: %{...}}\n  ]}\n]\n\n{:ok, session} = Session.start_link(\n  model: Models.resolve(:text),\n  tools: tools,\n  on_tool_call: fn %{function_calls: calls} -\u003e\n    responses = Enum.map(calls, \u0026execute_function/1)\n    {:tool_response, responses}  # Return to send automatically\n  end\n)\n```\n\nSee the [Live API Guide](docs/guides/live_api.md) for complete documentation including voice activity detection, session resumption, thinking budgets, and context window compression.\n\n### Rate Limiting \u0026 Concurrency (built-in)\n\n- Enabled by default: atomic budget reservations happen before dispatch; non-blocking mode returns `{:error, {:rate_limited, retry_at, details}}` with `retry_at` set to the window end.\n- Oversized requests (estimate exceeds budget) return `reason: :over_budget, request_too_large: true` immediately—no retry loop; surplus budget is returned after responses, shortfalls are charged.\n- Shared retry window with jittered release for 429s; telemetry fires `retry_window_set/hit/release` so callers can fan out retries safely.\n- Cached context tokens are counted toward budgets. When you precompute cache size, you can pass `estimated_cached_tokens:` alongside `estimated_input_tokens:` to budget correctly before the API reports usage.\n- Optional `max_budget_wait_ms` caps how long blocking calls sleep for a full window; if the cap is hit and the window is still full, you get a `rate_limited` error with `retry_at` set to the actual window end.\n- Concurrency gate: serialized permits via `max_concurrency_per_model` plus `permit_timeout_ms` (default `:infinity`, per-call override). `non_blocking: true` is the fail-fast path (returns `{:error, :no_permit_available}` immediately).\n- Streaming uses the same limiter: permits are held for the full stream, and streams may return `{:error, {:rate_limited, retry_at, details}}` if over budget or out of permits.\n- Partition the gate with `concurrency_key:` (e.g., tenant/location) to avoid cross-tenant starvation; default key is the model name.\n- Permit leak protection: holders are monitored; if a holder dies without releasing, its permits are reclaimed automatically.\n\nModel aliases: resolve the built-in use-case aliases via `Gemini.Config.model_for_use_case/2` (e.g., `:cache_context`, `:report_section`, `:fast_path`) to avoid scattering raw model strings and to respect the recommended token minima for each use case.\n\n### Timeouts (HTTP \u0026 Streaming)\n\n- Global HTTP/stream timeout default is 120_000ms via `config :gemini_ex, :timeout`.\n- Per-call override: `timeout:` on any request/stream.\n- Streaming extras: `max_retries`, `max_backoff_ms` (default 10_000), `connect_timeout` (default 5_000).\n\n### Advanced Generation Configuration\n\n```elixir\n# Using GenerationConfig struct for complex configurations\nconfig = %Gemini.Types.GenerationConfig{\n  temperature: 0.7,\n  max_output_tokens: 2000,\n  response_json_schema: %{\n    \"type\" =\u003e \"object\",\n    \"properties\" =\u003e %{\n      \"analysis\" =\u003e %{\"type\" =\u003e \"string\"},\n      \"recommendations\" =\u003e %{\"type\" =\u003e \"array\", \"items\" =\u003e %{\"type\" =\u003e \"string\"}}\n    }\n  },\n  response_mime_type: \"application/json\",\n  stop_sequences: [\"END\", \"COMPLETE\"],\n  presence_penalty: 0.5,\n  frequency_penalty: 0.3\n}\n\n{:ok, response} = Gemini.generate(\"Analyze market trends\", generation_config: config)\n\n# All generation config options are supported:\n{:ok, response} = Gemini.generate(\"Creative writing task\", [\n  temperature: 0.9,           # Creativity level\n  top_p: 0.8,                # Nucleus sampling\n  top_k: 40,                 # Top-k sampling\n  candidate_count: 3,        # Multiple responses\n  response_logprobs: true,   # Include probabilities\n  logprobs: 5               # Token probabilities\n])\n```\n\n### Structured JSON Outputs\n\nGenerate responses that guarantee adherence to a specific JSON Schema:\n\n```elixir\n# Define your schema\nschema = %{\n  \"type\" =\u003e \"object\",\n  \"properties\" =\u003e %{\n    \"answer\" =\u003e %{\"type\" =\u003e \"string\"},\n    \"confidence\" =\u003e %{\n      \"type\" =\u003e \"number\",\n      \"minimum\" =\u003e 0.0,\n      \"maximum\" =\u003e 1.0\n    }\n  }\n}\n\n# Use the convenient helper\nconfig = Gemini.Types.GenerationConfig.structured_json(schema)\n\n{:ok, response} = Gemini.generate(\n  \"What is the capital of France?\",\n  model: \"gemini-2.5-flash\",\n  generation_config: config\n)\n\n{:ok, text} = Gemini.extract_text(response)\n{:ok, data} = Jason.decode(text)\n# =\u003e %{\"answer\" =\u003e \"Paris\", \"confidence\" =\u003e 0.99}\n```\n\n`GenerationConfig.structured_json/2` uses `response_json_schema` (standard JSON Schema)\nby default. If you need Gemini's internal schema format, pass `schema_type: :response_schema`:\n\n```elixir\nconfig =\n  GenerationConfig.structured_json(%{\"type\" =\u003e \"OBJECT\"}, schema_type: :response_schema)\n```\n\n**New Features (November 2025):**\n- `anyOf` for union types\n- `$ref` for recursive schemas\n- `minimum`/`maximum` for numeric constraints\n- `prefixItems` for tuple-like arrays\n\nFor Gemini 2.0 models, add explicit property ordering:\n\n```elixir\nconfig =\n  GenerationConfig.structured_json(schema)\n  |\u003e GenerationConfig.property_ordering([\"answer\", \"confidence\"])\n```\n\nSee [Structured Outputs Guide](docs/guides/structured_outputs.md) for details.\n\n## Context Caching (New in v0.6.0!)\n\nCache large prompts/contexts once and reuse the cache ID to avoid resending bytes:\n\n```elixir\nalias Gemini.Types.Content\n\n# Create a cache from your content (supports system_instruction, tools, fileUri)\n{:ok, cache} =\n  Gemini.create_cache(\n    [\n      Content.text(\"long document or conversation history\"),\n      %Content{role: \"user\", parts: [%{file_uri: \"gs://cloud-samples-data/generative-ai/pdf/scene.pdf\"}]}\n    ],\n    display_name: \"My Cache\",\n    model: \"gemini-2.5-flash\",  # Use models that support caching\n    system_instruction: \"Answer in one concise paragraph.\"\n  )\n\n# Use cached content by name (e.g., \"cachedContents/123\")\n{:ok, response} =\n  Gemini.generate(\"Summarize the cached content\",\n    cached_content: cache.name,\n    model: \"gemini-2.5-flash\"\n  )\n```\n\n**TTL defaults:** The default cache TTL is configurable via `config :gemini_ex, :context_cache, default_ttl_seconds: ...` (defaults to 3_600). You can also override per call with `default_ttl_seconds:` or pass `:ttl`/`:expire_time` explicitly.\n\n**Models that support explicit caching:**\n- `gemini-2.5-flash`\n- `gemini-2.5-flash-lite`\n- `gemini-2.5-pro`\n- `gemini-2.0-flash-001`\n- `gemini-2.0-flash-lite-001`\n- `gemini-3-pro-preview`\n- `gemini-3-flash-preview`\n\nYou can list, get, update TTL, and delete caches via the top-level `Gemini.*cache*` helpers or `Gemini.APIs.ContextCache.*`. Vertex AI names are auto-expanded when `auth: :vertex_ai` or configured credentials are present.\n\n## Files API (New in v0.7.0!)\n\nUpload and manage files for use with Gemini models. Perfect for multimodal content generation with images, videos, audio, and documents.\n\n```elixir\nalias Gemini.APIs.Files\nalias Gemini.Types.File\n\n# Upload a file\n{:ok, file} = Files.upload(\"path/to/image.png\")\n\n# Wait for processing (videos/large files)\n{:ok, ready} = Files.wait_for_processing(file.name)\n\n# Use in content generation\n{:ok, response} = Gemini.generate([\n  \"What's in this image?\",\n  %{file_uri: ready.uri, mime_type: ready.mime_type}\n])\n\n# List all files\n{:ok, files} = Files.list_all()\n\n# Clean up\n:ok = Files.delete(file.name)\n```\n\n**Key Features:**\n- Resumable uploads with progress tracking\n- Support for images, videos, audio, and documents\n- Automatic MIME type detection\n- 48-hour file expiration\n\nSee [Files API Guide](docs/guides/files.md) for complete documentation.\n\n## File Search Stores (New in v0.8.x!)\n\nCreate semantic search stores for RAG and ground model responses with your own data (Vertex AI only).\n\n```elixir\nalias Gemini.APIs.FileSearchStores\nalias Gemini.Types.CreateFileSearchStoreConfig\n\n# Create and activate a store\nconfig = %CreateFileSearchStoreConfig{display_name: \"Support KB\"}\n{:ok, store} = FileSearchStores.create(config, auth: :vertex_ai)\n{:ok, active_store} = FileSearchStores.wait_for_active(store.name)\n\n# Upload documents directly to the store and wait for indexing\n{:ok, doc} =\n  FileSearchStores.upload_to_store(active_store.name, \"docs/faq.pdf\",\n    display_name: \"Support FAQ\",\n    auth: :vertex_ai\n  )\n\n{:ok, _} = FileSearchStores.wait_for_document(doc.name, auth: :vertex_ai)\n\n# Use the store to ground a generation request\n{:ok, response} =\n  Gemini.generate_content(\n    \"What is the warranty policy for the Pro model?\",\n    tools: [%{file_search_stores: [active_store.name]}],\n    auth: :vertex_ai\n  )\n```\n\nKey features: automatic chunking/indexing, upload/import existing Files API uploads or GCS URIs, list/delete stores, and helpers to wait for readiness.\n\n## Documents API (New in v0.7.0!)\n\nManage the documents inside your File Search Stores.\n\n```elixir\nalias Gemini.APIs.Documents\n\n# List and inspect documents\n{:ok, page} = Documents.list(\"ragStores/support-kb\", auth: :vertex_ai)\n{:ok, doc} = Documents.get(\"ragStores/support-kb/documents/doc123\", auth: :vertex_ai)\n\n# Wait for processing and clean up\n{:ok, ready_doc} = Documents.wait_for_processing(doc.name, on_status: \u0026IO.inspect/1, auth: :vertex_ai)\n:ok = Documents.delete(ready_doc.name, auth: :vertex_ai)\n```\n\nList helpers (`list_all/2`) collapse pagination, and wait helpers make it easy to block until documents are indexed.\n\n## Batches API (New in v0.7.0!)\n\nSubmit large batches of requests with 50% cost savings. Ideal for bulk processing, overnight jobs, and high-volume workloads.\n\n```elixir\nalias Gemini.APIs.{Files, Batches}\nalias Gemini.Types.BatchJob\n\n# 1. Upload input file (JSONL format)\n{:ok, input} = Files.upload(\"requests.jsonl\")\n\n# 2. Create batch job\n{:ok, batch} = Batches.create(\"gemini-2.5-flash\",\n  file_name: input.name,\n  display_name: \"My Batch\"\n)\n\n# 3. Wait for completion with progress\n{:ok, completed} = Batches.wait(batch.name,\n  on_progress: fn b -\u003e\n    if progress = BatchJob.get_progress(b) do\n      IO.puts(\"Progress: #{Float.round(progress, 1)}%\")\n    end\n  end\n)\n\n# 4. Check results\nif BatchJob.succeeded?(completed) do\n  IO.puts(\"Completed #{completed.completion_stats.success_count} requests\")\nend\n```\n\n**Key Features:**\n- 50% cost savings vs interactive API\n- File-based or inline request input\n- GCS and BigQuery integration (Vertex AI)\n- Comprehensive job management\n\nSee [Batches API Guide](docs/guides/batches.md) for complete documentation.\n\n## Operations API (New in v0.7.0!)\n\nTrack and manage long-running operations like video generation, file imports, and model tuning.\n\n```elixir\nalias Gemini.APIs.Operations\nalias Gemini.Types.Operation\n\n# Check operation status\n{:ok, op} = Operations.get(\"operations/abc123\")\nIO.puts(\"Done: #{op.done}\")\n\n# Wait for completion with exponential backoff\n{:ok, completed} = Operations.wait_with_backoff(\"operations/abc123\",\n  initial_delay: 1_000,\n  max_delay: 60_000,\n  timeout: 3_600_000,\n  on_progress: fn op -\u003e\n    if progress = Operation.get_progress(op) do\n      IO.puts(\"Progress: #{progress}%\")\n    end\n  end\n)\n\nif Operation.succeeded?(completed) do\n  IO.inspect(completed.response)\nend\n```\n\n**Key Features:**\n- Simple and exponential backoff polling\n- Progress tracking callbacks\n- Cancel and delete operations\n- Comprehensive state helpers\n\nSee [Operations API Guide](docs/guides/operations.md) for complete documentation.\n\n## Tunings API (New in v0.8.x!)\n\nFine-tune base models with supervised datasets (Vertex AI).\n\n```elixir\nalias Gemini.APIs.Tunings\nalias Gemini.Types.Tuning.CreateTuningJobConfig\n\nconfig = %CreateTuningJobConfig{\n  base_model: \"gemini-2.5-flash-001\",\n  tuned_model_display_name: \"support-bot\",\n  training_dataset_uri: \"gs://bucket/train.jsonl\",\n  validation_dataset_uri: \"gs://bucket/val.jsonl\",\n  epoch_count: 3,\n  learning_rate_multiplier: 1.0,\n  adapter_size: \"x1\"\n}\n\n{:ok, job} = Tunings.tune(config, auth: :vertex_ai)\n{:ok, completed} = Tunings.wait_for_completion(job.name, auth: :vertex_ai)\nIO.puts(\"Tuned model: #{completed.tuned_model}\")\n```\n\nYou also get `list/1`, `list_all/1`, `get/2`, and `cancel/2` helpers plus polling and progress callbacks.\n\n## RegisterFiles API (Gemini API only)\n\nRegister existing GCS files with the Gemini API without uploading. Ideal for large files already in Cloud Storage.\n\n```elixir\nalias Gemini.APIs.Files\n\n# GCS credentials with read access to the bucket\ncredentials = %{\n  \"type\" =\u003e \"service_account\",\n  \"client_email\" =\u003e \"...\",\n  \"private_key\" =\u003e \"...\",\n  # ... other service account fields\n}\n\n# Register GCS files\n{:ok, response} = Files.register_files(\n  [\"gs://my-bucket/documents/report.pdf\", \"gs://my-bucket/images/photo.jpg\"],\n  credentials: credentials\n)\n\n# Use registered files in generation\nEnum.each(response.files, fn file -\u003e\n  IO.puts(\"Registered: #{file.name} - #{file.uri}\")\nend)\n\nfile = hd(response.files)\n{:ok, response} = Gemini.generate([\n  \"Summarize this document\",\n  %{file_uri: file.uri, mime_type: file.mime_type}\n])\n```\n\n**Note:** This feature is only available in the Gemini Developer API, not Vertex AI. The credentials must have read access to the GCS bucket.\n\n## Model Armor (Vertex AI only)\n\nEnterprise content filtering with centralized policy management. Apply Model Armor templates to filter prompt and response content.\n\n```elixir\nalias Gemini.Types.ModelArmorConfig\n\nconfig = %ModelArmorConfig{\n  prompt_template_name: \"projects/my-project/locations/us-central1/templates/prompt-filter\",\n  response_template_name: \"projects/my-project/locations/us-central1/templates/response-filter\"\n}\n\n# Use in generate request (Vertex AI only)\n{:ok, response} = Gemini.generate(\"Hello world\",\n  auth: :vertex_ai,\n  model_armor_config: config\n)\n```\n\n**Important:**\n- Model Armor is only supported in Vertex AI, not the Gemini Developer API\n- `model_armor_config` and `safety_settings` are mutually exclusive - you cannot use both\n\n### Multi-turn Conversations\n\n```elixir\n# Create a chat session\n{:ok, session} = Gemini.create_chat_session([\n  model: \"gemini-flash-lite-latest\",\n  system_instruction: \"You are a helpful programming assistant.\"\n])\n\n# Send messages\n{:ok, response1} = Gemini.send_message(session, \"What is functional programming?\")\n{:ok, response2} = Gemini.send_message(session, \"Show me an example in Elixir\")\n\n# Get conversation history\nhistory = Gemini.get_conversation_history(session)\n```\n\n## Tool Calling (Function Calling)\n\nTool calling enables the Gemini model to interact with external functions and APIs, making it possible to build powerful agents that can perform actions, retrieve real-time data, and integrate with your systems. This transforms the model from a text generator into an intelligent agent capable of complex workflows.\n\n### Automatic Execution (Recommended)\n\nThe automatic tool calling system provides the easiest and most robust way to use tools. It handles the entire multi-turn conversation loop automatically, executing tool calls and managing the conversation state behind the scenes.\n\n#### Step 1: Define \u0026 Register Your Tools\n\n```elixir\n# Define your tool functions\ndefmodule DemoTools do\n  def get_weather(%{\"location\" =\u003e location}) do\n    # Your weather API integration here\n    %{\n      location: location,\n      temperature: 22,\n      condition: \"sunny\",\n      humidity: 65\n    }\n  end\n\n  def calculate(%{\"operation\" =\u003e op, \"a\" =\u003e a, \"b\" =\u003e b}) do\n    result = case op do\n      \"add\" -\u003e a + b\n      \"multiply\" -\u003e a * b\n      \"divide\" when b != 0 -\u003e a / b\n      _ -\u003e {:error, \"Invalid operation\"}\n    end\n    \n    %{operation: op, result: result}\n  end\nend\n\n# Create function declarations\n{:ok, weather_declaration} = Altar.ADM.new_function_declaration(%{\n  name: \"get_weather\",\n  description: \"Gets current weather information for a specified location\",\n  parameters: %{\n    type: \"object\",\n    properties: %{\n      location: %{\n        type: \"string\",\n        description: \"The location to get weather for (e.g., 'San Francisco')\"\n      }\n    },\n    required: [\"location\"]\n  }\n})\n\n{:ok, calc_declaration} = Altar.ADM.new_function_declaration(%{\n  name: \"calculate\",\n  description: \"Performs basic mathematical calculations\",\n  parameters: %{\n    type: \"object\",\n    properties: %{\n      operation: %{type: \"string\", enum: [\"add\", \"multiply\", \"divide\"]},\n      a: %{type: \"number\", description: \"First operand\"},\n      b: %{type: \"number\", description: \"Second operand\"}\n    },\n    required: [\"operation\", \"a\", \"b\"]\n  }\n})\n\n# Register the tools\nGemini.Tools.register(weather_declaration, \u0026DemoTools.get_weather/1)\nGemini.Tools.register(calc_declaration, \u0026DemoTools.calculate/1)\n```\n\n#### Step 2: Call the Model\n\n```elixir\n# Single call with automatic tool execution\n{:ok, response} = Gemini.generate_content_with_auto_tools(\n  \"What's the weather like in Tokyo? Also calculate 15 * 23.\",\n  tools: [weather_declaration, calc_declaration],\n  model: \"gemini-flash-lite-latest\",\n  temperature: 0.1\n)\n```\n\n#### Step 3: Get the Final Result\n\n```elixir\n# Extract the final text response\n{:ok, text} = Gemini.extract_text(response)\nIO.puts(text)\n# Output: \"The weather in Tokyo is sunny with 22°C and 65% humidity. \n#          The calculation of 15 * 23 equals 345.\"\n```\n\nThe model automatically:\n- Determines which tools to call based on your prompt\n- Executes the necessary function calls\n- Processes the results\n- Provides a natural language response incorporating all the data\n\n#### Streaming with Automatic Execution\n\nFor real-time responses with tool calling:\n\n```elixir\n# Start streaming with automatic tool execution\n{:ok, stream_id} = Gemini.stream_generate_with_auto_tools(\n  \"Check the weather in London and calculate the tip for a $50 meal\",\n  tools: [weather_declaration, calc_declaration],\n  model: \"gemini-flash-lite-latest\"\n)\n\n# Subscribe to the stream\n:ok = Gemini.subscribe_stream(stream_id)\n\n# The subscriber will only receive the final text chunks\n# All tool execution happens automatically in the background\nreceive do\n  {:stream_event, ^stream_id, event} -\u003e \n    case Gemini.extract_text(event) do\n      {:ok, text} -\u003e IO.write(text)\n      _ -\u003e :ok\n    end\n  {:stream_complete, ^stream_id} -\u003e IO.puts(\"\\n✅ Complete!\")\nend\n```\n\n### Built-in Tools (Gemini 3)\n\nGemini 3 models can call built-in tools for Google Search, URL Context, and Code Execution.\nEnable them in `tools:` and optionally combine with structured outputs:\n\n```elixir\n{:ok, response} =\n  Gemini.generate(\n    \"Find the latest Elixir release notes and summarize the key changes.\",\n    model: \"gemini-3-flash-preview\",\n    tools: [:google_search, :url_context],\n    response_mime_type: \"application/json\",\n    response_json_schema: %{\n      \"type\" =\u003e \"object\",\n      \"properties\" =\u003e %{\n        \"summary\" =\u003e %{\"type\" =\u003e \"string\"},\n        \"sources\" =\u003e %{\"type\" =\u003e \"array\", \"items\" =\u003e %{\"type\" =\u003e \"string\"}}\n      },\n      \"required\" =\u003e [\"summary\"]\n    }\n  )\n```\n\nBuilt-in tools can be mixed with your own function declarations in the same `tools:` list.\n\n### Manual Execution (Advanced)\n\nFor advanced use cases requiring full control over the conversation loop, custom state management, or detailed logging of tool executions:\n\n```elixir\n# Step 1: Generate content with tool declarations\n{:ok, response} = Gemini.generate_content(\n  \"What's the weather in Paris?\",\n  tools: [weather_declaration],\n  model: \"gemini-flash-lite-latest\"\n)\n\n# Step 2: Check for function calls in the response\ncase response.candidates do\n  [%{content: %{parts: parts}}] -\u003e\n    function_calls = Enum.filter(parts, \u0026match?(%{function_call: _}, \u00261))\n    \n    if function_calls != [] do\n      # Step 3: Execute the function calls\n      {:ok, tool_results} = Gemini.Tools.execute_calls(function_calls)\n      \n      # Step 4: Create content from tool results\n      tool_content = Gemini.Types.Content.from_tool_results(tool_results)\n      \n      # Step 5: Continue the conversation with results\n      conversation_history = [\n        %{role: \"user\", parts: [%{text: \"What's the weather in Paris?\"}]},\n        response.candidates |\u003e hd() |\u003e Map.get(:content),\n        tool_content\n      ]\n      \n      {:ok, final_response} = Gemini.generate_content(\n        conversation_history,\n        model: \"gemini-flash-lite-latest\"\n      )\n      \n      {:ok, text} = Gemini.extract_text(final_response)\n      IO.puts(text)\n    end\nend\n```\n\nThis manual approach gives you complete visibility and control over each step of the tool calling process, which can be valuable for debugging, logging, or implementing custom conversation management logic.\n\n## Embeddings (New in v0.3.0!)\n\nGenerate semantic embeddings for text to power RAG systems, semantic search, classification, and more.\n\n### Quick Start\n\n```elixir\n# Generate an embedding\n{:ok, response} = Gemini.embed_content(\"Hello, world!\")\nvalues = response.embedding.values  # [0.123, -0.456, ...]\n\n# Compute similarity\nalias Gemini.Types.Response.ContentEmbedding\n\n{:ok, resp1} = Gemini.embed_content(\"The cat sat on the mat\")\n{:ok, resp2} = Gemini.embed_content(\"A feline rested on the rug\")\n\n# Normalize for accurate similarity (required for non-3072 dimensions)\nnorm1 = ContentEmbedding.normalize(resp1.embedding)\nnorm2 = ContentEmbedding.normalize(resp2.embedding)\n\nsimilarity = ContentEmbedding.cosine_similarity(norm1, norm2)\n# =\u003e 0.85 (high similarity)\n```\n\n### MRL (Matryoshka Representation Learning)\n\nThe `gemini-embedding-001` model supports flexible dimensions (128-3072) with minimal quality loss:\n\n```elixir\n# 768 dimensions - RECOMMENDED (25% storage, 0.26% quality loss)\n{:ok, response} = Gemini.embed_content(\n  \"Your text\",\n  model: \"gemini-embedding-001\",\n  output_dimensionality: 768\n)\n\n# 1536 dimensions - High quality (50% storage, same MTEB score as 3072!)\n{:ok, response} = Gemini.embed_content(\n  \"Your text\",\n  output_dimensionality: 1536\n)\n```\n\n**MTEB Benchmark Scores:**\n- 3072d: 68.17 (100% storage, pre-normalized)\n- 1536d: 68.17 (50% storage, **same quality!**)\n- 768d: 67.99 (25% storage, -0.26% loss)\n- 512d: 67.55 (17% storage, -0.91% loss)\n\n### Task Types for Better Quality\n\nOptimize embeddings for your specific use case:\n\n```elixir\n# For knowledge base documents\n{:ok, doc_emb} = Gemini.embed_content(\n  document_text,\n  task_type: \"RETRIEVAL_DOCUMENT\",\n  title: \"Document Title\"  # Improves quality!\n)\n\n# For search queries\n{:ok, query_emb} = Gemini.embed_content(\n  user_query,\n  task_type: \"RETRIEVAL_QUERY\"\n)\n\n# For classification\n{:ok, emb} = Gemini.embed_content(\n  text,\n  task_type: \"CLASSIFICATION\"\n)\n```\n\n### Distance Metrics\n\n```elixir\nalias Gemini.Types.Response.ContentEmbedding\n\n# Cosine similarity (higher = more similar, -1 to 1)\nsimilarity = ContentEmbedding.cosine_similarity(emb1, emb2)\n\n# Euclidean distance (lower = more similar, 0 to ∞)\ndistance = ContentEmbedding.euclidean_distance(emb1, emb2)\n\n# Dot product (equals cosine for normalized embeddings)\ndot = ContentEmbedding.dot_product(emb1, emb2)\n\n# L2 norm (should be ~1.0 after normalization)\nnorm = ContentEmbedding.norm(embedding)\n```\n\n### Batch Embedding\n\nEfficient for multiple texts:\n\n```elixir\ntexts = [\"Text 1\", \"Text 2\", \"Text 3\"]\n{:ok, response} = Gemini.batch_embed_contents(\n  \"gemini-embedding-001\",\n  texts,\n  task_type: \"RETRIEVAL_DOCUMENT\"\n)\n\n# Access embeddings\nembeddings = response.embeddings  # List of ContentEmbedding structs\n```\n\n### Advanced Use Cases\n\nComplete production-ready examples in `examples/use_cases/`:\n\n- **`mrl_normalization_demo.exs`** - MRL concepts, MTEB scores, normalization, distance metrics\n- **`rag_demo.exs`** - Complete RAG pipeline with knowledge base indexing and retrieval\n- **`search_reranking.exs`** - Semantic reranking for improved search relevance\n- **`classification.exs`** - K-NN classification with few-shot learning\n\nSee [examples/EMBEDDINGS.md](examples/EMBEDDINGS.md) for comprehensive documentation.\n\n### Critical: Normalization\n\n**IMPORTANT:** Only 3072-dimensional embeddings are pre-normalized. All other dimensions MUST be normalized before computing similarity:\n\n```elixir\n# WRONG - Produces incorrect similarity scores\nsimilarity = ContentEmbedding.cosine_similarity(emb1, emb2)\n\n# CORRECT - Normalize first for non-3072 dimensions\nnorm1 = ContentEmbedding.normalize(emb1)\nnorm2 = ContentEmbedding.normalize(emb2)\nsimilarity = ContentEmbedding.cosine_similarity(norm1, norm2)\n```\n\n### Async Batch Embedding (New in v0.3.1!)\n\nFor production-scale embedding generation with **50% cost savings**:\n\n```elixir\n# Submit large batch asynchronously\n{:ok, batch} = Gemini.async_batch_embed_contents(\n  texts,\n  display_name: \"Knowledge Base Index\",\n  task_type: :retrieval_document,\n  output_dimensionality: 768\n)\n\n# Poll for completion with progress tracking\n{:ok, completed_batch} = Gemini.await_batch_completion(\n  batch.name,\n  poll_interval: 10_000,  # 10 seconds\n  timeout: 30 * 60 * 1000,  # 30 minutes\n  on_progress: fn b -\u003e\n    progress = b.batch_stats.successful_request_count / b.batch_stats.request_count * 100\n    IO.puts(\"Progress: #{Float.round(progress, 1)}%\")\n  end\n)\n\n# Retrieve embeddings\n{:ok, embeddings} = Gemini.get_batch_embeddings(completed_batch)\n```\n\n**When to use:**\n- Large-scale indexing (1000s-millions of documents)\n- RAG system setup and knowledge base building\n- Non-urgent embedding generation\n- Cost-sensitive workflows (50% savings!)\n\n**Live Examples:**\n```bash\nmix run examples/async_batch_embedding_demo.exs\nmix run examples/async_batch_production_demo.exs\n```\n\nSee [examples/ASYNC_BATCH_EMBEDDINGS.md](examples/ASYNC_BATCH_EMBEDDINGS.md) for complete guide.\n\n## Examples\n\nThe repository includes comprehensive examples demonstrating all library features. All examples are ready to run and include proper error handling.\n\n### Running Examples\n\nAll examples use the same execution method:\n\n```bash\nmix run examples/[example_name].exs\n```\n\n### Available Examples\n\n#### 1. **`demo.exs`** - Comprehensive Feature Showcase\n**The main library demonstration covering all core features.**\n\n```bash\nmix run examples/demo.exs\n```\n\n**Features demonstrated:**\n- Model listing and information retrieval\n- Simple text generation with various prompts\n- Configured generation (creative vs precise modes)\n- Multi-turn chat sessions with context\n- Token counting for different text lengths\n\n**Requirements:** `GEMINI_API_KEY` environment variable\n\n---\n\n#### 2. **`streaming_demo.exs`** - Real-time Streaming\n**Live demonstration of Server-Sent Events streaming with progressive text delivery.**\n\n```bash\nmix run examples/streaming_demo.exs\n```\n\n**Features demonstrated:**\n- Real-time progressive text streaming\n- Stream subscription and event handling\n- Authentication detection (Gemini API or Vertex AI)\n- Stream status monitoring\n\n**Requirements:** `GEMINI_API_KEY` or Vertex AI credentials\n\n---\n\n#### 3. **`demo_unified.exs`** - Multi-Auth Architecture\n**Showcases the unified architecture supporting multiple authentication methods.**\n\n```bash\nmix run examples/demo_unified.exs\n```\n\n**Features demonstrated:**\n- Configuration system and auth detection\n- Authentication strategy switching\n- Streaming manager capabilities\n- Backward compatibility verification\n\n**Requirements:** None (works with or without credentials)\n\n---\n\n#### 4. **`multi_auth_demo.exs`** - Concurrent Authentication\n**Demonstrates concurrent usage of multiple authentication strategies.**\n\n```bash\nmix run examples/multi_auth_demo.exs\n```\n\n**Features demonstrated:**\n- Concurrent Gemini API and Vertex AI requests\n- Authentication failure handling\n- Per-request auth strategy selection\n- Error handling for invalid credentials\n\n**Requirements:** `GEMINI_API_KEY` recommended (demonstrates Vertex AI auth failure)\n\n---\n\n#### 5. **`telemetry_showcase.exs`** - Comprehensive Telemetry System\n**Complete demonstration of the built-in telemetry and observability features.**\n\n```bash\nmix run examples/telemetry_showcase.exs\n```\n\n**Features demonstrated:**\n- Real-time telemetry event monitoring\n- 7 event types: request start/stop/exception, stream start/chunk/stop/exception\n- Telemetry helper functions (stream IDs, content classification, metadata)\n- Live performance measurement and analysis\n- Configuration management for telemetry\n\n**Requirements:** `GEMINI_API_KEY` for live telemetry (works without for utilities demo)\n\n---\n\n#### 6. **`auto_tool_calling_demo.exs`** - Automatic Tool Execution (Recommended)\n**Demonstrates the powerful automatic tool calling system for building intelligent agents.**\n\n```bash\nmix run examples/auto_tool_calling_demo.exs\n```\n\n**Features demonstrated:**\n- Tool function definition and registration\n- Automatic multi-turn tool execution\n- Multiple tool types (weather, calculator, time)\n- Function declaration creation with JSON schemas\n- Streaming with automatic tool execution\n\n**Requirements:** `GEMINI_API_KEY` for live tool execution\n\n---\n\n#### 7. **`tool_calling_demo.exs`** - Manual Tool Execution\n**Shows manual control over the tool calling conversation loop for advanced use cases.**\n\n```bash\nmix run examples/tool_calling_demo.exs\n```\n\n**Features demonstrated:**\n- Manual tool execution workflow\n- Step-by-step conversation management\n- Custom tool result processing\n- Advanced debugging and logging capabilities\n\n**Requirements:** `GEMINI_API_KEY` for live tool execution\n\n---\n\n#### 8. **`manual_tool_calling_demo.exs`** - Advanced Manual Tool Control\n**Comprehensive manual tool calling patterns for complex agent workflows.**\n\n```bash\nmix run examples/manual_tool_calling_demo.exs\n```\n\n**Features demonstrated:**\n- Complex multi-step tool workflows\n- Custom conversation state management\n- Error handling in tool execution\n- Integration patterns for external APIs\n\n**Requirements:** `GEMINI_API_KEY` for live tool execution\n\n---\n\n#### 9. **`live_auto_tool_test.exs`** - Live End-to-End Tool Calling Test **LIVE EXAMPLE**\n**A comprehensive live test demonstrating real automatic tool execution with the Gemini API.**\n\n```bash\nmix run examples/live_auto_tool_test.exs\n```\n\n**Features demonstrated:**\n- **Real Elixir module introspection** using `Code.ensure_loaded/1` and `Code.fetch_docs/1`\n- **Live automatic tool execution** with the actual Gemini API\n- **End-to-end workflow validation** from tool registration to final response\n- **Comprehensive error handling** and debug output\n- **Self-contained execution** with `Mix.install` dependency management\n- **Professional output formatting** with step-by-step progress indicators\n\n**What makes this special:**\n- **Actually calls the Gemini API** - not a mock or simulation\n- **Executes real Elixir code** - introspects modules like `Enum`, `String`, `GenServer`\n- **Demonstrates the complete pipeline** - tool registration -\u003e API call -\u003e tool execution -\u003e response synthesis\n- **Self-contained** - runs independently with just an API key\n- **Comprehensive logging** - shows exactly what's happening at each step\n\n**Requirements:** `GEMINI_API_KEY` environment variable (this is a live API test)\n\n**Example output:**\n```\nSUCCESS! Final Response from Gemini:\nThe `Enum` module in Elixir is a powerful tool for working with collections...\nBased on the information retrieved using `get_elixir_module_info`, here's a breakdown:\n1. Main Purpose: Provides consistent iteration over enumerables (lists, maps, ranges)\n2. Common Functions: map/2, filter/2, reduce/3, sum/1, sort/1...\n3. Usefulness: Unified interface, functional programming, high performance...\n```\n\n---\n\n#### 10. **`live_api_test.exs`** - API Testing and Validation\n**Comprehensive testing utility for validating both authentication methods.**\n\n```bash\nmix run examples/live_api_test.exs\n```\n\n**Features demonstrated:**\n- Full API testing suite for both auth methods\n- Configuration detection and validation\n- Model operations (listing, details, existence checks)\n- Streaming functionality testing\n- Performance monitoring\n\n**Requirements:** `GEMINI_API_KEY` and/or Vertex AI credentials\n\n---\n\n#### 11. **`11_live_text_chat.exs`** - Live API Multi-Turn Text Chat\n**Real-time bidirectional text conversations using the Live API.**\n\n```bash\nmix run examples/11_live_text_chat.exs\n```\n\n**Features demonstrated:**\n- Multi-turn conversations with context retention\n- Automatic model resolution via `Gemini.Live.Models.resolve(:text)`\n- Response timing measurements\n- System instructions for persona customization\n\n**Requirements:** `GEMINI_API_KEY` environment variable\n\n---\n\n#### 12. **`12_live_audio_streaming.exs`** - Live API Audio Streaming\n**Audio input/output streaming for voice applications.**\n\n```bash\nmix run examples/12_live_audio_streaming.exs\n```\n\n**Features demonstrated:**\n- Sending audio chunks (16-bit PCM, 16kHz mono)\n- Receiving audio responses (24kHz PCM)\n- Input/output transcription callbacks\n- Voice activity detection signals\n\n**Requirements:** `GEMINI_API_KEY` environment variable\n\n---\n\n#### 13. **`13_live_session_resumption.exs`** - Live API Session Resumption\n**Reconnect to sessions while preserving conversation context.**\n\n```bash\nmix run examples/13_live_session_resumption.exs\n```\n\n**Features demonstrated:**\n- Enable session resumption in config\n- Save handle from `on_session_resumption` callback\n- Resume with `resume_handle:` option\n- Context preservation across reconnections\n\n**Requirements:** `GEMINI_API_KEY` environment variable\n\n---\n\n#### 14. **`14_live_function_calling.exs`** - Live API Function Calling\n**Tool/function calling with full telemetry observability.**\n\n```bash\nmix run examples/14_live_function_calling.exs\n```\n\n**Features demonstrated:**\n- Define function declarations for Live API\n- Handle tool call requests in real-time\n- Send responses back to model\n- Observe all events via telemetry\n\n**Requirements:** `GEMINI_API_KEY` environment variable\n\n### Example Output\n\nEach example provides detailed output with:\n- Success indicators for working features\n- Error messages with clear explanations\n- Performance metrics and timing information\n- Configuration details and detected settings\n- Live telemetry events (in telemetry showcase)\n\n### Setting Up Authentication\n\nFor the examples to work with live API calls, set up authentication:\n\n```bash\n# For Gemini API (recommended for examples)\nexport GEMINI_API_KEY=\"your_gemini_api_key\"\n\n# For Vertex AI (optional, for multi-auth demos)\nexport VERTEX_JSON_FILE=\"/path/to/service-account.json\"\nexport VERTEX_PROJECT_ID=\"your-gcp-project-id\"\n```\n\n### Example Development Pattern\n\nThe examples follow a consistent pattern:\n- **Self-contained**: Each example runs independently\n- **Well-documented**: Clear inline comments and descriptions\n- **Error-resilient**: Graceful handling of missing credentials\n- **Informative output**: Detailed logging of operations and results\n\n## Authentication\n\n### Gemini API Key (Recommended for Development)\n\n```elixir\n# Environment variable (recommended)\nexport GEMINI_API_KEY=\"your_api_key\"\n\n# Application config\nconfig :gemini_ex, api_key: \"your_api_key\"\n\n# Per-request override\nGemini.generate(\"Hello\", api_key: \"specific_key\")\n```\n\n### Vertex AI (Recommended for Production)\n\n```elixir\n# Service Account JSON file\nexport VERTEX_SERVICE_ACCOUNT=\"/path/to/service-account.json\"\nexport VERTEX_PROJECT_ID=\"your-gcp-project\"\nexport VERTEX_LOCATION=\"us-central1\"\n\n# Application config\nconfig :gemini_ex, :auth,\n  type: :vertex_ai,\n  credentials: %{\n    service_account_key: System.get_env(\"VERTEX_SERVICE_ACCOUNT\"),\n    project_id: System.get_env(\"VERTEX_PROJECT_ID\"),\n    location: \"us-central1\"\n  }\n```\n\n### Application Default Credentials (ADC)\n\nZero-config GCP authentication with automatic credential discovery and token refresh.\n\n```elixir\n# Works on GCE/Cloud Run/GKE with no extra setup\n{:ok, response} = Gemini.generate(\"Hello from Vertex AI\", auth: :vertex_ai)\n\n# Or point to a service account key\nexport GOOGLE_APPLICATION_CREDENTIALS=\"/path/to/service_account.json\"\n{:ok, response} = Gemini.generate(\"Hello\", auth: :vertex_ai)\n```\n\nThe client checks `GOOGLE_APPLICATION_CREDENTIALS`, gcloud user credentials, and metadata server endpoints, caching access tokens for you via ETS.\n\n## Model Configuration System\n\nThe library includes an intelligent model registry that handles the differences between Gemini API (AI Studio) and Vertex AI.\n\n### Auth-Aware Model Defaults\n\nDefault models are automatically selected based on detected authentication:\n\n```elixir\n# With GEMINI_API_KEY set:\nGemini.Config.default_model()        #=\u003e \"gemini-flash-lite-latest\"\nGemini.Config.default_embedding_model()  #=\u003e \"gemini-embedding-001\"\n\n# With VERTEX_PROJECT_ID set (no GEMINI_API_KEY):\nGemini.Config.default_model()        #=\u003e \"gemini-2.5-flash-lite\"\nGemini.Config.default_embedding_model()  #=\u003e \"embeddinggemma\"\n```\n\n### Model Compatibility\n\nModels are organized by API compatibility:\n\n| Category | Example Models | Gemini API | Vertex AI |\n|----------|---------------|------------|-----------|\n| **Universal** | `gemini-2.5-flash`, `gemini-3-flash-preview` | ✓ | ✓ |\n| **AI Studio Only** | `gemini-flash-lite-latest`, `gemini-pro-latest` | ✓ | ✗ |\n| **Vertex AI Only** | `embeddinggemma`, `embeddinggemma-300m` | ✗ | ✓ |\n\n```elixir\n# Check model availability\nGemini.Config.model_available?(:flash_2_5, :vertex_ai)     #=\u003e true\nGemini.Config.model_available?(:flash_lite_latest, :vertex_ai) #=\u003e false\n\n# Get models for a specific API\nGemini.Config.models_for(:vertex_ai)  # All Vertex-compatible models\nGemini.Config.models_for(:both)       # Only universal models\n\n# Get model by key with validation\nGemini.Config.get_model(:flash_2_5)  #=\u003e \"gemini-2.5-flash\"\nGemini.Config.get_model(:flash_2_5, api: :vertex_ai)  # Validates compatibility\n```\n\n### Embedding Model Differences\n\nEmbedding models differ significantly between APIs:\n\n| Model | API | Default Dims | Task Type Handling |\n|-------|-----|--------------|-------------------|\n| `gemini-embedding-001` | Gemini API | 3072 | `taskType` parameter |\n| `embeddinggemma` | Vertex AI | 768 | Prompt prefixes |\n\n```elixir\n# Gemini API - uses taskType parameter\n{:ok, emb} = Gemini.embed_content(\"Search query\",\n  task_type: :retrieval_query  # Sent as API parameter\n)\n\n# Vertex AI with EmbeddingGemma - task embedded in prompt\n{:ok, emb} = Gemini.embed_content(\"Search query\",\n  task_type: :retrieval_query  # Becomes: \"task: search result | query: Search query\"\n)\n```\n\nThe library handles this automatically based on detected authentication.\n\n### Custom Model Configuration\n\nOverride defaults in your application config:\n\n```elixir\nconfig :gemini_ex,\n  default_model: \"gemini-2.5-flash\",\n  default_embedding_model: \"gemini-embedding-001\"\n```\n\nOr specify per-request:\n\n```elixir\nGemini.generate(\"Hello\", model: \"gemini-3-pro-preview\")\nGemini.embed_content(\"Text\", model: \"gemini-embedding-001\")\n```\n\n## Documentation\n\n- **[API Reference](https://hexdocs.pm/gemini_ex)** - Complete function documentation\n- **[Architecture Guide](https://hexdocs.pm/gemini_ex/architecture.html)** - System design and components\n- **[Authentication System](https://hexdocs.pm/gemini_ex/authentication_system.html)** - Detailed auth configuration\n- **[Examples](https://github.com/nshkrdotcom/gemini_ex/tree/main/examples)** - Working code examples\n- **Guides (docs/guides/...)**:\n  - `adc.md` - Application Default Credentials\n  - `batches.md` - Batches API\n  - `file_search_stores.md` - RAG stores and document ingestion\n  - `files.md` - Files API\n  - `function_calling.md` - Tool/function calling patterns\n  - `image_generation.md` - Imagen text-to-image/edit/upscale\n  - `live_api.md` - WebSocket Live API with native audio features\n  - `operations.md` - Long-running operations and polling\n  - `rate_limiting.md` - Limiter configuration and tuning\n  - `structured_outputs.md` - JSON schema and property ordering\n  - `system_instructions.md` - Persistent guardrails\n  - `tunings.md` - Fine-tuning jobs\n  - `video_generation.md` - Veo text-to-video\n\n## Architecture\n\nThe library features a modular, layered architecture:\n\n- **Authentication Layer**: Multi-strategy auth with automatic credential resolution\n- **Coordination Layer**: Unified API coordinator for all operations\n- **Streaming Layer**: Advanced SSE processing with state management\n- **HTTP Layer**: Dual client system for standard and streaming requests\n- **Type Layer**: Comprehensive schemas with runtime validation\n\n## Telemetry\n\nThe library emits telemetry events for observability and monitoring. Attach handlers to observe API calls and Live API sessions.\n\n### Live API Telemetry Events\n\n```elixir\n# Attach a handler for Live API messages\n:telemetry.attach(\n  \"my-live-handler\",\n  [:gemini, :live, :session, :message, :received],\n  fn event, measurements, metadata, _config -\u003e\n    IO.inspect({event, measurements, metadata})\n  end,\n  nil\n)\n```\n\n**Available Live API events:**\n- `[:gemini, :live, :session, :init]` - Session initialization\n- `[:gemini, :live, :session, :ready]` - Session connected and ready\n- `[:gemini, :live, :session, :message, :sent]` - Message sent to server\n- `[:gemini, :live, :session, :message, :received]` - Message received from server\n- `[:gemini, :live, :session, :tool_call]` - Tool call requested\n- `[:gemini, :live, :session, :close]` - Session closed\n- `[:gemini, :live, :session, :error]` - Error occurred\n- `[:gemini, :live, :session, :go_away]` - GoAway notice received\n\nSee `examples/telemetry_showcase.exs` for a complete telemetry integration example.\n\n## Advanced Usage\n\n### Complete Generation Configuration Support\n\nAll generation config options are fully supported across all API entry points:\n\n```elixir\n# Structured output with JSON schema\n{:ok, response} = Gemini.generate(\"Analyze this data\", [\n  response_json_schema: %{\n    \"type\" =\u003e \"object\",\n    \"properties\" =\u003e %{\n      \"summary\" =\u003e %{\"type\" =\u003e \"string\"},\n      \"insights\" =\u003e %{\"type\" =\u003e \"array\", \"items\" =\u003e %{\"type\" =\u003e \"string\"}}\n    }\n  },\n  response_mime_type: \"application/json\"\n])\n\n# Creative writing with advanced controls\n{:ok, response} = Gemini.generate(\"Write a story\", [\n  temperature: 0.9,\n  top_p: 0.8,\n  top_k: 40,\n  presence_penalty: 0.6,\n  frequency_penalty: 0.4,\n  stop_sequences: [\"THE END\", \"EPILOGUE\"]\n])\n```\n\n### Custom Model Configuration\n\n```elixir\n# List available models\n{:ok, models} = Gemini.list_models()\n\n# Get model details\n{:ok, model_info} = Gemini.get_model(\"gemini-flash-lite-latest\")\n\n# Count tokens\n{:ok, token_count} = Gemini.count_tokens(\"Your text here\", model: \"gemini-flash-lite-latest\")\n```\n\n**Model quick picks**\n- `gemini-flash-lite-latest` (default; fastest + most cost-efficient)\n- `gemini-2.5-flash` (balanced price/performance for high-volume workloads)\n- `gemini-3-flash-preview` (fast Gemini 3 with full thinking levels + built-in tools)\n- `gemini-3-pro-preview` (most capable multimodal reasoning)\n\n### Multimodal Content (New in v0.2.2!)\n\nThe library now accepts multiple intuitive input formats for images and text:\n\n```elixir\n# Anthropic-style format (flexible and intuitive)\ncontent = [\n  %{type: \"text\", text: \"What's in this image?\"},\n  %{type: \"image\", source: %{type: \"base64\", data: base64_image}}\n]\n\n{:ok, response} = Gemini.generate(content)\n\n# Automatic MIME type detection from image data\n{:ok, image_data} = File.read(\"photo.png\")\ncontent = [\n  %{type: \"text\", text: \"Describe this photo\"},\n  %{type: \"image\", source: %{type: \"base64\", data: Base.encode64(image_data)}}\n  # No mime_type needed - auto-detected as image/png!\n]\n\n# Or use the original Content struct format\nalias Gemini.Types.{Content, Part}\n\ncontent = [\n  Content.text(\"What is this?\"),\n  Content.image(\"path/to/image.png\")\n]\n\n{:ok, response} = Gemini.generate(content)\n\n# Mix and match formats in a single request\ncontent = [\n  \"Describe this image:\",                    # Simple string\n  %{type: \"image\", source: %{...}},          # Anthropic-style\n  %Content{role: \"user\", parts: [...]}       # Content struct\n]\n```\n\n**Supported image formats:** PNG, JPEG, GIF, WebP (auto-detected from magic bytes)\n\n### Image Generation API (Imagen, New in v0.8.x!)\n\nUse the dedicated Imagen endpoints for text-to-image, editing, and upscaling (Vertex AI).\n\n```elixir\nalias Gemini.APIs.Images\nalias Gemini.Types.Generation.Image.{ImageGenerationConfig, EditImageConfig, UpscaleImageConfig}\n\n# Text-to-image\n{:ok, images} =\n  Images.generate(\n    \"An isometric illustration of a futuristic Elixir server farm\",\n    %ImageGenerationConfig{\n      number_of_images: 2,\n      aspect_ratio: \"16:9\",\n      safety_filter_level: :standard\n    },\n    auth: :vertex_ai\n  )\n\n# Inpainting / editing with masks\n{:ok, edited} =\n  Images.edit(\n    \"Remove the logos and brighten the lighting\",\n    File.read!(\"assets/sample.png\"),\n    File.read!(\"assets/mask.png\"),\n    %EditImageConfig{edit_mode: :inpainting},\n    auth: :vertex_ai\n  )\n\n# Upscale existing images (2x or 4x)\n{:ok, sharp} =\n  Images.upscale(\n    File.read!(\"assets/sample.png\"),\n    %UpscaleImageConfig{upscale_factor: :x4},\n    auth: :vertex_ai\n  )\n```\n\nThe API returns base64 image data plus metadata; you can also pull from GCS/HTTP URIs and control person_generation, safety filters, and aspect ratios.\n\n### Video Generation API (Veo, New in v0.8.x!)\n\nGenerate short-form videos with Veo via Vertex AI.\n\n```elixir\nalias Gemini.APIs.Videos\nalias Gemini.Types.Generation.Video.VideoGenerationConfig\n\n{:ok, op} =\n  Videos.generate(\n    \"A cinematic drone shot over misty mountains at sunrise\",\n    %VideoGenerationConfig{duration_seconds: 6, aspect_ratio: \"16:9\"},\n    auth: :vertex_ai\n  )\n\n{:ok, completed} = Videos.wait_for_completion(op.name, auth: :vertex_ai)\nIO.inspect(completed.response)\n```\n\nUse `get_operation/2` or `list_operations/1` to poll or enumerate jobs, and `cancel/2` to stop a run mid-flight.\n\n### Inline Image Generation (Gemini 3 models)\n\nGenerate images with aspect ratio and resolution control:\n\n```elixir\nconfig = Gemini.Types.GenerationConfig.image_config(\n  aspect_ratio: \"16:9\",\n  image_size: \"4K\"\n)\n\n{:ok, response} =\n  Gemini.generate(\"A sunrise over the mountains\",\n    model: \"gemini-3-pro-image-preview\",\n    generation_config: config\n  )\n\nimages = Gemini.Types.Response.Image.extract_base64(response)\n```\n\n### Cost Optimization with Thinking Budgets (New in v0.2.2!)\n\nGemini 2.5 series models use internal \"thinking\" for complex reasoning. Control thinking token usage to optimize costs:\n\n```elixir\n# Disable thinking for simple tasks (save costs)\n{:ok, response} = Gemini.generate(\n  \"What is 2 + 2?\",\n  model: \"gemini-2.5-flash\",\n  thinking_config: %{thinking_budget: 0}\n)\n# Result: No thinking tokens charged!\n\n# Set fixed budget (balance cost and quality)\n{:ok, response} = Gemini.generate(\n  \"Write a Python function to sort a list\",\n  model: \"gemini-2.5-flash\",\n  thinking_config: %{thinking_budget: 1024}\n)\n\n# Dynamic thinking (model decides - default behavior)\n{:ok, response} = Gemini.generate(\n  \"Solve this complex problem...\",\n  model: \"gemini-2.5-flash\",\n  thinking_config: %{thinking_budget: -1}\n)\n\n# Get thought summaries (see model's reasoning)\n{:ok, response} = Gemini.generate(\n  \"Explain your reasoning step by step\",\n  model: \"gemini-2.5-flash\",\n  thinking_config: %{\n    thinking_budget: 2048,\n    include_thoughts: true\n  }\n)\n\n# Using GenerationConfig struct\nalias Gemini.Types.GenerationConfig\n\nconfig = GenerationConfig.new()\n|\u003e GenerationConfig.thinking_budget(1024)\n|\u003e GenerationConfig.include_thoughts(true)\n|\u003e GenerationConfig.temperature(0.7)\n\n{:ok, response} = Gemini.generate(\"prompt\", generation_config: config)\n```\n\n**Budget ranges by model:**\n- **Gemini 2.5 Pro:** 128-32,768 (cannot disable)\n- **Gemini 2.5 Flash:** 0-24,576 (can disable with 0)\n- **Gemini 2.5 Flash Lite:** 0 or 512-24,576\n\n**Special values:**\n- `0`: Disable thinking entirely (Flash/Lite only)\n- `-1`: Dynamic thinking (model decides budget)\n\n## Rate Limiting and Retries (Default ON)\n\n- Concurrency gating per model (default 4)\n- Retries on 429 using server `RetryInfo.retryDelay`; falls back to 60s if missing\n- Retries on 5xx/network with exponential backoff (`base_backoff_ms * 2^(attempt-1)` ± jitter)\n- Adaptive concurrency option reacts to 429s\n\nConfigure in `config :gemini_ex, :rate_limiter`:\n\n```elixir\nconfig :gemini_ex, :rate_limiter,\n  max_concurrency_per_model: 4,\n  max_attempts: 3,\n  base_backoff_ms: 1000,\n  jitter_factor: 0.25,\n  adaptive_concurrency: false,\n  adaptive_ceiling: 8\n```\n\nPer-call overrides:\n- `disable_rate_limiter: true` — bypass all gating/retry\n- `non_blocking: true` — return immediately on 429 with `{:error, {:rate_limited, retry_at, details}}`\n\n### Error Handling\n\n```elixir\ncase Gemini.generate(\"Hello world\") do\n  {:ok, response} -\u003e \n    # Handle success\n    {:ok, text} = Gemini.extract_text(response)\n    \n  {:error, %Gemini.Error{type: :rate_limit} = error} -\u003e \n    # Handle rate limiting\n    IO.puts(\"Rate limited. Retry after: #{error.retry_after}\")\n    \n  {:error, %Gemini.Error{type: :authentication} = error} -\u003e \n    # Handle auth errors\n    IO.puts(\"Auth error: #{error.message}\")\n    \n  {:error, error} -\u003e \n    # Handle other errors\n    IO.puts(\"Unexpected error: #{inspect(error)}\")\nend\n```\n\n## Testing\n\n```bash\n# Run all tests\nmix test\n\n# Run with coverage\nmix test --cover\n\n# Run integration tests (requires API key)\nGEMINI_API_KEY=\"your_key\" mix test --only integration\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](https://github.com/nshkrdotcom/gemini_ex/blob/main/LICENSE) file for details.\n\n## Acknowledgments\n\n- Google AI team for the Gemini API\n- Elixir community for excellent tooling and libraries\n- Contributors and maintainers\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fgemini_ex","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fnshkrdotcom%2Fgemini_ex","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fnshkrdotcom%2Fgemini_ex/lists"}