ruby_llm 1.7.1 → 1.8.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.
@@ -194,15 +194,15 @@ module RubyLLM
194
194
  end
195
195
 
196
196
  def embedding_models
197
- self.class.new(all.select { |m| m.type == 'embedding' })
197
+ self.class.new(all.select { |m| m.type == 'embedding' || m.modalities.output.include?('embeddings') })
198
198
  end
199
199
 
200
200
  def audio_models
201
- self.class.new(all.select { |m| m.type == 'audio' })
201
+ self.class.new(all.select { |m| m.type == 'audio' || m.modalities.output.include?('audio') })
202
202
  end
203
203
 
204
204
  def image_models
205
- self.class.new(all.select { |m| m.type == 'image' })
205
+ self.class.new(all.select { |m| m.type == 'image' || m.modalities.output.include?('image') })
206
206
  end
207
207
 
208
208
  def by_family(family)
@@ -217,6 +217,10 @@ module RubyLLM
217
217
  self.class.refresh!(remote_only: remote_only)
218
218
  end
219
219
 
220
+ def resolve(model_id, provider: nil, assume_exists: false, config: nil)
221
+ self.class.resolve(model_id, provider: provider, assume_exists: assume_exists, config: config)
222
+ end
223
+
220
224
  private
221
225
 
222
226
  def find_with_provider(model_id, provider)
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # Identify potentially harmful content in text.
5
+ # https://platform.openai.com/docs/guides/moderation
6
+ class Moderation
7
+ attr_reader :id, :model, :results
8
+
9
+ def initialize(id:, model:, results:)
10
+ @id = id
11
+ @model = model
12
+ @results = results
13
+ end
14
+
15
+ def self.moderate(input,
16
+ model: nil,
17
+ provider: nil,
18
+ assume_model_exists: false,
19
+ context: nil)
20
+ config = context&.config || RubyLLM.config
21
+ model ||= config.default_moderation_model || 'omni-moderation-latest'
22
+ model, provider_instance = Models.resolve(model, provider: provider, assume_exists: assume_model_exists,
23
+ config: config)
24
+ model_id = model.id
25
+
26
+ provider_instance.moderate(input, model: model_id)
27
+ end
28
+
29
+ # Convenience method to get content from moderation result
30
+ def content
31
+ results
32
+ end
33
+
34
+ # Check if any content was flagged
35
+ def flagged?
36
+ results.any? { |result| result['flagged'] }
37
+ end
38
+
39
+ # Get all flagged categories across all results
40
+ def flagged_categories
41
+ results.flat_map do |result|
42
+ result['categories']&.select { |_category, flagged| flagged }&.keys || []
43
+ end.uniq
44
+ end
45
+
46
+ # Get category scores for the first result (most common case)
47
+ def category_scores
48
+ results.first&.dig('category_scores') || {}
49
+ end
50
+
51
+ # Get categories for the first result (most common case)
52
+ def categories
53
+ results.first&.dig('categories') || {}
54
+ end
55
+ end
56
+ end
@@ -70,6 +70,12 @@ module RubyLLM
70
70
  parse_embedding_response(response, model:, text:)
71
71
  end
72
72
 
73
+ def moderate(input, model:)
74
+ payload = render_moderation_payload(input, model:)
75
+ response = @connection.post moderation_url, payload
76
+ parse_moderation_response(response, model:)
77
+ end
78
+
73
79
  def paint(prompt, model:, size:)
74
80
  payload = render_image_payload(prompt, model:, size:)
75
81
  response = @connection.post images_url, payload
@@ -52,6 +52,10 @@ module RubyLLM
52
52
  model_id.match?(/gemini|flash|pro|imagen/)
53
53
  end
54
54
 
55
+ def supports_video?(model_id)
56
+ model_id.match?(/gemini/)
57
+ end
58
+
55
59
  def supports_functions?(model_id)
56
60
  return false if model_id.match?(/text-embedding|embedding-001|aqa|flash-lite|imagen|gemini-2\.0-flash-lite/)
57
61
 
@@ -217,6 +221,7 @@ module RubyLLM
217
221
  modalities[:input] << 'pdf'
218
222
  end
219
223
 
224
+ modalities[:input] << 'video' if supports_video?(model_id)
220
225
  modalities[:input] << 'audio' if model_id.match?(/audio/)
221
226
  modalities[:output] << 'embeddings' if model_id.match?(/embedding|gemini-embedding/)
