ruby_llm 0.1.0.pre24 → 0.1.0.pre26

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.
@@ -18,7 +18,7 @@ module RubyLLM
18
18
  def all
19
19
  @all ||= begin
20
20
  data = JSON.parse(File.read(File.expand_path('models.json', __dir__)))
21
- data['models'].map { |model| ModelInfo.new(model.transform_keys(&:to_sym)) }
21
+ data.map { |model| ModelInfo.new(model.transform_keys(&:to_sym)) }
22
22
  end
23
23
  rescue Errno::ENOENT
24
24
  [] # Return empty array if file doesn't exist yet
@@ -53,7 +53,9 @@ module RubyLLM
53
53
  end
54
54
 
55
55
  def refresh!
56
- @all = nil
56
+ @all = RubyLLM.providers.flat_map do |provider|
57
+ provider.new.list_models
58
+ end
57
59
  end
58
60
  end
59
61
  end
@@ -46,7 +46,7 @@ module RubyLLM
46
46
  def stream_response(payload, &block)
47
47
  accumulator = StreamAccumulator.new
48
48
 
49
- post completion_url, payload do |req|
49
+ post stream_url, payload do |req|
50
50
  req.options.on_data = handle_stream do |chunk|
51
51
  accumulator.add chunk
52
52
  block.call chunk
@@ -104,13 +104,22 @@ module RubyLLM
104
104
  end
105
105
 
106
106
  def try_parse_json(maybe_json)
107
- return maybe_json if maybe_json.is_a?(Hash)
107
+ return maybe_json unless maybe_json.is_a?(String)
108
108
 
109
109
  JSON.parse(maybe_json)
110
110
  rescue JSON::ParserError
111
111
  maybe_json
112
112
  end
113
113
 
114
+ def capabilities
115
+ provider_name = self.class.name.split('::').last
116
+ RubyLLM.const_get "ModelCapabilities::#{provider_name}"
117
+ end
118
+
119
+ def slug
120
+ self.class.name.split('::').last.downcase
121
+ end
122
+
114
123
  class << self
115
124
  def register(name, provider_class)
116
125
  providers[name.to_sym] = provider_class
@@ -123,8 +132,6 @@ module RubyLLM
123
132
  provider_class.new
124
133
  end
125
134
 
126
- private
127
-
128
135
  def providers
129
136
  @providers ||= {}
130
137
  end
@@ -31,6 +31,10 @@ module RubyLLM
31
31
  '/v1/messages'
32
32
  end
33
33
 
34
+ def stream_url
35
+ completion_url
36
+ end
37
+
34
38
  def models_url
35
39
  '/v1/models'
36
40
  end
@@ -77,16 +81,15 @@ module RubyLLM
77
81
  )
78
82
  end
79
83
 
80
- def parse_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
81
- capabilities = ModelCapabilities::Anthropic.new
82
-
84
+ def parse_list_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
83
85
  (response.body['data'] || []).map do |model|
