Skip to content

Commit 421dac2

Browse files
committed
VCR support.
Thanks to @dalthon for inspiration in his PR #57
1 parent ec6a561 commit 421dac2

File tree

48 files changed

+19294
-50
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+19294
-50
lines changed

.rspec_status

Lines changed: 48 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,50 +1,50 @@
11
example_id | status | run_time |
22
-------------------------------------------------- | ------ | --------------- |
3-
./spec/ruby_llm/active_record/acts_as_spec.rb[1:1] | passed | 3.38 seconds |
4-
./spec/ruby_llm/active_record/acts_as_spec.rb[1:2] | passed | 2.48 seconds |
5-
./spec/ruby_llm/chat_content_spec.rb[1:1:1] | passed | 2.74 seconds |
6-
./spec/ruby_llm/chat_content_spec.rb[1:1:2] | passed | 1.29 seconds |
7-
./spec/ruby_llm/chat_content_spec.rb[1:1:3] | passed | 2.54 seconds |
8-
./spec/ruby_llm/chat_content_spec.rb[1:2:1] | passed | 2.77 seconds |
9-
./spec/ruby_llm/chat_content_spec.rb[1:2:2] | passed | 2.1 seconds |
10-
./spec/ruby_llm/chat_pdf_spec.rb[1:1:1] | passed | 7.75 seconds |
11-
./spec/ruby_llm/chat_pdf_spec.rb[1:1:2] | passed | 13.88 seconds |
12-
./spec/ruby_llm/chat_spec.rb[1:1:1:1] | passed | 1.02 seconds |
13-
./spec/ruby_llm/chat_spec.rb[1:1:1:2] | passed | 3.95 seconds |
14-
./spec/ruby_llm/chat_spec.rb[1:1:2:1] | passed | 0.4854 seconds |
15-
./spec/ruby_llm/chat_spec.rb[1:1:2:2] | passed | 1.37 seconds |
16-
./spec/ruby_llm/chat_spec.rb[1:1:3:1] | passed | 7.34 seconds |
17-
./spec/ruby_llm/chat_spec.rb[1:1:3:2] | passed | 19.22 seconds |
18-
./spec/ruby_llm/chat_spec.rb[1:1:4:1] | passed | 3.15 seconds |
19-
./spec/ruby_llm/chat_spec.rb[1:1:4:2] | passed | 2.51 seconds |
20-
./spec/ruby_llm/chat_streaming_spec.rb[1:1:1] | passed | 0.65115 seconds |
21-
./spec/ruby_llm/chat_streaming_spec.rb[1:1:2] | passed | 0.50907 seconds |
22-
./spec/ruby_llm/chat_streaming_spec.rb[1:1:3] | passed | 6.69 seconds |
23-
./spec/ruby_llm/chat_streaming_spec.rb[1:1:4] | passed | 0.70777 seconds |
24-
./spec/ruby_llm/chat_tools_spec.rb[1:1:1] | passed | 4.23 seconds |
25-
./spec/ruby_llm/chat_tools_spec.rb[1:1:2] | passed | 8.45 seconds |
26-
./spec/ruby_llm/chat_tools_spec.rb[1:1:3] | passed | 8.22 seconds |
27-
./spec/ruby_llm/chat_tools_spec.rb[1:1:4] | passed | 1.16 seconds |
28-
./spec/ruby_llm/chat_tools_spec.rb[1:1:5] | passed | 2.73 seconds |
29-
./spec/ruby_llm/chat_tools_spec.rb[1:1:6] | passed | 3.33 seconds |
30-
./spec/ruby_llm/chat_tools_spec.rb[1:1:7] | passed | 1.76 seconds |
31-
./spec/ruby_llm/chat_tools_spec.rb[1:1:8] | passed | 3 seconds |
32-
./spec/ruby_llm/chat_tools_spec.rb[1:1:9] | passed | 4.47 seconds |
33-
./spec/ruby_llm/embeddings_spec.rb[1:1:1:1] | passed | 0.33357 seconds |
34-
./spec/ruby_llm/embeddings_spec.rb[1:1:1:2] | passed | 0.43632 seconds |
35-
./spec/ruby_llm/embeddings_spec.rb[1:1:2:1] | passed | 0.65614 seconds |
36-
./spec/ruby_llm/embeddings_spec.rb[1:1:2:2] | passed | 2.16 seconds |
37-
./spec/ruby_llm/error_handling_spec.rb[1:1] | passed | 0.29366 seconds |
38-
./spec/ruby_llm/image_generation_spec.rb[1:1:1] | passed | 14.16 seconds |
39-
./spec/ruby_llm/image_generation_spec.rb[1:1:2] | passed | 16.22 seconds |
40-
./spec/ruby_llm/image_generation_spec.rb[1:1:3] | passed | 9.1 seconds |
41-
./spec/ruby_llm/image_generation_spec.rb[1:1:4] | passed | 0.00138 seconds |
42-
./spec/ruby_llm/models_spec.rb[1:1:1] | passed | 0.01071 seconds |
43-
./spec/ruby_llm/models_spec.rb[1:1:2] | passed | 0.00056 seconds |
44-
./spec/ruby_llm/models_spec.rb[1:1:3] | passed | 0.00336 seconds |
45-
./spec/ruby_llm/models_spec.rb[1:2:1] | passed | 0.00016 seconds |
46-
./spec/ruby_llm/models_spec.rb[1:2:2] | passed | 0.00085 seconds |
47-
./spec/ruby_llm/models_spec.rb[1:3:1] | passed | 1.44 seconds |
48-
./spec/ruby_llm/models_spec.rb[1:3:2] | passed | 1.23 seconds |
49-
./spec/ruby_llm/models_spec.rb[1:4:1] | passed | 0.0003 seconds |
50-
./spec/ruby_llm/models_spec.rb[1:4:2] | passed | 0.00175 seconds |
3+
./spec/ruby_llm/active_record/acts_as_spec.rb[1:1] | passed | 2.27 seconds |
4+
./spec/ruby_llm/active_record/acts_as_spec.rb[1:2] | passed | 1.73 seconds |
5+
./spec/ruby_llm/chat_content_spec.rb[1:1:1] | passed | 0.0149 seconds |
6+
./spec/ruby_llm/chat_content_spec.rb[1:1:2] | passed | 0.01414 seconds |
7+
./spec/ruby_llm/chat_content_spec.rb[1:1:3] | passed | 2.03 seconds |
8+
./spec/ruby_llm/chat_content_spec.rb[1:2:1] | passed | 1.35 seconds |
9+
./spec/ruby_llm/chat_content_spec.rb[1:2:2] | passed | 1.83 seconds |
10+
./spec/ruby_llm/chat_pdf_spec.rb[1:1:1] | passed | 0.01086 seconds |
11+
./spec/ruby_llm/chat_pdf_spec.rb[1:1:2] | passed | 0.02171 seconds |
12+
./spec/ruby_llm/chat_pdf_spec.rb[1:1:3] | passed | 0.01188 seconds |
13+
./spec/ruby_llm/chat_pdf_spec.rb[1:1:4] | passed | 0.01501 seconds |
14+
./spec/ruby_llm/chat_spec.rb[1:1:1] | passed | 0.00505 seconds |
15+
./spec/ruby_llm/chat_spec.rb[1:1:2] | passed | 0.00539 seconds |
16+
./spec/ruby_llm/chat_spec.rb[1:1:3] | passed | 0.00393 seconds |
17+
./spec/ruby_llm/chat_spec.rb[1:1:4] | passed | 0.0118 seconds |
18+
./spec/ruby_llm/chat_spec.rb[1:1:5] | passed | 0.01354 seconds |
19+
./spec/ruby_llm/chat_spec.rb[1:1:6] | passed | 0.01394 seconds |
20+
./spec/ruby_llm/chat_spec.rb[1:1:7] | passed | 2.09 seconds |
21+
./spec/ruby_llm/chat_spec.rb[1:1:8] | passed | 5.04 seconds |
22+
./spec/ruby_llm/chat_streaming_spec.rb[1:1:1] | passed | 0.00787 seconds |
23+
./spec/ruby_llm/chat_streaming_spec.rb[1:1:2] | passed | 0.00627 seconds |
24+
./spec/ruby_llm/chat_streaming_spec.rb[1:1:3] | passed | 0.00781 seconds |
25+
./spec/ruby_llm/chat_streaming_spec.rb[1:1:4] | passed | 0.55507 seconds |
26+
./spec/ruby_llm/chat_tools_spec.rb[1:1:1] | passed | 0.00892 seconds |
27+
./spec/ruby_llm/chat_tools_spec.rb[1:1:2] | passed | 0.02148 seconds |
28+
./spec/ruby_llm/chat_tools_spec.rb[1:1:3] | passed | 0.10751 seconds |
29+
./spec/ruby_llm/chat_tools_spec.rb[1:1:4] | passed | 0.01023 seconds |
30+
./spec/ruby_llm/chat_tools_spec.rb[1:1:5] | passed | 0.01184 seconds |
31+
./spec/ruby_llm/chat_tools_spec.rb[1:1:6] | passed | 0.02112 seconds |
32+
./spec/ruby_llm/chat_tools_spec.rb[1:1:7] | passed | 1.77 seconds |
33+
./spec/ruby_llm/chat_tools_spec.rb[1:1:8] | passed | 3.84 seconds |
34+
./spec/ruby_llm/chat_tools_spec.rb[1:1:9] | passed | 4.08 seconds |
35+
./spec/ruby_llm/embeddings_spec.rb[1:1:1] | passed | 0.01236 seconds |
36+
./spec/ruby_llm/embeddings_spec.rb[1:1:2] | passed | 0.03884 seconds |
37+
./spec/ruby_llm/embeddings_spec.rb[1:1:3] | passed | 1.11 seconds |
38+
./spec/ruby_llm/embeddings_spec.rb[1:1:4] | passed | 0.86723 seconds |
39+
./spec/ruby_llm/error_handling_spec.rb[1:1] | passed | 0.26252 seconds |
40+
./spec/ruby_llm/image_generation_spec.rb[1:1:1] | passed | 13.38 seconds |
41+
./spec/ruby_llm/image_generation_spec.rb[1:1:2] | passed | 17.34 seconds |
42+
./spec/ruby_llm/image_generation_spec.rb[1:1:3] | passed | 0.13049 seconds |
43+
./spec/ruby_llm/image_generation_spec.rb[1:1:4] | passed | 0.0003 seconds |
44+
./spec/ruby_llm/models_spec.rb[1:1:1] | passed | 0.00418 seconds |
45+
./spec/ruby_llm/models_spec.rb[1:1:2] | passed | 0.00088 seconds |
46+
./spec/ruby_llm/models_spec.rb[1:1:3] | passed | 0.00052 seconds |
47+
./spec/ruby_llm/models_spec.rb[1:2:1] | passed | 0.00017 seconds |
48+
./spec/ruby_llm/models_spec.rb[1:2:2] | passed | 0.00013 seconds |
49+
./spec/ruby_llm/models_spec.rb[1:3:1] | passed | 1.58 seconds |
50+
./spec/ruby_llm/models_spec.rb[1:3:2] | passed | 1.25 seconds |