222
227
  modalities[:output] = ['image'] if model_id.match?(/imagen/)
@@ -26,6 +26,9 @@ module RubyLLM
26
26
  gpt4o_realtime: /^gpt-4o-realtime/,
27
27
  gpt4o_search: /^gpt-4o-search/,
28
28
  gpt4o_transcribe: /^gpt-4o-transcribe/,
29
+ gpt5: /^gpt-5/,
30
+ gpt5_mini: /^gpt-5-mini/,
31
+ gpt5_nano: /^gpt-5-nano/,
29
32
  o1: /^o1(?!-(?:mini|pro))/,
30
33
  o1_mini: /^o1-mini/,
31
34
  o1_pro: /^o1-pro/,
@@ -44,7 +47,7 @@ module RubyLLM
44
47
  def context_window_for(model_id)
45
48
  case model_family(model_id)
46
49
  when 'gpt41', 'gpt41_mini', 'gpt41_nano' then 1_047_576
47
- when 'chatgpt4o', 'gpt4_turbo', 'gpt4o', 'gpt4o_audio', 'gpt4o_mini',
50
+ when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'chatgpt4o', 'gpt4_turbo', 'gpt4o', 'gpt4o_audio', 'gpt4o_mini',
48
51
  'gpt4o_mini_audio', 'gpt4o_mini_realtime', 'gpt4o_realtime',
49
52
  'gpt4o_search', 'gpt4o_transcribe', 'gpt4o_mini_search', 'o1_mini' then 128_000
50
53
  when 'gpt4' then 8_192
@@ -59,6 +62,7 @@ module RubyLLM
59
62
 
60
63
  def max_tokens_for(model_id)
61
64
  case model_family(model_id)
65
+ when 'gpt5', 'gpt5_mini', 'gpt5_nano' then 400_000
62
66
  when 'gpt41', 'gpt41_mini', 'gpt41_nano' then 32_768
63
67
  when 'chatgpt4o', 'gpt4o', 'gpt4o_mini', 'gpt4o_mini_search' then 16_384
64
68
  when 'babbage', 'davinci' then 16_384 # rubocop:disable Lint/DuplicateBranch
@@ -76,16 +80,17 @@ module RubyLLM
76
80
 
77
81
  def supports_vision?(model_id)
78
82
  case model_family(model_id)
79
- when 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4', 'gpt4_turbo', 'gpt4o', 'gpt4o_mini', 'o1',
80
- 'o1_pro', 'moderation', 'gpt4o_search', 'gpt4o_mini_search' then true
83
+ when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4',
84
+ 'gpt4_turbo', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro', 'moderation', 'gpt4o_search',
85
+ 'gpt4o_mini_search' then true
81
86
  else false
82
87
  end
83
88
  end
84
89
 
85
90
  def supports_functions?(model_id)
86
91
  case model_family(model_id)
87
- when 'gpt41', 'gpt41_mini', 'gpt41_nano', 'gpt4', 'gpt4_turbo', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro',
88
- 'o3_mini' then true
92
+ when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'gpt4', 'gpt4_turbo', 'gpt4o',
93
+ 'gpt4o_mini', 'o1', 'o1_pro', 'o3_mini' then true
89
94
  when 'chatgpt4o', 'gpt35_turbo', 'o1_mini', 'gpt4o_mini_tts',
90
95
  'gpt4o_transcribe', 'gpt4o_search', 'gpt4o_mini_search' then false
91
96
  else false # rubocop:disable Lint/DuplicateBranch
@@ -94,8 +99,8 @@ module RubyLLM
94
99
 
95
100
  def supports_structured_output?(model_id)
96
101
  case model_family(model_id)
97
- when 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4o', 'gpt4o_mini', 'o1', 'o1_pro',
98
- 'o3_mini' then true
102
+ when 'gpt5', 'gpt5_mini', 'gpt5_nano', 'gpt41', 'gpt41_mini', 'gpt41_nano', 'chatgpt4o', 'gpt4o',
103
+ 'gpt4o_mini', 'o1', 'o1_pro', 'o3_mini' then true
99
104
  else false
100
105
  end
101
106
  end
@@ -105,6 +110,9 @@ module RubyLLM
105
110
  end
106
111
 
