Skip to content

feat: add Google Gemini as alternative LLM provider#1968

Closed
lifrank1 wants to merge 1 commit into
we-promise:mainfrom
lifrank1:feat/gemini-llm-provider
Closed

feat: add Google Gemini as alternative LLM provider#1968
lifrank1 wants to merge 1 commit into
we-promise:mainfrom
lifrank1:feat/gemini-llm-provider

Conversation

@lifrank1
Copy link
Copy Markdown

@lifrank1 lifrank1 commented May 24, 2026

Summary

  • Google Gemini support: Uses Gemini's OpenAI-compatible endpoint (https://generativelanguage.googleapis.com/v1beta/openai/) via the existing Provider::Openai class — no new provider class needed
  • Provider fallback logic: OpenAI is used when OPENAI_ACCESS_TOKEN / openai_access_token setting is present; falls through to Gemini otherwise; if both are set, OpenAI wins
  • Bug fix — chat model override: chat_response was always forwarding the caller-supplied model name (e.g. "gpt-4.1") to custom providers, causing 404s from Gemini's endpoint. Now uses the provider's configured @default_model for any custom (non-standard-OpenAI) provider
  • Bug fix — AI availability gate: ai_available? only checked OpenAI keys; updated to also check Gemini key so "Enable AI Chats" appears when only Gemini is configured
  • Settings UX: OpenAI and Gemini sections displayed as peers with an "or" divider; token budget extracted into its own standalone section; Gemini partial includes an optional model field (default: gemini-2.5-flash)
  • Locale updates: Self-hosting settings and chat unavailable message updated to mention both providers

Test plan

  • test/models/provider/registry_test.rb — 6 new tests covering Gemini provider creation, model precedence, nil when no key, and OpenAI↔Gemini fallback/priority
  • test/models/user_test.rb — 1 new test: ai_available? returns true when only Gemini key is set
  • test/models/provider/openai_test.rb — 2 new tests: custom providers use configured model; standard OpenAI passes caller-supplied model through
  • Manual: set GEMINI_API_KEY only → "Enable AI Chats" button appears, chat works
  • Manual: set both keys → OpenAI is used (check worker logs)
  • Manual: Settings → Self-Hosting → AI Provider shows OpenAI / or / Gemini layout; token budget is in its own section below

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Google Gemini as an AI provider option, allowing configuration of Gemini API key and model selection in Self-Hosting settings alongside OpenAI.
    • Reorganized LLM token budget settings into a dedicated configuration section for improved clarity.
  • Documentation

    • Updated help text to guide users through Gemini provider configuration options.

Review Change Stack

