llm.rb 15.2.0 → 15.2.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 +40 -0
- data/README.md +25 -12
- data/lib/llm/active_record/acts_as_agent.rb +1 -1
- data/lib/llm/agent.rb +30 -15
- data/lib/llm/function.rb +7 -8
- data/lib/llm/sequel/agent.rb +1 -1
- data/lib/llm/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8437f48c9a995054696648dfbe3386a5f0d8c8a62379765d2eca8694cf3adb3b
|
|
4
|
+
data.tar.gz: d2c36a69342e2fef94472abc25e813a0ee8bbef9aba15e68b275d89d307a6405
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d1d120fe448ee693ba627cfdfe213cd42030fe8448f72c8cf6ecaddb62d2ac5f7b07ded6191142ec28571b41acbdf8c96b458420889e7fb75990f53f51b3ab43
|
|
7
|
+
data.tar.gz: 816b6feffe0339f52798062de3f36188b3d6574203289f450fd07de2e1e600b29aa956a8ba50947d3ae27540a4132222f3b31bb865784f88e24074388a852058
|
data/CHANGELOG.md
CHANGED
|
@@ -17,6 +17,46 @@
|
|
|
17
17
|
|
|
18
18
|
*No unreleased changes yet. Check back after the next release.*
|
|
19
19
|
|
|
20
|
+
## v15.2.1
|
|
21
|
+
|
|
22
|
+
Changes since `v15.2.0`.
|
|
23
|
+
|
|
24
|
+
This release makes the agent count its tool budget across the whole turn
|
|
25
|
+
and emit tool returns to the stream once that budget is spent, and lets
|
|
26
|
+
`LLM::Function#cancel` carry extra return fields.
|
|
27
|
+
|
|
28
|
+
### Agent
|
|
29
|
+
|
|
30
|
+
* **agent: count the tool budget across the whole turn** <br>
|
|
31
|
+
[`LLM::Agent.tool_budget`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#tool_budget-class_method)
|
|
32
|
+
now caps the tool calls a turn runs in total. A batch of calls is
|
|
33
|
+
spent as a batch, and a batch that would take the turn past its
|
|
34
|
+
budget is not run at all; once the budget is spent no tool call runs,
|
|
35
|
+
and the agent keeps sending its in-band advisory until the model
|
|
36
|
+
answers without requesting more tools. Previously the agent ran up to
|
|
37
|
+
the budget, then ran further batches after each advisory, so a turn
|
|
38
|
+
could run more tool calls than its budget allowed.
|
|
39
|
+
|
|
40
|
+
* **agent: emit tool returns when the budget is spent** <br>
|
|
41
|
+
Fix a bug where, once a turn's tool budget was spent, the agent passed
|
|
42
|
+
its in-band returns to the model without emitting them to the stream.
|
|
43
|
+
The stream went silent, so a stream that tracked tool-call state left
|
|
44
|
+
the calls in the `call` state. The returns are now emitted through
|
|
45
|
+
[`LLM::Stream#on_tool_return`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_tool_return-instance_method)
|
|
46
|
+
like any other tool return.
|
|
47
|
+
|
|
48
|
+
### Function
|
|
49
|
+
|
|
50
|
+
* **function: `LLM::Function#cancel` takes extra return fields** <br>
|
|
51
|
+
[`LLM::Function#cancel`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#cancel-instance_method)
|
|
52
|
+
now accepts keywords beyond `reason:` and merges them into the
|
|
53
|
+
[`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
|
|
54
|
+
it builds. `LLM::Function#budget_spent` now builds its return through
|
|
55
|
+
`cancel` instead of hand-rolling an error, so a budget-spent return is
|
|
56
|
+
marked `cancelled: true` and carries `action:` and `advice:` hints. A
|
|
57
|
+
callback that receives returns can now tell a cancellation apart from
|
|
58
|
+
an ordinary error.
|
|
59
|
+
|
|
20
60
|
## v15.2.0
|
|
21
61
|
|
|
22
62
|
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
|
|
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/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/sequel/agent.rb
CHANGED
data/lib/llm/version.rb
CHANGED