ruby_llm-providers-lms 0.1.0 → 0.1.1

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 (20) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +2 -5
  3. data/.rubocop.yml +1 -1
  4. data/README.md +135 -8
  5. data/lib/ruby_llm/providers/lms/connection_guard.rb +27 -0
  6. data/lib/ruby_llm/providers/lms.rb +6 -0
  7. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_handle_a_multi_turn_conversation.yml +2 -2
  8. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_have_a_basic_conversation.yml +1 -1
  9. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_use_tools.yml +2 -2
  10. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_returns_the_raw_response.yml +1 -1
  11. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_supports_streaming_responses.yml +1 -1
  12. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_can_have_a_basic_conversation.yml +1 -1
  13. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_returns_structured_output.yml +1 -1
  14. data/spec/fixtures/vcr_cassettes/rubyllm_chat_lms_qwen3_0_6b_bible_assistant_returns_the_raw_response.yml +1 -1
  15. data/spec/fixtures/vcr_cassettes/rubyllm_embedding_lms_text_embedding_nomic_embed_text_v1_5_embeds_one_text.yml +1 -1
  16. data/spec/fixtures/vcr_cassettes/rubyllm_embedding_lms_text_embedding_nomic_embed_text_v1_5_embeds_several_texts.yml +1 -1
  17. data/spec/ruby_llm/providers/lms/connection_guard_spec.rb +47 -0
  18. data/spec/ruby_llm/providers/lms_spec.rb +4 -0
  19. data/spec/support/vcr_configuration.rb +9 -1
  20. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6ecf4144c152ba49e31f4d1456e5998bc28db598698619a0be7c53bb1d45f01
4
- data.tar.gz: c76b3200ac422e1ef9ce27344071b98206e1a1f7017f7e3a03e4f26bf5a5d6d3
3
+ metadata.gz: cdaf33f5245f3b78bcfd33a13c7f2a0838149de0998b09c7f8e4256e447cda69
4
+ data.tar.gz: 285c1c5ddff4227b90b435755b8cc29b1a28bb695fa33c7f1c6510ce3a5853cb
5
5
  SHA512:
6
- metadata.gz: 669b63bf1a9f3685c4419620be7fcb3218007c2e6e620617f5852ae32a53a21f4b9ae7b4f77cc788c7c2e02265e71d0010ba399eae60392143254feecc857d8e
7
- data.tar.gz: 919adff659f4c78b594c137a659074af4d35383201b0338e0163d4e5cc2402db3b72ec23e6eaefdcea0de7b60c23429f2223d5630a293512194297c5701d871d
6
+ metadata.gz: 548a88a50a88795f9b5b7cd11c64ee1c43e76406c7730c34c42cf90bf2e748eded77029f38f9994f5ab8820a05d7e0479118e8a43f3dbb165a3bc11e0297a892
7
+ data.tar.gz: d01419d61951513cc00dcd72e546f7a56e2023fa59ff1bcadc99e0b117f1bdfab6b06c541078b0a984dede41f025e9668fd00921086b3163c5590654d1d99bc2
@@ -16,7 +16,8 @@ jobs:
16
16
  strategy:
17
17
  fail-fast: false
18
18
  matrix:
19
- ruby-version: ["3.1", "3.2", "3.3", "3.4", "4.0"]
19
+ # Non-EOL Rubies only; must match the gemspec's required_ruby_version.
20
+ ruby-version: ["3.3", "3.4", "4.0"]
20
21
  steps:
21
22
  - uses: actions/checkout@v6
22
23
 
@@ -26,7 +27,3 @@ jobs:
26
27
  bundler-cache: true
27
28
 
28
29
  - run: bundle exec rake
29
- if: matrix.ruby-version != '3.1'
30
-
31
- - run: bundle exec rake rubocop flay spec
32
- if: matrix.ruby-version == '3.1'
data/.rubocop.yml CHANGED
@@ -5,7 +5,7 @@ plugins:
5
5
 
