llm.rb 16.0.0 → 17.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +535 -2
  3. data/README.md +857 -501
  4. data/data/alibaba.json +2 -2
  5. data/data/anthropic.json +66 -3
  6. data/data/bedrock.json +781 -12
  7. data/data/deepinfra.json +157 -1
  8. data/data/google.json +48 -0
  9. data/data/mistral.json +326 -16
  10. data/data/moonshot.json +2 -1
  11. data/data/openai.json +13 -0
  12. data/data/openrouter.json +1078 -1235
  13. data/data/xai.json +4 -2
  14. data/docs/deepdive/advanced/cancellation.md +35 -7
  15. data/docs/deepdive/advanced/compaction.md +5 -5
  16. data/docs/deepdive/advanced/context.md +45 -24
  17. data/docs/deepdive/advanced/{guard.md → intercept.md} +87 -83
  18. data/docs/deepdive/features/builtin_tools.md +1 -1
  19. data/docs/deepdive/features/concurrency.md +15 -10
  20. data/docs/deepdive/features/database.md +19 -19
  21. data/docs/deepdive/features/embeddings.md +4 -4
  22. data/docs/deepdive/features/files.md +7 -5
  23. data/docs/deepdive/fundamentals/agents.md +13 -8
  24. data/docs/deepdive/fundamentals/providers.md +1 -1
  25. data/docs/deepdive/fundamentals/schema.md +2 -2
  26. data/docs/deepdive/fundamentals/stream.md +8 -8
  27. data/docs/deepdive/fundamentals/tools.md +14 -14
  28. data/docs/deepdive/media/ocr.md +1 -1
  29. data/docs/deepdive/protocols/a2a.md +3 -3
  30. data/docs/deepdive/protocols/mcp.md +116 -7
  31. data/docs/deepdive/reference/cost.md +1 -1
  32. data/docs/deepdive/reference/response.md +1 -1
  33. data/docs/deepdive/reference/tracer.md +90 -22
  34. data/docs/deepdive.md +1 -1
  35. data/lib/llm/agent/interrupt.rb +35 -46
  36. data/lib/llm/agent/registry.rb +106 -0
  37. data/lib/llm/agent.rb +242 -268
  38. data/lib/llm/context.rb +63 -55
  39. data/lib/llm/function/array.rb +2 -1
  40. data/lib/llm/function/async/group.rb +2 -2
  41. data/lib/llm/function/async/task.rb +25 -48
  42. data/lib/llm/function/fiber/group.rb +2 -2
  43. data/lib/llm/function/fiber/task.rb +16 -30
  44. data/lib/llm/function/fork/group.rb +2 -2
  45. data/lib/llm/function/fork/job.rb +8 -4
  46. data/lib/llm/function/fork/task.rb +25 -30
  47. data/lib/llm/function/group.rb +33 -5
  48. data/lib/llm/function/ractor/group.rb +2 -2
  49. data/lib/llm/function/ractor/job.rb +8 -6
  50. data/lib/llm/function/ractor/task.rb +32 -9
  51. data/lib/llm/function/sequential/group.rb +2 -1
  52. data/lib/llm/function/sequential/task.rb +8 -1
  53. data/lib/llm/function/task.rb +52 -4
  54. data/lib/llm/function/thread/group.rb +2 -2
  55. data/lib/llm/function/thread/task.rb +36 -10
  56. data/lib/llm/function/window.rb +16 -0
  57. data/lib/llm/function.rb +14 -43
  58. data/lib/llm/intercept/multi.rb +40 -0
  59. data/lib/llm/intercept/null.rb +18 -0
  60. data/lib/llm/intercept.rb +96 -0
  61. data/lib/llm/message.rb +1 -1
  62. data/lib/llm/provider.rb +16 -1
  63. data/lib/llm/providers/anthropic/stream_parser.rb +1 -1
  64. data/lib/llm/providers/bedrock/stream_parser.rb +1 -1
  65. data/lib/llm/providers/deepseek/files.rb +49 -0
  66. data/lib/llm/providers/deepseek/request_adapter/completion.rb +15 -13
  67. data/lib/llm/providers/deepseek.rb +16 -2
  68. data/lib/llm/providers/google/stream_parser.rb +1 -1
  69. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -1
  70. data/lib/llm/providers/openai/stream_parser.rb +1 -1
  71. data/lib/llm/stream.rb +8 -7
  72. data/lib/llm/tool.rb +1 -1
  73. data/lib/llm/tools/utils.rb +1 -1
  74. data/lib/llm/tracer/logger.rb +42 -1
  75. data/lib/llm/tracer/multi.rb +144 -0
  76. data/lib/llm/tracer/null.rb +20 -0
  77. data/lib/llm/tracer/pretty_logger.rb +33 -2
  78. data/lib/llm/tracer/rescue.rb +110 -0
  79. data/lib/llm/tracer/telemetry.rb +69 -4
  80. data/lib/llm/tracer.rb +125 -48
  81. data/lib/llm/utils.rb +12 -2
  82. data/lib/llm/version.rb +1 -1
  83. data/lib/llm.rb +36 -1
  84. data/llm.gemspec +9 -4
  85. metadata +16 -35
  86. data/lib/llm/function/tracing.rb +0 -21
  87. data/lib/llm/guard/null.rb +0 -19
  88. data/lib/llm/guard.rb +0 -55
