llm.rb 11.1.0 → 11.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 (48) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -1
  3. data/README.md +27 -4
  4. data/lib/llm/a2a/transport/http.rb +9 -8
  5. data/lib/llm/a2a.rb +14 -7
  6. data/lib/llm/agent.rb +6 -3
  7. data/lib/llm/context.rb +20 -6
  8. data/lib/llm/function/array.rb +6 -0
  9. data/lib/llm/function.rb +26 -0
  10. data/lib/llm/json_adapter.rb +8 -2
  11. data/lib/llm/mcp/transport/http.rb +7 -5
  12. data/lib/llm/mcp.rb +6 -7
  13. data/lib/llm/provider.rb +1 -18
  14. data/lib/llm/providers/anthropic/files.rb +6 -6
  15. data/lib/llm/providers/anthropic/models.rb +1 -1
  16. data/lib/llm/providers/anthropic.rb +1 -1
  17. data/lib/llm/providers/bedrock/models.rb +4 -4
  18. data/lib/llm/providers/bedrock/signature.rb +3 -3
  19. data/lib/llm/providers/bedrock.rb +1 -1
  20. data/lib/llm/providers/google/files.rb +5 -5
  21. data/lib/llm/providers/google/images.rb +1 -1
  22. data/lib/llm/providers/google/models.rb +1 -1
  23. data/lib/llm/providers/google.rb +2 -2
  24. data/lib/llm/providers/ollama/models.rb +1 -1
  25. data/lib/llm/providers/ollama.rb +2 -2
  26. data/lib/llm/providers/openai/audio.rb +3 -3
  27. data/lib/llm/providers/openai/files.rb +5 -5
  28. data/lib/llm/providers/openai/images.rb +3 -3
  29. data/lib/llm/providers/openai/models.rb +1 -1
  30. data/lib/llm/providers/openai/moderations.rb +1 -1
  31. data/lib/llm/providers/openai/responses.rb +3 -3
  32. data/lib/llm/providers/openai/vector_stores.rb +11 -11
  33. data/lib/llm/providers/openai.rb +2 -2
  34. data/lib/llm/skill.rb +1 -1
  35. data/lib/llm/tool.rb +21 -0
  36. data/lib/llm/transport/curb.rb +246 -0
  37. data/lib/llm/transport/execution.rb +1 -1
  38. data/lib/llm/transport/http.rb +9 -4
  39. data/lib/llm/transport/net_http_adapter.rb +61 -0
  40. data/lib/llm/transport/persistent_http.rb +10 -5
  41. data/lib/llm/transport/request.rb +121 -0
  42. data/lib/llm/transport/response/curb.rb +112 -0
  43. data/lib/llm/transport/response.rb +1 -0
  44. data/lib/llm/transport/utils.rb +42 -17
  45. data/lib/llm/transport.rb +17 -45
  46. data/lib/llm/version.rb +1 -1
  47. data/llm.gemspec +3 -3
  48. metadata +8 -4
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::Transport
4
+ ##
5
+ # The {LLM::Transport::Curb LLM::Transport::Curb} transport is an
6
+ # optional adapter for libcurl via the
7
+ # [curb](https://github.com/taf2/curb) gem.
8
+ #
9
+ # Curb is a C extension around libcurl. It releases the GVL during
10
+ # I/O so other Ruby threads can run while requests are in flight. Its
11
+ # timeout handling is built into libcurl itself — no thread-based
12
+ # timeout library required. It supports HTTP/2, connection reuse, and
13
+ # a wider range of network protocols out of the box.
14
+ #
15
+ # Unlike the built-in Net::HTTP transports, this transport does not
16
+ # require any Ruby standard library HTTP client and can be used on
17
+ # platforms where Net::HTTP is not available or desired.
18
+ #
19
+ # @example
20
+ # LLM.openai(key: ENV["KEY"], transport: :curb)
21
+ #
22
+ # @api private
23
+ class Curb < self
24
+ INTERRUPT_ERRORS = [::IOError, ::EOFError, Errno::EBADF].freeze
25
+ ActiveRequest = Struct.new(:easy, keyword_init: true)
26
+
27
+ ##
28
+ # @param [String] host
29
+ # @param [Integer] port
30
+ # @param [Integer] timeout
31
+ # @param [Boolean] ssl
32
+ # @return [LLM::Transport::Curb]
33
+ def initialize(host:, port:, timeout:, ssl:)
34
+ @host = host
35
+ @port = port
36
+ @timeout = timeout
37
+ @ssl = ssl
38
+ @base_uri = URI("#{ssl ? "https" : "http"}://#{host}:#{port}/")
39
+ @monitor = Monitor.new
40
+ end
41
+
42
+ ##
43
+ # Returns the current request owner.
44
+ # @return [Object]
45
+ def request_owner
46
+ return Fiber.current unless defined?(::Async)
47
+ Async::Task.current? ? Async::Task.current : Fiber.current
48
+ end
49
+
50
+ ##
51
+ # @return [Array<Class<Exception>>]
52
+ def interrupt_errors
53
+ [*INTERRUPT_ERRORS, *optional_interrupt_errors]
54
+ end
55
+
56
+ ##
57
+ # Interrupt an active request, if any.
58
+ #
59
+ # Sets the interrupt flag so the on_body callback can raise
60
+ # LLM::Interrupt on the next chunk.
61
+ #
62
+ # @param [Fiber] owner
63
+ # @return [nil]
64
+ def interrupt!(owner)
65
+ request_for(owner) or return
66
+ lock { (@interrupts ||= {})[owner] = true }
67
+ rescue *interrupt_errors
68
+ nil
69
+ end
70
+
71
+ ##
72
+ # Returns whether an execution owner was interrupted.
73
+ # @param [Fiber] owner
74
+ # @return [Boolean, nil]
75
+ def interrupted?(owner)
76
+ lock { @interrupts&.delete(owner) }
77
+ end
78
+
79
+ ##
80
+ # Performs a request through curb and returns a transport response
81
+ # wrapper so the provider layer can stay transport-agnostic.
82
+ #
83
+ # @param [LLM::Transport::Request] request
84
+ # @param [Fiber] owner
85
+ # @param [LLM::Object, nil] stream
86
+ # @yieldparam [LLM::Transport::Response] response
87
+ # @return [Object]
88
+ def request(request, owner:, stream: nil, &b)
89
+ easy = build_easy(request)
90
+ set_request(ActiveRequest.new(easy:), owner)
91
+ if stream
92
+ perform_streaming(easy, owner, stream)
93
+ elsif b
94
+ res = perform_blocking(easy, owner)
95
+ if LLM::Transport::Response === res
96
+ res.success? ? b.call(res) : res
97
+ else
98
+ res
99
+ end
100
+ else
101
+ perform_blocking(easy, owner)
102
+ end
103
+ ensure
104
+ clear_request(owner)
105
+ end
106
+
107
+ ##
108
+ # @return [String]
109
+ def inspect
110
+ "#<#{LLM::Utils.object_id(self)}>"
111
+ end
112
+
113
+ private
114
+
115
+ attr_reader :host, :port, :timeout, :ssl, :base_uri
116
+
117
+ def build_easy(request)
118
+ LLM.require "curb" unless defined?(::Curl)
119
+ easy = ::Curl::Easy.new(request_url(request))
120
+ easy.timeout = timeout
121
+ easy.connect_timeout = timeout
122
+ request.headers.each { |k, v| easy.headers[k] = v }
123
+ easy.follow_location = true
124
+ easy.ssl_verify_peer = false if !ssl
125
+ set_body(easy, request)
126
+ easy
127
+ end
128
+
129
+ def request_url(request)
130
+ path = request.path
131
+ return path if path.start_with?("http://", "https://")
132
+ scheme = ssl ? "https" : "http"
133
+ default_port = ssl ? 443 : 80
134
+ authority = port && port.to_i > 0 && port.to_i != default_port \
135
+ ? "#{host}:#{port}" : host
136
+ "#{scheme}://#{authority}#{path}"
137
+ end
138
+
139
+ def set_body(easy, request)
140
+ case request.method
141
+ when "POST"
142
+ easy.post_body = request.body if request.body
143
+ when "PUT"
144
+ easy.put_data = request.body if request.body
145
+ when "DELETE"
146
+ easy.delete = true
147
+ end
148
+ end
149
+
150
+ def perform_blocking(easy, owner)
151
+ check_interrupted(owner)
152
+ easy.on_body { |chunk|
153
+ check_interrupted(owner)
154
+ chunk.bytesize
155
+ }
156
+ easy.perform
157
+ build_response(easy)
158
+ end
159
+
160
+ def perform_streaming(easy, owner, stream)
161
+ res = nil
162
+ raw_body = +""
163
+ decoder = stream.decoder.new(stream.parser.new(stream.streamer))
164
+ easy.on_body do |chunk|
165
+ raise LLM::Interrupt, "request interrupted" if interrupted?(owner)
166
+ if (res ||= build_response_from_headers(easy))&.success? \
167
+ && res["content-type"].to_s.include?("text/event-stream")
168
+ decoder << chunk
169
+ else
170
+ raw_body << chunk
171
+ end
172
+ chunk.bytesize
173
+ end
174
+ easy.perform
175
+ res ||= build_response(easy)
176
+ if raw_body.empty?
177
+ body = decoder.body
178
+ res.body = (Hash === body || Array === body) \
179
+ ? LLM::Object.from(body) : body
180
+ else
181
+ res.body = raw_body
182
+ end
183
+ res
184
+ ensure
185
+ decoder&.free
186
+ end
187
+
188
+ def build_response(easy)
189
+ LLM::Transport::Response::Curb.new(
190
+ easy.response_code.to_i,
191
+ parse_headers(easy.header_str.to_s),
192
+ easy.body_str.to_s
193
+ )
194
+ end
195
+
196
+ def build_response_from_headers(easy)
197
+ return nil if easy.header_str.to_s.empty?
198
+ LLM::Transport::Response::Curb.new(
199
+ easy.response_code.to_i,
200
+ parse_headers(easy.header_str.to_s),
201
+ +""
202
+ )
203
+ end
204
+
205
+ def parse_headers(header_str)
206
+ headers = {}
207
+ header_str.each_line do |line|
208
+ line = line.strip
209
+ next if line.empty? || line.start_with?("HTTP/")
210
+ key, value = line.split(/: \s*/, 2)
211
+ headers[key.downcase] = value if key && value
212
+ end
213
+ headers
214
+ end
215
+
216
+ def check_interrupted(owner)
217
+ raise LLM::Interrupt, "request interrupted" if interrupted?(owner)
218
+ end
219
+
220
+ def request_for(owner)
221
+ lock do
222
+ @requests ||= {}
223
+ @requests[owner]
224
+ end
225
+ end
226
+
227
+ def set_request(req, owner)
228
+ lock do
229
+ @requests ||= {}
230
+ @requests[owner] = req
231
+ end
232
+ end
233
+
234
+ def clear_request(owner)
235
+ lock { @requests&.delete(owner) }
236
+ end
237
+
238
+ def lock(&)
239
+ @monitor.synchronize(&)
240
+ end
241
+
242
+ def optional_interrupt_errors
243
+ defined?(::Async::Stop) ? [Async::Stop] : []
244
+ end
245
+ end
246
+ end
@@ -13,7 +13,7 @@ class LLM::Transport
13
13
 