107
112
  PRICES = {
113
+ gpt5: { input: 1.25, output: 10.0, cached_input: 0.125 },
114
+ gpt5_mini: { input: 0.25, output: 2.0, cached_input: 0.025 },
115
+ gpt5_nano: { input: 0.05, output: 0.4, cached_input: 0.005 },
108
116
  gpt41: { input: 2.0, output: 8.0, cached_input: 0.5 },
109
117
  gpt41_mini: { input: 0.4, output: 1.6, cached_input: 0.1 },
110
118
  gpt41_nano: { input: 0.1, output: 0.4 },
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ class OpenAI
6
+ # Moderation methods of the OpenAI API integration
7
+ module Moderation
8
+ module_function
9
+
10
+ def moderation_url
11
+ 'moderations'
12
+ end
13
+
14
+ def render_moderation_payload(input, model:)
15
+ {
16
+ model: model,
17
+ input: input
18
+ }
19
+ end
20
+
21
+ def parse_moderation_response(response, model:)
22
+ data = response.body
23
+ raise Error.new(response, data.dig('error', 'message')) if data.dig('error', 'message')
24
+
25
+ RubyLLM::Moderation.new(
26
+ id: data['id'],
27
+ model: model,
28
+ results: data['results'] || []
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -7,6 +7,7 @@ module RubyLLM
7
7
  include OpenAI::Chat
8
8
  include OpenAI::Embeddings
9
9
  include OpenAI::Models
10
+ include OpenAI::Moderation
10
11
  include OpenAI::Streaming
11
12
  include OpenAI::Tools
12
13
  include OpenAI::Images
@@ -5,7 +5,7 @@ module RubyLLM
5
5
  class Railtie < Rails::Railtie
6
6
  initializer 'ruby_llm.inflections' do
7
7
  ActiveSupport::Inflector.inflections(:en) do |inflect|
8
- inflect.acronym 'LLM'
8
+ inflect.acronym 'RubyLLM'
9
9
  end
10
10
  end
11
11
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '1.7.1'
4
+ VERSION = '1.8.1'
5
5
  end
data/lib/ruby_llm.rb CHANGED
@@ -48,6 +48,10 @@ module RubyLLM
48
48
  Embedding.embed(...)
49
49
  end
50
50
 
51
+ def moderate(...)
52
+ Moderation.moderate(...)
53
+ end
54
+
51
55
  def paint(...)
52
56
  Image.paint(...)
53
57
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.1
4
+ version: 1.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -146,6 +146,7 @@ files:
146
146
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt
147
147
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt
148
148
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt
149
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt
149
150
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt
150
151
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt
151
152
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt
@@ -154,6 +155,7 @@ files:
154
155
  - lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt
155
156
  - lib/generators/ruby_llm/generator_helpers.rb
156
157
  - lib/generators/ruby_llm/install/install_generator.rb
158
+ - lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt
157
159
  - lib/generators/ruby_llm/install/templates/chat_model.rb.tt
158
160
  - lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt
159
161
  - lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt
@@ -194,6 +196,7 @@ files:
194
196
  - lib/ruby_llm/models.json
195
197
  - lib/ruby_llm/models.rb
196
198
  - lib/ruby_llm/models_schema.json
199
+ - lib/ruby_llm/moderation.rb
197
200
  - lib/ruby_llm/provider.rb
198
201
  - lib/ruby_llm/providers/anthropic.rb
199
202
  - lib/ruby_llm/providers/anthropic/capabilities.rb
@@ -247,6 +250,7 @@ files:
247
250
  - lib/ruby_llm/providers/openai/images.rb
248
251
  - lib/ruby_llm/providers/openai/media.rb
249
252
  - lib/ruby_llm/providers/openai/models.rb
253
+ - lib/ruby_llm/providers/openai/moderation.rb
250
254
  - lib/ruby_llm/providers/openai/streaming.rb
251
255
  - lib/ruby_llm/providers/openai/tools.rb
252
256
  - lib/ruby_llm/providers/openrouter.rb
@@ -280,6 +284,7 @@ metadata:
280
284
  changelog_uri: https://github.com/crmne/ruby_llm/commits/main
281
285
  documentation_uri: https://rubyllm.com
282
286
  bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
287
+ funding_uri: https://github.com/sponsors/crmne
283
288
  rubygems_mfa_required: 'true'
284
289
  post_install_message: |
285
290
  Upgrading from RubyLLM <= 1.6.x? Check the upgrade guide for new features and migration instructions