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/agent.rb
CHANGED
|
@@ -2,48 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module LLM
|
|
4
4
|
##
|
|
5
|
-
# {LLM::
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# and instructions.
|
|
5
|
+
# Unlike {LLM::Context LLM::Context}
|
|
6
|
+
# the {LLM::Agent LLM::Agent} class
|
|
7
|
+
# automatically manages the tool loop.
|
|
9
8
|
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
9
|
+
# @example class Agent < LLM::Agent
|
|
10
|
+
# class Agent < LLM::Agent
|
|
11
|
+
# set name: "sam altman",
|
|
12
|
+
# description: "a ceo",
|
|
13
|
+
# instructions: "beat the competition",
|
|
14
|
+
# tools: :toolset,
|
|
15
|
+
# stream: proc { Stream.new(llm) }
|
|
16
16
|
#
|
|
17
|
-
#
|
|
18
|
-
#
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# next request goes out.
|
|
22
|
-
# * An agent automatically executes tool loops (unlike {LLM::Context LLM::Context}).
|
|
23
|
-
# * The tool loop can be bounded with `tool_budget`. Once the budget is
|
|
24
|
-
# spent, no further tool calls are run for that turn: the agent sends an
|
|
25
|
-
# in-band advisory message back through the model instead, and keeps
|
|
26
|
-
# sending it while the model keeps asking for tools. The budget counts
|
|
27
|
-
# tool calls, so a batch of calls is spent as a batch, and a batch that
|
|
28
|
-
# would take the turn past its budget is not run at all. By default no
|
|
29
|
-
# budget is set (`nil`), so the feature is disabled.
|
|
30
|
-
# * Tool loop execution can be configured with `concurrency :sequential`,
|
|
31
|
-
# `:thread`, `:async`, `:fiber`, `:fork`, or `:ractor`.
|
|
32
|
-
#
|
|
33
|
-
# @example Subclass with defaults
|
|
34
|
-
# class SystemAdmin < LLM::Agent
|
|
35
|
-
# set model: "gpt-4.1-nano",
|
|
36
|
-
# instructions: "You are a Linux system admin",
|
|
37
|
-
# tools: [LLM::Tool::Exec],
|
|
38
|
-
# schema: Result
|
|
17
|
+
# private
|
|
18
|
+
# def toolset
|
|
19
|
+
# [LLM::Tool::Exec]
|
|
20
|
+
# end
|
|
39
21
|
# end
|
|
40
22
|
#
|
|
41
|
-
# llm
|
|
42
|
-
# agent =
|
|
43
|
-
# agent.talk("
|
|
23
|
+
# llm = LLM.openai
|
|
24
|
+
# agent = Agent.new(llm)
|
|
25
|
+
# agent.talk("setup ollama to run gpt-oss")
|
|
44
26
|
#
|
|
45
|
-
# @example
|
|
46
|
-
# llm
|
|
27
|
+
# @example LLM::Agent.new
|
|
28
|
+
# llm = LLM.deepseek
|
|
47
29
|
# agent = LLM::Agent.new(llm, stream: $stdout)
|
|
48
30
|
# agent.talk "Hello world"
|
|
49
31
|
#
|
|
@@ -52,6 +34,7 @@ module LLM
|
|
|
52
34
|
# @see LLM::Stream Stream callbacks for model output
|
|
53
35
|
class Agent
|
|
54
36
|
require_relative "agent/interrupt"
|
|
37
|
+
require_relative "agent/registry"
|
|
55
38
|
|
|
56
39
|
##
|
|
57
40
|
# @api private
|
|
@@ -59,15 +42,12 @@ module LLM
|
|
|
59
42
|
private_constant :UNDEFINED
|
|
60
43
|
|
|
61
44
|
##
|
|
62
|
-
#
|
|
63
|
-
# turn can tell it from a message the caller composed. The role
|
|
64
|
-
# cannot do that: it is true of whatever the caller put there.
|
|
45
|
+
# @return [Hash]
|
|
65
46
|
# @api private
|
|
66
47
|
INSTRUCTIONS = {instructions: true}.freeze
|
|
67
48
|
private_constant :INSTRUCTIONS
|
|
68
49
|
|
|
69
50
|
##
|
|
70
|
-
# Ugh :)
|
|
71
51
|
# @api private
|
|
72
52
|
FIELDS = %i[record
|
|
73
53
|
name description
|
|
@@ -76,7 +56,8 @@ module LLM
|
|
|
76
56
|
skills schema
|
|
77
57
|
tracer stream
|
|
78
58
|
tools concurrency
|
|
79
|
-
instructions confirm
|
|
59
|
+
instructions confirm
|
|
60
|
+
intercept]
|
|
80
61
|
IVARS = %i[record
|
|
81
62
|
name description
|
|
82
63
|
path tool_budget
|
|
@@ -94,39 +75,54 @@ module LLM
|
|
|
94
75
|
File = ::File
|
|
95
76
|
private_constant :File
|
|
96
77
|
|
|
78
|
+
##
|
|
79
|
+
# @return [LLM::Agent::Registry]
|
|
80
|
+
REGISTRY = Registry.new
|
|
81
|
+
private_constant :REGISTRY
|
|
82
|
+
|
|
97
83
|
##
|
|
98
84
|
# Returns a provider
|
|
99
85
|
# @return [LLM::Provider]
|
|
100
86
|
attr_reader :llm
|
|
101
87
|
|
|
102
88
|
##
|
|
103
|
-
#
|
|
104
|
-
#
|
|
105
|
-
#
|
|
106
|
-
#
|
|
107
|
-
#
|
|
89
|
+
# Returns an instance of {LLM::Agent::Registry LLM::Agent::Registry}.
|
|
90
|
+
# @note
|
|
91
|
+
# The registry holds all _active_
|
|
92
|
+
# instances of {LLM::Agent LLM::Agent}
|
|
93
|
+
# and inactive agents are automatically
|
|
94
|
+
# removed from the registry. The registry
|
|
95
|
+
# is local to a process, and its children.
|
|
96
|
+
# @return [LLM::Agent::Registry]
|
|
97
|
+
def self.registry
|
|
98
|
+
REGISTRY
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
##
|
|
102
|
+
# Bulk-assign class-level defaults
|
|
108
103
|
#
|
|
109
104
|
# @example
|
|
110
|
-
# class
|
|
111
|
-
# set name: "
|
|
112
|
-
#
|
|
113
|
-
#
|
|
105
|
+
# class Agent < LLM::Agent
|
|
106
|
+
# set name: "007",
|
|
107
|
+
# description: "a secret agrent",
|
|
108
|
+
# instructions: "find top secret material",
|
|
114
109
|
# tools: [LLM::Tool::Exec, LLM::Tool::ReadFile]
|
|
115
110
|
# end
|
|
116
111
|
#
|
|
117
112
|
# @param [Hash] properties
|
|
118
113
|
# @option properties [String, Symbol, Proc] :name
|
|
119
114
|
# @option properties [String, Symbol, Proc] :description
|
|
120
|
-
# @option properties [String] :instructions
|
|
121
|
-
# @option properties [String] :model
|
|
122
|
-
# @option properties [
|
|
123
|
-
# @option properties [
|
|
124
|
-
# @option properties [
|
|
115
|
+
# @option properties [String, Symbol, Proc] :instructions
|
|
116
|
+
# @option properties [String, Symbol, Proc] :model
|
|
117
|
+
# @option properties [LLM::Tracer, Symbol, Proc] :tracer
|
|
118
|
+
# @option properties [LLM::Stream, Symbol, Proc] :stream
|
|
119
|
+
# @option properties [LLM::Schema, Symbol, Proc] :schema
|
|
120
|
+
# @option properties [Array<LLM::Function>, Symbol, Proc] :tools
|
|
121
|
+
# @option properties [Array<String>, Symbol, Proc] :skills
|
|
125
122
|
# @option properties [Symbol, Array<Symbol>] :concurrency
|
|
126
|
-
# @option properties [LLM::Tracer, Proc] :tracer
|
|
127
|
-
# @option properties [Object, Proc] :stream
|
|
128
123
|
# @option properties [String, Symbol, Array<String, Symbol>, Proc] :confirm
|
|
129
|
-
# @raise [KeyError]
|
|
124
|
+
# @raise [KeyError]
|
|
125
|
+
# When a given key does not match a class-level accessor
|
|
130
126
|
# @return [void]
|
|
131
127
|
def self.set(properties)
|
|
132
128
|
properties.each do
|
|
@@ -189,162 +185,121 @@ module LLM
|
|
|
189
185
|
end
|
|
190
186
|
|
|
191
187
|
##
|
|
192
|
-
# Set or get the
|
|
193
|
-
# @param [
|
|
194
|
-
#
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# The schema
|
|
206
|
-
# @return [#to_json, nil]
|
|
207
|
-
# Returns the current schema when no argument is provided
|
|
208
|
-
def self.schema(schema = nil, &block)
|
|
209
|
-
return @schema if schema.nil? and !block
|
|
210
|
-
@schema = block || schema
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
##
|
|
214
|
-
# Set or get the default tools
|
|
215
|
-
# @param [Array<LLM::Function>, nil] tools
|
|
216
|
-
# One or more tools
|
|
217
|
-
# @return [Array<LLM::Function>]
|
|
218
|
-
# Returns the current tools when no argument is provided
|
|
219
|
-
def self.tools(*tools, &block)
|
|
220
|
-
return @tools || [] if tools.empty? and !block
|
|
221
|
-
if block
|
|
222
|
-
@tools = block
|
|
223
|
-
elsif single_callable?(tools)
|
|
224
|
-
@tools = tools.first
|
|
188
|
+
# Set or get the tool execution concurrency.
|
|
189
|
+
# @param [Symbol, Array<Symbol>, nil] concurrency
|
|
190
|
+
# Controls how pending tool loops are executed:
|
|
191
|
+
# - `:sequential`: sequential calls
|
|
192
|
+
# - `:thread`: concurrent threads
|
|
193
|
+
# - `:async`: concurrent async tasks
|
|
194
|
+
# - `:fiber`: concurrent scheduler-backed fibers
|
|
195
|
+
# - `:fork`: a child process
|
|
196
|
+
# - `:ractor`: Ruby-based ractors
|
|
197
|
+
# @return [Symbol, Array<Symbol>, nil]
|
|
198
|
+
def self.concurrency(concurrency = UNDEFINED)
|
|
199
|
+
if concurrency.equal?(UNDEFINED)
|
|
200
|
+
@concurrency
|
|
225
201
|
else
|
|
226
|
-
@
|
|
202
|
+
@concurrency = concurrency
|
|
227
203
|
end
|
|
228
204
|
end
|
|
229
205
|
|
|
230
206
|
##
|
|
231
|
-
# Set or get the
|
|
232
|
-
# @param [
|
|
233
|
-
#
|
|
234
|
-
# @
|
|
235
|
-
#
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
@skills = skills.first
|
|
207
|
+
# Set or get the model an agent will use
|
|
208
|
+
# @param [String, Symbol] model
|
|
209
|
+
# The model
|
|
210
|
+
# @param [Proc] block
|
|
211
|
+
# A block that is lazy-evaled.
|
|
212
|
+
# @return [String, nil]
|
|
213
|
+
# Returns the model
|
|
214
|
+
def self.model(model = UNDEFINED, &block)
|
|
215
|
+
if model.equal?(UNDEFINED) and block.nil?
|
|
216
|
+
@model
|
|
242
217
|
else
|
|
243
|
-
@
|
|
218
|
+
@model = block || model
|
|
244
219
|
end
|
|
245
220
|
end
|
|
246
221
|
|
|
247
222
|
##
|
|
248
|
-
# Set or get
|
|
249
|
-
#
|
|
250
|
-
#
|
|
251
|
-
# initialization time by calling the method with that name on the agent
|
|
252
|
-
# instance. This allows dynamic tool confirmation lists.
|
|
253
|
-
#
|
|
254
|
-
# @example
|
|
255
|
-
# class MyAgent < LLM::Agent
|
|
256
|
-
# confirm :tools_that_need_confirmation
|
|
257
|
-
#
|
|
258
|
-
# def tools_that_need_confirmation
|
|
259
|
-
# some_condition ? %w[delete destroy] : %w[delete]
|
|
260
|
-
# end
|
|
261
|
-
# end
|
|
262
|
-
#
|
|
263
|
-
# @param [String, Symbol, Array<String, Symbol>, Proc] tool_names
|
|
264
|
-
# One or more tool names.
|
|
223
|
+
# Set or get an agent's schema
|
|
224
|
+
# @param [LLM::Schema, Symbol] schema
|
|
225
|
+
# The schema
|
|
265
226
|
# @param [Proc] block
|
|
266
|
-
#
|
|
267
|
-
# @return [
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
@
|
|
272
|
-
elsif single_callable?(tool_names)
|
|
273
|
-
@confirm = tool_names.first
|
|
227
|
+
# A block that is lazy-evaled.
|
|
228
|
+
# @return [LLM::Schema, nil]
|
|
229
|
+
# Returns a schema
|
|
230
|
+
def self.schema(schema = UNDEFINED, &block)
|
|
231
|
+
if schema.equal?(UNDEFINED) and block.nil?
|
|
232
|
+
@schema
|
|
274
233
|
else
|
|
275
|
-
@
|
|
234
|
+
@schema = block || schema
|
|
276
235
|
end
|
|
277
236
|
end
|
|
278
237
|
|
|
279
238
|
##
|
|
280
|
-
# Set or get
|
|
281
|
-
# @param [String, nil] instructions
|
|
239
|
+
# Set or get an agent's instructions
|
|
240
|
+
# @param [String, Symbol, nil] instructions
|
|
282
241
|
# The system instructions
|
|
242
|
+
# @param [Proc] block
|
|
243
|
+
# A block that is lazy-evaled.
|
|
283
244
|
# @return [String, nil]
|
|
284
|
-
# Returns the
|
|
285
|
-
def self.instructions(instructions =
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
# Set or get the tool execution concurrency.
|
|
292
|
-
#
|
|
293
|
-
# @param [Symbol, Array<Symbol>, nil] concurrency
|
|
294
|
-
# Controls how pending tool loops are executed:
|
|
295
|
-
# - `:sequential`: sequential calls
|
|
296
|
-
# - `:thread`: concurrent threads
|
|
297
|
-
# - `:async`: concurrent async tasks
|
|
298
|
-
# - `:fiber`: concurrent scheduler-backed fibers
|
|
299
|
-
# - `:fork`: forked child processes
|
|
300
|
-
# - `:ractor`: concurrent Ruby ractors for class-based tools; MCP tools are not supported,
|
|
301
|
-
# and this mode is especially useful for CPU-bound tool work
|
|
302
|
-
# Usually pass a single strategy. Arrays are only for advanced mixed-work
|
|
303
|
-
# cases and are not needed for normal queued stream tool loops.
|
|
304
|
-
# @return [Symbol, Array<Symbol>, nil]
|
|
305
|
-
def self.concurrency(concurrency = nil)
|
|
306
|
-
return @concurrency if concurrency.nil?
|
|
307
|
-
@concurrency = concurrency
|
|
245
|
+
# Returns the instructions
|
|
246
|
+
def self.instructions(instructions = UNDEFINED, &block)
|
|
247
|
+
if instructions.equal?(UNDEFINED) and block.nil?
|
|
248
|
+
@instructions
|
|
249
|
+
else
|
|
250
|
+
@instructions = block || instructions
|
|
251
|
+
end
|
|
308
252
|
end
|
|
309
253
|
|
|
310
254
|
##
|
|
311
|
-
# Set or get
|
|
312
|
-
#
|
|
313
|
-
# When a block is provided, it is stored and evaluated lazily against the
|
|
314
|
-
# agent instance during initialization so it can build a tracer from the
|
|
315
|
-
# resolved provider.
|
|
316
|
-
#
|
|
255
|
+
# Set or get an agent's tracer.
|
|
317
256
|
# @example
|
|
318
257
|
# class Agent < LLM::Agent
|
|
319
|
-
# tracer { LLM::Tracer.logger(llm, io: $stdout) }
|
|
258
|
+
# set tracer: proc { LLM::Tracer.logger(llm, io: $stdout) }
|
|
320
259
|
# end
|
|
321
|
-
#
|
|
322
|
-
#
|
|
323
|
-
# @
|
|
260
|
+
# @param [LLM::Tracer, Symbol, nil] tracer
|
|
261
|
+
# A tracer.
|
|
262
|
+
# @param [Proc] block
|
|
263
|
+
# A block that is lazy-evaled.
|
|
324
264
|
# @return [LLM::Tracer, Proc, nil]
|
|
325
|
-
def self.tracer(tracer =
|
|
326
|
-
|
|
327
|
-
|
|
265
|
+
def self.tracer(tracer = UNDEFINED, &block)
|
|
266
|
+
if tracer.equal?(UNDEFINED) and block.nil?
|
|
267
|
+
@tracer
|
|
268
|
+
else
|
|
269
|
+
@tracer = block || tracer
|
|
270
|
+
end
|
|
328
271
|
end
|
|
329
272
|
|
|
330
273
|
##
|
|
331
|
-
# Set or get
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
335
|
-
#
|
|
336
|
-
#
|
|
274
|
+
# Set or get an agent's intercept.
|
|
275
|
+
# @param [LLM::Intercept] intercept
|
|
276
|
+
# An intercept object
|
|
277
|
+
# @param [Proc] block
|
|
278
|
+
# A block that is lazy-evaled.
|
|
279
|
+
# @return [LLM::Intercept, nil]
|
|
280
|
+
def self.intercept(intercept = UNDEFINED, &block)
|
|
281
|
+
if intercept.equal?(UNDEFINED) and block.nil?
|
|
282
|
+
@intercept
|
|
283
|
+
else
|
|
284
|
+
@intercept = block || intercept
|
|
285
|
+
end
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
##
|
|
289
|
+
# Set or get an agent's stream
|
|
337
290
|
# @example
|
|
338
291
|
# class Agent < LLM::Agent
|
|
339
|
-
# stream {
|
|
292
|
+
# set stream: proc { Stream.new }
|
|
340
293
|
# end
|
|
341
|
-
#
|
|
342
294
|
# @param [Object, Proc, nil] stream
|
|
343
295
|
# @yieldreturn [Object, nil]
|
|
344
296
|
# @return [Object, Proc, nil]
|
|
345
|
-
def self.stream(stream =
|
|
346
|
-
|
|
347
|
-
|
|
297
|
+
def self.stream(stream = UNDEFINED, &block)
|
|
298
|
+
if stream.equal?(UNDEFINED) and block.nil?
|
|
299
|
+
@stream
|
|
300
|
+
else
|
|
301
|
+
@stream = block || stream
|
|
302
|
+
end
|
|
348
303
|
end
|
|
349
304
|
|
|
350
305
|
##
|
|
@@ -363,22 +318,14 @@ module LLM
|
|
|
363
318
|
|
|
364
319
|
##
|
|
365
320
|
# Set or get the maximum number of tool calls
|
|
366
|
-
#
|
|
367
|
-
# budget is spent, no further tool calls are run:
|
|
368
|
-
# we return an in-band message that informs the
|
|
369
|
-
# model it has spent its tool call budget, and
|
|
370
|
-
# keep returning it while the model keeps asking
|
|
371
|
-
# for tools - a model will usually change course
|
|
372
|
-
# afterwards. The budget counts tool calls, so a
|
|
373
|
-
# batch of calls is spent as a batch, and a batch
|
|
374
|
-
# that would take the turn past its budget is not
|
|
375
|
-
# run at all.
|
|
321
|
+
# an agent can make in a single turn.
|
|
376
322
|
# @note
|
|
377
|
-
#
|
|
378
|
-
# (set to `nil`).
|
|
323
|
+
# This feature is disabled by default.
|
|
379
324
|
# @param [Integer] budget
|
|
380
325
|
# The maximum number of tool calls to allow in
|
|
381
326
|
# a single turn.
|
|
327
|
+
# @param [Proc] block
|
|
328
|
+
# A block that is lazy-evaled
|
|
382
329
|
# @return [Integer, nil]
|
|
383
330
|
def self.tool_budget(budget = UNDEFINED, &block)
|
|
384
331
|
if budget.equal?(UNDEFINED) and block.nil?
|
|
@@ -389,21 +336,80 @@ module LLM
|
|
|
389
336
|
end
|
|
390
337
|
|
|
391
338
|
##
|
|
392
|
-
#
|
|
393
|
-
#
|
|
394
|
-
# The retry budget is the maximum number of times a rate-limited
|
|
395
|
-
# request will be retried before giving up. Each retry sleeps a
|
|
396
|
-
# growing interval, so an exhausted budget surfaces the rate-limit
|
|
397
|
-
# error instead of blocking indefinitely. Enabled (5) by default; a
|
|
398
|
-
# raw {LLM::Context} disables it (0) unless configured.
|
|
339
|
+
# Set or get the number of attempts an agent
|
|
340
|
+
# will make when given a failed request.
|
|
399
341
|
# @param [Integer] budget
|
|
400
|
-
# The maximum number of
|
|
342
|
+
# The maximum number of retries in a turn.
|
|
343
|
+
# @param [Proc] block
|
|
344
|
+
# A block that is lazy-evaled
|
|
401
345
|
# @return [Integer, nil]
|
|
402
|
-
def self.retry_budget(budget = UNDEFINED)
|
|
403
|
-
if budget.equal?(UNDEFINED)
|
|
404
|
-
@retry_budget
|
|
346
|
+
def self.retry_budget(budget = UNDEFINED, &block)
|
|
347
|
+
if budget.equal?(UNDEFINED) and block.nil?
|
|
348
|
+
@retry_budget
|
|
349
|
+
else
|
|
350
|
+
@retry_budget = block || budget
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
##
|
|
355
|
+
# Set or get the default tools
|
|
356
|
+
# @param [Array<LLM::Function>, nil] tools
|
|
357
|
+
# One or more tools
|
|
358
|
+
# @return [Array<LLM::Function>]
|
|
359
|
+
# Returns the current tools when no argument is provided
|
|
360
|
+
def self.tools(*tools, &block)
|
|
361
|
+
return @tools || [] if tools.empty? and !block
|
|
362
|
+
if block
|
|
363
|
+
@tools = block
|
|
364
|
+
elsif single_callable?(tools)
|
|
365
|
+
@tools = tools.first
|
|
366
|
+
else
|
|
367
|
+
@tools = tools.flatten
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
##
|
|
372
|
+
# Set or get the default skills
|
|
373
|
+
# @param [Array<String>, nil] skills
|
|
374
|
+
# One or more skill directories
|
|
375
|
+
# @return [Array<String>, nil]
|
|
376
|
+
# Returns the current skills when no argument is provided
|
|
377
|
+
def self.skills(*skills, &block)
|
|
378
|
+
return @skills if skills.empty? and !block
|
|
379
|
+
if block
|
|
380
|
+
@skills = block
|
|
381
|
+
elsif single_callable?(skills)
|
|
382
|
+
@skills = skills.first
|
|
383
|
+
else
|
|
384
|
+
@skills = skills.flatten
|
|
385
|
+
end
|
|
386
|
+
end
|
|
387
|
+
|
|
388
|
+
##
|
|
389
|
+
# Set or get the agent tools that require confirmation.
|
|
390
|
+
#
|
|
391
|
+
# @example
|
|
392
|
+
# class MyAgent < LLM::Agent
|
|
393
|
+
# confirm :dangerous_tools
|
|
394
|
+
# private
|
|
395
|
+
# def dangerous_tools
|
|
396
|
+
# %w[delete]
|
|
397
|
+
# end
|
|
398
|
+
# end
|
|
399
|
+
#
|
|
400
|
+
# @param [String, Symbol, Array<String, Symbol>, Proc] tool_names
|
|
401
|
+
# One or more tool names.
|
|
402
|
+
# @param [Proc] block
|
|
403
|
+
# An optional, lazy-evaluated Proc
|
|
404
|
+
# @return [Array<String>, Proc, Symbol, nil]
|
|
405
|
+
def self.confirm(*tool_names, &block)
|
|
406
|
+
return @confirm if tool_names.empty? and !block
|
|
407
|
+
if block
|
|
408
|
+
@confirm = block
|
|
409
|
+
elsif single_callable?(tool_names)
|
|
410
|
+
@confirm = tool_names.first
|
|
405
411
|
else
|
|
406
|
-
@
|
|
412
|
+
@confirm = tool_names.flatten.map(&:to_s)
|
|
407
413
|
end
|
|
408
414
|
end
|
|
409
415
|
|
|
@@ -449,14 +455,9 @@ module LLM
|
|
|
449
455
|
instance_variable_set(:"@#{field}", resolved)
|
|
450
456
|
end
|
|
451
457
|
end
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
# rate limit rather than failing outright.
|
|
456
|
-
params[:retry_budget] = llm.retry_budget if params[:retry_budget].equal?(UNDEFINED)
|
|
457
|
-
##
|
|
458
|
-
# The context is bound to the same record the agent holds, so code
|
|
459
|
-
# written against a context alone can find it through `ctx.record`.
|
|
458
|
+
if params[:retry_budget].nil?
|
|
459
|
+
params[:retry_budget] = llm.retry_budget
|
|
460
|
+
end
|
|
460
461
|
@ctx = LLM::Context.new(llm, params.merge(record: @record).compact)
|
|
461
462
|
@path and File.readable?(@path) ? @ctx.restore(path:) : nil
|
|
462
463
|
end
|
|
@@ -707,17 +708,14 @@ module LLM
|
|
|
707
708
|
end
|
|
708
709
|
|
|
709
710
|
##
|
|
710
|
-
# Start an agent console
|
|
711
|
-
# with the agent and its current state. This
|
|
712
|
-
# method requires the following gems to be
|
|
713
|
-
# installed and available to require:
|
|
714
|
-
# 'unicode-display_width', 'kramdown', 'curses',
|
|
715
|
-
# 'test-cmd.rb', and 'xchan.rb'.
|
|
711
|
+
# Start an agent console.
|
|
716
712
|
#
|
|
717
713
|
# @note
|
|
718
|
-
#
|
|
719
|
-
#
|
|
720
|
-
#
|
|
714
|
+
# This method requires the following gems
|
|
715
|
+
# to be installed and available to require:
|
|
716
|
+
# 'unicode-display_width', 'kramdown',
|
|
717
|
+
# 'curses', 'test-cmd.rb', and 'xchan.rb'.
|
|
718
|
+
#
|
|
721
719
|
# @param [String] name
|
|
722
720
|
# The agent's name.
|
|
723
721
|
# Defaults to {LLM::Agent#name}.
|
|
@@ -732,11 +730,7 @@ module LLM
|
|
|
732
730
|
# When true, the tracer is kept alive during the
|
|
733
731
|
# console session. Default is false.
|
|
734
732
|
# @return [void]
|
|
735
|
-
def console(name: self.name, path: nil, tools: [], skills: [], tracer: false
|
|
736
|
-
if trace != nil
|
|
737
|
-
warn "llm.rb: trace option is deprecated, use tracer instead"
|
|
738
|
-
tracer = trace
|
|
739
|
-
end
|
|
733
|
+
def console(name: self.name, path: nil, tools: [], skills: [], tracer: false)
|
|
740
734
|
if !tracer
|
|
741
735
|
previous = self.tracer
|
|
742
736
|
self.tracer = nil
|
|
@@ -849,7 +843,7 @@ module LLM
|
|
|
849
843
|
#
|
|
850
844
|
# An agent with no instructions returns first. The caller reaches
|
|
851
845
|
# this through `apply_instructions`, which already returns early for
|
|
852
|
-
# the same reason, and the
|
|
846
|
+
# the same reason, and the check is repeated here so that the method
|
|
853
847
|
# cannot replace a message with nil however it is reached.
|
|
854
848
|
#
|
|
855
849
|
# Comparing content rather than the message is deliberate.
|
|
@@ -949,46 +943,26 @@ module LLM
|
|
|
949
943
|
end
|
|
950
944
|
res
|
|
951
945
|
end
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
# for the same reason. The tracer is part of it too, because the
|
|
973
|
-
# caller announces the turn's last phase to it before raising.
|
|
974
|
-
#
|
|
975
|
-
# Set to nil below once the turn is over, and that is not a
|
|
976
|
-
# formality: a worker's thread is reused for the turn after this
|
|
977
|
-
# one, and a cancel that arrived late would otherwise land in
|
|
978
|
-
# whatever that thread is doing.
|
|
979
|
-
@ctx.instance_variable_set(
|
|
980
|
-
:@caller,
|
|
981
|
-
LLM::Object.from(
|
|
982
|
-
thread: Thread.current,
|
|
983
|
-
fiber: @llm.request_owner,
|
|
984
|
-
scheduler: Fiber.scheduler,
|
|
985
|
-
tracer:
|
|
986
|
-
).extend(Interrupt)
|
|
987
|
-
)
|
|
988
|
-
@llm.with_tracer(tracer, &run)
|
|
989
|
-
ensure
|
|
990
|
-
@ctx.instance_variable_set(:@caller, nil)
|
|
991
|
-
tracer&.stop_trace
|
|
946
|
+
|
|
947
|
+
begin
|
|
948
|
+
tracer = @tracer || @llm.tracer
|
|
949
|
+
caller = LLM::Object.from(thread: Thread.current,
|
|
950
|
+
fiber: @llm.request_owner,
|
|
951
|
+
scheduler: Fiber.scheduler).extend(Interrupt)
|
|
952
|
+
@ctx.instance_variable_set(:@caller, caller)
|
|
953
|
+
tracer.start_trace(name: "llm.turn", trace_group_id: SecureRandom.uuid_v7)
|
|
954
|
+
REGISTRY.enter(self)
|
|
955
|
+
@llm.with_tracer(tracer) do
|
|
956
|
+
run.call
|
|
957
|
+
rescue LLM::Interrupt
|
|
958
|
+
tracer&.on_interrupt(scope: :agent)
|
|
959
|
+
raise
|
|
960
|
+
end
|
|
961
|
+
ensure
|
|
962
|
+
REGISTRY.exit(self)
|
|
963
|
+
@ctx.instance_variable_set(:@caller, nil)
|
|
964
|
+
tracer&.stop_trace
|
|
965
|
+
end
|
|
992
966
|
end
|
|
993
967
|
|
|
994
968
|
##
|