dify_llm 1.8.2 → 1.9.1

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -3
  3. data/lib/generators/ruby_llm/generator_helpers.rb +31 -10
  4. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +3 -0
  5. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +5 -0
  6. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +7 -1
  7. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +1 -1
  8. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +15 -0
  9. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +49 -0
  10. data/lib/ruby_llm/active_record/acts_as.rb +22 -24
  11. data/lib/ruby_llm/active_record/chat_methods.rb +41 -13
  12. data/lib/ruby_llm/active_record/message_methods.rb +11 -2
  13. data/lib/ruby_llm/active_record/model_methods.rb +1 -1
  14. data/lib/ruby_llm/aliases.json +61 -32
  15. data/lib/ruby_llm/attachment.rb +44 -13
  16. data/lib/ruby_llm/chat.rb +13 -2
  17. data/lib/ruby_llm/configuration.rb +6 -1
  18. data/lib/ruby_llm/connection.rb +3 -3
  19. data/lib/ruby_llm/content.rb +23 -0
  20. data/lib/ruby_llm/message.rb +11 -6
  21. data/lib/ruby_llm/model/info.rb +4 -0
  22. data/lib/ruby_llm/models.json +9649 -8211
  23. data/lib/ruby_llm/models.rb +14 -22
  24. data/lib/ruby_llm/provider.rb +23 -1
  25. data/lib/ruby_llm/providers/anthropic/chat.rb +22 -3
  26. data/lib/ruby_llm/providers/anthropic/content.rb +44 -0
  27. data/lib/ruby_llm/providers/anthropic/media.rb +3 -2
  28. data/lib/ruby_llm/providers/anthropic/models.rb +15 -0
  29. data/lib/ruby_llm/providers/anthropic/streaming.rb +2 -0
  30. data/lib/ruby_llm/providers/anthropic/tools.rb +20 -18
  31. data/lib/ruby_llm/providers/bedrock/media.rb +2 -1
  32. data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +15 -0
  33. data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +2 -0
  34. data/lib/ruby_llm/providers/dify/chat.rb +16 -5
  35. data/lib/ruby_llm/providers/gemini/chat.rb +352 -69
  36. data/lib/ruby_llm/providers/gemini/media.rb +59 -1
  37. data/lib/ruby_llm/providers/gemini/tools.rb +146 -25
  38. data/lib/ruby_llm/providers/gemini/transcription.rb +116 -0
  39. data/lib/ruby_llm/providers/gemini.rb +2 -1
  40. data/lib/ruby_llm/providers/gpustack/media.rb +1 -0
  41. data/lib/ruby_llm/providers/ollama/media.rb +1 -0
  42. data/lib/ruby_llm/providers/openai/chat.rb +7 -2
  43. data/lib/ruby_llm/providers/openai/media.rb +2 -1
  44. data/lib/ruby_llm/providers/openai/streaming.rb +7 -2
  45. data/lib/ruby_llm/providers/openai/tools.rb +26 -6
  46. data/lib/ruby_llm/providers/openai/transcription.rb +70 -0
  47. data/lib/ruby_llm/providers/openai.rb +1 -0
  48. data/lib/ruby_llm/providers/vertexai/transcription.rb +16 -0
  49. data/lib/ruby_llm/providers/vertexai.rb +11 -11
  50. data/lib/ruby_llm/railtie.rb +24 -22
  51. data/lib/ruby_llm/stream_accumulator.rb +10 -4
  52. data/lib/ruby_llm/tool.rb +126 -0
  53. data/lib/ruby_llm/transcription.rb +35 -0
  54. data/lib/ruby_llm/utils.rb +46 -0
  55. data/lib/ruby_llm/version.rb +1 -1
  56. data/lib/ruby_llm.rb +7 -0
  57. metadata +24 -3
@@ -8,16 +8,17 @@
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-sonnet": {
12
- "anthropic": "claude-3-5-sonnet-20241022",
13
- "openrouter": "anthropic/claude-3.5-sonnet",
14
- "bedrock": "anthropic.claude-3-5-sonnet-20240620-v1:0:200k"
11
+ "claude-3-5-haiku-latest": {
12
+ "anthropic": "claude-3-5-haiku-latest"
15
13
  },
