ruby_llm 1.3.0 → 1.3.2beta1

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.
@@ -92,7 +92,8 @@ module RubyLLM
92
92
  f.response :json, parser_options: { symbolize_names: true }
93
93
  end
94
94
  response = connection.get 'https://api.parsera.org/v1/llm-specs'
95
- response.body.map { |data| Model::Info.new(data) }
95
+ models = response.body.map { |data| Model::Info.new(data) }
96
+ models.reject { |model| model.provider.nil? || model.id.nil? }
96
97
  end
97
98
 
98
99
  def merge_models(provider_models, parsera_models)
@@ -34,7 +34,7 @@ module RubyLLM
34
34
  def embed(text, model:, connection:, dimensions:)
35
35
  payload = render_embedding_payload(text, model:, dimensions:)
36
36
  response = connection.post(embedding_url(model:), payload)
37
- parse_embedding_response(response, model:)
37
+ parse_embedding_response(response, model:, text:)
38
38
  end
39
39
 
40
40
  def paint(prompt, model:, size:, connection:)
@@ -14,12 +14,13 @@ module RubyLLM
14
14
  def format_tool_call(msg)
15
15
  tool_call = msg.tool_calls.values.first
16
16
 
17
+ content = []
18
+ content << Media.format_text(msg.content) unless msg.content.nil? || msg.content.empty?
19
+ content << format_tool_use_block(tool_call)
20
+
17
21
  {
18
22
  role: 'assistant',
19
- content: [
20
- Media.format_text(msg.content),
21
- format_tool_use_block(tool_call)
22
- ]
23
+ content:
23
24
  }
24
25
  end
25
26
 
@@ -24,13 +24,13 @@ module RubyLLM
24
24
  end
25
25
 
26
26
  def read_prelude(chunk, offset)
27
- total_length = chunk[offset...offset + 4].unpack1('N')
28
- headers_length = chunk[offset + 4...offset + 8].unpack1('N')
27
+ total_length = chunk[offset...(offset + 4)].unpack1('N')
28
+ headers_length = chunk[(offset + 4)...(offset + 8)].unpack1('N')
29
29
  [total_length, headers_length]
30
30
  end
31
31
 
32
32
  def valid_lengths?(total_length, headers_length)
33
- validate_length_constraints(total_length, headers_length)
33
+ valid_length_constraints?(total_length, headers_length)
34
34
  end
35
35
 
36
36
  def calculate_positions(offset, total_length, headers_length)
@@ -67,17 +67,17 @@ module RubyLLM
67
67
 
68
68
  def valid_prelude_at_position?(chunk, pos)
69
69
  lengths = extract_potential_lengths(chunk, pos)
70
- validate_length_constraints(*lengths)
70
+ valid_length_constraints?(*lengths)
71
71
  end
72
72
 
73
73
  def extract_potential_lengths(chunk, pos)
74
74
  [
75
- chunk[pos...pos + 4].unpack1('N'),
76
- chunk[pos + 4...pos + 8].unpack1('N')
75
+ chunk[pos...(pos + 4)].unpack1('N'),
76
+ chunk[(pos + 4)...(pos + 8)].unpack1('N')
77
77
  ]
78
78
  end
79
79
 
80
- def validate_length_constraints(total_length, headers_length)
80
+ def valid_length_constraints?(total_length, headers_length)
81
81
  return false if total_length.nil? || headers_length.nil?
82
82
  return false if total_length <= 0 || total_length > 1_000_000
83
83
  return false if headers_length <= 0 || headers_length >= total_length
@@ -15,9 +15,11 @@ module RubyLLM
15
15
  { requests: [text].flatten.map { |t| single_embedding_payload(t, model:, dimensions:) } }
16
16
  end
17
17
 
18
- def parse_embedding_response(response, model:)
18
+ def parse_embedding_response(response, model:, text:)
19
19
  vectors = response.body['embeddings']&.map { |e| e['values'] }
20
- vectors in [vectors]
20
+ # If we only got one embedding AND the input was a single string (not an array),
21
+ # return it as a single vector
22
+ vectors = vectors.first if vectors&.length == 1 && !text.is_a?(Array)
21
23
 
22
24
  Embedding.new(vectors:, model:, input_tokens: 0)
23
25
  end
@@ -215,10 +215,13 @@ module RubyLLM
215
215
  end
216
216
  end
217
217
 
218
- def normalize_temperature(temperature, model_id)
218
+ def self.normalize_temperature(temperature, model_id)
219
219
  if model_id.match?(/^o\d/)
220
220
  RubyLLM.logger.debug "Model #{model_id} requires temperature=1.0, ignoring provided value"
221
221
  1.0
222
+ elsif model_id.match?(/-search/)
223
+ RubyLLM.logger.debug "Model #{model_id} does not accept temperature parameter, removing"
224
+ nil
222
225
  else
223
226
  temperature
224
227
  end
@@ -12,18 +12,22 @@ module RubyLLM
12
12
  module_function
13
13
 
14
14
  def render_payload(messages, tools:, temperature:, model:, stream: false)
15
- {
15
+ payload = {
16
16
  model: model,
17
17
  messages: format_messages(messages),
18
- temperature: temperature,
19
18
  stream: stream
20
- }.tap do |payload|
21
- if tools.any?
22
- payload[:tools] = tools.map { |_, tool| tool_for(tool) }
23
- payload[:tool_choice] = 'auto'
24
- end
25
- payload[:stream_options] = { include_usage: true } if stream
19
+ }
20
+
21
+ # Only include temperature if it's not nil (some models don't accept it)
22
+ payload[:temperature] = temperature unless temperature.nil?
23
+
24
+ if tools.any?
25
+ payload[:tools] = tools.map { |_, tool| tool_for(tool) }
26
+ payload[:tool_choice] = 'auto'
26
27
  end
