Skip to content

Commit

Permalink
Add support for gemini (#525)
Browse files Browse the repository at this point in the history
Co-authored-by: Keith Schacht <[email protected]>
Co-authored-by: Dr Nic Williams <[email protected]>
  • Loading branch information
3 people authored Nov 29, 2024
1 parent 18e0672 commit b8c9c52
Show file tree
Hide file tree
Showing 30 changed files with 449 additions and 59 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ gem "amatch", "~> 0.4.1" # enables fuzzy comparison of strings, a tool uses this
gem "rails_heroicon", "~> 2.2.0"
gem "ruby-openai", "~> 7.0.1"
gem "anthropic", "~> 0.1.0" # TODO update to the latest version
gem "gemini-ai", "~> 4.2.0"
gem "tiktoken_ruby", "~> 0.0.9"
gem "solid_queue", "~> 1.0.0"
gem "name_of_person"
Expand Down
45 changes: 40 additions & 5 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -133,20 +133,40 @@ GEM
erubi (1.13.0)
et-orbi (1.2.11)
tzinfo
ethon (0.16.0)
ffi (>= 1.15.0)
event_stream_parser (1.0.0)
faraday (2.8.1)
base64
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday (2.12.1)
faraday-net_http (>= 2.0, < 3.5)
json
logger
faraday-multipart (1.0.4)
multipart-post (~> 2)
faraday-net_http (3.0.2)
faraday-typhoeus (1.1.0)
faraday (~> 2.0)
typhoeus (~> 1.4)
ffi (1.15.5)
fugit (1.11.1)
et-orbi (~> 1, >= 1.2.11)
raabro (~> 1.4)
gemini-ai (4.2.0)
event_stream_parser (~> 1.0)
faraday (~> 2.10)
faraday-typhoeus (~> 1.1)
googleauth (~> 1.8)
typhoeus (~> 1.4, >= 1.4.1)
globalid (1.2.1)
activesupport (>= 6.1)
google-cloud-env (2.2.1)
faraday (>= 1.0, < 3.a)
googleauth (1.11.2)
faraday (>= 1.0, < 3.a)
google-cloud-env (~> 2.1)
jwt (>= 1.4, < 3.0)
multi_json (~> 1.11)
os (>= 0.9, < 2.0)
signet (>= 0.16, < 2.a)
hashie (5.0.0)
i18n (1.14.6)
concurrent-ruby (~> 1.0)
Expand Down Expand Up @@ -186,6 +206,7 @@ GEM
mize (0.4.1)
protocol (~> 2.0)
msgpack (1.7.2)
multi_json (1.15.0)
multi_xml (0.7.1)
bigdecimal (~> 3.1)
multipart-post (2.3.0)
Expand All @@ -203,6 +224,8 @@ GEM
nio4r (2.7.3)
nokogiri (1.16.7-arm64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-darwin)
racc (~> 1.4)
nokogiri (1.16.7-x86_64-linux)
racc (~> 1.4)
oauth2 (2.0.9)
Expand Down Expand Up @@ -231,6 +254,7 @@ GEM
omniauth-rails_csrf_protection (1.0.2)
actionpack (>= 4.2)
omniauth (~> 2.0)
os (1.1.4)
ostruct (0.6.0)
parallel (1.24.0)
parser (3.2.2.4)
Expand Down Expand Up @@ -357,7 +381,6 @@ GEM
ruby-vips (2.2.2)
ffi (~> 1.12)
logger
ruby2_keywords (0.0.5)
ruby_parser (3.21.0)
racc (~> 1.5)
sexp_processor (~> 4.16)
Expand All @@ -370,6 +393,11 @@ GEM
rubyzip (>= 1.2.2, < 3.0)
websocket (~> 1.0)
sexp_processor (4.17.1)
signet (0.19.0)
addressable (~> 2.8)
faraday (>= 0.17.5, < 3.a)
jwt (>= 1.5, < 3.0)
multi_json (~> 1.10)
snaky_hash (2.0.1)
hashie
version_gem (~> 1.1, >= 1.1.1)
Expand Down Expand Up @@ -406,10 +434,13 @@ GEM
sync (0.5.0)
tailwindcss-rails (2.7.2-arm64-darwin)
railties (>= 7.0.0)
tailwindcss-rails (2.7.2-x86_64-darwin)
railties (>= 7.0.0)
tailwindcss-rails (2.7.2-x86_64-linux)
railties (>= 7.0.0)
thor (1.3.2)
tiktoken_ruby (0.0.9-arm64-darwin)
tiktoken_ruby (0.0.9-x86_64-darwin)
tiktoken_ruby (0.0.9-x86_64-linux)
timecop (0.9.8)
timeout (0.4.1)
Expand All @@ -420,6 +451,8 @@ GEM
actionpack (>= 6.0.0)
activejob (>= 6.0.0)
railties (>= 6.0.0)
typhoeus (1.4.1)
ethon (>= 0.9.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
Expand All @@ -442,6 +475,7 @@ GEM
PLATFORMS
arm64-darwin-23
arm64-darwin-24
x86_64-darwin-22
x86_64-linux

DEPENDENCIES
Expand All @@ -456,6 +490,7 @@ DEPENDENCIES
debug
dockerfile-rails (>= 1.6)
ffi (~> 1.15.5)
gemini-ai (~> 4.2.0)
image_processing (~> 1.13.0)
importmap-rails
minitest-stub_any_instance
Expand Down
1 change: 1 addition & 0 deletions app/assets/images/google_gemini_logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 5 additions & 1 deletion app/helpers/settings/api_services_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Settings
module APIServicesHelper
def official?(model)
openai?(model) || anthropic?(model) || groq?(model)
openai?(model) || anthropic?(model) || groq?(model) || gemini?(model)
end

def openai?(api_service)
Expand All @@ -15,5 +15,9 @@ def anthropic?(api_service)
def groq?(api_service)
api_service.url == APIService::URL_GROQ
end

def gemini?(api_service)
api_service.url == APIService::URL_GEMINI
end
end
end
7 changes: 7 additions & 0 deletions app/jobs/autotitle_conversation_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ def generate_title_for(text)
response_format: { type: "json_object" } # this causes problems for Groq even though it's supported: https://console.groq.com/docs/api-reference#chat-create
)
return JSON.parse(response)["topic"]
elsif ai_backend.class == AIBackend::Gemini
response = ai_backend.get_oneoff_message(
system_message,
[text],
generation_config: { response_mime_type: "application/json" }
)
return JSON.parse(response)["topic"]
else
response = ai_backend.get_oneoff_message(
system_message,
Expand Down
17 changes: 15 additions & 2 deletions app/jobs/get_next_ai_message_job.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
include ActionView::RecordIdentifier
require "nokogiri/xml/node"
class ::Gemini::Errors::ConfigurationError < ::Gemini::Errors::GeminiError; end

class GetNextAIMessageJob < ApplicationJob
include ActionView::Helpers::RenderingHelper
Expand Down Expand Up @@ -77,6 +78,10 @@ def perform(user_id, message_id, assistant_id, attempt = 1)
set_anthropic_error
wrap_up_the_message
return true
rescue Gemini::Errors::ConfigurationError => e
set_generic_error("Gemini")
wrap_up_the_message
return true
rescue Faraday::ParsingError => e
set_response_error
wrap_up_the_message
Expand Down Expand Up @@ -166,8 +171,16 @@ def set_unexpected_error(msg, text)

def set_billing_error
service = ai_backend.to_s.split("::").second
url = service == "OpenAI" ? "https://platform.openai.com/account/billing/overview" : "https://console.anthropic.com/settings/plans"

url = case service
when "OpenAI"
"https://platform.openai.com/account/billing/overview"
when "Anthropic"
"https://console.anthropic.com/settings/plans"
when "Gemini"
"https://aistudio.google.com/app/apikey"
else
"https://platform.openai.com/account/billing/overview"
end
@message.content_text = "(I received a quota error. Try again and if you still get this error then your API key is probably valid, but you may need to adding billing details. You are using " +
"#{service} so go here #{url} and add a credit card, or if you already have one review your billing plan.)"
end
Expand Down
14 changes: 11 additions & 3 deletions app/models/api_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ class APIService < ApplicationRecord
URL_OPEN_AI = "https://api.openai.com/"
URL_ANTHROPIC = "https://api.anthropic.com/"
URL_GROQ = "https://api.groq.com/openai/v1/"
URL_GEMINI = "https://generativelanguage.googleapis.com/v1beta/"

belongs_to :user

has_many :language_models, -> { not_deleted }

enum :driver, %w[ openai anthropic ].index_by(&:to_sym)
enum :driver, %w[openai anthropic gemini].index_by(&:to_sym)

validates :url, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]), if: -> { url.present? }
validates :name, :url, presence: true
Expand All @@ -20,11 +21,18 @@ class APIService < ApplicationRecord
scope :ordered, -> { order(:name) }

