dify_llm 1.8.1 → 1.9.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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -7
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +117 -69
  4. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +12 -12
  5. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +7 -7
  6. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  7. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +6 -6
  8. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +4 -4
  9. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -5
  10. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +5 -5
  11. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +4 -4
  12. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +8 -8
  13. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +5 -5
  14. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +9 -6
  15. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +7 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -5
  17. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +9 -9
  18. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +4 -6
  19. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +11 -11
  20. data/lib/generators/ruby_llm/generator_helpers.rb +131 -87
  21. data/lib/generators/ruby_llm/install/install_generator.rb +75 -79
  22. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +3 -0
  23. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +1 -1
  24. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +88 -85
  25. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  26. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  27. data/lib/ruby_llm/active_record/acts_as.rb +17 -8
  28. data/lib/ruby_llm/active_record/chat_methods.rb +41 -13
  29. data/lib/ruby_llm/active_record/message_methods.rb +11 -2
  30. data/lib/ruby_llm/active_record/model_methods.rb +1 -1
  31. data/lib/ruby_llm/aliases.json +62 -20
  32. data/lib/ruby_llm/attachment.rb +8 -0
  33. data/lib/ruby_llm/chat.rb +13 -2
  34. data/lib/ruby_llm/configuration.rb +6 -1
  35. data/lib/ruby_llm/connection.rb +4 -4
  36. data/lib/ruby_llm/content.rb +23 -0
  37. data/lib/ruby_llm/message.rb +11 -6
  38. data/lib/ruby_llm/model/info.rb +4 -0
  39. data/lib/ruby_llm/models.json +9410 -7793
  40. data/lib/ruby_llm/models.rb +14 -22
  41. data/lib/ruby_llm/provider.rb +23 -1
  42. data/lib/ruby_llm/providers/anthropic/chat.rb +22 -3
  43. data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
  44. data/lib/ruby_llm/providers/anthropic/media.rb +2 -1
  45. data/lib/ruby_llm/providers/anthropic/models.rb +15 -0
  46. data/lib/ruby_llm/providers/anthropic/streaming.rb +2 -0
  47. data/lib/ruby_llm/providers/anthropic/tools.rb +20 -18
  48. data/lib/ruby_llm/providers/bedrock/media.rb +2 -1
  49. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +15 -0
  50. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +2 -0
  51. data/lib/ruby_llm/providers/dify/chat.rb +16 -5
  52. data/lib/ruby_llm/providers/gemini/chat.rb +352 -69
  53. data/lib/ruby_llm/providers/gemini/media.rb +59 -1
  54. data/lib/ruby_llm/providers/gemini/tools.rb +146 -25
  55. data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
  56. data/lib/ruby_llm/providers/gemini.rb +2 -1
  57. data/lib/ruby_llm/providers/gpustack/media.rb +1 -0
  58. data/lib/ruby_llm/providers/ollama/media.rb +1 -0
  59. data/lib/ruby_llm/providers/openai/chat.rb +7 -2
  60. data/lib/ruby_llm/providers/openai/media.rb +2 -1
  61. data/lib/ruby_llm/providers/openai/streaming.rb +7 -2
  62. data/lib/ruby_llm/providers/openai/tools.rb +26 -6
  63. data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
  64. data/lib/ruby_llm/providers/openai.rb +1 -0
  65. data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
  66. data/lib/ruby_llm/providers/vertexai.rb +3 -0
  67. data/lib/ruby_llm/stream_accumulator.rb +10 -4
  68. data/lib/ruby_llm/tool.rb +126 -0
  69. data/lib/ruby_llm/transcription.rb +35 -0
  70. data/lib/ruby_llm/utils.rb +46 -0
  71. data/lib/ruby_llm/version.rb +1 -1
  72. data/lib/ruby_llm.rb +6 -0
  73. metadata +25 -3
@@ -5,117 +5,120 @@ require 'rails/generators/active_record'
5
5
  require_relative '../generator_helpers'
6
6
 
7
7
  module RubyLLM