28
+
29
+ payload[:stream_options] = { include_usage: true } if stream
30
+ payload
27
31
  end
28
32
 
29
33
  def parse_completion_response(response)
@@ -19,13 +19,14 @@ module RubyLLM
19
19
  }.compact
20
20
  end
21
21
 
22
- def parse_embedding_response(response, model:)
22
+ def parse_embedding_response(response, model:, text:)
23
23
  data = response.body
24
24
  input_tokens = data.dig('usage', 'prompt_tokens') || 0
25
25
  vectors = data['data'].map { |d| d['embedding'] }
26
26
 
27
- # If we only got one embedding, return it as a single vector
28
- vectors in [vectors]
27
+ # If we only got one embedding AND the input was a single string (not an array),
28
+ # return it as a single vector
29
+ vectors = vectors.first if vectors.length == 1 && !text.is_a?(Array)
29
30
 
30
31
  Embedding.new(vectors:, model:, input_tokens:)
31
32
  end
data/lib/ruby_llm/tool.rb CHANGED
@@ -49,14 +49,14 @@ module RubyLLM
49
49
  end
50
50
 
51
51
  def name
52
- self.class.name
53
- .unicode_normalize(:nfkd)
54
- .encode('ASCII', replace: '')
55
- .gsub(/[^a-zA-Z0-9_-]/, '-')
56
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
57
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
58
- .downcase
59
- .delete_suffix('_tool')
52
+ klass_name = self.class.name
53
+ normalized = klass_name.to_s.dup.force_encoding('UTF-8').unicode_normalize(:nfkd)
54
+ normalized.encode('ASCII', replace: '')
55
+ .gsub(/[^a-zA-Z0-9_-]/, '-')
56
+ .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
57
+ .gsub(/([a-z\d])([A-Z])/, '\1_\2')
58
+ .downcase
59
+ .delete_suffix('_tool')
60
60
  end
61
61
 
62
62
  def description
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- VERSION = '1.3.0'
4
+ VERSION = '1.3.2beta1'
5
5
  end
data/lib/ruby_llm.rb CHANGED
@@ -67,7 +67,7 @@ module RubyLLM
67
67
  end
68
68
 
69
69
  def logger
70
- @logger ||= Logger.new(
70
+ @logger ||= config.logger || Logger.new(
71
71
  config.log_file,
72
72
  progname: 'RubyLLM',
73
73
  level: config.log_level
@@ -23,14 +23,14 @@ def generate_models_markdown
23
23
  layout: default
24
24
  title: Available Models
25
25
  parent: Guides
26
- nav_order: 10
26
+ nav_order: 11
27
27
  permalink: /guides/available-models
28
28
  ---
29
29
 
30
30
  # Available Models
31
31
  {: .no_toc }
32
32
 
33
- This guide lists all models available in RubyLLM, automatically generated from the current model registry.
33
+ This guide lists all models available in RubyLLM, automatically generated from the current [model registry](https://github.com/crmne/ruby_llm/blob/main/lib/ruby_llm/models.json).
34
34
  {: .fs-6 .fw-300 }
35
35
 
36
36
  ## Table of contents
@@ -41,15 +41,21 @@ def generate_models_markdown
41
41
 
42
42
  ---
43
43
 
44
- ## Contributing
44
+ ## How Model Data Works
45
45
 
46
- The model list is automatically generated from the model registry. To add or update models:
46
+ RubyLLM's model registry combines data from multiple sources:
47
47
 
48
- 1. Edit the appropriate `capabilities.rb` file in `lib/ruby_llm/providers/<provider>/`
49
- 2. Run `rake models:update` to refresh the model registry
50
- 3. Submit a pull request with the updated `models.json`
48
+ - **OpenAI, Anthropic, DeepSeek, Gemini**: Data from [Parsera](https://api.parsera.org/v1/llm-specs)
49
+ - **OpenRouter**: Direct from OpenRouter's API
50
+ - **Other providers**: Defined in `capabilities.rb` files
51
51
 
52
- See [Contributing Guide](/CONTRIBUTING.md) for more details.
52
+ ## Contributing Model Updates
53
+
54
+ **For major providers** (OpenAI, Anthropic, DeepSeek, Gemini): File issues with [Parsera](https://github.com/parsera-labs/api-llm-specs/issues) for public model data corrections.
55
+
56
+ **For other providers**: Edit `lib/ruby_llm/providers/<provider>/capabilities.rb` then run `rake models:update`.
57
+
58
+ See the [Contributing Guide](https://github.com/crmne/ruby_llm/blob/main/CONTRIBUTING.md) for details.
53
59
 
54
60
  ## Last Updated
55
61
  {: .d-inline-block }
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.3.0
4
+ version: 1.3.2beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -235,14 +235,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
235
235
  requirements:
236
236
  - - ">="
237
237
  - !ruby/object:Gem::Version
238
- version: 3.1.0
238
+ version: 3.1.3
239
239
  required_rubygems_version: !ruby/object:Gem::Requirement
240
240
  requirements:
241
241
  - - ">="
242
242
  - !ruby/object:Gem::Version
243
243
  version: '0'
244
244
  requirements: []
245
- rubygems_version: 3.6.7
245
+ rubygems_version: 3.6.9
246
246
  specification_version: 4
247
247
  summary: A single delightful Ruby way to work with AI.
248
248
  test_files: []