14
14
  ##
15
15
  # Executes a HTTP request
16
- # @param [Net::HTTPRequest] request
16
+ # @param [LLM::Transport::Request] request
17
17
  # The request to send
18
18
  # @param [Proc] b
19
19
  # A block to yield the response to (optional)
@@ -11,8 +11,10 @@ class LLM::Transport
11
11
  #
12
12
  # @api private
13
13
  class HTTP < self
14
+ include NetHTTPAdapter
15
+
14
16
  INTERRUPT_ERRORS = [::IOError, ::EOFError, Errno::EBADF].freeze
15
- Request = Struct.new(:client, keyword_init: true)
17
+ ActiveRequest = Struct.new(:client, keyword_init: true)
16
18
 
17
19
  ##
18
20
  # @param [String] host
@@ -67,15 +69,18 @@ class LLM::Transport
67
69
 
68
70
  ##
69
71
  # Performs a request on the current HTTP transport.
70
- # @param [Net::HTTPRequest] request
72
+ # Accepts both {Net::HTTPRequest} and {LLM::Transport::Request}.
73
+ #
74
+ # @param [Net::HTTPRequest, LLM::Transport::Request] request
71
75
  # @param [Fiber] owner
72
76
  # @param [LLM::Object, nil] stream
73
77
  # @yieldparam [LLM::Transport::Response] response
