ruby_llm 1.5.1 → 1.6.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.
- checksums.yaml +4 -4
- data/README.md +8 -34
- data/lib/ruby_llm/active_record/acts_as.rb +64 -10
- data/lib/ruby_llm/aliases.json +23 -3
- data/lib/ruby_llm/chat.rb +30 -8
- data/lib/ruby_llm/configuration.rb +7 -18
- data/lib/ruby_llm/connection.rb +11 -6
- data/lib/ruby_llm/context.rb +2 -3
- data/lib/ruby_llm/embedding.rb +3 -4
- data/lib/ruby_llm/error.rb +2 -2
- data/lib/ruby_llm/image.rb +3 -4
- data/lib/ruby_llm/message.rb +4 -0
- data/lib/ruby_llm/models.json +6598 -6370
- data/lib/ruby_llm/models.rb +22 -31
- data/lib/ruby_llm/provider.rb +150 -89
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -2
- data/lib/ruby_llm/providers/anthropic/chat.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/media.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/models.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/streaming.rb +1 -1
- data/lib/ruby_llm/providers/anthropic/tools.rb +1 -1
- data/lib/ruby_llm/providers/anthropic.rb +17 -22
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +3 -63
- data/lib/ruby_llm/providers/bedrock/chat.rb +5 -4
- data/lib/ruby_llm/providers/bedrock/media.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/models.rb +5 -6
- data/lib/ruby_llm/providers/bedrock/signing.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +5 -4
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/streaming.rb +1 -1
- data/lib/ruby_llm/providers/bedrock.rb +26 -31
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +16 -57
- data/lib/ruby_llm/providers/deepseek/chat.rb +1 -1
- data/lib/ruby_llm/providers/deepseek.rb +12 -17
- data/lib/ruby_llm/providers/gemini/capabilities.rb +1 -1
- data/lib/ruby_llm/providers/gemini/chat.rb +1 -1
- data/lib/ruby_llm/providers/gemini/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/gemini/images.rb +1 -1
- data/lib/ruby_llm/providers/gemini/media.rb +1 -1
- data/lib/ruby_llm/providers/gemini/models.rb +1 -1
- data/lib/ruby_llm/providers/gemini/streaming.rb +1 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +1 -7
- data/lib/ruby_llm/providers/gemini.rb +18 -23
- data/lib/ruby_llm/providers/gpustack/chat.rb +1 -1
- data/lib/ruby_llm/providers/gpustack/models.rb +1 -1
- data/lib/ruby_llm/providers/gpustack.rb +16 -19
- data/lib/ruby_llm/providers/mistral/capabilities.rb +1 -1
- data/lib/ruby_llm/providers/mistral/chat.rb +1 -1
- data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/mistral/models.rb +1 -1
- data/lib/ruby_llm/providers/mistral.rb +14 -19
- data/lib/ruby_llm/providers/ollama/chat.rb +1 -1
- data/lib/ruby_llm/providers/ollama/media.rb +1 -1
- data/lib/ruby_llm/providers/ollama.rb +13 -18
- data/lib/ruby_llm/providers/openai/capabilities.rb +3 -3
- data/lib/ruby_llm/providers/openai/chat.rb +3 -6
- data/lib/ruby_llm/providers/openai/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/openai/images.rb +1 -1
- data/lib/ruby_llm/providers/openai/media.rb +1 -1
- data/lib/ruby_llm/providers/openai/models.rb +1 -1
- data/lib/ruby_llm/providers/openai/streaming.rb +1 -1
- data/lib/ruby_llm/providers/openai/tools.rb +1 -1
- data/lib/ruby_llm/providers/openai.rb +24 -36
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +9 -14
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +1 -30
- data/lib/ruby_llm/providers/perplexity/chat.rb +1 -1
- data/lib/ruby_llm/providers/perplexity/models.rb +1 -1
- data/lib/ruby_llm/providers/perplexity.rb +13 -18
- data/lib/ruby_llm/stream_accumulator.rb +3 -3
- data/lib/ruby_llm/streaming.rb +16 -3
- data/lib/ruby_llm/tool.rb +19 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/models_docs.rake +18 -11
- data/lib/tasks/models_update.rake +5 -4
- metadata +9 -8
@@ -2,14 +2,11 @@
|
|
2
2
|
|
3
3
|
module RubyLLM
|
4
4
|
module Providers
|
5
|
-
|
5
|
+
class Perplexity
|
6
6
|
# Determines capabilities and pricing for Perplexity models
|
7
7
|
module Capabilities
|
8
8
|
module_function
|
9
9
|
|
10
|
-
# Returns the context window size for the given model ID
|
11
|
-
# @param model_id [String] the model identifier
|
12
|
-
# @return [Integer] the context window size in tokens
|
13
10
|
def context_window_for(model_id)
|
14
11
|
case model_id
|
15
12
|
when /sonar-pro/ then 200_000
|
@@ -17,9 +14,6 @@ module RubyLLM
|
|
17
14
|
end
|
18
15
|
end
|
19
16
|
|
20
|
-
# Returns the maximum number of tokens that can be generated
|
21
|
-
# @param model_id [String] the model identifier
|
22
|
-
# @return [Integer] the maximum number of tokens
|
23
17
|
def max_tokens_for(model_id)
|
24
18
|
case model_id
|
25
19
|
when /sonar-(?:pro|reasoning-pro)/ then 8_192
|
@@ -27,23 +21,14 @@ module RubyLLM
|
|
27
21
|
end
|
28
22
|
end
|
29
23
|
|
30
|
-
# Returns the price per million tokens for input
|
31
|
-
# @param model_id [String] the model identifier
|
32
|
-
# @return [Float] the price per million tokens in USD
|
33
24
|
def input_price_for(model_id)
|
34
25
|
PRICES.dig(model_family(model_id), :input) || 1.0
|
35
26
|
end
|
36
27
|
|
37
|
-
# Returns the price per million tokens for output
|
38
|
-
# @param model_id [String] the model identifier
|
39
|
-
# @return [Float] the price per million tokens in USD
|
40
28
|
def output_price_for(model_id)
|
41
29
|
PRICES.dig(model_family(model_id), :output) || 1.0
|
42
30
|
end
|
43
31
|
|
44
|
-
# Determines if the model supports vision capabilities
|
45
|
-
# @param model_id [String] the model identifier
|
46
|
-
# @return [Boolean] true if the model supports vision
|
47
32
|
def supports_vision?(model_id)
|
48
33
|
case model_id
|
49
34
|
when /sonar-reasoning-pro/, /sonar-reasoning/, /sonar-pro/, /sonar/ then true
|
@@ -51,21 +36,14 @@ module RubyLLM
|
|
51
36
|
end
|
52
37
|
end
|
53
38
|
|
54
|
-
# Determines if the model supports function calling
|
55
|
-
# @param model_id [String] the model identifier
|
56
|
-
# @return [Boolean] true if the model supports functions
|
57
39
|
def supports_functions?(_model_id)
|
58
40
|
false
|
59
41
|
end
|
60
42
|
|
61
|
-
# Determines if the model supports JSON mode
|
62
43
|
def supports_json_mode?(_model_id)
|
63
44
|
true
|
64
45
|
end
|
65
46
|
|
66
|
-
# Formats the model ID into a human-readable display name
|
67
|
-
# @param model_id [String] the model identifier
|
68
|
-
# @return [String] the formatted display name
|
69
47
|
def format_display_name(model_id)
|
70
48
|
case model_id
|
71
49
|
when 'sonar' then 'Sonar'
|
@@ -80,16 +58,10 @@ module RubyLLM
|
|
80
58
|
end
|
81
59
|
end
|
82
60
|
|
83
|
-
# Returns the model type
|
84
|
-
# @param model_id [String] the model identifier
|
85
|
-
# @return [String] the model type (e.g., 'chat')
|
86
61
|
def model_type(_model_id)
|
87
62
|
'chat'
|
88
63
|
end
|
89
64
|
|
90
|
-
# Returns the model family
|
91
|
-
# @param model_id [String] the model identifier
|
92
|
-
# @return [Symbol] the model family
|
93
65
|
def model_family(model_id)
|
94
66
|
case model_id
|
95
67
|
when 'sonar' then :sonar
|
@@ -123,7 +95,6 @@ module RubyLLM
|
|
123
95
|
output_per_million: prices[:output]
|
124
96
|
}
|
125
97
|
|
126
|
-
# Add special pricing if available
|
127
98
|
standard_pricing[:citation_per_million] = prices[:citation] if prices[:citation]
|
128
99
|
standard_pricing[:reasoning_per_million] = prices[:reasoning] if prices[:reasoning]
|
129
100
|
standard_pricing[:search_per_thousand] = prices[:search_queries] if prices[:search_queries]
|
@@ -3,34 +3,29 @@
|
|
3
3
|
module RubyLLM
|
4
4
|
module Providers
|
5
5
|
# Perplexity API integration.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
extend Perplexity::Models
|
6
|
+
class Perplexity < OpenAI
|
7
|
+
include Perplexity::Chat
|
8
|
+
include Perplexity::Models
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
def api_base(_config)
|
10
|
+
def api_base
|
14
11
|
'https://api.perplexity.ai'
|
15
12
|
end
|
16
13
|
|
17
|
-
def headers
|
14
|
+
def headers
|
18
15
|
{
|
19
|
-
'Authorization' => "Bearer #{config.perplexity_api_key}",
|
16
|
+
'Authorization' => "Bearer #{@config.perplexity_api_key}",
|
20
17
|
'Content-Type' => 'application/json'
|
21
18
|
}
|
22
19
|
end
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
def slug
|
29
|
-
'perplexity'
|
30
|
-
end
|
21
|
+
class << self
|
22
|
+
def capabilities
|
23
|
+
Perplexity::Capabilities
|
24
|
+
end
|
31
25
|
|
32
|
-
|
33
|
-
|
26
|
+
def configuration_requirements
|
27
|
+
%i[perplexity_api_key]
|
28
|
+
end
|
34
29
|
end
|
35
30
|
|
36
31
|
def parse_error(response)
|
@@ -16,7 +16,7 @@ module RubyLLM
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def add(chunk)
|
19
|
-
RubyLLM.logger.debug chunk.inspect
|
19
|
+
RubyLLM.logger.debug chunk.inspect if RubyLLM.config.log_stream_debug
|
20
20
|
@model_id ||= chunk.model_id
|
21
21
|
|
22
22
|
if chunk.tool_call?
|
@@ -26,7 +26,7 @@ module RubyLLM
|
|
26
26
|
end
|
27
27
|
|
28
28
|
count_tokens chunk
|
29
|
-
RubyLLM.logger.debug inspect
|
29
|
+
RubyLLM.logger.debug inspect if RubyLLM.config.log_stream_debug
|
30
30
|
end
|
31
31
|
|
32
32
|
def to_message(response)
|
@@ -62,7 +62,7 @@ module RubyLLM
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def accumulate_tool_calls(new_tool_calls)
|
65
|
-
RubyLLM.logger.debug "Accumulating tool calls: #{new_tool_calls}"
|
65
|
+
RubyLLM.logger.debug "Accumulating tool calls: #{new_tool_calls}" if RubyLLM.config.log_stream_debug
|
66
66
|
new_tool_calls.each_value do |tool_call|
|
67
67
|
if tool_call.id
|
68
68
|
tool_call_id = tool_call.id.empty? ? SecureRandom.uuid : tool_call.id
|
data/lib/ruby_llm/streaming.rb
CHANGED
@@ -8,10 +8,12 @@ module RubyLLM
|
|
8
8
|
module Streaming
|
9
9
|
module_function
|
10
10
|
|
11
|
-
def stream_response(connection, payload, &block)
|
11
|
+
def stream_response(connection, payload, additional_headers = {}, &block)
|
12
12
|
accumulator = StreamAccumulator.new
|
13
13
|
|
14
14
|
response = connection.post stream_url, payload do |req|
|
15
|
+
# Merge additional headers, with existing headers taking precedence
|
16
|
+
req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
|
15
17
|
if req.options.respond_to?(:on_data)
|
16
18
|
# Handle Faraday 2.x streaming with on_data method
|
17
19
|
req.options.on_data = handle_stream do |chunk|
|
@@ -27,7 +29,9 @@ module RubyLLM
|
|
27
29
|
end
|
28
30
|
end
|
29
31
|
|
30
|
-
accumulator.to_message(response)
|
32
|
+
message = accumulator.to_message(response)
|
33
|
+
RubyLLM.logger.debug "Stream completed: #{message.inspect}"
|
34
|
+
message
|
31
35
|
end
|
32
36
|
|
33
37
|
def handle_stream(&block)
|
@@ -56,7 +60,7 @@ module RubyLLM
|
|
56
60
|
end
|
57
61
|
|
58
62
|
def process_stream_chunk(chunk, parser, env, &)
|
59
|
-
RubyLLM.logger.debug "Received chunk: #{chunk}"
|
63
|
+
RubyLLM.logger.debug "Received chunk: #{chunk}" if RubyLLM.config.log_stream_debug
|
60
64
|
|
61
65
|
if error_chunk?(chunk)
|
62
66
|
handle_error_chunk(chunk, env)
|
@@ -145,5 +149,14 @@ module RubyLLM
|
|
145
149
|
rescue JSON::ParserError => e
|
146
150
|
RubyLLM.logger.debug "Failed to parse error event: #{e.message}"
|
147
151
|
end
|
152
|
+
|
153
|
+
# Default implementation - providers should override this method
|
154
|
+
def parse_streaming_error(data)
|
155
|
+
error_data = JSON.parse(data)
|
156
|
+
[500, error_data['message'] || 'Unknown streaming error']
|
157
|
+
rescue JSON::ParserError => e
|
158
|
+
RubyLLM.logger.debug "Failed to parse streaming error: #{e.message}"
|
159
|
+
[500, "Failed to parse error: #{data}"]
|
160
|
+
end
|
148
161
|
end
|
149
162
|
end
|
data/lib/ruby_llm/tool.rb
CHANGED
@@ -32,6 +32,19 @@ module RubyLLM
|
|
32
32
|
# end
|
33
33
|
# end
|
34
34
|
class Tool
|
35
|
+
# Stops conversation continuation after tool execution
|
36
|
+
class Halt
|
37
|
+
attr_reader :content
|
38
|
+
|
39
|
+
def initialize(content)
|
40
|
+
@content = content
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_s
|
44
|
+
@content.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
35
48
|
class << self
|
36
49
|
def description(text = nil)
|
37
50
|
return @description unless text
|
@@ -77,5 +90,11 @@ module RubyLLM
|
|
77
90
|
def execute(...)
|
78
91
|
raise NotImplementedError, 'Subclasses must implement #execute'
|
79
92
|
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def halt(message)
|
97
|
+
Halt.new(message)
|
98
|
+
end
|
80
99
|
end
|
81
100
|
end
|
data/lib/ruby_llm/version.rb
CHANGED
data/lib/tasks/models_docs.rake
CHANGED
@@ -12,8 +12,8 @@ namespace :models do
|
|
12
12
|
output = generate_models_markdown
|
13
13
|
|
14
14
|
# Write the output
|
15
|
-
File.write('docs/available-models.md', output)
|
16
|
-
puts 'Generated docs/available-models.md'
|
15
|
+
File.write('docs/_reference/available-models.md', output)
|
16
|
+
puts 'Generated docs/_reference/available-models.md'
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
@@ -22,15 +22,16 @@ def generate_models_markdown
|
|
22
22
|
---
|
23
23
|
layout: default
|
24
24
|
title: Available Models
|
25
|
-
nav_order:
|
26
|
-
permalink: /available-models
|
25
|
+
nav_order: 1
|
27
26
|
description: Browse hundreds of AI models from every major provider. Always up-to-date, automatically generated.
|
27
|
+
redirect_from:
|
28
|
+
- /guides/available-models
|
28
29
|
---
|
29
30
|
|
30
|
-
#
|
31
|
+
# {{ page.title }}
|
31
32
|
{: .no_toc }
|
32
33
|
|
33
|
-
|
34
|
+
{{ page.description }}
|
34
35
|
{: .fs-6 .fw-300 }
|
35
36
|
|
36
37
|
## Table of contents
|
@@ -41,6 +42,13 @@ def generate_models_markdown
|
|
41
42
|
|
42
43
|
---
|
43
44
|
|
45
|
+
After reading this guide, you will know:
|
46
|
+
|
47
|
+
* How RubyLLM's model registry works and where data comes from
|
48
|
+
* How to find models by provider, capability, or purpose
|
49
|
+
* What information is available for each model
|
50
|
+
* How to use model aliases for simpler configuration
|
51
|
+
|
44
52
|
## How Model Data Works
|
45
53
|
|
46
54
|
RubyLLM's model registry combines data from multiple sources:
|
@@ -78,12 +86,12 @@ def generate_models_markdown
|
|
78
86
|
end
|
79
87
|
|
80
88
|
def generate_provider_sections
|
81
|
-
RubyLLM::Provider.providers.
|
89
|
+
RubyLLM::Provider.providers.map do |provider, provider_class|
|
82
90
|
models = RubyLLM.models.by_provider(provider)
|
83
91
|
next if models.none?
|
84
92
|
|
85
93
|
<<~PROVIDER
|
86
|
-
### #{
|
94
|
+
### #{provider_class.name} (#{models.count})
|
87
95
|
|
88
96
|
#{models_table(models)}
|
89
97
|
PROVIDER
|
@@ -167,15 +175,14 @@ end
|
|
167
175
|
def models_table(models)
|
168
176
|
return '*No models found*' if models.none?
|
169
177
|
|
170
|
-
headers = ['Model', '
|
171
|
-
alignment = [':--', ':--', '
|
178
|
+
headers = ['Model', 'Provider', 'Context', 'Max Output', 'Standard Pricing (per 1M tokens)']
|
179
|
+
alignment = [':--', ':--', '--:', '--:', ':--']
|
172
180
|
|
173
181
|
rows = models.sort_by { |m| [m.provider, m.name] }.map do |model|
|
174
182
|
# Format pricing information
|
175
183
|
pricing = standard_pricing_display(model)
|
176
184
|
|
177
185
|
[
|
178
|
-
model.name,
|
179
186
|
model.id,
|
180
187
|
model.provider,
|
181
188
|
model.context_window || '-',
|
@@ -86,8 +86,8 @@ def display_model_stats
|
|
86
86
|
puts "\nModel count:"
|
87
87
|
provider_counts = @models.all.group_by(&:provider).transform_values(&:count)
|
88
88
|
|
89
|
-
RubyLLM::Provider.providers.
|
90
|
-
name =
|
89
|
+
RubyLLM::Provider.providers.each do |sym, provider_class|
|
90
|
+
name = provider_class.name
|
91
91
|
count = provider_counts[sym.to_s] || 0
|
92
92
|
status = status(sym)
|
93
93
|
puts " #{name}: #{count} models #{status}"
|
@@ -97,9 +97,10 @@ def display_model_stats
|
|
97
97
|
end
|
98
98
|
|
99
99
|
def status(provider_sym)
|
100
|
-
|
100
|
+
provider_class = RubyLLM::Provider.providers[provider_sym]
|
101
|
+
if provider_class.local?
|
101
102
|
' (LOCAL - SKIP)'
|
102
|
-
elsif
|
103
|
+
elsif provider_class.configured?(RubyLLM.config)
|
103
104
|
' (OK)'
|
104
105
|
else
|
105
106
|
' (NOT CONFIGURED)'
|
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.
|
4
|
+
version: 1.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carmine Paolino
|
@@ -121,12 +121,13 @@ dependencies:
|
|
121
121
|
- - "~>"
|
122
122
|
- !ruby/object:Gem::Version
|
123
123
|
version: '2'
|
124
|
-
description:
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
124
|
+
description: One beautiful Ruby API for GPT, Claude, Gemini, and more. Easily build
|
125
|
+
chatbots, AI agents, RAG applications, and content generators. Features chat (text,
|
126
|
+
images, audio, PDFs), image generation, embeddings, tools (function calling), structured
|
127
|
+
output, Rails integration, and streaming. Works with OpenAI, Anthropic, Google Gemini,
|
128
|
+
AWS Bedrock, DeepSeek, Mistral, Ollama (local models), OpenRouter, Perplexity, GPUStack,
|
129
|
+
and any OpenAI-compatible API. Minimal dependencies - just Faraday, Zeitwerk, and
|
130
|
+
Marcel.
|
130
131
|
email:
|
131
132
|
- carmine@paolino.me
|
132
133
|
executables: []
|
@@ -266,5 +267,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
266
267
|
requirements: []
|
267
268
|
rubygems_version: 3.6.9
|
268
269
|
specification_version: 4
|
269
|
-
summary:
|
270
|
+
summary: One beautiful Ruby API for GPT, Claude, Gemini, and more.
|
270
271
|
test_files: []
|