Skip to content

Commit aaafbdf

Browse files
committed
Add tests for Gemini provider
Comprehensive test coverage for GeminiProvider, Gemini::Options, and streaming lifecycle behaviors. Test coverage (21 tests, 35 assertions): - Provider class (6 tests): service_name, options_klass, prompt_request_type delegation to OpenAI::Chat::RequestType, initialization, inheritance from OpenAI::ChatProvider, client construction - Options (8 tests): api_key validation, GEMINI_API_KEY env resolution, GOOGLE_API_KEY env resolution, GEMINI over GOOGLE precedence, explicit-over-ENV precedence, access_token alias, organization_id returns nil, project_id returns nil - Streaming lifecycle (7 tests): inherits :open event emission from OpenAI::ChatProvider, broadcast_stream_open idempotency, message_merge_delta handles Gemini role duplication correctly, full lifecycle event ordering (open -> update -> close), streaming flag state transitions The streaming tests specifically verify the message_merge_delta override prevents role concatenation when Gemini sends role in every chunk.
1 parent 0329a41 commit aaafbdf

File tree

5 files changed

+408
-0
lines changed

5 files changed

+408
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# frozen_string_literal: true
2+
3+
module Providers
4+
# Example agent using Google's Gemini models.
5+
#
6+
# Demonstrates basic prompt generation with the Gemini provider.
7+
# Configured to use Gemini 2.0 Flash with default instructions.
8+
#
9+
# @example Basic usage
10+
# response = Providers::GeminiAgent.ask(message: "Hello").generate_now
11+
# response.message.content #=> "Hi! How can I help you today?"
12+
# region agent
13+
class GeminiAgent < ApplicationAgent
14+
generate_with :gemini, model: "gemini-2.0-flash"
15+
16+
# @return [ActiveAgent::Generation]
17+
def ask
18+
prompt(message: params[:message])
19+
end
20+
end
21+
# endregion agent
22+
end

test/dummy/config/active_agent.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ mock: &mock
3838
ruby_llm: &ruby_llm
3939
service: "RubyLLM"
4040
# endregion ruby_llm_anchor
41+
# region gemini_anchor
42+
gemini: &gemini
43+
service: "Gemini"
44+
model: "gemini-2.0-flash"
45+
api_key: <%= Rails.application.credentials.dig(:gemini, :api_key) %>
46+
# endregion gemini_anchor
4147
# endregion config_anchors
4248

4349
# region config_development
@@ -72,6 +78,10 @@ development:
7278
ruby_llm:
7379
<<: *ruby_llm
7480
# endregion ruby_llm_dev_config
81+
# region gemini_dev_config
82+
gemini:
83+
<<: *gemini
84+
# endregion gemini_dev_config
7585
# endregion config_development
7686

