Skip to content

Commit d628495

Browse files
committed
feat: add support for assuming model existence in chat and with_model methods
1 parent 7017dcf commit d628495

File tree

4 files changed

+212
-8
lines changed

4 files changed

+212
-8
lines changed

lib/ruby_llm.rb

+2-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,8 @@ module RubyLLM
3030
class Error < StandardError; end
3131

3232
class << self
33-
def chat(model: nil, provider: nil)
34-
Chat.new(model: model, provider: provider)
33+
def chat(model: nil, provider: nil, assume_model_exists: false)
34+
Chat.new(model:, provider:, assume_model_exists:)
3535
end
3636

3737
def embed(...)

lib/ruby_llm/chat.rb

+19-6
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,18 @@ module RubyLLM
88
# chat = RubyLLM.chat
99
# chat.ask "What's the best way to learn Ruby?"
1010
# chat.ask "Can you elaborate on that?"
11-
class Chat
11+
class Chat # rubocop:disable Metrics/ClassLength
1212
include Enumerable
1313

1414
attr_reader :model, :messages, :tools
1515

16-
def initialize(model: nil, provider: nil)
16+
def initialize(model: nil, provider: nil, assume_model_exists: false) # rubocop:disable Metrics/MethodLength
17+
if assume_model_exists && !provider
18+
raise ArgumentError, 'Provider must be specified if assume_model_exists is true'
19+
end
20+
1721
model_id = model || RubyLLM.config.default_model
18-
with_model(model_id, provider: provider)
22+
with_model(model_id, provider: provider, assume_exists: assume_model_exists)
1923
@temperature = 0.7
2024
@messages = []
2125
@tools = {}
@@ -54,9 +58,18 @@ def with_tools(*tools)
5458
self
5559
end
5660

57-
def with_model(model_id, provider: nil)
58-
@model = Models.find model_id, provider
59-
@provider = Provider.providers[@model.provider.to_sym] || raise(Error, "Unknown provider: #{@model.provider}")
61+
def with_model(model_id, provider: nil, assume_exists: false) # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
62+
if assume_exists
63+
raise ArgumentError, 'Provider must be specified if assume_exists is true' unless provider
64+
65+
@provider = Provider.providers[provider.to_sym] || raise(Error, "Unknown provider: #{provider.to_sym}")
66+
@model = Struct.new(:id, :provider, :supports_functions, :supports_vision).new(model_id, provider, true, true)
67+
RubyLLM.logger.warn "Assuming model '#{model_id}' exists for provider '#{provider}'. " \
68+
'Capabilities may not be accurately reflected.'
69+
else
70+
@model = Models.find model_id, provider
71+
@provider = Provider.providers[@model.provider.to_sym] || raise(Error, "Unknown provider: #{@model.provider}")
72+
end
6073
self
6174
end
6275

spec/fixtures/vcr_cassettes/chat_assume_model_exists_works_with_models_not_in_registry_but_available_in_api.yml

+111
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# frozen_string_literal: true
2+
3+
require 'spec_helper'
4+
5+
RSpec.describe RubyLLM::Chat do
6+
include_context 'with configured RubyLLM'
7+
8+
describe '#assume_model_exists' do
9+
let(:real_model) { 'gpt-4.1-nano' }
10+
let(:custom_model) { 'my-custom-model' }
11+
let(:provider) { :openai }
12+
# Keep a reference to the original models for cleanup
13+
let!(:original_models) { RubyLLM::Models.instance.all.dup }
14+
15+
# Clean up the model registry after each test
16+
after do
17+
RubyLLM::Models.instance.instance_variable_set(:@models, original_models)
18+
end
19+
20+
it 'requires provider when assuming model exists' do
21+
expect do
22+
described_class.new(model: custom_model, assume_model_exists: true)
23+
end.to raise_error(ArgumentError, /Provider must be specified/)
24+
end
25+
26+
it 'skips registry validation when assuming model exists' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
27+
expect(RubyLLM::Models).not_to receive(:find) # rubocop:disable RSpec/MessageSpies
28+
29+
chat = described_class.new(
30+
model: custom_model,
31+
provider: provider,
32+
assume_model_exists: true
33+
)
34+
35+
expect(chat.model.id).to eq(custom_model)
36+
expect(chat.model.provider).to eq(provider)
37+
end
38+
39+
it 'works with RubyLLM.chat convenience method' do # rubocop:disable RSpec/ExampleLength
40+
chat = RubyLLM.chat(
41+
model: custom_model,
42+
provider: provider,
43+
assume_model_exists: true
44+
)
45+
46+
expect(chat.model.id).to eq(custom_model)
47+
end
48+
49+
it 'works with models not in registry but available in API' do # rubocop:disable RSpec/ExampleLength,RSpec/MultipleExpectations
50+
# Simulate model missing from registry
51+
filtered_models = original_models.reject { |m| m.id == real_model }
52+
RubyLLM::Models.instance.instance_variable_set(:@models, filtered_models)
53+
54+
# Should raise error when not assuming existence
55+
expect do
56+
RubyLLM.chat(model: real_model)
57+
end.to raise_error(RubyLLM::ModelNotFoundError)
58+
59+
# Should work when assuming existence
60+
chat = RubyLLM.chat(
61+
model: real_model,
62+
provider: provider,
63+
assume_model_exists: true
64+
)
65+
66+
# Should be able to actually use the model (relies on VCR)
67+
response = chat.ask('What is 2 + 2?')
68+
expect(response.content).to include('4')
69+
end
70+
71+
it 'works with with_model method' do # rubocop:disable RSpec/MultipleExpectations
72+
chat = RubyLLM.chat
73+
74+
chat.with_model(custom_model, provider: provider, assume_exists: true)
75+
76+
expect(chat.model.id).to eq(custom_model)
77+
expect(chat.model.provider).to eq(provider)
78+
end
79+
end
80+
end

0 commit comments

Comments
 (0)