ruby_llm 1.0.1 → 1.1.0rc1
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 +28 -12
- data/lib/ruby_llm/active_record/acts_as.rb +46 -7
- data/lib/ruby_llm/aliases.json +65 -0
- data/lib/ruby_llm/aliases.rb +56 -0
- data/lib/ruby_llm/chat.rb +10 -9
- data/lib/ruby_llm/configuration.rb +4 -0
- data/lib/ruby_llm/error.rb +15 -4
- data/lib/ruby_llm/models.json +1163 -303
- data/lib/ruby_llm/models.rb +40 -11
- data/lib/ruby_llm/provider.rb +32 -39
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +8 -9
- data/lib/ruby_llm/providers/anthropic/chat.rb +31 -4
- data/lib/ruby_llm/providers/anthropic/streaming.rb +12 -6
- data/lib/ruby_llm/providers/anthropic.rb +4 -0
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +168 -0
- data/lib/ruby_llm/providers/bedrock/chat.rb +108 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +84 -0
- data/lib/ruby_llm/providers/bedrock/signing.rb +831 -0
- data/lib/ruby_llm/providers/bedrock/streaming/base.rb +46 -0
- data/lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb +63 -0
- data/lib/ruby_llm/providers/bedrock/streaming/message_processing.rb +79 -0
- data/lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb +90 -0
- data/lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb +91 -0
- data/lib/ruby_llm/providers/bedrock/streaming.rb +36 -0
- data/lib/ruby_llm/providers/bedrock.rb +83 -0
- data/lib/ruby_llm/providers/deepseek/chat.rb +17 -0
- data/lib/ruby_llm/providers/deepseek.rb +5 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +50 -34
- data/lib/ruby_llm/providers/gemini/chat.rb +8 -15
- data/lib/ruby_llm/providers/gemini/images.rb +5 -10
- data/lib/ruby_llm/providers/gemini/streaming.rb +35 -76
- data/lib/ruby_llm/providers/gemini/tools.rb +12 -12
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +146 -206
- data/lib/ruby_llm/providers/openai/streaming.rb +9 -13
- data/lib/ruby_llm/providers/openai.rb +4 -0
- data/lib/ruby_llm/streaming.rb +96 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm.rb +6 -3
- data/lib/tasks/browser_helper.rb +97 -0
- data/lib/tasks/capability_generator.rb +123 -0
- data/lib/tasks/capability_scraper.rb +224 -0
- data/lib/tasks/cli_helper.rb +22 -0
- data/lib/tasks/code_validator.rb +29 -0
- data/lib/tasks/model_updater.rb +66 -0
- data/lib/tasks/models.rake +28 -193
- data/lib/tasks/vcr.rake +13 -30
- metadata +27 -19
- data/.github/workflows/cicd.yml +0 -158
- data/.github/workflows/docs.yml +0 -53
- data/.gitignore +0 -59
- data/.overcommit.yml +0 -26
- data/.rspec +0 -3
- data/.rubocop.yml +0 -10
- data/.yardopts +0 -12
- data/CONTRIBUTING.md +0 -207
- data/Gemfile +0 -33
- data/Rakefile +0 -9
- data/bin/console +0 -17
- data/bin/setup +0 -6
- data/ruby_llm.gemspec +0 -44
data/lib/tasks/models.rake
CHANGED
@@ -1,208 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
require 'nokogiri'
|
7
|
-
require 'ruby_llm'
|
8
|
-
|
9
|
-
# URLs to process
|
10
|
-
PROVIDER_DOCS = {
|
11
|
-
openai: {
|
12
|
-
models: 'https://platform.openai.com/docs/models',
|
13
|
-
pricing: 'https://platform.openai.com/docs/pricing'
|
14
|
-
},
|
15
|
-
gemini: {
|
16
|
-
models: 'https://ai.google.dev/gemini-api/docs/models/gemini',
|
17
|
-
pricing: 'https://ai.google.dev/gemini-api/docs/pricing'
|
18
|
-
},
|
19
|
-
deepseek: {
|
20
|
-
models: 'https://api-docs.deepseek.com/quick_start/pricing/'
|
21
|
-
},
|
22
|
-
anthropic: {
|
23
|
-
models: 'https://docs.anthropic.com/en/docs/about-claude/models/all-models'
|
24
|
-
}
|
25
|
-
}.freeze
|
26
|
-
|
27
|
-
def fetch_page(url) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
28
|
-
if url.include?('openai.com')
|
29
|
-
puts "Please visit #{url} and paste the content below (type 'END' on a new line when done):"
|
30
|
-
original_separator = $INPUT_RECORD_SEPARATOR
|
31
|
-
$/ = 'END'
|
32
|
-
content = $stdin.gets&.chomp
|
33
|
-
$/ = original_separator
|
34
|
-
|
35
|
-
raise "No content provided for #{url}" unless content
|
36
|
-
|
37
|
-
content
|
38
|
-
else
|
39
|
-
response = http_client.get(url)
|
40
|
-
html = Nokogiri::HTML(response.body)
|
41
|
-
|
42
|
-
# Remove script tags and comments
|
43
|
-
html.css('script').remove
|
44
|
-
html.xpath('//comment()').remove
|
45
|
-
|
46
|
-
# Extract text content
|
47
|
-
text = html.css('body').text
|
48
|
-
|
49
|
-
# Clean up whitespace
|
50
|
-
text.gsub!(/\s+/, ' ')
|
51
|
-
text.strip!
|
52
|
-
|
53
|
-
text
|
54
|
-
end
|
55
|
-
rescue StandardError => e
|
56
|
-
raise "Failed to fetch #{url}: #{e.message}"
|
57
|
-
end
|
58
|
-
|
59
|
-
def http_client
|
60
|
-
@http_client ||= Faraday.new do |f|
|
61
|
-
f.response :raise_error
|
62
|
-
f.response :logger, RubyLLM.logger, { headers: false, bodies: true }
|
63
|
-
end
|
64
|
-
end
|
3
|
+
require_relative 'model_updater'
|
4
|
+
require_relative 'capability_scraper'
|
5
|
+
require_relative 'capability_generator'
|
65
6
|
|
66
7
|
namespace :models do # rubocop:disable Metrics/BlockLength
|
67
|
-
desc 'Update available models from providers'
|
8
|
+
desc 'Update available models from providers (API keys needed)'
|
68
9
|
task :update do
|
69
|
-
|
70
|
-
RubyLLM.configure do |config|
|
71
|
-
config.openai_api_key = ENV.fetch('OPENAI_API_KEY')
|
72
|
-
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY')
|
73
|
-
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY')
|
74
|
-
config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY')
|
75
|
-
end
|
76
|
-
|
77
|
-
models = RubyLLM.models.refresh!
|
78
|
-
models.save_models
|
79
|
-
|
80
|
-
puts "Updated models.json with #{models.all.size} models:"
|
81
|
-
RubyLLM::Provider.providers.each do |provider_sym, provider_module|
|
82
|
-
provider_name = provider_module.to_s.split('::').last
|
83
|
-
provider_models = models.all.select { |m| m.provider == provider_sym.to_s }
|
84
|
-
puts "#{provider_name} models: #{provider_models.size}"
|
85
|
-
end
|
10
|
+
ModelUpdater.new.run
|
86
11
|
end
|
87
12
|
|
88
|
-
desc 'Update
|
89
|
-
task :update_capabilities do
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
RubyLLM.configure do |config|
|
95
|
-
config.openai_api_key = ENV.fetch('OPENAI_API_KEY')
|
96
|
-
config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY')
|
97
|
-
config.gemini_api_key = ENV.fetch('GEMINI_API_KEY')
|
13
|
+
desc 'Update capabilities modules (GEMINI_API_KEY needed)'
|
14
|
+
task :update_capabilities, [:providers] do |_t, args|
|
15
|
+
gemini_key = ENV.fetch('GEMINI_API_KEY', nil)
|
16
|
+
unless gemini_key && !gemini_key.empty?
|
17
|
+
puts 'Error: GEMINI_API_KEY required'
|
18
|
+
exit(1)
|
98
19
|
end
|
99
20
|
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
PROVIDER_DOCS
|
105
|
-
end
|
106
|
-
|
107
|
-
# Process each provider
|
108
|
-
providers_to_process.each do |provider, urls| # rubocop:disable Metrics/BlockLength
|
109
|
-
puts "Processing #{provider}..."
|
110
|
-
|
111
|
-
# Initialize our AI assistants
|
112
|
-
#
|
113
|
-
gemini = RubyLLM.chat(model: 'gemini-2.0-flash').with_temperature(0)
|
114
|
-
claude = RubyLLM.chat(model: 'claude-3-7-sonnet-20250219').with_temperature(0)
|
115
|
-
|
116
|
-
# Read existing capabilities file if present
|
117
|
-
existing_file = "lib/ruby_llm/providers/#{provider}/capabilities.rb"
|
118
|
-
existing_code = File.read(existing_file) if File.exist?(existing_file)
|
119
|
-
|
120
|
-
begin
|
121
|
-
# Download documentation
|
122
|
-
docs = urls.map do |type, url|
|
123
|
-
puts " Getting #{type} documentation..."
|
124
|
-
content = fetch_page(url)
|
125
|
-
|
126
|
-
puts "\nHere's what I got:\n\n"
|
127
|
-
puts "#{content.slice(0, 500)}...\n\n"
|
128
|
-
|
129
|
-
loop do
|
130
|
-
print 'Does this content look correct? (y/n): '
|
131
|
-
answer = $stdin.gets&.chomp&.downcase
|
132
|
-
break if answer == 'y'
|
133
|
-
raise "Content verification failed for #{url}" if answer == 'n'
|
134
|
-
end
|
135
|
-
|
136
|
-
"#{type.to_s.upcase} DOCUMENTATION:\n\n#{content}"
|
137
|
-
end.join("\n\n")
|
138
|
-
|
139
|
-
# Extract relevant information with Gemini
|
140
|
-
puts ' Extracting model information...'
|
141
|
-
extraction_prompt = <<~PROMPT
|
142
|
-
Extract relevant model capabilities information from this documentation:
|
143
|
-
|
144
|
-
#{docs}
|
145
|
-
|
146
|
-
Focus on:
|
147
|
-
1. Available models and their IDs
|
148
|
-
2. Context window sizes
|
149
|
-
3. Maximum output tokens
|
150
|
-
4. Pricing information
|
151
|
-
5. Model capabilities (vision, function calling, etc)
|
152
|
-
|
153
|
-
Format the information clearly and concisely, focusing only on facts about the models.
|
154
|
-
PROMPT
|
155
|
-
|
156
|
-
model_info = gemini.ask(extraction_prompt).content
|
157
|
-
|
158
|
-
# Generate Ruby code with Claude
|
159
|
-
puts ' Generating Ruby code...'
|
160
|
-
code_prompt = <<~PROMPT
|
161
|
-
I need you to generate a Ruby module for #{provider}'s model capabilities.
|
162
|
-
Use this extracted model information:
|
163
|
-
|
164
|
-
#{model_info}
|
165
|
-
|
166
|
-
The module should go in lib/ruby_llm/providers/#{provider}/capabilities.rb and follow these conventions:
|
167
|
-
|
168
|
-
1. Include methods for determining context windows, token limits, pricing, and capabilities
|
169
|
-
2. Use consistent naming with other providers
|
170
|
-
3. Include detailed pricing information in a PRICES constant
|
171
|
-
4. Follow the existing structure in the codebase
|
172
|
-
5. Use Ruby idioms and clean code practices
|
173
|
-
6. Include module_function to make methods callable at module level
|
174
|
-
7. Include all necessary method documentation
|
175
|
-
|
176
|
-
Here's the existing implementation for reference (maintain similar structure and same method names):
|
177
|
-
|
178
|
-
#{existing_code}
|
179
|
-
|
180
|
-
Focus on accuracy and maintaining consistency with the existing codebase structure.
|
181
|
-
PROMPT
|
182
|
-
|
183
|
-
response = claude.ask(code_prompt)
|
184
|
-
|
185
|
-
# Extract Ruby code from Claude's response
|
186
|
-
puts " Extracting Ruby code from Claude's response..."
|
187
|
-
ruby_code = nil
|
188
|
-
|
189
|
-
# Look for Ruby code block
|
190
|
-
ruby_code = Regexp.last_match(1).strip if response.content =~ /```ruby\s*(.*?)```/m
|
191
|
-
|
192
|
-
# Verify we found Ruby code
|
193
|
-
raise "No Ruby code block found in Claude's response" if ruby_code.nil? || ruby_code.empty?
|
21
|
+
RubyLLM.configure do |c|
|
22
|
+
c.gemini_api_key = gemini_key
|
23
|
+
c.request_timeout = 300
|
24
|
+
end
|
194
25
|
|
195
|
-
|
196
|
-
file_path = "lib/ruby_llm/providers/#{provider}/capabilities.rb"
|
197
|
-
puts " Writing #{file_path}..."
|
26
|
+
target_providers = CapabilityScraper.parse_providers(args[:providers])
|
198
27
|
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
28
|
+
begin
|
29
|
+
scraper = CapabilityScraper.new(target_providers)
|
30
|
+
scraper.run do |provider, docs_html|
|
31
|
+
generator = CapabilityGenerator.new(provider, docs_html)
|
32
|
+
generator.generate_and_save
|
203
33
|
end
|
34
|
+
rescue StandardError => e
|
35
|
+
puts "Error: #{e.message}"
|
36
|
+
puts e.backtrace.first(5).join("\n")
|
37
|
+
ensure
|
38
|
+
puts 'Update process complete. Review generated files.'
|
204
39
|
end
|
205
|
-
|
206
|
-
puts "Done! Don't forget to review the generated code and run the tests."
|
207
40
|
end
|
208
41
|
end
|
42
|
+
|
43
|
+
task default: ['models:update']
|
data/lib/tasks/vcr.rake
CHANGED
@@ -29,13 +29,8 @@ def record_for_providers(providers, cassette_dir) # rubocop:disable Metrics/AbcS
|
|
29
29
|
return
|
30
30
|
end
|
31
31
|
|
32
|
-
# Get URL patterns from the providers themselves
|
33
|
-
provider_patterns = get_provider_patterns(providers)
|
34
|
-
|
35
|
-
puts "Finding cassettes for providers: #{providers.join(', ')}"
|
36
|
-
|
37
32
|
# Find and delete matching cassettes
|
38
|
-
cassettes_to_delete = find_matching_cassettes(cassette_dir,
|
33
|
+
cassettes_to_delete = find_matching_cassettes(cassette_dir, providers)
|
39
34
|
|
40
35
|
if cassettes_to_delete.empty?
|
41
36
|
puts 'No cassettes found for the specified providers.'
|
@@ -51,33 +46,21 @@ def record_for_providers(providers, cassette_dir) # rubocop:disable Metrics/AbcS
|
|
51
46
|
puts 'Please review the updated cassettes for sensitive information.'
|
52
47
|
end
|
53
48
|
|
54
|
-
def
|
55
|
-
provider_patterns = {}
|
56
|
-
|
57
|
-
providers.each do |provider_name|
|
58
|
-
provider_module = RubyLLM::Provider.providers[provider_name.to_sym]
|
59
|
-
next unless provider_module
|
60
|
-
|
61
|
-
# Extract the base URL from the provider's api_base method
|
62
|
-
api_base = provider_module.api_base.to_s
|
63
|
-
|
64
|
-
# Create a regex pattern from the domain
|
65
|
-
next unless api_base && !api_base.empty?
|
66
|
-
|
67
|
-
domain = URI.parse(api_base).host
|
68
|
-
pattern = Regexp.new(Regexp.escape(domain))
|
69
|
-
provider_patterns[provider_name] = pattern
|
70
|
-
end
|
71
|
-
|
72
|
-
provider_patterns
|
73
|
-
end
|
74
|
-
|
75
|
-
def find_matching_cassettes(dir, patterns)
|
49
|
+
def find_matching_cassettes(dir, providers) # rubocop:disable Metrics/MethodLength
|
76
50
|
cassettes = []
|
77
51
|
|
78
52
|
Dir.glob("#{dir}/**/*.yml").each do |file|
|
79
|
-
|
80
|
-
|
53
|
+
basename = File.basename(file)
|
54
|
+
|
55
|
+
# Precise matching to avoid cross-provider confusion
|
56
|
+
providers.each do |provider|
|
57
|
+
# Match only exact provider prefixes
|
58
|
+
next unless basename =~ /^[^_]*_#{provider}_/ || # For first section like "chat_openai_"
|
59
|
+
basename =~ /_#{provider}_[^_]+_/ # For middle sections like "_openai_gpt4_"
|
60
|
+
|
61
|
+
cassettes << file
|
62
|
+
break
|
63
|
+
end
|
81
64
|
end
|
82
65
|
|
83
66
|
cassettes
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ruby_llm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.1.0rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Carmine Paolino
|
8
8
|
autorequire:
|
9
|
-
bindir:
|
9
|
+
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-04-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: base64
|
@@ -96,31 +96,21 @@ dependencies:
|
|
96
96
|
version: '2'
|
97
97
|
description: A delightful Ruby way to work with AI. Chat in text, analyze and generate
|
98
98
|
images, understand audio, and use tools through a unified interface to OpenAI, Anthropic,
|
99
|
-
Google, and DeepSeek. Built for developer happiness with
|
100
|
-
proper streaming, and Rails integration. No wrapping your
|
101
|
-
- just clean Ruby code that works.
|
99
|
+
Google, AWS Bedrock Anthropic, and DeepSeek. Built for developer happiness with
|
100
|
+
automatic token counting, proper streaming, and Rails integration. No wrapping your
|
101
|
+
head around multiple APIs - just clean Ruby code that works.
|
102
102
|
email:
|
103
103
|
- carmine@paolino.me
|
104
104
|
executables: []
|
105
105
|
extensions: []
|
106
106
|
extra_rdoc_files: []
|
107
107
|
files:
|
108
|
-
- ".github/workflows/cicd.yml"
|
109
|
-
- ".github/workflows/docs.yml"
|
110
|
-
- ".gitignore"
|
111
|
-
- ".overcommit.yml"
|
112
|
-
- ".rspec"
|
113
|
-
- ".rubocop.yml"
|
114
|
-
- ".yardopts"
|
115
|
-
- CONTRIBUTING.md
|
116
|
-
- Gemfile
|
117
108
|
- LICENSE
|
118
109
|
- README.md
|
119
|
-
- Rakefile
|
120
|
-
- bin/console
|
121
|
-
- bin/setup
|
122
110
|
- lib/ruby_llm.rb
|
123
111
|
- lib/ruby_llm/active_record/acts_as.rb
|
112
|
+
- lib/ruby_llm/aliases.json
|
113
|
+
- lib/ruby_llm/aliases.rb
|
124
114
|
- lib/ruby_llm/chat.rb
|
125
115
|
- lib/ruby_llm/chunk.rb
|
126
116
|
- lib/ruby_llm/configuration.rb
|
@@ -141,8 +131,20 @@ files:
|
|
141
131
|
- lib/ruby_llm/providers/anthropic/models.rb
|
142
132
|
- lib/ruby_llm/providers/anthropic/streaming.rb
|
143
133
|
- lib/ruby_llm/providers/anthropic/tools.rb
|
134
|
+
- lib/ruby_llm/providers/bedrock.rb
|
135
|
+
- lib/ruby_llm/providers/bedrock/capabilities.rb
|
136
|
+
- lib/ruby_llm/providers/bedrock/chat.rb
|
137
|
+
- lib/ruby_llm/providers/bedrock/models.rb
|
138
|
+
- lib/ruby_llm/providers/bedrock/signing.rb
|
139
|
+
- lib/ruby_llm/providers/bedrock/streaming.rb
|
140
|
+
- lib/ruby_llm/providers/bedrock/streaming/base.rb
|
141
|
+
- lib/ruby_llm/providers/bedrock/streaming/content_extraction.rb
|
142
|
+
- lib/ruby_llm/providers/bedrock/streaming/message_processing.rb
|
143
|
+
- lib/ruby_llm/providers/bedrock/streaming/payload_processing.rb
|
144
|
+
- lib/ruby_llm/providers/bedrock/streaming/prelude_handling.rb
|
144
145
|
- lib/ruby_llm/providers/deepseek.rb
|
145
146
|
- lib/ruby_llm/providers/deepseek/capabilities.rb
|
147
|
+
- lib/ruby_llm/providers/deepseek/chat.rb
|
146
148
|
- lib/ruby_llm/providers/gemini.rb
|
147
149
|
- lib/ruby_llm/providers/gemini/capabilities.rb
|
148
150
|
- lib/ruby_llm/providers/gemini/chat.rb
|
@@ -163,12 +165,18 @@ files:
|
|
163
165
|
- lib/ruby_llm/providers/openai/tools.rb
|
164
166
|
- lib/ruby_llm/railtie.rb
|
165
167
|
- lib/ruby_llm/stream_accumulator.rb
|
168
|
+
- lib/ruby_llm/streaming.rb
|
166
169
|
- lib/ruby_llm/tool.rb
|
167
170
|
- lib/ruby_llm/tool_call.rb
|
168
171
|
- lib/ruby_llm/version.rb
|
172
|
+
- lib/tasks/browser_helper.rb
|
173
|
+
- lib/tasks/capability_generator.rb
|
174
|
+
- lib/tasks/capability_scraper.rb
|
175
|
+
- lib/tasks/cli_helper.rb
|
176
|
+
- lib/tasks/code_validator.rb
|
177
|
+
- lib/tasks/model_updater.rb
|
169
178
|
- lib/tasks/models.rake
|
170
179
|
- lib/tasks/vcr.rake
|
171
|
-
- ruby_llm.gemspec
|
172
180
|
homepage: https://rubyllm.com
|
173
181
|
licenses:
|
174
182
|
- MIT
|
data/.github/workflows/cicd.yml
DELETED
@@ -1,158 +0,0 @@
|
|
1
|
-
name: CI
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [ "main" ]
|
6
|
-
paths:
|
7
|
-
- 'lib/**'
|
8
|
-
- 'spec/**'
|
9
|
-
- 'Gemfile'
|
10
|
-
- 'Rakefile'
|
11
|
-
- 'ruby_llm.gemspec'
|
12
|
-
- '.github/workflows/cicd.yml'
|
13
|
-
pull_request:
|
14
|
-
branches: [ "main" ]
|
15
|
-
paths:
|
16
|
-
- 'lib/**'
|
17
|
-
- 'spec/**'
|
18
|
-
- 'Gemfile'
|
19
|
-
- 'Rakefile'
|
20
|
-
- 'ruby_llm.gemspec'
|
21
|
-
- '.github/workflows/cicd.yml'
|
22
|
-
workflow_call:
|
23
|
-
|
24
|
-
# Define default permissions for this workflow
|
25
|
-
permissions:
|
26
|
-
contents: read
|
27
|
-
packages: write # Needed for publishing to GitHub Packages
|
28
|
-
|
29
|
-
jobs:
|
30
|
-
test:
|
31
|
-
runs-on: ubuntu-latest
|
32
|
-
strategy:
|
33
|
-
matrix:
|
34
|
-
ruby-version: ['3.1']
|
35
|
-
|
36
|
-
steps:
|
37
|
-
- uses: actions/checkout@v4
|
38
|
-
|
39
|
-
- name: Set up Ruby
|
40
|
-
uses: ruby/setup-ruby@v1
|
41
|
-
with:
|
42
|
-
ruby-version: ${{ matrix.ruby-version }}
|
43
|
-
bundler-cache: true
|
44
|
-
|
45
|
-
- name: Install dependencies
|
46
|
-
run: bundle install
|
47
|
-
|
48
|
-
- name: Check code format
|
49
|
-
run: bundle exec rubocop
|
50
|
-
|
51
|
-
- name: Run tests
|
52
|
-
env:
|
53
|
-
# For PRs, we use VCR cassettes
|
54
|
-
# For main branch, we use real API keys for verification
|
55
|
-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
56
|
-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
57
|
-
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
58
|
-
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
59
|
-
run: bundle exec rspec
|
60
|
-
|
61
|
-
- name: Upload coverage to Codecov
|
62
|
-
uses: codecov/codecov-action@v5
|
63
|
-
env:
|
64
|
-
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
65
|
-
with:
|
66
|
-
files: ./coverage/coverage.xml
|
67
|
-
fail_ci_if_error: false
|
68
|
-
|
69
|
-
publish:
|
70
|
-
name: Build + Publish
|
71
|
-
needs: test
|
72
|
-
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
73
|
-
runs-on: ubuntu-latest
|
74
|
-
permissions:
|
75
|
-
contents: read
|
76
|
-
packages: write
|
77
|
-
|
78
|
-
steps:
|
79
|
-
- uses: actions/checkout@v4
|
80
|
-
|
81
|
-
- name: Set up Ruby
|
82
|
-
uses: ruby/setup-ruby@v1
|
83
|
-
with:
|
84
|
-
ruby-version: '3.3'
|
85
|
-
bundler-cache: true
|
86
|
-
|
87
|
-
- name: Check if version has changed
|
88
|
-
id: check_version
|
89
|
-
run: |
|
90
|
-
VERSION=$(ruby -r ./lib/ruby_llm/version.rb -e "puts RubyLLM::VERSION")
|
91
|
-
echo "Current version: $VERSION"
|
92
|
-
|
93
|
-
# Try to fetch from RubyGems
|
94
|
-
PUBLISHED_VERSION=$(gem list ruby_llm -r | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+' || echo "0.0.0")
|
95
|
-
echo "Published version: $PUBLISHED_VERSION"
|
96
|
-
|
97
|
-
if [ "$VERSION" = "$PUBLISHED_VERSION" ]; then
|
98
|
-
echo "Version has not changed, skipping publish"
|
99
|
-
echo "version_changed=false" >> $GITHUB_OUTPUT
|
100
|
-
else
|
101
|
-
echo "Version has changed from $PUBLISHED_VERSION to $VERSION"
|
102
|
-
echo "version_changed=true" >> $GITHUB_OUTPUT
|
103
|
-
fi
|
104
|
-
|
105
|
-
- name: Test with real APIs before publishing
|
106
|
-
if: steps.check_version.outputs.version_changed == 'true'
|
107
|
-
run: |
|
108
|
-
echo "Removing all VCR cassettes to test against real APIs..."
|
109
|
-
rm -rf spec/fixtures/vcr_cassettes
|
110
|
-
|
111
|
-
echo "Running tests with real API calls..."
|
112
|
-
env -u CI bundle exec rspec
|
113
|
-
env:
|
114
|
-
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
|
115
|
-
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
|
116
|
-
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
117
|
-
DEEPSEEK_API_KEY: ${{ secrets.DEEPSEEK_API_KEY }}
|
118
|
-
|
119
|
-
- name: Publish to GPR
|
120
|
-
if: steps.check_version.outputs.version_changed == 'true'
|
121
|
-
run: |
|
122
|
-
mkdir -p $HOME/.gem
|
123
|
-
touch $HOME/.gem/credentials
|
124
|
-
chmod 0600 $HOME/.gem/credentials
|
125
|
-
printf -- "---\n:github: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
126
|
-
gem build *.gemspec
|
127
|
-
output=$(gem push --KEY github --host https://rubygems.pkg.github.com/${OWNER} *.gem 2>&1) || {
|
128
|
-
echo "$output"
|
129
|
-
if echo "$output" | grep -q "already been pushed"; then
|
130
|
-
echo "Version already exists, skipping"
|
131
|
-
exit 0
|
132
|
-
else
|
133
|
-
exit 1
|
134
|
-
fi
|
135
|
-
}
|
136
|
-
env:
|
137
|
-
GEM_HOST_API_KEY: "Bearer ${{secrets.GITHUB_TOKEN}}"
|
138
|
-
OWNER: ${{ github.repository_owner }}
|
139
|
-
|
140
|
-
- name: Publish to RubyGems
|
141
|
-
if: steps.check_version.outputs.version_changed == 'true'
|
142
|
-
run: |
|
143
|
-
mkdir -p $HOME/.gem
|
144
|
-
touch $HOME/.gem/credentials
|
145
|
-
chmod 0600 $HOME/.gem/credentials
|
146
|
-
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
147
|
-
gem build *.gemspec
|
148
|
-
output=$(gem push *.gem 2>&1) || {
|
149
|
-
echo "$output"
|
150
|
-
if echo "$output" | grep -q "Repushing of gem versions is not allowed"; then
|
151
|
-
echo "Version already exists, skipping"
|
152
|
-
exit 0
|
153
|
-
else
|
154
|
-
exit 1
|
155
|
-
fi
|
156
|
-
}
|
157
|
-
env:
|
158
|
-
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
data/.github/workflows/docs.yml
DELETED
@@ -1,53 +0,0 @@
|
|
1
|
-
name: Deploy docs
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches: [main]
|
6
|
-
paths:
|
7
|
-
- 'docs/**'
|
8
|
-
- '.github/workflows/docs.yml'
|
9
|
-
workflow_dispatch:
|
10
|
-
|
11
|
-
permissions:
|
12
|
-
contents: read
|
13
|
-
pages: write
|
14
|
-
id-token: write
|
15
|
-
|
16
|
-
concurrency:
|
17
|
-
group: "pages"
|
18
|
-
cancel-in-progress: false
|
19
|
-
|
20
|
-
jobs:
|
21
|
-
build:
|
22
|
-
runs-on: ubuntu-latest
|
23
|
-
steps:
|
24
|
-
- name: Checkout
|
25
|
-
uses: actions/checkout@v4
|
26
|
-
- name: Setup Ruby
|
27
|
-
uses: ruby/setup-ruby@v1
|
28
|
-
with:
|
29
|
-
ruby-version: '3.3'
|
30
|
-
bundler-cache: true
|
31
|
-
working-directory: docs
|
32
|
-
- name: Setup Pages
|
33
|
-
uses: actions/configure-pages@v4
|
34
|
-
- name: Build with Jekyll
|
35
|
-
working-directory: docs
|
36
|
-
run: bundle exec jekyll build --baseurl "${{ steps.pages.outputs.base_path }}"
|
37
|
-
env:
|
38
|
-
JEKYLL_ENV: production
|
39
|
-
- name: Upload artifact
|
40
|
-
uses: actions/upload-pages-artifact@v3
|
41
|
-
with:
|
42
|
-
path: docs/_site
|
43
|
-
|
44
|
-
deploy:
|
45
|
-
environment:
|
46
|
-
name: github-pages
|
47
|
-
url: ${{ steps.deployment.outputs.page_url }}
|
48
|
-
runs-on: ubuntu-latest
|
49
|
-
needs: build
|
50
|
-
steps:
|
51
|
-
- name: Deploy to GitHub Pages
|
52
|
-
id: deployment
|
53
|
-
uses: actions/deploy-pages@v4
|
data/.gitignore
DELETED
@@ -1,59 +0,0 @@
|
|
1
|
-
*.gem
|
2
|
-
*.rbc
|
3
|
-
/.config
|
4
|
-
/coverage/
|
5
|
-
/InstalledFiles
|
6
|
-
/pkg/
|
7
|
-
/spec/reports/
|
8
|
-
/spec/examples.txt
|
9
|
-
/test/tmp/
|
10
|
-
/test/version_tmp/
|
11
|
-
/tmp/
|
12
|
-
.rspec_status
|
13
|
-
|
14
|
-
# Used by dotenv library to load environment variables.
|
15
|
-
.env
|
16
|
-
|
17
|
-
# Ignore Byebug command history file.
|
18
|
-
.byebug_history
|
19
|
-
|
20
|
-
## Specific to RubyMotion:
|
21
|
-
.dat*
|
22
|
-
.repl_history
|
23
|
-
build/
|
24
|
-
*.bridgesupport
|
25
|
-
build-iPhoneOS/
|
26
|
-
build-iPhoneSimulator/
|
27
|
-
|
28
|
-
## Specific to RubyMotion (use of CocoaPods):
|
29
|
-
#
|
30
|
-
# We recommend against adding the Pods directory to your .gitignore. However
|
31
|
-
# you should judge for yourself, the pros and cons are mentioned at:
|
32
|
-
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
33
|
-
#
|
34
|
-
# vendor/Pods/
|
35
|
-
|
36
|
-
## Documentation cache and generated files:
|
37
|
-
/.yardoc/
|
38
|
-
/_yardoc/
|
39
|
-
/doc/
|
40
|
-
/rdoc/
|
41
|
-
|
42
|
-
## Environment normalization:
|
43
|
-
/.bundle/
|
44
|
-
/vendor/bundle
|
45
|
-
/lib/bundler/man/
|
46
|
-
|
47
|
-
# for a library or gem, you might want to ignore these files since the code is
|
48
|
-
# intended to run in multiple environments; otherwise, check them in:
|
49
|
-
Gemfile.lock
|
50
|
-
# .ruby-version
|
51
|
-
# .ruby-gemset
|
52
|
-
|
53
|
-
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
54
|
-
.rvmrc
|
55
|
-
|
56
|
-
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
57
|
-
# .rubocop-https?--*
|
58
|
-
|
59
|
-
repomix-output.*
|
data/.overcommit.yml
DELETED
@@ -1,26 +0,0 @@
|
|
1
|
-
PreCommit:
|
2
|
-
RuboCop:
|
3
|
-
enabled: true
|
4
|
-
auto_correct: true
|
5
|
-
on_warn: fail # Treat all warnings as failures
|
6
|
-
|
7
|
-
Flay:
|
8
|
-
enabled: true
|
9
|
-
|
10
|
-
RSpec:
|
11
|
-
enabled: true
|
12
|
-
command: ['bundle', 'exec', 'rspec']
|
13
|
-
on_warn: fail
|
14
|
-
|
15
|
-
TrailingWhitespace:
|
16
|
-
enabled: true
|
17
|
-
auto_correct: true
|
18
|
-
exclude:
|
19
|
-
- '**/db/structure.sql' # Ignore trailing whitespace in generated files
|
20
|
-
|
21
|
-
PostCheckout:
|
22
|
-
ALL: # Special hook name that customizes all hooks of this type
|
23
|
-
quiet: true # Change all post-checkout hooks to only display output on failure
|
24
|
-
|
25
|
-
IndexTags:
|
26
|
-
enabled: true # Generate a tags file with `ctags` each time HEAD changes
|
data/.rspec
DELETED