ruby_llm 1.2.0 → 1.3.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 +80 -133
- data/lib/ruby_llm/active_record/acts_as.rb +144 -47
- data/lib/ruby_llm/aliases.json +187 -17
- data/lib/ruby_llm/attachment.rb +164 -0
- data/lib/ruby_llm/chat.rb +31 -20
- data/lib/ruby_llm/configuration.rb +34 -1
- data/lib/ruby_llm/connection.rb +121 -0
- data/lib/ruby_llm/content.rb +27 -79
- data/lib/ruby_llm/context.rb +30 -0
- data/lib/ruby_llm/embedding.rb +13 -5
- data/lib/ruby_llm/error.rb +2 -1
- data/lib/ruby_llm/image.rb +15 -8
- data/lib/ruby_llm/message.rb +14 -6
- data/lib/ruby_llm/mime_type.rb +67 -0
- data/lib/ruby_llm/model/info.rb +101 -0
- data/lib/ruby_llm/model/modalities.rb +22 -0
- data/lib/ruby_llm/model/pricing.rb +51 -0
- data/lib/ruby_llm/model/pricing_category.rb +48 -0
- data/lib/ruby_llm/model/pricing_tier.rb +34 -0
- data/lib/ruby_llm/model.rb +7 -0
- data/lib/ruby_llm/models.json +26279 -2362
- data/lib/ruby_llm/models.rb +95 -14
- data/lib/ruby_llm/provider.rb +48 -90
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +76 -13
- data/lib/ruby_llm/providers/anthropic/chat.rb +7 -14
- data/lib/ruby_llm/providers/anthropic/media.rb +49 -28
- data/lib/ruby_llm/providers/anthropic/models.rb +16 -16
- data/lib/ruby_llm/providers/anthropic/tools.rb +2 -2
- data/lib/ruby_llm/providers/anthropic.rb +3 -3
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +61 -2
- data/lib/ruby_llm/providers/bedrock/chat.rb +30 -73
- data/lib/ruby_llm/providers/bedrock/media.rb +59 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +50 -58
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +16 -0
- data/lib/ruby_llm/providers/bedrock.rb +14 -25
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +35 -2
- data/lib/ruby_llm/providers/deepseek.rb +3 -3
- data/lib/ruby_llm/providers/gemini/capabilities.rb +84 -3
- data/lib/ruby_llm/providers/gemini/chat.rb +8 -37
- data/lib/ruby_llm/providers/gemini/embeddings.rb +18 -34
- data/lib/ruby_llm/providers/gemini/images.rb +4 -3
- data/lib/ruby_llm/providers/gemini/media.rb +28 -111
- data/lib/ruby_llm/providers/gemini/models.rb +17 -23
- data/lib/ruby_llm/providers/gemini/tools.rb +1 -1
- data/lib/ruby_llm/providers/gemini.rb +3 -3
- data/lib/ruby_llm/providers/ollama/chat.rb +28 -0
- data/lib/ruby_llm/providers/ollama/media.rb +48 -0
- data/lib/ruby_llm/providers/ollama.rb +34 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +78 -3
- data/lib/ruby_llm/providers/openai/chat.rb +6 -4
- data/lib/ruby_llm/providers/openai/embeddings.rb +8 -12
- data/lib/ruby_llm/providers/openai/images.rb +3 -2
- data/lib/ruby_llm/providers/openai/media.rb +48 -21
- data/lib/ruby_llm/providers/openai/models.rb +17 -18
- data/lib/ruby_llm/providers/openai/tools.rb +9 -5
- data/lib/ruby_llm/providers/openai.rb +7 -5
- data/lib/ruby_llm/providers/openrouter/models.rb +88 -0
- data/lib/ruby_llm/providers/openrouter.rb +31 -0
- data/lib/ruby_llm/stream_accumulator.rb +4 -4
- data/lib/ruby_llm/streaming.rb +48 -13
- data/lib/ruby_llm/utils.rb +27 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +15 -5
- data/lib/tasks/aliases.rake +235 -0
- data/lib/tasks/models_docs.rake +164 -121
- data/lib/tasks/models_update.rake +79 -0
- data/lib/tasks/release.rake +32 -0
- data/lib/tasks/vcr.rake +4 -2
- metadata +56 -32
- data/lib/ruby_llm/model_info.rb +0 -56
- data/lib/tasks/browser_helper.rb +0 -97
- data/lib/tasks/capability_generator.rb +0 -123
- data/lib/tasks/capability_scraper.rb +0 -224
- data/lib/tasks/cli_helper.rb +0 -22
- data/lib/tasks/code_validator.rb +0 -29
- data/lib/tasks/model_updater.rb +0 -66
- data/lib/tasks/models.rake +0 -43
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'dotenv/load'
|
4
|
+
require 'ruby_llm'
|
5
|
+
|
6
|
+
task default: ['models:update']
|
7
|
+
|
8
|
+
namespace :models do
|
9
|
+
desc 'Update available models from providers (API keys needed)'
|
10
|
+
task :update do
|
11
|
+
puts 'Configuring RubyLLM...'
|
12
|
+
configure_from_env
|
13
|
+
|
14
|
+
refresh_models
|
15
|
+
display_model_stats
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure_from_env
|
20
|
+
RubyLLM.configure do |config|
|
21
|
+
config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
|
22
|
+
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
|
23
|
+
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
|
24
|
+
config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY', nil)
|
25
|
+
config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
|
26
|
+
configure_bedrock(config)
|
27
|
+
config.request_timeout = 30
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def configure_bedrock(config)
|
32
|
+
config.bedrock_api_key = ENV.fetch('AWS_ACCESS_KEY_ID', nil)
|
33
|
+
config.bedrock_secret_key = ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)
|
34
|
+
config.bedrock_region = ENV.fetch('AWS_REGION', nil)
|
35
|
+
config.bedrock_session_token = ENV.fetch('AWS_SESSION_TOKEN', nil)
|
36
|
+
end
|
37
|
+
|
38
|
+
def refresh_models
|
39
|
+
initial_count = RubyLLM.models.all.size
|
40
|
+
puts "Refreshing models (#{initial_count} cached)..."
|
41
|
+
|
42
|
+
models = RubyLLM.models.refresh!
|
43
|
+
|
44
|
+
if models.all.empty? && initial_count.zero?
|
45
|
+
puts 'Error: Failed to fetch models.'
|
46
|
+
exit(1)
|
47
|
+
elsif models.all.size == initial_count && initial_count.positive?
|
48
|
+
puts 'Warning: Model list unchanged.'
|
49
|
+
else
|
50
|
+
puts "Saving models.json (#{models.all.size} models)"
|
51
|
+
models.save_models
|
52
|
+
end
|
53
|
+
|
54
|
+
@models = models
|
55
|
+
end
|
56
|
+
|
57
|
+
def display_model_stats
|
58
|
+
puts "\nModel count:"
|
59
|
+
provider_counts = @models.all.group_by(&:provider).transform_values(&:count)
|
60
|
+
|
61
|
+
RubyLLM::Provider.providers.each_key do |sym|
|
62
|
+
name = sym.to_s.capitalize
|
63
|
+
count = provider_counts[sym.to_s] || 0
|
64
|
+
status = status(sym)
|
65
|
+
puts " #{name}: #{count} models #{status}"
|
66
|
+
end
|
67
|
+
|
68
|
+
puts 'Refresh complete.'
|
69
|
+
end
|
70
|
+
|
71
|
+
def status(provider_sym)
|
72
|
+
if RubyLLM::Provider.providers[provider_sym].local?
|
73
|
+
' (LOCAL - SKIP)'
|
74
|
+
elsif RubyLLM::Provider.providers[provider_sym].configured?
|
75
|
+
' (OK)'
|
76
|
+
else
|
77
|
+
' (NOT CONFIGURED)'
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :release do
|
4
|
+
desc 'Verify cassettes are fresh enough for release'
|
5
|
+
task :verify_cassettes do
|
6
|
+
max_age_days = 1
|
7
|
+
cassette_dir = 'spec/fixtures/vcr_cassettes'
|
8
|
+
stale_cassettes = []
|
9
|
+
|
10
|
+
Dir.glob("#{cassette_dir}/**/*.yml").each do |cassette|
|
11
|
+
age_days = (Time.now - File.mtime(cassette)) / 86_400
|
12
|
+
|
13
|
+
next unless age_days > max_age_days
|
14
|
+
|
15
|
+
stale_cassettes << {
|
16
|
+
file: File.basename(cassette),
|
17
|
+
age: age_days.round(1)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
if stale_cassettes.any?
|
22
|
+
puts "\n❌ Found stale cassettes (older than #{max_age_days} days):"
|
23
|
+
stale_cassettes.each do |c|
|
24
|
+
puts " - #{c[:file]} (#{c[:age]} days old)"
|
25
|
+
end
|
26
|
+
puts "\nRun locally: bundle exec rspec"
|
27
|
+
exit 1
|
28
|
+
else
|
29
|
+
puts "✅ All cassettes are fresh (< #{max_age_days} days old)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/tasks/vcr.rake
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'dotenv/load'
|
4
|
+
|
3
5
|
# Helper functions at the top level
|
4
6
|
def record_all_cassettes(cassette_dir)
|
5
7
|
# Re-record all cassettes
|
@@ -11,7 +13,7 @@ def record_all_cassettes(cassette_dir)
|
|
11
13
|
puts 'Done recording. Please review the new cassettes.'
|
12
14
|
end
|
13
15
|
|
14
|
-
def record_for_providers(providers, cassette_dir)
|
16
|
+
def record_for_providers(providers, cassette_dir)
|
15
17
|
# Get the list of available providers from RubyLLM itself
|
16
18
|
all_providers = RubyLLM::Provider.providers.keys.map(&:to_s)
|
17
19
|
|
@@ -46,7 +48,7 @@ def record_for_providers(providers, cassette_dir) # rubocop:disable Metrics/AbcS
|
|
46
48
|
puts 'Please review the updated cassettes for sensitive information.'
|
47
49
|
end
|
48
50
|
|
49
|
-
def find_matching_cassettes(dir, providers)
|
51
|
+
def find_matching_cassettes(dir, providers)
|
50
52
|
cassettes = []
|
51
53
|
|
52
54
|
Dir.glob("#{dir}/**/*.yml").each do |file|
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_llm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carmine Paolino
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: base64
|
@@ -42,58 +41,72 @@ dependencies:
|
|
42
41
|
name: faraday
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
44
43
|
requirements:
|
45
|
-
- - "
|
44
|
+
- - ">="
|
46
45
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
46
|
+
version: 1.10.0
|
48
47
|
type: :runtime
|
49
48
|
prerelease: false
|
50
49
|
version_requirements: !ruby/object:Gem::Requirement
|
51
50
|
requirements:
|
52
|
-
- - "
|
51
|
+
- - ">="
|
53
52
|
- !ruby/object:Gem::Version
|
54
|
-
version:
|
53
|
+
version: 1.10.0
|
55
54
|
- !ruby/object:Gem::Dependency
|
56
55
|
name: faraday-multipart
|
57
56
|
requirement: !ruby/object:Gem::Requirement
|
58
57
|
requirements:
|
59
|
-
- - "
|
58
|
+
- - ">="
|
60
59
|
- !ruby/object:Gem::Version
|
61
60
|
version: '1'
|
62
61
|
type: :runtime
|
63
62
|
prerelease: false
|
64
63
|
version_requirements: !ruby/object:Gem::Requirement
|
65
64
|
requirements:
|
66
|
-
- - "
|
65
|
+
- - ">="
|
67
66
|
- !ruby/object:Gem::Version
|
68
67
|
version: '1'
|
69
68
|
- !ruby/object:Gem::Dependency
|
70
69
|
name: faraday-net_http
|
71
70
|
requirement: !ruby/object:Gem::Requirement
|
72
71
|
requirements:
|
73
|
-
- - "
|
72
|
+
- - ">="
|
74
73
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
74
|
+
version: '1'
|
76
75
|
type: :runtime
|
77
76
|
prerelease: false
|
78
77
|
version_requirements: !ruby/object:Gem::Requirement
|
79
78
|
requirements:
|
80
|
-
- - "
|
79
|
+
- - ">="
|
81
80
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
81
|
+
version: '1'
|
83
82
|
- !ruby/object:Gem::Dependency
|
84
83
|
name: faraday-retry
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '1'
|
89
|
+
type: :runtime
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '1'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: marcel
|
85
98
|
requirement: !ruby/object:Gem::Requirement
|
86
99
|
requirements:
|
87
100
|
- - "~>"
|
88
101
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
102
|
+
version: '1.0'
|
90
103
|
type: :runtime
|
91
104
|
prerelease: false
|
92
105
|
version_requirements: !ruby/object:Gem::Requirement
|
93
106
|
requirements:
|
94
107
|
- - "~>"
|
95
108
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
109
|
+
version: '1.0'
|
97
110
|
- !ruby/object:Gem::Dependency
|
98
111
|
name: zeitwerk
|
99
112
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,11 +121,12 @@ dependencies:
|
|
108
121
|
- - "~>"
|
109
122
|
- !ruby/object:Gem::Version
|
110
123
|
version: '2'
|
111
|
-
description: A delightful Ruby way to work with AI.
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
124
|
+
description: A delightful Ruby way to work with AI. Tired of juggling different SDKs?
|
125
|
+
RubyLLM provides one beautiful, Ruby-like interface for OpenAI, Anthropic, Gemini,
|
126
|
+
Bedrock, OpenRouter, DeepSeek, Ollama, and any OpenAI-compatible API. Chat (with
|
127
|
+
text, images, audio, PDFs), generate images, create embeddings, use tools (function
|
128
|
+
calling), stream responses, and integrate with Rails effortlessly. Minimal dependencies,
|
129
|
+
maximum developer happiness - just clean Ruby code that works.
|
116
130
|
email:
|
117
131
|
- carmine@paolino.me
|
118
132
|
executables: []
|
@@ -125,15 +139,24 @@ files:
|
|
125
139
|
- lib/ruby_llm/active_record/acts_as.rb
|
126
140
|
- lib/ruby_llm/aliases.json
|
127
141
|
- lib/ruby_llm/aliases.rb
|
142
|
+
- lib/ruby_llm/attachment.rb
|
128
143
|
- lib/ruby_llm/chat.rb
|
129
144
|
- lib/ruby_llm/chunk.rb
|
130
145
|
- lib/ruby_llm/configuration.rb
|
146
|
+
- lib/ruby_llm/connection.rb
|
131
147
|
- lib/ruby_llm/content.rb
|
148
|
+
- lib/ruby_llm/context.rb
|
132
149
|
- lib/ruby_llm/embedding.rb
|
133
150
|
- lib/ruby_llm/error.rb
|
134
151
|
- lib/ruby_llm/image.rb
|
135
152
|
- lib/ruby_llm/message.rb
|
136
|
-
- lib/ruby_llm/
|
153
|
+
- lib/ruby_llm/mime_type.rb
|
154
|
+
- lib/ruby_llm/model.rb
|
155
|
+
- lib/ruby_llm/model/info.rb
|
156
|
+
- lib/ruby_llm/model/modalities.rb
|
157
|
+
- lib/ruby_llm/model/pricing.rb
|
158
|
+
- lib/ruby_llm/model/pricing_category.rb
|
159
|
+
- lib/ruby_llm/model/pricing_tier.rb
|
137
160
|
- lib/ruby_llm/models.json
|
138
161
|
- lib/ruby_llm/models.rb
|
139
162
|
- lib/ruby_llm/provider.rb
|
@@ -148,6 +171,7 @@ files:
|
|
148
171
|
- lib/ruby_llm/providers/bedrock.rb
|
149
172
|
- lib/ruby_llm/providers/bedrock/capabilities.rb
|
150
173
|
- lib/ruby_llm/providers/bedrock/chat.rb
|
174
|
+
- lib/ruby_llm/providers/bedrock/media.rb
|
151
175
|
- lib/ruby_llm/providers/bedrock/models.rb
|
152
176
|
- lib/ruby_llm/providers/bedrock/signing.rb
|
153
177
|
- lib/ruby_llm/providers/bedrock/streaming.rb
|
@@ -168,6 +192,9 @@ files:
|
|
168
192
|
- lib/ruby_llm/providers/gemini/models.rb
|
169
193
|
- lib/ruby_llm/providers/gemini/streaming.rb
|
170
194
|
- lib/ruby_llm/providers/gemini/tools.rb
|
195
|
+
- lib/ruby_llm/providers/ollama.rb
|
196
|
+
- lib/ruby_llm/providers/ollama/chat.rb
|
197
|
+
- lib/ruby_llm/providers/ollama/media.rb
|
171
198
|
- lib/ruby_llm/providers/openai.rb
|
172
199
|
- lib/ruby_llm/providers/openai/capabilities.rb
|
173
200
|
- lib/ruby_llm/providers/openai/chat.rb
|
@@ -177,20 +204,19 @@ files:
|
|
177
204
|
- lib/ruby_llm/providers/openai/models.rb
|
178
205
|
- lib/ruby_llm/providers/openai/streaming.rb
|
179
206
|
- lib/ruby_llm/providers/openai/tools.rb
|
207
|
+
- lib/ruby_llm/providers/openrouter.rb
|
208
|
+
- lib/ruby_llm/providers/openrouter/models.rb
|
180
209
|
- lib/ruby_llm/railtie.rb
|
181
210
|
- lib/ruby_llm/stream_accumulator.rb
|
182
211
|
- lib/ruby_llm/streaming.rb
|
183
212
|
- lib/ruby_llm/tool.rb
|
184
213
|
- lib/ruby_llm/tool_call.rb
|
214
|
+
- lib/ruby_llm/utils.rb
|
185
215
|
- lib/ruby_llm/version.rb
|
186
|
-
- lib/tasks/
|
187
|
-
- lib/tasks/capability_generator.rb
|
188
|
-
- lib/tasks/capability_scraper.rb
|
189
|
-
- lib/tasks/cli_helper.rb
|
190
|
-
- lib/tasks/code_validator.rb
|
191
|
-
- lib/tasks/model_updater.rb
|
192
|
-
- lib/tasks/models.rake
|
216
|
+
- lib/tasks/aliases.rake
|
193
217
|
- lib/tasks/models_docs.rake
|
218
|
+
- lib/tasks/models_update.rake
|
219
|
+
- lib/tasks/release.rake
|
194
220
|
- lib/tasks/vcr.rake
|
195
221
|
homepage: https://rubyllm.com
|
196
222
|
licenses:
|
@@ -202,7 +228,6 @@ metadata:
|
|
202
228
|
documentation_uri: https://rubyllm.com
|
203
229
|
bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
|
204
230
|
rubygems_mfa_required: 'true'
|
205
|
-
post_install_message:
|
206
231
|
rdoc_options: []
|
207
232
|
require_paths:
|
208
233
|
- lib
|
@@ -217,8 +242,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
217
242
|
- !ruby/object:Gem::Version
|
218
243
|
version: '0'
|
219
244
|
requirements: []
|
220
|
-
rubygems_version: 3.
|
221
|
-
signing_key:
|
245
|
+
rubygems_version: 3.6.7
|
222
246
|
specification_version: 4
|
223
|
-
summary:
|
247
|
+
summary: A single delightful Ruby way to work with AI.
|
224
248
|
test_files: []
|
data/lib/ruby_llm/model_info.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'time'
|
4
|
-
|
5
|
-
module RubyLLM
|
6
|
-
# Information about an AI model's capabilities, pricing, and metadata.
|
7
|
-
# Used by the Models registry to help developers choose the right model
|
8
|
-
# for their needs.
|
9
|
-
#
|
10
|
-
# Example:
|
11
|
-
# model = RubyLLM.models.find('gpt-4')
|
12
|
-
# model.supports_vision? # => true
|
13
|
-
# model.supports_functions? # => true
|
14
|
-
# model.input_price_per_million # => 30.0
|
15
|
-
class ModelInfo
|
16
|
-
attr_reader :id, :created_at, :display_name, :provider, :metadata,
|
17
|
-
:context_window, :max_tokens, :supports_vision, :supports_functions,
|
18
|
-
:supports_json_mode, :input_price_per_million, :output_price_per_million, :type, :family
|
19
|
-
|
20
|
-
def initialize(data) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
21
|
-
@id = data[:id]
|
22
|
-
@created_at = data[:created_at].is_a?(String) ? Time.parse(data[:created_at]) : data[:created_at]
|
23
|
-
@display_name = data[:display_name]
|
24
|
-
@provider = data[:provider]
|
25
|
-
@context_window = data[:context_window]
|
26
|
-
@max_tokens = data[:max_tokens]
|
27
|
-
@type = data[:type]
|
28
|
-
@family = data[:family]
|
29
|
-
@supports_vision = data[:supports_vision]
|
30
|
-
@supports_functions = data[:supports_functions]
|
31
|
-
@supports_json_mode = data[:supports_json_mode]
|
32
|
-
@input_price_per_million = data[:input_price_per_million]
|
33
|
-
@output_price_per_million = data[:output_price_per_million]
|
34
|
-
@metadata = data[:metadata] || {}
|
35
|
-
end
|
36
|
-
|
37
|
-
def to_h # rubocop:disable Metrics/MethodLength
|
38
|
-
{
|
39
|
-
id: id,
|
40
|
-
created_at: created_at&.iso8601,
|
41
|
-
display_name: display_name,
|
42
|
-
provider: provider,
|
43
|
-
context_window: context_window,
|
44
|
-
max_tokens: max_tokens,
|
45
|
-
type: type,
|
46
|
-
family: family,
|
47
|
-
supports_vision: supports_vision,
|
48
|
-
supports_functions: supports_functions,
|
49
|
-
supports_json_mode: supports_json_mode,
|
50
|
-
input_price_per_million: input_price_per_million,
|
51
|
-
output_price_per_million: output_price_per_million,
|
52
|
-
metadata: metadata
|
53
|
-
}
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|
data/lib/tasks/browser_helper.rb
DELETED
@@ -1,97 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'ferrum'
|
4
|
-
require_relative 'cli_helper'
|
5
|
-
|
6
|
-
class BrowserHelper # rubocop:disable Style/Documentation
|
7
|
-
REALISTIC_USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36' # rubocop:disable Layout/LineLength
|
8
|
-
|
9
|
-
def initialize
|
10
|
-
@browser = create_browser
|
11
|
-
end
|
12
|
-
|
13
|
-
def goto(url)
|
14
|
-
@browser.goto(url)
|
15
|
-
end
|
16
|
-
|
17
|
-
def current_url
|
18
|
-
@browser.page.url
|
19
|
-
rescue StandardError
|
20
|
-
'N/A'
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_page_content(context = 'current page') # rubocop:disable Metrics/MethodLength
|
24
|
-
puts " Extracting HTML for #{context}..."
|
25
|
-
|
26
|
-
begin
|
27
|
-
sleep(1.0) # Small delay for page stability
|
28
|
-
html = @browser.body
|
29
|
-
|
30
|
-
if html && !html.empty?
|
31
|
-
puts " Extracted ~#{html.length} chars of HTML"
|
32
|
-
puts ' WARNING: Challenge page detected' if html.match?(/challenge-platform|Checking site/)
|
33
|
-
html
|
34
|
-
else
|
35
|
-
puts ' Warning: Empty content returned'
|
36
|
-
''
|
37
|
-
end
|
38
|
-
rescue StandardError => e
|
39
|
-
puts " Error getting HTML: #{e.class} - #{e.message}"
|
40
|
-
''
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def wait_for_page_load
|
45
|
-
handle_cloudflare_challenge
|
46
|
-
end
|
47
|
-
|
48
|
-
def close
|
49
|
-
puts "\nClosing browser..."
|
50
|
-
@browser.quit
|
51
|
-
rescue StandardError => e
|
52
|
-
puts " Warning: Error closing browser: #{e.message}"
|
53
|
-
end
|
54
|
-
|
55
|
-
private
|
56
|
-
|
57
|
-
def create_browser
|
58
|
-
puts ' Initializing browser for manual interaction...'
|
59
|
-
|
60
|
-
Ferrum::Browser.new(
|
61
|
-
window_size: [1366, 768],
|
62
|
-
headless: false,
|
63
|
-
browser_options: browser_options,
|
64
|
-
timeout: 120,
|
65
|
-
process_timeout: 120,
|
66
|
-
pending_connection_errors: false
|
67
|
-
)
|
68
|
-
end
|
69
|
-
|
70
|
-
def browser_options
|
71
|
-
{
|
72
|
-
'user-agent' => REALISTIC_USER_AGENT,
|
73
|
-
'disable-gpu' => nil,
|
74
|
-
'no-sandbox' => nil,
|
75
|
-
'disable-blink-features' => 'AutomationControlled',
|
76
|
-
'disable-infobars' => nil,
|
77
|
-
'start-maximized' => nil
|
78
|
-
}
|
79
|
-
end
|
80
|
-
|
81
|
-
def handle_cloudflare_challenge # rubocop:disable Metrics/MethodLength
|
82
|
-
puts "\nWaiting for Cloudflare challenge resolution..."
|
83
|
-
puts 'c: Challenge solved'
|
84
|
-
puts 'q: Quit/Skip'
|
85
|
-
|
86
|
-
choice = CliHelper.get_user_choice('Confirm when ready', %w[c q])
|
87
|
-
return false if choice == 'q'
|
88
|
-
|
89
|
-
begin
|
90
|
-
@browser.page.target_id
|
91
|
-
true
|
92
|
-
rescue StandardError
|
93
|
-
puts 'Browser check failed after challenge'
|
94
|
-
false
|
95
|
-
end
|
96
|
-
end
|
97
|
-
end
|
@@ -1,123 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'ruby_llm'
|
4
|
-
require 'json'
|
5
|
-
require_relative 'code_validator'
|
6
|
-
|
7
|
-
class CapabilityGenerator # rubocop:disable Style/Documentation
|
8
|
-
def initialize(provider_name, docs_html)
|
9
|
-
@provider_name = provider_name
|
10
|
-
@docs_html = docs_html
|
11
|
-
@processed_html = process_html(docs_html)
|
12
|
-
end
|
13
|
-
|
14
|
-
def generate_and_save # rubocop:disable Metrics/MethodLength
|
15
|
-
puts " Starting code generation for #{@provider_name}..."
|
16
|
-
|
17
|
-
existing_path = File.expand_path("../../lib/ruby_llm/providers/#{@provider_name}/capabilities.rb", __dir__)
|
18
|
-
unless File.exist?(existing_path)
|
19
|
-
puts " Skipping: No file at #{existing_path}"
|
20
|
-
return
|
21
|
-
end
|
22
|
-
|
23
|
-
existing_code = File.read(existing_path)
|
24
|
-
puts ' Read existing code'
|
25
|
-
|
26
|
-
generated_code = generate_capabilities(existing_code)
|
27
|
-
|
28
|
-
if generated_code
|
29
|
-
puts " Writing updated code to #{existing_path}..."
|
30
|
-
File.write(existing_path, generated_code)
|
31
|
-
puts " Updated #{@provider_name}"
|
32
|
-
|
33
|
-
verify_code_with_models_update
|
34
|
-
else
|
35
|
-
puts " Failed to generate valid code for #{@provider_name}"
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
|
-
private
|
40
|
-
|
41
|
-
def process_html(docs_html) # rubocop:disable Metrics/MethodLength
|
42
|
-
docs_html.transform_values do |html|
|
43
|
-
next '' if html.nil? || html.empty?
|
44
|
-
|
45
|
-
# Extract just the main content areas, skip scripts, styles, etc
|
46
|
-
main_content = html.scan(%r{<main.*?>.*?</main>}m).first ||
|
47
|
-
html.scan(%r{<article.*?>.*?</article>}m).first ||
|
48
|
-
html.scan(%r{<div class="content.*?>.*?</div>}m).first
|
49
|
-
|
50
|
-
if main_content
|
51
|
-
# Further clean up the content
|
52
|
-
main_content.gsub(%r{<script.*?>.*?</script>}m, '')
|
53
|
-
.gsub(%r{<style.*?>.*?</style>}m, '')
|
54
|
-
.gsub(/<!--.*?-->/m, '')
|
55
|
-
.gsub(/\s+/, ' ')
|
56
|
-
.strip
|
57
|
-
else
|
58
|
-
''
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def generate_capabilities(existing_code) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
64
|
-
max_retries = 3
|
65
|
-
retries = 0
|
66
|
-
|
67
|
-
loop do # rubocop:disable Metrics/BlockLength
|
68
|
-
puts " Attempt #{retries + 1}/#{max_retries}..."
|
69
|
-
|
70
|
-
begin
|
71
|
-
gemini = RubyLLM.chat(model: 'gemini-1.5-pro-latest')
|
72
|
-
.with_temperature(0.1)
|
73
|
-
|
74
|
-
docs_json = JSON.pretty_generate(@processed_html)
|
75
|
-
|
76
|
-
prompt = <<~PROMPT
|
77
|
-
Update RubyLLM::Providers::#{@provider_name.capitalize}::Capabilities module.
|
78
|
-
Only use the provided HTML content and existing code structure.
|
79
|
-
Focus on updating values while preserving the module structure.
|
80
|
-
|
81
|
-
Existing code to maintain structure:
|
82
|
-
```ruby
|
83
|
-
#{existing_code}
|
84
|
-
```
|
85
|
-
|
86
|
-
HTML content to extract new values from:
|
87
|
-
```json
|
88
|
-
#{docs_json}
|
89
|
-
```
|
90
|
-
|
91
|
-
Return ONLY the complete Ruby code within ```ruby ``` tags.
|
92
|
-
PROMPT
|
93
|
-
|
94
|
-
response = gemini.ask(prompt)
|
95
|
-
generated_code = CodeValidator.extract_code_from_response(response.content)
|
96
|
-
|
97
|
-
return generated_code if generated_code && CodeValidator.validate_syntax(generated_code)
|
98
|
-
rescue RubyLLM::BadRequestError => e
|
99
|
-
puts " Error: #{e.message}"
|
100
|
-
# Try with even less content if we hit token limits
|
101
|
-
@processed_html = @processed_html.transform_values { |html| html[0..10_000] }
|
102
|
-
rescue StandardError => e
|
103
|
-
puts " Error: #{e.class} - #{e.message}"
|
104
|
-
end
|
105
|
-
|
106
|
-
retries += 1
|
107
|
-
break if retries >= max_retries
|
108
|
-
end
|
109
|
-
|
110
|
-
nil
|
111
|
-
end
|
112
|
-
|
113
|
-
def verify_code_with_models_update
|
114
|
-
puts ' Verifying with models:update...'
|
115
|
-
begin
|
116
|
-
Rake::Task['models:update'].reenable
|
117
|
-
Rake::Task['models:update'].invoke
|
118
|
-
puts ' Verification successful'
|
119
|
-
rescue StandardError => e
|
120
|
-
puts " Verification failed: #{e.message}"
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|