ruby_llm 1.14.0 → 1.15.0
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 +7 -5
- data/lib/generators/ruby_llm/generator_helpers.rb +8 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +1 -1
- data/lib/ruby_llm/active_record/acts_as.rb +3 -0
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +52 -25
- data/lib/ruby_llm/active_record/chat_methods.rb +47 -23
- data/lib/ruby_llm/active_record/message_methods.rb +19 -14
- data/lib/ruby_llm/active_record/model_methods.rb +7 -9
- data/lib/ruby_llm/active_record/payload_helpers.rb +29 -0
- data/lib/ruby_llm/active_record/tool_call_methods.rb +5 -15
- data/lib/ruby_llm/agent.rb +3 -2
- data/lib/ruby_llm/aliases.json +53 -14
- data/lib/ruby_llm/attachment.rb +11 -27
- data/lib/ruby_llm/chat.rb +62 -21
- data/lib/ruby_llm/cost.rb +224 -0
- data/lib/ruby_llm/image.rb +37 -4
- data/lib/ruby_llm/message.rb +20 -0
- data/lib/ruby_llm/model/info.rb +17 -0
- data/lib/ruby_llm/model/pricing_category.rb +13 -2
- data/lib/ruby_llm/models.json +26511 -24930
- data/lib/ruby_llm/models.rb +2 -1
- data/lib/ruby_llm/models_schema.json +3 -0
- data/lib/ruby_llm/provider.rb +10 -3
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -133
- data/lib/ruby_llm/providers/anthropic/models.rb +2 -8
- data/lib/ruby_llm/providers/anthropic/tools.rb +4 -1
- data/lib/ruby_llm/providers/bedrock/chat.rb +24 -13
- data/lib/ruby_llm/providers/bedrock/streaming.rb +4 -1
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +1 -119
- data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -215
- data/lib/ruby_llm/providers/gemini/chat.rb +8 -1
- data/lib/ruby_llm/providers/gemini/images.rb +2 -2
- data/lib/ruby_llm/providers/gemini/models.rb +2 -4
- data/lib/ruby_llm/providers/gemini/streaming.rb +4 -1
- data/lib/ruby_llm/providers/gemini/tools.rb +3 -1
- data/lib/ruby_llm/providers/mistral/capabilities.rb +6 -1
- data/lib/ruby_llm/providers/mistral/chat.rb +55 -4
- data/lib/ruby_llm/providers/openai/capabilities.rb +157 -195
- data/lib/ruby_llm/providers/openai/chat.rb +45 -6
- data/lib/ruby_llm/providers/openai/images.rb +58 -6
- data/lib/ruby_llm/providers/openai/models.rb +2 -4
- data/lib/ruby_llm/providers/openai/streaming.rb +5 -6
- data/lib/ruby_llm/providers/openrouter/chat.rb +30 -6
- data/lib/ruby_llm/providers/openrouter/images.rb +2 -2
- data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
- data/lib/ruby_llm/providers/openrouter/streaming.rb +5 -6
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
- data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
- data/lib/ruby_llm/railtie.rb +6 -0
- data/lib/ruby_llm/tokens.rb +8 -0
- data/lib/ruby_llm/tool.rb +24 -7
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +2 -4
- data/lib/tasks/models.rake +13 -12
- metadata +21 -5
|
@@ -60,8 +60,7 @@ module RubyLLM
|
|
|
60
60
|
return unless message_data
|
|
61
61
|
|
|
62
62
|
usage = data['usage'] || {}
|
|
63
|
-
|
|
64
|
-
thinking_tokens = usage.dig('completion_tokens_details', 'reasoning_tokens')
|
|
63
|
+
thinking_tokens = thinking_tokens(usage)
|
|
65
64
|
thinking_text = extract_thinking_text(message_data)
|
|
66
65
|
thinking_signature = extract_thinking_signature(message_data)
|
|
67
66
|
|
|
@@ -70,16 +69,41 @@ module RubyLLM
|
|
|
70
69
|
content: message_data['content'],
|
|
71
70
|
thinking: Thinking.build(text: thinking_text, signature: thinking_signature),
|
|
72
71
|
tool_calls: OpenAI::Tools.parse_tool_calls(message_data['tool_calls']),
|
|
73
|
-
input_tokens: usage
|
|
74
|
-
output_tokens: usage
|
|
75
|
-
cached_tokens:
|
|
76
|
-
cache_creation_tokens:
|
|
72
|
+
input_tokens: input_tokens(usage),
|
|
73
|
+
output_tokens: output_tokens(usage),
|
|
74
|
+
cached_tokens: cache_read_tokens(usage),
|
|
75
|
+
cache_creation_tokens: cache_write_tokens(usage),
|
|
77
76
|
thinking_tokens: thinking_tokens,
|
|
78
77
|
model_id: data['model'],
|
|
79
78
|
raw: response
|
|
80
79
|
)
|
|
81
80
|
end
|
|
82
81
|
|
|
82
|
+
def input_tokens(usage)
|
|
83
|
+
return usage['prompt_cache_miss_tokens'] if usage['prompt_cache_miss_tokens']
|
|
84
|
+
|
|
85
|
+
prompt_tokens = usage['prompt_tokens']
|
|
86
|
+
return unless prompt_tokens
|
|
87
|
+
|
|
88
|
+
[prompt_tokens.to_i - cache_read_tokens(usage).to_i - cache_write_tokens(usage).to_i, 0].max
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def output_tokens(usage)
|
|
92
|
+
OpenAI::Chat.output_tokens(usage)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def cache_read_tokens(usage)
|
|
96
|
+
usage.dig('prompt_tokens_details', 'cached_tokens') || usage['prompt_cache_hit_tokens']
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def cache_write_tokens(usage)
|
|
100
|
+
usage.dig('prompt_tokens_details', 'cache_write_tokens') || 0
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def thinking_tokens(usage)
|
|
104
|
+
OpenAI::Chat.thinking_tokens(usage)
|
|
105
|
+
end
|
|
106
|
+
|
|
83
107
|
def format_messages(messages)
|
|
84
108
|
messages.map do |msg|
|
|
85
109
|
{
|
|
@@ -9,11 +9,11 @@ module RubyLLM
|
|
|
9
9
|
module Images
|
|
10
10
|
module_function
|
|
11
11
|
|
|
12
|
-
def images_url
|
|
12
|
+
def images_url(with: nil, mask: nil) # rubocop:disable Lint/UnusedMethodArgument
|
|
13
13
|
'chat/completions'
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def render_image_payload(prompt, model:, size:)
|
|
16
|
+
def render_image_payload(prompt, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists
|
|
17
17
|
RubyLLM.logger.debug { "Ignoring size #{size}. OpenRouter image generation does not support size parameter." }
|
|
18
18
|
{
|
|
19
19
|
model: model,
|
|
@@ -23,7 +23,7 @@ module RubyLLM
|
|
|
23
23
|
pricing_types = {
|
|
24
24
|
prompt: :input_per_million,
|
|
25
25
|
completion: :output_per_million,
|
|
26
|
-
input_cache_read: :
|
|
26
|
+
input_cache_read: :cache_read_input_per_million,
|
|
27
27
|
internal_reasoning: :reasoning_output_per_million
|
|
28
28
|
}
|
|
29
29
|
|
|
@@ -13,7 +13,6 @@ module RubyLLM
|
|
|
13
13
|
|
|
14
14
|
def build_chunk(data)
|
|
15
15
|
usage = data['usage'] || {}
|
|
16
|
-
cached_tokens = usage.dig('prompt_tokens_details', 'cached_tokens')
|
|
17
16
|
delta = data.dig('choices', 0, 'delta') || {}
|
|
18
17
|
|
|
19
18
|
Chunk.new(
|
|
@@ -25,11 +24,11 @@ module RubyLLM
|
|
|
25
24
|
signature: extract_thinking_signature(delta)
|
|
26
25
|
),
|
|
27
26
|
tool_calls: OpenAI::Tools.parse_tool_calls(delta['tool_calls'], parse_arguments: false),
|
|
28
|
-
input_tokens: usage
|
|
29
|
-
output_tokens: usage
|
|
30
|
-
cached_tokens:
|
|
31
|
-
cache_creation_tokens:
|
|
32
|
-
thinking_tokens:
|
|
27
|
+
input_tokens: OpenRouter::Chat.input_tokens(usage),
|
|
28
|
+
output_tokens: OpenRouter::Chat.output_tokens(usage),
|
|
29
|
+
cached_tokens: OpenRouter::Chat.cache_read_tokens(usage),
|
|
30
|
+
cache_creation_tokens: OpenRouter::Chat.cache_write_tokens(usage),
|
|
31
|
+
thinking_tokens: OpenRouter::Chat.thinking_tokens(usage)
|
|
33
32
|
)
|
|
34
33
|
end
|
|
35
34
|
|
|
@@ -3,63 +3,55 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class Perplexity
|
|
6
|
-
#
|
|
6
|
+
# Provider-level capability checks and narrow registry fallbacks.
|
|
7
7
|
module Capabilities
|
|
8
8
|
module_function
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
10
|
+
PRICES = {
|
|
11
|
+
sonar: { input: 1.0, output: 1.0 },
|
|
12
|
+
sonar_pro: { input: 3.0, output: 15.0 },
|
|
13
|
+
sonar_reasoning: { input: 1.0, output: 5.0 },
|
|
14
|
+
sonar_reasoning_pro: { input: 2.0, output: 8.0 },
|
|
15
|
+
sonar_deep_research: {
|
|
16
|
+
input: 2.0,
|
|
17
|
+
output: 8.0,
|
|
18
|
+
reasoning_output: 3.0
|
|
19
|
+
}
|
|
20
|
+
}.freeze
|
|
16
21
|
|
|
17
|
-
def
|
|
18
|
-
|
|
19
|
-
when /sonar-(?:pro|reasoning-pro)/ then 8_192
|
|
20
|
-
else 4_096
|
|
21
|
-
end
|
|
22
|
+
def supports_tool_choice?(_model_id)
|
|
23
|
+
false
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
+
def supports_tool_parallel_control?(_model_id)
|
|
27
|
+
false
|
|
26
28
|
end
|
|
27
29
|
|
|
28
|
-
def
|
|
29
|
-
|
|
30
|
+
def context_window_for(model_id)
|
|
31
|
+
model_id.match?(/sonar-pro/) ? 200_000 : 128_000
|
|
30
32
|
end
|
|
31
33
|
|
|
32
|
-
def
|
|
33
|
-
|
|
34
|
-
when /sonar-reasoning-pro/, /sonar-reasoning/, /sonar-pro/, /sonar/ then true
|
|
35
|
-
else false
|
|
36
|
-
end
|
|
34
|
+
def max_tokens_for(model_id)
|
|
35
|
+
model_id.match?(/sonar-(?:pro|reasoning-pro)/) ? 8_192 : 4_096
|
|
37
36
|
end
|
|
38
37
|
|
|
39
|
-
def
|
|
40
|
-
|
|
38
|
+
def critical_capabilities_for(model_id)
|
|
39
|
+
capabilities = []
|
|
40
|
+
capabilities << 'vision' if model_id.match?(/sonar(?:-pro|-reasoning(?:-pro)?)?$/)
|
|
41
|
+
capabilities << 'reasoning' if model_id.match?(/reasoning|deep-research/)
|
|
42
|
+
capabilities
|
|
41
43
|
end
|
|
42
44
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
end
|
|
45
|
+
def pricing_for(model_id)
|
|
46
|
+
prices = PRICES.fetch(model_family(model_id), { input: 1.0, output: 1.0 })
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
when 'sonar-reasoning-pro' then 'Sonar Reasoning Pro'
|
|
53
|
-
when 'sonar-deep-research' then 'Sonar Deep Research'
|
|
54
|
-
else
|
|
55
|
-
model_id.split('-')
|
|
56
|
-
.map(&:capitalize)
|
|
57
|
-
.join(' ')
|
|
58
|
-
end
|
|
59
|
-
end
|
|
48
|
+
standard = {
|
|
49
|
+
input_per_million: prices[:input],
|
|
50
|
+
output_per_million: prices[:output]
|
|
51
|
+
}
|
|
52
|
+
standard[:reasoning_output_per_million] = prices[:reasoning_output] if prices[:reasoning_output]
|
|
60
53
|
|
|
61
|
-
|
|
62
|
-
'chat'
|
|
54
|
+
{ text_tokens: { standard: standard } }
|
|
63
55
|
end
|
|
64
56
|
|
|
65
57
|
def model_family(model_id)
|
|
@@ -73,64 +65,7 @@ module RubyLLM
|
|
|
73
65
|
end
|
|
74
66
|
end
|
|
75
67
|
|
|
76
|
-
|
|
77
|
-
{
|
|
78
|
-
input: ['text'],
|
|
79
|
-
output: ['text']
|
|
80
|
-
}
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
def capabilities_for(model_id)
|
|
84
|
-
capabilities = %w[streaming json_mode]
|
|
85
|
-
capabilities << 'vision' if supports_vision?(model_id)
|
|
86
|
-
capabilities
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def pricing_for(model_id)
|
|
90
|
-
family = model_family(model_id)
|
|
91
|
-
prices = PRICES.fetch(family, { input: 1.0, output: 1.0 })
|
|
92
|
-
|
|
93
|
-
standard_pricing = {
|
|
94
|
-
input_per_million: prices[:input],
|
|
95
|
-
output_per_million: prices[:output]
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
standard_pricing[:citation_per_million] = prices[:citation] if prices[:citation]
|
|
99
|
-
standard_pricing[:reasoning_per_million] = prices[:reasoning] if prices[:reasoning]
|
|
100
|
-
standard_pricing[:search_per_thousand] = prices[:search_queries] if prices[:search_queries]
|
|
101
|
-
|
|
102
|
-
{
|
|
103
|
-
text_tokens: {
|
|
104
|
-
standard: standard_pricing
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
PRICES = {
|
|
110
|
-
sonar: {
|
|
111
|
-
input: 1.0,
|
|
112
|
-
output: 1.0
|
|
113
|
-
},
|
|
114
|
-
sonar_pro: {
|
|
115
|
-
input: 3.0,
|
|
116
|
-
output: 15.0
|
|
117
|
-
},
|
|
118
|
-
sonar_reasoning: {
|
|
119
|
-
input: 1.0,
|
|
120
|
-
output: 5.0
|
|
121
|
-
},
|
|
122
|
-
sonar_reasoning_pro: {
|
|
123
|
-
input: 2.0,
|
|
124
|
-
output: 8.0
|
|
125
|
-
},
|
|
126
|
-
sonar_deep_research: {
|
|
127
|
-
input: 2.0,
|
|
128
|
-
output: 8.0,
|
|
129
|
-
citation: 2.0,
|
|
130
|
-
reasoning: 3.0,
|
|
131
|
-
search_queries: 5.0
|
|
132
|
-
}
|
|
133
|
-
}.freeze
|
|
68
|
+
module_function :context_window_for, :max_tokens_for, :critical_capabilities_for, :pricing_for, :model_family
|
|
134
69
|
end
|
|
135
70
|
end
|
|
136
71
|
end
|
|
@@ -5,33 +5,31 @@ module RubyLLM
|
|
|
5
5
|
class Perplexity
|
|
6
6
|
# Models methods of the Perplexity API integration
|
|
7
7
|
module Models
|
|
8
|
+
MODEL_IDS = %w[
|
|
9
|
+
sonar
|
|
10
|
+
sonar-pro
|
|
11
|
+
sonar-reasoning
|
|
12
|
+
sonar-reasoning-pro
|
|
13
|
+
sonar-deep-research
|
|
14
|
+
].freeze
|
|
15
|
+
|
|
8
16
|
def list_models(**)
|
|
9
17
|
slug = 'perplexity'
|
|
10
|
-
|
|
11
|
-
parse_list_models_response(nil, slug, capabilities)
|
|
18
|
+
parse_list_models_response(nil, slug, Perplexity::Capabilities)
|
|
12
19
|
end
|
|
13
20
|
|
|
14
21
|
def parse_list_models_response(_response, slug, capabilities)
|
|
15
|
-
|
|
16
|
-
create_model_info('sonar', slug, capabilities),
|
|
17
|
-
create_model_info('sonar-pro', slug, capabilities),
|
|
18
|
-
create_model_info('sonar-reasoning', slug, capabilities),
|
|
19
|
-
create_model_info('sonar-reasoning-pro', slug, capabilities),
|
|
20
|
-
create_model_info('sonar-deep-research', slug, capabilities)
|
|
21
|
-
]
|
|
22
|
+
MODEL_IDS.map { |id| create_model_info(id, slug, capabilities) }
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
def create_model_info(id, slug, capabilities)
|
|
25
26
|
Model::Info.new(
|
|
26
27
|
id: id,
|
|
27
|
-
name:
|
|
28
|
+
name: id,
|
|
28
29
|
provider: slug,
|
|
29
|
-
family: capabilities.model_family(id).to_s,
|
|
30
|
-
created_at: Time.now,
|
|
31
30
|
context_window: capabilities.context_window_for(id),
|
|
32
31
|
max_output_tokens: capabilities.max_tokens_for(id),
|
|
33
|
-
|
|
34
|
-
capabilities: capabilities.capabilities_for(id),
|
|
32
|
+
capabilities: capabilities.critical_capabilities_for(id),
|
|
35
33
|
pricing: capabilities.pricing_for(id),
|
|
36
34
|
metadata: {}
|
|
37
35
|
)
|
data/lib/ruby_llm/railtie.rb
CHANGED
|
@@ -12,6 +12,12 @@ if defined?(Rails::Railtie)
|
|
|
12
12
|
|
|
13
13
|
initializer 'ruby_llm.active_record' do
|
|
14
14
|
ActiveSupport.on_load :active_record do
|
|
15
|
+
require 'ruby_llm/active_record/payload_helpers'
|
|
16
|
+
require 'ruby_llm/active_record/chat_methods'
|
|
17
|
+
require 'ruby_llm/active_record/message_methods'
|
|
18
|
+
require 'ruby_llm/active_record/model_methods'
|
|
19
|
+
require 'ruby_llm/active_record/tool_call_methods'
|
|
20
|
+
|
|
15
21
|
if RubyLLM.config.use_new_acts_as
|
|
16
22
|
require 'ruby_llm/active_record/acts_as'
|
|
17
23
|
::ActiveRecord::Base.include RubyLLM::ActiveRecord::ActsAs
|
data/lib/ruby_llm/tokens.rb
CHANGED
data/lib/ruby_llm/tool.rb
CHANGED
|
@@ -7,10 +7,10 @@ module RubyLLM
|
|
|
7
7
|
class Parameter
|
|
8
8
|
attr_reader :name, :type, :description, :required
|
|
9
9
|
|
|
10
|
-
def initialize(name, type: 'string', desc: nil, required: true)
|
|
10
|
+
def initialize(name, type: 'string', desc: nil, description: nil, required: true)
|
|
11
11
|
@name = name
|
|
12
12
|
@type = type
|
|
13
|
-
@description = desc
|
|
13
|
+
@description = desc || description
|
|
14
14
|
@required = required
|
|
15
15
|
end
|
|
16
16
|
end
|
|
@@ -30,6 +30,8 @@ module RubyLLM
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
POSITIONAL_PARAMETER_KINDS = %i[req opt rest].freeze
|
|
34
|
+
|
|
33
35
|
class << self
|
|
34
36
|
attr_reader :params_schema_definition
|
|
35
37
|
|
|
@@ -38,6 +40,7 @@ module RubyLLM
|
|
|
38
40
|
|
|
39
41
|
@description = text
|
|
40
42
|
end
|
|
43
|
+
alias desc description
|
|
41
44
|
|
|
42
45
|
def param(name, **options)
|
|
43
46
|
parameters[name] = Parameter.new(name, **options)
|
|
@@ -94,6 +97,8 @@ module RubyLLM
|
|
|
94
97
|
definition.json_schema
|
|
95
98
|
elsif parameters.any?
|
|
96
99
|
SchemaDefinition.from_parameters(parameters)&.json_schema
|
|
100
|
+
else
|
|
101
|
+
SchemaDefinition.from_parameters(inferred_parameters, allow_empty: true)&.json_schema
|
|
97
102
|
end
|
|
98
103
|
end
|
|
99
104
|
end
|
|
@@ -127,9 +132,10 @@ module RubyLLM
|
|
|
127
132
|
end
|
|
128
133
|
|
|
129
134
|
def validate_keyword_arguments(arguments)
|
|
130
|
-
required_keywords, optional_keywords, accepts_extra_keywords =
|
|
135
|
+
required_keywords, optional_keywords, accepts_extra_keywords, accepts_positional_arguments =
|
|
136
|
+
execute_keyword_signature
|
|
131
137
|
|
|
132
|
-
return nil if required_keywords.empty? && optional_keywords.empty?
|
|
138
|
+
return nil if required_keywords.empty? && optional_keywords.empty? && accepts_positional_arguments
|
|
133
139
|
|
|
134
140
|
argument_keys = arguments.keys
|
|
135
141
|
missing_keyword = first_missing_keyword(required_keywords, argument_keys)
|
|
@@ -148,8 +154,11 @@ module RubyLLM
|
|
|
148
154
|
required_keywords = keyword_signature.filter_map { |kind, name| name if kind == :keyreq }
|
|
149
155
|
optional_keywords = keyword_signature.filter_map { |kind, name| name if kind == :key }
|
|
150
156
|
accepts_extra_keywords = keyword_signature.any? { |kind, _| kind == :keyrest }
|
|
157
|
+
accepts_positional_arguments = keyword_signature.any? do |kind, _|
|
|
158
|
+
POSITIONAL_PARAMETER_KINDS.include?(kind)
|
|
159
|
+
end
|
|
151
160
|
|
|
152
|
-
[required_keywords, optional_keywords, accepts_extra_keywords]
|
|
161
|
+
[required_keywords, optional_keywords, accepts_extra_keywords, accepts_positional_arguments]
|
|
153
162
|
end
|
|
154
163
|
|
|
155
164
|
def first_missing_keyword(required_keywords, argument_keys)
|
|
@@ -160,11 +169,19 @@ module RubyLLM
|
|
|
160
169
|
(argument_keys - allowed_keywords).first
|
|
161
170
|
end
|
|
162
171
|
|
|
172
|
+
def inferred_parameters
|
|
173
|
+
required_keywords, optional_keywords, = execute_keyword_signature
|
|
174
|
+
|
|
175
|
+
(required_keywords + optional_keywords).to_h do |name|
|
|
176
|
+
[name, Parameter.new(name, required: required_keywords.include?(name))]
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
163
180
|
# Wraps schema handling for tool parameters, supporting JSON Schema hashes,
|
|
164
181
|
# RubyLLM::Schema instances/classes, and DSL blocks.
|
|
165
182
|
class SchemaDefinition
|
|
166
|
-
def self.from_parameters(parameters)
|
|
167
|
-
return nil if parameters.nil? || parameters.empty?
|
|
183
|
+
def self.from_parameters(parameters, allow_empty: false)
|
|
184
|
+
return nil if parameters.nil? || (parameters.empty? && !allow_empty)
|
|
168
185
|
|
|
169
186
|
properties = parameters.to_h do |name, param|
|
|
170
187
|
schema = {
|
data/lib/ruby_llm/version.rb
CHANGED
data/lib/ruby_llm.rb
CHANGED
|
@@ -33,6 +33,7 @@ loader.inflector.inflect(
|
|
|
33
33
|
)
|
|
34
34
|
loader.ignore("#{__dir__}/tasks")
|
|
35
35
|
loader.ignore("#{__dir__}/generators")
|
|
36
|
+
loader.ignore("#{__dir__}/ruby_llm/active_record")
|
|
36
37
|
loader.ignore("#{__dir__}/ruby_llm/railtie.rb")
|
|
37
38
|
loader.setup
|
|
38
39
|
|
|
@@ -107,7 +108,4 @@ RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity
|
|
|
107
108
|
RubyLLM::Provider.register :vertexai, RubyLLM::Providers::VertexAI
|
|
108
109
|
RubyLLM::Provider.register :xai, RubyLLM::Providers::XAI
|
|
109
110
|
|
|
110
|
-
if defined?(Rails::Railtie)
|
|
111
|
-
require 'ruby_llm/railtie'
|
|
112
|
-
require 'ruby_llm/active_record/acts_as'
|
|
113
|
-
end
|
|
111
|
+
require 'ruby_llm/railtie' if defined?(Rails::Railtie)
|
data/lib/tasks/models.rake
CHANGED
|
@@ -329,22 +329,23 @@ end
|
|
|
329
329
|
|
|
330
330
|
def standard_pricing_display(model)
|
|
331
331
|
pricing_data = model.pricing.to_h[:text_tokens]&.dig(:standard) || {}
|
|
332
|
+
parts = [
|
|
333
|
+
pricing_part(pricing_data, :input_per_million, 'In'),
|
|
334
|
+
pricing_part(pricing_data, :output_per_million, 'Out'),
|
|
335
|
+
pricing_part(pricing_data, %i[cache_read_input_per_million cached_input_per_million], 'Cache Read'),
|
|
336
|
+
pricing_part(pricing_data, %i[cache_write_input_per_million cache_creation_input_per_million], 'Cache Write')
|
|
337
|
+
].compact
|
|
332
338
|
|
|
333
|
-
if
|
|
334
|
-
parts = []
|
|
339
|
+
return parts.join(', ') if parts.any?
|
|
335
340
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
parts << "Out: $#{format('%.2f', pricing_data[:output_per_million])}" if pricing_data[:output_per_million]
|
|
339
|
-
|
|
340
|
-
if pricing_data[:cached_input_per_million]
|
|
341
|
-
parts << "Cache: $#{format('%.2f', pricing_data[:cached_input_per_million])}"
|
|
342
|
-
end
|
|
341
|
+
'-'
|
|
342
|
+
end
|
|
343
343
|
|
|
344
|
-
|
|
345
|
-
|
|
344
|
+
def pricing_part(pricing_data, key, label)
|
|
345
|
+
key = Array(key).find { |candidate| pricing_data[candidate] }
|
|
346
|
+
return unless key
|
|
346
347
|
|
|
347
|
-
'
|
|
348
|
+
"#{label}: $#{format('%.2f', pricing_data[key])}"
|
|
348
349
|
end
|
|
349
350
|
|
|
350
351
|
def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
|
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.15.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Carmine Paolino
|
|
@@ -228,6 +228,7 @@ files:
|
|
|
228
228
|
- lib/ruby_llm/active_record/chat_methods.rb
|
|
229
229
|
- lib/ruby_llm/active_record/message_methods.rb
|
|
230
230
|
- lib/ruby_llm/active_record/model_methods.rb
|
|
231
|
+
- lib/ruby_llm/active_record/payload_helpers.rb
|
|
231
232
|
- lib/ruby_llm/active_record/tool_call_methods.rb
|
|
232
233
|
- lib/ruby_llm/agent.rb
|
|
233
234
|
- lib/ruby_llm/aliases.json
|
|
@@ -239,6 +240,7 @@ files:
|
|
|
239
240
|
- lib/ruby_llm/connection.rb
|
|
240
241
|
- lib/ruby_llm/content.rb
|
|
241
242
|
- lib/ruby_llm/context.rb
|
|
243
|
+
- lib/ruby_llm/cost.rb
|
|
242
244
|
- lib/ruby_llm/embedding.rb
|
|
243
245
|
- lib/ruby_llm/error.rb
|
|
244
246
|
- lib/ruby_llm/image.rb
|
|
@@ -353,14 +355,28 @@ licenses:
|
|
|
353
355
|
metadata:
|
|
354
356
|
homepage_uri: https://rubyllm.com
|
|
355
357
|
source_code_uri: https://github.com/crmne/ruby_llm
|
|
356
|
-
changelog_uri: https://github.com/crmne/ruby_llm/
|
|
358
|
+
changelog_uri: https://github.com/crmne/ruby_llm/releases
|
|
357
359
|
documentation_uri: https://rubyllm.com
|
|
358
360
|
bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
|
|
359
361
|
funding_uri: https://github.com/sponsors/crmne
|
|
360
362
|
rubygems_mfa_required: 'true'
|
|
361
363
|
post_install_message: |
|
|
362
|
-
|
|
363
|
-
|
|
364
|
+
RubyLLM 1.15 upgrade note:
|
|
365
|
+
|
|
366
|
+
Token accounting is now normalized across providers. `input_tokens` means
|
|
367
|
+
standard input tokens; prompt cache reads and writes are exposed separately
|
|
368
|
+
as `cache_read_tokens` and `cache_write_tokens`.
|
|
369
|
+
|
|
370
|
+
Need request-side input activity?
|
|
371
|
+
input_tokens + cache_read_tokens + cache_write_tokens
|
|
372
|
+
|
|
373
|
+
New cost helpers:
|
|
374
|
+
response.cost.total
|
|
375
|
+
chat.cost.total
|
|
376
|
+
agent.cost.total
|
|
377
|
+
|
|
378
|
+
Upgrading from RubyLLM < 1.15? Read the full upgrade guide:
|
|
379
|
+
https://rubyllm.com/upgrading/
|
|
364
380
|
rdoc_options: []
|
|
365
381
|
require_paths:
|
|
366
382
|
- lib
|
|
@@ -375,7 +391,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
375
391
|
- !ruby/object:Gem::Version
|
|
376
392
|
version: '0'
|
|
377
393
|
requirements: []
|
|
378
|
-
rubygems_version: 4.0.
|
|
394
|
+
rubygems_version: 4.0.6
|
|
379
395
|
specification_version: 4
|
|
380
396
|
summary: One beautiful Ruby API for GPT, Claude, Gemini, and more.
|
|
381
397
|
test_files: []
|