ruby_llm-providers-lms 0.1.1 → 0.2.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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +121 -0
  3. data/README.md +467 -104
  4. data/lib/ruby_llm/providers/lms/anthropic_messages.rb +28 -0
  5. data/lib/ruby_llm/providers/lms/endpoints.rb +18 -0
  6. data/lib/ruby_llm/providers/lms/model_management.rb +59 -0
  7. data/lib/ruby_llm/providers/lms/models.rb +44 -12
  8. data/lib/ruby_llm/providers/lms/native_chat/conversation.rb +93 -0
  9. data/lib/ruby_llm/providers/lms/native_chat/streaming.rb +67 -0
  10. data/lib/ruby_llm/providers/lms/native_chat.rb +134 -0
  11. data/lib/ruby_llm/providers/lms/native_v1.rb +97 -0
  12. data/lib/ruby_llm/providers/lms/responses.rb +47 -0
  13. data/lib/ruby_llm/providers/lms/version.rb +15 -0
  14. data/lib/ruby_llm/providers/lms.rb +51 -1
  15. data/models.json +69 -454
  16. metadata +12 -37
  17. data/.flayignore +0 -1
  18. data/.github/workflows/ci.yml +0 -29
  19. data/.github/workflows/gitleaks.yml +0 -22
  20. data/.github/workflows/release.yml +0 -36
  21. data/.overcommit.yml +0 -31
  22. data/.rspec +0 -2
  23. data/.rubocop.yml +0 -29
  24. data/Archspec.rb +0 -14
  25. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_handle_a_multi_turn_conversation.yml +0 -92
  26. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_have_a_basic_conversation.yml +0 -45
  27. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_use_tools.yml +0 -129
  28. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_returns_the_raw_response.yml +0 -73
  29. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_supports_streaming_responses.yml +0 -81
  30. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_can_have_a_basic_conversation.yml +0 -73
  31. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_returns_structured_output.yml +0 -73
  32. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_returns_the_raw_response.yml +0 -73
  33. data/spec/fixtures/vcr_cassettes/rubyllm_embedding_lms_text_embedding_nomic_embed_text_v1_5_embeds_one_text.yml +0 -828
  34. data/spec/fixtures/vcr_cassettes/rubyllm_embedding_lms_text_embedding_nomic_embed_text_v1_5_embeds_several_texts.yml +0 -2375
  35. data/spec/ruby_llm/chat_schema_spec.rb +0 -30
  36. data/spec/ruby_llm/chat_spec.rb +0 -35
  37. data/spec/ruby_llm/chat_streaming_spec.rb +0 -22
  38. data/spec/ruby_llm/chat_tools_spec.rb +0 -30
  39. data/spec/ruby_llm/embedding_spec.rb +0 -49
  40. data/spec/ruby_llm/image_spec.rb +0 -23
  41. data/spec/ruby_llm/models_spec.rb +0 -11
  42. data/spec/ruby_llm/moderation_spec.rb +0 -22
  43. data/spec/ruby_llm/providers/lms/connection_guard_spec.rb +0 -47
  44. data/spec/ruby_llm/providers/lms_spec.rb +0 -153
  45. data/spec/ruby_llm/rerank_spec.rb +0 -23
  46. data/spec/ruby_llm/speech_spec.rb +0 -25
  47. data/spec/ruby_llm/video_spec.rb +0 -27
  48. data/spec/spec_helper.rb +0 -26
  49. data/spec/support/models.rb +0 -29
  50. data/spec/support/rubyllm_configuration.rb +0 -14
  51. data/spec/support/vcr_configuration.rb +0 -24
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Chat, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- let(:person_schema) do
9
- {
10
- type: 'object',
11
- properties: {
12
- name: { type: 'string' },
13
- age: { type: 'integer' }
14
- },
15
- required: %w[name age],
16
- additionalProperties: false
17
- }
18
- end
19
-
20
- each_model(STRUCTURED_OUTPUT_MODELS) do |provider, model|
21
- it "#{provider}/#{model} returns structured output" do
22
- response = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
23
- .with_schema(person_schema)
24
- .ask('Generate a person named John who is 30 years old')
25
-
26
- expect(response.content).to be_a(String)
27
- expect(response.parsed).to include('name' => 'John', 'age' => 30)
28
- end
29
- end
30
- end
@@ -1,35 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Chat, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(CHAT_MODELS) do |provider, model|
9
- it "#{provider}/#{model} can have a basic conversation" do
10
- response = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true).ask("What's 2 + 2?")
11
-
12
- expect(response.content).to include('4')
13
- expect(response.role).to eq(:assistant)
14
- expect(response.tokens.input.to_i).to be_positive
15
- expect(response.tokens.output.to_i).to be_positive
16
- end
17
-
18
- it "#{provider}/#{model} returns the raw response" do
19
- response = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
20
- .ask('What is the capital of France?')
21
-
22
- expect(response.raw.status).to eq(200)
23
- expect(response.raw.headers).not_to be_empty
24
- expect(response.raw.body).not_to be_empty
25
- expect(response.raw.env.request_body).not_to be_empty
26
- end
27
-
28
- it "#{provider}/#{model} can handle a multi-turn conversation" do
29
- chat = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
30
-
31
- expect(chat.ask('Who created the programming language Ruby?').content).to match(/Matz|Matsumoto/i)
32
- expect(chat.ask('What year was Ruby first released?').content).to include('199')
33
- end
34
- end
35
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Chat, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(CHAT_MODELS) do |provider, model|
9
- it "#{provider}/#{model} supports streaming responses" do
10
- chunks = []
11
- chat = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true)
12
-
13
- response = chat.ask('Count from 1 to 3') { |chunk| chunks << chunk }
14
-
15
- expect(chunks).not_to be_empty
16
- expect(chunks.first).to be_a(RubyLLM::Chunk)
17
- expect(response.raw.status).to eq(200)
18
- expect(response.raw.headers).not_to be_empty
19
- expect(response.raw.env.request_body).not_to be_empty
20
- end
21
- end
22
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Chat, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- let(:weather_tool) do
9
- Class.new(RubyLLM::Tool) do
10
- description 'Gets current weather for a location'
11
- parameter :latitude, description: 'Latitude'
12
- parameter :longitude, description: 'Longitude'
13
-
14
- def execute(latitude:, longitude:)
15
- "Current weather at #{latitude}, #{longitude}: 15°C, Wind: 10 km/h"
16
- end
17
- end
18
- end
19
-
20
- each_model(TOOL_MODELS) do |provider, model|
21
- it "#{provider}/#{model} can use tools" do
22
- chat = RubyLLM.chat(model: model, provider: provider, assume_model_exists: true).with_tools(weather_tool)
23
- response = chat.ask("What's the weather in Berlin? Use 52.5200, 13.4050.")
24
-
25
- expect(response.content).to include('15')
26
- expect(response.content).to include('10')
27
- expect(chat.messages.any?(&:tool_call?)).to be(true)
28
- end
29
- end
30
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Embedding, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(EMBEDDING_MODELS) do |provider, model, model_info|
9
- it "#{provider}/#{model} embeds one text" do
10
- embedding = RubyLLM.embed(
11
- "Ruby is a programmer's best friend",
12
- model: model,
13
- provider: provider,
14
- assume_model_exists: true
15
- )
16
-
17
- expect(embedding.vectors).to be_an(Array)
18
- expect(embedding.vectors.first).to be_a(Numeric)
19
- expect(embedding.model).to eq(model)
20
- expect(embedding.tokens.input.to_i).to be >= 0
21
- end
22
-
23
- it "#{provider}/#{model} embeds several texts" do
24
- embeddings = RubyLLM.embed(
25
- %w[Ruby Python JavaScript],
26
- model: model,
27
- provider: provider,
28
- assume_model_exists: true
29
- )
30
-
31
- expect(embeddings.vectors.size).to eq(3)
32
- expect(embeddings.vectors).to all(be_an(Array))
33
- end
34
-
35
- next unless model_info[:dimensions]
36
-
37
- it "#{provider}/#{model} supports custom dimensions" do
38
- embedding = RubyLLM.embed(
39
- 'Ruby',
40
- model: model,
41
- provider: provider,
42
- assume_model_exists: true,
43
- dimensions: model_info[:dimensions]
44
- )
45
-
46
- expect(embedding.vectors.length).to eq(model_info[:dimensions])
47
- end
48
- end
49
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Image, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(IMAGE_GENERATION_MODELS) do |provider, model, model_info|
9
- it "#{provider}/#{model} paints an image" do
10
- image = RubyLLM.paint(
11
- 'a siamese cat',
12
- model: model,
13
- provider: provider,
14
- assume_model_exists: true,
15
- provider_options: model_info.fetch(:provider_options, {})
16
- )
17
-
18
- expect(image.mime_type).to include('image')
19
- expect(image.model).to eq(model)
20
- expect(image.to_blob.bytesize).to be > 1000
21
- end
22
- end
23
- end
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Models do
6
- include_context 'with configured RubyLLM'
7
-
8
- it 'expects generated providers to add registry metadata before use' do
9
- expect(RubyLLM::Providers::LMS.assume_models_exist?).to be(false)
10
- end
11
- end
@@ -1,22 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Moderation, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(MODERATION_MODELS) do |provider, model|
9
- it "#{provider}/#{model} moderates content" do
10
- moderation = RubyLLM.moderate(
11
- 'This is a safe message',
12
- model: model,
13
- provider: provider,
14
- assume_model_exists: true
15
- )
16
-
17
- expect(moderation).to be_a(described_class)
18
- expect(moderation.results).to all(be_a(RubyLLM::Moderation::Result))
19
- expect(moderation.flagged?).to be_in([true, false])
20
- end
21
- end
22
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Providers::LMS::ConnectionGuard do
6
- subject(:guard) { described_class.new(connection) }
7
-
8
- let(:provider) { instance_double(RubyLLM::Providers::LMS, api_base: 'http://localhost:1234/v1') }
9
- let(:connection) { instance_double(RubyLLM::Transport::Connection, provider: provider) }
10
-
11
- { get: ['models'], post: ['chat/completions', {}], patch: ['models', {}], delete: ['models'] }.each do |verb, args|
12
- it "wraps a connection failure on ##{verb} in a RubyLLM::Error" do
13
- allow(connection).to receive(verb).and_raise(Faraday::ConnectionFailed, 'Connection refused')
14
-
15
- expect { guard.public_send(verb, *args) }.to raise_error(RubyLLM::Error) do |error|
16
- expect(error.message).to include('http://localhost:1234/v1')
17
- expect(error.message).to include('lms server start')
18
- expect(error.cause).to be_a(Faraday::ConnectionFailed)
19
- end
20
- end
21
- end
22
-
23
- it 'passes successful calls through with arguments and block' do
24
- response = instance_double(Faraday::Response)
25
- request = Object.new
26
- allow(connection).to receive(:post).with('chat/completions', { model: 'x' }, usage: nil)
27
- .and_yield(request).and_return(response)
28
-
29
- yielded = nil
30
- result = guard.post('chat/completions', { model: 'x' }, usage: nil) { |req| yielded = req }
31
-
32
- expect(result).to be(response)
33
- expect(yielded).to be(request)
34
- end
35
-
36
- it 'lets other errors propagate untouched' do
37
- allow(connection).to receive(:get).and_raise(RubyLLM::UnauthorizedError.new('nope'))
38
-
39
- expect { guard.get('models') }.to raise_error(RubyLLM::UnauthorizedError, 'nope')
40
- end
41
-
42
- it 'names the server address and the start command in its message' do
43
- expect(guard.unreachable_message)
44
- .to eq('Cannot connect to the LM Studio server at http://localhost:1234/v1. ' \
45
- 'Start it with `lms server start`.')
46
- end
47
- end
@@ -1,153 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Providers::LMS do
6
- subject(:provider) { described_class.new(config) }
7
-
8
- let(:config) do
9
- RubyLLM::Configuration.new.tap do |provider_config|
10
- provider_config.lms_api_base = 'http://example.test:1234/v1'
11
- end
12
- end
13
-
14
- it 'is registered with RubyLLM' do
15
- expect(RubyLLM::Provider.resolve(:lms)).to eq(described_class)
16
- end
17
-
18
- it 'registers its protocols' do
19
- expect(described_class.protocols).to include(
20
- chat_completions: described_class::ChatCompletions,
21
- responses: RubyLLM::Protocols::Responses
22
- )
23
- end
24
-
25
- it 'declares provider configuration' do
26
- expect(described_class.configuration_options).to eq(%i[lms_api_base lms_api_key])
27
- expect(described_class.configuration_requirements).to eq([])
28
- end
29
-
30
- it 'is a local provider with a human-readable name' do
31
- expect(described_class.local?).to be(true)
32
- expect(described_class.display_name).to eq('LM Studio')
33
- end
34
-
35
- it 'uses the configured API base' do
36
- expect(provider.api_base).to eq('http://example.test:1234/v1')
37
- end
38
-
39
- it 'defaults to the LM Studio server address' do
40
- provider = described_class.new(RubyLLM::Configuration.new)
41
- expect(provider.api_base).to eq('http://localhost:1234/v1')
42
- end
43
-
44
- it 'sends no Authorization header without an API key' do
45
- expect(provider.headers).to eq({})
46
- end
47
-
48
- it 'sends a bearer token when an API key is configured' do
49
- config.lms_api_key = 'test-key'
50
- expect(provider.headers).to eq('Authorization' => 'Bearer test-key')
51
- end
52
-
53
- it 'guards its connection against a server that is not running' do
54
- expect(provider.connection).to be_a(described_class::ConnectionGuard)
55
- end
56
-
57
- describe 'model listing' do
58
- subject(:protocol) do
59
- described_class::ChatCompletions.new(described_class.new(config))
60
- end
61
-
62
- let(:llm_detail) do
63
- {
64
- 'type' => 'llm',
65
- 'arch' => 'qwen2',
66
- 'publisher' => 'lmstudio-community',
67
- 'compatibility_type' => 'gguf',
68
- 'quantization' => 'Q4_K_M',
69
- 'state' => 'loaded',
70
- 'max_context_length' => 32_768,
71
- 'capabilities' => ['tool_use']
72
- }
73
- end
74
- let(:vlm_detail) { { 'type' => 'vlm', 'arch' => 'qwen2_vl' } }
75
- let(:embedding_detail) { { 'type' => 'embeddings', 'arch' => 'nomic-bert' } }
76
-
77
- describe '#build_modalities' do
78
- it 'maps chat models to text in and out' do
79
- expect(protocol.build_modalities(llm_detail)).to eq(input: %w[text], output: %w[text])
80
- end
81
-
82
- it 'adds image input for vision models' do
83
- expect(protocol.build_modalities(vlm_detail)).to eq(input: %w[text image], output: %w[text])
84
- end
85
-
86
- it 'maps embedding models to embedding output' do
87
- expect(protocol.build_modalities(embedding_detail)).to eq(input: %w[text], output: %w[embeddings])
88
- end
89
- end
90
-
91
- describe '#build_capabilities' do
92
- it 'derives function calling from reported tool_use' do
93
- expect(protocol.build_capabilities(llm_detail))
94
- .to eq(%w[streaming structured_output function_calling])
95
- end
96
-
97
- it 'derives vision from the model type' do
98
- expect(protocol.build_capabilities(vlm_detail)).to include('vision')
99
- end
100
-
101
- it 'reports no chat capabilities for embedding models' do
102
- expect(protocol.build_capabilities(embedding_detail)).to eq([])
103
- end
104
-
105
- it 'falls back to base capabilities without native details' do
106
- expect(protocol.build_capabilities({})).to eq(%w[streaming structured_output])
107
- end
108
- end
109
-
110
- describe '#build_metadata' do
111
- it 'keeps native details and drops missing fields' do
112
- metadata = protocol.build_metadata({ 'owned_by' => 'organization_owner' }, llm_detail)
113
- expect(metadata).to eq(
114
- owned_by: 'organization_owner',
115
- publisher: 'lmstudio-community',
116
- arch: 'qwen2',
117
- compatibility_type: 'gguf',
118
- quantization: 'Q4_K_M',
119
- state: 'loaded'
120
- )
121
- end
122
-
123
- it 'compacts unknown details away' do
124
- expect(protocol.build_metadata({ 'owned_by' => 'organization_owner' }, {}))
125
- .to eq(owned_by: 'organization_owner')
126
- end
127
- end
128
-
129
- describe '#parse_list_models_response' do
130
- it 'builds enriched models from the listing and native details' do
131
- response = instance_double(Faraday::Response,
132
- body: { 'data' => [{ 'id' => 'qwen2.5-7b-instruct',
133
- 'owned_by' => 'organization_owner' }] })
134
- models = protocol.parse_list_models_response(response, 'lms',
135
- details: { 'qwen2.5-7b-instruct' => llm_detail })
136
- expect(models.length).to eq(1)
137
- model = models.first
138
- expect(model.id).to eq('qwen2.5-7b-instruct')
139
- expect(model.provider).to eq('lms')
140
- expect(model.family).to eq('qwen2')
141
- expect(model.context_window).to eq(32_768)
142
- expect(model.capabilities).to include('function_calling')
143
- end
144
-
145
- it 'builds plain models when native details are unavailable' do
146
- response = instance_double(Faraday::Response, body: { 'data' => [{ 'id' => 'some-model' }] })
147
- model = protocol.parse_list_models_response(response, 'lms').first
148
- expect(model.family).to eq('lms')
149
- expect(model.capabilities).to eq(%w[streaming structured_output])
150
- end
151
- end
152
- end
153
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Rerank, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(RERANK_MODELS) do |provider, model|
9
- it "#{provider}/#{model} orders documents by relevance" do
10
- rerank = RubyLLM.rerank(
11
- 'What is the capital of the United States?',
12
- ['Carson City is the capital of Nevada.', 'Washington, D.C. is the capital of the United States.'],
13
- model: model,
14
- provider: provider,
15
- assume_model_exists: true
16
- )
17
-
18
- expect(rerank.results.first.document).to include('Washington')
19
- expect(rerank.results.first.score).to be > rerank.results.last.score
20
- expect(rerank.results.map(&:index)).to contain_exactly(0, 1)
21
- end
22
- end
23
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Speech, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- each_model(SPEECH_MODELS) do |provider, model, model_info|
9
- it "#{provider}/#{model} speaks" do
10
- speech = RubyLLM.speak(
11
- 'Ruby is a programming language designed for developer happiness.',
12
- model: model,
13
- provider: provider,
14
- assume_model_exists: true,
15
- voice: model_info[:voice],
16
- format: model_info[:format]
17
- )
18
-
19
- expect(speech.data).to be_a(String)
20
- expect(speech.data.bytesize).to be > 1000
21
- expect(speech.model).to eq(model)
22
- expect(speech.mime_type).to start_with('audio/')
23
- end
24
- end
25
- end
@@ -1,27 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'spec_helper'
4
-
5
- RSpec.describe RubyLLM::Video, :live do
6
- include_context 'with configured RubyLLM'
7
-
8
- before do
9
- RubyLLM.config.video_generation_poll_interval = VCR.current_cassette&.recording? ? 5 : 0
10
- end
11
-
12
- each_model(VIDEO_GENERATION_MODELS) do |provider, model, model_info|
13
- it "#{provider}/#{model} animates a video" do
14
- video = RubyLLM.animate(
15
- 'a calm ocean wave at sunset',
16
- model: model,
17
- provider: provider,
18
- assume_model_exists: true,
19
- provider_options: model_info.fetch(:provider_options, {})
20
- )
21
-
22
- expect(video.mime_type).to include('video')
23
- expect(video.url || video.data).not_to be_nil
24
- expect(video.to_blob.bytesize).to be > 10_000
25
- end
26
- end
27
- end
data/spec/spec_helper.rb DELETED
@@ -1,26 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'bundler/setup'
4
- require 'dotenv/load'
5
- require 'fileutils'
6
-
7
- require 'vcr'
8
- require 'ruby_llm/providers/lms'
9
- require 'webmock/rspec'
10
-
11
- Dir[File.expand_path('support/**/*.rb', __dir__)].each { |file| require file }
12
-
13
- RSpec.configure do |config|
14
- config.disable_monkey_patching!
15
- config.expect_with(:rspec) { |expectations| expectations.syntax = :expect }
16
- config.order = :random
17
- Kernel.srand config.seed
18
-
19
- config.around(:each, :live) do |example|
20
- cassette_name = example.full_description.downcase.gsub(/[^a-z0-9]+/, '_').delete_prefix('_').delete_suffix('_')
21
- cassette_path = File.join(VCR.configuration.cassette_library_dir, "#{cassette_name}.yml")
22
-
23
- VCR.use_cassette(cassette_name) { example.run }
24
- FileUtils.rm_f(cassette_path) if example.exception
25
- end
26
- end
@@ -1,29 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Model matrices for the live contract specs. LM Studio serves whatever
4
- # models are downloaded on this machine, so these ids are machine-specific;
5
- # swap in ids from `lms ls` (or models.json) when recording cassettes.
6
- # See RubyLLM's full live matrix and specs: https://github.com/crmne/ruby_llm/tree/main/spec
7
- PROVIDER = :lms
8
- CHAT_MODELS = [
9
- { provider: PROVIDER, model: 'openai/gpt-oss-20b' }
10
- ].freeze
11
- TOOL_MODELS = CHAT_MODELS
12
- # gpt-oss models mangle json_schema values on LM Studio (harmony format);
13
- # qwen3 models honor it.
14
- STRUCTURED_OUTPUT_MODELS = [
15
- { provider: PROVIDER, model: 'qwen3-0.6b-bible-assistant' }
16
- ].freeze
17
-
18
- EMBEDDING_MODELS = [
19
- { provider: PROVIDER, model: 'text-embedding-nomic-embed-text-v1.5' }
20
- ].freeze
21
- IMAGE_GENERATION_MODELS = [].freeze
22
- SPEECH_MODELS = [].freeze
23
- VIDEO_GENERATION_MODELS = [].freeze
24
- MODERATION_MODELS = [].freeze
25
- RERANK_MODELS = [].freeze
26
-
27
- def each_model(models)
28
- models.each { |model_info| yield model_info[:provider], model_info[:model], model_info }
29
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- RSpec.shared_context 'with configured RubyLLM' do
4
- before do
5
- RubyLLM.configure do |config|
6
- config.lms_api_key = ENV.fetch('LMS_API_KEY', 'test')
7
- config.lms_api_base = ENV.fetch('LMS_API_BASE', 'http://localhost:1234/v1')
8
- config.max_retries = 0
9
- config.retry_backoff_factor = 0
10
- config.retry_interval = 0
11
- config.retry_interval_randomness = 0
12
- end
13
- end
14
- end
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- VCR.configure do |config|
4
- config.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
5
- config.hook_into :webmock
6
- config.default_cassette_options = { record: ENV['CI'] ? :none : :once }
7
- config.allow_http_connections_when_no_cassette = true
8
- config.filter_sensitive_data('<LMS_API_KEY>') { ENV.fetch('LMS_API_KEY', nil) }
9
-
10
- # Normalize the recorder's server address to the default so cassettes
11
- # replay on any machine (CI included) without LMS_API_BASE set. The
12
- # placeholder must be a parseable URI, not a <TOKEN>: VCR parses every
13
- # cassette URI on replay and raises URI::InvalidURIError otherwise.
14
- config.filter_sensitive_data(RubyLLM::Providers::LMS::DEFAULT_API_BASE) do
15
- base = ENV.fetch('LMS_API_BASE', nil)
16
- base unless base.nil? || base == RubyLLM::Providers::LMS::DEFAULT_API_BASE
17
- end
18
-
19
- config.before_record do |interaction|
20
- next unless interaction.request.headers['Authorization']
21
-
22
- interaction.request.headers['Authorization'] = ['Bearer <AUTH_TOKEN>']
23
- end
24
- end