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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +535 -2
- data/README.md +857 -501
- data/data/alibaba.json +2 -2
- data/data/anthropic.json +66 -3
- data/data/bedrock.json +781 -12
- data/data/deepinfra.json +157 -1
- data/data/google.json +48 -0
- data/data/mistral.json +326 -16
- data/data/moonshot.json +2 -1
- data/data/openai.json +13 -0
- data/data/openrouter.json +1078 -1235
- data/data/xai.json +4 -2
- data/docs/deepdive/advanced/cancellation.md +35 -7
- data/docs/deepdive/advanced/compaction.md +5 -5
- data/docs/deepdive/advanced/context.md +45 -24
- data/docs/deepdive/advanced/{guard.md → intercept.md} +87 -83
- data/docs/deepdive/features/builtin_tools.md +1 -1
- data/docs/deepdive/features/concurrency.md +15 -10
- data/docs/deepdive/features/database.md +19 -19
- data/docs/deepdive/features/embeddings.md +4 -4
- data/docs/deepdive/features/files.md +7 -5
- data/docs/deepdive/fundamentals/agents.md +13 -8
- data/docs/deepdive/fundamentals/providers.md +1 -1
- data/docs/deepdive/fundamentals/schema.md +2 -2
- data/docs/deepdive/fundamentals/stream.md +8 -8
- data/docs/deepdive/fundamentals/tools.md +14 -14
- data/docs/deepdive/media/ocr.md +1 -1
- data/docs/deepdive/protocols/a2a.md +3 -3
- data/docs/deepdive/protocols/mcp.md +116 -7
- data/docs/deepdive/reference/cost.md +1 -1
- data/docs/deepdive/reference/response.md +1 -1
- data/docs/deepdive/reference/tracer.md +90 -22
- data/docs/deepdive.md +1 -1
- data/lib/llm/agent/interrupt.rb +35 -46
- data/lib/llm/agent/registry.rb +106 -0
- data/lib/llm/agent.rb +242 -268
- data/lib/llm/context.rb +63 -55
- data/lib/llm/function/array.rb +2 -1
- data/lib/llm/function/async/group.rb +2 -2
- data/lib/llm/function/async/task.rb +25 -48
- data/lib/llm/function/fiber/group.rb +2 -2
- data/lib/llm/function/fiber/task.rb +16 -30
- data/lib/llm/function/fork/group.rb +2 -2
- data/lib/llm/function/fork/job.rb +8 -4
- data/lib/llm/function/fork/task.rb +25 -30
- data/lib/llm/function/group.rb +33 -5
- data/lib/llm/function/ractor/group.rb +2 -2
- data/lib/llm/function/ractor/job.rb +8 -6
- data/lib/llm/function/ractor/task.rb +32 -9
- data/lib/llm/function/sequential/group.rb +2 -1
- data/lib/llm/function/sequential/task.rb +8 -1
- data/lib/llm/function/task.rb +52 -4
- data/lib/llm/function/thread/group.rb +2 -2
- data/lib/llm/function/thread/task.rb +36 -10
- data/lib/llm/function/window.rb +16 -0
- data/lib/llm/function.rb +14 -43
- data/lib/llm/intercept/multi.rb +40 -0
- data/lib/llm/intercept/null.rb +18 -0
- data/lib/llm/intercept.rb +96 -0
- data/lib/llm/message.rb +1 -1
- data/lib/llm/provider.rb +16 -1
- data/lib/llm/providers/anthropic/stream_parser.rb +1 -1
- data/lib/llm/providers/bedrock/stream_parser.rb +1 -1
- data/lib/llm/providers/deepseek/files.rb +49 -0
- data/lib/llm/providers/deepseek/request_adapter/completion.rb +15 -13
- data/lib/llm/providers/deepseek.rb +16 -2
- data/lib/llm/providers/google/stream_parser.rb +1 -1
- data/lib/llm/providers/openai/responses/stream_parser.rb +1 -1
- data/lib/llm/providers/openai/stream_parser.rb +1 -1
- data/lib/llm/stream.rb +8 -7
- data/lib/llm/tool.rb +1 -1
- data/lib/llm/tools/utils.rb +1 -1
- data/lib/llm/tracer/logger.rb +42 -1
- data/lib/llm/tracer/multi.rb +144 -0
- data/lib/llm/tracer/null.rb +20 -0
- data/lib/llm/tracer/pretty_logger.rb +33 -2
- data/lib/llm/tracer/rescue.rb +110 -0
- data/lib/llm/tracer/telemetry.rb +69 -4
- data/lib/llm/tracer.rb +125 -48
- data/lib/llm/utils.rb +12 -2
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +36 -1
- data/llm.gemspec +9 -4
- metadata +16 -35
- data/lib/llm/function/tracing.rb +0 -21
- data/lib/llm/guard/null.rb +0 -19
- data/lib/llm/guard.rb +0 -55
data/lib/llm/tracer/telemetry.rb
CHANGED
|
@@ -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.
|
|
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/
|
|
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
|
-
#
|
|
128
|
+
# This method is called when a tracer exits a scope that
|
|
129
|
+
# has been opened by {LLM::Provider#with_tracer}.
|
|
99
130
|
#
|
|
100
|
-
#
|
|
101
|
-
#
|
|
102
|
-
#
|
|
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
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
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
|
|
141
|
-
#
|
|
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
|
|
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
|
|
177
|
-
#
|
|
178
|
-
#
|
|
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
|
|
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
|
-
#
|
|
189
|
-
#
|
|
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
|
-
#
|
|
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
|
-
#
|
|
201
|
-
#
|
|
202
|
-
#
|
|
203
|
-
#
|
|
204
|
-
#
|
|
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
|
-
#
|
|
207
|
-
#
|
|
208
|
-
#
|
|
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
|
|
220
|
-
# between its requests
|
|
298
|
+
# :request, :agent, or :tool
|
|
221
299
|
# @param [Object, nil] span
|
|
222
|
-
# The span that {#on_request_start} returned
|
|
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}
|
|
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
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/
|
|
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",
|
|
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.
|
|
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:
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
504
|
-
- lib/llm/
|
|
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
|
data/lib/llm/function/tracing.rb
DELETED
|
@@ -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
|
data/lib/llm/guard/null.rb
DELETED
|
@@ -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
|