llm.rb 4.4.0 → 4.5.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.
- checksums.yaml +4 -4
- data/README.md +37 -13
- data/lib/llm/provider.rb +29 -24
- data/lib/llm/providers/anthropic/files.rb +19 -13
- data/lib/llm/providers/anthropic/models.rb +4 -3
- data/lib/llm/providers/anthropic.rb +11 -8
- data/lib/llm/providers/gemini/files.rb +16 -11
- data/lib/llm/providers/gemini/images.rb +4 -3
- data/lib/llm/providers/gemini/models.rb +4 -3
- data/lib/llm/providers/gemini/request_adapter/completion.rb +1 -1
- data/lib/llm/providers/gemini.rb +11 -7
- data/lib/llm/providers/ollama/models.rb +4 -3
- data/lib/llm/providers/ollama.rb +12 -8
- data/lib/llm/providers/openai/audio.rb +10 -7
- data/lib/llm/providers/openai/files.rb +16 -11
- data/lib/llm/providers/openai/images.rb +10 -7
- data/lib/llm/providers/openai/models.rb +4 -3
- data/lib/llm/providers/openai/moderations.rb +4 -3
- data/lib/llm/providers/openai/responses.rb +10 -7
- data/lib/llm/providers/openai/vector_stores.rb +34 -23
- data/lib/llm/providers/openai.rb +12 -8
- data/lib/llm/tracer/telemetry.rb +26 -8
- data/lib/llm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c8252114d7ab58f00fd2d14389e932cfa9b2f0a71d96d9ea2c261f1f8b67d721
|
|
4
|
+
data.tar.gz: 8c00745ba750d0e271d8a4e5d5d9418a13556847c181e912aa8a8a1e7a9344b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b45173644a3803db844c1cad679a14ec647e058bd4517ce0160b7f2f352480877375748a4abb727007d3c76e7b977a6c106a5bfa438575f69ef8cd60a5612d2
|
|
7
|
+
data.tar.gz: bebdb107819b1410bc6644529ad0f56544deadbae9661ab8ed2c84a420c74097b357113650de7ed7da1d0b388c601a9b7e60e30d887b62d72e7cfb84f3f0dd1a
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"><img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc"></a>
|
|
6
6
|
<a href="https://opensource.org/license/0bsd"><img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License"></a>
|
|
7
|
-
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.
|
|
7
|
+
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.5.0-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
|
@@ -177,11 +177,47 @@ end
|
|
|
177
177
|
ses.talk(prompt)
|
|
178
178
|
```
|
|
179
179
|
|
|
180
|
+
#### Threads
|
|
181
|
+
|
|
182
|
+
llm.rb is designed for threaded environments with throughput in mind.
|
|
183
|
+
Locks are used selectively, and localized state is preferred wherever
|
|
184
|
+
possible. Blanket locking across every class would help guarantee
|
|
185
|
+
correctness but it would also add contention, reduce throughput,
|
|
186
|
+
and increase complexity.
|
|
187
|
+
|
|
188
|
+
That's why we decided to optimize for both correctness and throughput
|
|
189
|
+
instead. An important part of that design is guaranteeing that
|
|
190
|
+
[LLM::Provider](https://0x1eef.github.io/x/llm.rb/LLM/Provider.html)
|
|
191
|
+
is safe to share across threads. [LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html) and
|
|
192
|
+
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html) are
|
|
193
|
+
stateful objects that should be kept local to a single thread. So the
|
|
194
|
+
recommended pattern is to keep one session or agent per thread,
|
|
195
|
+
and share a provider across multiple threads:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
#!/usr/bin/env ruby
|
|
199
|
+
require "llm"
|
|
200
|
+
|
|
201
|
+
llm = LLM.openai(key: ENV["KEY"], persistent: true)
|
|
202
|
+
schema = llm.schema.object(answer: llm.schema.integer.required)
|
|
203
|
+
|
|
204
|
+
vals = 10.times.map do |x|
|
|
205
|
+
Thread.new do
|
|
206
|
+
ses = LLM::Session.new(llm, schema:)
|
|
207
|
+
res = ses.talk "#{x} + 5 = ?"
|
|
208
|
+
res.messages.find(&:assistant?).content!
|
|
209
|
+
end
|
|
210
|
+
end.map(&:value)
|
|
211
|
+
|
|
212
|
+
vals.each { |val| puts val }
|
|
213
|
+
```
|
|
214
|
+
|
|
180
215
|
## Features
|
|
181
216
|
|
|
182
217
|
#### General
|
|
183
218
|
- ✅ Unified API across providers
|
|
184
219
|
- 📦 Zero runtime deps (stdlib-only)
|
|
220
|
+
- 🧵 Thread-safe providers for multi-threaded workloads
|
|
185
221
|
- 🧩 Pluggable JSON adapters (JSON, Oj, Yajl, etc)
|
|
186
222
|
- 🧱 Builtin tracer API ([LLM::Tracer](https://0x1eef.github.io/x/llm.rb/LLM/Tracer.html))
|
|
187
223
|
|
|
@@ -438,18 +474,6 @@ ses2.restore(string: json)
|
|
|
438
474
|
ses2.talk "Howdy partner. I'm back"
|
|
439
475
|
```
|
|
440
476
|
|
|
441
|
-
#### Thread Safety
|
|
442
|
-
|
|
443
|
-
The llm.rb library is thread-safe and can be used in a multi-threaded
|
|
444
|
-
environments but it is important to keep in mind that the
|
|
445
|
-
[LLM::Provider](https://0x1eef.github.io/x/llm.rb/LLM/Provider.html)
|
|
446
|
-
and
|
|
447
|
-
[LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html)
|
|
448
|
-
classes should be instantiated once per thread, and not shared
|
|
449
|
-
between threads. Generally the library tries to avoid global or
|
|
450
|
-
shared state but where it exists reentrant locks are used to
|
|
451
|
-
ensure thread-safety.
|
|
452
|
-
|
|
453
477
|
### Tools
|
|
454
478
|
|
|
455
479
|
#### LLM::Function
|
data/lib/llm/provider.rb
CHANGED
|
@@ -36,10 +36,11 @@ class LLM::Provider
|
|
|
36
36
|
@port = port
|
|
37
37
|
@timeout = timeout
|
|
38
38
|
@ssl = ssl
|
|
39
|
-
@client = persistent ? persistent_client :
|
|
39
|
+
@client = persistent ? persistent_client : nil
|
|
40
40
|
@tracer = LLM::Tracer::Null.new(self)
|
|
41
41
|
@base_uri = URI("#{ssl ? "https" : "http"}://#{host}:#{port}/")
|
|
42
42
|
@headers = {"User-Agent" => "llm.rb v#{LLM::VERSION}"}
|
|
43
|
+
@monitor = Monitor.new
|
|
43
44
|
end
|
|
44
45
|
|
|
45
46
|
##
|
|
@@ -182,7 +183,7 @@ class LLM::Provider
|
|
|
182
183
|
# Returns an object that can generate a JSON schema
|
|
183
184
|
# @return [LLM::Schema]
|
|
184
185
|
def schema
|
|
185
|
-
|
|
186
|
+
LLM::Schema.new
|
|
186
187
|
end
|
|
187
188
|
|
|
188
189
|
##
|
|
@@ -196,7 +197,9 @@ class LLM::Provider
|
|
|
196
197
|
# @return [LLM::Provider]
|
|
197
198
|
# Returns self
|
|
198
199
|
def with(headers:)
|
|
199
|
-
|
|
200
|
+
lock do
|
|
201
|
+
tap { @headers.merge!(headers) }
|
|
202
|
+
end
|
|
200
203
|
end
|
|
201
204
|
|
|
202
205
|
##
|
|
@@ -277,10 +280,12 @@ class LLM::Provider
|
|
|
277
280
|
# A tracer
|
|
278
281
|
# @return [void]
|
|
279
282
|
def tracer=(tracer)
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
283
|
+
lock do
|
|
284
|
+
@tracer = if tracer.nil?
|
|
285
|
+
LLM::Tracer::Null.new(self)
|
|
286
|
+
else
|
|
287
|
+
tracer
|
|
288
|
+
end
|
|
284
289
|
end
|
|
285
290
|
end
|
|
286
291
|
|
|
@@ -336,10 +341,12 @@ class LLM::Provider
|
|
|
336
341
|
# When there is a network error at the operating system level
|
|
337
342
|
# @return [Net::HTTPResponse]
|
|
338
343
|
def execute(request:, operation:, stream: nil, stream_parser: self.stream_parser, model: nil, &b)
|
|
339
|
-
|
|
340
|
-
|
|
344
|
+
tracer = @tracer
|
|
345
|
+
span = tracer.on_request_start(operation:, model:)
|
|
346
|
+
http = client || transient_client
|
|
347
|
+
args = (Net::HTTP === http) ? [request] : [URI.join(base_uri, request.path), request]
|
|
341
348
|
res = if stream
|
|
342
|
-
|
|
349
|
+
http.request(*args) do |res|
|
|
343
350
|
handler = event_handler.new stream_parser.new(stream)
|
|
344
351
|
parser = LLM::EventStream::Parser.new
|
|
345
352
|
parser.register(handler)
|
|
@@ -353,10 +360,10 @@ class LLM::Provider
|
|
|
353
360
|
parser&.free
|
|
354
361
|
end
|
|
355
362
|
else
|
|
356
|
-
b ?
|
|
357
|
-
|
|
363
|
+
b ? http.request(*args) { (Net::HTTPSuccess === _1) ? b.call(_1) : _1 } :
|
|
364
|
+
http.request(*args)
|
|
358
365
|
end
|
|
359
|
-
[handle_response(res, span), span]
|
|
366
|
+
[handle_response(res, tracer, span), span, tracer]
|
|
360
367
|
end
|
|
361
368
|
|
|
362
369
|
##
|
|
@@ -366,14 +373,18 @@ class LLM::Provider
|
|
|
366
373
|
# @param [Object, nil] span
|
|
367
374
|
# The span
|
|
368
375
|
# @return [Net::HTTPResponse]
|
|
369
|
-
def handle_response(res, span)
|
|
376
|
+
def handle_response(res, tracer, span)
|
|
370
377
|
case res
|
|
371
378
|
when Net::HTTPOK then res.body = parse_response(res)
|
|
372
|
-
else error_handler.new(
|
|
379
|
+
else error_handler.new(tracer, span, res).raise_error!
|
|
373
380
|
end
|
|
374
381
|
res
|
|
375
382
|
end
|
|
376
383
|
|
|
384
|
+
##
|
|
385
|
+
# Parse a HTTP response
|
|
386
|
+
# @param [Net::HTTPResponse] res
|
|
387
|
+
# @return [LLM::Object, String]
|
|
377
388
|
def parse_response(res)
|
|
378
389
|
case res["content-type"]
|
|
379
390
|
when %r|\Aapplication/json\s*| then LLM::Object.from(LLM.json.load(res.body))
|
|
@@ -418,14 +429,8 @@ class LLM::Provider
|
|
|
418
429
|
end
|
|
419
430
|
|
|
420
431
|
##
|
|
421
|
-
#
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
# @param [LLM::Response] res
|
|
425
|
-
# @param [Object, nil] span
|
|
426
|
-
# @return [LLM::Response]
|
|
427
|
-
def finish_trace(operation:, res:, model: nil, span: nil)
|
|
428
|
-
@tracer.on_request_finish(operation:, model:, res:, span:)
|
|
429
|
-
res
|
|
432
|
+
# @api private
|
|
433
|
+
def lock(&)
|
|
434
|
+
@monitor.synchronize(&)
|
|
430
435
|
end
|
|
431
436
|
end
|
|
@@ -38,9 +38,10 @@ class LLM::Anthropic
|
|
|
38
38
|
def all(**params)
|
|
39
39
|
query = URI.encode_www_form(params)
|
|
40
40
|
req = Net::HTTP::Get.new("/v1/files?#{query}", headers)
|
|
41
|
-
res, span = execute(request: req, operation: "request")
|
|
41
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
42
42
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
43
|
-
|
|
43
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
44
|
+
res
|
|
44
45
|
end
|
|
45
46
|
|
|
46
47
|
##
|
|
@@ -58,9 +59,10 @@ class LLM::Anthropic
|
|
|
58
59
|
req = Net::HTTP::Post.new("/v1/files", headers)
|
|
59
60
|
req["content-type"] = multi.content_type
|
|
60
61
|
set_body_stream(req, multi.body)
|
|
61
|
-
res, span = execute(request: req, operation: "request")
|
|
62
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
62
63
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
63
|
-
|
|
64
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
65
|
+
res
|
|
64
66
|
end
|
|
65
67
|
|
|
66
68
|
##
|
|
@@ -78,9 +80,10 @@ class LLM::Anthropic
|
|
|
78
80
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
79
81
|
query = URI.encode_www_form(params)
|
|
80
82
|
req = Net::HTTP::Get.new("/v1/files/#{file_id}?#{query}", headers)
|
|
81
|
-
res, span = execute(request: req, operation: "request")
|
|
83
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
82
84
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
83
|
-
|
|
85
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
86
|
+
res
|
|
84
87
|
end
|
|
85
88
|
|
|
86
89
|
##
|
|
@@ -98,9 +101,10 @@ class LLM::Anthropic
|
|
|
98
101
|
query = URI.encode_www_form(params)
|
|
99
102
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
100
103
|
req = Net::HTTP::Get.new("/v1/files/#{file_id}?#{query}", headers)
|
|
101
|
-
res, span = execute(request: req, operation: "request")
|
|
104
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
102
105
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
103
|
-
|
|
106
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
107
|
+
res
|
|
104
108
|
end
|
|
105
109
|
alias_method :retrieve_metadata, :get_metadata
|
|
106
110
|
|
|
@@ -117,9 +121,10 @@ class LLM::Anthropic
|
|
|
117
121
|
def delete(file:)
|
|
118
122
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
119
123
|
req = Net::HTTP::Delete.new("/v1/files/#{file_id}", headers)
|
|
120
|
-
res, span = execute(request: req, operation: "request")
|
|
124
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
121
125
|
res = LLM::Response.new(res)
|
|
122
|
-
|
|
126
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
127
|
+
res
|
|
123
128
|
end
|
|
124
129
|
|
|
125
130
|
##
|
|
@@ -142,9 +147,10 @@ class LLM::Anthropic
|
|
|
142
147
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
143
148
|
req = Net::HTTP::Get.new("/v1/files/#{file_id}/content?#{query}", headers)
|
|
144
149
|
io = StringIO.new("".b)
|
|
145
|
-
res, span = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
150
|
+
res, span, tracer = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
146
151
|
res = LLM::Response.new(res).tap { _1.define_singleton_method(:file) { io } }
|
|
147
|
-
|
|
152
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
153
|
+
res
|
|
148
154
|
end
|
|
149
155
|
|
|
150
156
|
private
|
|
@@ -153,7 +159,7 @@ class LLM::Anthropic
|
|
|
153
159
|
@provider.instance_variable_get(:@key)
|
|
154
160
|
end
|
|
155
161
|
|
|
156
|
-
[:headers, :execute, :set_body_stream
|
|
162
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
157
163
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
158
164
|
end
|
|
159
165
|
end
|
|
@@ -40,14 +40,15 @@ class LLM::Anthropic
|
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params)
|
|
42
42
|
req = Net::HTTP::Get.new("/v1/models?#{query}", headers)
|
|
43
|
-
res, span = execute(request: req, operation: "request")
|
|
43
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
45
|
-
|
|
45
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
46
|
+
res
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
private
|
|
49
50
|
|
|
50
|
-
[:headers, :execute
|
|
51
|
+
[:headers, :execute].each do |m|
|
|
51
52
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
52
53
|
end
|
|
53
54
|
end
|
|
@@ -43,10 +43,11 @@ module LLM
|
|
|
43
43
|
def complete(prompt, params = {})
|
|
44
44
|
params, stream, tools, role = normalize_complete_params(params)
|
|
45
45
|
req = build_complete_request(prompt, params, role)
|
|
46
|
-
res, span = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
46
|
+
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
47
47
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
48
48
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
49
|
-
|
|
49
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
50
|
+
res
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
##
|
|
@@ -110,12 +111,14 @@ module LLM
|
|
|
110
111
|
private
|
|
111
112
|
|
|
112
113
|
def headers
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
114
|
+
lock do
|
|
115
|
+
(@headers || {}).merge(
|
|
116
|
+
"Content-Type" => "application/json",
|
|
117
|
+
"x-api-key" => @key,
|
|
118
|
+
"anthropic-version" => "2023-06-01",
|
|
119
|
+
"anthropic-beta" => "files-api-2025-04-14"
|
|
120
|
+
)
|
|
121
|
+
end
|
|
119
122
|
end
|
|
120
123
|
|
|
121
124
|
def stream_parser
|
|
@@ -46,9 +46,10 @@ class LLM::Gemini
|
|
|
46
46
|
def all(**params)
|
|
47
47
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
48
48
|
req = Net::HTTP::Get.new("/v1beta/files?#{query}", headers)
|
|
49
|
-
res, span = execute(request: req, operation: "request")
|
|
49
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
50
50
|
res = ResponseAdapter.adapt(res, type: :files)
|
|
51
|
-
|
|
51
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
52
|
+
res
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
##
|
|
@@ -69,9 +70,10 @@ class LLM::Gemini
|
|
|
69
70
|
req["X-Goog-Upload-Command"] = "upload, finalize"
|
|
70
71
|
file.with_io do |io|
|
|
71
72
|
set_body_stream(req, io)
|
|
72
|
-
res, span = execute(request: req, operation: "request")
|
|
73
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
73
74
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
74
|
-
|
|
75
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
76
|
+
res
|
|
75
77
|
end
|
|
76
78
|
end
|
|
77
79
|
|
|
@@ -90,9 +92,10 @@ class LLM::Gemini
|
|
|
90
92
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
91
93
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
92
94
|
req = Net::HTTP::Get.new("/v1beta/#{file_id}?#{query}", headers)
|
|
93
|
-
res, span = execute(request: req, operation: "request")
|
|
95
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
94
96
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
95
|
-
|
|
97
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
98
|
+
res
|
|
96
99
|
end
|
|
97
100
|
|
|
98
101
|
##
|
|
@@ -109,9 +112,10 @@ class LLM::Gemini
|
|
|
109
112
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
110
113
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
111
114
|
req = Net::HTTP::Delete.new("/v1beta/#{file_id}?#{query}", headers)
|
|
112
|
-
res, span = execute(request: req, operation: "request")
|
|
115
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
113
116
|
res = LLM::Response.new(res)
|
|
114
|
-
|
|
117
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
118
|
+
res
|
|
115
119
|
end
|
|
116
120
|
|
|
117
121
|
##
|
|
@@ -132,8 +136,9 @@ class LLM::Gemini
|
|
|
132
136
|
req["X-Goog-Upload-Header-Content-Length"] = file.bytesize
|
|
133
137
|
req["X-Goog-Upload-Header-Content-Type"] = file.mime_type
|
|
134
138
|
req.body = LLM.json.dump({file: {display_name: File.basename(file.path)}})
|
|
135
|
-
res, span = execute(request: req, operation: "request")
|
|
136
|
-
|
|
139
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
140
|
+
trace_res = LLM::Response.new(res)
|
|
141
|
+
tracer.on_request_finish(operation: "request", res: trace_res, span:)
|
|
137
142
|
res["x-goog-upload-url"]
|
|
138
143
|
end
|
|
139
144
|
|
|
@@ -141,7 +146,7 @@ class LLM::Gemini
|
|
|
141
146
|
@provider.instance_variable_get(:@key)
|
|
142
147
|
end
|
|
143
148
|
|
|
144
|
-
[:headers, :execute, :set_body_stream
|
|
149
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
145
150
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
146
151
|
end
|
|
147
152
|
end
|
|
@@ -51,9 +51,10 @@ class LLM::Gemini
|
|
|
51
51
|
instances: [{prompt:}]
|
|
52
52
|
})
|
|
53
53
|
req.body = body
|
|
54
|
-
res, span = execute(request: req, operation: "request")
|
|
54
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
55
55
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
56
|
-
|
|
56
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
57
|
+
res
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
##
|
|
@@ -90,7 +91,7 @@ class LLM::Gemini
|
|
|
90
91
|
@provider.instance_variable_get(:@key)
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
[:headers, :execute, :set_body_stream
|
|
94
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
94
95
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
95
96
|
end
|
|
96
97
|
end
|
|
@@ -42,9 +42,10 @@ class LLM::Gemini
|
|
|
42
42
|
def all(**params)
|
|
43
43
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
44
44
|
req = Net::HTTP::Get.new("/v1beta/models?#{query}", headers)
|
|
45
|
-
res, span = execute(request: req, operation: "request")
|
|
45
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
46
46
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
47
|
-
|
|
47
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
48
|
+
res
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
private
|
|
@@ -53,7 +54,7 @@ class LLM::Gemini
|
|
|
53
54
|
@provider.instance_variable_get(:@key)
|
|
54
55
|
end
|
|
55
56
|
|
|
56
|
-
[:headers, :execute
|
|
57
|
+
[:headers, :execute].each do |m|
|
|
57
58
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
58
59
|
end
|
|
59
60
|
end
|
|
@@ -48,7 +48,7 @@ module LLM::Gemini::RequestAdapter
|
|
|
48
48
|
def adapt_object(object)
|
|
49
49
|
case object.kind
|
|
50
50
|
when :image_url
|
|
51
|
-
[{file_data: {
|
|
51
|
+
[{file_data: {file_uri: object.value.to_s}}]
|
|
52
52
|
when :local_file
|
|
53
53
|
file = object.value
|
|
54
54
|
[{inline_data: {mime_type: file.mime_type, data: file.to_b64}}]
|
data/lib/llm/providers/gemini.rb
CHANGED
|
@@ -49,9 +49,10 @@ module LLM
|
|
|
49
49
|
path = ["/v1beta/models/#{model}", "embedContent?key=#{@key}"].join(":")
|
|
50
50
|
req = Net::HTTP::Post.new(path, headers)
|
|
51
51
|
req.body = LLM.json.dump({content: {parts: [{text: input}]}})
|
|
52
|
-
res, span = execute(request: req, operation: "embeddings", model:)
|
|
52
|
+
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
53
53
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
54
|
-
|
|
54
|
+
tracer.on_request_finish(operation: "embeddings", model:, res:, span:)
|
|
55
|
+
res
|
|
55
56
|
end
|
|
56
57
|
|
|
57
58
|
##
|
|
@@ -67,10 +68,11 @@ module LLM
|
|
|
67
68
|
def complete(prompt, params = {})
|
|
68
69
|
params, stream, tools, role, model = normalize_complete_params(params)
|
|
69
70
|
req = build_complete_request(prompt, params, role, model, stream)
|
|
70
|
-
res, span = execute(request: req, stream: stream, operation: "chat", model:)
|
|
71
|
+
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model:)
|
|
71
72
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
72
73
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
73
|
-
|
|
74
|
+
tracer.on_request_finish(operation: "chat", model:, res:, span:)
|
|
75
|
+
res
|
|
74
76
|
end
|
|
75
77
|
|
|
76
78
|
##
|
|
@@ -167,9 +169,11 @@ module LLM
|
|
|
167
169
|
private
|
|
168
170
|
|
|
169
171
|
def headers
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
lock do
|
|
173
|
+
(@headers || {}).merge(
|
|
174
|
+
"Content-Type" => "application/json"
|
|
175
|
+
)
|
|
176
|
+
end
|
|
173
177
|
end
|
|
174
178
|
|
|
175
179
|
def stream_parser
|
|
@@ -43,14 +43,15 @@ class LLM::Ollama
|
|
|
43
43
|
def all(**params)
|
|
44
44
|
query = URI.encode_www_form(params)
|
|
45
45
|
req = Net::HTTP::Get.new("/api/tags?#{query}", headers)
|
|
46
|
-
res, span = execute(request: req, operation: "request")
|
|
46
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
47
47
|
res = LLM::Response.new(res)
|
|
48
|
-
|
|
48
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
49
|
+
res
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
private
|
|
52
53
|
|
|
53
|
-
[:headers, :execute
|
|
54
|
+
[:headers, :execute].each do |m|
|
|
54
55
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
55
56
|
end
|
|
56
57
|
end
|
data/lib/llm/providers/ollama.rb
CHANGED
|
@@ -43,9 +43,10 @@ module LLM
|
|
|
43
43
|
params = {model:}.merge!(params)
|
|
44
44
|
req = Net::HTTP::Post.new("/v1/embeddings", headers)
|
|
45
45
|
req.body = LLM.json.dump({input:}.merge!(params))
|
|
46
|
-
res, span = execute(request: req, operation: "embeddings", model:)
|
|
46
|
+
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
47
47
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
48
|
-
|
|
48
|
+
tracer.on_request_finish(operation: "embeddings", model:, res:, span:)
|
|
49
|
+
res
|
|
49
50
|
end
|
|
50
51
|
|
|
51
52
|
##
|
|
@@ -61,10 +62,11 @@ module LLM
|
|
|
61
62
|
def complete(prompt, params = {})
|
|
62
63
|
params, stream, tools, role = normalize_complete_params(params)
|
|
63
64
|
req = build_complete_request(prompt, params, role)
|
|
64
|
-
res, span = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
65
|
+
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
65
66
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
66
67
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
67
|
-
|
|
68
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
69
|
+
res
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
##
|
|
@@ -92,10 +94,12 @@ module LLM
|
|
|
92
94
|
private
|
|
93
95
|
|
|
94
96
|
def headers
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
lock do
|
|
98
|
+
(@headers || {}).merge(
|
|
99
|
+
"Content-Type" => "application/json",
|
|
100
|
+
"Authorization" => "Bearer #{@key}"
|
|
101
|
+
)
|
|
102
|
+
end
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def stream_parser
|
|
@@ -35,9 +35,10 @@ class LLM::OpenAI
|
|
|
35
35
|
req = Net::HTTP::Post.new("/v1/audio/speech", headers)
|
|
36
36
|
req.body = LLM.json.dump({input:, voice:, model:, response_format:}.merge!(params))
|
|
37
37
|
io = StringIO.new("".b)
|
|
38
|
-
res, span = execute(request: req, operation: "request") { _1.read_body { |chunk| io << chunk } }
|
|
38
|
+
res, span, tracer = execute(request: req, operation: "request") { _1.read_body { |chunk| io << chunk } }
|
|
39
39
|
res = LLM::Response.new(res).tap { _1.define_singleton_method(:audio) { io } }
|
|
40
|
-
|
|
40
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
41
|
+
res
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
##
|
|
@@ -57,9 +58,10 @@ class LLM::OpenAI
|
|
|
57
58
|
req = Net::HTTP::Post.new("/v1/audio/transcriptions", headers)
|
|
58
59
|
req["content-type"] = multi.content_type
|
|
59
60
|
set_body_stream(req, multi.body)
|
|
60
|
-
res, span = execute(request: req, operation: "request")
|
|
61
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
61
62
|
res = LLM::Response.new(res)
|
|
62
|
-
|
|
63
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
64
|
+
res
|
|
63
65
|
end
|
|
64
66
|
|
|
65
67
|
##
|
|
@@ -80,14 +82,15 @@ class LLM::OpenAI
|
|
|
80
82
|
req = Net::HTTP::Post.new("/v1/audio/translations", headers)
|
|
81
83
|
req["content-type"] = multi.content_type
|
|
82
84
|
set_body_stream(req, multi.body)
|
|
83
|
-
res, span = execute(request: req, operation: "request")
|
|
85
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
84
86
|
res = LLM::Response.new(res)
|
|
85
|
-
|
|
87
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
88
|
+
res
|
|
86
89
|
end
|
|
87
90
|
|
|
88
91
|
private
|
|
89
92
|
|
|
90
|
-
[:headers, :execute, :set_body_stream
|
|
93
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
91
94
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
92
95
|
end
|
|
93
96
|
end
|
|
@@ -41,9 +41,10 @@ class LLM::OpenAI
|
|
|
41
41
|
def all(**params)
|
|
42
42
|
query = URI.encode_www_form(params)
|
|
43
43
|
req = Net::HTTP::Get.new("/v1/files?#{query}", headers)
|
|
44
|
-
res, span = execute(request: req, operation: "request")
|
|
44
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
45
45
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
46
|
-
|
|
46
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
47
|
+
res
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
##
|
|
@@ -62,9 +63,10 @@ class LLM::OpenAI
|
|
|
62
63
|
req = Net::HTTP::Post.new("/v1/files", headers)
|
|
63
64
|
req["content-type"] = multi.content_type
|
|
64
65
|
set_body_stream(req, multi.body)
|
|
65
|
-
res, span = execute(request: req, operation: "request")
|
|
66
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
66
67
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
67
|
-
|
|
68
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
69
|
+
res
|
|
68
70
|
end
|
|
69
71
|
|
|
70
72
|
##
|
|
@@ -82,9 +84,10 @@ class LLM::OpenAI
|
|
|
82
84
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
83
85
|
query = URI.encode_www_form(params)
|
|
84
86
|
req = Net::HTTP::Get.new("/v1/files/#{file_id}?#{query}", headers)
|
|
85
|
-
res, span = execute(request: req, operation: "request")
|
|
87
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
86
88
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
87
|
-
|
|
89
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
90
|
+
res
|
|
88
91
|
end
|
|
89
92
|
|
|
90
93
|
##
|
|
@@ -104,9 +107,10 @@ class LLM::OpenAI
|
|
|
104
107
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
105
108
|
req = Net::HTTP::Get.new("/v1/files/#{file_id}/content?#{query}", headers)
|
|
106
109
|
io = StringIO.new("".b)
|
|
107
|
-
res, span = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
110
|
+
res, span, tracer = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
108
111
|
res = LLM::Response.new(res).tap { _1.define_singleton_method(:file) { io } }
|
|
109
|
-
|
|
112
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
113
|
+
res
|
|
110
114
|
end
|
|
111
115
|
|
|
112
116
|
##
|
|
@@ -122,14 +126,15 @@ class LLM::OpenAI
|
|
|
122
126
|
def delete(file:)
|
|
123
127
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
124
128
|
req = Net::HTTP::Delete.new("/v1/files/#{file_id}", headers)
|
|
125
|
-
res, span = execute(request: req, operation: "request")
|
|
129
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
126
130
|
res = LLM::Response.new(res)
|
|
127
|
-
|
|
131
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
132
|
+
res
|
|
128
133
|
end
|
|
129
134
|
|
|
130
135
|
private
|
|
131
136
|
|
|
132
|
-
[:headers, :execute, :set_body_stream
|
|
137
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
133
138
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
134
139
|
end
|
|
135
140
|
end
|
|
@@ -50,9 +50,10 @@ class LLM::OpenAI
|
|
|
50
50
|
def create(prompt:, model: "dall-e-3", **params)
|
|
51
51
|
req = Net::HTTP::Post.new("/v1/images/generations", headers)
|
|
52
52
|
req.body = LLM.json.dump({prompt:, n: 1, model:}.merge!(params))
|
|
53
|
-
res, span = execute(request: req, operation: "request")
|
|
53
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
54
54
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
55
|
-
|
|
55
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
56
|
+
res
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
##
|
|
@@ -73,9 +74,10 @@ class LLM::OpenAI
|
|
|
73
74
|
req = Net::HTTP::Post.new("/v1/images/variations", headers)
|
|
74
75
|
req["content-type"] = multi.content_type
|
|
75
76
|
set_body_stream(req, multi.body)
|
|
76
|
-
res, span = execute(request: req, operation: "request")
|
|
77
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
77
78
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
78
|
-
|
|
79
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
80
|
+
res
|
|
79
81
|
end
|
|
80
82
|
|
|
81
83
|
##
|
|
@@ -97,14 +99,15 @@ class LLM::OpenAI
|
|
|
97
99
|
req = Net::HTTP::Post.new("/v1/images/edits", headers)
|
|
98
100
|
req["content-type"] = multi.content_type
|
|
99
101
|
set_body_stream(req, multi.body)
|
|
100
|
-
res, span = execute(request: req, operation: "request")
|
|
102
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
101
103
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
102
|
-
|
|
104
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
105
|
+
res
|
|
103
106
|
end
|
|
104
107
|
|
|
105
108
|
private
|
|
106
109
|
|
|
107
|
-
[:headers, :execute, :set_body_stream
|
|
110
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
108
111
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
109
112
|
end
|
|
110
113
|
end
|
|
@@ -40,14 +40,15 @@ class LLM::OpenAI
|
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params)
|
|
42
42
|
req = Net::HTTP::Get.new("/v1/models?#{query}", headers)
|
|
43
|
-
res, span = execute(request: req, operation: "request")
|
|
43
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
45
|
-
|
|
45
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
46
|
+
res
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
private
|
|
49
50
|
|
|
50
|
-
[:headers, :execute, :set_body_stream
|
|
51
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
51
52
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
52
53
|
end
|
|
53
54
|
end
|
|
@@ -50,14 +50,15 @@ class LLM::OpenAI
|
|
|
50
50
|
req = Net::HTTP::Post.new("/v1/moderations", headers)
|
|
51
51
|
input = RequestAdapter::Moderation.new(input).adapt
|
|
52
52
|
req.body = LLM.json.dump({input:, model:}.merge!(params))
|
|
53
|
-
res, span = execute(request: req, operation: "request")
|
|
53
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
54
54
|
res = ResponseAdapter.adapt(res, type: :moderations)
|
|
55
|
-
|
|
55
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
56
|
+
res
|
|
56
57
|
end
|
|
57
58
|
|
|
58
59
|
private
|
|
59
60
|
|
|
60
|
-
[:headers, :execute
|
|
61
|
+
[:headers, :execute].each do |m|
|
|
61
62
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -44,10 +44,11 @@ class LLM::OpenAI
|
|
|
44
44
|
messages = [*(params.delete(:input) || []), LLM::Message.new(role, prompt)]
|
|
45
45
|
body = LLM.json.dump({input: [adapt(messages, mode: :response)].flatten}.merge!(params))
|
|
46
46
|
set_body_stream(req, StringIO.new(body))
|
|
47
|
-
res, span = execute(request: req, stream:, stream_parser:, operation: "chat", model: params[:model])
|
|
47
|
+
res, span, tracer = execute(request: req, stream:, stream_parser:, operation: "chat", model: params[:model])
|
|
48
48
|
res = ResponseAdapter.adapt(res, type: :responds)
|
|
49
49
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
50
|
-
|
|
50
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
51
|
+
res
|
|
51
52
|
end
|
|
52
53
|
|
|
53
54
|
##
|
|
@@ -60,9 +61,10 @@ class LLM::OpenAI
|
|
|
60
61
|
response_id = response.respond_to?(:id) ? response.id : response
|
|
61
62
|
query = URI.encode_www_form(params)
|
|
62
63
|
req = Net::HTTP::Get.new("/v1/responses/#{response_id}?#{query}", headers)
|
|
63
|
-
res, span = execute(request: req, operation: "request")
|
|
64
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
64
65
|
res = ResponseAdapter.adapt(res, type: :responds)
|
|
65
|
-
|
|
66
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
67
|
+
res
|
|
66
68
|
end
|
|
67
69
|
|
|
68
70
|
##
|
|
@@ -74,14 +76,15 @@ class LLM::OpenAI
|
|
|
74
76
|
def delete(response)
|
|
75
77
|
response_id = response.respond_to?(:id) ? response.id : response
|
|
76
78
|
req = Net::HTTP::Delete.new("/v1/responses/#{response_id}", headers)
|
|
77
|
-
res, span = execute(request: req, operation: "request")
|
|
79
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
78
80
|
res = LLM::Response.new(res)
|
|
79
|
-
|
|
81
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
82
|
+
res
|
|
80
83
|
end
|
|
81
84
|
|
|
82
85
|
private
|
|
83
86
|
|
|
84
|
-
[:headers, :execute, :set_body_stream, :resolve_tools
|
|
87
|
+
[:headers, :execute, :set_body_stream, :resolve_tools].each do |m|
|
|
85
88
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
86
89
|
end
|
|
87
90
|
|
|
@@ -32,9 +32,10 @@ class LLM::OpenAI
|
|
|
32
32
|
def all(**params)
|
|
33
33
|
query = URI.encode_www_form(params)
|
|
34
34
|
req = Net::HTTP::Get.new("/v1/vector_stores?#{query}", headers)
|
|
35
|
-
res, span = execute(request: req, operation: "request")
|
|
35
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
36
36
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
37
|
-
|
|
37
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
38
|
+
res
|
|
38
39
|
end
|
|
39
40
|
|
|
40
41
|
##
|
|
@@ -48,9 +49,10 @@ class LLM::OpenAI
|
|
|
48
49
|
def create(name:, file_ids: nil, **params)
|
|
49
50
|
req = Net::HTTP::Post.new("/v1/vector_stores", headers)
|
|
50
51
|
req.body = LLM.json.dump(params.merge({name:, file_ids:}).compact)
|
|
51
|
-
res, span = execute(request: req, operation: "request")
|
|
52
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
52
53
|
res = LLM::Response.new(res)
|
|
53
|
-
|
|
54
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
55
|
+
res
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
##
|
|
@@ -71,9 +73,10 @@ class LLM::OpenAI
|
|
|
71
73
|
def get(vector:)
|
|
72
74
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
73
75
|
req = Net::HTTP::Get.new("/v1/vector_stores/#{vector_id}", headers)
|
|
74
|
-
res, span = execute(request: req, operation: "request")
|
|
76
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
75
77
|
res = LLM::Response.new(res)
|
|
76
|
-
|
|
78
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
79
|
+
res
|
|
77
80
|
end
|
|
78
81
|
|
|
79
82
|
##
|
|
@@ -88,9 +91,10 @@ class LLM::OpenAI
|
|
|
88
91
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
89
92
|
req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}", headers)
|
|
90
93
|
req.body = LLM.json.dump(params.merge({name:}).compact)
|
|
91
|
-
res, span = execute(request: req, operation: "request")
|
|
94
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
92
95
|
res = LLM::Response.new(res)
|
|
93
|
-
|
|
96
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
97
|
+
res
|
|
94
98
|
end
|
|
95
99
|
|
|
96
100
|
##
|
|
@@ -102,9 +106,10 @@ class LLM::OpenAI
|
|
|
102
106
|
def delete(vector:)
|
|
103
107
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
104
108
|
req = Net::HTTP::Delete.new("/v1/vector_stores/#{vector_id}", headers)
|
|
105
|
-
res, span = execute(request: req, operation: "request")
|
|
109
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
106
110
|
res = LLM::Response.new(res)
|
|
107
|
-
|
|
111
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
112
|
+
res
|
|
108
113
|
end
|
|
109
114
|
|
|
110
115
|
##
|
|
@@ -119,9 +124,10 @@ class LLM::OpenAI
|
|
|
119
124
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
120
125
|
req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/search", headers)
|
|
121
126
|
req.body = LLM.json.dump(params.merge({query:}).compact)
|
|
122
|
-
res, span = execute(request: req, operation: "retrieval")
|
|
127
|
+
res, span, tracer = execute(request: req, operation: "retrieval")
|
|
123
128
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
124
|
-
|
|
129
|
+
tracer.on_request_finish(operation: "retrieval", res:, span:)
|
|
130
|
+
res
|
|
125
131
|
end
|
|
126
132
|
|
|
127
133
|
##
|
|
@@ -135,9 +141,10 @@ class LLM::OpenAI
|
|
|
135
141
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
136
142
|
query = URI.encode_www_form(params)
|
|
137
143
|
req = Net::HTTP::Get.new("/v1/vector_stores/#{vector_id}/files?#{query}", headers)
|
|
138
|
-
res, span = execute(request: req, operation: "request")
|
|
144
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
139
145
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
140
|
-
|
|
146
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
147
|
+
res
|
|
141
148
|
end
|
|
142
149
|
|
|
143
150
|
##
|
|
@@ -154,9 +161,10 @@ class LLM::OpenAI
|
|
|
154
161
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
155
162
|
req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/files", headers)
|
|
156
163
|
req.body = LLM.json.dump(params.merge({file_id:, attributes:}).compact)
|
|
157
|
-
res, span = execute(request: req, operation: "request")
|
|
164
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
158
165
|
res = LLM::Response.new(res)
|
|
159
|
-
|
|
166
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
167
|
+
res
|
|
160
168
|
end
|
|
161
169
|
alias_method :create_file, :add_file
|
|
162
170
|
|
|
@@ -184,9 +192,10 @@ class LLM::OpenAI
|
|
|
184
192
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
185
193
|
req = Net::HTTP::Post.new("/v1/vector_stores/#{vector_id}/files/#{file_id}", headers)
|
|
186
194
|
req.body = LLM.json.dump(params.merge({attributes:}).compact)
|
|
187
|
-
res, span = execute(request: req, operation: "request")
|
|
195
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
188
196
|
res = LLM::Response.new(res)
|
|
189
|
-
|
|
197
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
198
|
+
res
|
|
190
199
|
end
|
|
191
200
|
|
|
192
201
|
##
|
|
@@ -201,9 +210,10 @@ class LLM::OpenAI
|
|
|
201
210
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
202
211
|
query = URI.encode_www_form(params)
|
|
203
212
|
req = Net::HTTP::Get.new("/v1/vector_stores/#{vector_id}/files/#{file_id}?#{query}", headers)
|
|
204
|
-
res, span = execute(request: req, operation: "request")
|
|
213
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
205
214
|
res = LLM::Response.new(res)
|
|
206
|
-
|
|
215
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
216
|
+
res
|
|
207
217
|
end
|
|
208
218
|
|
|
209
219
|
##
|
|
@@ -217,9 +227,10 @@ class LLM::OpenAI
|
|
|
217
227
|
vector_id = vector.respond_to?(:id) ? vector.id : vector
|
|
218
228
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
219
229
|
req = Net::HTTP::Delete.new("/v1/vector_stores/#{vector_id}/files/#{file_id}", headers)
|
|
220
|
-
res, span = execute(request: req, operation: "request")
|
|
230
|
+
res, span, tracer = execute(request: req, operation: "request")
|
|
221
231
|
res = LLM::Response.new(res)
|
|
222
|
-
|
|
232
|
+
tracer.on_request_finish(operation: "request", res:, span:)
|
|
233
|
+
res
|
|
223
234
|
end
|
|
224
235
|
|
|
225
236
|
##
|
|
@@ -248,7 +259,7 @@ class LLM::OpenAI
|
|
|
248
259
|
|
|
249
260
|
private
|
|
250
261
|
|
|
251
|
-
[:headers, :execute, :set_body_stream
|
|
262
|
+
[:headers, :execute, :set_body_stream].each do |m|
|
|
252
263
|
define_method(m) { |*args, **kwargs, &b| @provider.send(m, *args, **kwargs, &b) }
|
|
253
264
|
end
|
|
254
265
|
end
|
data/lib/llm/providers/openai.rb
CHANGED
|
@@ -47,9 +47,10 @@ module LLM
|
|
|
47
47
|
def embed(input, model: "text-embedding-3-small", **params)
|
|
48
48
|
req = Net::HTTP::Post.new("/v1/embeddings", headers)
|
|
49
49
|
req.body = LLM.json.dump({input:, model:}.merge!(params))
|
|
50
|
-
res, span = execute(request: req, operation: "embeddings", model:)
|
|
50
|
+
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
51
51
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
52
|
-
|
|
52
|
+
tracer.on_request_finish(operation: "embeddings", model:, res:, span:)
|
|
53
|
+
res
|
|
53
54
|
end
|
|
54
55
|
|
|
55
56
|
##
|
|
@@ -65,10 +66,11 @@ module LLM
|
|
|
65
66
|
def complete(prompt, params = {})
|
|
66
67
|
params, stream, tools, role = normalize_complete_params(params)
|
|
67
68
|
req = build_complete_request(prompt, params, role)
|
|
68
|
-
res, span = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
69
|
+
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
69
70
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
70
71
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
71
|
-
|
|
72
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
73
|
+
res
|
|
72
74
|
end
|
|
73
75
|
|
|
74
76
|
##
|
|
@@ -181,10 +183,12 @@ module LLM
|
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def headers
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
186
|
+
lock do
|
|
187
|
+
(@headers || {}).merge(
|
|
188
|
+
"Content-Type" => "application/json",
|
|
189
|
+
"Authorization" => "Bearer #{@key}"
|
|
190
|
+
)
|
|
191
|
+
end
|
|
188
192
|
end
|
|
189
193
|
|
|
190
194
|
def stream_parser
|
data/lib/llm/tracer/telemetry.rb
CHANGED
|
@@ -48,8 +48,6 @@ module LLM
|
|
|
48
48
|
def initialize(provider, options = {})
|
|
49
49
|
super
|
|
50
50
|
@exporter = options.delete(:exporter)
|
|
51
|
-
@root_span = nil
|
|
52
|
-
@root_context = nil
|
|
53
51
|
setup!
|
|
54
52
|
end
|
|
55
53
|
|
|
@@ -69,22 +67,23 @@ module LLM
|
|
|
69
67
|
)
|
|
70
68
|
attrs = attributes.compact
|
|
71
69
|
attrs["llm.trace_group_id"] = trace_group_id.to_s
|
|
72
|
-
|
|
70
|
+
root_span = @tracer.start_span(
|
|
73
71
|
name,
|
|
74
72
|
kind: :server,
|
|
75
73
|
attributes: attrs,
|
|
76
74
|
with_parent: parent_ctx
|
|
77
75
|
)
|
|
78
|
-
|
|
76
|
+
thread[thread_root_span_key] = root_span
|
|
77
|
+
thread[thread_root_context_key] = ::OpenTelemetry::Trace.context_with_span(root_span)
|
|
79
78
|
self
|
|
80
79
|
end
|
|
81
80
|
|
|
82
81
|
##
|
|
83
82
|
# @return [self]
|
|
84
83
|
def stop_trace
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
84
|
+
thread[thread_root_span_key]&.finish
|
|
85
|
+
thread[thread_root_span_key] = nil
|
|
86
|
+
thread[thread_root_context_key] = nil
|
|
88
87
|
self
|
|
89
88
|
end
|
|
90
89
|
|
|
@@ -197,11 +196,30 @@ module LLM
|
|
|
197
196
|
##
|
|
198
197
|
# @api private
|
|
199
198
|
def create_span(name, kind: :client, attributes: {})
|
|
199
|
+
root_context = thread[thread_root_context_key]
|
|
200
200
|
opts = {kind:, attributes:}
|
|
201
|
-
opts[:with_parent] =
|
|
201
|
+
opts[:with_parent] = root_context if root_context
|
|
202
202
|
@tracer.start_span(name, **opts)
|
|
203
203
|
end
|
|
204
204
|
|
|
205
|
+
##
|
|
206
|
+
# @api private
|
|
207
|
+
def thread_root_span_key
|
|
208
|
+
@thread_root_span_key ||= :"llm.telemetry.root_span.#{object_id}"
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
##
|
|
212
|
+
# @api private
|
|
213
|
+
def thread_root_context_key
|
|
214
|
+
@thread_root_context_key ||= :"llm.telemetry.root_context.#{object_id}"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
##
|
|
218
|
+
# @api private
|
|
219
|
+
def thread
|
|
220
|
+
Thread.current
|
|
221
|
+
end
|
|
222
|
+
|
|
205
223
|
##
|
|
206
224
|
# Converts a string trace_group_id to an OpenTelemetry SpanContext so all
|
|
207
225
|
# spans created with this context share the same trace_id.
|
data/lib/llm/version.rb
CHANGED