ruby_llm_community 0.0.6 → 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 +20 -3
- 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 +43 -0
- 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 +10 -4
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +4 -3
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +3 -0
- 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 +112 -332
- 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 +130 -11
- data/lib/ruby_llm/aliases.rb +7 -25
- data/lib/ruby_llm/attachment.rb +22 -0
- data/lib/ruby_llm/chat.rb +10 -17
- data/lib/ruby_llm/configuration.rb +11 -12
- data/lib/ruby_llm/connection.rb +4 -4
- data/lib/ruby_llm/connection_multipart.rb +19 -0
- data/lib/ruby_llm/content.rb +5 -2
- data/lib/ruby_llm/embedding.rb +1 -2
- data/lib/ruby_llm/error.rb +0 -8
- data/lib/ruby_llm/image.rb +23 -8
- data/lib/ruby_llm/image_attachment.rb +30 -0
- data/lib/ruby_llm/message.rb +7 -7
- data/lib/ruby_llm/model/info.rb +12 -10
- data/lib/ruby_llm/model/pricing.rb +0 -3
- data/lib/ruby_llm/model/pricing_category.rb +0 -2
- data/lib/ruby_llm/model/pricing_tier.rb +0 -1
- data/lib/ruby_llm/models.json +4705 -2144
- data/lib/ruby_llm/models.rb +56 -35
- data/lib/ruby_llm/provider.rb +14 -12
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -46
- data/lib/ruby_llm/providers/anthropic/chat.rb +2 -2
- data/lib/ruby_llm/providers/anthropic/media.rb +1 -2
- data/lib/ruby_llm/providers/anthropic/tools.rb +1 -2
- data/lib/ruby_llm/providers/anthropic.rb +1 -2
- data/lib/ruby_llm/providers/bedrock/chat.rb +2 -4
- data/lib/ruby_llm/providers/bedrock/media.rb +0 -1
- data/lib/ruby_llm/providers/bedrock/models.rb +19 -3
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +0 -7
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +0 -12
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +0 -13
- data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -18
- data/lib/ruby_llm/providers/bedrock.rb +1 -2
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +1 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +0 -1
- data/lib/ruby_llm/providers/gemini/capabilities.rb +28 -100
- data/lib/ruby_llm/providers/gemini/chat.rb +57 -29
- data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -2
- data/lib/ruby_llm/providers/gemini/images.rb +1 -2
- data/lib/ruby_llm/providers/gemini/media.rb +1 -2
- data/lib/ruby_llm/providers/gemini/models.rb +1 -2
- data/lib/ruby_llm/providers/gemini/streaming.rb +15 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +0 -5
- data/lib/ruby_llm/providers/gpustack/chat.rb +11 -1
- data/lib/ruby_llm/providers/gpustack/media.rb +45 -0
- data/lib/ruby_llm/providers/gpustack/models.rb +44 -9
- data/lib/ruby_llm/providers/gpustack.rb +1 -0
- data/lib/ruby_llm/providers/mistral/capabilities.rb +2 -10
- data/lib/ruby_llm/providers/mistral/chat.rb +0 -2
- data/lib/ruby_llm/providers/mistral/embeddings.rb +0 -3
- data/lib/ruby_llm/providers/mistral/models.rb +0 -1
- data/lib/ruby_llm/providers/ollama/chat.rb +0 -1
- data/lib/ruby_llm/providers/ollama/media.rb +2 -7
- data/lib/ruby_llm/providers/ollama/models.rb +36 -0
- data/lib/ruby_llm/providers/ollama.rb +1 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +3 -16
- data/lib/ruby_llm/providers/openai/chat.rb +1 -3
- data/lib/ruby_llm/providers/openai/embeddings.rb +0 -3
- data/lib/ruby_llm/providers/openai/images.rb +73 -3
- data/lib/ruby_llm/providers/openai/media.rb +4 -5
- data/lib/ruby_llm/providers/openai/response.rb +121 -29
- data/lib/ruby_llm/providers/openai/response_media.rb +3 -3
- data/lib/ruby_llm/providers/openai/streaming.rb +110 -47
- data/lib/ruby_llm/providers/openai/tools.rb +12 -7
- data/lib/ruby_llm/providers/openai.rb +1 -3
- data/lib/ruby_llm/providers/openai_base.rb +2 -2
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -16
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -1
- data/lib/ruby_llm/providers/perplexity/chat.rb +0 -1
- data/lib/ruby_llm/providers/perplexity.rb +1 -5
- data/lib/ruby_llm/providers/vertexai/chat.rb +14 -0
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +32 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +130 -0
- data/lib/ruby_llm/providers/vertexai/streaming.rb +14 -0
- data/lib/ruby_llm/providers/vertexai.rb +55 -0
- 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 -4
- data/lib/ruby_llm/stream_accumulator.rb +68 -10
- data/lib/ruby_llm/streaming.rb +16 -25
- data/lib/ruby_llm/tool.rb +2 -19
- data/lib/ruby_llm/tool_call.rb +0 -9
- data/lib/ruby_llm/utils.rb +5 -9
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm_community.rb +8 -5
- data/lib/tasks/models.rake +549 -0
- data/lib/tasks/release.rake +37 -2
- data/lib/tasks/ruby_llm.rake +15 -0
- data/lib/tasks/vcr.rake +2 -9
- metadata +44 -6
- data/lib/generators/ruby_llm/install/templates/INSTALL_INFO.md.tt +0 -108
- data/lib/generators/ruby_llm/install_generator.rb +0 -121
- data/lib/tasks/aliases.rake +0 -235
- data/lib/tasks/models_docs.rake +0 -224
- data/lib/tasks/models_update.rake +0 -108
@@ -3,385 +3,165 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module ActiveRecord
|
5
5
|
# Adds chat and message persistence capabilities to ActiveRecord models.
|
6
|
-
# Provides a clean interface for storing chat history, message metadata,
|
7
|
-
# and attachments in your database.
|
8
6
|
module ActsAs
|
9
7
|
extend ActiveSupport::Concern
|
10
8
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
19
|
+
end
|
17
20
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
inverse_of: :chat,
|
22
|
-
dependent: :destroy
|
21
|
+
def load_from_database!
|
22
|
+
@models = read_from_database
|
23
|
+
end
|
23
24
|
|
24
|
-
|
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
|
25
30
|
end
|
31
|
+
end
|
26
32
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
touch_chat: false)
|
32
|
-
include MessageMethods
|
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
|
33
37
|
|
34
|
-
|
35
|
-
@chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
|
38
|
+
class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
|
36
39
|
|
37
|
-
|
38
|
-
|
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
|
39
44
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
inverse_of: :messages,
|
44
|
-
touch: touch_chat
|
45
|
-
|
46
|
-
has_many :tool_calls,
|
47
|
-
class_name: @tool_call_class,
|
45
|
+
has_many messages,
|
46
|
+
-> { order(created_at: :asc) },
|
47
|
+
class_name: self.message_class,
|
48
48
|
dependent: :destroy
|
49
49
|
|
50
|
-
belongs_to
|
51
|
-
class_name:
|
52
|
-
|
53
|
-
optional: true,
|
54
|
-
inverse_of: :result
|
55
|
-
|
56
|
-
delegate :tool_call?, :tool_result?, :tool_results, to: :to_llm
|
57
|
-
end
|
58
|
-
|
59
|
-
def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
|
60
|
-
@message_class = message_class.to_s
|
61
|
-
@message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
|
62
|
-
@result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
|
63
|
-
|
64
|
-
belongs_to :message,
|
65
|
-
class_name: @message_class,
|
66
|
-
foreign_key: @message_foreign_key,
|
67
|
-
inverse_of: :tool_calls
|
68
|
-
|
69
|
-
has_one :result,
|
70
|
-
class_name: @message_class,
|
71
|
-
foreign_key: @result_foreign_key,
|
72
|
-
inverse_of: :parent_tool_call,
|
73
|
-
dependent: :nullify
|
74
|
-
end
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# Methods mixed into chat models to handle message persistence and
|
79
|
-
# provide a conversation interface.
|
80
|
-
module ChatMethods
|
81
|
-
extend ActiveSupport::Concern
|
82
|
-
|
83
|
-
class_methods do
|
84
|
-
attr_reader :tool_call_class
|
85
|
-
end
|
50
|
+
belongs_to model,
|
51
|
+
class_name: self.model_class,
|
52
|
+
optional: true
|
86
53
|
|
87
|
-
|
88
|
-
@chat ||= if context
|
89
|
-
context.chat(model: model_id)
|
90
|
-
else
|
91
|
-
RubyLLM.chat(model: model_id)
|
92
|
-
end
|
93
|
-
@chat.reset_messages!
|
54
|
+
delegate :add_message, to: :to_llm
|
94
55
|
|
95
|
-
|
96
|
-
|
97
|
-
|
56
|
+
define_method :messages_association do
|
57
|
+
send(messages_association_name)
|
58
|
+
end
|
98
59
|
|
99
|
-
|
100
|
-
|
60
|
+
define_method :model_association do
|
61
|
+
send(model_association_name)
|
62
|
+
end
|
101
63
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
messages.create!(role: :system, content: instructions)
|
64
|
+
define_method :'model_association=' do |value|
|
65
|
+
send("#{model_association_name}=", value)
|
66
|
+
end
|
106
67
|
end
|
107
|
-
to_llm.with_instructions(instructions)
|
108
|
-
self
|
109
|
-
end
|
110
|
-
|
111
|
-
def with_tool(...)
|
112
|
-
to_llm.with_tool(...)
|
113
|
-
self
|
114
|
-
end
|
115
|
-
|
116
|
-
def with_tools(...)
|
117
|
-
to_llm.with_tools(...)
|
118
|
-
self
|
119
|
-
end
|
120
|
-
|
121
|
-
def with_model(...)
|
122
|
-
update(model_id: to_llm.with_model(...).model.id)
|
123
|
-
self
|
124
|
-
end
|
125
68
|
|
126
|
-
|
127
|
-
|
128
|
-
self
|
129
|
-
end
|
130
|
-
|
131
|
-
def with_context(context)
|
132
|
-
to_llm(context: context)
|
133
|
-
self
|
134
|
-
end
|
69
|
+
def acts_as_model(chats: :chats, chat_class: nil)
|
70
|
+
include RubyLLM::ActiveRecord::ModelMethods
|
135
71
|
|
136
|
-
|
137
|
-
to_llm.with_params(...)
|
138
|
-
self
|
139
|
-
end
|
72
|
+
class_attribute :chats_association_name, :chat_class
|
140
73
|
|
141
|
-
|
142
|
-
|
143
|
-
self
|
144
|
-
end
|
145
|
-
|
146
|
-
def with_schema(...)
|
147
|
-
to_llm.with_schema(...)
|
148
|
-
self
|
149
|
-
end
|
74
|
+
self.chats_association_name = chats
|
75
|
+
self.chat_class = (chat_class || chats.to_s.classify).to_s
|
150
76
|
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
end
|
155
|
-
|
156
|
-
def on_new_message(&block)
|
157
|
-
to_llm
|
158
|
-
|
159
|
-
existing_callback = @chat.instance_variable_get(:@on)[:new_message]
|
160
|
-
|
161
|
-
@chat.on_new_message do
|
162
|
-
existing_callback&.call
|
163
|
-
block&.call
|
164
|
-
end
|
165
|
-
self
|
166
|
-
end
|
77
|
+
validates :model_id, presence: true, uniqueness: { scope: :provider }
|
78
|
+
validates :provider, presence: true
|
79
|
+
validates :name, presence: true
|
167
80
|
|
168
|
-
|
169
|
-
to_llm
|
81
|
+
has_many chats, class_name: self.chat_class
|
170
82
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
existing_callback&.call(msg)
|
175
|
-
block&.call(msg)
|
83
|
+
define_method :chats_association do
|
84
|
+
send(chats_association_name)
|
85
|
+
end
|
176
86
|
end
|
177
|
-
self
|
178
|
-
end
|
179
87
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
184
92
|
|
185
|
-
|
186
|
-
|
187
|
-
self
|
188
|
-
end
|
93
|
+
class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
|
94
|
+
:chat_class, :tool_call_class, :model_class
|
189
95
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
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
|
195
102
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
end
|
200
|
-
|
201
|
-
alias say ask
|
202
|
-
|
203
|
-
def complete(...)
|
204
|
-
to_llm.complete(...)
|
205
|
-
rescue RubyLLM::Error => e
|
206
|
-
cleanup_failed_messages if @message&.persisted? && @message.content.blank?
|
207
|
-
cleanup_orphaned_tool_results
|
208
|
-
raise e
|
209
|
-
end
|
210
|
-
|
211
|
-
private
|
212
|
-
|
213
|
-
def cleanup_failed_messages
|
214
|
-
RubyLLM.logger.debug "RubyLLM: API call failed, destroying message: #{@message.id}"
|
215
|
-
@message.destroy
|
216
|
-
end
|
103
|
+
belongs_to chat,
|
104
|
+
class_name: self.chat_class,
|
105
|
+
touch: touch_chat
|
217
106
|
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
last = messages.order(:id).last
|
107
|
+
has_many tool_calls,
|
108
|
+
class_name: self.tool_call_class,
|
109
|
+
dependent: :destroy
|
222
110
|
|
223
|
-
|
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
|
224
115
|
|
225
|
-
|
226
|
-
|
227
|
-
|
116
|
+
has_many :tool_results,
|
117
|
+
through: tool_calls,
|
118
|
+
source: :result,
|
119
|
+
class_name: name
|
228
120
|
|
229
|
-
|
230
|
-
|
231
|
-
|
121
|
+
belongs_to model,
|
122
|
+
class_name: self.model_class,
|
123
|
+
optional: true
|
232
124
|
|
233
|
-
|
234
|
-
@chat.on_new_message { persist_new_message }
|
235
|
-
@chat.on_end_message { |msg| persist_message_completion(msg) }
|
125
|
+
delegate :tool_call?, :tool_result?, to: :to_llm
|
236
126
|
|
237
|
-
|
238
|
-
|
239
|
-
|
127
|
+
define_method :chat_association do
|
128
|
+
send(chat_association_name)
|
129
|
+
end
|
240
130
|
|
241
|
-
|
242
|
-
|
243
|
-
|
131
|
+
define_method :tool_calls_association do
|
132
|
+
send(tool_calls_association_name)
|
133
|
+
end
|
244
134
|
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
|
249
|
-
|
250
|
-
transaction do
|
251
|
-
# Convert parsed JSON back to JSON string for storage
|
252
|
-
content = message.content
|
253
|
-
content = content.to_json if content.is_a?(Hash) || content.is_a?(Array)
|
254
|
-
|
255
|
-
@message.update!(
|
256
|
-
role: message.role,
|
257
|
-
content: content,
|
258
|
-
model_id: message.model_id,
|
259
|
-
input_tokens: message.input_tokens,
|
260
|
-
output_tokens: message.output_tokens
|
261
|
-
)
|
262
|
-
@message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
|
263
|
-
@message.save!
|
264
|
-
persist_tool_calls(message.tool_calls) if message.tool_calls.present?
|
135
|
+
define_method :model_association do
|
136
|
+
send(model_association_name)
|
137
|
+
end
|
265
138
|
end
|
266
|
-
end
|
267
139
|
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
attributes[:tool_call_id] = attributes.delete(:id)
|
272
|
-
@message.tool_calls.create!(**attributes)
|
273
|
-
end
|
274
|
-
end
|
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
|
275
143
|
|
276
|
-
|
277
|
-
|
278
|
-
|
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
|
279
148
|
|
280
|
-
|
281
|
-
|
149
|
+
belongs_to message,
|
150
|
+
class_name: self.message_class
|
282
151
|
|
283
|
-
|
284
|
-
|
285
|
-
|
152
|
+
has_one result,
|
153
|
+
class_name: self.result_class,
|
154
|
+
dependent: :nullify
|
286
155
|
|
287
|
-
|
288
|
-
|
289
|
-
case attachment
|
290
|
-
when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
|
291
|
-
attachment
|
292
|
-
when ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
293
|
-
attachment.blobs
|
294
|
-
when Hash
|
295
|
-
attachment.values.map { |v| prepare_for_active_storage(v) }
|
296
|
-
else
|
297
|
-
convert_to_active_storage_format(attachment)
|
156
|
+
define_method :message_association do
|
157
|
+
send(message_association_name)
|
298
158
|
end
|
299
|
-
end.flatten.compact
|
300
|
-
end
|
301
|
-
|
302
|
-
def convert_to_active_storage_format(source)
|
303
|
-
return if source.blank?
|
304
|
-
|
305
|
-
# Let RubyLLM::Attachment handle the heavy lifting
|
306
|
-
attachment = RubyLLM::Attachment.new(source)
|
307
|
-
|
308
|
-
{
|
309
|
-
io: StringIO.new(attachment.content),
|
310
|
-
filename: attachment.filename,
|
311
|
-
content_type: attachment.mime_type
|
312
|
-
}
|
313
|
-
rescue StandardError => e
|
314
|
-
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
315
|
-
nil
|
316
|
-
end
|
317
|
-
end
|
318
|
-
|
319
|
-
# Methods mixed into message models to handle serialization and
|
320
|
-
# provide a clean interface to the underlying message data.
|
321
|
-
module MessageMethods
|
322
|
-
extend ActiveSupport::Concern
|
323
|
-
|
324
|
-
class_methods do
|
325
|
-
attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
|
326
|
-
end
|
327
159
|
|
328
|
-
|
329
|
-
|
330
|
-
role: role.to_sym,
|
331
|
-
content: extract_content,
|
332
|
-
tool_calls: extract_tool_calls,
|
333
|
-
tool_call_id: extract_tool_call_id,
|
334
|
-
input_tokens: input_tokens,
|
335
|
-
output_tokens: output_tokens,
|
336
|
-
model_id: model_id
|
337
|
-
)
|
338
|
-
end
|
339
|
-
|
340
|
-
private
|
341
|
-
|
342
|
-
def extract_tool_calls
|
343
|
-
tool_calls.to_h do |tool_call|
|
344
|
-
[
|
345
|
-
tool_call.tool_call_id,
|
346
|
-
RubyLLM::ToolCall.new(
|
347
|
-
id: tool_call.tool_call_id,
|
348
|
-
name: tool_call.name,
|
349
|
-
arguments: tool_call.arguments
|
350
|
-
)
|
351
|
-
]
|
352
|
-
end
|
353
|
-
end
|
354
|
-
|
355
|
-
def extract_tool_call_id
|
356
|
-
parent_tool_call&.tool_call_id
|
357
|
-
end
|
358
|
-
|
359
|
-
def extract_content
|
360
|
-
return content unless respond_to?(:attachments) && attachments.attached?
|
361
|
-
|
362
|
-
RubyLLM::Content.new(content).tap do |content_obj|
|
363
|
-
@_tempfiles = []
|
364
|
-
|
365
|
-
attachments.each do |attachment|
|
366
|
-
tempfile = download_attachment(attachment)
|
367
|
-
content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
|
160
|
+
define_method :result_association do
|
161
|
+
send(result_association_name)
|
368
162
|
end
|
369
163
|
end
|
370
164
|
end
|
371
|
-
|
372
|
-
def download_attachment(attachment)
|
373
|
-
ext = File.extname(attachment.filename.to_s)
|
374
|
-
basename = File.basename(attachment.filename.to_s, ext)
|
375
|
-
tempfile = Tempfile.new([basename, ext])
|
376
|
-
tempfile.binmode
|
377
|
-
|
378
|
-
attachment.download { |chunk| tempfile.write(chunk) }
|
379
|
-
|
380
|
-
tempfile.flush
|
381
|
-
tempfile.rewind
|
382
|
-
@_tempfiles << tempfile
|
383
|
-
tempfile
|
384
|
-
end
|
385
165
|
end
|
386
166
|
end
|
387
167
|
end
|