7787
# region config_test
@@ -92,4 +102,6 @@ test:
92102
<<: *mock
93103
ruby_llm:
94104
<<: *ruby_llm
105+
gemini:
106+
<<: *gemini
95107
# endregion config_test
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
begin
6+
require "openai"
7+
rescue LoadError
8+
puts "OpenAI gem not available, skipping Gemini provider tests"
9+
return
10+
end
11+
12+
require_relative "../../../lib/active_agent/providers/gemini_provider"
13+
14+
class GeminiProviderTest < ActiveSupport::TestCase
15+
setup do
16+
@valid_config = {
17+
service: "Gemini",
18+
api_key: "test-api-key",
19+
messages: [ { role: "user", content: "Hello" } ]
20+
}
21+
end
22+
23+
test "service_name returns Gemini" do
24+
assert_equal "Gemini", ActiveAgent::Providers::GeminiProvider.service_name
25+
end
26+
27+
test "options_klass returns Gemini::Options" do
28+
assert_equal(
29+
ActiveAgent::Providers::Gemini::Options,
30+
ActiveAgent::Providers::GeminiProvider.options_klass
31+
)
32+
end
33+
34+
test "prompt_request_type returns Gemini::RequestType" do
35+
request_type = ActiveAgent::Providers::GeminiProvider.prompt_request_type
36+
37+
# Gemini::RequestType is aliased to OpenAI::Chat::RequestType
38+
assert_instance_of ActiveAgent::Providers::OpenAI::Chat::RequestType, request_type
39+
end
40+
41+
test "initializes provider with valid configuration" do
42+
provider = ActiveAgent::Providers::GeminiProvider.new(@valid_config)
43+
44+
assert_instance_of ActiveAgent::Providers::GeminiProvider, provider
45+
end
46+
47+
test "inherits from OpenAI::ChatProvider" do
48+
assert ActiveAgent::Providers::GeminiProvider < ActiveAgent::Providers::OpenAI::ChatProvider
49+
end
50+
51+
test "client is configured with Gemini base_url" do
52+
provider = ActiveAgent::Providers::GeminiProvider.new(@valid_config)
53+
client = provider.client
54+
55+
assert_kind_of ::OpenAI::Client, client
56+
end
57+
end
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# frozen_string_literal: true
2+
3+
require "test_helper"
4+
5+
begin
6+
require "openai"
7+
rescue LoadError
8+
puts "OpenAI gem not available, skipping Gemini options tests"
9+
return
10+
end
11+
12+
require_relative "../../../lib/active_agent/providers/gemini_provider"
13+
14+
class GeminiOptionsTest < ActiveSupport::TestCase
15+
setup do
16+
@valid_options = {
17+
api_key: "test-api-key"
18+
}
19+
end
20+
21+
test "validates presence of api_key" do
22+
original_keys = [
23+
ENV["GEMINI_API_KEY"],
24+
ENV["GOOGLE_API_KEY"]
25+
]
26+
ENV.delete("GEMINI_API_KEY")
27+
ENV.delete("GOOGLE_API_KEY")
28+
29+
options = ActiveAgent::Providers::Gemini::Options.new({})
30+
31+
assert_not options.valid?
32+
assert_includes options.errors[:api_key], "can't be blank"
33+
ensure
34+
ENV["GEMINI_API_KEY"] = original_keys[0]
35+
ENV["GOOGLE_API_KEY"] = original_keys[1]
36+
end
37+
38+
test "resolves api_key from GEMINI_API_KEY environment variable" do
39+
original_keys = [
40+
ENV["GEMINI_API_KEY"],
41+
ENV["GOOGLE_API_KEY"]
42+
]
43+
ENV["GEMINI_API_KEY"] = "env-gemini-key"
44+
ENV.delete("GOOGLE_API_KEY")
45+
46+
options = ActiveAgent::Providers::Gemini::Options.new({})
47+
48+
assert_equal "env-gemini-key", options.api_key
49+
ensure
50+
ENV["GEMINI_API_KEY"] = original_keys[0]
51+
ENV["GOOGLE_API_KEY"] = original_keys[1]
52+
end
53+
54+
test "resolves api_key from GOOGLE_API_KEY environment variable" do
55+
original_keys = [
56+
ENV["GEMINI_API_KEY"],
57+
ENV["GOOGLE_API_KEY"]
58+
]
59+
ENV.delete("GEMINI_API_KEY")
60+
ENV["GOOGLE_API_KEY"] = "env-google-key"
61+
62+
options = ActiveAgent::Providers::Gemini::Options.new({})
63+
64+
assert_equal "env-google-key", options.api_key
65+
ensure
66+
ENV["GEMINI_API_KEY"] = original_keys[0]
67+
ENV["GOOGLE_API_KEY"] = original_keys[1]
68+
end
69+
70+
test "prefers GEMINI_API_KEY over GOOGLE_API_KEY" do
71+
original_keys = [
72+
ENV["GEMINI_API_KEY"],
73+
ENV["GOOGLE_API_KEY"]
74+
]
75+
ENV["GEMINI_API_KEY"] = "gemini-key"
76+
ENV["GOOGLE_API_KEY"] = "google-key"
77+
78+
options = ActiveAgent::Providers::Gemini::Options.new({})
79+
80+
assert_equal "gemini-key", options.api_key
81+
ensure
82+
ENV["GEMINI_API_KEY"] = original_keys[0]
83+
ENV["GOOGLE_API_KEY"] = original_keys[1]
84+
end
85+
86+
test "prefers explicit api_key over environment variables" do
87+
original_key = ENV["GEMINI_API_KEY"]
88+
ENV["GEMINI_API_KEY"] = "env-key"
89+
90+
options = ActiveAgent::Providers::Gemini::Options.new(@valid_options)
91+
92+
assert_equal "test-api-key", options.api_key
93+
ensure
94+
ENV["GEMINI_API_KEY"] = original_key
95+
end
96+
97+
test "accepts access_token as alias for api_key" do
98+
options = ActiveAgent::Providers::Gemini::Options.new(
99+
access_token: "token-via-access-token"
100+
)
101+
102+
assert_equal "token-via-access-token", options.api_key
103+
end
104+
105+
test "organization_id returns nil" do
106+
options = ActiveAgent::Providers::Gemini::Options.new(@valid_options)
107+
108+
assert_nil options.organization
109+
end
110+
111+
test "project_id returns nil" do
112+
options = ActiveAgent::Providers::Gemini::Options.new(@valid_options)
113+
114+
assert_nil options.project
115+
end
116+
end

0 commit comments

Comments
 (0)