6
6
  AllCops:
7
7
  NewCops: enable
8
- TargetRubyVersion: 3.1
8
+ TargetRubyVersion: 3.3
9
9
  Exclude:
10
10
  - Archspec.rb
11
11
  - vendor/**/*
data/README.md CHANGED
@@ -40,15 +40,50 @@ response = chat.ask('Hello')
40
40
  puts response.content
41
41
  ```
42
42
 
43
+ If the server isn't running, requests raise a `RubyLLM::Error` telling you to
44
+ start it (`lms server start`) instead of a raw connection failure.
45
+
43
46
  Because LM Studio is a local provider, RubyLLM assumes any model id you pass
44
47
  exists — LM Studio will just-in-time load the model if it isn't loaded yet.
45
- To browse what the server offers:
48
+ To browse what the server offers right now, ask the provider directly:
46
49
 
47
50
  ```ruby
48
- RubyLLM.models.refresh!
49
- RubyLLM.models.by_provider(:lms).each { |model| puts model.id }
51
+ provider = RubyLLM::Provider.resolve!(:lms).new(RubyLLM.config)
52
+ provider.list_models.each { |model| puts model.id }
50
53
  ```
51
54
 
55
+ ## The model catalog (models.json)
56
+
57
+ The gem ships a `models.json` at its root. **This file is only a sample**,
58
+ recorded from the author's machine: LM Studio serves whatever models *you*
59
+ have downloaded, so your catalog will be different. RubyLLM loads the shipped
60
+ file as the provider's model registry so `RubyLLM.models` has something to
61
+ show.
62
+
63
+ Note that `RubyLLM.models.refresh` does **not** query your LM Studio server:
64
+ provider gems that ship a registry file are excluded from the live fetch, and
65
+ refresh just re-reads the registered `models.json`. Your live server is still
66
+ the source of truth, reached differently depending on what you need:
67
+
68
+ - **Chatting** needs no catalog at all — any model id LM Studio knows will
69
+ work (see above).
70
+ - **Browsing what your server offers** is `provider.list_models` (see the
71
+ snippet above), which hits the live endpoints every time.
72
+ - **Making `RubyLLM.models` reflect your machine** means registering your own
73
+ catalog file in place of the shipped sample:
74
+
75
+ ```ruby
76
+ require 'ruby_llm/providers/lms'
77
+
78
+ provider = RubyLLM::Provider.resolve!(:lms).new(RubyLLM.config)
79
+ RubyLLM::Models.new(provider.list_models).save_to_json('my_models.json')
80
+ RubyLLM::Provider.register :lms, RubyLLM::Providers::LMS, models: 'my_models.json'
81
+ RubyLLM.models.refresh
82
+ ```
83
+
84
+ Don't edit the `models.json` inside the installed gem — it is overwritten on
85
+ every gem update; re-register with your own file instead.
86
+
52
87
  Embeddings work the same way:
53
88
 
54
89
  ```ruby
@@ -61,6 +96,99 @@ To use the Responses dialect instead of Chat Completions:
61
96
  RubyLLM.configure { |config| config.lms_protocol = :responses }
