dify_llm 1.6.4 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -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 +1 -1
  21. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +3 -3
  22. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +6 -6
  23. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -3
  24. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +5 -5
  25. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +6 -3
  26. data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -1
  27. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +1 -1
  28. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +1 -1
  29. data/lib/generators/ruby_llm/{migrate_model_fields → upgrade_to_v1_7}/templates/migration.rb.tt +16 -21
  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 +88 -58
  32. data/lib/ruby_llm/active_record/chat_methods.rb +51 -30
  33. data/lib/ruby_llm/active_record/message_methods.rb +2 -2
  34. data/lib/ruby_llm/aliases.json +0 -8
  35. data/lib/ruby_llm/configuration.rb +5 -0
  36. data/lib/ruby_llm/models.json +1724 -1497
  37. data/lib/ruby_llm/railtie.rb +4 -12
  38. data/lib/ruby_llm/version.rb +1 -1
  39. data/lib/ruby_llm.rb +2 -1
  40. metadata +23 -6
  41. data/lib/generators/ruby_llm/install/templates/create_chats_legacy_migration.rb.tt +0 -8
  42. data/lib/generators/ruby_llm/install/templates/create_messages_legacy_migration.rb.tt +0 -16
  43. data/lib/generators/ruby_llm/install_generator.rb +0 -184
  44. data/lib/generators/ruby_llm/migrate_model_fields_generator.rb +0 -84
@@ -31,105 +31,135 @@ module RubyLLM
31
31
  end
32
32
 
33
33
  class_methods do # rubocop:disable Metrics/BlockLength
34
- def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall',
35
- model_class: 'Model', model_foreign_key: nil)
34
+ def acts_as_chat(messages: :messages, message_class: nil,
35
+ model: :model, model_class: nil)
36
36
  include RubyLLM::ActiveRecord::ChatMethods
37
37
 
38
- @message_class = message_class.to_s
39
- @tool_call_class = tool_call_class.to_s
40
- @model_class = model_class.to_s
41
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
38
+ class_attribute :messages_association_name, :model_association_name, :message_class, :model_class
42
39
 
43
- has_many :messages,
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
44
+
45
+ has_many messages,
44
46
  -> { order(created_at: :asc) },
45
- class_name: @message_class,
46
- inverse_of: :chat,
47
+ class_name: self.message_class,
47
48
  dependent: :destroy
48
49
 
49
- belongs_to :model,
50
- class_name: @model_class,
51
- foreign_key: @model_foreign_key,
50
+ belongs_to model,
51
+ class_name: self.model_class,
52
52
  optional: true
53
53
 
54
54
  delegate :add_message, to: :to_llm
55
+
56
+ define_method :messages_association do
57
+ send(messages_association_name)
58
+ end
59
+
60
+ define_method :model_association do
61
+ send(model_association_name)
62
+ end
63
+
64
+ define_method :'model_association=' do |value|
65
+ send("#{model_association_name}=", value)
66
+ end
55
67
  end
56
68
 
57
- def acts_as_model(chat_class: 'Chat')
69
+ def acts_as_model(chats: :chats, chat_class: nil)
58
70
  include RubyLLM::ActiveRecord::ModelMethods
59
71
 
60
- @chat_class = chat_class.to_s
72
+ class_attribute :chats_association_name, :chat_class
73
+
74
+ self.chats_association_name = chats
75
+ self.chat_class = (chat_class || chats.to_s.classify).to_s
61
76
 
62
77
  validates :model_id, presence: true, uniqueness: { scope: :provider }
63
78
  validates :provider, presence: true
64
79
  validates :name, presence: true
65
80
 
66
- has_many :chats,
67
- class_name: @chat_class,
68
- foreign_key: ActiveSupport::Inflector.foreign_key(name)
81
+ has_many chats, class_name: self.chat_class
82
+
83
+ define_method :chats_association do
84
+ send(chats_association_name)
85
+ end
69
86
  end
70
87
 
71
- def acts_as_message(chat_class: 'Chat', # rubocop:disable Metrics/ParameterLists
72
- chat_foreign_key: nil,
73
- tool_call_class: 'ToolCall',
74
- tool_call_foreign_key: nil,
75
- model_class: 'Model',
76
- model_foreign_key: nil,
77
- touch_chat: false)
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)
78
91
  include RubyLLM::ActiveRecord::MessageMethods