74
78
  # @return [Object]
75
79
  def request(request, owner:, stream: nil, &b)
80
+ http_req = resolve_request(request)
76
81
  client = client()
77
- set_request(Request.new(client:), owner)
78
- perform_request(client, request, stream, &b)
82
+ set_request(ActiveRequest.new(client:), owner)
83
+ perform_request(client, http_req, stream, &b)
79
84
  ensure
80
85
  clear_request(owner)
81
86
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::Transport
4
+ ##
5
+ # @api private
6
+ module NetHTTPAdapter
7
+ private
8
+
9
+ def resolve_request(request)
10
+ return request if ::Net::HTTPRequest === request
11
+ build_net_http_request(request)
12
+ end
13
+
14
+ def build_net_http_request(req)
15
+ method = req.method.downcase.to_sym
16
+ path = req.path
17
+ headers = req.headers
18
+ http_req = case method
19
+ when :get then ::Net::HTTP::Get.new(path, headers)
20
+ when :post then ::Net::HTTP::Post.new(path, headers)
21
+ when :put then ::Net::HTTP::Put.new(path, headers)
22
+ when :patch then ::Net::HTTP::Patch.new(path, headers)
23
+ when :delete then ::Net::HTTP::Delete.new(path, headers)
24
+ else ::Net::HTTP::GenericRequest.new(method, path, nil, headers)
25
+ end
26
+ if req.body
27
+ http_req.body = req.body
28
+ elsif req.body_stream
29
+ http_req.body_stream = req.body_stream
30
+ end
31
+ http_req
32
+ end
33
+
34
+ def perform_request(client, request, stream, &b)
35
+ if stream
36
+ client.request(request) do |raw|
37
+ res = LLM::Transport::Response.from(raw)
38
+ if res.success?
39
+ parser = stream.decoder.new(stream.parser.new(stream.streamer))
40
+ res.read_body(parser)
41
+ body = parser.body
42
+ res.body = (Hash === body || Array === body) ? LLM::Object.from(body) : body
43
+ else
44
+ body = +""
45
+ res.read_body { body << _1 }
46
+ res.body = body
47
+ end
48
+ ensure
49
+ parser&.free
50
+ end
51
+ elsif b
52
+ client.request(request) do |raw|
53
+ res = LLM::Transport::Response.from(raw)
54
+ res.success? ? b.call(res) : res
55
+ end
56
+ else
57
+ LLM::Transport::Response.from(client.request(request))
58
+ end
59
+ end
60
+ end
61
+ end
@@ -10,8 +10,10 @@ class LLM::Transport
10
10
  #
