ruby_llm_community 1.0.0 → 1.1.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +18 -1
  3. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +127 -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/install_generator.rb +227 -0
  20. data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +2 -2
  21. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
  22. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
  23. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +12 -3
  24. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
  25. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +9 -8
  26. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
  27. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +2 -5
  28. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
  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/upgrade_to_v1_7_generator.rb +170 -0
  31. data/lib/ruby_llm/active_record/acts_as.rb +108 -467
  32. data/lib/ruby_llm/active_record/acts_as_legacy.rb +403 -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 +72 -6
  37. data/lib/ruby_llm/attachment.rb +22 -0
  38. data/lib/ruby_llm/configuration.rb +6 -0
  39. data/lib/ruby_llm/image_attachment.rb +12 -3
  40. data/lib/ruby_llm/message.rb +1 -1
  41. data/lib/ruby_llm/models.json +2640 -1756
  42. data/lib/ruby_llm/models.rb +5 -15
  43. data/lib/ruby_llm/provider.rb +6 -4
  44. data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
  45. data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
  46. data/lib/ruby_llm/providers/gemini/media.rb +1 -1
  47. data/lib/ruby_llm/providers/gpustack/media.rb +1 -1
  48. data/lib/ruby_llm/providers/ollama/media.rb +1 -1
  49. data/lib/ruby_llm/providers/openai/media.rb +4 -4
  50. data/lib/ruby_llm/providers/openai/response.rb +7 -6
  51. data/lib/ruby_llm/providers/openai/response_media.rb +1 -1
  52. data/lib/ruby_llm/providers/openai/streaming.rb +14 -11
  53. data/lib/ruby_llm/providers/openai/tools.rb +11 -6
  54. data/lib/ruby_llm/providers/vertexai.rb +1 -1
  55. data/lib/ruby_llm/providers/xai/capabilities.rb +166 -0
  56. data/lib/ruby_llm/providers/xai/chat.rb +15 -0
  57. data/lib/ruby_llm/providers/xai/models.rb +48 -0
  58. data/lib/ruby_llm/providers/xai.rb +46 -0
  59. data/lib/ruby_llm/railtie.rb +20 -3
  60. data/lib/ruby_llm/stream_accumulator.rb +0 -4
  61. data/lib/ruby_llm/utils.rb +5 -9
  62. data/lib/ruby_llm/version.rb +1 -1
  63. data/lib/ruby_llm_community.rb +4 -3
  64. data/lib/tasks/models.rake +29 -5
  65. data/lib/tasks/ruby_llm.rake +15 -0
  66. data/lib/tasks/vcr.rake +2 -2
  67. metadata +32 -3
  68. data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
  69. data/lib/generators/ruby_llm/install_generator.rb +0 -146
@@ -6,521 +6,162 @@ module RubyLLM
6
6
  module ActsAs
7
7
  extend ActiveSupport::Concern
8
8
 
9
- class_methods do # rubocop:disable Metrics/BlockLength
10
- def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall',
11
- model_class: 'Model', model_foreign_key: nil, model_primary_key: nil)
12
- include ChatMethods
13
-
14
- @message_class = message_class.to_s
15
- @tool_call_class = tool_call_class.to_s
16
- @model_class = model_class.to_s
17
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
18
- @model_primary_key = model_primary_key || 'model_id'
19
-
20
- has_many :messages,
21
- -> { order(created_at: :asc) },
22
- class_name: @message_class,
23
- inverse_of: :chat,
24
- dependent: :destroy
25
-
26
- # Set up model association if model registry is configured
27
- if RubyLLM.config.model_registry_class
28
- belongs_to :model,
29
- class_name: @model_class,
30
- foreign_key: @model_foreign_key,
31
- primary_key: @model_primary_key,
32
- optional: true
9
+ # When ActsAs is included, ensure models are loaded from database
10
+ def self.included(base)
11
+ super
12
+ # Monkey-patch Models to use database when ActsAs is active
13
+ RubyLLM::Models.class_eval do
14
+ def load_models
15
+ read_from_database
16
+ rescue StandardError => e
17
+ RubyLLM.logger.debug "Failed to load models from database: #{e.message}, falling back to JSON"
18
+ read_from_json
33
19
  end
34
20
 