79
92
 
80
- @chat_class = chat_class.to_s
81
- @chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
82
-
83
- @tool_call_class = tool_call_class.to_s
84
- @tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
93
+ class_attribute :chat_association_name, :tool_calls_association_name, :model_association_name,
94
+ :chat_class, :tool_call_class, :model_class
85
95
 
86
- @model_class = model_class.to_s
87
- @model_foreign_key = model_foreign_key || ActiveSupport::Inflector.foreign_key(@model_class)
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
88
102
 
89
- belongs_to :chat,
90
- class_name: @chat_class,
91
- foreign_key: @chat_foreign_key,
92
- inverse_of: :messages,
103
+ belongs_to chat,
104
+ class_name: self.chat_class,
93
105
  touch: touch_chat
94
106
 
95
- has_many :tool_calls,
96
- class_name: @tool_call_class,
107
+ has_many tool_calls,
108
+ class_name: self.tool_call_class,
97
109
  dependent: :destroy
98
110
 
99
111
  belongs_to :parent_tool_call,
100
- class_name: @tool_call_class,
101
- foreign_key: @tool_call_foreign_key,
102
- optional: true,
103
- inverse_of: :result
112
+ class_name: self.tool_call_class,
113
+ foreign_key: ActiveSupport::Inflector.foreign_key(tool_calls.to_s.singularize),
114
+ optional: true
104
115
 
105
116
  has_many :tool_results,
106
- through: :tool_calls,
117
+ through: tool_calls,
107
118
  source: :result,
108
- class_name: @message_class
119
+ class_name: name
109
120
 
110
- belongs_to :model,
111
- class_name: @model_class,
112
- foreign_key: @model_foreign_key,
121
+ belongs_to model,
122
+ class_name: self.model_class,
113
123
  optional: true
114
124
 
115
125
  delegate :tool_call?, :tool_result?, to: :to_llm
126
+
127
+ define_method :chat_association do
128
+ send(chat_association_name)
129
+ end
130
+
131
+ define_method :tool_calls_association do
132
+ send(tool_calls_association_name)
133
+ end
134
+
135
+ define_method :model_association do
136
+ send(model_association_name)
137
+ end
116
138
  end
117
139
 
118
- def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
119
- @message_class = message_class.to_s
120
- @message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
121
- @result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
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
122
143
 
123
- belongs_to :message,
124
- class_name: @message_class,
125
- foreign_key: @message_foreign_key,
126
- inverse_of: :tool_calls
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
127
148
 
128
- has_one :result,
129
- class_name: @message_class,
130
- foreign_key: @result_foreign_key,
131
- inverse_of: :parent_tool_call,
149
+ belongs_to message,
150
+ class_name: self.message_class
151
+
152
+ has_one result,
153
+ class_name: self.result_class,
132
154
  dependent: :nullify
155
+
156
+ define_method :message_association do
157
+ send(message_association_name)
158
+ end
159
+
160
+ define_method :result_association do
161
+ send(result_association_name)
162
+ end
133
163
  end
134
164
  end
135
165
  end
@@ -10,15 +10,17 @@ module RubyLLM
10
10
  before_save :resolve_model_from_strings
11
11
  end
12
12
 
13
- class_methods do
14
- attr_reader :tool_call_class, :model_class
15
- end
16
-
17
13
  attr_accessor :assume_model_exists, :context
18
14
 
19
15
  def model=(value)
20
16
  @model_string = value if value.is_a?(String)
21
- super unless value.is_a?(String)
17
+ return if value.is_a?(String)
18
+
19
+ if self.class.model_association_name == :model
20
+ super
21
+ else
22
+ self.model_association = value
23
+ end
22
24
  end
23
25
 
24
26
  def model_id=(value)
@@ -26,7 +28,7 @@ module RubyLLM
26
28
  end
27
29
 
28
30
  def model_id
29
- model&.model_id
31
+ model_association&.model_id
30
32
  end
31
33
 
32
34
  def provider=(value)
@@ -34,19 +36,21 @@ module RubyLLM
34
36
  end