11
11
  # @api private
12
12
  class PersistentHTTP < self
13
+ include NetHTTPAdapter
14
+
13
15
  INTERRUPT_ERRORS = [::IOError, ::EOFError, Errno::EBADF].freeze
14
- Request = Struct.new(:client, :connection, keyword_init: true)
16
+ ActiveRequest = Struct.new(:client, :connection, keyword_init: true)
15
17
  @registry = {}
16
18
  @monitor = Monitor.new
17
19
 
@@ -79,15 +81,18 @@ class LLM::Transport
79
81
 
80
82
  ##
81
83
  # Performs a request on the current HTTP transport.
82
- # @param [Net::HTTPRequest] request
84
+ # Accepts both {Net::HTTPRequest} and {LLM::Transport::Request}.
85
+ #
86
+ # @param [Net::HTTPRequest, LLM::Transport::Request] request
83
87
  # @param [Fiber] owner
84
88
  # @param [LLM::Object, nil] stream
85
89
  # @yieldparam [LLM::Transport::Response] response
86
90
  # @return [Object]
87
91
  def request(request, owner:, stream: nil, &b)
88
- client.connection_for(URI.join(base_uri, request.path)) do |connection|
89
- set_request(Request.new(client:, connection:), owner)
90
- perform_request(connection.http, request, stream, &b)
92
+ http_req = resolve_request(request)
93
+ client.connection_for(URI.join(base_uri, http_req.path)) do |connection|
94
+ set_request(ActiveRequest.new(client:, connection:), owner)
95
+ perform_request(connection.http, http_req, stream, &b)
91
96
  end
92
97
  ensure
93
98
  clear_request(owner)
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::Transport
4
+ ##
5
+ # {LLM::Transport::Request LLM::Transport::Request} defines the
6
+ # normalized request interface expected by transports.
7
+ #
8
+ # Providers build request objects through this class, then hand them
9
+ # to a transport for execution without depending on any specific HTTP
10
+ # client library.
11
+ class Request
12
+ ##
13
+ # @return [Object, nil]
14
+ attr_accessor :body
15
+
16
+ ##
17
+ # @return [IO, nil]
18
+ attr_accessor :body_stream
19
+
20
+ ##
21
+ # @return [String]
22
+ attr_reader :method
23
+
24
+ ##
25
+ # @return [String]
26
+ attr_reader :path
27
+
28
+ ##
29
+ # @return [Hash]
30
+ attr_reader :headers
31
+
32
+ ##
33
+ # @param [String] path
34
+ # @param [Hash, nil] headers
35
+ # @return [LLM::Transport::Request]
36
+ def self.get(path, headers = nil)
37
+ new("GET", path, headers)
38
+ end
39
+
40
+ ##
41
+ # @param [String] path
42
+ # @param [Hash, nil] headers
43
+ # @return [LLM::Transport::Request]
44
+ def self.post(path, headers = nil)
45
+ new("POST", path, headers)
46
+ end
47
+
48
+ ##
49
+ # @param [String] path
50
+ # @param [Hash, nil] headers
51
+ # @return [LLM::Transport::Request]
52
+ def self.put(path, headers = nil)
53
+ new("PUT", path, headers)
54
+ end
55
+
56
+ ##
57
+ # @param [String] path
58
+ # @param [Hash, nil] headers
59
+ # @return [LLM::Transport::Request]
60
+ def self.patch(path, headers = nil)
61
+ new("PATCH", path, headers)
62
+ end
63
+
64
+ ##
65
+ # @param [String] path
66
+ # @param [Hash, nil] headers
67
+ # @return [LLM::Transport::Request]
68
+ def self.delete(path, headers = nil)
69
+ new("DELETE", path, headers)
70
+ end
71
+
72
+ ##
73
+ # @param [String] method
74
+ # @param [String] path
75
+ # @param [Hash, nil] headers
76
+ # @return [LLM::Transport::Request]
77
+ def initialize(method, path, headers = nil)
78
+ @method = method.to_s.upcase
79
+ @path = path.to_s
80
+ @headers = {}
81
+ (headers || {}).each { self[_1] = _2 }
82
+ end
83
+
84
+ ##
85
+ # @param [String] key
86
+ # @return [String, nil]
87
+ def [](key)
88
+ @headers[normalize_header(key)]
89
+ end
90
+
91
+ ##
92
+ # @param [String] key
93
+ # @param [Object] value
94
+ # @return [String]
95
+ def []=(key, value)
96
+ @headers[normalize_header(key)] = value.to_s
97
+ end
98
+
99
+ ##
100
+ # @yieldparam [String] key
101
+ # @yieldparam [String] value
102
+ # @return [Hash]
103
+ def each_header(&block)
104
+ @headers.each(&block)
105
+ end
106
+
107
+ ##
108
+ # @return [String]
109
+ def inspect
110
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}" \
111
+ " @method=#{@method} @path=#{@path}" \
112
+ " @headers=#{@headers.inspect}>"
113
+ end
114
+
115
+ private
116
+
117
+ def normalize_header(key)
118
+ key.to_s.downcase
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ class LLM::Transport::Response
4
+ ##
5
+ # {LLM::Transport::Response::Curb LLM::Transport::Response::Curb}
6
+ # adapts a raw status code, header hash, and body string to the
7
+ # {LLM::Transport::Response LLM::Transport::Response} interface.
8
+ #
9
+ # This is the response wrapper used by the
10
+ # {LLM::Transport::Curb LLM::Transport::Curb} transport.
11
+ class Curb < self
12
+ ##
13
+ # @return [Integer]
14
+ attr_reader :code
15
+
16
+ ##
17
+ # @return [Hash]
18
+ attr_reader :headers
19
+
20
+ ##
21
+ # @return [String]
22
+ attr_accessor :body
23
+
24
+ ##
25
+ # @param [#to_i] code
26
+ # @param [Hash] headers
27
+ # @param [String] body
28
+ # @return [LLM::Transport::Response::Curb]
29
+ def initialize(code, headers = {}, body = +"")
30
+ @code = code.to_i
31
+ @headers = {}
32
+ (headers || {}).each { @headers[_1.to_s.downcase] = _2.to_s }
33
+ @body = body
34
+ end
35
+
36
+ ##
37
+ # @param [String] key
38
+ # @return [String, nil]
39
+ def [](key)
40
+ @headers[key.to_s.downcase]
41
+ end
42
+
43
+ ##
44
+ # @param [Object, nil] dest
45
+ # @yieldparam [String] chunk
46
+ # @return [void]
47
+ def read_body(dest = nil, &block)
48
+ return @body unless block_given? || dest
49
+ if dest
50
+ dest << @body.to_s
51
+ else
52
+ yield @body.to_s
53
+ end
54
+ @body
55
+ end
56
+
57
+ ##
58
+ # @return [Boolean]
59
+ def success?
60
+ code.between?(200, 299)
61
+ end
62
+
63
+ ##
64
+ # @return [Boolean]
65
+ def ok?
66
+ code == 200
67
+ end
68
+
69
+ ##
70
+ # @return [Boolean]
71
+ def bad_request?
72
+ code == 400
73
+ end
74
+
75
+ ##
76
+ # @return [Boolean]
77
+ def unauthorized?
78
+ code == 401
79
+ end
80
+
81
+ ##
82
+ # @return [Boolean]
83
+ def forbidden?
84
+ code == 403
85
+ end
86
+
87
+ ##
88
+ # @return [Boolean]
89
+ def not_found?
90
+ code == 404
91
+ end
92
+
93
+ ##
94
+ # @return [Boolean]
95
+ def rate_limited?
96
+ code == 429
97
+ end
98
+
99
+ ##
100
+ # @return [Boolean]
101
+ def server_error?
102
+ code.between?(500, 599)
103
+ end
104
+
105
+ ##
106
+ # @return [String]
107
+ def inspect
108
+ "#<#{self.class.name}:0x#{object_id.to_s(16)}" \
109
+ " @code=#{@code} @headers=#{@headers.inspect}>"
110
+ end
111
+ end
112
+ end
@@ -17,6 +17,7 @@ class LLM::Transport
17
17
  # how the request was actually performed.
18
18
  class Response
19
19
  require_relative "response/http"
20
+ require_relative "response/curb"
20
21
 
21
22
  ##
22
23
  # @param [Object] res
@@ -4,32 +4,57 @@ class LLM::Transport
4
4
  ##
5
5
  # Shared utility methods for HTTP-backed transports.
6
6
  #
7
+ # These methods resolve the transport options accepted by providers,
8
+ # MCP HTTP clients, and A2A HTTP clients into concrete
9
+ # {LLM::Transport} instances.
10
+ #
7
11
  # @api private
8
12
  module Utils
9
13
  extend self
10
- private
11
14
 
12
- def resolve_transport(uri, transport, timeout)
13
- return default_transport(uri, timeout) if transport.nil?
14
- if Class === transport && transport <= LLM::Transport
15
- transport.new(
16
- host: uri.host,
17
- port: uri.port,
18
- timeout:,
19
- ssl: uri.scheme == "https"
20
- )
15
+ ##
16
+ # Resolves a transport configuration into a transport instance.
17
+ #
18
+ # Nil values use the default Net::HTTP transport, or the persistent
19
+ # Net::HTTP transport when `persistent` is true. Transport subclasses
20
+ # are instantiated with the endpoint settings, symbols are resolved
21
+ # through {LLM::Transport} shortcut methods, and transport instances
22
+ # are returned as-is.
23
+ #
24
+ # @param [String] host
25
+ # @param [Integer] port
26
+ # @param [Integer, nil] timeout
27
+ # @param [Boolean] ssl
28
+ # @param [Boolean] persistent
29
+ # @param [LLM::Transport, Class, Symbol, nil] transport
30
+ # @return [LLM::Transport]
31
+ def resolve_transport(host:, port:, timeout:, ssl:, persistent:, transport:)
32
+ if transport.nil?
33
+ default_transport(host:, port:, timeout:, ssl:, persistent:)
34
+ elsif Class === transport && transport <= LLM::Transport
35
+ transport.new(host:, port:, timeout:, ssl:)
36
+ elsif Symbol === transport
37
+ transport = LLM::Transport.public_send(transport)
38
+ transport.new(host:, port:, timeout:, ssl:)
21
39
  else
22
40
  transport
23
41
  end
24
42
  end
25
43
 
26
- def default_transport(uri, timeout)
27
- LLM::Transport::HTTP.new(
28
- host: uri.host,
29
- port: uri.port,
30
- timeout:,
31
- ssl: uri.scheme == "https"
32
- )
44
+ private
45
+
46
+ ##
47
+ # Builds the default Net::HTTP transport for an endpoint.
48
+ #
49
+ # @param [String] host
50
+ # @param [Integer] port
51
+ # @param [Integer, nil] timeout
52
+ # @param [Boolean] ssl
53
+ # @param [Boolean] persistent
54
+ # @return [LLM::Transport]
55
+ def default_transport(host:, port:, timeout:, ssl:, persistent:)
56
+ target = persistent ? LLM::Transport::PersistentHTTP : LLM::Transport::HTTP
57
+ target.new(host:, port:, timeout:, ssl:)
33
58
  end
34
59
  end
35
60
  end