62
97
  ```
63
98
 
99
+ ## LM Studio server options
100
+
101
+ LM Studio serves three API surfaces on one port (`http://localhost:1234` by
102
+ default). This gem talks to the first two:
103
+
104
+ | Surface | Endpoints | Used by this gem |
105
+ | --- | --- | --- |
106
+ | OpenAI-compatible | `GET /v1/models`, `POST /v1/chat/completions`, `POST /v1/completions`, `POST /v1/embeddings`, `POST /v1/responses` | chat, tools, structured output, embeddings, Responses protocol |
107
+ | Native REST v0 | `GET /api/v0/models`, `GET /api/v0/models/{model}`, `POST /api/v0/chat/completions`, `POST /api/v0/completions`, `POST /api/v0/embeddings` | model-listing enrichment |
108
+ | Native REST v1 | `POST /api/v1/chat`, `GET /api/v1/list`, `POST /api/v1/load`, `POST /api/v1/unload`, `POST /api/v1/download`, `GET /api/v1/download-status` | not yet (see below) |
109
+
110
+ ### Standard request parameters
111
+
112
+ On its OpenAI-compatible chat endpoints LM Studio honors the usual OpenAI
113
+ vocabulary: `model`, `messages`, `temperature`, `top_p`, `max_tokens`,
114
+ `stream`, `stop`, `presence_penalty`, `frequency_penalty`, `logit_bias`,
115
+ `seed`, `tools` / `tool_choice`, and `response_format` with a `json_schema`
116
+ (strict structured output). Vision models accept image content parts. You
117
+ reach all of these through RubyLLM's normal API — `with_temperature`,
118
+ `with_max_output_tokens`, `with_tools`, `with_schema`, `chat.ask(with: 'image.png')`,
119
+ and so on; nothing LM Studio-specific is required.
120
+
121
+ ### LM Studio extras
122
+
123
+ LM Studio also accepts request fields that are not part of OpenAI's
124
+ vocabulary. RubyLLM merges `with_provider_options` into the request payload
125
+ as-is, so all of these work today:
126
+
127
+ | Option | What it does |
128
+ | --- | --- |
129
+ | `ttl` | Idle time-to-live in seconds for the model handling this request. A JIT-loaded model unloads itself after sitting idle that long (LM Studio's default is 60 minutes; each request resets the timer). |
130
+ | `draft_model` | Enables speculative decoding: a small draft model proposes tokens the main model verifies, often speeding up generation substantially. Both models must use the same tokenizer family. |
131
+ | `top_k` | Sample only from the k most likely tokens. |
132
+ | `min_p` | Discard tokens below this base probability. |
133
+ | `repeat_penalty` | Penalize recently generated tokens to discourage repetition. |
134
+
135
+ ```ruby
136
+ chat = RubyLLM.chat(model: 'qwen/qwen3-4b', provider: :lms)
137
+ chat.with_provider_options(
138
+ ttl: 300, # unload after 5 idle minutes
139
+ draft_model: 'qwen/qwen3-0.6b', # speculative decoding
140
+ top_k: 40,
141
+ min_p: 0.05,
142
+ repeat_penalty: 1.1
143
+ )
144
+ chat.ask('Hello')
145
+ ```
146
+
147
+ ### Model lifecycle: JIT loading, TTL, auto-evict
148
+
149
+ Because LM Studio only serves models you have downloaded, the server manages
150
+ memory rather than a fleet:
151
+
152
+ - **JIT loading** — with just-in-time loading enabled (the server default),
153
+ `/v1/models` lists every downloaded model and an inference request for an
154
+ unloaded model loads it on demand. Expect a long time-to-first-token on
155
+ that first request. With JIT off, only already-loaded models are served.
156
+ - **TTL** — JIT-loaded models default to a 60-minute idle TTL; override it
157
+ per request with the `ttl` option above, or at load time with
158
+ `lms load <model> --ttl 3600`. Models loaded explicitly via `lms load`
159
+ have no TTL and stay resident until unloaded.
160
+ - **Auto-evict** — by default LM Studio keeps at most one JIT-loaded model
161
+ in memory, evicting the previous one before loading the next. Turn
162
+ auto-evict off in the server settings to keep several models resident at
163
+ once (each still subject to its own TTL).
164
+
165
+ The `state` field in this gem's model metadata (`loaded` / `not-loaded`)
166
+ comes from the native v0 API and tells you which models are resident right
167
+ now.
168
+
169
+ ### Native REST APIs (not yet wired up)
170
+
171
+ The native APIs offer capabilities the OpenAI-compatible surface cannot
172
+ express. This gem does not use them yet, beyond the v0 model-listing
173
+ enrichment:
174
+
175
+ - **Performance stats** — native v0/v1 chat responses include a `stats`
176
+ object (tokens per second, time to first token, generation time, and
177
+ draft-token acceptance counts when speculative decoding is active) plus
178
+ `model_info` and `runtime` blocks.
179
+ - **Stateful chats** — `/api/v1/chat` stores conversations server-side
180
+ (`store`, default true) and continues them via `previous_response_id`.
181
+ - **Reasoning control** — `/api/v1/chat` takes a `reasoning` effort option
182
+ (`off` / `low` / `medium` / `high` / `on`) and returns reasoning output as
183
+ separate items.
184
+ - **Server-side MCP tools** — `/api/v1/chat` can run tools itself through
185
+ `integrations`: pre-configured MCP plugins from `mcp.json` or ephemeral
186
+ MCP servers declared in the request, with `allowed_tools` filtering.
187
+ - **Per-request context length** — `/api/v1/chat` accepts a
188
+ `context_length` override.
189
+ - **Model management** — `/api/v1/load`, `/api/v1/unload`, `/api/v1/download`,
190
+ and `/api/v1/download-status` control what is in memory and on disk.
191
+
64
192
  ## Development
65
193
 
66
194
  Start the LM Studio server (`lms server start`), then:
@@ -70,11 +198,10 @@ bundle exec rake models # refresh models.json from your local server
70
198
  bundle exec rake # rubocop, flay, archspec, specs
71
199
  ```
72
200
 
73
- `rake models` calls the local server's model-listing endpoints and writes
74
- `models.json` at the gem root. RubyLLM loads that catalog as a fallback when
75
- this provider is registered. Since LM Studio catalogs are machine-specific
76
- (they list whatever you have downloaded), the packaged catalog is only a
77
- sample — live listings via `RubyLLM.models.refresh!` reflect your machine.
201
+ `rake models` calls the local server's model-listing endpoints and rewrites
202
+ the sample `models.json` at the gem root (see "The model catalog" above)
203
+ it is a maintainer task for refreshing the shipped sample, not something gem
204
+ users run.
78
205
 
79
206
  The suite always runs the provider integration specs. The first local run
80
207
  calls the API and records VCR cassettes; CI only replays committed cassettes.
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'delegate'
4
+
5
+ module RubyLLM
6
+ module Providers
7
+ class LMS < Provider
8
+ # Wraps the transport connection so a request against a server that
9
+ # is not running surfaces as a RubyLLM::Error explaining how to start
10
+ # LM Studio, instead of a raw Faraday::ConnectionFailed.
11
+ class ConnectionGuard < SimpleDelegator
12
+ %i[get post patch delete].each do |verb|
13
+ define_method(verb) do |*args, **kwargs, &block|
14
+ __getobj__.public_send(verb, *args, **kwargs, &block)
15
+ rescue Faraday::ConnectionFailed
16
+ raise Error, unreachable_message
17
+ end
18
+ end
19
+
20
+ def unreachable_message
21
+ "Cannot connect to the LM Studio server at #{provider.api_base}. " \
22
+ 'Start it with `lms server start`.'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ruby_llm'
4
+ require 'ruby_llm/providers/lms/connection_guard'
4
5
  require 'ruby_llm/providers/lms/models'
5
6
 
6
7
  module RubyLLM
@@ -20,6 +21,11 @@ module RubyLLM
20
21
  protocol :chat_completions, ChatCompletions
21
22
  protocol :responses, Protocols::Responses
22
23
 
24
+ def initialize(config)
25
+ super
26
+ @connection = ConnectionGuard.new(@connection)
27
+ end
28
+
23
29
  def api_base
24
30
  @config.lms_api_base || DEFAULT_API_BASE
25
31
  end
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"Who
@@ -44,7 +44,7 @@ http_interactions:
44
44
  recorded_at: Fri, 11 Sep 2026 17:27:55 GMT
45
45
  - request:
46
46
  method: post
47
- uri: "<LMS_API_BASE>/chat/completions"
47
+ uri: "http://localhost:1234/v1/chat/completions"
48
48
  body:
49
49
  encoding: UTF-8
50
50
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"Who
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What''s
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What''s
@@ -82,7 +82,7 @@ http_interactions:
82
82
  recorded_at: Fri, 11 Sep 2026 17:28:04 GMT
83
83
  - request:
84
84
  method: post
85
- uri: "<LMS_API_BASE>/chat/completions"
85
+ uri: "http://localhost:1234/v1/chat/completions"
86
86
  body:
87
87
  encoding: UTF-8
88
88
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What''s
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"What
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"openai/gpt-oss-20b","messages":[{"role":"user","content":"Count
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"qwen3-0.6b-bible-assistant","messages":[{"role":"user","content":"What''s
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"qwen3-0.6b-bible-assistant","messages":[{"role":"user","content":"Generate
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/chat/completions"
5
+ uri: "http://localhost:1234/v1/chat/completions"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"qwen3-0.6b-bible-assistant","messages":[{"role":"user","content":"What
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/embeddings"
5
+ uri: "http://localhost:1234/v1/embeddings"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"text-embedding-nomic-embed-text-v1.5","input":"Ruby is a
@@ -2,7 +2,7 @@
2
2
  http_interactions:
3
3
  - request:
4
4
  method: post
5
- uri: "<LMS_API_BASE>/embeddings"
5
+ uri: "http://localhost:1234/v1/embeddings"
6
6
  body:
7
7
  encoding: UTF-8
8
8
  string: '{"model":"text-embedding-nomic-embed-text-v1.5","input":["Ruby","Python","JavaScript"]}'
@@ -0,0 +1,47 @@
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
@@ -50,6 +50,10 @@ RSpec.describe RubyLLM::Providers::LMS do
50
50
  expect(provider.headers).to eq('Authorization' => 'Bearer test-key')
51
51
  end
52
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
+
53
57
  describe 'model listing' do
54
58
  subject(:protocol) do
55
59
  described_class::ChatCompletions.new(described_class.new(config))
@@ -6,7 +6,15 @@ VCR.configure do |config|
6
6
  config.default_cassette_options = { record: ENV['CI'] ? :none : :once }
7
7
  config.allow_http_connections_when_no_cassette = true
8
8
  config.filter_sensitive_data('<LMS_API_KEY>') { ENV.fetch('LMS_API_KEY', nil) }
9
- config.filter_sensitive_data('<LMS_API_BASE>') { ENV.fetch('LMS_API_BASE', 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
10
18
 
11
19
  config.before_record do |interaction|
12
20
  next unless interaction.request.headers['Authorization']
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm-providers-lms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - madbomber
@@ -42,6 +42,7 @@ files:
42
42
  - LICENSE
43
43
  - README.md
44
44
  - lib/ruby_llm/providers/lms.rb
45
+ - lib/ruby_llm/providers/lms/connection_guard.rb
45
46
  - lib/ruby_llm/providers/lms/models.rb
46
47
  - models.json
47
48
  - spec/fixtures/vcr_cassettes/rubyllm_chat_lms_openai_gpt_oss_20b_can_handle_a_multi_turn_conversation.yml
@@ -62,6 +63,7 @@ files:
62
63
  - spec/ruby_llm/image_spec.rb
63
64
  - spec/ruby_llm/models_spec.rb
64
65
  - spec/ruby_llm/moderation_spec.rb
66
+ - spec/ruby_llm/providers/lms/connection_guard_spec.rb
65
67
  - spec/ruby_llm/providers/lms_spec.rb
66
68
  - spec/ruby_llm/rerank_spec.rb
67
69
  - spec/ruby_llm/speech_spec.rb
@@ -86,7 +88,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
88
  requirements:
87
89
  - - ">="
88
90
  - !ruby/object:Gem::Version
89
- version: '3.1'
91
+ version: '3.3'
90
92
  required_rubygems_version: !ruby/object:Gem::Requirement
91
93
  requirements:
92
94
  - - ">="