35
37
 
36
38
  def provider
37
- model&.provider
39
+ model_association&.provider
38
40
  end
39
41
 
40
42
  private
41
43
 
42
44
  def resolve_model_from_strings # rubocop:disable Metrics/PerceivedComplexity
45
+ config = context&.config || RubyLLM.config
46
+ @model_string ||= config.default_model unless model_association
43
47
  return unless @model_string
44
48
 
45
49
  model_info, _provider = Models.resolve(
46
50
  @model_string,
47
51
  provider: @provider_string,
48
52
  assume_exists: assume_model_exists || false,
49
- config: context&.config || RubyLLM.config
53
+ config: config
50
54
  )
51
55
 
52
56
  model_class = self.class.model_class.constantize
@@ -64,7 +68,7 @@ module RubyLLM
64
68
  m.metadata = model_info.metadata || {}
65
69
  end
66
70
 
67
- self.model = model_record
71
+ self.model_association = model_record
68
72
  @model_string = nil
69
73
  @provider_string = nil
70
74
  end
@@ -72,15 +76,14 @@ module RubyLLM
72
76
  public
73
77
 
74
78
  def to_llm
75
- raise 'No model specified' unless model
76
-
79
+ model_record = model_association
77
80
  @chat ||= (context || RubyLLM).chat(
78
- model: model.model_id,
79
- provider: model.provider.to_sym
81
+ model: model_record.model_id,
82
+ provider: model_record.provider.to_sym
80
83
  )
81
84
  @chat.reset_messages!
82
85
 
83
- messages.each do |msg|
86
+ messages_association.each do |msg|
84
87
  @chat.add_message(msg.to_llm)
85
88
  end
86
89
 
@@ -89,8 +92,8 @@ module RubyLLM
89
92
 
90
93
  def with_instructions(instructions, replace: false)
91
94
  transaction do
92
- messages.where(role: :system).destroy_all if replace
93
- messages.create!(role: :system, content: instructions)
95
+ messages_association.where(role: :system).destroy_all if replace
96
+ messages_association.create!(role: :system, content: instructions)
94
97
  end
95
98
  to_llm.with_instructions(instructions)
96
99
  self
@@ -106,12 +109,13 @@ module RubyLLM
106
109
  self
107
110
  end
108
111
 
109
- def with_model(model_name, provider: nil)
110
- self.provider = provider if provider
112
+ def with_model(model_name, provider: nil, assume_exists: false)
111
113
  self.model = model_name
114
+ self.provider = provider if provider
115
+ self.assume_model_exists = assume_exists
112
116
  resolve_model_from_strings
113
117
  save!
114
- to_llm.with_model(model.model_id, provider: model.provider.to_sym)
118
+ to_llm.with_model(model.model_id, provider: model.provider.to_sym, assume_exists:)
115
119
  self
116
120
  end
117
121
 
@@ -170,7 +174,7 @@ module RubyLLM
170
174
  end
171
175
 
172
176
  def create_user_message(content, with: nil)
173
- message_record = messages.create!(role: :user, content: content)
177
+ message_record = messages_association.create!(role: :user, content: content)
174
178
  persist_content(message_record, with) if with.present?
175
179
  message_record
176
180
  end
@@ -198,8 +202,8 @@ module RubyLLM
198
202
  end
199
203
 
200
204
  def cleanup_orphaned_tool_results # rubocop:disable Metrics/PerceivedComplexity
201
- messages.reload
202
- last = messages.order(:id).last
205
+ messages_association.reload
206
+ last = messages_association.order(:id).last
203
207
 
204
208
  return unless last&.tool_call? || last&.tool_result?
205
209
 
@@ -228,7 +232,7 @@ module RubyLLM
228
232
  end
229
233
 
230
234
  def persist_new_message
231
- @message = messages.create!(role: :assistant, content: '')
235
+ @message = messages_association.create!(role: :assistant, content: '')
232
236
  end
233
237
 
234
238
  def persist_message_completion(message) # rubocop:disable Metrics/PerceivedComplexity
@@ -247,15 +251,22 @@ module RubyLLM
247
251
  content = content.to_json
248
252
  end
249
253
 