8
- class UpgradeToV17Generator < Rails::Generators::Base # rubocop:disable Style/Documentation
9
- include Rails::Generators::Migration
10
- include RubyLLM::GeneratorHelpers
11
-
12
- namespace 'ruby_llm:upgrade_to_v1_7'
13
- source_root File.expand_path('templates', __dir__)
14
-
15
- # Override source_paths to include install templates
16
- def self.source_paths
17
- [
18
- File.expand_path('templates', __dir__),
19
- File.expand_path('../install/templates', __dir__)
20
- ]
21
- end
8
+ module Generators
9
+ # Generator to upgrade existing RubyLLM apps to v1.7 with new Rails-like API
10
+ class UpgradeToV17Generator < Rails::Generators::Base
11
+ include Rails::Generators::Migration
12
+ include RubyLLM::Generators::GeneratorHelpers
13
+
14
+ namespace 'ruby_llm:upgrade_to_v1_7'
15
+ source_root File.expand_path('templates', __dir__)
16
+
17
+ # Override source_paths to include install templates
18
+ def self.source_paths
19
+ [
20
+ File.expand_path('templates', __dir__),
21
+ File.expand_path('../install/templates', __dir__)
22
+ ]
23
+ end
22
24
 
23
- argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
25
+ argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName ...'
24
26
 
25
- desc 'Upgrades existing RubyLLM apps to v1.7 with new Rails-like API\n' \
26
- 'Usage: rails g ruby_llm:upgrade_to_v1_7 [chat:ChatName] [message:MessageName] ...'
27
+ desc 'Upgrades existing RubyLLM apps to v1.7 with new Rails-like API\n' \
28
+ 'Usage: rails g ruby_llm:upgrade_to_v1_7 [chat:ChatName] [message:MessageName] ...'
27
29
 
28
- def self.next_migration_number(dirname)
29
- ::ActiveRecord::Generators::Base.next_migration_number(dirname)
30
- end
30
+ def self.next_migration_number(dirname)
31
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
32
+ end
31
33
 
32
- def create_migration_file
33
- @model_table_already_existed = table_exists?(table_name_for(model_model_name))
34
+ def create_migration_file
35
+ @model_table_already_existed = table_exists?(table_name_for(model_model_name))
34
36
 
35
- # First check if models table exists, if not create it
36
- unless @model_table_already_existed
37
- migration_template 'create_models_migration.rb.tt',
38
- "db/migrate/create_#{table_name_for(model_model_name)}.rb",
39
- migration_version: migration_version,
40
- model_model_name: model_model_name
37
+ # First check if models table exists, if not create it
38
+ unless @model_table_already_existed
39
+ migration_template 'create_models_migration.rb.tt',
40
+ "db/migrate/create_#{table_name_for(model_model_name)}.rb",
41
+ migration_version: migration_version,
42
+ model_model_name: model_model_name
41
43
 
42
- sleep 1 # Ensure different timestamp
43
- end
44
+ sleep 1 # Ensure different timestamp
45
+ end
44
46
 
45
- migration_template 'migration.rb.tt',
46
- 'db/migrate/migrate_to_ruby_llm_model_references.rb',
47
- migration_version: migration_version,
48
- chat_model_name: chat_model_name,
49
- message_model_name: message_model_name,
50
- tool_call_model_name: tool_call_model_name,
51
- model_model_name: model_model_name,
52
- model_table_already_existed: @model_table_already_existed
53
- end
47
+ migration_template 'migration.rb.tt',
48
+ 'db/migrate/migrate_to_ruby_llm_model_references.rb',
49
+ migration_version: migration_version,
50
+ chat_model_name: chat_model_name,
51
+ message_model_name: message_model_name,
52
+ tool_call_model_name: tool_call_model_name,
53
+ model_model_name: model_model_name,
54
+ model_table_already_existed: @model_table_already_existed
55
+ end
54
56
 
55
- def create_model_file
56
- create_namespace_modules
57
+ def create_model_file
58
+ create_namespace_modules
57
59
 
