ruby_llm 1.6.3 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -3
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +115 -0
  4. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
  7. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
  8. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +10 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +30 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
  19. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +2 -2
  20. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
  21. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
  22. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +43 -0
  23. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
  24. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +10 -4
  25. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
  26. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
  27. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
  28. data/lib/generators/ruby_llm/install_generator.rb +129 -33
  29. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +137 -0
  30. data/lib/generators/ruby_llm/upgrade_to_v1_7_generator.rb +160 -0
  31. data/lib/ruby_llm/active_record/acts_as.rb +112 -319
  32. data/lib/ruby_llm/active_record/acts_as_legacy.rb +398 -0
  33. data/lib/ruby_llm/active_record/chat_methods.rb +336 -0
  34. data/lib/ruby_llm/active_record/message_methods.rb +72 -0
  35. data/lib/ruby_llm/active_record/model_methods.rb +84 -0
  36. data/lib/ruby_llm/aliases.json +58 -13
  37. data/lib/ruby_llm/attachment.rb +20 -0
  38. data/lib/ruby_llm/chat.rb +8 -7
  39. data/lib/ruby_llm/configuration.rb +9 -0
  40. data/lib/ruby_llm/connection.rb +4 -4
  41. data/lib/ruby_llm/model/info.rb +12 -0
  42. data/lib/ruby_llm/models.json +3579 -2029
  43. data/lib/ruby_llm/models.rb +51 -22
  44. data/lib/ruby_llm/provider.rb +3 -3
  45. data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
  46. data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
  47. data/lib/ruby_llm/providers/anthropic/tools.rb +1 -1
  48. data/lib/ruby_llm/providers/bedrock/chat.rb +2 -2
  49. data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
  50. data/lib/ruby_llm/providers/gemini/chat.rb +53 -25
  51. data/lib/ruby_llm/providers/gemini/media.rb +1 -1
  52. data/lib/ruby_llm/providers/gpustack/chat.rb +11 -0
  53. data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
  54. data/lib/ruby_llm/providers/gpustack/models.rb +44 -8
  55. data/lib/ruby_llm/providers/gpustack.rb +1 -0
  56. data/lib/ruby_llm/providers/ollama/media.rb +2 -6
  57. data/lib/ruby_llm/providers/ollama/models.rb +36 -0
  58. data/lib/ruby_llm/providers/ollama.rb +1 -0
  59. data/lib/ruby_llm/providers/openai/chat.rb +1 -1
  60. data/lib/ruby_llm/providers/openai/media.rb +4 -4
  61. data/lib/ruby_llm/providers/openai/tools.rb +11 -6
  62. data/lib/ruby_llm/providers/openai.rb +2 -2
  63. data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
  64. data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
  65. data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
  66. data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
  67. data/lib/ruby_llm/providers/vertexai.rb +55 -0
  68. data/lib/ruby_llm/railtie.rb +20 -3
  69. data/lib/ruby_llm/streaming.rb +1 -1
  70. data/lib/ruby_llm/utils.rb +5 -9
  71. data/lib/ruby_llm/version.rb +1 -1
  72. data/lib/ruby_llm.rb +4 -3
  73. data/lib/tasks/models.rake +525 -0
  74. data/lib/tasks/release.rake +37 -2
  75. data/lib/tasks/ruby_llm.rake +15 -0
  76. data/lib/tasks/vcr.rake +2 -2
  77. metadata +37 -5
  78. data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
  79. data/lib/tasks/aliases.rake +0 -205
  80. data/lib/tasks/models_docs.rake +0 -214
  81. data/lib/tasks/models_update.rake +0 -108
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class VertexAI
6
+ # Chat methods for the Vertex AI implementation
7
+ module Chat
8
+ def completion_url
9
+ "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{@model}:generateContent" # rubocop:disable Layout/LineLength
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class VertexAI
6
+ # Embeddings methods for the Vertex AI implementation
7
+ module Embeddings
8
+ module_function
9
+
10
+ def embedding_url(model:)
11
+ "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{model}:predict" # rubocop:disable Layout/LineLength
12
+ end
13
+
14
+ def render_embedding_payload(text, model:, dimensions:) # rubocop:disable Lint/UnusedMethodArgument
15
+ {
16
+ instances: [text].flatten.map { |t| { content: t.to_s } }
17
+ }.tap do |payload|
18
+ payload[:parameters] = { outputDimensionality: dimensions } if dimensions
19
+ end
20
+ end
21
+
22
+ def parse_embedding_response(response, model:, text:)
23
+ predictions = response.body['predictions']
24
+ vectors = predictions&.map { |p| p.dig('embeddings', 'values') }
25
+ vectors = vectors.first if vectors&.length == 1 && !text.is_a?(Array)
26
+
27
+ Embedding.new(vectors:, model:, input_tokens: 0)
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class VertexAI
6
+ # Models methods for the Vertex AI integration
7
+ module Models
8
+ # Gemini and other Google models that aren't returned by the API
9
+ KNOWN_GOOGLE_MODELS = %w[
10
+ gemini-2.5-flash-lite
11
+ gemini-2.5-pro
12
+ gemini-2.5-flash
13
+ gemini-2.0-flash-lite-001
14
+ gemini-2.0-flash-001
15
+ gemini-2.0-flash
16
+ gemini-2.0-flash-exp
17
+ gemini-1.5-pro-002
18
+ gemini-1.5-pro
19
+ gemini-1.5-flash-002
20
+ gemini-1.5-flash
21
+ gemini-1.5-flash-8b
22
+ gemini-pro
23
+ gemini-pro-vision
24
+ gemini-exp-1206
25
+ gemini-exp-1121
26
+ gemini-embedding-001
27
+ text-embedding-005
28
+ text-embedding-004
29
+ text-multilingual-embedding-002
30
+ ].freeze
31
+
32
+ def list_models
33
+ all_models = []
34
+ page_token = nil
35
+
36
+ all_models.concat(build_known_models)
37
+
38
+ loop do
39
+ response = @connection.get('publishers/google/models') do |req|
40
+ req.headers['x-goog-user-project'] = @config.vertexai_project_id
41
+ req.params = { pageSize: 100 }
42
+ req.params[:pageToken] = page_token if page_token
43
+ end
44
+
45
+ publisher_models = response.body['publisherModels'] || []
46
+ publisher_models.each do |model_data|
47
+ next if model_data['launchStage'] == 'DEPRECATED'
48
+
49
+ model_id = extract_model_id_from_path(model_data['name'])
50
+ all_models << build_model_from_api_data(model_data, model_id)
51
+ end
52
+
53
+ page_token = response.body['nextPageToken']
54
+ break unless page_token
55
+ end
56
+
57
+ all_models
58
+ rescue StandardError => e
59
+ RubyLLM.logger.debug "Error fetching Vertex AI models: #{e.message}"
60
+ build_known_models
61
+ end
62
+
63
+ private
64
+
65
+ def build_known_models
66
+ KNOWN_GOOGLE_MODELS.map do |model_id|
67
+ Model::Info.new(
68
+ id: model_id,
69
+ name: model_id,
70
+ provider: slug,
71
+ family: determine_model_family(model_id),
72
+ created_at: nil,
73
+ context_window: nil,
74
+ max_output_tokens: nil,
75
+ modalities: nil,
76
+ capabilities: %w[streaming function_calling],
77
+ pricing: nil,
78
+ metadata: {
79
+ source: 'known_models'
80
+ }
81
+ )
82
+ end
83
+ end
84
+
85
+ def build_model_from_api_data(model_data, model_id)
86
+ Model::Info.new(
87
+ id: model_id,
88
+ name: model_id,
89
+ provider: slug,
90
+ family: determine_model_family(model_id),
91
+ created_at: nil,
92
+ context_window: nil,
93
+ max_output_tokens: nil,
94
+ modalities: nil,
95
+ capabilities: extract_capabilities(model_data),
96
+ pricing: nil,
97
+ metadata: {
98
+ version_id: model_data['versionId'],
99
+ open_source_category: model_data['openSourceCategory'],
100
+ launch_stage: model_data['launchStage'],
101
+ supported_actions: model_data['supportedActions'],
102
+ publisher_model_template: model_data['publisherModelTemplate']
103
+ }
104
+ )
105
+ end
106
+
107
+ def extract_model_id_from_path(path)
108
+ path.split('/').last
109
+ end
110
+
111
+ def determine_model_family(model_id)
112
+ case model_id
113
+ when /^gemini-2\.\d+/ then 'gemini-2'
114
+ when /^gemini-1\.\d+/ then 'gemini-1.5'
115
+ when /^text-embedding/ then 'text-embedding'
116
+ when /bison/ then 'palm'
117
+ else 'gemini'
118
+ end
119
+ end
120
+
121
+ def extract_capabilities(model_data)
122
+ capabilities = ['streaming']
123
+ model_name = model_data['name']
124
+ capabilities << 'function_calling' if model_name.include?('gemini')
125
+ capabilities.uniq
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class VertexAI
6
+ # Streaming methods for the Vertex AI implementation
7
+ module Streaming
8
+ def stream_url
9
+ "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{@model}:streamGenerateContent?alt=sse" # rubocop:disable Layout/LineLength
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ # Google Vertex AI implementation
6
+ class VertexAI < Gemini
7
+ include VertexAI::Chat
8
+ include VertexAI::Streaming
9
+ include VertexAI::Embeddings
10
+ include VertexAI::Models
11
+
12
+ def initialize(config)
13
+ super
14
+ @authorizer = nil
15
+ end
16
+
17
+ def api_base
18
+ "https://#{@config.vertexai_location}-aiplatform.googleapis.com/v1beta1"
19
+ end
20
+
21
+ def headers
22
+ {
23
+ 'Authorization' => "Bearer #{access_token}"
24
+ }
25
+ end
26
+
27
+ class << self
28
+ def configuration_requirements
29
+ %i[vertexai_project_id vertexai_location]
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def access_token
36
+ return 'test-token' if defined?(VCR) && !VCR.current_cassette.recording?
37
+
38
+ initialize_authorizer unless @authorizer
39
+ @authorizer.fetch_access_token!['access_token']
40
+ end
41
+
42
+ def initialize_authorizer
43
+ require 'googleauth'
44
+ @authorizer = ::Google::Auth.get_application_default(
45
+ scope: [
46
+ 'https://www.googleapis.com/auth/cloud-platform',
47
+ 'https://www.googleapis.com/auth/generative-language.retriever'
48
+ ]
49
+ )
50
+ rescue LoadError
51
+ raise Error, 'The googleauth gem is required for Vertex AI. Please add it to your Gemfile: gem "googleauth"'
52
+ end
53
+ end
54
+ end
55
+ end
@@ -3,14 +3,31 @@
3
3
  module RubyLLM
