Skip to content

Commit 0329a41

Browse files
committed
Add Gemini provider via OpenAI-compatible API
Enables using Google's Gemini models through the OpenAI-compatible API endpoint at generativelanguage.googleapis.com. The provider inherits from OpenAI::ChatProvider, reusing streaming, tool use, and structured output functionality while overriding only Gemini-specific behaviors. Implementation: - GeminiProvider inherits OpenAI::ChatProvider, overrides message_merge_delta to fix Gemini's streaming role duplication (Gemini sends role in every chunk, causing "assistantassistant...") - Gemini::Options handles API key resolution: explicit api_key, access_token alias, then environment variables (GEMINI_API_KEY, GOOGLE_API_KEY in priority order) - Reuses OpenAI::Chat::RequestType — no protocol translation needed as Gemini implements OpenAI-compatible format - organization_id and project_id disabled (not used by Gemini API) - Connection error handling with instrumentation logging Follows the same pattern established by OllamaProvider which also inherits from OpenAI::ChatProvider for OpenAI-compatible endpoints.
1 parent c09c797 commit 0329a41

File tree

3 files changed

+131
-0
lines changed

3 files changed

+131
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "options"
4+
require_relative "../open_ai/chat/_types"
5+
6+
module ActiveAgent
7+
module Providers
8+
module Gemini
9+
# Reuse OpenAI Chat request type (same API format)
10+
RequestType = OpenAI::Chat::RequestType
11+
end
12+
end
13+
end
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "../open_ai/options"
4+
5+
module ActiveAgent
6+
module Providers
7+
module Gemini
8+
# Configuration options for Gemini provider
9+
#
10+
# Extends OpenAI::Options with Gemini-specific settings including
11+
# the default base URL for Gemini's OpenAI-compatible API endpoint.
12+
#
13+
# @example Basic configuration
14+
# options = Options.new(api_key: 'your-api-key')
15+
#
16+
# @example With environment variable
17+
# # Set GEMINI_API_KEY or GOOGLE_API_KEY
18+
# options = Options.new({})
19+
#
20+
# @see https://ai.google.dev/gemini-api/docs/openai
21+
class Options < ActiveAgent::Providers::OpenAI::Options
22+
GEMINI_BASE_URL = "https://generativelanguage.googleapis.com/v1beta/openai/"
23+
24+
attribute :base_url, :string, fallback: GEMINI_BASE_URL
25+
26+
private
27+
28+
def resolve_api_key(kwargs)
29+
kwargs[:api_key] ||
30+
kwargs[:access_token] ||
31+
ENV["GEMINI_API_KEY"] ||
32+
ENV["GOOGLE_API_KEY"]
33+
end
34+
35+
# Not used as part of Gemini
36+
def resolve_organization_id(kwargs) = nil
37+
def resolve_project_id(kwargs) = nil
38+
end
39+
end
40+
end
41+
end
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
require_relative "_base_provider"
2+
3+
require_gem!(:openai, __FILE__)
4+
5+
require_relative "open_ai_provider"
6+
require_relative "gemini/_types"
7+
8+
module ActiveAgent
9+
module Providers
10+
# Provides access to Google's Gemini API via OpenAI-compatible endpoint.
11+
#
12+
# Extends OpenAI provider to work with Gemini's OpenAI-compatible API,
13+
# enabling access to Gemini models through a familiar interface.
14+
#
15+
# @see OpenAI::ChatProvider
16+
# @see https://ai.google.dev/gemini-api/docs/openai
17+
class GeminiProvider < OpenAI::ChatProvider
18+
# @return [String]
19+
def self.service_name
20+
"Gemini"
21+
end
22+
23+
# @return [Class]
24+
def self.options_klass
25+
namespace::Options
26+
end
27+
28+
# @return [ActiveModel::Type::Value]
29+
def self.prompt_request_type
30+
namespace::RequestType.new
31+
end
32+
33+
protected
34+
35+
# Executes chat completion request with Gemini-specific error handling.
36+
#
37+
# @see OpenAI::ChatProvider#api_prompt_execute
38+
# @param parameters [Hash]
39+
# @return [Object, nil] response object or nil for streaming
40+
# @raise [OpenAI::Errors::APIConnectionError] when Gemini API unreachable
41+
def api_prompt_execute(parameters)
42+
super
43+
44+
rescue ::OpenAI::Errors::APIConnectionError => exception
45+
log_connection_error(exception)
46+
raise exception
47+
end
48+
49+
# Merges streaming delta into the message with role cleanup.
50+
#
51+
# Overrides parent to handle Gemini's role copying behavior which duplicates
52+
# the role field in every streaming chunk, requiring manual cleanup to prevent
53+
# message corruption.
54+
#
55+
# @see OpenAI::ChatProvider#message_merge_delta
56+
# @param message [Hash]
57+
# @param delta [Hash]
58+
# @return [Hash]
59+
def message_merge_delta(message, delta)
60+
message[:role] = delta.delete(:role) if delta[:role]
61+
62+
hash_merge_delta(message, delta)
63+
end
64+
65+
# Logs connection failures with Gemini API details for debugging.
66+
#
67+
# @param error [Exception]
68+
# @return [void]
69+
def log_connection_error(error)
70+
instrument("connection_error.provider.active_agent",
71+
uri_base: options.base_url,
72+
exception: error.class,
73+
message: error.message)
74+
end
75+
end
76+
end
77+
end

0 commit comments

Comments
 (0)