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.
- checksums.yaml +4 -4
- data/README.md +18 -1
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +127 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +39 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +24 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +12 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +29 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +11 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +23 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +10 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +9 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +16 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +30 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/install/install_generator.rb +227 -0
- data/lib/generators/ruby_llm/install/templates/chat_model.rb.tt +2 -2
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -4
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +8 -7
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +12 -3
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +6 -5
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +9 -8
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +2 -5
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +2 -2
- data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +137 -0
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +170 -0
- data/lib/ruby_llm/active_record/acts_as.rb +108 -467
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +403 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +336 -0
- data/lib/ruby_llm/active_record/message_methods.rb +72 -0
- data/lib/ruby_llm/active_record/model_methods.rb +84 -0
- data/lib/ruby_llm/aliases.json +72 -6
- data/lib/ruby_llm/attachment.rb +22 -0
- data/lib/ruby_llm/configuration.rb +6 -0
- data/lib/ruby_llm/image_attachment.rb +12 -3
- data/lib/ruby_llm/message.rb +1 -1
- data/lib/ruby_llm/models.json +2640 -1756
- data/lib/ruby_llm/models.rb +5 -15
- data/lib/ruby_llm/provider.rb +6 -4
- data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/models.rb +19 -1
- data/lib/ruby_llm/providers/gemini/media.rb +1 -1
- data/lib/ruby_llm/providers/gpustack/media.rb +1 -1
- data/lib/ruby_llm/providers/ollama/media.rb +1 -1
- data/lib/ruby_llm/providers/openai/media.rb +4 -4
- data/lib/ruby_llm/providers/openai/response.rb +7 -6
- data/lib/ruby_llm/providers/openai/response_media.rb +1 -1
- data/lib/ruby_llm/providers/openai/streaming.rb +14 -11
- data/lib/ruby_llm/providers/openai/tools.rb +11 -6
- data/lib/ruby_llm/providers/vertexai.rb +1 -1
- data/lib/ruby_llm/providers/xai/capabilities.rb +166 -0
- data/lib/ruby_llm/providers/xai/chat.rb +15 -0
- data/lib/ruby_llm/providers/xai/models.rb +48 -0
- data/lib/ruby_llm/providers/xai.rb +46 -0
- data/lib/ruby_llm/railtie.rb +20 -3
- data/lib/ruby_llm/stream_accumulator.rb +0 -4
- data/lib/ruby_llm/utils.rb +5 -9
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm_community.rb +4 -3
- data/lib/tasks/models.rake +29 -5
- data/lib/tasks/ruby_llm.rake +15 -0
- data/lib/tasks/vcr.rake +2 -2
- metadata +32 -3
- data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
- 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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
36
|
-
|
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
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
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
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
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
|
-
|
273
|
-
|
274
|
-
|
275
|
-
end
|
50
|
+
belongs_to model,
|
51
|
+
class_name: self.model_class,
|
52
|
+
optional: true
|
276
53
|
|
277
|
-
|
278
|
-
messages.reload
|
279
|
-
last = messages.order(:id).last
|
54
|
+
delegate :add_message, to: :to_llm
|
280
55
|
|
281
|
-
|
56
|
+
define_method :messages_association do
|
57
|
+
send(messages_association_name)
|
58
|
+
end
|
282
59
|
|
283
|
-
|
284
|
-
|
285
|
-
|
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
|
-
|
291
|
-
|
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
|
-
|
298
|
-
|
69
|
+
def acts_as_model(chats: :chats, chat_class: nil)
|
70
|
+
include RubyLLM::ActiveRecord::ModelMethods
|
299
71
|
|
300
|
-
|
301
|
-
@chat.on_end_message { |msg| persist_message_completion(msg) }
|
72
|
+
class_attribute :chats_association_name, :chat_class
|
302
73
|
|
303
|
-
|
304
|
-
|
305
|
-
end
|
74
|
+
self.chats_association_name = chats
|
75
|
+
self.chat_class = (chat_class || chats.to_s.classify).to_s
|
306
76
|
|
307
|
-
|
308
|
-
|
309
|
-
|
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
|
-
|
317
|
-
content = message.content
|
318
|
-
attachments_to_persist = nil
|
81
|
+
has_many chats, class_name: self.chat_class
|
319
82
|
|
320
|
-
|
321
|
-
|
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
|
-
|
358
|
-
|
359
|
-
|
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
|
-
|
362
|
-
|
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
|
-
|
377
|
-
|
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
|
-
|
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
|
-
|
393
|
-
|
394
|
-
|
107
|
+
has_many tool_calls,
|
108
|
+
class_name: self.tool_call_class,
|
109
|
+
dependent: :destroy
|
395
110
|
|
396
|
-
|
397
|
-
|
398
|
-
|
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
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
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
|
-
|
413
|
-
|
414
|
-
|
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
|
-
|
428
|
-
parent_tool_call&.tool_call_id
|
429
|
-
end
|
125
|
+
delegate :tool_call?, :tool_result?, to: :to_llm
|
430
126
|
|
431
|
-
|
432
|
-
|
127
|
+
define_method :chat_association do
|
128
|
+
send(chat_association_name)
|
129
|
+
end
|
433
130
|
|
434
|
-
|
435
|
-
|
131
|
+
define_method :tool_calls_association do
|
132
|
+
send(tool_calls_association_name)
|
133
|
+
end
|
436
134
|
|
437
|
-
|
438
|
-
|
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
|
-
|
445
|
-
|
446
|
-
|
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
|
-
|
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
|
-
|
453
|
-
|
454
|
-
@_tempfiles << tempfile
|
455
|
-
tempfile
|
456
|
-
end
|
457
|
-
end
|
149
|
+
belongs_to message,
|
150
|
+
class_name: self.message_class
|
458
151
|
|
459
|
-
|
460
|
-
|
461
|
-
|
152
|
+
has_one result,
|
153
|
+
class_name: self.result_class,
|
154
|
+
dependent: :nullify
|
462
155
|
|
463
|
-
|
464
|
-
|
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
|
-
|
483
|
-
|
484
|
-
|
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
|