ruby_llm 1.6.1 → 1.6.2
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 +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +1 -1
- data/lib/ruby_llm/chat.rb +1 -9
- data/lib/ruby_llm/configuration.rb +0 -2
- data/lib/ruby_llm/content.rb +1 -1
- data/lib/ruby_llm/error.rb +0 -2
- data/lib/ruby_llm/models.json +72 -9
- data/lib/ruby_llm/models.rb +1 -5
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +1 -1
- data/lib/ruby_llm/providers/gemini/chat.rb +7 -1
- data/lib/ruby_llm/providers/gemini/streaming.rb +4 -1
- data/lib/ruby_llm/providers/openai/capabilities.rb +2 -2
- data/lib/ruby_llm/stream_accumulator.rb +2 -2
- data/lib/ruby_llm/streaming.rb +1 -1
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/tasks/aliases.rake +2 -2
- data/lib/tasks/models_docs.rake +4 -4
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1b210b6577ab40b05d222354a9cb4c9dd115d7237e4b88be3cd63ee42aa2e122
|
4
|
+
data.tar.gz: 58d0df110e469fef90f5c5591bed4dd3a2d4369afa982217793c11fecb00e320
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3979e1be85c20e8961eb54bcc1d3cf0e8f49dc10fbb5bbbf57e292a098b4c09b053101473c59aef2186c5583276ea5c909b461ba34ac4f8c8eb0f0a5e69cc76d
|
7
|
+
data.tar.gz: 452f86034599f6d8b2d200c3357137f0ec93087b410c4eef5e5393976414b8a986871fa532c6149fb69b081d66f1a823891ab27bd186fb9a31a1ca232707a13d
|
data/README.md
CHANGED
@@ -6,7 +6,7 @@
|
|
6
6
|
**One *beautiful* Ruby API for GPT, Claude, Gemini, and more.** Easily build chatbots, AI agents, RAG applications, and content generators. Features chat (text, images, audio, PDFs), image generation, embeddings, tools (function calling), structured output, Rails integration, and streaming. Works with OpenAI, Anthropic, Google Gemini, AWS Bedrock, DeepSeek, Mistral, Ollama (local models), OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API.
|
7
7
|
|
8
8
|
<div class="badge-container">
|
9
|
-
<a href="https://badge.fury.io/rb/ruby_llm"><img src="https://badge.fury.io/rb/ruby_llm.svg?a=
|
9
|
+
<a href="https://badge.fury.io/rb/ruby_llm"><img src="https://badge.fury.io/rb/ruby_llm.svg?a=4" alt="Gem Version" /></a>
|
10
10
|
<a href="https://github.com/testdouble/standard"><img src="https://img.shields.io/badge/code_style-standard-brightgreen.svg" alt="Ruby Style Guide" /></a>
|
11
11
|
<a href="https://rubygems.org/gems/ruby_llm"><img alt="Gem Downloads" src="https://img.shields.io/gem/dt/ruby_llm"></a>
|
12
12
|
<a href="https://codecov.io/gh/crmne/ruby_llm"><img src="https://codecov.io/gh/crmne/ruby_llm/branch/main/graph/badge.svg" alt="codecov" /></a>
|
data/lib/ruby_llm/chat.rb
CHANGED
@@ -51,10 +51,6 @@ module RubyLLM
|
|
51
51
|
end
|
52
52
|
|
53
53
|
def with_tool(tool)
|
54
|
-
unless @model.supports_functions?
|
55
|
-
raise UnsupportedFunctionsError, "Model #{@model.id} doesn't support function calling"
|
56
|
-
end
|
57
|
-
|
58
54
|
tool_instance = tool.is_a?(Class) ? tool.new : tool
|
59
55
|
@tools[tool_instance.name.to_sym] = tool_instance
|
60
56
|
self
|
@@ -94,11 +90,7 @@ module RubyLLM
|
|
94
90
|
self
|
95
91
|
end
|
96
92
|
|
97
|
-
def with_schema(schema
|
98
|
-
unless force || @model.structured_output?
|
99
|
-
raise UnsupportedStructuredOutputError, "Model #{@model.id} doesn't support structured output"
|
100
|
-
end
|
101
|
-
|
93
|
+
def with_schema(schema)
|
102
94
|
schema_instance = schema.is_a?(Class) ? schema.new : schema
|
103
95
|
|
104
96
|
# Accept both RubyLLM::Schema instances and plain JSON schemas
|
@@ -44,7 +44,6 @@ module RubyLLM
|
|
44
44
|
:logger,
|
45
45
|
:log_file,
|
46
46
|
:log_level,
|
47
|
-
:log_assume_model_exists,
|
48
47
|
:log_stream_debug
|
49
48
|
|
50
49
|
def initialize
|
@@ -64,7 +63,6 @@ module RubyLLM
|
|
64
63
|
# Logging configuration
|
65
64
|
@log_file = $stdout
|
66
65
|
@log_level = ENV['RUBYLLM_DEBUG'] ? Logger::DEBUG : Logger::INFO
|
67
|
-
@log_assume_model_exists = true
|
68
66
|
@log_stream_debug = ENV['RUBYLLM_STREAM_DEBUG'] == 'true'
|
69
67
|
end
|
70
68
|
|
data/lib/ruby_llm/content.rb
CHANGED
@@ -43,7 +43,7 @@ module RubyLLM
|
|
43
43
|
def process_attachments(attachments)
|
44
44
|
if attachments.is_a?(Hash)
|
45
45
|
# Ignores types (like :image, :audio, :text, :pdf) since we have robust MIME type detection
|
46
|
-
attachments.each_value(
|
46
|
+
attachments.each_value { |attachment| process_attachments_array_or_string(attachment) }
|
47
47
|
else
|
48
48
|
process_attachments_array_or_string attachments
|
49
49
|
end
|
data/lib/ruby_llm/error.rb
CHANGED
@@ -23,9 +23,7 @@ module RubyLLM
|
|
23
23
|
class ConfigurationError < StandardError; end
|
24
24
|
class InvalidRoleError < StandardError; end
|
25
25
|
class ModelNotFoundError < StandardError; end
|
26
|
-
class UnsupportedFunctionsError < StandardError; end
|
27
26
|
class UnsupportedAttachmentError < StandardError; end
|
28
|
-
class UnsupportedStructuredOutputError < StandardError; end
|
29
27
|
|
30
28
|
# Error classes for different HTTP status codes
|
31
29
|
class BadRequestError < Error; end
|
data/lib/ruby_llm/models.json
CHANGED
@@ -16285,7 +16285,6 @@
|
|
16285
16285
|
"supported_parameters": [
|
16286
16286
|
"frequency_penalty",
|
16287
16287
|
"logit_bias",
|
16288
|
-
"logprobs",
|
16289
16288
|
"max_tokens",
|
16290
16289
|
"min_p",
|
16291
16290
|
"presence_penalty",
|
@@ -16297,7 +16296,6 @@
|
|
16297
16296
|
"tool_choice",
|
16298
16297
|
"tools",
|
16299
16298
|
"top_k",
|
16300
|
-
"top_logprobs",
|
16301
16299
|
"top_p"
|
16302
16300
|
]
|
16303
16301
|
}
|
@@ -17474,7 +17472,6 @@
|
|
17474
17472
|
"supported_parameters": [
|
17475
17473
|
"frequency_penalty",
|
17476
17474
|
"logit_bias",
|
17477
|
-
"logprobs",
|
17478
17475
|
"max_tokens",
|
17479
17476
|
"min_p",
|
17480
17477
|
"presence_penalty",
|
@@ -17484,7 +17481,6 @@
|
|
17484
17481
|
"stop",
|
17485
17482
|
"temperature",
|
17486
17483
|
"top_k",
|
17487
|
-
"top_logprobs",
|
17488
17484
|
"top_p"
|
17489
17485
|
]
|
17490
17486
|
}
|
@@ -19472,6 +19468,72 @@
|
|
19472
19468
|
]
|
19473
19469
|
}
|
19474
19470
|
},
|
19471
|
+
{
|
19472
|
+
"id": "mistralai/mistral-medium-3.1",
|
19473
|
+
"name": "Mistral: Mistral Medium 3.1",
|
19474
|
+
"provider": "openrouter",
|
19475
|
+
"family": "mistralai",
|
19476
|
+
"created_at": "2025-08-13 16:33:59 +0200",
|
19477
|
+
"context_window": 131072,
|
19478
|
+
"max_output_tokens": null,
|
19479
|
+
"knowledge_cutoff": null,
|
19480
|
+
"modalities": {
|
19481
|
+
"input": [
|
19482
|
+
"text",
|
19483
|
+
"image"
|
19484
|
+
],
|
19485
|
+
"output": [
|
19486
|
+
"text"
|
19487
|
+
]
|
19488
|
+
},
|
19489
|
+
"capabilities": [
|
19490
|
+
"streaming",
|
19491
|
+
"function_calling",
|
19492
|
+
"structured_output"
|
19493
|
+
],
|
19494
|
+
"pricing": {
|
19495
|
+
"text_tokens": {
|
19496
|
+
"standard": {
|
19497
|
+
"input_per_million": 0.39999999999999997,
|
19498
|
+
"output_per_million": 2.0
|
19499
|
+
}
|
19500
|
+
}
|
19501
|
+
},
|
19502
|
+
"metadata": {
|
19503
|
+
"description": "Mistral Medium 3.1 is an updated version of Mistral Medium 3, which is a high-performance enterprise-grade language model designed to deliver frontier-level capabilities at significantly reduced operational cost. It balances state-of-the-art reasoning and multimodal performance with 8× lower cost compared to traditional large models, making it suitable for scalable deployments across professional and industrial use cases.\n\nThe model excels in domains such as coding, STEM reasoning, and enterprise adaptation. It supports hybrid, on-prem, and in-VPC deployments and is optimized for integration into custom workflows. Mistral Medium 3.1 offers competitive accuracy relative to larger models like Claude Sonnet 3.5/3.7, Llama 4 Maverick, and Command R+, while maintaining broad compatibility across cloud environments.",
|
19504
|
+
"architecture": {
|
19505
|
+
"modality": "text+image->text",
|
19506
|
+
"input_modalities": [
|
19507
|
+
"text",
|
19508
|
+
"image"
|
19509
|
+
],
|
19510
|
+
"output_modalities": [
|
19511
|
+
"text"
|
19512
|
+
],
|
19513
|
+
"tokenizer": "Mistral",
|
19514
|
+
"instruct_type": null
|
19515
|
+
},
|
19516
|
+
"top_provider": {
|
19517
|
+
"context_length": 131072,
|
19518
|
+
"max_completion_tokens": null,
|
19519
|
+
"is_moderated": false
|
19520
|
+
},
|
19521
|
+
"per_request_limits": null,
|
19522
|
+
"supported_parameters": [
|
19523
|
+
"frequency_penalty",
|
19524
|
+
"max_tokens",
|
19525
|
+
"presence_penalty",
|
19526
|
+
"response_format",
|
19527
|
+
"seed",
|
19528
|
+
"stop",
|
19529
|
+
"structured_outputs",
|
19530
|
+
"temperature",
|
19531
|
+
"tool_choice",
|
19532
|
+
"tools",
|
19533
|
+
"top_p"
|
19534
|
+
]
|
19535
|
+
}
|
19536
|
+
},
|
19475
19537
|
{
|
19476
19538
|
"id": "mistralai/mistral-nemo",
|
19477
19539
|
"name": "Mistral: Mistral Nemo",
|
@@ -29554,6 +29616,7 @@
|
|
29554
29616
|
"response_format",
|
29555
29617
|
"seed",
|
29556
29618
|
"stop",
|
29619
|
+
"structured_outputs",
|
29557
29620
|
"temperature",
|
29558
29621
|
"tool_choice",
|
29559
29622
|
"tools",
|
@@ -29700,7 +29763,7 @@
|
|
29700
29763
|
"name": "Sonar",
|
29701
29764
|
"provider": "perplexity",
|
29702
29765
|
"family": "sonar",
|
29703
|
-
"created_at": "2025-08-
|
29766
|
+
"created_at": "2025-08-14 00:27:27 +0200",
|
29704
29767
|
"context_window": 128000,
|
29705
29768
|
"max_output_tokens": 4096,
|
29706
29769
|
"knowledge_cutoff": null,
|
@@ -29732,7 +29795,7 @@
|
|
29732
29795
|
"name": "Sonar Deep Research",
|
29733
29796
|
"provider": "perplexity",
|
29734
29797
|
"family": "sonar_deep_research",
|
29735
|
-
"created_at": "2025-08-
|
29798
|
+
"created_at": "2025-08-14 00:27:27 +0200",
|
29736
29799
|
"context_window": 128000,
|
29737
29800
|
"max_output_tokens": 4096,
|
29738
29801
|
"knowledge_cutoff": null,
|
@@ -29767,7 +29830,7 @@
|
|
29767
29830
|
"name": "Sonar Pro",
|
29768
29831
|
"provider": "perplexity",
|
29769
29832
|
"family": "sonar_pro",
|
29770
|
-
"created_at": "2025-08-
|
29833
|
+
"created_at": "2025-08-14 00:27:27 +0200",
|
29771
29834
|
"context_window": 200000,
|
29772
29835
|
"max_output_tokens": 8192,
|
29773
29836
|
"knowledge_cutoff": null,
|
@@ -29799,7 +29862,7 @@
|
|
29799
29862
|
"name": "Sonar Reasoning",
|
29800
29863
|
"provider": "perplexity",
|
29801
29864
|
"family": "sonar_reasoning",
|
29802
|
-
"created_at": "2025-08-
|
29865
|
+
"created_at": "2025-08-14 00:27:27 +0200",
|
29803
29866
|
"context_window": 128000,
|
29804
29867
|
"max_output_tokens": 4096,
|
29805
29868
|
"knowledge_cutoff": null,
|
@@ -29831,7 +29894,7 @@
|
|
29831
29894
|
"name": "Sonar Reasoning Pro",
|
29832
29895
|
"provider": "perplexity",
|
29833
29896
|
"family": "sonar_reasoning_pro",
|
29834
|
-
"created_at": "2025-08-
|
29897
|
+
"created_at": "2025-08-14 00:27:27 +0200",
|
29835
29898
|
"context_window": 128000,
|
29836
29899
|
"max_output_tokens": 8192,
|
29837
29900
|
"knowledge_cutoff": null,
|
data/lib/ruby_llm/models.rb
CHANGED
@@ -64,16 +64,12 @@ module RubyLLM
|
|
64
64
|
|
65
65
|
model = Model::Info.new(
|
66
66
|
id: model_id,
|
67
|
-
name: model_id.
|
67
|
+
name: model_id.tr('-', ' ').capitalize,
|
68
68
|
provider: provider_instance.slug,
|
69
69
|
capabilities: %w[function_calling streaming],
|
70
70
|
modalities: { input: %w[text image], output: %w[text] },
|
71
71
|
metadata: { warning: 'Assuming model exists, capabilities may not be accurate' }
|
72
72
|
)
|
73
|
-
if RubyLLM.config.log_assume_model_exists
|
74
|
-
RubyLLM.logger.warn "Assuming model '#{model_id}' exists for provider '#{provider}'. " \
|
75
|
-
'Capabilities may not be accurately reflected.'
|
76
|
-
end
|
77
73
|
else
|
78
74
|
model = Models.find model_id, provider
|
79
75
|
provider_class = Provider.providers[model.provider.to_sym] || raise(Error,
|
@@ -80,7 +80,7 @@ module RubyLLM
|
|
80
80
|
content: extract_content(data),
|
81
81
|
tool_calls: tool_calls,
|
82
82
|
input_tokens: data.dig('usageMetadata', 'promptTokenCount'),
|
83
|
-
output_tokens: data
|
83
|
+
output_tokens: calculate_output_tokens(data),
|
84
84
|
model_id: data['modelVersion'] || response.env.url.path.split('/')[3].split(':')[0],
|
85
85
|
raw: response
|
86
86
|
)
|
@@ -133,6 +133,12 @@ module RubyLLM
|
|
133
133
|
parts = candidate.dig('content', 'parts')
|
134
134
|
parts&.any? { |p| p['functionCall'] }
|
135
135
|
end
|
136
|
+
|
137
|
+
def calculate_output_tokens(data)
|
138
|
+
candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
|
139
|
+
thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
|
140
|
+
candidates + thoughts
|
141
|
+
end
|
136
142
|
end
|
137
143
|
end
|
138
144
|
end
|
@@ -42,7 +42,10 @@ module RubyLLM
|
|
42
42
|
end
|
43
43
|
|
44
44
|
def extract_output_tokens(data)
|
45
|
-
data.dig('usageMetadata', 'candidatesTokenCount')
|
45
|
+
candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
|
46
|
+
thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
|
47
|
+
total = candidates + thoughts
|
48
|
+
total.positive? ? total : nil
|
46
49
|
end
|
47
50
|
|
48
51
|
def parse_streaming_error(data)
|
@@ -198,11 +198,11 @@ module RubyLLM
|
|
198
198
|
.gsub(/(\d{4}) (\d{2}) (\d{2})/, '\1\2\3')
|
199
199
|
.gsub(/^(?:Gpt|Chatgpt|Tts|Dall E) /) { |m| special_prefix_format(m.strip) }
|
200
200
|
.gsub(/^O([13]) /, 'O\1-')
|
201
|
-
.gsub(/^O[13] Mini/, '\0'.
|
201
|
+
.gsub(/^O[13] Mini/, '\0'.tr(' ', '-'))
|
202
202
|
.gsub(/\d\.\d /, '\0'.sub(' ', '-'))
|
203
203
|
.gsub(/4o (?=Mini|Preview|Turbo|Audio|Realtime|Transcribe|Tts)/, '4o-')
|
204
204
|
.gsub(/\bHd\b/, 'HD')
|
205
|
-
.gsub(/(?:Omni|Text) Moderation/, '\0'.
|
205
|
+
.gsub(/(?:Omni|Text) Moderation/, '\0'.tr(' ', '-'))
|
206
206
|
.gsub('Text Embedding', 'text-embedding-')
|
207
207
|
end
|
208
208
|
|
@@ -8,7 +8,7 @@ module RubyLLM
|
|
8
8
|
attr_reader :content, :model_id, :tool_calls
|
9
9
|
|
10
10
|
def initialize
|
11
|
-
@content =
|
11
|
+
@content = +''
|
12
12
|
@tool_calls = {}
|
13
13
|
@input_tokens = 0
|
14
14
|
@output_tokens = 0
|
@@ -66,7 +66,7 @@ module RubyLLM
|
|
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
|
69
|
-
tool_call_arguments = tool_call.arguments.empty? ?
|
69
|
+
tool_call_arguments = tool_call.arguments.empty? ? +'' : tool_call.arguments
|
70
70
|
@tool_calls[tool_call.id] = ToolCall.new(
|
71
71
|
id: tool_call_id,
|
72
72
|
name: tool_call.name,
|
data/lib/ruby_llm/streaming.rb
CHANGED
data/lib/ruby_llm/version.rb
CHANGED
data/lib/tasks/aliases.rake
CHANGED
@@ -65,7 +65,7 @@ namespace :aliases do # rubocop:disable Metrics/BlockLength
|
|
65
65
|
|
66
66
|
base_name = Regexp.last_match(1)
|
67
67
|
# Normalize to Anthropic naming convention
|
68
|
-
anthropic_name = base_name.
|
68
|
+
anthropic_name = base_name.tr('.', '-')
|
69
69
|
|
70
70
|
# Skip if we already have an alias for this
|
71
71
|
next if aliases[anthropic_name]
|
@@ -91,7 +91,7 @@ namespace :aliases do # rubocop:disable Metrics/BlockLength
|
|
91
91
|
# OpenRouter uses "google/" prefix and sometimes different naming
|
92
92
|
openrouter_variants = [
|
93
93
|
"google/#{model}",
|
94
|
-
"google/#{model.gsub('gemini-', 'gemini-').
|
94
|
+
"google/#{model.gsub('gemini-', 'gemini-').tr('.', '-')}",
|
95
95
|
"google/#{model.gsub('gemini-', 'gemini-')}"
|
96
96
|
]
|
97
97
|
|
data/lib/tasks/models_docs.rake
CHANGED
@@ -86,7 +86,7 @@ def generate_models_markdown
|
|
86
86
|
end
|
87
87
|
|
88
88
|
def generate_provider_sections
|
89
|
-
RubyLLM::Provider.providers.
|
89
|
+
RubyLLM::Provider.providers.filter_map do |provider, provider_class|
|
90
90
|
models = RubyLLM.models.by_provider(provider)
|
91
91
|
next if models.none?
|
92
92
|
|
@@ -95,7 +95,7 @@ def generate_provider_sections
|
|
95
95
|
|
96
96
|
#{models_table(models)}
|
97
97
|
PROVIDER
|
98
|
-
end.
|
98
|
+
end.join("\n\n")
|
99
99
|
end
|
100
100
|
|
101
101
|
def generate_capability_sections
|
@@ -107,7 +107,7 @@ def generate_capability_sections
|
|
107
107
|
'Batch Processing' => RubyLLM.models.select { |m| m.capabilities.include?('batch') }
|
108
108
|
}
|
109
109
|
|
110
|
-
capabilities.
|
110
|
+
capabilities.filter_map do |capability, models|
|
111
111
|
next if models.none?
|
112
112
|
|
113
113
|
<<~CAPABILITY
|
@@ -115,7 +115,7 @@ def generate_capability_sections
|
|
115
115
|
|
116
116
|
#{models_table(models)}
|
117
117
|
CAPABILITY
|
118
|
-
end.
|
118
|
+
end.join("\n\n")
|
119
119
|
end
|
120
120
|
|
121
121
|
def generate_modality_sections # rubocop:disable Metrics/PerceivedComplexity
|