16
14
  "claude-3-7-sonnet": {
17
15
  "anthropic": "claude-3-7-sonnet-20250219",
18
16
  "openrouter": "anthropic/claude-3.7-sonnet",
19
17
  "bedrock": "us.anthropic.claude-3-7-sonnet-20250219-v1:0"
20
18
  },
19
+ "claude-3-7-sonnet-latest": {
20
+ "anthropic": "claude-3-7-sonnet-latest"
21
+ },
21
22
  "claude-3-haiku": {
22
23
  "anthropic": "claude-3-haiku-20240307",
23
24
  "openrouter": "anthropic/claude-3-haiku",
@@ -31,11 +32,19 @@
31
32
  "claude-3-sonnet": {
32
33
  "bedrock": "anthropic.claude-3-sonnet-20240229-v1:0"
33
34
  },
35
+ "claude-haiku-4-5": {
36
+ "anthropic": "claude-haiku-4-5-20251001",
37
+ "openrouter": "anthropic/claude-haiku-4.5",
38
+ "bedrock": "us.anthropic.claude-haiku-4-5-20251001-v1:0"
39
+ },
34
40
  "claude-opus-4": {
35
41
  "anthropic": "claude-opus-4-20250514",
36
42
  "openrouter": "anthropic/claude-opus-4",
37
43
  "bedrock": "us.anthropic.claude-opus-4-1-20250805-v1:0"
38
44
  },
45
+ "claude-opus-4-0": {
46
+ "anthropic": "claude-opus-4-0"
47
+ },
39
48
  "claude-opus-4-1": {
40
49
  "anthropic": "claude-opus-4-1-20250805",
41
50
  "openrouter": "anthropic/claude-opus-4.1",
@@ -46,30 +55,18 @@
46
55
  "openrouter": "anthropic/claude-sonnet-4",
47
56
  "bedrock": "us.anthropic.claude-sonnet-4-20250514-v1:0"
48
57
  },
58
+ "claude-sonnet-4-0": {
59
+ "anthropic": "claude-sonnet-4-0"
60
+ },
61
+ "claude-sonnet-4-5": {
62
+ "anthropic": "claude-sonnet-4-5-20250929",
63
+ "openrouter": "anthropic/claude-sonnet-4.5",
64
+ "bedrock": "us.anthropic.claude-sonnet-4-5-20250929-v1:0"
65
+ },
49
66
  "deepseek-chat": {
50
67
  "deepseek": "deepseek-chat",
51
68
  "openrouter": "deepseek/deepseek-chat"
52
69
  },
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
70
  "gemini-2.0-flash": {
74
71
  "gemini": "gemini-2.0-flash",
75
72
  "vertexai": "gemini-2.0-flash"
@@ -93,6 +90,10 @@
93
90
  "openrouter": "google/gemini-2.5-flash",
94
91
  "vertexai": "gemini-2.5-flash"
95
92
  },
93
+ "gemini-2.5-flash-image": {
94
+ "gemini": "gemini-2.5-flash-image",
95
+ "openrouter": "google/gemini-2.5-flash-image"
96
+ },
96
97
  "gemini-2.5-flash-image-preview": {
97
98
  "gemini": "gemini-2.5-flash-image-preview",
98
99
  "openrouter": "google/gemini-2.5-flash-image-preview"
@@ -106,6 +107,14 @@
106
107
  "gemini": "gemini-2.5-flash-lite-preview-06-17",
107
108
  "openrouter": "google/gemini-2.5-flash-lite-preview-06-17"
108
109
  },
110
+ "gemini-2.5-flash-lite-preview-09-2025": {
111
+ "gemini": "gemini-2.5-flash-lite-preview-09-2025",
112
+ "openrouter": "google/gemini-2.5-flash-lite-preview-09-2025"
113
+ },
114
+ "gemini-2.5-flash-preview-09-2025": {
115
+ "gemini": "gemini-2.5-flash-preview-09-2025",
116
+ "openrouter": "google/gemini-2.5-flash-preview-09-2025"
117
+ },
109
118
  "gemini-2.5-pro": {
110
119
  "gemini": "gemini-2.5-pro",
111
120
  "openrouter": "google/gemini-2.5-pro",
@@ -219,6 +228,10 @@
219
228
  "openai": "gpt-5",
220
229
  "openrouter": "openai/gpt-5"
221
230
  },
231
+ "gpt-5-codex": {
232
+ "openai": "gpt-5-codex",
233
+ "openrouter": "openai/gpt-5-codex"
234
+ },
222
235
  "gpt-5-mini": {
223
236
  "openai": "gpt-5-mini",
224
237
  "openrouter": "openai/gpt-5-mini"
@@ -227,18 +240,26 @@
227
240
  "openai": "gpt-5-nano",
228
241
  "openrouter": "openai/gpt-5-nano"
229
242
  },
243
+ "gpt-5-pro": {
244
+ "openai": "gpt-5-pro",
245
+ "openrouter": "openai/gpt-5-pro"
246
+ },
247
+ "gpt-oss-120b": {
248
+ "openai": "gpt-oss-120b",
249
+ "openrouter": "openai/gpt-oss-120b"
250
+ },
251
+ "gpt-oss-20b": {
252
+ "openai": "gpt-oss-20b",
253
+ "openrouter": "openai/gpt-oss-20b"
254
+ },
255
+ "imagen-4.0-generate-001": {
256
+ "gemini": "imagen-4.0-generate-001",
257
+ "vertexai": "imagen-4.0-generate-001"
258
+ },
230
259
  "o1": {
231
260
  "openai": "o1",
232
261
  "openrouter": "openai/o1"
233
262
  },
234
- "o1-mini": {
235
- "openai": "o1-mini",
236
- "openrouter": "openai/o1-mini"
237
- },
238
- "o1-mini-2024-09-12": {
239
- "openai": "o1-mini-2024-09-12",
240
- "openrouter": "openai/o1-mini-2024-09-12"
241
- },
242
263
  "o1-pro": {
243
264
  "openai": "o1-pro",
244
265
  "openrouter": "openai/o1-pro"
@@ -247,6 +268,10 @@
247
268
  "openai": "o3",
248
269
  "openrouter": "openai/o3"
249
270
  },
271
+ "o3-deep-research": {
272
+ "openai": "o3-deep-research",
273
+ "openrouter": "openai/o3-deep-research"
274
+ },
250
275
  "o3-mini": {
251
276
  "openai": "o3-mini",
252
277
  "openrouter": "openai/o3-mini"
@@ -259,6 +284,10 @@
259
284
  "openai": "o4-mini",
260
285
  "openrouter": "openai/o4-mini"
261
286
  },
287
+ "o4-mini-deep-research": {
288
+ "openai": "o4-mini-deep-research",
289
+ "openrouter": "openai/o4-mini-deep-research"
290
+ },
262
291
  "text-embedding-004": {
263
292
  "gemini": "text-embedding-004",
264
293
  "vertexai": "text-embedding-004"
@@ -7,19 +7,8 @@ module RubyLLM
7
7
 
8
8
  def initialize(source, filename: nil)
9
9
  @source = source
10
- if url?
11
- @source = URI source
12
- @filename = filename || File.basename(@source.path).to_s
13
- elsif uuid?
14
- @upload_file_id = source
15
- elsif path?
16
- @source = Pathname.new source
17
- @filename = filename || @source.basename.to_s
18
- elsif active_storage?
19
- @filename = filename || extract_filename_from_active_storage
20
- else
21
- @filename = filename
22
- end
10
+ @source = source_type_cast
11
+ @filename = filename || source_filename
23
12
 
24
13
  determine_mime_type if @upload_file_id.nil?
25
14
  end
@@ -71,6 +60,14 @@ module RubyLLM
71
60
  Base64.strict_encode64(content)
72
61
  end
73
62
 
63
+ def save(path)
64
+ return unless io_like?
65
+
66
+ File.open(path, 'w') do |f|
67
+ f.puts(@source.read)
68
+ end
69
+ end
70
+
74
71
  def for_llm
75
72
  case type
76
73
  when :text
@@ -165,6 +162,40 @@ module RubyLLM
165
162
  end
166
163
  end
167
164
 
165
+ def source_type_cast
166
+ if url?
167
+ URI(@source)
168
+ elsif uuid?
169
+ @upload_file_id = @source
170
+ elsif path?
171
+ Pathname.new(@source)
172
+ else
173
+ @source
174
+ end
175
+ end
176
+
177
+ def source_filename
178
+ if url?
179
+ File.basename(@source.path).to_s
180
+ elsif path?
181
+ @source.basename.to_s
182
+ elsif io_like?
183
+ extract_filename_from_io
184
+ elsif active_storage?
185
+ extract_filename_from_active_storage
186
+ end
187
+ end
188
+
189
+ def extract_filename_from_io
190
+ if defined?(ActionDispatch::Http::UploadedFile) && @source.is_a?(ActionDispatch::Http::UploadedFile)
191
+ @source.original_filename.to_s
192
+ elsif @source.respond_to?(:path)
193
+ File.basename(@source.path).to_s
194
+ else
195
+ 'attachment'
196
+ end
197
+ end
198
+
168
199
  def extract_filename_from_active_storage # rubocop:disable Metrics/PerceivedComplexity
169
200
  return 'attachment' unless defined?(ActiveStorage)
170
201
 
data/lib/ruby_llm/chat.rb CHANGED
@@ -31,7 +31,7 @@ module RubyLLM
31
31
  end
32
32
 
33
33
  def ask(message = nil, with: nil, &)
34
- add_message role: :user, content: Content.new(message, with)
34
+ add_message role: :user, content: build_content(message, with)
35
35
  complete(&)
36
36
  end
37
37
 
@@ -193,7 +193,8 @@ module RubyLLM
193
193
  @on[:tool_call]&.call(tool_call)
194
194
  result = execute_tool tool_call
195
195
  @on[:tool_result]&.call(result)
196
- content = result.is_a?(Content) ? result : result.to_s
196
+ tool_payload = result.is_a?(Tool::Halt) ? result.content : result
197
+ content = content_like?(tool_payload) ? tool_payload : tool_payload.to_s
197
198
  message = add_message role: :tool, content:, tool_call_id: tool_call.id
198
199
  @on[:end_message]&.call(message)
199
200
 
@@ -208,5 +209,15 @@ module RubyLLM
208
209
  args = tool_call.arguments
209
210
  tool.call(args)
210
211
  end
212
+
213
+ def build_content(message, attachments)
214
+ return message if content_like?(message)
215
+
216
+ Content.new(message, attachments)
217
+ end
218
+
219
+ def content_like?(object)
220
+ object.is_a?(Content) || object.is_a?(Content::Raw)
221
+ end
211
222
  end
212
223
  end
@@ -10,6 +10,7 @@ module RubyLLM
10
10
  :openai_use_system_role,
11
11
  :anthropic_api_key,
12
12
  :gemini_api_key,
13
+ :gemini_api_base,
13
14
  :vertexai_project_id,
14
15
  :vertexai_location,
15
16
  :deepseek_api_key,
@@ -31,7 +32,9 @@ module RubyLLM
31
32
  :default_embedding_model,
32
33
  :default_moderation_model,
33
34
  :default_image_model,
35
+ :default_transcription_model,
34
36
  # Model registry
37
+ :model_registry_file,
35
38
  :model_registry_class,
36
39
  # Rails integration
37
40
  :use_new_acts_as,
@@ -49,7 +52,7 @@ module RubyLLM
49
52
  :log_stream_debug
50
53
 
51
54
  def initialize
52
- @request_timeout = 120
55
+ @request_timeout = 300
53
56
  @max_retries = 3
54
57
  @retry_interval = 0.1
55
58
  @retry_backoff_factor = 2
@@ -60,7 +63,9 @@ module RubyLLM
60
63
  @default_embedding_model = 'text-embedding-3-small'
61
64
  @default_moderation_model = 'omni-moderation-latest'
62
65
  @default_image_model = 'gpt-image-1'
66
+ @default_transcription_model = 'whisper-1'
63
67
 
68
+ @model_registry_file = File.expand_path('models.json', __dir__)
64
69
  @model_registry_class = 'Model'
65
70
  @use_new_acts_as = false
66
71
 
@@ -34,8 +34,7 @@ module RubyLLM
34
34
  end
35
35
 
36
36
  def post(url, payload, &)
37
- body = payload.is_a?(Hash) ? JSON.generate(payload, ascii_only: false) : payload
38
- @connection.post url, body do |req|
37
+ @connection.post url, payload do |req|
39
38
  req.headers.merge! @provider.headers if @provider.respond_to?(:headers)
40
39
  yield req if block_given?
41
40
  end
@@ -77,7 +76,7 @@ module RubyLLM
77
76
  errors: true,
78
77
  headers: false,
79
78
  log_level: :debug do |logger|
80
- logger.filter(%r{[A-Za-z0-9+/=]{100,}}, 'data":"[BASE64 DATA]"')
79
+ logger.filter(%r{[A-Za-z0-9+/=]{100,}}, '[BASE64 DATA]')
81
80
  logger.filter(/[-\d.e,\s]{100,}/, '[EMBEDDINGS ARRAY]')
82
81
  end
83
82
  end
@@ -94,6 +93,7 @@ module RubyLLM
94
93
  end
95
94
 
96
95
  def setup_middleware(faraday)
96
+ faraday.request :multipart
97
97
  faraday.request :json
98
98
  faraday.response :json
99
99
  faraday.adapter :net_http
@@ -48,3 +48,26 @@ module RubyLLM
48
48
  end
49
49
  end
50
50
  end
51
+
52
+ module RubyLLM
53
+ class Content
54
+ # Represents provider-specific payloads that should bypass RubyLLM formatting.
55
+ class Raw
56
+ attr_reader :value
57
+
58
+ def initialize(value)
59
+ raise ArgumentError, 'Raw content payload cannot be nil' if value.nil?
60
+
61
+ @value = value
62
+ end
63
+
64
+ def format
65
+ @value
66
+ end
67
+
68
+ def to_h
69
+ @value
70
+ end
71
+ end
72
+ end
73
+ end
@@ -5,18 +5,21 @@ module RubyLLM
5
5
  class Message
6
6
  ROLES = %i[system user assistant tool].freeze
7
7
 
8
- attr_reader :role, :tool_calls, :tool_call_id, :input_tokens, :output_tokens, :model_id, :raw, :conversation_id
8
+ attr_reader :role, :model_id, :tool_calls, :tool_call_id, :input_tokens, :output_tokens,
9
+ :cached_tokens, :cache_creation_tokens, :raw, :conversation_id
9
10
  attr_writer :content
10
11
 
11
12
  def initialize(options = {})
12
13
  @role = options.fetch(:role).to_sym
13
14
  @content = normalize_content(options.fetch(:content))
15
+ @model_id = options[:model_id]
14
16
  @tool_calls = options[:tool_calls]
17
+ @tool_call_id = options[:tool_call_id]
18
+ @conversation_id = options[:conversation_id]
15
19
  @input_tokens = options[:input_tokens]
16
20
  @output_tokens = options[:output_tokens]
17
- @model_id = options[:model_id]
18
- @conversation_id = options[:conversation_id]
19
- @tool_call_id = options[:tool_call_id]
21
+ @cached_tokens = options[:cached_tokens]
22
+ @cache_creation_tokens = options[:cache_creation_tokens]
20
23
  @raw = options[:raw]
21
24
 
22
25
  ensure_valid_role
@@ -46,12 +49,14 @@ module RubyLLM
46
49
  {
47
50
  role: role,
48
51
  content: content,
52
+ model_id: model_id,
49
53
  tool_calls: tool_calls,
50
54
  tool_call_id: tool_call_id,
55
+ conversation_id: conversation_id,
51
56
  input_tokens: input_tokens,
52
57
  output_tokens: output_tokens,
53
- conversation_id: conversation_id,
54
- model_id: model_id
58
+ cached_tokens: cached_tokens,
59
+ cache_creation_tokens: cache_creation_tokens
55
60
  }.compact
56
61
  end
57
62
 
@@ -72,6 +72,10 @@ module RubyLLM
72
72
  pricing.text_tokens.output
73
73
  end
74
74
 
75
+ def provider_class
76
+ RubyLLM::Provider.resolve provider
77
+ end
78
+
75
79
  def type # rubocop:disable Metrics/PerceivedComplexity
76
80
  if modalities.output.include?('embeddings') && !modalities.output.include?('text')
77
81
  'embedding'