@@ -139,7 +139,7 @@ module LLM
139
139
  ##
140
140
  # @param (see LLM::Tracer#on_tool_finish)
141
141
  # @return (see LLM::Tracer#on_tool_finish)
142
- def on_tool_finish(result:, span:)
142
+ def on_tool_finish(result:, span:, started_at:, finished_at:)
143
143
  return nil unless span
144
144
  attributes = {
145
145
  "gen_ai.tool.call.id" => result.id,
@@ -154,15 +154,69 @@ module LLM
154
154
  ##
155
155
  # @param (see LLM::Tracer#on_tool_error)
156
156
  # @return (see LLM::Tracer#on_tool_error)
157
- def on_tool_error(ex:, span:)
157
+ def on_tool_error(ex:, span:, started_at:, finished_at:)
158
158
  return nil unless span
159
- attributes = {"error.type" => ex.class.to_s}.compact
159
+ attributes = {"error.type" => ex.value[:type]}.compact
160
160
  attributes.each { span.set_attribute(_1, _2) }
161
161
  span.add_event("gen_ai.tool.finish")
162
- span.status = ::OpenTelemetry::Trace::Status.error(ex.message)
162
+ span.status = ::OpenTelemetry::Trace::Status.error(ex.value[:message].to_s)
163
+ span.tap(&:finish)
164
+ end
165
+
166
+ ##
167
+ # @param (see LLM::Tracer#on_tool_interrupt)
168
+ # @return (see LLM::Tracer#on_tool_interrupt)
169
+ def on_tool_interrupt(ex:, span:, started_at:, finished_at:)
170
+ return nil unless span
171
+ attributes = {"error.type" => "LLM::Interrupt"}
172
+ attributes.each { span.set_attribute(_1, _2) }
173
+ span.status = ::OpenTelemetry::Trace::Status.error("tool interrupted")
174
+ span.add_event("gen_ai.tool.finish")
163
175
  span.tap(&:finish)
164
176
  end
165
177
 
178
+ ##
179
+ # @param [Symbol] scope
180
+ # @param [Object] span
181
+ # @param [String] request_id
182
+ # @return [void]
183
+ def on_interrupt(scope:, span: nil, request_id: nil)
184
+ case scope
185
+ when :request
186
+ ##
187
+ # This is where we match on_request_start and
188
+ # close the span it had opened. It is the only
189
+ # scope where we have a span to close.
190
+ attributes = {"error.type" => "LLM::Interrupt"}
191
+ attributes.each { span.set_attribute(_1, _2) }
192
+ span.status = OpenTelemetry::Trace::Status.error("request interrupted")
193
+ span.add_event("gen_ai.request.finish")
194
+ span.tap(&:finish)
195
+ when :tool
196
+ ##
197
+ # This means an interrupt was received when
198
+ # an agent was running tools and no request
199
+ # was active at the time.
200
+ #
201
+ # There is no span to close, and each tool
202
+ # call is closed by #{on_tool_interrupt}.
203
+ # There is seemingly nothing useful to do
204
+ # here.
205
+ when :agent
206
+ ##
207
+ # This means an interrupt was received when
208
+ # an agent was between requests and no request
209
+ # was active at the time.
210
+ #
211
+ # There is no span to close, and no tools are
212
+ # running. There is seemingly nothing useful to
213
+ # do here.
214
+ else
215
+ raise LLM::Error,
216
+ "scope '#{scope}' is not a valid tracer scope"
217
+ end
218
+ end
219
+
166
220
  ##
167
221
  # @note
168
222
  # This method returns an empty array for exporters that
@@ -188,6 +242,17 @@ module LLM
188
242
  nil
189
243
  end
190
244
 
245
+ ##
246
+ # This method is called when the outermost
247
+ # {LLM::Provider#with_tracer} method has returned
248
+ # control to the caller. It will flush the queue
249
+ # of any spans that are not yet exported, and
250
+ # it is called after a trace has finished.
251
+ # @return [nil]
252
+ def on_exit
253
+ flush!
254
+ end
255
+
191
256
  private
192
257
 
193
258
  ##
data/lib/llm/tracer.rb CHANGED
@@ -9,11 +9,22 @@ module LLM
9
9
  # {LLM::Tracer::Logger LLM::Tracer::Logger} for example
10
10
  # tracer implementations.
11
11
  class Tracer
12
+ require_relative "tracer/registry"
13
+ require_relative "tracer/rescue"
14
+
15
+ ##
16
+ # @param [Class] klass
17
+ # A subclalss of {LLM::Tracer}
18
+ # @return [void]
19
+ def self.inherited(klass)
20
+ klass.prepend Rescue
21
+ end
22
+
12
23
  require_relative "tracer/logger"
13
24
  require_relative "tracer/telemetry"
14
25
  require_relative "tracer/null"
15
26
  require_relative "tracer/pretty_logger"
16
- require_relative "tracer/registry"
27
+ require_relative "tracer/multi"
17
28
 
18
29
  ##
19
30
  # Returns the registry that counts the open scopes for each tracer.
@@ -57,6 +68,7 @@ module LLM
57
68
  def self.pretty_logger(llm, **options)
58
69
  PrettyLogger.new(llm, **options)
59
70
  end
71
+ (class << self; self; end).alias_method(:pretty, :pretty_logger)
60
72
 
61
73
  ##
62
74
  # Builds a {LLM::Tracer::Logger} for a provider.
@@ -80,6 +92,24 @@ module LLM
80
92
  Telemetry.new(llm, **options)
81
93
  end
82
94
 
95
+ ##
96
+ # Builds a {LLM::Tracer::Multi}
97
+ # @param [LLM::Provider] llm
98
+ # @param [Array<LLM::Tracer>] tracers
99
+ # One or more tracers
100
+ # @return [LLM::Tracer::Multi]
101
+ def self.multi(llm, *tracers)
102
+ Multi.new(
103
+ *tracers.map do |tracer|
104
+ if Class === tracer
105
+ tracer.new(llm)
106
+ else
107
+ tracer
108
+ end
109
+ end
110
+ )
111
+ end
112
+
83
113
  ##
84
114
  # @return [LLM::Provider]
85
115
  attr_reader :llm
@@ -95,26 +125,25 @@ module LLM
95
125
  end
96
126
 
97
127
  ##
98
- # Called when the tracer is no longer in use.
128
+ # This method is called when a tracer exits a scope that
129
+ # has been opened by {LLM::Provider#with_tracer}.
99
130
  #
100
- # A tracer that holds something - a file, a span, a connection pool -
101
- # releases it here. The default does nothing, because most tracers hold
102
- # nothing that needs releasing.
131
+ # It is unlikely you will ever use the {LLM::Provider#with_tracer}
132
+ # method directly but {LLM::Agent} uses it. An agent can have its
133
+ # own tracer, and it is scoped via {LLM::Provider#with_tracer}. When
134
+ # the tracer is done and {LLM::Provider#with_tracer} returns, this
135
+ # method (on_exit) is called.
103
136
  #
104
- # It is called once, when the last scope that is open for this tracer
105
- # ends, and that scope can have been opened by another thread: a tool
106
- # runs on a thread of its own and scopes the turn's tracer while it
107
- # does. It is called after the scoped lookup is restored, so a tracer
108
- # that reads {LLM::Provider#tracer} inside it sees the tracer that the
109
- # next request will see.
137
+ # A tracer that holds something - a file, a span, a connection pool -
138
+ # can use this method to release it. The {LLM::Tracer::Telemetry}
139
+ # tracer uses this method to flush its queue of spans for export,
140
+ # and after the tracer has exited is a good time to do that.
110
141
  #
111
- # A tracer can be scoped again afterwards, on a later turn, so it has
112
- # to stay usable after this call, and it has to tolerate the call
113
- # happening more than once over its life.
114
142
  # @see LLM::Tracer::Registry
115
143
  # @see LLM::Provider#with_tracer
116
144
  # @return [void]
117
145
  def on_exit
146
+ raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
118
147
  end
119
148
 
120
149
  ##
@@ -137,10 +166,12 @@ module LLM
137
166
  # @param [LLM::Response] res
138
167
  # @param [Object, nil] span
139
168
  # @param [String] model
140
- # @param [Hash, nil] outputs Optional span attributes (e.g. gen_ai.output.messages) from llm.rb or caller.
141
- # @param [Hash, nil] metadata Optional metadata from llm.rb or caller.
169
+ # @param [Hash, nil] outputs
170
+ # Optional span attributes (e.g. gen_ai.output.messages) from llm.rb or caller.
171
+ # @param [Hash, nil] metadata
172
+ # Optional metadata from llm.rb or caller.
142
173
  # @param [String] request_id
143
- # The id of the request, as passed to {#on_request_start}
174
+ # The id of the request that can be matched back to {#on_request_start}
144
175
  # @return [void]
145
176
  def on_request_finish(operation:, res:, model: nil, span: nil, outputs: nil, metadata: nil, request_id:)
146
177
  raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
@@ -173,59 +204,105 @@ module LLM
173
204
  end
174
205
 
175
206
  ##
176
- # Called after a local tool/function succeeds.
177
- # @param [LLM::Function::Return] result
178
- # The tool return object.
207
+ # Called when a tool is interrupted.
208
+ #
209
+ # This method is called once per tool - in a batch -
210
+ # and only after all tools have been spawned in parallel
211
+ # and then waited on sequentially. The `*_at` parameters
212
+ # let you know when a tool has started and finished (as a
213
+ # monotonic clock) since it cannot be inferred from the
214
+ # time the method is called.
215
+ #
216
+ # @param [LLM::Interrupt] ex
217
+ # An instance of {LLM::Interrupt LLM::Interrupt}
179
218
  # @param [Object, nil] span
180
- # The span/context object returned by {#on_tool_start}.
219
+ # The span/context object returned by {#on_tool_start}
220
+ # @param [Float] started_at
221
+ # When the call opened
222
+ # @param [Float] finished_at
223
+ # When the call ended
181
224
  # @return [void]
182
- def on_tool_finish(result:, span:)
225
+ def on_tool_interrupt(ex:, span:, started_at:, finished_at:)
226
+ raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
227
+ end
228
+
229
+ ##
230
+ # Called after a tool/function succeeds.
231
+ #
232
+ # This method is called once per tool - in a batch -
233
+ # and only after all tools have been spawned in parallel
234
+ # and then waited on sequentially. The `*_at` parameters
235
+ # let you know when a tool has started and finished (as a
236
+ # monotonic clock) since it cannot be inferred from the
237
+ # time the method is called.
238
+ #
239
+ # @param [LLM::Function::Return] result
240
+ # An {LLM::Function::Return}
241
+ # @param [Object] span
242
+ # The span/context object returned by {#on_tool_start}
243
+ # @param [Float] started_at
244
+ # When the call opened
245
+ # @param [Float] finished_at
246
+ # When the call ended
247
+ # @return [void]
248
+ def on_tool_finish(result:, span:, started_at:, finished_at:)
183
249
  raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
184
250
  end
185
251
 
186
252
  ##
187
253
  # Called when a local tool/function raises.
188
- # @param [Exception] ex
189
- # The raised error.
254
+ #
255
+ # This method is called once per tool - in a batch -
256
+ # and only after all tools have been spawned in parallel
257
+ # and then waited on sequentially. The `*_at` parameters
258
+ # let you know when a tool has started and finished (as a
259
+ # monotonic clock) since it cannot be inferred from the
260
+ # time the method is called.
261
+ #
262
+ # @note
263
+ # This method provides the callback with
264
+ # an {LLM::Function::Return} object that
265
+ # encapsulates the exception a tool failed
266
+ # with. It does _not_ provide an Exception
267
+ # object.
268
+ #
269
+ # @param [LLM::Function::Return] ex
270
+ # An {LLM::Function::Return} that holds the error
190
271
  # @param [Object, nil] span
191
272
  # The span/context object returned by {#on_tool_start}.
273
+ # @param [Float] started_at
274
+ # When the call opened
275
+ # @param [Float] finished_at
276
+ # When the call ended
192
277
  # @return [void]
193
- def on_tool_error(ex:, span:)
278
+ def on_tool_error(ex:, span:, started_at:, finished_at:)
194
279
  raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
195
280
  end
196
281
 
197
282
  ##
198
- # Called when a request, a tool, or a turn is interrupted.
283
+ # This method is called when an agent receives
284
+ # an interrupt. An interrupt can occur in different
285
+ # scopes: `:request`, `:agent`, or `:tool`.
199
286
  #
200
- # An interrupt is not a failure, so this is not {#on_request_error} or
201
- # {#on_tool_error} under another name: it is the third ending either
202
- # scope can have, and it is called before {LLM::Interrupt} reaches the
203
- # caller, so a tracer that has to record what happened to a turn does it
204
- # while the work is still the work in flight.
287
+ # The `:request` scope provides a span and request ID,
288
+ # and it can be used to close a request that was
289
+ # interrupted. The other scopes (`:tool`, `:agent`)
290
+ # occur in a context where there is no span or
291
+ # request ID available.
205
292
  #
206
- # A request is announced once, by the transport. A tool is announced
207
- # once for the phase rather than once per tool: a cancel reaches every
208
- # tool that is running and the caller hears one exception, so there is
209
- # no one tool the announcement belongs to. A turn is announced as
210
- # `:agent` when the interrupt lands between two of its requests, which
211
- # is the one point of a turn that has no span of its own.
293
+ # @note
294
+ # This is the last and final callback that is received
295
+ # as part of the interrupt process.
212
296
  #
213
- # The default does nothing, which is the difference from the rest of the
214
- # lifecycle. Every other hook raises, because a tracer that means to draw
215
- # a request has to answer for it - but an interrupt is delivered to
216
- # whatever tracer happens to be bound, and a hook that raised here would
217
- # replace the interrupt every caller is written against.
218
297
  # @param [Symbol] scope
219
- # :request for a request, :tool for a tool, :agent for a turn that is
220
- # between its requests
298
+ # :request, :agent, or :tool
221
299
  # @param [Object, nil] span
222
- # The span that {#on_request_start} returned. A tool pass has none:
223
- # a tool's span belongs to the call, and the announcement is not one
224
- # tool's to make.
300
+ # The span that {#on_request_start} returned
225
301
  # @param [String, nil] request_id
226
- # The id, as passed to {#on_request_start}, when the scope is a request
302
+ # The id, as passed to {#on_request_start}
227
303
  # @return [void]
228
304
  def on_interrupt(scope:, span: nil, request_id: nil)
305
+ raise NotImplementedError, "#{self.class} does not implement '#{__method__}'"
229
306
  end
230
307
 
231
308
  ##
data/lib/llm/utils.rb CHANGED
@@ -48,16 +48,26 @@ module LLM
48
48
  (defined?(::Sequel::Model) and ::Sequel::Model === obj)
49
49
  end
50
50
 
51
+ ##
52
+ # Returns true when a value is a UUIDv7 string: 32 hexadecimal
53
+ # digits, with or without hyphens, whose version nibble is a 7.
54
+ # @param [Object] id
55
+ # @return [Boolean]
56
+ def uuidv7?(id)
57
+ hex = id.to_s.delete("-")
58
+ hex.match?(/\A\h{32}\z/) && hex[12] == "7"
59
+ end
60
+
51
61
  ##
52
62
  # Returns the UTC time encoded in a UUIDv7, or nil when the
53
63
  # given value is not a UUIDv7. The first 48 bits of a UUIDv7
54
64
  # are a Unix millisecond timestamp.
65
+ # @see #uuidv7?
55
66
  # @param [Object] id
56
67
  # @return [Time, nil]
57
68
  def timestamp(id)
69
+ return nil unless uuidv7?(id)
58
70
  hex = id.to_s.delete("-")
59
- return nil unless hex.match?(/\A\h{32}\z/)
60
- return nil unless hex[12] == "7"
61
71
  Time.at(hex[0, 12].to_i(16) / 1000.0).utc
62
72
  end
63
73
 
data/lib/llm/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LLM
4
- VERSION = "16.0.0"
4
+ VERSION = "17.0.1"
5
5
  end
data/lib/llm.rb CHANGED
@@ -44,7 +44,7 @@ module LLM
44
44
  require_relative "llm/stream"
45
45
  require_relative "llm/provider"
46
46
  require_relative "llm/context"
47
- require_relative "llm/guard"
47
+ require_relative "llm/intercept"
48
48
  require_relative "llm/agent"
49
49
  require_relative "llm/buffer"
50
50
  require_relative "llm/function"
@@ -362,6 +362,41 @@ module LLM
362
362
  LLM::Function.new(key, &b)
363
363
  end
364
364
 
365
+ ##
366
+ # Interrupts the turn running under this identity, in this process.
367
+ #
368
+ # Named by the agent or by the id, and not by both: a canceller has one of
369
+ # the two in hand, never the pair. `false` means nothing was registered
370
+ # under it - the ordinary race, a turn that finished first, and not a
371
+ # failure.
372
+ #
373
+ # It reaches the agents *this process* is running. A cancel that lands in
374
+ # another worker finds nothing here and must not be told otherwise; an
375
+ # application that needs a guarantee keeps one of its own, and what this is
376
+ # for is the fast path.
377
+ # @see LLM::Agent::Registry
378
+ # @param [LLM::Agent, nil] agent
379
+ # @param [String, Integer, nil] id
380
+ # @param [Integer] attempts
381
+ # @param [Numeric] interval
382
+ # @return [Boolean]
383
+ # Whether anything was reached
384
+ def interrupt(agent: nil, id: nil, attempts: 1, interval: 0.1)
385
+ result = false
386
+ attempts.times do |count|
387
+ record = LLM::Agent.registry.find(agent:, id:)
388
+ if record
389
+ record.interrupt!
390
+ result = true
391
+ break
392
+ else
393
+ break if (count+1) == attempts
394
+ sleep(interval)
395
+ end
396
+ end
397
+ result
398
+ end
399
+
365
400
  ##
366
401
  # Provides a thread-safe lock
367
402
  # @param [Symbol] name The name of the lock
data/llm.gemspec CHANGED
@@ -5,7 +5,14 @@ require_relative "lib/llm/version"
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "llm.rb"
7
7
  spec.version = LLM::VERSION
8
- spec.authors = ["Robert Gleeson", "Antar Azri", "Rodrigo Serrano"]
8
+ spec.authors = ["Robert Gleeson",
9
+ "Antar Azri",
10
+ "Rodrigo Serrano",
11
+ "Azmi Muwahid",
12
+ "George Pickett",
13
+ "Mika Mikasuki",
14
+ "Nikulin Nikita",
15
+ "pttydou"]
9
16
  spec.email = ["robert@r.uby.dev"]
10
17
 
11
18
  spec.summary = "An agentic runtime for Ruby"
@@ -40,7 +47,7 @@ DESCRIPTION
40
47
  spec.add_development_dependency "yard", "~> 0.9.37"
41
48
  spec.add_development_dependency "redcarpet", "~> 3.6"
42
49
  spec.add_development_dependency "webrick", "~> 1.8"
43
- spec.add_development_dependency "test-cmd.rb", "~> 2.7.1"
50
+ spec.add_development_dependency "test-cmd.rb", "~> 2.7.3"
44
51
  spec.add_development_dependency "rake", "~> 13.0"
45
52
  spec.add_development_dependency "rspec", "~> 3.0"
46
53
  spec.add_development_dependency "standard", "= 1.54.0"
@@ -56,8 +63,6 @@ DESCRIPTION
56
63
  spec.add_development_dependency "xchan.rb", "~> 0.24"
57
64
  spec.add_development_dependency "pg", "~> 1.5"
58
65
  spec.add_development_dependency "irb", "~> 1.18"
59
- spec.add_development_dependency "curb", "~> 1.3"
60
- spec.add_development_dependency "curses", "~> 1.6"
61
66
  spec.add_development_dependency "kramdown", "~> 2.5"
62
67
  spec.add_development_dependency "unicode-display_width", "~> 3.2"
63
68
  spec.add_development_dependency "async", "~> 2.45"
metadata CHANGED
@@ -1,12 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 16.0.0
4
+ version: 17.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Robert Gleeson
8
8
  - Antar Azri
9
9
  - Rodrigo Serrano
10
+ - Azmi Muwahid
11
+ - George Pickett
12
+ - Mika Mikasuki
13
+ - Nikulin Nikita
14
+ - pttydou
10
15
  bindir: bin
11
16
  cert_chain: []
12
17
  date: 1980-01-02 00:00:00.000000000 Z
@@ -73,14 +78,14 @@ dependencies:
73
78
  requirements:
74
79
  - - "~>"
75
80
  - !ruby/object:Gem::Version
76
- version: 2.7.1
81
+ version: 2.7.3
77
82
  type: :development
78
83
  prerelease: false
79
84
  version_requirements: !ruby/object:Gem::Requirement
80
85
  requirements:
81
86
  - - "~>"
82
87
  - !ruby/object:Gem::Version
83
- version: 2.7.1
88
+ version: 2.7.3
84
89
  - !ruby/object:Gem::Dependency
85
90
  name: rake
86
91
  requirement: !ruby/object:Gem::Requirement
@@ -291,34 +296,6 @@ dependencies:
291
296
  - - "~>"
292
297
  - !ruby/object:Gem::Version
293
298
  version: '1.18'
294
- - !ruby/object:Gem::Dependency
295
- name: curb
296
- requirement: !ruby/object:Gem::Requirement
297
- requirements:
298
- - - "~>"
299
- - !ruby/object:Gem::Version
300
- version: '1.3'
301
- type: :development
302
- prerelease: false
303
- version_requirements: !ruby/object:Gem::Requirement
304
- requirements:
305
- - - "~>"
306
- - !ruby/object:Gem::Version
307
- version: '1.3'
308
- - !ruby/object:Gem::Dependency
309
- name: curses
310
- requirement: !ruby/object:Gem::Requirement
311
- requirements:
312
- - - "~>"
313
- - !ruby/object:Gem::Version
314
- version: '1.6'
315
- type: :development
316
- prerelease: false
317
- version_requirements: !ruby/object:Gem::Requirement
318
- requirements:
319
- - - "~>"
320
- - !ruby/object:Gem::Version
321
- version: '1.6'
322
299
  - !ruby/object:Gem::Dependency
323
300
  name: kramdown
324
301
  requirement: !ruby/object:Gem::Requirement
@@ -394,7 +371,7 @@ files:
394
371
  - docs/deepdive/advanced/cancellation.md
395
372
  - docs/deepdive/advanced/compaction.md
396
373
  - docs/deepdive/advanced/context.md
397
- - docs/deepdive/advanced/guard.md
374
+ - docs/deepdive/advanced/intercept.md
398
375
  - docs/deepdive/advanced/transformer.md
399
376
  - docs/deepdive/advanced/transports.md
400
377
  - docs/deepdive/features/builtin_tools.md
@@ -438,6 +415,7 @@ files:
438
415
  - lib/llm/active_record/message.rb
439
416
  - lib/llm/agent.rb
440
417
  - lib/llm/agent/interrupt.rb
418
+ - lib/llm/agent/registry.rb
441
419
  - lib/llm/buffer.rb
442
420
  - lib/llm/compactor.rb
443
421
  - lib/llm/compactor/null.rb
@@ -498,10 +476,10 @@ files:
498
476
  - lib/llm/function/task.rb
499
477
  - lib/llm/function/thread/group.rb
500
478
  - lib/llm/function/thread/task.rb
501
- - lib/llm/function/tracing.rb
502
479
  - lib/llm/function/window.rb
503
- - lib/llm/guard.rb
504
- - lib/llm/guard/null.rb
480
+ - lib/llm/intercept.rb
481
+ - lib/llm/intercept/multi.rb
482
+ - lib/llm/intercept/null.rb
505
483
  - lib/llm/json_adapter.rb
506
484
  - lib/llm/mcp.rb
507
485
  - lib/llm/mcp/command.rb
@@ -555,6 +533,7 @@ files:
555
533
  - lib/llm/providers/deepinfra/images.rb
556
534
  - lib/llm/providers/deepinfra/response_adapter.rb
557
535
  - lib/llm/providers/deepseek.rb
536
+ - lib/llm/providers/deepseek/files.rb
558
537
  - lib/llm/providers/deepseek/images.rb
559
538
  - lib/llm/providers/deepseek/request_adapter.rb
560
539
  - lib/llm/providers/deepseek/request_adapter/completion.rb
@@ -673,9 +652,11 @@ files:
673
652
  - lib/llm/tools/write_file.rb
674
653
  - lib/llm/tracer.rb
675
654
  - lib/llm/tracer/logger.rb
655
+ - lib/llm/tracer/multi.rb
676
656
  - lib/llm/tracer/null.rb
677
657
  - lib/llm/tracer/pretty_logger.rb
678
658
  - lib/llm/tracer/registry.rb
659
+ - lib/llm/tracer/rescue.rb
679
660
  - lib/llm/tracer/telemetry.rb
680
661
  - lib/llm/transformer.rb
681
662
  - lib/llm/transformer/null.rb
@@ -1,21 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # The {LLM::Function::Tracing LLM::Function::Tracing} module patches
6
- # an LLM function (or tool) in order to add tracing support.
7
- module Tracing
8
- def call(...)
9
- return super unless @tracer
10
- span = @tracer.on_tool_start(id:, name:, arguments:, model:)
11
- result = super
12
- @tracer.on_tool_finish(result:, span:)
13
- result
14
- rescue LLM::Interrupt
15
- raise
16
- rescue => ex
17
- @tracer.on_tool_error(ex:, span:)
18
- raise(ex)
19
- end
20
- end
21
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Guard
4
- ##
5
- # {LLM::Guard::Null LLM::Guard::Null} is a no-op guard. It never blocks
6
- # pending tool work and is the default guard used by
7
- # {LLM::Context LLM::Context}.
8
- class Null < self
9
- ##
10
- # @param [LLM::Function] function
11
- # The pending function (ignored)
12
- # @param [Hash] opts
13
- # Per-call options (ignored)
14
- # @return [nil]
15
- def call(function:, **opts)
16
- nil
17
- end
18
- end
19
- end