35
- delegate :add_message, to: :to_llm
36
- end
37
-
38
- def acts_as_model(chat_class: 'Chat')
39
- include ModelMethods
40
-
41
- @chat_class = chat_class.to_s
42
-
43
- validates :model_id, presence: true, uniqueness: { scope: :provider }
44
- validates :name, presence: true
45
- validates :provider, presence: true
46
-
47
- # Set up chat association if configured
48
- return unless RubyLLM.config.model_registry_class
49
-
50
- has_many :chats,
51
- class_name: @chat_class,
52
- foreign_key: 'model_id',
53
- primary_key: 'model_id'
54
- end
55
-
56
- def acts_as_message(chat_class: 'Chat', # rubocop:disable Metrics/ParameterLists
57
- chat_foreign_key: nil,
58
- tool_call_class: 'ToolCall',
59
- tool_call_foreign_key: nil,
60
- model_class: 'Model',
61
- model_foreign_key: nil,
62
- model_primary_key: nil,
63
- touch_chat: false)
64
- include MessageMethods
65
-
66
- @chat_class = chat_class.to_s
67
- @chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
68
-
69
- @tool_call_class = tool_call_class.to_s
70
- @tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
71
-
72
- @model_class = model_class.to_s
73
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
74
- @model_primary_key = model_primary_key || 'model_id'
75
-
76
- belongs_to :chat,
77
- class_name: @chat_class,
78
- foreign_key: @chat_foreign_key,
79
- inverse_of: :messages,
80
- touch: touch_chat
81
-
82
- has_many :tool_calls,
83
- class_name: @tool_call_class,
84
- dependent: :destroy
85
-
86
- belongs_to :parent_tool_call,
87
- class_name: @tool_call_class,
88
- foreign_key: @tool_call_foreign_key,
89
- optional: true,
90
- inverse_of: :result
91
-
92
- has_many :tool_results,
93
- through: :tool_calls,
94
- source: :result,
95
- class_name: @message_class
96
-
97
- # Set up model association if model registry is configured
98
- if RubyLLM.config.model_registry_class
99
- belongs_to :model,
100
- class_name: @model_class,
101
- foreign_key: @model_foreign_key,
102
- primary_key: @model_primary_key,
103
- optional: true
21
+ def load_from_database!
22
+ @models = read_from_database
104
23
  end
105
24
 
106
- delegate :tool_call?, :tool_result?, to: :to_llm
107
- end
108
-
109
- def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
110
- @message_class = message_class.to_s
111
- @message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
112
- @result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
113
-
114
- belongs_to :message,
115
- class_name: @message_class,
116
- foreign_key: @message_foreign_key,
117
- inverse_of: :tool_calls
118
-
119
- has_one :result,
120
- class_name: @message_class,
121
- foreign_key: @result_foreign_key,
122
- inverse_of: :parent_tool_call,
123
- dependent: :nullify
124
- end
125
- end
126
- end
127
-
128
- # Methods mixed into chat models.
129
- module ChatMethods
130
- extend ActiveSupport::Concern
131
-
132
- class_methods do
133
- attr_reader :tool_call_class
134
- end
135
-
136
- def to_llm(context: nil)
137
- # If we have a model association, use both model_id and provider
138
- # Otherwise, model_id is a string that RubyLLM can resolve
139
- if respond_to?(:model) && model
140
- model_to_use = model.model_id
141
- provider_to_use = model.provider.to_sym
142
- else
143
- model_to_use = model_id
144
- provider_to_use = nil
145
- end
146
-
147
- @chat ||= if context
148
- context.chat(model: model_to_use, provider: provider_to_use)
149
- else
150
- RubyLLM.chat(model: model_to_use, provider: provider_to_use)
151
- end
152
- @chat.reset_messages!
153
-
154
- messages.each do |msg|
155
- @chat.add_message(msg.to_llm)
156
- end
157
-
158
- setup_persistence_callbacks
159
- end
160
-
161
- def with_instructions(instructions, replace: false)
162
- transaction do
163
- messages.where(role: :system).destroy_all if replace
164
- messages.create!(role: :system, content: instructions)
165
- end
166
- to_llm.with_instructions(instructions)
167
- self
168
- end
169
-
170
- def with_tool(...)
171
- to_llm.with_tool(...)
172
- self
173
- end
174
-
175
- def with_tools(...)
176
- to_llm.with_tools(...)
177
- self
178
- end
179
-
180
- def with_model(...)
181
- update(model_id: to_llm.with_model(...).model.id)
182
- self
183
- end
184
-
185
- def with_temperature(...)
186
- to_llm.with_temperature(...)
187
- self
188
- end
189
-
190
- def with_context(context)
191
- to_llm(context: context)
192
- self
193
- end
194
-
195
- def with_params(...)
196
- to_llm.with_params(...)
197
- self
198
- end
199
-
200
- def with_headers(...)
201
- to_llm.with_headers(...)
202
- self
203
- end
204
-
205
- def with_schema(...)
206
- to_llm.with_schema(...)
207
- self
208
- end
209
-
210
- def cache_prompts(...)
211
- to_llm.cache_prompts(...)
212
- self
213
- end
214
-
215
- def on_new_message(&block)
216
- to_llm
217
-
218
- existing_callback = @chat.instance_variable_get(:@on)[:new_message]
219
-
220
- @chat.on_new_message do
221
- existing_callback&.call
222
- block&.call
223
- end
224
- self
225
- end
226
-
227
- def on_end_message(&block)
228
- to_llm
229
-
230
- existing_callback = @chat.instance_variable_get(:@on)[:end_message]
231
-
232
- @chat.on_end_message do |msg|
233
- existing_callback&.call(msg)
234
- block&.call(msg)
25
+ def read_from_database
26
+ model_class = RubyLLM.config.model_registry_class
27
+ model_class = model_class.constantize if model_class.is_a?(String)
28
+ model_class.all.map(&:to_llm)
29
+ end
235
30
  end