4
4
  # Rails integration for RubyLLM
5
5
  class Railtie < Rails::Railtie
6
+ initializer 'ruby_llm.inflections' do
7
+ ActiveSupport::Inflector.inflections(:en) do |inflect|
8
+ inflect.acronym 'LLM'
9
+ end
10
+ end
11
+
6
12
  initializer 'ruby_llm.active_record' do
7
13
  ActiveSupport.on_load :active_record do
8
- include RubyLLM::ActiveRecord::ActsAs
14
+ if RubyLLM.config.use_new_acts_as
15
+ require 'ruby_llm/active_record/acts_as'
16
+ ::ActiveRecord::Base.include RubyLLM::ActiveRecord::ActsAs
17
+ else
18
+ require 'ruby_llm/active_record/acts_as_legacy'
19
+ ::ActiveRecord::Base.include RubyLLM::ActiveRecord::ActsAsLegacy
20
+
21
+ Rails.logger.warn(
22
+ "\n!!! RubyLLM's legacy acts_as API is deprecated and will be removed in RubyLLM 2.0.0. " \
23
+ "Please consult the migration guide at https://rubyllm.com/upgrading-to-1-7/\n"
24
+ )
25
+ end
9
26
  end
10
27
  end
11
28
 
12
- generators do
13
- require 'generators/ruby_llm/install_generator'
29
+ rake_tasks do
30
+ load 'tasks/ruby_llm.rake'
14
31
  end
