llm.rb 15.2.2 → 15.4.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/CHANGELOG.md +197 -3
- data/README.md +180 -64
- data/bin/llm.rb +9 -2
- data/data/alibaba.json +45 -0
- data/data/anthropic.json +67 -0
- data/data/bedrock.json +1466 -397
- data/data/deepinfra.json +148 -16
- data/data/deepseek.json +3 -0
- data/data/mistral.json +42 -0
- data/data/openai.json +186 -0
- data/data/openrouter.json +1538 -378
- data/data/xai.json +53 -20
- data/data/zai.json +90 -4
- data/docs/deepdive/advanced/compaction.md +1 -2
- data/docs/deepdive/advanced/context.md +214 -1
- data/docs/deepdive/advanced/guard.md +9 -57
- data/docs/deepdive/features/builtin_tools.md +14 -16
- data/docs/deepdive/features/console.md +5 -0
- data/docs/deepdive/features/database.md +85 -10
- data/docs/deepdive/fundamentals/agents.md +7 -8
- data/docs/deepdive/fundamentals/providers.md +45 -5
- data/docs/deepdive/fundamentals/schema.md +73 -0
- data/docs/deepdive/fundamentals/tools.md +80 -27
- data/docs/deepdive/media/audio.md +8 -19
- data/docs/deepdive/media/images.md +8 -10
- data/docs/deepdive/media/ocr.md +1 -3
- data/docs/deepdive/reference/cost.md +48 -0
- data/docs/deepdive/reference/tracer.md +76 -0
- data/docs/deepdive.md +1 -1
- data/lib/llm/active_record/message.rb +113 -0
- data/lib/llm/active_record.rb +1 -0
- data/lib/llm/agent.rb +44 -19
- data/lib/llm/console/buffer.rb +9 -1
- data/lib/llm/console.rb +6 -1
- data/lib/llm/context/deserializer.rb +10 -3
- data/lib/llm/context.rb +62 -26
- data/lib/llm/guard.rb +2 -8
- data/lib/llm/message.rb +18 -7
- data/lib/llm/provider.rb +74 -16
- data/lib/llm/providers/alibaba.rb +15 -0
- data/lib/llm/providers/anthropic/error_handler.rb +5 -2
- data/lib/llm/providers/anthropic/files.rb +12 -12
- data/lib/llm/providers/anthropic/models.rb +2 -2
- data/lib/llm/providers/anthropic.rb +5 -3
- data/lib/llm/providers/bedrock/error_handler.rb +3 -2
- data/lib/llm/providers/bedrock/models.rb +5 -3
- data/lib/llm/providers/bedrock.rb +5 -3
- data/lib/llm/providers/deepinfra/audio.rb +4 -4
- data/lib/llm/providers/deepinfra/images.rb +4 -4
- data/lib/llm/providers/google/error_handler.rb +5 -2
- data/lib/llm/providers/google/files.rb +10 -10
- data/lib/llm/providers/google/images.rb +2 -2
- data/lib/llm/providers/google/models.rb +2 -2
- data/lib/llm/providers/google.rb +7 -7
- data/lib/llm/providers/mistral.rb +3 -1
- data/lib/llm/providers/ollama/error_handler.rb +5 -2
- data/lib/llm/providers/ollama/models.rb +2 -2
- data/lib/llm/providers/ollama.rb +7 -5
- data/lib/llm/providers/openai/audio.rb +6 -6
- data/lib/llm/providers/openai/error_handler.rb +5 -2
- data/lib/llm/providers/openai/files.rb +10 -10
- data/lib/llm/providers/openai/images.rb +4 -4
- data/lib/llm/providers/openai/models.rb +2 -2
- data/lib/llm/providers/openai/moderations.rb +2 -2
- data/lib/llm/providers/openai/request_adapter.rb +1 -1
- data/lib/llm/providers/openai/responses.rb +8 -8
- data/lib/llm/providers/openai/vector_stores.rb +22 -22
- data/lib/llm/providers/openai.rb +10 -8
- data/lib/llm/providers/xai/images.rb +4 -4
- data/lib/llm/schema.rb +24 -0
- data/lib/llm/tracer/telemetry.rb +4 -4
- data/lib/llm/tracer.rb +11 -3
- data/lib/llm/transport/execution.rb +8 -4
- data/lib/llm/utils.rb +13 -0
- data/lib/llm/version.rb +1 -1
- data/llm.gemspec +2 -2
- metadata +5 -5
- data/lib/llm/guard/loop.rb +0 -89
data/lib/llm/provider.rb
CHANGED
|
@@ -255,6 +255,22 @@ class LLM::Provider
|
|
|
255
255
|
raise NotImplementedError
|
|
256
256
|
end
|
|
257
257
|
|
|
258
|
+
##
|
|
259
|
+
# Returns the number of times a rate-limited
|
|
260
|
+
# request is retried before the error is raised.
|
|
261
|
+
#
|
|
262
|
+
# Each retry sleeps a growing interval, so a budget
|
|
263
|
+
# that is exhausted surfaces the rate-limit error
|
|
264
|
+
# instead of blocking indefinitely. A provider can
|
|
265
|
+
# return a different budget when its API recovers
|
|
266
|
+
# from rate limits more slowly.
|
|
267
|
+
#
|
|
268
|
+
# @see LLM::Agent#retry_budget
|
|
269
|
+
# @return [Integer]
|
|
270
|
+
def retry_budget
|
|
271
|
+
5
|
|
272
|
+
end
|
|
273
|
+
|
|
258
274
|
##
|
|
259
275
|
# Returns an object that can generate a JSON schema
|
|
260
276
|
# @return [LLM::Schema]
|
|
@@ -263,11 +279,21 @@ class LLM::Provider
|
|
|
263
279
|
end
|
|
264
280
|
|
|
265
281
|
##
|
|
266
|
-
# Add one or more headers to all requests
|
|
267
|
-
#
|
|
282
|
+
# Add one or more headers to all requests, or scope them to a block.
|
|
283
|
+
#
|
|
284
|
+
# Without a block the headers are merged into the provider's defaults
|
|
285
|
+
# permanently. With a block the headers apply only to the current fiber,
|
|
286
|
+
# for the duration of the block, then the previous headers are restored.
|
|
287
|
+
# This is useful for a header that varies per context, such as
|
|
288
|
+
# OpenRouter's `x-session-id`.
|
|
289
|
+
# @example Permanent
|
|
268
290
|
# llm = LLM.openai(key: ENV["KEY"])
|
|
269
291
|
# llm.with("OpenAI-Organization" => ENV["ORG"])
|
|
270
292
|
# llm.with("OpenAI-Project" => ENV["PROJECT"])
|
|
293
|
+
# @example Scoped
|
|
294
|
+
# llm.with("x-session-id" => ctx.id) do
|
|
295
|
+
# llm.complete("hello")
|
|
296
|
+
# end
|
|
271
297
|
# @param [Hash<String,String>] headers
|
|
272
298
|
# One or more headers
|
|
273
299
|
# @note
|
|
@@ -275,12 +301,26 @@ class LLM::Provider
|
|
|
275
301
|
# provided via the `headers:` keyword argument,
|
|
276
302
|
# or provided directly as a Hash without the
|
|
277
303
|
# `headers:` key namespace.
|
|
304
|
+
# @yield
|
|
278
305
|
# @return [LLM::Provider]
|
|
279
|
-
# Returns self
|
|
280
|
-
def with(**headers)
|
|
306
|
+
# Returns self without a block, the block's value with one
|
|
307
|
+
def with(**headers, &block)
|
|
281
308
|
headers = headers.merge(headers.delete(:headers) || {})
|
|
282
|
-
|
|
283
|
-
|
|
309
|
+
if block
|
|
310
|
+
wm = weakmaps.header
|
|
311
|
+
previous = wm[self]
|
|
312
|
+
wm[self] = (previous || {}).merge(headers)
|
|
313
|
+
block.call
|
|
314
|
+
else
|
|
315
|
+
lock { tap { @headers.merge!(headers) } }
|
|
316
|
+
end
|
|
317
|
+
ensure
|
|
318
|
+
if block
|
|
319
|
+
if previous.nil?
|
|
320
|
+
wm.respond_to?(:delete) ? wm.delete(self) : wm[self] = nil
|
|
321
|
+
else
|
|
322
|
+
wm[self] = previous
|
|
323
|
+
end
|
|
284
324
|
end
|
|
285
325
|
end
|
|
286
326
|
|
|
@@ -349,7 +389,7 @@ class LLM::Provider
|
|
|
349
389
|
# @return [LLM::Tracer]
|
|
350
390
|
# Returns the current scoped tracer override or provider default tracer
|
|
351
391
|
def tracer
|
|
352
|
-
|
|
392
|
+
weakmaps.tracer[self] || @tracer || LLM::Tracer::Null.new(self)
|
|
353
393
|
end
|
|
354
394
|
|
|
355
395
|
##
|
|
@@ -378,17 +418,18 @@ class LLM::Provider
|
|
|
378
418
|
# @yield
|
|
379
419
|
# @return [Object]
|
|
380
420
|
def with_tracer(tracer)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
421
|
+
wm = weakmaps.tracer
|
|
422
|
+
had_override = wm.key?(self)
|
|
423
|
+
previous = wm[self]
|
|
424
|
+
wm[self] = tracer || LLM::Tracer::Null.new(self)
|
|
384
425
|
yield
|
|
385
426
|
ensure
|
|
386
427
|
if had_override
|
|
387
|
-
|
|
388
|
-
elsif
|
|
389
|
-
|
|
428
|
+
wm[self] = previous
|
|
429
|
+
elsif wm.respond_to?(:delete)
|
|
430
|
+
wm.delete(self)
|
|
390
431
|
else
|
|
391
|
-
|
|
432
|
+
wm[self] = nil
|
|
392
433
|
end
|
|
393
434
|
end
|
|
394
435
|
|
|
@@ -504,8 +545,25 @@ class LLM::Provider
|
|
|
504
545
|
end
|
|
505
546
|
|
|
506
547
|
##
|
|
548
|
+
# Returns the temporary headers set by {#with}
|
|
549
|
+
# for the current fiber.
|
|
507
550
|
# @api private
|
|
508
|
-
|
|
509
|
-
|
|
551
|
+
# @return [Hash]
|
|
552
|
+
def temporary_headers
|
|
553
|
+
weakmaps.header[self] || {}
|
|
554
|
+
end
|
|
555
|
+
|
|
556
|
+
##
|
|
557
|
+
# @return [LLM::Object]
|
|
558
|
+
def weakmaps
|
|
559
|
+
if thread["llm.#{name}.weakmaps"]
|
|
560
|
+
thread["llm.#{name}.weakmaps"]
|
|
561
|
+
else
|
|
562
|
+
weakmaps = LLM::Object.from(
|
|
563
|
+
tracer: ObjectSpace::WeakMap.new,
|
|
564
|
+
header: ObjectSpace::WeakMap.new
|
|
565
|
+
)
|
|
566
|
+
thread["llm.#{name}.weakmaps"] = weakmaps
|
|
567
|
+
end
|
|
510
568
|
end
|
|
511
569
|
end
|
|
@@ -89,5 +89,20 @@ module LLM
|
|
|
89
89
|
def default_model
|
|
90
90
|
"deepseek-v4-flash-0731"
|
|
91
91
|
end
|
|
92
|
+
|
|
93
|
+
##
|
|
94
|
+
# Returns the number of times a rate-limited
|
|
95
|
+
# request is retried.
|
|
96
|
+
#
|
|
97
|
+
# Alibaba (token plan) will frequently issue
|
|
98
|
+
# rate limits or time outs that it recovers from.
|
|
99
|
+
# The higher retry count is to account for scenarios
|
|
100
|
+
# where it takes longer than expected to recover.
|
|
101
|
+
#
|
|
102
|
+
# @see LLM::Provider#retry_budget
|
|
103
|
+
# @return [Integer]
|
|
104
|
+
def retry_budget
|
|
105
|
+
8
|
|
106
|
+
end
|
|
92
107
|
end
|
|
93
108
|
end
|
|
@@ -21,10 +21,13 @@ class LLM::Anthropic
|
|
|
21
21
|
# The span
|
|
22
22
|
# @param [LLM::Transport::Response, Net::HTTPResponse] res
|
|
23
23
|
# The response from the server
|
|
24
|
+
# @param [String] request_id
|
|
25
|
+
# The id of the request that failed
|
|
24
26
|
# @return [LLM::Anthropic::ErrorHandler]
|
|
25
|
-
def initialize(tracer, span, res)
|
|
27
|
+
def initialize(tracer, span, res, request_id)
|
|
26
28
|
@tracer = tracer
|
|
27
29
|
@span = span
|
|
30
|
+
@request_id = request_id
|
|
28
31
|
@res = LLM::Transport::Response.from(res)
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -33,7 +36,7 @@ class LLM::Anthropic
|
|
|
33
36
|
# Raises a subclass of {LLM::Error LLM::Error}
|
|
34
37
|
def raise_error!
|
|
35
38
|
ex = error
|
|
36
|
-
@tracer.on_request_error(ex:, span:)
|
|
39
|
+
@tracer.on_request_error(ex:, span:, request_id: @request_id)
|
|
37
40
|
ensure
|
|
38
41
|
raise(ex) if ex
|
|
39
42
|
end
|
|
@@ -38,9 +38,9 @@ class LLM::Anthropic
|
|
|
38
38
|
def all(**params)
|
|
39
39
|
query = URI.encode_www_form(params)
|
|
40
40
|
req = LLM::Transport::Request.get("/v1/files?#{query}", headers)
|
|
41
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
41
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
42
42
|
res = ResponseAdapter.adapt(res, type: :enumerable)
|
|
43
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
43
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
44
44
|
res
|
|
45
45
|
end
|
|
46
46
|
|
|
@@ -59,9 +59,9 @@ class LLM::Anthropic
|
|
|
59
59
|
req = LLM::Transport::Request.post("/v1/files", headers)
|
|
60
60
|
req["content-type"] = multi.content_type
|
|
61
61
|
transport.set_body_stream(req, multi.body)
|
|
62
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
62
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
63
63
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
64
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
64
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
65
65
|
res
|
|
66
66
|
end
|
|
67
67
|
|
|
@@ -80,9 +80,9 @@ class LLM::Anthropic
|
|
|
80
80
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
81
81
|
query = URI.encode_www_form(params)
|
|
82
82
|
req = LLM::Transport::Request.get("/v1/files/#{file_id}?#{query}", headers)
|
|
83
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
83
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
84
84
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
85
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
85
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
86
86
|
res
|
|
87
87
|
end
|
|
88
88
|
|
|
@@ -101,9 +101,9 @@ class LLM::Anthropic
|
|
|
101
101
|
query = URI.encode_www_form(params)
|
|
102
102
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
103
103
|
req = LLM::Transport::Request.get("/v1/files/#{file_id}?#{query}", headers)
|
|
104
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
104
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
105
105
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
106
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
106
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
107
107
|
res
|
|
108
108
|
end
|
|
109
109
|
alias_method :retrieve_metadata, :get_metadata
|
|
@@ -121,9 +121,9 @@ class LLM::Anthropic
|
|
|
121
121
|
def delete(file:)
|
|
122
122
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
123
123
|
req = LLM::Transport::Request.delete("/v1/files/#{file_id}", headers)
|
|
124
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
124
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
125
125
|
res = LLM::Response.new(res)
|
|
126
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
126
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
127
127
|
res
|
|
128
128
|
end
|
|
129
129
|
|
|
@@ -147,9 +147,9 @@ class LLM::Anthropic
|
|
|
147
147
|
file_id = file.respond_to?(:id) ? file.id : file
|
|
148
148
|
req = LLM::Transport::Request.get("/v1/files/#{file_id}/content?#{query}", headers)
|
|
149
149
|
io = StringIO.new("".b)
|
|
150
|
-
res, span, tracer = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
150
|
+
res, span, tracer, request_id = execute(request: req, operation: "request") { |res| res.read_body { |chunk| io << chunk } }
|
|
151
151
|
res = LLM::Response.new(res).tap { _1.define_singleton_method(:file) { io } }
|
|
152
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
152
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
153
153
|
res
|
|
154
154
|
end
|
|
155
155
|
|
|
@@ -40,9 +40,9 @@ class LLM::Anthropic
|
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params)
|
|
42
42
|
req = LLM::Transport::Request.get("/v1/models?#{query}", headers)
|
|
43
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
43
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
45
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
45
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
46
46
|
res
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -52,10 +52,10 @@ module LLM
|
|
|
52
52
|
def complete(prompt, params = {})
|
|
53
53
|
params, stream, tools, role = normalize_complete_params(params)
|
|
54
54
|
req = build_complete_request(prompt, params, role)
|
|
55
|
-
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
55
|
+
res, span, tracer, request_id = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
56
56
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
57
57
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
58
|
-
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
58
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:, request_id:)
|
|
59
59
|
res
|
|
60
60
|
end
|
|
61
61
|
|
|
@@ -141,7 +141,9 @@ module LLM
|
|
|
141
141
|
|
|
142
142
|
def headers
|
|
143
143
|
lock do
|
|
144
|
-
(@headers || {})
|
|
144
|
+
(@headers || {})
|
|
145
|
+
.merge(temporary_headers)
|
|
146
|
+
.merge(
|
|
145
147
|
"Content-Type" => "application/json",
|
|
146
148
|
"x-api-key" => @key,
|
|
147
149
|
"anthropic-version" => "2023-06-01",
|
|
@@ -23,9 +23,10 @@ class LLM::Bedrock
|
|
|
23
23
|
# @param [Object, nil] span
|
|
24
24
|
# @param [LLM::Transport::Response, Net::HTTPResponse] res
|
|
25
25
|
# @return [LLM::Bedrock::ErrorHandler]
|
|
26
|
-
def initialize(tracer, span, res)
|
|
26
|
+
def initialize(tracer, span, res, request_id)
|
|
27
27
|
@tracer = tracer
|
|
28
28
|
@span = span
|
|
29
|
+
@request_id = request_id
|
|
29
30
|
@res = LLM::Transport::Response.from(res)
|
|
30
31
|
end
|
|
31
32
|
|
|
@@ -33,7 +34,7 @@ class LLM::Bedrock
|
|
|
33
34
|
# @raise [LLM::Error]
|
|
34
35
|
def raise_error!
|
|
35
36
|
ex = error
|
|
36
|
-
@tracer.on_request_error(ex:, span:)
|
|
37
|
+
@tracer.on_request_error(ex:, span:, request_id: @request_id)
|
|
37
38
|
ensure
|
|
38
39
|
raise(ex) if ex
|
|
39
40
|
end
|
|
@@ -40,9 +40,10 @@ class LLM::Bedrock
|
|
|
40
40
|
# @return [LLM::Response]
|
|
41
41
|
def all(**params)
|
|
42
42
|
host = credentials.host
|
|
43
|
+
request_id = SecureRandom.uuid_v7
|
|
43
44
|
req = build_request(host, params)
|
|
44
45
|
res = build_transport(host).request(req, owner: self)
|
|
45
|
-
handle_response(res)
|
|
46
|
+
handle_response(res, request_id)
|
|
46
47
|
end
|
|
47
48
|
|
|
48
49
|
private
|
|
@@ -69,9 +70,10 @@ class LLM::Bedrock
|
|
|
69
70
|
|
|
70
71
|
##
|
|
71
72
|
# @param [LLM::Transport::Response, Net::HTTPResponse] res
|
|
73
|
+
# @param [String] request_id
|
|
72
74
|
# @return [LLM::Response]
|
|
73
75
|
# @raise [LLM::Error]
|
|
74
|
-
def handle_response(res)
|
|
76
|
+
def handle_response(res, request_id)
|
|
75
77
|
res = LLM::Transport::Response.from(res)
|
|
76
78
|
if res.success?
|
|
77
79
|
res.body = LLM::Object.from(LLM.json.load(res.body || "{}"))
|
|
@@ -79,7 +81,7 @@ class LLM::Bedrock
|
|
|
79
81
|
else
|
|
80
82
|
body = +""
|
|
81
83
|
res.read_body { body << _1 } if res.body.nil?
|
|
82
|
-
LLM::Bedrock::ErrorHandler.new(tracer, nil, res).raise_error!
|
|
84
|
+
LLM::Bedrock::ErrorHandler.new(tracer, nil, res, request_id).raise_error!
|
|
83
85
|
end
|
|
84
86
|
end
|
|
85
87
|
|
|
@@ -89,10 +89,10 @@ module LLM
|
|
|
89
89
|
tracer.set_request_metadata(user_input: extract_user_input(messages, fallback: prompt))
|
|
90
90
|
sign!(req, body)
|
|
91
91
|
model_id = model_id_for(req.path)
|
|
92
|
-
res, span, tracer = execute(request: req, stream:, operation: "chat", stream_parser:, model: model_id)
|
|
92
|
+
res, span, tracer, request_id = execute(request: req, stream:, operation: "chat", stream_parser:, model: model_id)
|
|
93
93
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
94
94
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
95
|
-
tracer.on_request_finish(operation: "chat", model: model_id, res:, span:)
|
|
95
|
+
tracer.on_request_finish(operation: "chat", model: model_id, res:, span:, request_id:)
|
|
96
96
|
res
|
|
97
97
|
end
|
|
98
98
|
|
|
@@ -183,7 +183,9 @@ module LLM
|
|
|
183
183
|
|
|
184
184
|
def headers
|
|
185
185
|
lock do
|
|
186
|
-
(@headers || {})
|
|
186
|
+
(@headers || {})
|
|
187
|
+
.merge(temporary_headers)
|
|
188
|
+
.merge("Content-Type" => "application/json")
|
|
187
189
|
end
|
|
188
190
|
end
|
|
189
191
|
|
|
@@ -23,9 +23,9 @@ class LLM::DeepInfra
|
|
|
23
23
|
path = path("/v1/inference/#{model}", base_path: false)
|
|
24
24
|
req = LLM::Transport::Request.post(path, headers)
|
|
25
25
|
req.body = JSON.dump(params.merge(text: input))
|
|
26
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
26
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
27
27
|
res = ResponseAdapter.adapt LLM::Response.new(res), type: :audio
|
|
28
|
-
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
28
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:, request_id:)
|
|
29
29
|
res
|
|
30
30
|
end
|
|
31
31
|
|
|
@@ -45,9 +45,9 @@ class LLM::DeepInfra
|
|
|
45
45
|
req = LLM::Transport::Request.post(path, headers)
|
|
46
46
|
req["content-type"] = multi.content_type
|
|
47
47
|
transport.set_body_stream(req, multi.body)
|
|
48
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
48
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
49
49
|
res = LLM::Response.new(res)
|
|
50
|
-
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
50
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:, request_id:)
|
|
51
51
|
res
|
|
52
52
|
end
|
|
53
53
|
|
|
@@ -44,9 +44,9 @@ class LLM::DeepInfra
|
|
|
44
44
|
req = LLM::Transport::Request.post(path("/images/generations"), headers)
|
|
45
45
|
params = {prompt:, model:, size:, n:, response_format:, quality:, style:}.compact
|
|
46
46
|
req.body = LLM.json.dump(params)
|
|
47
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
47
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
48
48
|
res = LLM::OpenAI::ResponseAdapter.adapt(res, type: :image)
|
|
49
|
-
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
49
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:, request_id:)
|
|
50
50
|
res
|
|
51
51
|
end
|
|
52
52
|
|
|
@@ -75,9 +75,9 @@ class LLM::DeepInfra
|
|
|
75
75
|
req = LLM::Transport::Request.post(path("/images/edits"), headers)
|
|
76
76
|
req["content-type"] = multi.content_type
|
|
77
77
|
transport.set_body_stream(req, multi.body)
|
|
78
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
78
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
79
79
|
res = LLM::OpenAI::ResponseAdapter.adapt(res, type: :image)
|
|
80
|
-
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
80
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:, request_id:)
|
|
81
81
|
res
|
|
82
82
|
end
|
|
83
83
|
|
|
@@ -21,10 +21,13 @@ class LLM::Google
|
|
|
21
21
|
# The span
|
|
22
22
|
# @param [LLM::Transport::Response, Net::HTTPResponse] res
|
|
23
23
|
# The response from the server
|
|
24
|
+
# @param [String] request_id
|
|
25
|
+
# The id of the request that failed
|
|
24
26
|
# @return [LLM::Google::ErrorHandler]
|
|
25
|
-
def initialize(tracer, span, res)
|
|
27
|
+
def initialize(tracer, span, res, request_id)
|
|
26
28
|
@tracer = tracer
|
|
27
29
|
@span = span
|
|
30
|
+
@request_id = request_id
|
|
28
31
|
@res = LLM::Transport::Response.from(res)
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -33,7 +36,7 @@ class LLM::Google
|
|
|
33
36
|
# Raises a subclass of {LLM::Error LLM::Error}
|
|
34
37
|
def raise_error!
|
|
35
38
|
ex = error
|
|
36
|
-
@tracer.on_request_error(ex:, span:)
|
|
39
|
+
@tracer.on_request_error(ex:, span:, request_id: @request_id)
|
|
37
40
|
ensure
|
|
38
41
|
raise(ex) if ex
|
|
39
42
|
end
|
|
@@ -46,9 +46,9 @@ class LLM::Google
|
|
|
46
46
|
def all(**params)
|
|
47
47
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
48
48
|
req = LLM::Transport::Request.get("/v1beta/files?#{query}", headers)
|
|
49
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
49
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
50
50
|
res = ResponseAdapter.adapt(res, type: :files)
|
|
51
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
51
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
52
52
|
res
|
|
53
53
|
end
|
|
54
54
|
|
|
@@ -70,9 +70,9 @@ class LLM::Google
|
|
|
70
70
|
req["X-Goog-Upload-Command"] = "upload, finalize"
|
|
71
71
|
file.with_io do |io|
|
|
72
72
|
transport.set_body_stream(req, io)
|
|
73
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
73
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
74
74
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
75
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
75
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
76
76
|
res
|
|
77
77
|
end
|
|
78
78
|
end
|
|
@@ -92,9 +92,9 @@ class LLM::Google
|
|
|
92
92
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
93
93
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
94
94
|
req = LLM::Transport::Request.get("/v1beta/#{file_id}?#{query}", headers)
|
|
95
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
95
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
96
96
|
res = ResponseAdapter.adapt(res, type: :file)
|
|
97
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
97
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
98
98
|
res
|
|
99
99
|
end
|
|
100
100
|
|
|
@@ -112,9 +112,9 @@ class LLM::Google
|
|
|
112
112
|
file_id = file.respond_to?(:name) ? file.name : file.to_s
|
|
113
113
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
114
114
|
req = LLM::Transport::Request.delete("/v1beta/#{file_id}?#{query}", headers)
|
|
115
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
115
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
116
116
|
res = LLM::Response.new(res)
|
|
117
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
117
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
118
118
|
res
|
|
119
119
|
end
|
|
120
120
|
|
|
@@ -134,9 +134,9 @@ class LLM::Google
|
|
|
134
134
|
req["X-Goog-Upload-Header-Content-Length"] = file.bytesize
|
|
135
135
|
req["X-Goog-Upload-Header-Content-Type"] = file.mime_type
|
|
136
136
|
req.body = LLM.json.dump({file: {display_name: File.basename(file.path)}})
|
|
137
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
137
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
138
138
|
trace_res = LLM::Response.new(res)
|
|
139
|
-
tracer.on_request_finish(operation: "request", res: trace_res, span:)
|
|
139
|
+
tracer.on_request_finish(operation: "request", res: trace_res, span:, request_id:)
|
|
140
140
|
res["x-goog-upload-url"]
|
|
141
141
|
end
|
|
142
142
|
|
|
@@ -51,9 +51,9 @@ class LLM::Google
|
|
|
51
51
|
instances: [{prompt:}]
|
|
52
52
|
})
|
|
53
53
|
req.body = body
|
|
54
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
54
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
55
55
|
res = ResponseAdapter.adapt(res, type: :image)
|
|
56
|
-
tracer.on_request_finish(operation: "request", model:, res:, span:)
|
|
56
|
+
tracer.on_request_finish(operation: "request", model:, res:, span:, request_id:)
|
|
57
57
|
res
|
|
58
58
|
end
|
|
59
59
|
|
|
@@ -40,9 +40,9 @@ class LLM::Google
|
|
|
40
40
|
def all(**params)
|
|
41
41
|
query = URI.encode_www_form(params.merge!(key: key))
|
|
42
42
|
req = LLM::Transport::Request.get("/v1beta/models?#{query}", headers)
|
|
43
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
43
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
44
44
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
45
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
45
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
46
46
|
res
|
|
47
47
|
end
|
|
48
48
|
|
data/lib/llm/providers/google.rb
CHANGED
|
@@ -58,9 +58,9 @@ module LLM
|
|
|
58
58
|
path = ["/v1beta/models/#{model}", "embedContent?key=#{@key}"].join(":")
|
|
59
59
|
req = LLM::Transport::Request.post(path, headers)
|
|
60
60
|
req.body = LLM.json.dump({content: {parts: [{text: input}]}})
|
|
61
|
-
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
61
|
+
res, span, tracer, request_id = execute(request: req, operation: "embeddings", model:)
|
|
62
62
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
63
|
-
tracer.on_request_finish(operation: "embeddings", model:, res:, span:)
|
|
63
|
+
tracer.on_request_finish(operation: "embeddings", model:, res:, span:, request_id:)
|
|
64
64
|
res
|
|
65
65
|
end
|
|
66
66
|
|
|
@@ -77,10 +77,10 @@ module LLM
|
|
|
77
77
|
def complete(prompt, params = {})
|
|
78
78
|
params, stream, tools, role, model = normalize_complete_params(params)
|
|
79
79
|
req = build_complete_request(prompt, params, role, model, stream)
|
|
80
|
-
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model:)
|
|
80
|
+
res, span, tracer, request_id = execute(request: req, stream: stream, operation: "chat", model:)
|
|
81
81
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
82
82
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
83
|
-
tracer.on_request_finish(operation: "chat", model:, res:, span:)
|
|
83
|
+
tracer.on_request_finish(operation: "chat", model:, res:, span:, request_id:)
|
|
84
84
|
res
|
|
85
85
|
end
|
|
86
86
|
|
|
@@ -186,9 +186,9 @@ module LLM
|
|
|
186
186
|
|
|
187
187
|
def headers
|
|
188
188
|
lock do
|
|
189
|
-
(@headers || {})
|
|
190
|
-
|
|
191
|
-
)
|
|
189
|
+
(@headers || {})
|
|
190
|
+
.merge(temporary_headers)
|
|
191
|
+
.merge("Content-Type" => "application/json")
|
|
192
192
|
end
|
|
193
193
|
end
|
|
194
194
|
|
|
@@ -21,10 +21,13 @@ class LLM::Ollama
|
|
|
21
21
|
# The span
|
|
22
22
|
# @param [LLM::Transport::Response, Net::HTTPResponse] res
|
|
23
23
|
# The response from the server
|
|
24
|
+
# @param [String] request_id
|
|
25
|
+
# The id of the request that failed
|
|
24
26
|
# @return [LLM::Ollama::ErrorHandler]
|
|
25
|
-
def initialize(tracer, span, res)
|
|
27
|
+
def initialize(tracer, span, res, request_id)
|
|
26
28
|
@tracer = tracer
|
|
27
29
|
@span = span
|
|
30
|
+
@request_id = request_id
|
|
28
31
|
@res = LLM::Transport::Response.from(res)
|
|
29
32
|
end
|
|
30
33
|
|
|
@@ -33,7 +36,7 @@ class LLM::Ollama
|
|
|
33
36
|
# Raises a subclass of {LLM::Error LLM::Error}
|
|
34
37
|
def raise_error!
|
|
35
38
|
ex = error
|
|
36
|
-
@tracer.on_request_error(ex:, span:)
|
|
39
|
+
@tracer.on_request_error(ex:, span:, request_id: @request_id)
|
|
37
40
|
ensure
|
|
38
41
|
raise(ex) if ex
|
|
39
42
|
end
|
|
@@ -41,9 +41,9 @@ class LLM::Ollama
|
|
|
41
41
|
def all(**params)
|
|
42
42
|
query = URI.encode_www_form(params)
|
|
43
43
|
req = LLM::Transport::Request.get("/api/tags?#{query}", headers)
|
|
44
|
-
res, span, tracer = execute(request: req, operation: "request")
|
|
44
|
+
res, span, tracer, request_id = execute(request: req, operation: "request")
|
|
45
45
|
res = ResponseAdapter.adapt(res, type: :models)
|
|
46
|
-
tracer.on_request_finish(operation: "request", res:, span:)
|
|
46
|
+
tracer.on_request_finish(operation: "request", res:, span:, request_id:)
|
|
47
47
|
res
|
|
48
48
|
end
|
|
49
49
|
|
data/lib/llm/providers/ollama.rb
CHANGED
|
@@ -50,9 +50,9 @@ module LLM
|
|
|
50
50
|
params = {model:}.merge!(params)
|
|
51
51
|
req = LLM::Transport::Request.post("/v1/embeddings", headers)
|
|
52
52
|
req.body = LLM.json.dump({input:}.merge!(params))
|
|
53
|
-
res, span, tracer = execute(request: req, operation: "embeddings", model:)
|
|
53
|
+
res, span, tracer, request_id = execute(request: req, operation: "embeddings", model:)
|
|
54
54
|
res = ResponseAdapter.adapt(res, type: :embedding)
|
|
55
|
-
tracer.on_request_finish(operation: "embeddings", model:, res:, span:)
|
|
55
|
+
tracer.on_request_finish(operation: "embeddings", model:, res:, span:, request_id:)
|
|
56
56
|
res
|
|
57
57
|
end
|
|
58
58
|
|
|
@@ -69,10 +69,10 @@ module LLM
|
|
|
69
69
|
def complete(prompt, params = {})
|
|
70
70
|
params, stream, tools, role = normalize_complete_params(params)
|
|
71
71
|
req = build_complete_request(prompt, params, role)
|
|
72
|
-
res, span, tracer = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
72
|
+
res, span, tracer, request_id = execute(request: req, stream: stream, operation: "chat", model: params[:model])
|
|
73
73
|
res = ResponseAdapter.adapt(res, type: :completion)
|
|
74
74
|
.extend(Module.new { define_method(:__tools__) { tools } })
|
|
75
|
-
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:)
|
|
75
|
+
tracer.on_request_finish(operation: "chat", model: params[:model], res:, span:, request_id:)
|
|
76
76
|
res
|
|
77
77
|
end
|
|
78
78
|
|
|
@@ -113,7 +113,9 @@ module LLM
|
|
|
113
113
|
|
|
114
114
|
def headers
|
|
115
115
|
lock do
|
|
116
|
-
(@headers || {})
|
|
116
|
+
(@headers || {})
|
|
117
|
+
.merge(temporary_headers)
|
|
118
|
+
.merge(
|
|
117
119
|
"Content-Type" => "application/json",
|
|
118
120
|
"Authorization" => "Bearer #{@key}"
|
|
119
121
|
)
|