- Add `gemini_api_key` and `gemini_model` fields to Setting (encrypted)
- Add `gemini` factory to Provider::Registry using Gemini's OpenAI-compatible
  endpoint (https://generativelanguage.googleapis.com/v1beta/openai/)
- Fall back from OpenAI provider to Gemini when no OpenAI key is configured;
  OpenAI takes priority when both are set
- Fix `chat_response` to use the configured model for custom providers
  (Gemini, Ollama, etc.) instead of the caller-supplied OpenAI model name
- Fix `ai_available?` to also check for Gemini API key so "Enable AI Chats"
  is accessible when only Gemini is configured
- Update chat unavailable copy to mention both OpenAI and Gemini
- Restructure Self-Hosting settings UI: OpenAI and Gemini displayed as peers
  with an "or" divider; token budget extracted into its own section
- Add Gemini settings partial with optional model field (defaults to gemini-2.5-flash)
- Add tests: 6 registry tests, 1 user test, 2 openai model-routing tests
@superagent-security superagent-security Bot added the contributor:verified Contributor passed trust analysis. label May 24, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

📝 Walkthrough

Walkthrough

This PR extends the application to support Google Gemini as an alternative AI provider alongside OpenAI. It adds Gemini configuration fields with encryption, implements provider selection with fallback logic, refines OpenAI's model handling for custom endpoints, and provides UI and localization for the new settings.

Changes

Gemini AI Provider Integration

Layer / File(s) Summary
Gemini provider infrastructure and credential management
app/models/setting.rb, app/models/provider/registry.rb, app/models/user.rb
New Setting fields gemini_api_key (encrypted) and gemini_model are added. Provider::Registry#gemini instantiates a Provider::Openai configured with Gemini's OpenAI-compatible endpoint, falling back from OpenAI when the token is absent. User#ai_available? is extended to detect Gemini API credentials alongside OpenAI.
OpenAI provider model selection logic
app/models/provider/openai.rb
chat_response computes effective_model: custom-provider instances (including Gemini) use their configured @default_model; standard OpenAI uses the caller's model parameter. Both native and generic chat paths receive effective_model.
Gemini settings persistence in controller
app/controllers/settings/hostings_controller.rb
The update method encrypts and persists gemini_api_key and stores gemini_model when provided. Strong parameters now permit both fields. LLM budget validation errors use the translation key path settings.hostings.llm_token_budget.<key>_label.
Settings UI partials and localization
app/views/settings/hostings/_gemini_settings.html.erb, app/views/settings/hostings/_llm_token_budget.html.erb, app/views/settings/hostings/_openai_settings.html.erb, app/views/settings/hostings/show.html.erb, config/locales/views/settings/hostings/en.yml, config/locales/views/chats/en.yml
New Gemini settings form partial (_gemini_settings) with auto-submitting fields for API key and model. Token budget controls are extracted into _llm_token_budget (used by both providers). The main settings view reorganizes sections to group OpenAI and Gemini under a dedicated "AI provider" section. English localization adds Gemini configuration labels, help text, and AI provider selection guidance; chat locale updates AI unavailability help to reference the Self-Hosting settings page.
Test coverage for Gemini and model selection
test/models/provider/openai_test.rb, test/models/provider/registry_test.rb, test/models/user_test.rb
Tests verify Gemini provider instantiation and classification, model selection behavior (custom providers use configured model; standard OpenAI uses caller's), provider fallback logic (OpenAI → Gemini, with priority when both present), and User#ai_available? returns true when Gemini API key is set.

Sequence Diagram(s)

sequenceDiagram
  participant RegistryClient
  participant ProviderRegistry
  participant SettingModel
  participant ProviderOpenai
  RegistryClient->>ProviderRegistry: openai() / gemini()
  ProviderRegistry->>SettingModel: gemini_api_key, gemini_model
  ProviderRegistry->>ProviderRegistry: Gemini API key missing?
  alt Gemini key exists
    ProviderRegistry->>ProviderOpenai: new with Gemini base URL
    ProviderOpenai-->>ProviderRegistry: Provider::Openai instance
  else OpenAI missing, fallback
    ProviderRegistry->>ProviderRegistry: openai() → gemini()
  else Both absent
    ProviderRegistry-->>RegistryClient: nil
  end
  ProviderRegistry-->>RegistryClient: Provider instance or nil
Loading
flowchart
  chat_response[chat_response called]
  chat_response --> custom_check{custom_provider?}
  custom_check -->|yes| use_default["effective_model = `@default_model`"]
  custom_check -->|no| use_caller["effective_model = model param"]
  use_default --> delegate["pass effective_model to native/generic response"]
  use_caller --> delegate
  delegate --> result["Return response"]
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • we-promise/sure#618: Both PRs update Settings::HostingsController#update and hosting_params strong-parameter whitelist to persist new hosting configuration fields.
  • we-promise/sure#1384: Both PRs modify Provider::Openai#chat_response model-routing logic and update settings controller validation/localization paths.
  • we-promise/sure#122: Both PRs extend User#ai_available? and Settings::HostingsController#update to handle self-hosted provider credentials and configuration.

Suggested labels

enhancement

Suggested reviewers

  • jjmata
  • sokie

🐰 A rabbit hops with glee,
New paths to code with Gemini free,
Where models blend and fallbacks flow,
Settings dance, as users know—
AI choices now: a buffet bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding Google Gemini as an alternative LLM provider, which is the primary objective across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 24, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 526ccf000c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +68 to +70
# Fall back to Gemini (via its OpenAI-compatible endpoint) when no
# OpenAI key is configured — all existing call-sites continue to work.
return gemini unless access_token.present?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid OpenAI→Gemini fallback for OpenAI-specific model calls

Returning gemini from openai here changes all Provider::Registry.get_provider(:openai) call sites to use Gemini when no OpenAI key is set, but some of those paths still pass OpenAI model names explicitly. For example, Assistant::Function::ImportBankStatement#execute always sends model: openai_model (defaulting to gpt-4.1), and Provider::Openai#extract_bank_statement does not override that model for custom providers, so Gemini-only deployments will send an invalid model and fail extraction requests.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/models/setting.rb`:
- Around line 13-14: Add a setter for gemini_model that mirrors openai_model=:
implement a gemini_model=(val) method in the Setting model that compares the new
value to the current value, writes the attribute (same persistence approach used
by openai_model=, e.g. write_attribute or self[:gemini_model]=) and, if the
model changed, calls the same cache invalidation routine used by openai_model=
(the method used there — e.g. clear_ai_cache!, clear_all_ai_cache, or
equivalent) so Gemini model changes flush the AI cache.

In `@test/models/user_test.rb`:
- Around line 279-292: The test mutates Setting.openai_access_token but only
restores Setting.gemini_api_key in the ensure block; capture the original
Setting.openai_access_token at the start of the test (e.g., store it in a local
variable like previous_openai), and then restore it alongside previous for
Setting.gemini_api_key in the ensure block so both settings (openai_access_token
and gemini_api_key) are restored after the test in the test "ai_available?
returns true when gemini api key set in settings".
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d38f1afa-2cca-4e88-a057-078200b83491

📥 Commits

Reviewing files that changed from the base of the PR and between 89f4249 and 526ccf0.

📒 Files selected for processing (14)
  • app/controllers/settings/hostings_controller.rb
  • app/models/provider/openai.rb
  • app/models/provider/registry.rb
  • app/models/setting.rb
  • app/models/user.rb
  • app/views/settings/hostings/_gemini_settings.html.erb
  • app/views/settings/hostings/_llm_token_budget.html.erb
  • app/views/settings/hostings/_openai_settings.html.erb
  • app/views/settings/hostings/show.html.erb
  • config/locales/views/chats/en.yml
  • config/locales/views/settings/hostings/en.yml
  • test/models/provider/openai_test.rb
  • test/models/provider/registry_test.rb
  • test/models/user_test.rb
💤 Files with no reviewable changes (1)
  • app/views/settings/hostings/_openai_settings.html.erb

Comment thread app/models/setting.rb
Comment on lines +13 to +14
field :gemini_api_key, type: :string, default: ENV["GEMINI_API_KEY"]
field :gemini_model, type: :string, default: ENV["GEMINI_MODEL"]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Mirror cache invalidation behavior for gemini_model changes.

openai_model= clears AI cache when the model changes, but gemini_model currently has no equivalent hook. This can leave stale cached AI outputs after switching Gemini models.

💡 Suggested parity fix
 class << self
   alias_method :raw_onboarding_state, :onboarding_state
   alias_method :raw_onboarding_state=, :onboarding_state=
   alias_method :raw_openai_model, :openai_model
   alias_method :raw_openai_model=, :openai_model=
+  alias_method :raw_gemini_model, :gemini_model
+  alias_method :raw_gemini_model=, :gemini_model=
@@
   def openai_model=(value)
     old_value = raw_openai_model
     self.raw_openai_model = value

     if old_value != value && old_value.present?
       Rails.logger.info("OpenAI model changed from #{old_value} to #{value}, clearing AI cache for all families")
       Family.find_each do |family|
         ClearAiCacheJob.perform_later(family)
       end
     end
   end
+
+  def gemini_model=(value)
+    old_value = raw_gemini_model
+    self.raw_gemini_model = value
+
+    if old_value != value && old_value.present?
+      Rails.logger.info("Gemini model changed from #{old_value} to #{value}, clearing AI cache for all families")
+      Family.find_each do |family|
+        ClearAiCacheJob.perform_later(family)
+      end
+    end
+  end
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/setting.rb` around lines 13 - 14, Add a setter for gemini_model
that mirrors openai_model=: implement a gemini_model=(val) method in the Setting
model that compares the new value to the current value, writes the attribute
(same persistence approach used by openai_model=, e.g. write_attribute or
self[:gemini_model]=) and, if the model changed, calls the same cache
invalidation routine used by openai_model= (the method used there — e.g.
clear_ai_cache!, clear_all_ai_cache, or equivalent) so Gemini model changes
flush the AI cache.

Comment thread test/models/user_test.rb
Comment on lines +279 to +292
test "ai_available? returns true when gemini api key set in settings" do
Rails.application.config.app_mode.stubs(:self_hosted?).returns(true)
previous = Setting.gemini_api_key
with_env_overrides OPENAI_ACCESS_TOKEN: nil, GEMINI_API_KEY: nil, EXTERNAL_ASSISTANT_URL: nil, EXTERNAL_ASSISTANT_TOKEN: nil do
Setting.openai_access_token = nil
Setting.gemini_api_key = nil
assert_not @user.ai_available?

Setting.gemini_api_key = "gemini-test-key"
assert @user.ai_available?
end
ensure
Setting.gemini_api_key = previous
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Restore OpenAI setting in teardown path for this test.

Line 283 mutates Setting.openai_access_token, but the ensure block only restores Gemini. This leaks global state and can make later tests order-dependent.

Suggested fix
 test "ai_available? returns true when gemini api key set in settings" do
   Rails.application.config.app_mode.stubs(:self_hosted?).returns(true)
-  previous = Setting.gemini_api_key
+  previous_openai = Setting.openai_access_token
+  previous_gemini = Setting.gemini_api_key
   with_env_overrides OPENAI_ACCESS_TOKEN: nil, GEMINI_API_KEY: nil, EXTERNAL_ASSISTANT_URL: nil, EXTERNAL_ASSISTANT_TOKEN: nil do
     Setting.openai_access_token = nil
     Setting.gemini_api_key = nil
     assert_not `@user.ai_available`?

     Setting.gemini_api_key = "gemini-test-key"
     assert `@user.ai_available`?
   end
 ensure
-  Setting.gemini_api_key = previous
+  Setting.openai_access_token = previous_openai
+  Setting.gemini_api_key = previous_gemini
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/models/user_test.rb` around lines 279 - 292, The test mutates
Setting.openai_access_token but only restores Setting.gemini_api_key in the
ensure block; capture the original Setting.openai_access_token at the start of
the test (e.g., store it in a local variable like previous_openai), and then
restore it alongside previous for Setting.gemini_api_key in the ensure block so
both settings (openai_access_token and gemini_api_key) are restored after the
test in the test "ai_available? returns true when gemini api key set in
settings".

Copy link
Copy Markdown
Collaborator

jjmata commented May 25, 2026

Reusing Provider::Openai via the OpenAI-compatible endpoint is the right call — no new class, minimal blast radius. The effective_model fix for custom providers is a real bug fix that would have caused silent 404s from Gemini. A few things worth discussing:

Transparent fallback in Provider::Registry.openai

return gemini unless access_token.present?

Any code that calls Provider::Registry.get_provider(:openai) now silently receives a Gemini instance when no OpenAI key is set. This is the intended behavior, but it means log lines, error messages, and any provider-name checks that say "openai" will now appear for Gemini calls. The provider_name on the Gemini-backed instance will return "Custom OpenAI-compatible (https://generativelanguage.googleapis.com/...)" rather than something user-friendly like "Google Gemini". Worth checking if provider_name is surfaced anywhere in the UI or logs.

Hardcoded Gemini endpoint URL

uri_base: "https://generativelanguage.googleapis.com/v1beta/openai/"

There's no GEMINI_URI_BASE env var for self-hosters who might run a local Gemini-compatible proxy (similar to the OPENAI_URI_BASE flexibility for OpenAI). Low priority, but a ENV["GEMINI_URI_BASE"].presence || prefix would be consistent with how OpenAI is handled.

gemini_model is unencrypted

gemini_api_key is correctly in ENCRYPTED_SETTING_FIELDS. gemini_model is not, which is correct — model names aren't sensitive. Just confirming this was intentional.

custom_provider? predicate

The diff references custom_provider? in Provider::Openai but that method isn't defined here — presumably it already exists in the base class. Worth confirming it correctly identifies the Gemini instance (i.e., the URI base check works as expected).

Both providers listed in available_providers for :llm

Adding :gemini to the list is fine, but what does Provider::Registry.for_concept(:llm) return when both are configured? If it returns both, any caller iterating over :llm providers would call both OpenAI and Gemini. The PR description says "OpenAI wins when both are set", which holds for get_provider(:openai), but for_concept(:llm) returning two entries might not be the right behavior.


Generated by Claude Code

@alessiocappa
Copy link
Copy Markdown
Collaborator

Is this really necessary? I’ve been using Gemini for quite some time now, and it already works in the current version.

Perhaps it’s a bit confusing now because “OpenAI” is mentioned in the settings, but technically, you can already use an OpenAI-compatible endpoint and key:

Screenshot 2026-05-25 alle 18 11 00

I would likely modify the existing code to make it more generic, rather than creating separate settings specifically for Gemini.

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 26, 2026

Hey @lifrank1 ... we have this working just by using https://generativelanguage.googleapis.com/v1beta/openai/ as the base URL. If you want to do Gemini "proper" take a look at the 5 PR series for Anthropic for inspiration: #1983

@jjmata jjmata closed this May 26, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. not-gittensor pr:verified PR passed security analysis.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants