ruby_llm 1.2.0 → 1.3.0rc1

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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +80 -133
  3. data/lib/ruby_llm/active_record/acts_as.rb +212 -33
  4. data/lib/ruby_llm/aliases.json +48 -6
  5. data/lib/ruby_llm/attachments/audio.rb +12 -0
  6. data/lib/ruby_llm/attachments/image.rb +9 -0
  7. data/lib/ruby_llm/attachments/pdf.rb +9 -0
  8. data/lib/ruby_llm/attachments.rb +78 -0
  9. data/lib/ruby_llm/chat.rb +22 -19
  10. data/lib/ruby_llm/configuration.rb +30 -1
  11. data/lib/ruby_llm/connection.rb +95 -0
  12. data/lib/ruby_llm/content.rb +51 -72
  13. data/lib/ruby_llm/context.rb +30 -0
  14. data/lib/ruby_llm/embedding.rb +13 -5
  15. data/lib/ruby_llm/error.rb +1 -1
  16. data/lib/ruby_llm/image.rb +13 -5
  17. data/lib/ruby_llm/message.rb +12 -4
  18. data/lib/ruby_llm/mime_types.rb +713 -0
  19. data/lib/ruby_llm/model_info.rb +208 -27
  20. data/lib/ruby_llm/models.json +25766 -2154
  21. data/lib/ruby_llm/models.rb +95 -14
  22. data/lib/ruby_llm/provider.rb +48 -90
  23. data/lib/ruby_llm/providers/anthropic/capabilities.rb +76 -13
  24. data/lib/ruby_llm/providers/anthropic/chat.rb +7 -14
  25. data/lib/ruby_llm/providers/anthropic/media.rb +44 -34
  26. data/lib/ruby_llm/providers/anthropic/models.rb +15 -15
  27. data/lib/ruby_llm/providers/anthropic/tools.rb +2 -2
  28. data/lib/ruby_llm/providers/anthropic.rb +3 -3
  29. data/lib/ruby_llm/providers/bedrock/capabilities.rb +61 -2
  30. data/lib/ruby_llm/providers/bedrock/chat.rb +30 -73
  31. data/lib/ruby_llm/providers/bedrock/media.rb +56 -0
  32. data/lib/ruby_llm/providers/bedrock/models.rb +50 -58
  33. data/lib/ruby_llm/providers/bedrock/streaming/base.rb +16 -0
  34. data/lib/ruby_llm/providers/bedrock.rb +14 -25
  35. data/lib/ruby_llm/providers/deepseek/capabilities.rb +35 -2
  36. data/lib/ruby_llm/providers/deepseek.rb +3 -3
  37. data/lib/ruby_llm/providers/gemini/capabilities.rb +84 -3
  38. data/lib/ruby_llm/providers/gemini/chat.rb +8 -37
  39. data/lib/ruby_llm/providers/gemini/embeddings.rb +18 -34
  40. data/lib/ruby_llm/providers/gemini/images.rb +2 -2
  41. data/lib/ruby_llm/providers/gemini/media.rb +39 -110
  42. data/lib/ruby_llm/providers/gemini/models.rb +16 -22
  43. data/lib/ruby_llm/providers/gemini/tools.rb +1 -1
  44. data/lib/ruby_llm/providers/gemini.rb +3 -3
  45. data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
  46. data/lib/ruby_llm/providers/ollama/media.rb +44 -0
  47. data/lib/ruby_llm/providers/ollama.rb +34 -0
  48. data/lib/ruby_llm/providers/openai/capabilities.rb +78 -3
  49. data/lib/ruby_llm/providers/openai/chat.rb +6 -4
  50. data/lib/ruby_llm/providers/openai/embeddings.rb +8 -12
  51. data/lib/ruby_llm/providers/openai/media.rb +38 -21
  52. data/lib/ruby_llm/providers/openai/models.rb +16 -17
  53. data/lib/ruby_llm/providers/openai/tools.rb +9 -5
  54. data/lib/ruby_llm/providers/openai.rb +7 -5
  55. data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
  56. data/lib/ruby_llm/providers/openrouter.rb +31 -0
  57. data/lib/ruby_llm/stream_accumulator.rb +4 -4
  58. data/lib/ruby_llm/streaming.rb +3 -3
  59. data/lib/ruby_llm/utils.rb +22 -0
  60. data/lib/ruby_llm/version.rb +1 -1
  61. data/lib/ruby_llm.rb +15 -5
  62. data/lib/tasks/models.rake +69 -33
  63. data/lib/tasks/models_docs.rake +164 -121
  64. data/lib/tasks/vcr.rake +4 -2
  65. metadata +23 -14
  66. data/lib/tasks/browser_helper.rb +0 -97
  67. data/lib/tasks/capability_generator.rb +0 -123
  68. data/lib/tasks/capability_scraper.rb +0 -224
  69. data/lib/tasks/cli_helper.rb +0 -22
  70. data/lib/tasks/code_validator.rb +0 -29
  71. data/lib/tasks/model_updater.rb +0 -66
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'time'
4
-
5
3
  module RubyLLM
6
4
  # Information about an AI model's capabilities, pricing, and metadata.
7
5
  # Used by the Models registry to help developers choose the right model
@@ -13,44 +11,227 @@ module RubyLLM
13
11
  # model.supports_functions? # => true
14
12
  # model.input_price_per_million # => 30.0
15
13
  class ModelInfo
16
- attr_reader :id, :created_at, :display_name, :provider, :metadata,
17
- :context_window, :max_tokens, :supports_vision, :supports_functions,
18
- :supports_json_mode, :input_price_per_million, :output_price_per_million, :type, :family
14
+ attr_reader :id, :name, :provider, :family, :created_at, :context_window, :max_output_tokens, :knowledge_cutoff,
15
+ :modalities, :capabilities, :pricing, :metadata
19
16
 
20
- def initialize(data) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
17
+ def initialize(data)
21
18
  @id = data[:id]
22
- @created_at = data[:created_at].is_a?(String) ? Time.parse(data[:created_at]) : data[:created_at]
23
- @display_name = data[:display_name]
19
+ @name = data[:name]
24
20
  @provider = data[:provider]
25
- @context_window = data[:context_window]
26
- @max_tokens = data[:max_tokens]
27
- @type = data[:type]
28
21
  @family = data[:family]
29
- @supports_vision = data[:supports_vision]
30
- @supports_functions = data[:supports_functions]
31
- @supports_json_mode = data[:supports_json_mode]
32
- @input_price_per_million = data[:input_price_per_million]
33
- @output_price_per_million = data[:output_price_per_million]
22
+ @created_at = data[:created_at]
23
+ @context_window = data[:context_window]
24
+ @max_output_tokens = data[:max_output_tokens]
25
+ @knowledge_cutoff = data[:knowledge_cutoff]
26
+ @modalities = Modalities.new(data[:modalities] || {})
27
+ @capabilities = data[:capabilities] || []
28
+ @pricing = PricingCollection.new(data[:pricing] || {})
34
29
  @metadata = data[:metadata] || {}
35
30
  end
36
31
 
37
- def to_h # rubocop:disable Metrics/MethodLength
32
+ # Capability methods
33
+ def supports?(capability)
34
+ capabilities.include?(capability.to_s)
35
+ end
36
+
37
+ %w[function_calling structured_output batch reasoning citations streaming].each do |cap|
38
+ define_method "#{cap}?" do
39
+ supports?(cap)
40
+ end
41
+ end
42
+
43
+ # Backward compatibility methods
44
+ def display_name
45
+ name
46
+ end
47
+
48
+ def max_tokens
49
+ max_output_tokens
50
+ end
51
+
52
+ def supports_vision?
53
+ modalities.input.include?('image')
54
+ end
55
+
56
+ def supports_functions?
57
+ function_calling?
58
+ end
59
+
60
+ def input_price_per_million
61
+ pricing.text_tokens.input
62
+ end
63
+
64
+ def output_price_per_million
65
+ pricing.text_tokens.output
66
+ end
67
+
68
+ def type # rubocop:disable Metrics/PerceivedComplexity
69
+ if modalities.output.include?('embeddings') && !modalities.output.include?('text')
70
+ 'embedding'
71
+ elsif modalities.output.include?('image') && !modalities.output.include?('text')
72
+ 'image'
73
+ elsif modalities.output.include?('audio') && !modalities.output.include?('text')
74
+ 'audio'
75
+ elsif modalities.output.include?('moderation')
76
+ 'moderation'
77
+ else
78
+ 'chat'
79
+ end
80
+ end
81
+
82
+ def to_h
38
83
  {
39
84
  id: id,
40
- created_at: created_at&.iso8601,
41
- display_name: display_name,
85
+ name: name,
42
86
  provider: provider,
43
- context_window: context_window,
44
- max_tokens: max_tokens,
45
- type: type,
46
87
  family: family,
47
- supports_vision: supports_vision,
48
- supports_functions: supports_functions,
49
- supports_json_mode: supports_json_mode,
50
- input_price_per_million: input_price_per_million,
51
- output_price_per_million: output_price_per_million,
88
+ created_at: created_at,
89
+ context_window: context_window,
90
+ max_output_tokens: max_output_tokens,
91
+ knowledge_cutoff: knowledge_cutoff,
92
+ modalities: modalities.to_h,
93
+ capabilities: capabilities,
94
+ pricing: pricing.to_h,
52
95
  metadata: metadata
53
96
  }
54
97
  end
55
98
  end
99
+
100
+ # Holds and manages input and output modalities for a language model
101
+ class Modalities
102
+ attr_reader :input, :output
103
+
104
+ def initialize(data)
105
+ @input = Array(data[:input]).map(&:to_s)
106
+ @output = Array(data[:output]).map(&:to_s)
107
+ end
108
+
109
+ def to_h
110
+ {
111
+ input: input,
112
+ output: output
113
+ }
114
+ end
115
+ end
116
+
117
+ # A collection that manages and provides access to different categories of pricing information
118
+ # (text tokens, images, audio tokens, embeddings)
119
+ class PricingCollection
120
+ def initialize(data)
121
+ @data = {}
122
+
123
+ # Initialize pricing categories
124
+ %i[text_tokens images audio_tokens embeddings].each do |category|
125
+ @data[category] = PricingCategory.new(data[category]) if data[category] && !empty_pricing?(data[category])
126
+ end
127
+ end
128
+
129
+ def method_missing(method, *args)
130
+ if respond_to_missing?(method)
131
+ @data[method.to_sym] || PricingCategory.new
132
+ else
133
+ super
134
+ end
135
+ end
136
+
137
+ def respond_to_missing?(method, include_private = false)
138
+ %i[text_tokens images audio_tokens embeddings].include?(method.to_sym) || super
139
+ end
140
+
141
+ def to_h
142
+ @data.transform_values(&:to_h)
143
+ end
144
+
145
+ private
146
+
147
+ def empty_pricing?(data)
148
+ # Check if all pricing values in this category are zero or nil
149
+ return true unless data
150
+
151
+ %i[standard batch].each do |tier|
152
+ next unless data[tier]
153
+
154
+ data[tier].each_value do |value|
155
+ return false if value && value != 0.0
156
+ end
157
+ end
158
+
159
+ true
160
+ end
161
+ end
162
+
163
+ # Represents pricing tiers for different usage categories (standard and batch), managing access to their respective
164
+ # rates
165
+ class PricingCategory
166
+ attr_reader :standard, :batch
167
+
168
+ def initialize(data = {})
169
+ @standard = PricingTier.new(data[:standard] || {}) unless empty_tier?(data[:standard])
170
+ @batch = PricingTier.new(data[:batch] || {}) unless empty_tier?(data[:batch])
171
+ end
172
+
173
+ # Shorthand methods that default to standard tier
174
+ def input
175
+ standard&.input_per_million
176
+ end
177
+
178
+ def output
179
+ standard&.output_per_million
180
+ end
181
+
182
+ def cached_input
183
+ standard&.cached_input_per_million
184
+ end
185
+
186
+ # Get value for a specific tier
187
+ def [](key)
188
+ key == :batch ? batch : standard
189
+ end
190
+
191
+ def to_h
192
+ result = {}
193
+ result[:standard] = standard.to_h if standard
194
+ result[:batch] = batch.to_h if batch
195
+ result
196
+ end
197
+
198
+ private
199
+
200
+ def empty_tier?(tier_data)
201
+ return true unless tier_data
202
+
203
+ tier_data.values.all? { |v| v.nil? || v == 0.0 }
204
+ end
205
+ end
206
+
207
+ # A dynamic class for storing non-zero pricing values with flexible attribute access
208
+ class PricingTier
209
+ def initialize(data = {})
210
+ @values = {}
211
+
212
+ # Only store non-zero values
213
+ data.each do |key, value|
214
+ @values[key.to_sym] = value if value && value != 0.0
215
+ end
216
+ end
217
+
218
+ def method_missing(method, *args)
219
+ if method.to_s.end_with?('=')
220
+ key = method.to_s.chomp('=').to_sym
221
+ @values[key] = args.first if args.first && args.first != 0.0
222
+ elsif @values.key?(method)
223
+ @values[method]
224
+ else
225
+ super
226
+ end
227
+ end
228
+
229
+ def respond_to_missing?(method, include_private = false)
230
+ method.to_s.end_with?('=') || @values.key?(method.to_sym) || super
231
+ end
232
+
233
+ def to_h
234
+ @values
235
+ end
236
+ end
56
237
  end