llm.rb 15.2.0 → 15.2.2
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 +57 -0
- data/README.md +25 -12
- data/lib/llm/active_record/acts_as_agent.rb +1 -1
- data/lib/llm/active_record/acts_as_llm.rb +6 -0
- data/lib/llm/agent.rb +30 -15
- data/lib/llm/console/buffer.rb +1 -1
- data/lib/llm/console/command.rb +1 -1
- data/lib/llm/console/input.rb +1 -1
- data/lib/llm/console/status.rb +1 -1
- data/lib/llm/console/stream.rb +1 -1
- data/lib/llm/console/window.rb +2 -2
- data/lib/llm/function.rb +7 -8
- data/lib/llm/registry/model.rb +1 -1
- data/lib/llm/sequel/agent.rb +1 -1
- data/lib/llm/sequel/plugin.rb +6 -0
- data/lib/llm/tools/exec.rb +1 -3
- data/lib/llm/tools/git.rb +5 -2
- data/lib/llm/tools/read_file.rb +1 -1
- data/lib/llm/version.rb +1 -1
- data/llm.gemspec +1 -7
- metadata +3 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '01569fad6afcf4d422b666778989e7bafd1c6e994b6e2be7801ccb5842a7aa3d'
|
|
4
|
+
data.tar.gz: f60ddf02e1a8405cf393e8c581cce61415f459d3207a7c8dc531cbe4e0e87f27
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8d9f88421f209e72e1631cbe3e4119d80af29aa72dc44a1917a6dfcbaa76b3d8014d70afe1c771177f6a755ea2d840ffaf92a850606fdd9d08db5c0d0283d0d6
|
|
7
|
+
data.tar.gz: b9d2417b3d2ce4f54ae5c657791518261332508b7dcfe726f19f81d97a27f329220a81ea2ced411fd15625b11c3964c69e1bcfef5df799f9ee0d16f2342cdd56
|
data/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,63 @@
|
|
|
17
17
|
|
|
18
18
|
*No unreleased changes yet. Check back after the next release.*
|
|
19
19
|
|
|
20
|
+
## v15.2.2
|
|
21
|
+
|
|
22
|
+
Changes since `v15.2.1`.
|
|
23
|
+
|
|
24
|
+
This release fixes the `set_provider`, `set_context`, and `set_tracer`
|
|
25
|
+
callbacks installed by `acts_as_llm` and `plugin :llm` so a subclass
|
|
26
|
+
inherits them from its superclass instead of raising.
|
|
27
|
+
|
|
28
|
+
### Fix
|
|
29
|
+
|
|
30
|
+
* **activerecord, sequel: inherit `set_provider` from a superclass** <br>
|
|
31
|
+
The `set_provider`, `set_context`, and `set_tracer` callbacks installed by
|
|
32
|
+
`acts_as_llm` / `plugin :llm` (and their agent counterparts) are now resolved
|
|
33
|
+
through the normal method lookup chain. Defining a callback on a superclass,
|
|
34
|
+
the usual single-table inheritance setup, no longer raises
|
|
35
|
+
`NotImplementedError`, and a subclass can still override it.
|
|
36
|
+
|
|
37
|
+
## v15.2.1
|
|
38
|
+
|
|
39
|
+
Changes since `v15.2.0`.
|
|
40
|
+
|
|
41
|
+
This release makes the agent count its tool budget across the whole turn
|
|
42
|
+
and emit tool returns to the stream once that budget is spent, and lets
|
|
43
|
+
`LLM::Function#cancel` carry extra return fields.
|
|
44
|
+
|
|
45
|
+
### Agent
|
|
46
|
+
|
|
47
|
+
* **agent: count the tool budget across the whole turn** <br>
|
|
48
|
+
[`LLM::Agent.tool_budget`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
|
|
49
|
+
now caps the tool calls a turn runs in total. A batch of calls is
|
|
50
|
+
spent as a batch, and a batch that would take the turn past its
|
|
51
|
+
budget is not run at all; once the budget is spent no tool call runs,
|
|
52
|
+
and the agent keeps sending its in-band advisory until the model
|
|
53
|
+
answers without requesting more tools. Previously the agent ran up to
|
|
54
|
+
the budget, then ran further batches after each advisory, so a turn
|
|
55
|
+
could run more tool calls than its budget allowed.
|
|
56
|
+
|
|
57
|
+
* **agent: emit tool returns when the budget is spent** <br>
|
|
58
|
+
Fix a bug where, once a turn's tool budget was spent, the agent passed
|
|
59
|
+
its in-band returns to the model without emitting them to the stream.
|
|
60
|
+
The stream went silent, so a stream that tracked tool-call state left
|
|
61
|
+
the calls in the `call` state. The returns are now emitted through
|
|
62
|
+
[`LLM::Stream#on_tool_return`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_tool_return-instance_method)
|
|
63
|
+
like any other tool return.
|
|
64
|
+
|
|
65
|
+
### Function
|
|
66
|
+
|
|
67
|
+
* **function: `LLM::Function#cancel` takes extra return fields** <br>
|
|
68
|
+
[`LLM::Function#cancel`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#cancel-instance_method)
|
|
69
|
+
now accepts keywords beyond `reason:` and merges them into the
|
|
70
|
+
[`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
|
|
71
|
+
it builds. `LLM::Function#budget_spent` now builds its return through
|
|
72
|
+
`cancel` instead of hand-rolling an error, so a budget-spent return is
|
|
73
|
+
marked `cancelled: true` and carries `action:` and `advice:` hints. A
|
|
74
|
+
callback that receives returns can now tell a cancellation apart from
|
|
75
|
+
an ordinary error.
|
|
76
|
+
|
|
20
77
|
## v15.2.0
|
|
21
78
|
|
|
22
79
|
Changes since `v15.1.0`.
|
data/README.md
CHANGED
|
@@ -19,10 +19,10 @@ on CRuby. It has zero runtime dependencies by default, supports
|
|
|
19
19
|
concurrent and parallel tool execution and has a single coherent API
|
|
20
20
|
that spans 14+ providers.
|
|
21
21
|
|
|
22
|
-
The
|
|
22
|
+
The most effective way to learn about llm.rb is to ask [the r.uby.dev chatbot](https://r.uby.dev)
|
|
23
23
|
a question. It is connected to the llm.rb GitHub repository, backed by
|
|
24
|
-
ActiveRecord and uses the builtin MCP feature to connect to GitHub.
|
|
25
|
-
|
|
24
|
+
ActiveRecord and uses the builtin MCP feature to connect to GitHub. The chatbot
|
|
25
|
+
is an llm.rb agent that is deployed with [roda-llm](https://github.com/r-uby-dev/roda-llm#readme).
|
|
26
26
|
|
|
27
27
|
## Install
|
|
28
28
|
|
|
@@ -37,9 +37,22 @@ gem install llm.rb
|
|
|
37
37
|
The
|
|
38
38
|
[`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
39
39
|
class is the default high-level interface,
|
|
40
|
-
and it is recommended for most use-cases. It manages tool
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
and it is recommended for most use-cases. It manages the tool loop
|
|
41
|
+
and provides configurable features on top of it. For example you can
|
|
42
|
+
manage the tool loop with a retry budget alongside a tool call budget,
|
|
43
|
+
among other features.
|
|
44
|
+
|
|
45
|
+
The runtime is designed to keep the tool loop alive and it will
|
|
46
|
+
avoid exceptions. When an error is encountered in a tool or during
|
|
47
|
+
the lifecycle of an agent it is almost always reported back to the
|
|
48
|
+
model as an in-band error that allows the model to correct course.
|
|
49
|
+
|
|
50
|
+
A lot of care also goes into keeping the tool loop from entering
|
|
51
|
+
an invalid state that would lead to API-level errors. For example,
|
|
52
|
+
when a tool call is interrupted it could leave an unanswered tool
|
|
53
|
+
call that a model will reject on the next turn. The runtime takes
|
|
54
|
+
care of this by pruning orphaned tool calls and ensuring that the
|
|
55
|
+
tool loop always remains valid.
|
|
43
56
|
|
|
44
57
|
```ruby
|
|
45
58
|
require "llm"
|
|
@@ -249,12 +262,12 @@ end
|
|
|
249
262
|
<br>
|
|
250
263
|
|
|
251
264
|
The [LLM::Agent#console](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#console-instance_method)
|
|
252
|
-
method drops you into
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
265
|
+
method drops you into an interactive console that is built on
|
|
266
|
+
top of curses. It can help you debug agents, test your tools,
|
|
267
|
+
connect to MCP servers, and other A2A agents. The console stands
|
|
268
|
+
out because it connects to the surrounding runtime and it can
|
|
269
|
+
be extended by your code. Think of it as `binding.irb` but
|
|
270
|
+
for agents.
|
|
258
271
|
|
|
259
272
|
##### Demo
|
|
260
273
|
|
|
@@ -220,19 +220,25 @@ module LLM::ActiveRecord
|
|
|
220
220
|
|
|
221
221
|
##
|
|
222
222
|
# @return [LLM::Provider]
|
|
223
|
+
# @raise [NotImplementedError]
|
|
224
|
+
# when neither this model nor one of its ancestors implements the
|
|
225
|
+
# callback
|
|
223
226
|
def set_provider
|
|
227
|
+
return super if defined?(super)
|
|
224
228
|
raise NotImplementedError, "implement the set_provider callback"
|
|
225
229
|
end
|
|
226
230
|
|
|
227
231
|
##
|
|
228
232
|
# @return [Hash]
|
|
229
233
|
def set_context
|
|
234
|
+
return super if defined?(super)
|
|
230
235
|
EMPTY_HASH.dup
|
|
231
236
|
end
|
|
232
237
|
|
|
233
238
|
##
|
|
234
239
|
# @return [LLM::Tracer]
|
|
235
240
|
def set_tracer
|
|
241
|
+
return super if defined?(super)
|
|
236
242
|
nil
|
|
237
243
|
end
|
|
238
244
|
|
data/lib/llm/agent.rb
CHANGED
|
@@ -22,9 +22,12 @@ module LLM
|
|
|
22
22
|
# tool-call patterns and blocks stuck execution before more tool work is
|
|
23
23
|
# queued.
|
|
24
24
|
# * The tool loop can be bounded with `tool_budget`. Once the budget is
|
|
25
|
-
# spent,
|
|
26
|
-
#
|
|
27
|
-
#
|
|
25
|
+
# spent, no further tool calls are run for that turn: the agent sends an
|
|
26
|
+
# in-band advisory message back through the model instead, and keeps
|
|
27
|
+
# sending it while the model keeps asking for tools. The budget counts
|
|
28
|
+
# tool calls, so a batch of calls is spent as a batch, and a batch that
|
|
29
|
+
# would take the turn past its budget is not run at all. By default no
|
|
30
|
+
# budget is set (`nil`), so the feature is disabled.
|
|
28
31
|
# * Tool loop execution can be configured with `concurrency :sequential`,
|
|
29
32
|
# `:thread`, `:async`, `:fiber`, `:fork`, or `:ractor`.
|
|
30
33
|
#
|
|
@@ -32,7 +35,7 @@ module LLM
|
|
|
32
35
|
# class SystemAdmin < LLM::Agent
|
|
33
36
|
# set model: "gpt-4.1-nano",
|
|
34
37
|
# instructions: "You are a Linux system admin",
|
|
35
|
-
# tools: [
|
|
38
|
+
# tools: [LLM::Tool::Exec],
|
|
36
39
|
# schema: Result
|
|
37
40
|
# end
|
|
38
41
|
#
|
|
@@ -99,7 +102,7 @@ module LLM
|
|
|
99
102
|
# set name: "admin",
|
|
100
103
|
# instructions: "You are a system administrator",
|
|
101
104
|
# model: "gpt-4.1-nano",
|
|
102
|
-
# tools: [
|
|
105
|
+
# tools: [LLM::Tool::Exec, LLM::Tool::ReadFile]
|
|
103
106
|
# end
|
|
104
107
|
#
|
|
105
108
|
# @param [Hash] properties
|
|
@@ -345,10 +348,15 @@ module LLM
|
|
|
345
348
|
##
|
|
346
349
|
# Set or get the maximum number of tool calls
|
|
347
350
|
# that are allowed in a single turn. Once the
|
|
348
|
-
# budget is spent,
|
|
349
|
-
# message that informs the
|
|
350
|
-
# its tool call budget
|
|
351
|
-
#
|
|
351
|
+
# budget is spent, no further tool calls are run:
|
|
352
|
+
# we return an in-band message that informs the
|
|
353
|
+
# model it has spent its tool call budget, and
|
|
354
|
+
# keep returning it while the model keeps asking
|
|
355
|
+
# for tools - a model will usually change course
|
|
356
|
+
# afterwards. The budget counts tool calls, so a
|
|
357
|
+
# batch of calls is spent as a batch, and a batch
|
|
358
|
+
# that would take the turn past its budget is not
|
|
359
|
+
# run at all.
|
|
352
360
|
# @note
|
|
353
361
|
# By default this feature is disabled
|
|
354
362
|
# (set to `nil`).
|
|
@@ -823,14 +831,21 @@ module LLM
|
|
|
823
831
|
stream = params[:stream] || @ctx.params[:stream]
|
|
824
832
|
params[:stream] = LLM::Stream.try(stream, extra: {concurrency:})
|
|
825
833
|
res = talk.call(apply_instructions(prompt), params)
|
|
834
|
+
spent = 0
|
|
826
835
|
while @ctx.pending_functions?
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
836
|
+
batch = @ctx.pending_functions.size
|
|
837
|
+
if max and spent + batch > max
|
|
838
|
+
##
|
|
839
|
+
# The budget is spent, so the calls are not run.
|
|
840
|
+
# They still have to be answered though: each one
|
|
841
|
+
# gets an in-band return and, just as important,
|
|
842
|
+
# that return is emitted to the stream.
|
|
843
|
+
tools = @ctx.pending_functions
|
|
844
|
+
returns = tools.map(&:budget_spent)
|
|
845
|
+
@ctx.method(:emit_tool_returns).call(tools, returns)
|
|
846
|
+
res = talk.call(returns, params)
|
|
833
847
|
else
|
|
848
|
+
spent += batch if max
|
|
834
849
|
res = talk.call(call_functions, params)
|
|
835
850
|
end
|
|
836
851
|
end
|
data/lib/llm/console/buffer.rb
CHANGED
|
@@ -16,7 +16,7 @@ class LLM::Console
|
|
|
16
16
|
# row by overwriting its contents repeatedly.
|
|
17
17
|
class Buffer
|
|
18
18
|
##
|
|
19
|
-
# @param [LLM::Console]
|
|
19
|
+
# @param [LLM::Console] repl
|
|
20
20
|
# An instance of {LLM::Console LLM::Console}.
|
|
21
21
|
# @return [LLM::Console::Buffer]
|
|
22
22
|
def initialize(repl)
|
data/lib/llm/console/command.rb
CHANGED
data/lib/llm/console/input.rb
CHANGED
data/lib/llm/console/status.rb
CHANGED
data/lib/llm/console/stream.rb
CHANGED
data/lib/llm/console/window.rb
CHANGED
data/lib/llm/function.rb
CHANGED
|
@@ -294,8 +294,8 @@ class LLM::Function
|
|
|
294
294
|
# ctx.talk "I want to run the functions"
|
|
295
295
|
# ctx.talk ctx.pending_functions.map(&:cancel)
|
|
296
296
|
# @return [LLM::Function::Return]
|
|
297
|
-
def cancel(reason: "function call cancelled")
|
|
298
|
-
Return.new(id, name,
|
|
297
|
+
def cancel(reason: "function call cancelled", **extra)
|
|
298
|
+
Return.new(id, name, extra.merge(cancelled: true, reason:))
|
|
299
299
|
ensure
|
|
300
300
|
@cancelled = true
|
|
301
301
|
end
|
|
@@ -356,12 +356,11 @@ class LLM::Function
|
|
|
356
356
|
# call budget has been spent.
|
|
357
357
|
# @return [LLM::Function::Return]
|
|
358
358
|
def budget_spent
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
})
|
|
359
|
+
cancel(
|
|
360
|
+
reason: "you are making too many tool calls",
|
|
361
|
+
action: ["stop requesting tool calls"],
|
|
362
|
+
advice: ["produce a response from tool calls already made", "temporary error that resets on the next turn"]
|
|
363
|
+
)
|
|
365
364
|
end
|
|
366
365
|
|
|
367
366
|
##
|
data/lib/llm/registry/model.rb
CHANGED
|
@@ -6,7 +6,7 @@ class LLM::Registry
|
|
|
6
6
|
# metadata (pricing, limits, capabilities, and
|
|
7
7
|
# modalities).
|
|
8
8
|
#
|
|
9
|
-
# Models are
|
|
9
|
+
# Models are `Comparable` by price: input cost first, then
|
|
10
10
|
# output cost, so `models.sort` orders them from cheapest to
|
|
11
11
|
# most expensive.
|
|
12
12
|
class Model
|
data/lib/llm/sequel/agent.rb
CHANGED
data/lib/llm/sequel/plugin.rb
CHANGED
|
@@ -320,19 +320,25 @@ module LLM::Sequel
|
|
|
320
320
|
|
|
321
321
|
##
|
|
322
322
|
# @return [LLM::Provider]
|
|
323
|
+
# @raise [NotImplementedError]
|
|
324
|
+
# when neither this model nor one of its ancestors implements the
|
|
325
|
+
# callback
|
|
323
326
|
def set_provider
|
|
327
|
+
return super if defined?(super)
|
|
324
328
|
raise NotImplementedError, "implement the set_provider callback"
|
|
325
329
|
end
|
|
326
330
|
|
|
327
331
|
##
|
|
328
332
|
# @return [Hash]
|
|
329
333
|
def set_context
|
|
334
|
+
return super if defined?(super)
|
|
330
335
|
Plugin::EMPTY_HASH.dup
|
|
331
336
|
end
|
|
332
337
|
|
|
333
338
|
##
|
|
334
339
|
# @return [LLM::Tracer]
|
|
335
340
|
def set_tracer
|
|
341
|
+
return super if defined?(super)
|
|
336
342
|
nil
|
|
337
343
|
end
|
|
338
344
|
|
data/lib/llm/tools/exec.rb
CHANGED
|
@@ -46,10 +46,8 @@ class LLM::Tool
|
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
##
|
|
49
|
-
# @param [String] name
|
|
50
|
-
# The name of a command
|
|
51
49
|
# @param [Array<String>] arguments
|
|
52
|
-
#
|
|
50
|
+
# A command and its arguments. The first element is the command name.
|
|
53
51
|
# @param [Integer] timeout
|
|
54
52
|
# The maximum allowed time for the command to run (in seconds)
|
|
55
53
|
# @param [Integer] max_bytes
|
data/lib/llm/tools/git.rb
CHANGED
|
@@ -16,8 +16,11 @@ class LLM::Tool
|
|
|
16
16
|
defaults arguments: [], timeout: 5
|
|
17
17
|
|
|
18
18
|
##
|
|
19
|
-
# @param [String]
|
|
20
|
-
#
|
|
19
|
+
# @param [Array<String>] arguments
|
|
20
|
+
# One or more git arguments. The first is the subcommand and must be
|
|
21
|
+
# one of `log`, `diff`, `commit`, `checkout`, `branch`, or `show`.
|
|
22
|
+
# @param [Integer] timeout
|
|
23
|
+
# The maximum time to allow the command to run (in seconds)
|
|
21
24
|
# @return [Hash]
|
|
22
25
|
def call(arguments: [], timeout: 5)
|
|
23
26
|
subcommand = arguments[0]
|
data/lib/llm/tools/read_file.rb
CHANGED
|
@@ -4,7 +4,7 @@ class LLM::Tool
|
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Tool::ReadFile} class implements a tool that
|
|
6
6
|
# can read the contents of a file. It returns the content
|
|
7
|
-
# as structured lines ({lineno:, content:}), so the model can
|
|
7
|
+
# as structured lines (`{lineno:, content:}`), so the model can
|
|
8
8
|
# reference line numbers when requesting a narrower range.
|
|
9
9
|
class ReadFile < self
|
|
10
10
|
require_relative "utils"
|
data/lib/llm/version.rb
CHANGED
data/llm.gemspec
CHANGED
|
@@ -37,13 +37,7 @@ DESCRIPTION
|
|
|
37
37
|
spec.post_install_message = "\n" \
|
|
38
38
|
"Got a question about llm.rb? " \
|
|
39
39
|
"\n" \
|
|
40
|
-
"
|
|
41
|
-
"\n" \
|
|
42
|
-
"It is connected to the official GitHub repository." \
|
|
43
|
-
"\n" \
|
|
44
|
-
"100% free to use." \
|
|
45
|
-
"\n" \
|
|
46
|
-
"Built with llm.rb and DeepSeek." \
|
|
40
|
+
"The https://r.uby.dev website can help." \
|
|
47
41
|
"\n\n"
|
|
48
42
|
|
|
49
43
|
spec.add_development_dependency "webmock", "~> 3.24.0"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: llm.rb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 15.2.
|
|
4
|
+
version: 15.2.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Robert Gleeson
|
|
@@ -695,9 +695,8 @@ metadata:
|
|
|
695
695
|
source_code_uri: https://github.com/r-uby-dev/llm
|
|
696
696
|
documentation_uri: https://r.uby.dev
|
|
697
697
|
changelog_uri: https://github.com/r-uby-dev/llm/blob/main/CHANGELOG.md
|
|
698
|
-
post_install_message: "\nGot a question about llm.rb? \
|
|
699
|
-
|
|
700
|
-
with llm.rb and DeepSeek.\n\n"
|
|
698
|
+
post_install_message: "\nGot a question about llm.rb? \nThe https://r.uby.dev website
|
|
699
|
+
can help.\n\n"
|
|
701
700
|
rdoc_options: []
|
|
702
701
|
require_paths:
|
|
703
702
|
- lib
|