84
86
  ModelInfo.new(
85
87
  id: model['id'],
86
88
  created_at: Time.parse(model['created_at']),
87
89
  display_name: model['display_name'],
88
- provider: 'anthropic',
89
- metadata: { type: model['type'] },
90
+ provider: slug,
91
+ type: model['type'],
92
+ family: capabilities.model_family(model['id']),
90
93
  context_window: capabilities.determine_context_window(model['id']),
91
94
  max_tokens: capabilities.determine_max_tokens(model['id']),
92
95
  supports_vision: capabilities.supports_vision?(model['id']),
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ # DeepSeek API integration.
6
+ class DeepSeek < OpenAI
7
+ private
8
+
9
+ def api_base
10
+ 'https://api.deepseek.com'
11
+ end
12
+
13
+ def headers
14
+ {
15
+ 'Authorization' => "Bearer #{RubyLLM.config.deepseek_api_key}"
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ module Providers
5
+ # Gemini API integration.
6
+ class Gemini < OpenAI
7
+ private
8
+
9
+ def api_base
10
+ 'https://generativelanguage.googleapis.com/v1beta/openai'
11
+ end
12
+
13
+ def headers
14
+ {
15
+ 'Authorization' => "Bearer #{RubyLLM.config.gemini_api_key}"
16
+ }
17
+ end
18
+
19
+ def parse_list_models_response(response)
20
+ response.body['data']&.each do |model|
21
+ model['id'] = model['id'].delete_prefix('models/')
22
+ end
23
+
24
+ super(response)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -18,7 +18,7 @@ module RubyLLM
18
18
  private
19
19
 
20
20
  def api_base
21
- 'https://api.openai.com'
21
+ 'https://api.openai.com/v1'
22
22
  end
23
23
 
24
24
  def headers
@@ -28,15 +28,19 @@ module RubyLLM
28
28
  end
29
29
 
30
30
  def completion_url
31
- '/v1/chat/completions'
31
+ 'chat/completions'
32
+ end
33
+
34
+ def stream_url
35
+ completion_url
32
36
  end
33
37
 
34
38
  def models_url
35
- '/v1/models'
39
+ 'models'
36
40
  end
37
41
 
38
42
  def embedding_url
39
- '/v1/embeddings'
43
+ 'embeddings'
40
44
  end
41
45
 
42
46
  def build_payload(messages, tools:, temperature:, model:, stream: false) # rubocop:disable Metrics/MethodLength
@@ -189,13 +193,14 @@ module RubyLLM
189
193
  end
190
194
 
191
195
  def parse_list_models_response(response) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
192
- capabilities = ModelCapabilities::OpenAI
193
196
  (response.body['data'] || []).map do |model|
194
197
  ModelInfo.new(
195
198
  id: model['id'],
196
- created_at: Time.at(model['created']),
199
+ created_at: model['created'] ? Time.at(model['created']) : nil,
197
200
  display_name: capabilities.format_display_name(model['id']),
198
- provider: 'openai',
201
+ provider: slug,
202
+ type: capabilities.model_type(model['id']),
203
+ family: capabilities.model_family(model['id']),
199
204
  metadata: {
200
205
  object: model['object'],
201
206
  owned_by: model['owned_by']
@@ -32,7 +32,7 @@ module RubyLLM
32
32
  def to_message
33
33
  Message.new(
34
34
  role: :assistant,
35
- content: content,
35
+ content: content.empty? ? nil : content,
36
36
  model_id: model_id,
37
37
  tool_calls: tool_calls_from_stream,
38
38
  input_tokens: @input_tokens.positive? ? @input_tokens : nil,
@@ -52,13 +52,16 @@ module RubyLLM
52
52
  end
53
53
  end
54
54
 
55
- def accumulate_tool_calls(new_tool_calls) # rubocop:disable Metrics/MethodLength
55
+ def accumulate_tool_calls(new_tool_calls) # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
56
+ RubyLLM.logger.debug "Accumulating tool calls: #{new_tool_calls}"
56
57
  new_tool_calls.each_value do |tool_call|
57
58
  if tool_call.id
59
+ tool_call_id = tool_call.id.empty? ? SecureRandom.uuid : tool_call.id
60
+ tool_call_arguments = tool_call.arguments.empty? ? String.new : tool_call.arguments
58
61
  @tool_calls[tool_call.id] = ToolCall.new(
59
- id: tool_call.id,
62
+ id: tool_call_id,
60
63
  name: tool_call.name,
61
- arguments: String.new
64
+ arguments: tool_call_arguments
62
65
  )
63
66
  @latest_tool_call_id = tool_call.id
64
67
  else
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '0.1.0.pre24'
4
+ VERSION = '0.1.0.pre26'
5
5
  end
data/lib/ruby_llm.rb CHANGED
@@ -12,7 +12,8 @@ loader.inflector.inflect(
12
12
  'ruby_llm' => 'RubyLLM',
13
13
  'llm' => 'LLM',
14
14
  'openai' => 'OpenAI',
15
- 'api' => 'API'
15
+ 'api' => 'API',
16
+ 'deepseek' => 'DeepSeek'
16
17
  )
17
18
  loader.setup
18
19
 
@@ -35,6 +36,10 @@ module RubyLLM
35
36
  Models
36
37
  end
37
38
 
39
+ def providers
40
+ Provider.providers.values
41
+ end
42
+
38
43
  def configure
39
44
  yield config
40
45
  end
@@ -55,6 +60,8 @@ end
55
60
 
56
61
  RubyLLM::Provider.register :openai, RubyLLM::Providers::OpenAI
57
62
  RubyLLM::Provider.register :anthropic, RubyLLM::Providers::Anthropic
63
+ RubyLLM::Provider.register :gemini, RubyLLM::Providers::Gemini
64
+ RubyLLM::Provider.register :deepseek, RubyLLM::Providers::DeepSeek
58
65
 
59
66
  if defined?(Rails::Railtie)
60
67
  require 'ruby_llm/railtie'
@@ -9,17 +9,21 @@ namespace :ruby_llm do
9
9
  RubyLLM.configure do |config|
10
10
  config.openai_api_key = ENV.fetch('OPENAI_API_KEY')
11
11
  config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY')
12
+ config.gemini_api_key = ENV['GEMINI_API_KEY']
13
+ config.deepseek_api_key = ENV['DEEPSEEK_API_KEY']
12
14
  end
13
15
 
14
16
  # Get all models
15
- models = RubyLLM.models.refresh
17
+ models = RubyLLM.models.refresh!
16
18
 
17
19
  # Write to models.json
18
20
  models_file = File.expand_path('../../lib/ruby_llm/models.json', __dir__)
19
21
  File.write(models_file, JSON.pretty_generate(models.map(&:to_h)))
20
22
 
21
23
  puts "Updated models.json with #{models.size} models:"
22
- puts "OpenAI models: #{models.count { |m| m.provider == 'openai' }}"
23
- puts "Anthropic models: #{models.count { |m| m.provider == 'anthropic' }}"
24
+ RubyLLM::Provider.providers.each do |provider_sym, provider_module|
25
+ provider_name = provider_module.to_s.split('::').last
26
+ puts "#{provider_name} models: #{models.count { |m| m.provider == provider_sym.to_s }}"
27
+ end
24
28
  end
25
29
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre24
4
+ version: 0.1.0.pre26
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-02-14 00:00:00.000000000 Z
11
+ date: 2025-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: event_stream_parser
@@ -353,12 +353,16 @@ files:
353
353
  - lib/ruby_llm/error.rb
354
354
  - lib/ruby_llm/message.rb
355
355
  - lib/ruby_llm/model_capabilities/anthropic.rb
356
+ - lib/ruby_llm/model_capabilities/deepseek.rb
357
+ - lib/ruby_llm/model_capabilities/gemini.rb
356
358
  - lib/ruby_llm/model_capabilities/openai.rb
357
359
  - lib/ruby_llm/model_info.rb
358
360
  - lib/ruby_llm/models.json
359
361
  - lib/ruby_llm/models.rb
360
362
  - lib/ruby_llm/provider.rb
361
363
  - lib/ruby_llm/providers/anthropic.rb
364
+ - lib/ruby_llm/providers/deepseek.rb
365
+ - lib/ruby_llm/providers/gemini.rb
362
366
  - lib/ruby_llm/providers/openai.rb
363
367
  - lib/ruby_llm/railtie.rb
364
368
  - lib/ruby_llm/stream_accumulator.rb