250
- @message.update!(
254
+ attrs = {
251
255
  role: message.role,
252
256
  content: content,
253
- model: model,
254
257
  input_tokens: message.input_tokens,
255
258
  output_tokens: message.output_tokens
256
- )
257
- @message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
258
- @message.save!
259
+ }
260
+
261
+ # Add model association dynamically
262
+ attrs[self.class.model_association_name] = model_association
263
+
264
+ if tool_call_id
265
+ parent_tool_call_assoc = @message.class.reflect_on_association(:parent_tool_call)
266
+ attrs[parent_tool_call_assoc.foreign_key] = tool_call_id
267
+ end
268
+
269
+ @message.update!(attrs)
259
270
 
260
271
  persist_content(@message, attachments_to_persist) if attachments_to_persist
261
272
  persist_tool_calls(message.tool_calls) if message.tool_calls.present?
@@ -266,12 +277,22 @@ module RubyLLM
266
277
  tool_calls.each_value do |tool_call|
267
278
  attributes = tool_call.to_h
268
279
  attributes[:tool_call_id] = attributes.delete(:id)
269
- @message.tool_calls.create!(**attributes)
280
+ @message.tool_calls_association.create!(**attributes)
270
281
  end
271
282
  end
272
283
 
273
284
  def find_tool_call_id(tool_call_id)
274
- self.class.tool_call_class.constantize.find_by(tool_call_id: tool_call_id)&.id
285
+ messages = messages_association
286
+ message_class = messages.klass
287
+ tool_calls_assoc = message_class.tool_calls_association_name
288
+ tool_call_table_name = message_class.reflect_on_association(tool_calls_assoc).table_name
289
+
290
+ message_with_tool_call = messages.joins(tool_calls_assoc)
291
+ .find_by(tool_call_table_name => { tool_call_id: tool_call_id })
292
+ return nil unless message_with_tool_call
293
+
294
+ tool_call = message_with_tool_call.tool_calls_association.find_by(tool_call_id: tool_call_id)
295
+ tool_call&.id
275
296
  end
276
297
 
277
298
  def persist_content(message_record, attachments)
@@ -18,14 +18,14 @@ module RubyLLM
18
18
  tool_call_id: extract_tool_call_id,
19
19
  input_tokens: input_tokens,
20
20
  output_tokens: output_tokens,
21
- model_id: model_id
21
+ model_id: model_association&.model_id
22
22
  )
23
23
  end
24
24
 
25
25
  private
26
26
 
27
27
  def extract_tool_calls
28
- tool_calls.to_h do |tool_call|
28
+ tool_calls_association.to_h do |tool_call|
29
29
  [
30
30
  tool_call.tool_call_id,
31
31
  RubyLLM::ToolCall.new(
@@ -227,14 +227,6 @@
227
227
  "openai": "gpt-5-nano",
228
228
  "openrouter": "openai/gpt-5-nano"
229
229
  },
230
- "gpt-oss-120b": {
231
- "openai": "gpt-oss-120b",
232
- "openrouter": "openai/gpt-oss-120b"
233
- },
234
- "gpt-oss-20b": {
235
- "openai": "gpt-oss-20b",
236
- "openrouter": "openai/gpt-oss-20b"
237
- },
238
230
  "o1": {
239
231
  "openai": "o1",
240
232
  "openrouter": "openai/o1"
@@ -32,6 +32,8 @@ module RubyLLM
32
32
  :default_image_model,
33
33
  # Model registry
34
34
  :model_registry_class,
35
+ # Rails integration
36
+ :use_new_acts_as,
35
37
  # Connection configuration
36
38
  :request_timeout,
37
39
  :max_retries,
@@ -57,6 +59,9 @@ module RubyLLM
57
59
  @default_embedding_model = 'text-embedding-3-small'
58
60
  @default_image_model = 'gpt-image-1'
59
61
 
62
+ @model_registry_class = 'Model'
63
+ @use_new_acts_as = false
64
+
60
65
  @log_file = $stdout
61
66
  @log_level = ENV['RUBYLLM_DEBUG'] ? Logger::DEBUG : Logger::INFO
62
67
  @log_stream_debug = ENV['RUBYLLM_STREAM_DEBUG'] == 'true'