CONTRIBUTING.md

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Contributing to RubyLLM
2+
3+
First off, thank you for considering contributing to RubyLLM! It's people like you that make RubyLLM such a great tool.
4+
5+
## Development Setup
6+
7+
Here's how to get started:
8+
9+
```bash
10+
# Clone the repository
11+
git clone https://github.com/crmne/ruby_llm.git
12+
cd ruby_llm
13+
14+
# Install dependencies
15+
bundle install
16+
17+
# Set up git hooks
18+
overcommit --install
19+
20+
# Run the tests (uses VCR cassettes)
21+
bundle exec rspec
22+
```
23+
24+
## Development Workflow
25+
26+
We recommend using GitHub CLI to simplify the workflow:
27+
28+
```bash
29+
# Create a new branch for your feature
30+
gh repo fork crmne/ruby_llm --clone
31+
cd ruby_llm
32+
git checkout -b my-new-feature
33+
34+
# Make your changes and test them
35+
# ...
36+
37+
# Commit your changes
38+
git commit
39+
40+
# Create a PR
41+
gh pr create --web
42+
```
43+
44+
## Running Tests
45+
46+
Tests automatically use VCR to record and replay HTTP interactions, so you don't need real API keys for testing:
47+
48+
```bash
49+
# Run all tests (using existing VCR cassettes)
50+
bundle exec rspec
51+
52+
# Run a specific test file
53+
bundle exec rspec spec/ruby_llm/chat_spec.rb
54+
```
55+
56+
### Recording VCR Cassettes
57+
58+
When you make changes that affect API interactions, you can record new VCR cassettes.
59+
60+
If you have keys for all providers:
61+
62+
```bash
63+
# Re-record all cassettes
64+
bundle exec rake vcr:record[all]
65+
```
66+
67+
If you only have keys for specific providers (e.g., just OpenAI):
68+
69+
```bash
70+
# Set the API keys you have
71+
export OPENAI_API_KEY=your_openai_key
72+
73+
# Find and remove only cassettes for OpenAI, then run tests to re-record them
74+
bundle exec rake vcr:record[openai]
75+
76+
# You can also specify multiple providers
77+
bundle exec rake vcr:record[openai,anthropic]
78+
```
79+
80+
Important: After recording new cassettes, please **manually check** them for any sensitive information that might have been missed by the automatic filters.
81+
82+
## Adding New Tests
83+
84+
Tests automatically create VCR cassettes based on their descriptions, so make sure your test descriptions are unique and descriptive.
85+
86+
## Coding Style
87+
88+
We follow the [Standard Ruby](https://github.com/testdouble/standard) style. Please ensure your contributions adhere to this style.
89+
90+
```bash
91+
# Check your code style
92+
bundle exec rubocop
93+
94+
# Auto-fix style issues where possible
95+
bundle exec rubocop -A
96+
```
97+
98+
## Documentation
99+
100+
When adding new features, please include documentation updates:
101+
102+
- Update relevant guides in the `docs/guides/` directory
103+
- Add inline documentation using YARD comments
104+
- Keep the README clean and focused on helping new users get started quickly
105+
106+
## Philosophy
107+
108+
RubyLLM follows certain design philosophies and conventions. Please refer to our [Philosophy Guide](https://rubyllm.com/philosophy) to ensure your contributions align with the project's vision.
109+
110+
## Discussions and Issues
111+
112+
- For questions and discussions, please use [GitHub Discussions](https://github.com/crmne/ruby_llm/discussions)
113+
- For bugs and feature requests, please use [GitHub Issues](https://github.com/crmne/ruby_llm/issues)
114+
115+
## Release Process
116+
117+
Gem versioning follows [Semantic Versioning](https://semver.org/):
118+
119+
1. MAJOR version for incompatible API changes
120+
2. MINOR version for backwards-compatible functionality
121+
3. PATCH version for backwards-compatible bug fixes
122+
123+
Releases are handled by the maintainers through the CI/CD pipeline.
124+
125+
Thanks for helping make RubyLLM better!

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ group :development do
2727
gem 'simplecov', '>= 0.21'
2828
gem 'simplecov-cobertura'
2929
gem 'sqlite3'
30+
gem 'vcr'
3031
gem 'webmock', '~> 3.18'
3132
gem 'yard', '>= 0.9'
3233
end

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,18 @@ chat.with_tool(Search).ask "Find documents about Ruby 3.3 features"
193193

194194
Check out the guides at https://rubyllm.com for deeper dives into conversations with tools, streaming responses, embedding generations, and more.
195195

196+
## Contributing
197+
198+
We welcome contributions to RubyLLM!
199+
200+
See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed instructions on how to:
201+
- Run the test suite
202+
- Add new features
203+
- Update documentation
204+
- Re-record VCR cassettes when needed
205+
206+
We appreciate your help making RubyLLM better!
207+
196208
## License
197209

198210
Released under the MIT License.

lib/tasks/vcr.rake

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# frozen_string_literal: true
2+
3+
# Helper functions at the top level
4+
def record_all_cassettes(cassette_dir)
5+
# Re-record all cassettes
6+
FileUtils.rm_rf(cassette_dir)
7+
FileUtils.mkdir_p(cassette_dir)
8+
9+
puts 'Recording cassettes for all providers...'
10+
run_tests
11+
puts 'Done recording. Please review the new cassettes.'
12+
end
13+
14+
def record_for_providers(providers, cassette_dir) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
15+
# Get the list of available providers from RubyLLM itself
16+
all_providers = RubyLLM::Provider.providers.keys.map(&:to_s)
17+
18+
# Check for valid providers
19+
if providers.empty?
20+
puts "Please specify providers or 'all'. Example: rake vcr:record[openai,anthropic]"
21+
puts "Available providers: #{all_providers.join(', ')}"
22+
return
23+
end
24+
25+
invalid_providers = providers - all_providers
26+
if invalid_providers.any?
27+
puts "Invalid providers: #{invalid_providers.join(', ')}"
28+
puts "Available providers: #{all_providers.join(', ')}"
29+
return
30+
end
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+
# Find and delete matching cassettes
38+
cassettes_to_delete = find_matching_cassettes(cassette_dir, provider_patterns)
39+
40+
if cassettes_to_delete.empty?
41+
puts 'No cassettes found for the specified providers.'
42+
puts 'Running tests to record new cassettes...'
43+
else
44+
delete_cassettes(cassettes_to_delete)
45+
puts "\nRunning tests to record new cassettes..."
46+
end
47+
48+
run_tests
49+
50+
puts "\nDone recording cassettes for #{providers.join(', ')}."
51+
puts 'Please review the updated cassettes for sensitive information.'
52+
end
53+
54+
def get_provider_patterns(providers) # rubocop:disable Metrics/MethodLength
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)
76+
cassettes = []
77+
78+
Dir.glob("#{dir}/**/*.yml").each do |file|
79+
content = File.read(file)
80+
cassettes << file if patterns.values.any? { |pattern| content.match?(pattern) }
81+
end
82+
83+
cassettes
84+
end
85+
86+
def delete_cassettes(cassettes)
87+
puts "Deleting #{cassettes.size} cassettes for re-recording:"
88+
cassettes.each do |file|
89+
puts " - #{File.basename(file)}"
90+
File.delete(file)
91+
end
92+
end
93+
94+
def run_tests
95+
system('bundle exec rspec') || abort('Tests failed')
96+
end
97+
98+
namespace :vcr do
99+
desc 'Record VCR cassettes (rake vcr:record[all] or vcr:record[openai,anthropic])'
100+
task :record, [:providers] do |_, args|
101+
require 'fileutils'
102+
require 'ruby_llm'
103+
104+
providers = (args[:providers] || '').downcase.split(',')
105+
cassette_dir = 'spec/fixtures/vcr_cassettes'
106+
FileUtils.mkdir_p(cassette_dir)
107+
108+
if providers.include?('all')
109+
record_all_cassettes(cassette_dir)
110+
else
111+
record_for_providers(providers, cassette_dir)
112+
end
113+
end
114+
end

0 commit comments

Comments
 (0)