236
- self
237
- end
238
-
239
- def on_tool_call(...)
240
- to_llm.on_tool_call(...)
241
- self
242
- end
243
-
244
- def on_tool_result(...)
245
- to_llm.on_tool_result(...)
246
- self
247
31
  end
248
32
 
249
- def create_user_message(content, with: nil)
250
- message_record = messages.create!(role: :user, content: content)
251
- persist_content(message_record, with) if with.present?
252
- message_record
253
- end
33
+ class_methods do # rubocop:disable Metrics/BlockLength
34
+ def acts_as_chat(messages: :messages, message_class: nil,
35
+ model: :model, model_class: nil)
36
+ include RubyLLM::ActiveRecord::ChatMethods
254
37
 
255
- def ask(message, with: nil, &)
256
- create_user_message(message, with:)
257
- complete(&)
258
- end
38
+ class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
259
39
 
260
- alias say ask
40
+ self.messages_association_name = messages
41
+ self.model_association_name = model
42
+ self.message_class = (message_class || messages.to_s.classify).to_s
43
+ self.model_class = (model_class || model.to_s.classify).to_s
261
44
 
262
- def complete(...)
263
- to_llm.complete(...)
264
- rescue RubyLLM::Error => e
265
- cleanup_failed_messages if @message&.persisted? && @message.content.blank?
266
- cleanup_orphaned_tool_results
267
- raise e
268
- end
269
-
270
- private
45
+ has_many messages,
46
+ -> { order(created_at: :asc) },
47
+ class_name: self.message_class,
48
+ dependent: :destroy
271
49
 
272
- def cleanup_failed_messages
273
- RubyLLM.logger.warn "RubyLLM: API call failed, destroying message: #{@message.id}"
274
- @message.destroy
275
- end
50
+ belongs_to model,
51
+ class_name: self.model_class,
52
+ optional: true
276
53
 
277
- def cleanup_orphaned_tool_results # rubocop:disable Metrics/PerceivedComplexity
278
- messages.reload
279
- last = messages.order(:id).last
54
+ delegate :add_message, to: :to_llm
280
55
 
281
- return unless last&.tool_call? || last&.tool_result?
56
+ define_method :messages_association do
57
+ send(messages_association_name)
58
+ end
282
59
 
283
- if last.tool_call?
284
- last.destroy
285
- elsif last.tool_result?
286
- tool_call_message = last.parent_tool_call.message
287
- expected_results = tool_call_message.tool_calls.pluck(:id)
288
- actual_results = tool_call_message.tool_results.pluck(:tool_call_id)
60
+ define_method :model_association do
61
+ send(model_association_name)
62
+ end
289
63
 
290
- if expected_results.sort != actual_results.sort
291
- tool_call_message.tool_results.each(&:destroy)
292
- tool_call_message.destroy
64
+ define_method :'model_association=' do |value|
65
+ send("#{model_association_name}=", value)
293
66
  end
294
67
  end
295
- end
296
68
 
297
- def setup_persistence_callbacks
298
- return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
69
+ def acts_as_model(chats: :chats, chat_class: nil)
70
+ include RubyLLM::ActiveRecord::ModelMethods
299
71
 
300
- @chat.on_new_message { persist_new_message }
301
- @chat.on_end_message { |msg| persist_message_completion(msg) }
72
+ class_attribute :chats_association_name, :chat_class
302
73
 
303
- @chat.instance_variable_set(:@_persistence_callbacks_setup, true)
304
- @chat
305
- end
74
+ self.chats_association_name = chats
75
+ self.chat_class = (chat_class || chats.to_s.classify).to_s
306
76
 