15
32
  end
16
33
  end
@@ -24,7 +24,7 @@ module RubyLLM
24
24
  end
25
25
 
26
26
  message = accumulator.to_message(response)
27
- RubyLLM.logger.debug "Stream completed: #{message.inspect}"
27
+ RubyLLM.logger.debug "Stream completed: #{message.content}"
28
28
  message
29
29
  end
30
30
 
@@ -5,10 +5,6 @@ module RubyLLM
5
5
  module Utils
6
6
  module_function
7
7
 
8
- def format_text_file_for_llm(text_file)
9
- "<file name='#{text_file.filename}' mime_type='#{text_file.mime_type}'>#{text_file.content}</file>"
10
- end
11
-
12
8
  def hash_get(hash, key)
13
9
  hash[key.to_sym] || hash[key.to_s]
14
10
  end
@@ -36,12 +32,12 @@ module RubyLLM
36
32
  value.is_a?(Date) ? value : Date.parse(value.to_s)
37
33
  end
38
34
 
39
- def deep_merge(params, payload)
40
- params.merge(payload) do |_key, params_value, payload_value|
41
- if params_value.is_a?(Hash) && payload_value.is_a?(Hash)
42
- deep_merge(params_value, payload_value)
35
+ def deep_merge(original, overrides)
36
+ original.merge(overrides) do |_key, original_value, overrides_value|
37
+ if original_value.is_a?(Hash) && overrides_value.is_a?(Hash)
38
+ deep_merge(original_value, overrides_value)
43
39
  else
44
- payload_value
40
+ overrides_value
45
41
  end
46
42
  end
47
43
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '1.6.3'
4
+ VERSION = '1.7.0'
5
5
  end
data/lib/ruby_llm.rb CHANGED
@@ -21,11 +21,11 @@ loader.inflector.inflect(
21
21
  'openrouter' => 'OpenRouter',
22
22
  'gpustack' => 'GPUStack',
23
23
  'mistral' => 'Mistral',
24
- 'pdf' => 'PDF'
24
+ 'vertexai' => 'VertexAI',
25
+ 'pdf' => 'PDF',
26
+ 'UI' => 'UI'
25
27
  )
26
28
  loader.ignore("#{__dir__}/tasks")
27
- loader.ignore("#{__dir__}/ruby_llm/railtie")
28
- loader.ignore("#{__dir__}/ruby_llm/active_record")
29
29
  loader.ignore("#{__dir__}/generators")
30
30
  loader.setup
31
31
 
@@ -88,6 +88,7 @@ RubyLLM::Provider.register :ollama, RubyLLM::Providers::Ollama
88
88
  RubyLLM::Provider.register :openai, RubyLLM::Providers::OpenAI
89
89
  RubyLLM::Provider.register :openrouter, RubyLLM::Providers::OpenRouter
90
90
  RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity
91
+ RubyLLM::Provider.register :vertexai, RubyLLM::Providers::VertexAI
91
92
 
92
93
  if defined?(Rails::Railtie)
93
94
  require 'ruby_llm/railtie'