58
- template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
59
- end
60
+ template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
61
+ end
60
62
 
61
- def update_existing_models
62
- update_model_acts_as(chat_model_name, 'acts_as_chat', acts_as_chat_declaration)
63
- update_model_acts_as(message_model_name, 'acts_as_message', acts_as_message_declaration)
64
- update_model_acts_as(tool_call_model_name, 'acts_as_tool_call', acts_as_tool_call_declaration)
65
- end
63
+ def update_existing_models
64
+ update_model_acts_as(chat_model_name, 'acts_as_chat', acts_as_chat_declaration)
65
+ update_model_acts_as(message_model_name, 'acts_as_message', acts_as_message_declaration)
66
+ update_model_acts_as(tool_call_model_name, 'acts_as_tool_call', acts_as_tool_call_declaration)
67
+ end
66
68
 
67
- def update_initializer
68
- initializer_path = 'config/initializers/ruby_llm.rb'
69
+ def update_initializer
70
+ initializer_path = 'config/initializers/ruby_llm.rb'
69
71
 
70
- unless File.exist?(initializer_path)
71
- say_status :warning, 'No initializer found. Creating one...', :yellow
72
- template 'initializer.rb.tt', initializer_path
73
- return
74
- end
72
+ unless File.exist?(initializer_path)
73
+ say_status :warning, 'No initializer found. Creating one...', :yellow
74
+ template 'initializer.rb.tt', initializer_path
75
+ return
76
+ end
75
77
 
76
- initializer_content = File.read(initializer_path)
78
+ initializer_content = File.read(initializer_path)
77
79
 
78
- return if initializer_content.include?('config.use_new_acts_as')
80
+ return if initializer_content.include?('config.use_new_acts_as')
79
81
 
80
- inject_into_file initializer_path, before: /^end/ do
81
- lines = ["\n # Enable the new Rails-like API", ' config.use_new_acts_as = true']
82
- lines << " config.model_registry_class = \"#{model_model_name}\"" if model_model_name != 'Model'
83
- lines << "\n"
84
- lines.join("\n")
82
+ inject_into_file initializer_path, before: /^end/ do
83
+ lines = ["\n # Enable the new Rails-like API", ' config.use_new_acts_as = true']
84
+ lines << " config.model_registry_class = \"#{model_model_name}\"" if model_model_name != 'Model'
85
+ lines << "\n"
86
+ lines.join("\n")
87
+ end
85
88
  end
86
- end
87
89
 
88
- def show_next_steps
89
- say_status :success, 'Upgrade prepared!', :green
90
- say <<~INSTRUCTIONS
90
+ def show_next_steps
91
+ say_status :success, 'Upgrade prepared!', :green
92
+ say <<~INSTRUCTIONS
91
93
 
92
- Next steps:
93
- 1. Review the generated migrations
94
- 2. Run: rails db:migrate
95
- 3. Update your code to use the new API: #{chat_model_name}.create! now has the same signature as RubyLLM.chat
94
+ Next steps:
95
+ 1. Review the generated migrations
96
+ 2. Run: rails db:migrate
97
+ 3. Update your code to use the new API: #{chat_model_name}.create! now has the same signature as RubyLLM.chat
96
98
 
97
- ⚠️ If you get "undefined method 'acts_as_model'" during migration:
98
- Add this to config/application.rb BEFORE your Application class:
99
+ ⚠️ If you get "undefined method 'acts_as_model'" during migration:
100
+ Add this to config/application.rb BEFORE your Application class:
99
101
 
100
- RubyLLM.configure do |config|
101
- config.use_new_acts_as = true
102
- end
102
+ RubyLLM.configure do |config|
103
+ config.use_new_acts_as = true
104
+ end
103
105
 
104
- 📚 See the full migration guide: https://rubyllm.com/upgrading-to-1-7/
106
+ 📚 See the full migration guide: https://rubyllm.com/upgrading-to-1-7/
105
107
 
106
- INSTRUCTIONS
107
- end
108
+ INSTRUCTIONS
109
+ end
108
110
 