307
- def persist_new_message
308
- @message = messages.create!(role: :assistant, content: '')
309
- end
310
-
311
- def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
312
- return unless message
313
-
314
- tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
77
+ validates :model_id, presence: true, uniqueness: { scope: :provider }
78
+ validates :provider, presence: true
79
+ validates :name, presence: true
315
80
 
316
- transaction do
317
- content = message.content
318
- attachments_to_persist = nil
81
+ has_many chats, class_name: self.chat_class
319
82
 
320
- if content.is_a?(RubyLLM::Content)
321
- attachments_to_persist = content.attachments if content.attachments.any?
322
- content = content.text
323
- elsif content.is_a?(Hash) || content.is_a?(Array)
324
- content = content.to_json
83
+ define_method :chats_association do
84
+ send(chats_association_name)
325
85
  end
326
-
327
- @message.update!(
328
- role: message.role,
329
- content: content,
330
- model_id: message.model_id,
331
- input_tokens: message.input_tokens,
332
- output_tokens: message.output_tokens
333
- )
334
- @message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
335
- @message.save!
336
-
337
- persist_content(@message, attachments_to_persist) if attachments_to_persist
338
- persist_tool_calls(message.tool_calls) if message.tool_calls.present?
339
- end
340
- end
341
-
342
- def persist_tool_calls(tool_calls)
343
- tool_calls.each_value do |tool_call|
344
- attributes = tool_call.to_h
345
- attributes[:tool_call_id] = attributes.delete(:id)
346
- @message.tool_calls.create!(**attributes)
347
86
  end
348
- end
349
-
350
- def find_tool_call_id(tool_call_id)
351
- self.class.tool_call_class.constantize.find_by(tool_call_id: tool_call_id)&.id
352
- end
353
-
354
- def persist_content(message_record, attachments)
355
- return unless message_record.respond_to?(:attachments)
356
87
 
357
- attachables = prepare_for_active_storage(attachments)
358
- message_record.attachments.attach(attachables) if attachables.any?
359
- end
88
+ def acts_as_message(chat: :chat, chat_class: nil, touch_chat: false, # rubocop:disable Metrics/ParameterLists
89
+ tool_calls: :tool_calls, tool_call_class: nil,
90
+ model: :model, model_class: nil)
91
+ include RubyLLM::ActiveRecord::MessageMethods
360
92
 
361
- def prepare_for_active_storage(attachments)
362
- Utils.to_safe_array(attachments).filter_map do |attachment|
363
- case attachment
364
- when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
365
- attachment
366
- when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
367
- attachment.blobs
368
- when Hash
369
- attachment.values.map { |v| prepare_for_active_storage(v) }
370
- else
371
- convert_to_active_storage_format(attachment)
372
- end
373
- end.flatten.compact
374
- end
93
+ class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
94
+ :chat_class, :tool_call_class, :model_class
375
95
 
376
- def convert_to_active_storage_format(source)
377
- return if source.blank?
96
+ self.chat_association_name = chat
97
+ self.tool_calls_association_name = tool_calls
98
+ self.model_association_name = model
99
+ self.chat_class = (chat_class || chat.to_s.classify).to_s
100
+ self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
101
+ self.model_class = (model_class || model.to_s.classify).to_s
378
102
 
379
- attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
380
-
381
- {
382
- io: StringIO.new(attachment.content),
383
- filename: attachment.filename,
384
- content_type: attachment.mime_type
385
- }
386
- rescue StandardError => e
387
- RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
388
- nil
389
- end
390
- end
103
+ belongs_to chat,
104
+ class_name: self.chat_class,
105
+ touch: touch_chat
391
106
 
392
- # Methods mixed into message models.
393
- module MessageMethods
394
- extend ActiveSupport::Concern
107
+ has_many tool_calls,
108
+ class_name: self.tool_call_class,
109
+ dependent: :destroy
395
110
 
396
- class_methods do
397
- attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
398
- end
111
+ belongs_to :parent_tool_call,
112
+ class_name: self.tool_call_class,
113
+ foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
114
+ optional: true
399
115
 
400
- def to_llm
401
- RubyLLM::Message.new(
402
- role: role.to_sym,
403
- content: extract_content,
404
- tool_calls: extract_tool_calls,
405
- tool_call_id: extract_tool_call_id,
406
- input_tokens: input_tokens,
407
- output_tokens: output_tokens,
408
- model_id: model_id
409
- )
410
- end
116
+ has_many :tool_results,
117
+ through: tool_calls,
118
+ source: :result,
119
+ class_name: name
411
120
 