def ai_backend
openai? ? AIBackend::OpenAI : AIBackend::Anthropic
case driver
when "openai"
AIBackend::OpenAI
when "anthropic"
AIBackend::Anthropic
when "gemini"
AIBackend::Gemini
end
end

def requires_token?
[URL_OPEN_AI, URL_ANTHROPIC].include?(url) # other services may require it but we don't always know
[URL_OPEN_AI, URL_ANTHROPIC, URL_GEMINI].include?(url) # other services may require it but we don't always know
end

def effective_token
Expand Down
2 changes: 1 addition & 1 deletion app/models/assistant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def to_param
end

def language_model_api_name=(api_name)
self.language_model = LanguageModel.find_by(api_name:)
self.language_model = LanguageModel.for_user(user).find_by(api_name:)
end

private
Expand Down
14 changes: 8 additions & 6 deletions app/models/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,17 @@ def redirect_to_processed_path(variant)
)
end

def file_base64(variant = :large)
return nil if !file.attached?
wait_for_file_variant_to_process!(variant.to_sym)
file_contents = file.variant(variant.to_sym).processed.download
base64 = Base64.strict_encode64(file_contents)
end

private

def file_data_url(variant = :large)
wait_for_file_variant_to_process!(variant)

file_contents = file.variant(variant.to_sym).processed.download
base64_data = Base64.strict_encode64(file_contents)

"data:#{file.blob.content_type};base64,#{base64_data}"
"data:#{file.blob.content_type};base64,#{file_base64(variant)}"
end

def set_default_user
Expand Down
1 change: 1 addition & 0 deletions app/models/user/registerable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def create_initial_assistants_etc
api_services.create!(url: APIService::URL_OPEN_AI, driver: :openai, name: "OpenAI")
api_services.create!(url: APIService::URL_ANTHROPIC, driver: :anthropic, name: "Anthropic")
api_services.create!(url: APIService::URL_GROQ, driver: :openai, name: "Groq")
api_services.create!(url: APIService::URL_GEMINI, driver: :gemini, name: "Google Gemini")

LanguageModel.import_from_file(users: [self])
Assistant.import_from_file(users: [self])
Expand Down
Loading

0 comments on commit b8c9c52

Please sign in to comment.