109
- private
111
+ private
110
112
 
111
- def update_model_acts_as(model_name, old_acts_as, new_acts_as)
112
- model_path = "app/models/#{model_name.underscore}.rb"
113
- return unless File.exist?(Rails.root.join(model_path))
113
+ def update_model_acts_as(model_name, old_acts_as, new_acts_as)
114
+ model_path = "app/models/#{model_name.underscore}.rb"
115
+ return unless File.exist?(Rails.root.join(model_path))
114
116
 
115
- content = File.read(Rails.root.join(model_path))
116
- return unless content.match?(/^\s*#{old_acts_as}/)
117
+ content = File.read(Rails.root.join(model_path))
118
+ return unless content.match?(/^\s*#{old_acts_as}/)
117
119
 
118
- gsub_file model_path, /^\s*#{old_acts_as}.*$/, " #{new_acts_as}"
120
+ gsub_file model_path, /^\s*#{old_acts_as}.*$/, " #{new_acts_as}"
121
+ end
119
122
  end
120
123
  end
121
124
  end
@@ -0,0 +1,15 @@
1
+ class AddRubyLlmV19Columns < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ unless column_exists?(:<%= message_table_name %>, :cached_tokens)
4
+ add_column :<%= message_table_name %>, :cached_tokens, :integer
5
+ end
6
+
7
+ unless column_exists?(:<%= message_table_name %>, :cache_creation_tokens)
8
+ add_column :<%= message_table_name %>, :cache_creation_tokens, :integer
9
+ end
10
+
11
+ unless column_exists?(:<%= message_table_name %>, :content_raw)
12
+ add_column :<%= message_table_name %>, :content_raw, :json
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+ require 'rails/generators/active_record'
5
+ require_relative '../generator_helpers'
6
+
7
+ module RubyLLM
8
+ module Generators
9
+ # Generator to add v1.9 columns (cached tokens + raw content support) to existing apps.
10
+ class UpgradeToV19Generator < Rails::Generators::Base
11
+ include Rails::Generators::Migration
12
+ include RubyLLM::Generators::GeneratorHelpers
13
+
14
+ namespace 'ruby_llm:upgrade_to_v1_9'
15
+ source_root File.expand_path('templates', __dir__)
16
+
17
+ argument :model_mappings, type: :array, default: [], banner: 'message:MessageName'
18
+
19
+ desc 'Adds cached token columns and raw content storage fields introduced in v1.9.0'
20
+
21
+ def self.next_migration_number(dirname)
22
+ ::ActiveRecord::Generators::Base.next_migration_number(dirname)
23
+ end
24
+
25
+ def create_migration_file
26
+ parse_model_mappings
27
+
28
+ migration_template 'add_v1_9_message_columns.rb.tt',
29
+ 'db/migrate/add_ruby_llm_v1_9_columns.rb',
30
+ migration_version: migration_version,
31
+ message_table_name: message_table_name
32
+ end
33
+
34
+ def show_next_steps
35
+ say_status :success, 'Upgrade prepared!', :green
36
+ say <<~INSTRUCTIONS
37
+
38
+ Next steps:
39
+ 1. Review the generated migration
40
+ 2. Run: rails db:migrate
41
+ 3. Restart your application server
42
+
43
+ 📚 See the v1.9.0 release notes for details on cached token tracking and raw content support.
44
+
45
+ INSTRUCTIONS
46
+ end
47
+ end
48
+ end
49
+ end
@@ -11,22 +11,22 @@ module RubyLLM
11
11
  super
12
12
  # Monkey-patch Models to use database when ActsAs is active
13
13
  RubyLLM::Models.class_eval do
14
- def load_models
14
+ def self.load_models
15
15
  read_from_database
16
16
  rescue StandardError => e
17
17
  RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
18
18
  read_from_json
19
19
  end
20
20
 
21
- def load_from_database!
22
- @models = read_from_database
23
- end
24
-
25
- def read_from_database
21
+ def self.read_from_database
26
22
  model_class = RubyLLM.config.model_registry_class
27
23
  model_class = model_class.constantize if model_class.is_a?(String)
28
24
  model_class.all.map(&:to_llm)
29
25
  end
26
+
27
+ def load_from_database!
28
+ @models = self.class.read_from_database
29
+ end
30
30
  end
31
31
  end
32
32
 
@@ -45,10 +45,12 @@ module RubyLLM
45
45
  has_many messages,
46
46
  -> { order(created_at: :asc) },
47
47
  class_name: self.message_class,
48
+ foreign_key: ActiveSupport::Inflector.foreign_key(table_name.singularize),
48
49
  dependent: :destroy
49
50
 
50
51
  belongs_to model,
51
52
  class_name: self.model_class,
53
+ foreign_key: ActiveSupport::Inflector.foreign_key(model.to_s.singularize),
52
54
  optional: true
53
55
 
54
56
  delegate :add_message, to: :to_llm
@@ -78,7 +80,9 @@ module RubyLLM
78
80
  validates :provider, presence: true
79
81
  validates :name, presence: true
80
82
 
81
- has_many chats, class_name: self.chat_class
83
+ has_many chats,
84
+ class_name: self.chat_class,
85
+ foreign_key: ActiveSupport::Inflector.foreign_key(table_name.singularize)
82
86
 
83
87
  define_method :chats_association do
84
88
  send(chats_association_name)
@@ -102,10 +106,12 @@ module RubyLLM
102
106
 
103
107
  belongs_to chat,
104
108
  class_name: self.chat_class,
109
+ foreign_key: ActiveSupport::Inflector.foreign_key(chat.to_s.singularize),
105
110
  touch: touch_chat
106
111
 
107
112
  has_many tool_calls,
108
113
  class_name: self.tool_call_class,
114
+ foreign_key: ActiveSupport::Inflector.foreign_key(table_name.singularize),
109
115
  dependent: :destroy
110
116
 
111
117
  belongs_to :parent_tool_call,
@@ -120,6 +126,7 @@ module RubyLLM
120
126
 
121
127
  belongs_to model,
122
128
  class_name: self.model_class,
129
+ foreign_key: ActiveSupport::Inflector.foreign_key(model.to_s.singularize),
123
130
  optional: true
124
131
 
125
132
  delegate :tool_call?, :tool_result?, to: :to_llm
@@ -147,10 +154,12 @@ module RubyLLM
147
154
  self.result_class = (result_class || self.message_class).to_s
148
155
 
149
156
  belongs_to message,
150
- class_name: self.message_class
157
+ class_name: self.message_class,
158
+ foreign_key: ActiveSupport::Inflector.foreign_key(message.to_s.singularize)
151
159
 
152
160
  has_one result,
153
161
  class_name: self.result_class,
162
+ foreign_key: ActiveSupport::Inflector.foreign_key(table_name.singularize),
154
163
  dependent: :nullify
155
164
 
156
165
  define_method :message_association do
@@ -174,8 +174,16 @@ module RubyLLM
174
174
  end
175
175
 
176
176
  def create_user_message(content, with: nil)
177
- message_record = messages_association.create!(role: :user, content: content)
177
+ content_text, attachments, content_raw = prepare_content_for_storage(content)
178
+
179
+ message_record = messages_association.build(role: :user)
180
+ message_record.content = content_text
181
+ message_record.content_raw = content_raw if message_record.respond_to?(:content_raw=)
182
+ message_record.save!
183
+
178
184
  persist_content(message_record, with) if with.present?
185
+ persist_content(message_record, attachments) if attachments.present?
186
+
179
187
  message_record
180
188
  end
181
189
 
@@ -235,28 +243,25 @@ module RubyLLM
235
243
  @message = messages_association.create!(role: :assistant, content: '')
236
244
  end
237
245
 
238
- def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
246
+ # rubocop:disable Metrics/PerceivedComplexity
247
+ def persist_message_completion(message)
239
248
  return unless message
240
249
 
241
250
  tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
242
251
 
243
252
  transaction do
244
- content = message.content
245
- attachments_to_persist = nil
246
-
247
- if content.is_a?(RubyLLM::Content)
248
- attachments_to_persist = content.attachments if content.attachments.any?
249
- content = content.text
250
- elsif content.is_a?(Hash) || content.is_a?(Array)
251
- content = content.to_json
252
- end
253
+ content_text, attachments_to_persist, content_raw = prepare_content_for_storage(message.content)
253
254
 
254
255
  attrs = {
255
256
  role: message.role,
256
- content: content,
257
+ content: content_text,
257
258
  input_tokens: message.input_tokens,
258
259
  output_tokens: message.output_tokens
259
260
  }
261
+ attrs[:cached_tokens] = message.cached_tokens if @message.has_attribute?(:cached_tokens)
262
+ if @message.has_attribute?(:cache_creation_tokens)
263
+ attrs[:cache_creation_tokens] = message.cache_creation_tokens
264
+ end
260
265
 
261
266
  # Add model association dynamically
262
267
  attrs[self.class.model_association_name] = model_association
@@ -266,12 +271,15 @@ module RubyLLM
266
271
  attrs[parent_tool_call_assoc.foreign_key] = tool_call_id
267
272
  end
268
273
 
269
- @message.update!(attrs)
274
+ @message.assign_attributes(attrs)
275
+ @message.content_raw = content_raw if @message.respond_to?(:content_raw=)
276
+ @message.save!
270
277
 
271
278
  persist_content(@message, attachments_to_persist) if attachments_to_persist
272
279
  persist_tool_calls(message.tool_calls) if message.tool_calls.present?
273
280
  end
274
281
  end
282
+ # rubocop:enable Metrics/PerceivedComplexity
275
283
 
276
284
  def persist_tool_calls(tool_calls)
277
285
  tool_calls.each_value do |tool_call|
@@ -331,6 +339,26 @@ module RubyLLM
331
339
  RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
332
340
  nil
333
341
  end
342
+
343
+ def prepare_content_for_storage(content)
344
+ attachments = nil
345
+ content_raw = nil
346
+ content_text = content
347
+
348
+ case content
349
+ when RubyLLM::Content::Raw
350
+ content_raw = content.value
351
+ content_text = nil
352
+ when RubyLLM::Content
353
+ attachments = content.attachments if content.attachments.any?
354
+ content_text = content.text
355
+ when Hash, Array
356
+ content_raw = content
357
+ content_text = nil
358
+ end
359
+
360
+ [content_text, attachments, content_raw]
361
+ end
334
362
  end
335
363
  end
336
364
  end
@@ -11,6 +11,9 @@ module RubyLLM
11
11
  end
12
12
 
13
13
  def to_llm
14
+ cached = has_attribute?(:cached_tokens) ? self[:cached_tokens] : nil
15
+ cache_creation = has_attribute?(:cache_creation_tokens) ? self[:cache_creation_tokens] : nil
16
+
14
17
  RubyLLM::Message.new(
15
18
  role: role.to_sym,
16
19
  content: extract_content,
@@ -18,6 +21,8 @@ module RubyLLM
18
21
  tool_call_id: extract_tool_call_id,
19
22
  input_tokens: input_tokens,
20
23
  output_tokens: output_tokens,
24
+ cached_tokens: cached,
25
+ cache_creation_tokens: cache_creation,
21
26
  model_id: model_association&.model_id
22
27
  )
23
28
  end
@@ -42,9 +47,13 @@ module RubyLLM
42
47
  end
43
48
 
44
49
  def extract_content
45
- return content unless respond_to?(:attachments) && attachments.attached?
50
+ return RubyLLM::Content::Raw.new(content_raw) if has_attribute?(:content_raw) && content_raw.present?
51
+
52
+ content_value = self[:content]
53
+
54
+ return content_value unless respond_to?(:attachments) && attachments.attached?
46
55
 
47
- RubyLLM::Content.new(content).tap do |content_obj|
56
+ RubyLLM::Content.new(content_value).tap do |content_obj|
48
57
  @_tempfiles = []
49
58
 
50
59
  attachments.each do |attachment|
@@ -77,7 +77,7 @@ module RubyLLM
77
77
  delegate :supports?, :supports_vision?, :supports_functions?, :type,
78
78
  :input_price_per_million, :output_price_per_million,
79
79
  :function_calling?, :structured_output?, :batch?,
80
- :reasoning?, :citations?, :streaming?,
80
+ :reasoning?, :citations?, :streaming?, :provider_class,
81
81
  to: :to_llm
82
82
  end
83
83
  end
@@ -8,6 +8,9 @@
8
8
  "openrouter": "anthropic/claude-3.5-haiku",
9
9
  "bedrock": "anthropic.claude-3-5-haiku-20241022-v1:0"
10
10
  },
11
+ "claude-3-5-haiku-latest": {
12
+ "anthropic": "claude-3-5-haiku-latest"
13
+ },
11
14
  "claude-3-5-sonnet": {
12
15
  "anthropic": "claude-3-5-sonnet-20241022",
13
16
  "openrouter": "anthropic/claude-3.5-sonnet",
@@ -18,6 +21,9 @@
18
21
  "openrouter": "anthropic/claude-3.7-sonnet",
19
22
  "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
20
23
  },
24
+ "claude-3-7-sonnet-latest": {
25
+ "anthropic": "claude-3-7-sonnet-latest"
26
+ },
21
27
  "claude-3-haiku": {
22
28
  "anthropic": "claude-3-haiku-20240307",
23
29
  "openrouter": "anthropic/claude-3-haiku",
@@ -31,11 +37,19 @@
31
37
  "claude-3-sonnet": {
32
38
  "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
33
39
  },
40
+ "claude-haiku-4-5": {
41
+ "anthropic": "claude-haiku-4-5-20251001",
42
+ "openrouter": "anthropic/claude-haiku-4.5",
43
+ "bedrock": "us.anthropic.claude-haiku-4-5-20251001-v1:0"
44
+ },
34
45
  "claude-opus-4": {
35
46
  "anthropic": "claude-opus-4-20250514",
36
47
  "openrouter": "anthropic/claude-opus-4",
37
48
  "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
38
49
  },
50
+ "claude-opus-4-0": {
51
+ "anthropic": "claude-opus-4-0"
52
+ },
39
53
  "claude-opus-4-1": {
40
54
  "anthropic": "claude-opus-4-1-20250805",
41
55
  "openrouter": "anthropic/claude-opus-4.1",
@@ -46,30 +60,18 @@
46
60
  "openrouter": "anthropic/claude-sonnet-4",
47
61
  "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
48
62
  },
63
+ "claude-sonnet-4-0": {
64
+ "anthropic": "claude-sonnet-4-0"
65
+ },
66
+ "claude-sonnet-4-5": {
67
+ "anthropic": "claude-sonnet-4-5-20250929",
68
+ "openrouter": "anthropic/claude-sonnet-4.5",
69
+ "bedrock": "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
70
+ },
49
71
  "deepseek-chat": {
50
72
  "deepseek": "deepseek-chat",
51
73
  "openrouter": "deepseek/deepseek-chat"
52
74
  },
53
- "gemini-1.5-flash": {
54
- "gemini": "gemini-1.5-flash",
55
- "vertexai": "gemini-1.5-flash"
56
- },
57
- "gemini-1.5-flash-002": {
58
- "gemini": "gemini-1.5-flash-002",
59
- "vertexai": "gemini-1.5-flash-002"
60
- },
61
- "gemini-1.5-flash-8b": {
62
- "gemini": "gemini-1.5-flash-8b",
63
- "vertexai": "gemini-1.5-flash-8b"
64
- },
65
- "gemini-1.5-pro": {
66
- "gemini": "gemini-1.5-pro",
67
- "vertexai": "gemini-1.5-pro"
68
- },
69
- "gemini-1.5-pro-002": {
70
- "gemini": "gemini-1.5-pro-002",
71
- "vertexai": "gemini-1.5-pro-002"
72
- },
73
75
  "gemini-2.0-flash": {
74
76
  "gemini": "gemini-2.0-flash",
75
77
  "vertexai": "gemini-2.0-flash"
@@ -93,6 +95,10 @@
93
95
  "openrouter": "google/gemini-2.5-flash",
94
96
  "vertexai": "gemini-2.5-flash"
95
97
  },
98
+ "gemini-2.5-flash-image": {
99
+ "gemini": "gemini-2.5-flash-image",
100
+ "openrouter": "google/gemini-2.5-flash-image"
101
+ },
96
102
  "gemini-2.5-flash-image-preview": {
97
103
  "gemini": "gemini-2.5-flash-image-preview",
98
104
  "openrouter": "google/gemini-2.5-flash-image-preview"
@@ -106,6 +112,14 @@
106
112
  "gemini": "gemini-2.5-flash-lite-preview-06-17",
107
113
  "openrouter": "google/gemini-2.5-flash-lite-preview-06-17"
108
114
  },
115
+ "gemini-2.5-flash-lite-preview-09-2025": {
116
+ "gemini": "gemini-2.5-flash-lite-preview-09-2025",
117
+ "openrouter": "google/gemini-2.5-flash-lite-preview-09-2025"
118
+ },
119
+ "gemini-2.5-flash-preview-09-2025": {
120
+ "gemini": "gemini-2.5-flash-preview-09-2025",
121
+ "openrouter": "google/gemini-2.5-flash-preview-09-2025"
122
+ },
109
123
  "gemini-2.5-pro": {
110
124
  "gemini": "gemini-2.5-pro",
111
125
  "openrouter": "google/gemini-2.5-pro",
@@ -219,6 +233,10 @@
219
233
  "openai": "gpt-5",
220
234
  "openrouter": "openai/gpt-5"
221
235
  },
236
+ "gpt-5-codex": {
237
+ "openai": "gpt-5-codex",
238
+ "openrouter": "openai/gpt-5-codex"
239
+ },
222
240
  "gpt-5-mini": {
223
241
  "openai": "gpt-5-mini",
224
242
  "openrouter": "openai/gpt-5-mini"
@@ -227,6 +245,22 @@
227
245
  "openai": "gpt-5-nano",
228
246
  "openrouter": "openai/gpt-5-nano"
229
247
  },
248
+ "gpt-5-pro": {
249
+ "openai": "gpt-5-pro",
250
+ "openrouter": "openai/gpt-5-pro"
251
+ },
252
+ "gpt-oss-120b": {
253
+ "openai": "gpt-oss-120b",
254
+ "openrouter": "openai/gpt-oss-120b"
255
+ },
256
+ "gpt-oss-20b": {
257
+ "openai": "gpt-oss-20b",
258
+ "openrouter": "openai/gpt-oss-20b"
259
+ },
260
+ "imagen-4.0-generate-001": {
261
+ "gemini": "imagen-4.0-generate-001",
262
+ "vertexai": "imagen-4.0-generate-001"
263
+ },
230
264
  "o1": {
231
265
  "openai": "o1",
232
266
  "openrouter": "openai/o1"
@@ -247,6 +281,10 @@
247
281
  "openai": "o3",
248
282
  "openrouter": "openai/o3"
249
283
  },
284
+ "o3-deep-research": {
285
+ "openai": "o3-deep-research",
286
+ "openrouter": "openai/o3-deep-research"
287
+ },
250
288
  "o3-mini": {
251
289
  "openai": "o3-mini",
252
290
  "openrouter": "openai/o3-mini"
@@ -259,6 +297,10 @@
259
297
  "openai": "o4-mini",
260
298
  "openrouter": "openai/o4-mini"
261
299
  },
300
+ "o4-mini-deep-research": {
301
+ "openai": "o4-mini-deep-research",
302
+ "openrouter": "openai/o4-mini-deep-research"
303
+ },
262
304
  "text-embedding-004": {
263
305
  "gemini": "text-embedding-004",
264
306
  "vertexai": "text-embedding-004"