412
- private
413
-
414
- def extract_tool_calls
415
- tool_calls.to_h do |tool_call|
416
- [
417
- tool_call.tool_call_id,
418
- RubyLLM::ToolCall.new(
419
- id: tool_call.tool_call_id,
420
- name: tool_call.name,
421
- arguments: tool_call.arguments
422
- )
423
- ]
424
- end
425
- end
121
+ belongs_to model,
122
+ class_name: self.model_class,
123
+ optional: true
426
124
 
427
- def extract_tool_call_id
428
- parent_tool_call&.tool_call_id
429
- end
125
+ delegate :tool_call?, :tool_result?, to: :to_llm
430
126
 
431
- def extract_content
432
- return content unless respond_to?(:attachments) && attachments.attached?
127
+ define_method :chat_association do
128
+ send(chat_association_name)
129
+ end
433
130
 
434
- RubyLLM::Content.new(content).tap do |content_obj|
435
- @_tempfiles = []
131
+ define_method :tool_calls_association do
132
+ send(tool_calls_association_name)
133
+ end
436
134
 
437
- attachments.each do |attachment|
438
- tempfile = download_attachment(attachment)
439
- content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
135
+ define_method :model_association do
136
+ send(model_association_name)
440
137
  end
441
138
  end
442
- end
443
139
 
444
- def download_attachment(attachment)
445
- ext = File.extname(attachment.filename.to_s)
446
- basename = File.basename(attachment.filename.to_s, ext)
447
- tempfile = Tempfile.new([basename, ext])
448
- tempfile.binmode
140
+ def acts_as_tool_call(message: :message, message_class: nil,
141
+ result: :result, result_class: nil)
142
+ class_attribute :message_association_name, :result_association_name, :message_class, :result_class
449
143
 
450
- attachment.download { |chunk| tempfile.write(chunk) }
144
+ self.message_association_name = message
145
+ self.result_association_name = result
146
+ self.message_class = (message_class || message.to_s.classify).to_s
147
+ self.result_class = (result_class || self.message_class).to_s
451
148
 
452
- tempfile.flush
453
- tempfile.rewind
454
- @_tempfiles << tempfile
455
- tempfile
456
- end
457
- end
149
+ belongs_to message,
150
+ class_name: self.message_class
458
151
 
459
- # Methods mixed into model registry models.
460
- module ModelMethods
461
- extend ActiveSupport::Concern
152
+ has_one result,
153
+ class_name: self.result_class,
154
+ dependent: :nullify
462
155
 
463
- class_methods do # rubocop:disable Metrics/BlockLength
464
- def refresh!
465
- RubyLLM.models.refresh!
466
-
467
- transaction do
468
- RubyLLM.models.all.each do |model_info|
469
- model = find_or_initialize_by(
470
- model_id: model_info.id,
471
- provider: model_info.provider
472
- )
473
- model.update!(from_llm_attributes(model_info))
474
- end
156
+ define_method :message_association do
157
+ send(message_association_name)
475
158
  end
476
- end
477
-
478
- def from_llm(model_info)
479
- new(from_llm_attributes(model_info))
480
- end
481
159
 
482
- private
483
-
484
- def from_llm_attributes(model_info)
485
- {
486
- model_id: model_info.id,
487
- name: model_info.name,
488
- provider: model_info.provider,
489
- family: model_info.family,
490
- model_created_at: model_info.created_at,
491
- context_window: model_info.context_window,
492
- max_output_tokens: model_info.max_output_tokens,
493
- knowledge_cutoff: model_info.knowledge_cutoff,
494
- modalities: model_info.modalities.to_h,
495
- capabilities: model_info.capabilities,
496
- pricing: model_info.pricing.to_h,
497
- metadata: model_info.metadata
498
- }
160
+ define_method :result_association do
161
+ send(result_association_name)
162
+ end
499
163
  end
500
164
  end
501
-
502
- def to_llm
503
- RubyLLM::Model::Info.new(
504
- id: model_id,
505
- name: name,
506
- provider: provider,
507
- family: family,
508
- created_at: model_created_at,
509
- context_window: context_window,
510
- max_output_tokens: max_output_tokens,
511
- knowledge_cutoff: knowledge_cutoff,
512
- modalities: modalities&.deep_symbolize_keys || {},
513
- capabilities: capabilities,
514
- pricing: pricing&.deep_symbolize_keys || {},
515
- metadata: metadata&.deep_symbolize_keys || {}
516
- )
517
- end
518
-
519
- delegate :supports?, :supports_vision?, :supports_functions?, :type,
520
- :input_price_per_million, :output_price_per_million,
521
- :function_calling?, :structured_output?, :batch?,
522
- :reasoning?, :citations?, :streaming?,
523
- to: :to_llm
524
165
  end
525
166
  end
526
167
  end