llm.rb 16.0.0 → 17.0.0
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 +510 -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 +244 -269
- 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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: '058dd39edfa1c7dee3a429e32c0af2f1ad022fbbfd4c5341be2116264027a09f'
|
|
4
|
+
data.tar.gz: 460e7aca153eff0138e53ca21e7b1063294869983676562c41f31a076cd74d91
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9f9edd9b05c9825e5c9854fb113d98fd7e76bf53e992fd26ffb83e4021ac4a64902246389f2620de23901a939890ad2601366dead580036c64c37dad4ab92d62
|
|
7
|
+
data.tar.gz: 5b34de772aa2c6c1823e5bde6b2a8314ff4de120edbe1a1a64cd3be8b9c0b4f448806a8630ff79bfec39acc200e3c2a35720cd59ae95fb0aae82f0850878fc79
|
data/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,521 @@
|
|
|
1
1
|
> Changelog <br>
|
|
2
2
|
> [r.uby.dev](https://r.uby.dev) project
|
|
3
3
|
|
|
4
|
-
This file covers the
|
|
5
|
-
|
|
4
|
+
This file covers the v17 series and the previous
|
|
5
|
+
v16 series. <br> Previous releases (v15 and
|
|
6
|
+
before) can be seen in [changelog/old.md](changelog/old.md).
|
|
6
7
|
|
|
7
8
|
## What's next
|
|
8
9
|
|
|
9
10
|
*No unreleased changes yet. Check back after the next release.*
|
|
10
11
|
|
|
12
|
+
## v17.0.0
|
|
13
|
+
|
|
14
|
+
Changes since `v16.0.0`.
|
|
15
|
+
|
|
16
|
+
This release is large, and includes a number of breaking
|
|
17
|
+
changes. It covers a lot of ground. I will try to cover the
|
|
18
|
+
highlights but I won't be able to cover everything, and
|
|
19
|
+
there won't be much depth. Check out the changelog entries
|
|
20
|
+
to learn more.
|
|
21
|
+
|
|
22
|
+
The Files API provided by deepseek is now supported
|
|
23
|
+
(via [LLM::DeepSeek#files](https://r.uby.dev/api-docs/llm.rb/LLM/DeepSeek/Files.html)),
|
|
24
|
+
and there was a large focus on the cancellation and
|
|
25
|
+
interrupt story. [LLM::Tracer]((https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html)
|
|
26
|
+
saw a lot of changes and it has the most changelog
|
|
27
|
+
entries in this release.
|
|
28
|
+
|
|
29
|
+
The `LLM::Guard` class was a focus as well, and it
|
|
30
|
+
has been renamed to
|
|
31
|
+
[LLM::Intercept](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html)
|
|
32
|
+
. An intercept can block a tool call with a return
|
|
33
|
+
of its own, it can mutate tool-call parameters in
|
|
34
|
+
place, validate tool call parameters, and anything
|
|
35
|
+
else a middleman that sits between the model and a
|
|
36
|
+
tool could do.
|
|
37
|
+
|
|
38
|
+
It is now possible to combine multiple tracers _or_
|
|
39
|
+
intercepts into a single tracer, or intercept. The
|
|
40
|
+
[LLM::Tracer.multi]((https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#multi-class_method))
|
|
41
|
+
and
|
|
42
|
+
[LLM::Intercept.multi]((https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html#multi-class_method))
|
|
43
|
+
methods exist for this purpose.
|
|
44
|
+
|
|
45
|
+
For everything else, keep reading.
|
|
46
|
+
|
|
47
|
+
### Breaking
|
|
48
|
+
|
|
49
|
+
#### Migration
|
|
50
|
+
|
|
51
|
+
| Old | New |
|
|
52
|
+
|-----|-----|
|
|
53
|
+
| the default read timeout is 600 seconds | the default read timeout is 240 seconds |
|
|
54
|
+
| `LLM::Tracer#on_interrupt` and `#on_exit` do nothing | both raise `NotImplementedError`, so a tracer answers for each |
|
|
55
|
+
| `LLM::Tracer#on_tool_finish(result:, span:)` and `#on_tool_error(ex:, span:)` | both also take `started_at:` and `finished_at:` |
|
|
56
|
+
| `LLM::Tracer#on_tool_error` is given the `Exception` a tool raised | it is given the `LLM::Function::Return` that holds the error |
|
|
57
|
+
| a tracer's tool callbacks run on the thread or fiber that ran the tool | they run on the agent's thread, like the other callbacks |
|
|
58
|
+
| a cancelled `:ractor` call answers in band with `cancelled: true` | it raises `LLM::Interrupt` in the caller's thread |
|
|
59
|
+
| `LLM::Guard`, `LLM::Guard::Null` | `LLM::Intercept`, `LLM::Intercept::Null` |
|
|
60
|
+
| `guard:` and `guard_options:` on a context or agent | `intercept:` and `intercept_options:` |
|
|
61
|
+
| `LLM::Context#guard` | `LLM::Context#intercept` |
|
|
62
|
+
| a guard that returned anything truthy stopped the tool call | only a `LLM::Function::Return` stops it, and any other value lets the call run |
|
|
63
|
+
| `trace:` on `LLM::Agent#console`, a deprecated alias | it is removed, and `tracer:` is the keyword |
|
|
64
|
+
|
|
65
|
+
### Intercept
|
|
66
|
+
|
|
67
|
+
* **intercept: rename `LLM::Guard` to `LLM::Intercept`** <br>
|
|
68
|
+
The hook that sees a pending tool call before it
|
|
69
|
+
runs is an
|
|
70
|
+
[`LLM::Intercept`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html)
|
|
71
|
+
now, because it does more than guard a call:
|
|
72
|
+
`#call` receives the `LLM::Function`, so an
|
|
73
|
+
intercept can rewrite a call and let it run by
|
|
74
|
+
returning `nil`. A subclass of `LLM::Guard` is
|
|
75
|
+
renamed, the `guard:` and `guard_options:` a
|
|
76
|
+
context or agent takes are `intercept:` and
|
|
77
|
+
`intercept_options:`, and `LLM::Context#guard` is
|
|
78
|
+
`LLM::Context#intercept`. `LLM::Guard` is
|
|
79
|
+
removed, and no alias is left for it.
|
|
80
|
+
|
|
81
|
+
* **intercept: add `LLM::Intercept.multi`** <br>
|
|
82
|
+
The
|
|
83
|
+
[`LLM::Intercept.multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html#multi-class_method)
|
|
84
|
+
factory combines one or more intercepts into a
|
|
85
|
+
single
|
|
86
|
+
[`LLM::Intercept::Multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept/Multi.html)
|
|
87
|
+
that a context or agent can be given as
|
|
88
|
+
`intercept:`. Each intercept is called in the
|
|
89
|
+
order it was given, and the first that returns an
|
|
90
|
+
[`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html)
|
|
91
|
+
ends the call, so an intercept that returns `nil`
|
|
92
|
+
lets the next one decide. An `intercept:` is
|
|
93
|
+
taken as an instance now as well as a class,
|
|
94
|
+
which is what lets the factory's return value be
|
|
95
|
+
used as it is.
|
|
96
|
+
|
|
97
|
+
* **intercept: give an intercept the options it was built with** <br>
|
|
98
|
+
The `intercept_options:` a context or agent is
|
|
99
|
+
built with now reach
|
|
100
|
+
[`LLM::Intercept#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html#call-instance_method)
|
|
101
|
+
as keywords, and an intercept can read them
|
|
102
|
+
through
|
|
103
|
+
[`LLM::Intercept#options`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html#options-instance_method).
|
|
104
|
+
Before, the options were held on the context and
|
|
105
|
+
never passed to the intercept, so a subclass that
|
|
106
|
+
wanted them had no way to read them.
|
|
107
|
+
|
|
108
|
+
### Provider
|
|
109
|
+
|
|
110
|
+
* **deepseek: upload a file once and refer to it by id** <br>
|
|
111
|
+
[`LLM::DeepSeek#files`](https://r.uby.dev/api-docs/llm.rb/LLM/DeepSeek.html#files-instance_method)
|
|
112
|
+
returns an
|
|
113
|
+
[`LLM::DeepSeek::Files`](https://r.uby.dev/api-docs/llm.rb/LLM/DeepSeek/Files.html)
|
|
114
|
+
object now, so a file can be uploaded once and a
|
|
115
|
+
later chat request can name it instead of
|
|
116
|
+
resending its bytes. Before, the call raised
|
|
117
|
+
`NotImplementedError`. Uploads default to
|
|
118
|
+
`purpose: "user_data"`, the only purpose
|
|
119
|
+
DeepSeek accepts, and requests go to the root of
|
|
120
|
+
the API host, where DeepSeek serves its Files
|
|
121
|
+
API.
|
|
122
|
+
|
|
123
|
+
* **provider: add `LLM::Provider#retry_errors`** <br>
|
|
124
|
+
The
|
|
125
|
+
[`LLM::Provider#retry_errors`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html#retry_errors-instance_method)
|
|
126
|
+
method returns the exceptions that a request can
|
|
127
|
+
raise and that are retried up to the provider's
|
|
128
|
+
retry budget. A provider extends the array it
|
|
129
|
+
inherits rather than replacing it, and
|
|
130
|
+
[`LLM::DeepSeek#retry_errors`](https://r.uby.dev/api-docs/llm.rb/LLM/DeepSeek.html#retry_errors-instance_method)
|
|
131
|
+
adds `LLM::ServerError`, the error DeepSeek
|
|
132
|
+
returns when its servers are overloaded, so a
|
|
133
|
+
request that finds an overloaded server is retried
|
|
134
|
+
with a growing delay like a rate limit instead of
|
|
135
|
+
failing on the first response.
|
|
136
|
+
|
|
137
|
+
* **provider: drop the default read timeout to 240 seconds** <br>
|
|
138
|
+
The default read timeout is now 240 seconds (four
|
|
139
|
+
minutes), down from 600 seconds (ten minutes). A
|
|
140
|
+
timeout is retried, so the old default could be
|
|
141
|
+
spent on a response that never arrives, and the
|
|
142
|
+
new one fails sooner and opens a new session
|
|
143
|
+
instead. DeepSeek prompted the change: it can
|
|
144
|
+
stall past four minutes when it is under heavy
|
|
145
|
+
load. Pass `read_timeout:` or `timeout:` to a
|
|
146
|
+
provider to choose your own.
|
|
147
|
+
|
|
148
|
+
### Agent
|
|
149
|
+
|
|
150
|
+
* **agent: interrupt an agent via `LLM::Agent#id`** <br>
|
|
151
|
+
The
|
|
152
|
+
[`LLM.interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM.html#interrupt-class_method)
|
|
153
|
+
method can interrupt an agent with nothing more
|
|
154
|
+
than an ID that references it. A controller, a
|
|
155
|
+
background job, or a socket handler can use this
|
|
156
|
+
method to interrupt an agent that is running
|
|
157
|
+
in the same process but on a different thread
|
|
158
|
+
or fiber.
|
|
159
|
+
|
|
160
|
+
* **agent: add `LLM::Agent.registry`** <br>
|
|
161
|
+
The
|
|
162
|
+
[`LLM::Agent.registry`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#registry-class_method)
|
|
163
|
+
method returns an object that holds every
|
|
164
|
+
active instance of
|
|
165
|
+
[`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
166
|
+
An agent enters the registry when it starts a turn,
|
|
167
|
+
and it exits the registry after that. The registry is
|
|
168
|
+
thread-safe, local to a single process
|
|
169
|
+
and
|
|
170
|
+
[`LLM.interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM.html#interrupt-class_method)
|
|
171
|
+
uses it under the hood to find an agent by ID.
|
|
172
|
+
|
|
173
|
+
* **agent: `LLM.interrupt` can retry until it finds an agent** <br>
|
|
174
|
+
The method now takes `attempts:` and `interval:`, so
|
|
175
|
+
a caller that asks for an agent before its turn has
|
|
176
|
+
started can poll for it instead of giving up on the
|
|
177
|
+
first miss, which is the loop a caller used to write
|
|
178
|
+
by hand. `attempts` defaults to 1 and `interval` to
|
|
179
|
+
0.1 seconds, so the method is unchanged until a caller
|
|
180
|
+
asks it to wait. It returns `true` when an interrupt
|
|
181
|
+
took place, and `false` otherwise.
|
|
182
|
+
|
|
183
|
+
* **agent: set an intercept at the class level** <br>
|
|
184
|
+
[`LLM::Agent.intercept`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#intercept-class_method)
|
|
185
|
+
is where an agent's intercept is declared, so it
|
|
186
|
+
can sit beside the agent's other defaults. It
|
|
187
|
+
takes an intercept, or a block that is resolved
|
|
188
|
+
when the agent is built, and `set intercept:`
|
|
189
|
+
takes a `Symbol` that names an instance method,
|
|
190
|
+
which is how
|
|
191
|
+
[`LLM::Intercept.multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Intercept.html#multi-class_method)
|
|
192
|
+
is composed. An `intercept:` given to
|
|
193
|
+
[`LLM::Agent.new`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#initialize-instance_method)
|
|
194
|
+
is resolved the same way, and an ActiveRecord or
|
|
195
|
+
Sequel model that wraps an agent carries it into
|
|
196
|
+
the context it builds.
|
|
197
|
+
|
|
198
|
+
* **agent: remove the deprecated `trace:` keyword from `LLM::Agent#console`** <br>
|
|
199
|
+
The `trace:` keyword is gone from
|
|
200
|
+
[`LLM::Agent#console`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#console-instance_method)
|
|
201
|
+
and from its `repl` alias. It was renamed to
|
|
202
|
+
`tracer:` in an earlier release, and it was kept
|
|
203
|
+
for a caller that still passed it, with a warning
|
|
204
|
+
each time. The keyword is `tracer:`, so
|
|
205
|
+
`trace: true` is `tracer: true`.
|
|
206
|
+
|
|
207
|
+
### Function
|
|
208
|
+
|
|
209
|
+
* **function: `:ractor` raises `LLM::Interrupt` (like everyone else)** <br>
|
|
210
|
+
An
|
|
211
|
+
[`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
|
|
212
|
+
that is delivered to a tool running on a ractor
|
|
213
|
+
will travel back to the caller and raise itself
|
|
214
|
+
on the caller's thread, the same as all other
|
|
215
|
+
concurrency strategies. This only happens after
|
|
216
|
+
all tools have been interrupted themselves, and
|
|
217
|
+
it is the last step in an interrupt. The caller
|
|
218
|
+
is expected to rescue `LLM::Interrupt` and handle
|
|
219
|
+
the interrupt from there.
|
|
220
|
+
|
|
221
|
+
* **function: add `LLM::Function::Task#intercepted?`** <br>
|
|
222
|
+
An intercept stops a tool call when it returns
|
|
223
|
+
an
|
|
224
|
+
[`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html),
|
|
225
|
+
because that Return is the answer the model is
|
|
226
|
+
given for the call. Any other value lets the call
|
|
227
|
+
through, where only `nil` did that before, so a
|
|
228
|
+
guard that returned anything else stopped a tool
|
|
229
|
+
call and answered the model with it.
|
|
230
|
+
[`LLM::Function::Task#intercepted?`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#intercepted?-instance_method)
|
|
231
|
+
is the predicate a task asks, and it is true only
|
|
232
|
+
for a Return.
|
|
233
|
+
|
|
234
|
+
### Tracer
|
|
235
|
+
|
|
236
|
+
* **tracer: add `LLM::Tracer::Multi`** <br>
|
|
237
|
+
The
|
|
238
|
+
[`LLM::Tracer::Multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Multi.html)
|
|
239
|
+
tracer fans every tracing callback out to a set of
|
|
240
|
+
tracers, so a request can be logged to a file and
|
|
241
|
+
exported to OpenTelemetry at the same time instead
|
|
242
|
+
of choosing between them. Its `on_request_start`
|
|
243
|
+
and `on_tool_start` return an array with one span
|
|
244
|
+
per tracer, and that array is passed back as
|
|
245
|
+
`span:` to the matching finish, error, or interrupt
|
|
246
|
+
callback, so each tracer is handed the span it
|
|
247
|
+
returned. A tracer that raises is reported rather
|
|
248
|
+
than taking the request down with it, and
|
|
249
|
+
`LLM::Interrupt` still travels up the stack.
|
|
250
|
+
|
|
251
|
+
* **tracer: add `LLM::Tracer.multi`** <br>
|
|
252
|
+
Add
|
|
253
|
+
[`LLM::Tracer.multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#multi-class_method)
|
|
254
|
+
as a factory for `LLM::Tracer::Multi`. The
|
|
255
|
+
provider comes first and is required, so a
|
|
256
|
+
tracer given as a class is built with it,
|
|
257
|
+
while a tracer given as an instance is used
|
|
258
|
+
as it is. Builtin tracers and a custom one
|
|
259
|
+
can be mixed in a single call.
|
|
260
|
+
|
|
261
|
+
* **tracer: build every tracer given to `LLM::Tracer.multi`** <br>
|
|
262
|
+
Fix a bug where
|
|
263
|
+
[`LLM::Tracer.multi`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#multi-class_method)
|
|
264
|
+
passed its tracers to `LLM::Tracer::Multi.new` as
|
|
265
|
+
a single array rather than one argument each,
|
|
266
|
+
so a `Multi` held that array as its only
|
|
267
|
+
tracer and every callback raised `NoMethodError`
|
|
268
|
+
on it.
|
|
269
|
+
[`LLM::Tracer::Rescue`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Rescue.html)
|
|
270
|
+
reported the crash to standard error and
|
|
271
|
+
swallowed it, so the request was not traced.
|
|
272
|
+
|
|
273
|
+
* **tracer: alias `LLM::Tracer.pretty_logger` as `LLM::Tracer.pretty`** <br>
|
|
274
|
+
Add
|
|
275
|
+
[`LLM::Tracer.pretty`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#pretty_logger-class_method)
|
|
276
|
+
as an alias for `LLM::Tracer.pretty_logger`,
|
|
277
|
+
so the shorter name builds the same tracer
|
|
278
|
+
with the same options. The README uses
|
|
279
|
+
`pretty` throughout.
|
|
280
|
+
|
|
281
|
+
* **tracer: protect an agent from a tracer crash** <br>
|
|
282
|
+
The
|
|
283
|
+
[`LLM::Tracer::Rescue`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Rescue.html)
|
|
284
|
+
module prepends itself on every subclass of
|
|
285
|
+
[`LLM::Tracer`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html)
|
|
286
|
+
and rescues uncaught exceptions that occur
|
|
287
|
+
in tracer callback. Any callback that lets
|
|
288
|
+
an exception exit its method will be rescued
|
|
289
|
+
and the exception dumped onto standard error.
|
|
290
|
+
|
|
291
|
+
* **tracer: run every `on_tool_*` callback on the agent's thread** <br>
|
|
292
|
+
The
|
|
293
|
+
[`LLM::Tracer#on_tool_start`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_start-instance_method),
|
|
294
|
+
`#on_tool_finish`, `#on_tool_error`, and
|
|
295
|
+
`#on_tool_interrupt` callbacks are now
|
|
296
|
+
called on the same thread as the agent
|
|
297
|
+
they belong to - and this is true
|
|
298
|
+
across all strategies. The previous
|
|
299
|
+
implementation could call the tracer
|
|
300
|
+
across threads and fibers when given
|
|
301
|
+
the `thread`, `async`, and `fiber`
|
|
302
|
+
strategies.
|
|
303
|
+
|
|
304
|
+
Both `ractor`, and `fork` already kept the
|
|
305
|
+
tracer on one thread, and now all strategies
|
|
306
|
+
do. The last exception to the rule was
|
|
307
|
+
`LLM::Tracer#on_interrupt`, which could be
|
|
308
|
+
called on the thread that asked for the
|
|
309
|
+
interrupt rather than the thread that
|
|
310
|
+
received the interrupt.
|
|
311
|
+
|
|
312
|
+
* **tracer: run `LLM::Tracer#on_interrupt` on the agent's thread** <br>
|
|
313
|
+
The
|
|
314
|
+
[`LLM::Tracer#on_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_interrupt-instance_method)
|
|
315
|
+
callback is now called on the agent's thread,
|
|
316
|
+
or fiber, the same as every other callback.
|
|
317
|
+
Before, it was called by whoever asked for the
|
|
318
|
+
interrupt, so a tracer could be called across
|
|
319
|
+
threads for an interrupt alone. The raise now
|
|
320
|
+
unwinds to the turn it stopped, the tracer is
|
|
321
|
+
told `scope: :agent` there, and the interrupt
|
|
322
|
+
is re-raised for the caller.
|
|
323
|
+
|
|
324
|
+
* **tracer: `on_interrupt` and `on_exit` raise `NotImplementedError`** <br>
|
|
325
|
+
The
|
|
326
|
+
[`LLM::Tracer#on_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_interrupt-instance_method)
|
|
327
|
+
and
|
|
328
|
+
[`LLM::Tracer#on_exit`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_exit-instance_method)
|
|
329
|
+
methods now raise `NotImplementedError`
|
|
330
|
+
by default and subclasses of
|
|
331
|
+
[`LLM::Tracer`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html)
|
|
332
|
+
must implement them, otherwise
|
|
333
|
+
the tracer will crash and the crash
|
|
334
|
+
will be logged to standard error
|
|
335
|
+
(`$stderr`). A tracer crash does
|
|
336
|
+
not crash an agent, and both of
|
|
337
|
+
these methods can be implemented
|
|
338
|
+
as noops that return `nil`.
|
|
339
|
+
|
|
340
|
+
* **tracer: `on_tool_{finish,interrupt,error}` receive `started_at` / `finished_at`** <br>
|
|
341
|
+
The
|
|
342
|
+
[`LLM::Tracer#on_tool_finish`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_finish-instance_method),
|
|
343
|
+
[`LLM::Tracer#on_tool_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_interrupt-instance_method)
|
|
344
|
+
and
|
|
345
|
+
[`LLM::Tracer#on_tool_error`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_error-instance_method)
|
|
346
|
+
methods now receive `started_at`
|
|
347
|
+
and `finished_at` attributes that can
|
|
348
|
+
determine how long a tool took to run.
|
|
349
|
+
Both are based on a monotonic clock
|
|
350
|
+
(`Process::CLOCK_MONOTONIC`) and
|
|
351
|
+
neither are `Time` objects.
|
|
352
|
+
|
|
353
|
+
* **tracer: add `LLM::Tracer#on_tool_interrupt`** <br>
|
|
354
|
+
The [`LLM::Tracer#on_tool_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_interrupt-instance_method)
|
|
355
|
+
method allows a tracer to close a span that
|
|
356
|
+
was opened by
|
|
357
|
+
[`LLM::Tracer#on_tool_start`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_tool_start-instance_method)
|
|
358
|
+
and it is called for every tool that
|
|
359
|
+
was interrupted. The callback receives
|
|
360
|
+
the tool name and its tool call ID plus
|
|
361
|
+
the span that was opened by `on_tool_start`.
|
|
362
|
+
|
|
363
|
+
* **tracer: return a span from `LLM::Tracer::Logger#on_tool_start`** <br>
|
|
364
|
+
The
|
|
365
|
+
[`LLM::Tracer::Logger#on_tool_start`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Logger.html#on_tool_start-instance_method)
|
|
366
|
+
method now returns a span that can be
|
|
367
|
+
matched by another callback method that
|
|
368
|
+
will close the span (eg `on_tool_start -> on_tool_finish`).
|
|
369
|
+
|
|
370
|
+
* **tracer: return a span from `LLM::Tracer::PrettyLogger#on_tool_start`** <br>
|
|
371
|
+
The
|
|
372
|
+
[`LLM::Tracer::PrettyLogger#on_tool_start`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html#on_tool_start-instance_method)
|
|
373
|
+
method now returns a span that can be
|
|
374
|
+
matched by another callback method that
|
|
375
|
+
will close the span (eg `on_tool_start -> on_tool_finish`).
|
|
376
|
+
|
|
377
|
+
* **tracer: add `LLM::Tracer::Logger#on_tool_interrupt`** <br>
|
|
378
|
+
This method implements the new callback available on the
|
|
379
|
+
superclass of Logger. The callback emits an `tool.interrupt`
|
|
380
|
+
event and carries other metadata with it.
|
|
381
|
+
|
|
382
|
+
* **tracer: add `LLM::Tracer::PrettyLogger#on_tool_interrupt`** <br>
|
|
383
|
+
This method implements the new callback available on the
|
|
384
|
+
superclass of PrettyLogger. The callback emits a log entry
|
|
385
|
+
that captures the tool interrupt via an IO object.
|
|
386
|
+
|
|
387
|
+
* **tracer: add `LLM::Tracer::Telemetry#on_tool_interrupt`** <br>
|
|
388
|
+
This method implements the new callback available on
|
|
389
|
+
the superclass of Telemetry. The callback records
|
|
390
|
+
`error.type` as `LLM::Interrupt`, marks the span as
|
|
391
|
+
an error, adds the standard `gen_ai.tool.finish`
|
|
392
|
+
event and closes the span that was opened by
|
|
393
|
+
`on_tool_start`. It follows the shape of
|
|
394
|
+
`on_tool_error` and `on_interrupt` instead of
|
|
395
|
+
emitting a `gen_ai.tool.interrupt` event, which is
|
|
396
|
+
not part of the OpenTelemetry standard.
|
|
397
|
+
|
|
398
|
+
* **tracer: `LLM::Tracer#on_interrupt` raises `NotImplementedError` by default** <br>
|
|
399
|
+
The
|
|
400
|
+
[`LLM::Tracer#on_interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_interrupt-instance_method)
|
|
401
|
+
method is no longer a noop that returns nil. All tracers
|
|
402
|
+
that subclass `LLM::Tracer` must implement this method. The
|
|
403
|
+
change makes `on_interrupt` consistent with all other
|
|
404
|
+
callback methods that are available on [LLM::Tracer](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html).
|
|
405
|
+
|
|
406
|
+
* **tracer: add `LLM::Tracer::Logger#on_interrupt`** <br>
|
|
407
|
+
This method implements the callback available on the
|
|
408
|
+
superclass of Logger. The callback emits an `info` event
|
|
409
|
+
that is named from the scope (eg `request.interrupt`)
|
|
410
|
+
and carries the scope and the request ID with it.
|
|
411
|
+
|
|
412
|
+
* **tracer: add `LLM::Tracer::PrettyLogger#on_interrupt`** <br>
|
|
413
|
+
This method implements the callback available on the
|
|
414
|
+
superclass of PrettyLogger. The callback writes a line
|
|
415
|
+
to its IO object that names the provider and the scope
|
|
416
|
+
the interrupt was received in.
|
|
417
|
+
|
|
418
|
+
* **tracer: add `LLM::Tracer::Telemetry#on_interrupt`** <br>
|
|
419
|
+
This method implements the callback available on the
|
|
420
|
+
superclass of Telemetry. A `:request` interrupt closes
|
|
421
|
+
the span that `on_request_start` opened and marks it
|
|
422
|
+
with an error and a `gen_ai.request.finish` event, since
|
|
423
|
+
a request is the only interrupted scope with a span left
|
|
424
|
+
open. The `:tool` and `:agent` scopes do nothing, because
|
|
425
|
+
tools are closed by `on_tool_interrupt`, and any other
|
|
426
|
+
scope raises `LLM::Error`.
|
|
427
|
+
|
|
428
|
+
* **tracer: `LLM::Tracer#on_exit` raises `NotImplementedError` by default** <br>
|
|
429
|
+
The
|
|
430
|
+
[`LLM::Tracer#on_exit`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#on_exit-instance_method)
|
|
431
|
+
method is no longer a noop that returns nil. All tracers
|
|
432
|
+
that subclass `LLM::Tracer` must implement this method. The
|
|
433
|
+
change makes `on_exit` consistent with all other
|
|
434
|
+
callback methods that are available on [LLM::Tracer](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html).
|
|
435
|
+
|
|
436
|
+
### LLM::Context
|
|
437
|
+
|
|
438
|
+
* **context: inherit database ID** <br>
|
|
439
|
+
The
|
|
440
|
+
[`LLM::Context#id`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#id-instance_method)
|
|
441
|
+
and
|
|
442
|
+
[`LLM::Agent#id`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#id-instance_method)
|
|
443
|
+
methods will inherit the database ID
|
|
444
|
+
of the record that is associated with
|
|
445
|
+
them __if__ a record is in scope and
|
|
446
|
+
__if__ the database ID is in the UUIDv7
|
|
447
|
+
format. Otherwise a random UUIDv7 is
|
|
448
|
+
generated as the ID. A record is normally
|
|
449
|
+
in scope when given an ActiveRecord or
|
|
450
|
+
Sequel model has wrapped an instance
|
|
451
|
+
of
|
|
452
|
+
[LLM::Context](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
|
|
453
|
+
/
|
|
454
|
+
[LLM::Agent](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
455
|
+
|
|
456
|
+
* **context: an ID must be in the UUIDv7 format** <br>
|
|
457
|
+
An agent and context ID must __always__ carry a
|
|
458
|
+
UUIDv7 format, even when given an explicit `id`
|
|
459
|
+
as a parameter, and anything else raises
|
|
460
|
+
`LLM::Error`. This is because the ID is used
|
|
461
|
+
internally to compute other attributes
|
|
462
|
+
(such as `LLM::Agent#created_at`). The runtime
|
|
463
|
+
checks it with
|
|
464
|
+
[`LLM::Utils.uuidv7?`](https://r.uby.dev/api-docs/llm.rb/LLM/Utils.html#uuidv7?-instance_method),
|
|
465
|
+
the same predicate `LLM::Utils.timestamp` reads
|
|
466
|
+
a time through, so an id this predicate accepts
|
|
467
|
+
is an id `created_at` can answer for.
|
|
468
|
+
|
|
469
|
+
### Registry
|
|
470
|
+
|
|
471
|
+
* **refresh model metadata** <br>
|
|
472
|
+
Update `data/` with current listings, limits,
|
|
473
|
+
and pricing. Anthropic adds Claude Haiku 5.5,
|
|
474
|
+
Bedrock adds Claude Haiku 5.5, Claude Sonnet
|
|
475
|
+
5.5 in the US and EU, Claude Sonnet 5 and
|
|
476
|
+
Claude Opus 5 in India, and GLM-5.3 in the US
|
|
477
|
+
and global regions, Mistral adds the Ministral
|
|
478
|
+
3 3B, 8B, and 14B models, Mistral Large 4,
|
|
479
|
+
Codestral 25.08, Voxtral Small, GLM-5.2, and
|
|
480
|
+
Leanstral 1.5, DeepInfra adds
|
|
481
|
+
`Qwen/Qwen3-VL-30B-A3B-Instruct`, and
|
|
482
|
+
OpenRouter adds six models. OpenRouter drops
|
|
483
|
+
sixteen entries, twelve of them Qwen, and
|
|
484
|
+
Alibaba, Mistral, Moonshot, OpenAI, and xAI
|
|
485
|
+
correct limits and prices. Google adds Gemini 4
|
|
486
|
+
Argon, a reasoning model with a 1M-token context,
|
|
487
|
+
DeepInfra adds Claude Fable 5 and Mistral Small 3
|
|
488
|
+
and deprecates `Qwen/Qwen3.8-2.4T-A95B`, and
|
|
489
|
+
OpenRouter adds `liquid/lfm-2.5-24b-a2b`,
|
|
490
|
+
`google/gemini-4-argon`, the Scaledown extract
|
|
491
|
+
and summarize models, and the Pareto models,
|
|
492
|
+
drops `unbiased/pareto` and three Ling 3.0 Flash
|
|
493
|
+
and Apodex entries, and reprices several DeepSeek,
|
|
494
|
+
Z.ai, Tencent, Moonshot, Meta, and Gemma models.
|
|
495
|
+
|
|
496
|
+
### Fix
|
|
497
|
+
|
|
498
|
+
* **tools: require `test-cmd.rb` `~> 2.7.3`** <br>
|
|
499
|
+
The exec-backed tools now need `test-cmd.rb`
|
|
500
|
+
2.7.3 or later, up from 2.7.1. Version 2.7.2
|
|
501
|
+
fixes a race where `Process.wait` could claim a
|
|
502
|
+
process another thread had spawned, so a tool
|
|
503
|
+
could answer with a status and a pid that
|
|
504
|
+
belonged to another command, and 2.7.3 stops
|
|
505
|
+
looking for `/bin/false` when a command is not
|
|
506
|
+
found, which Windows does not have.
|
|
507
|
+
|
|
508
|
+
* **tools: name the tool that was not found** <br>
|
|
509
|
+
The in-band error returned when a model asks for
|
|
510
|
+
a tool that does not exist names it now, as
|
|
511
|
+
`tool 'exec' not found`, where it said only
|
|
512
|
+
`tool not found`. That is the same message
|
|
513
|
+
[`LLM::Tool.find_by_name!`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#find_by_name!-class_method)
|
|
514
|
+
raises when it cannot resolve a name, so the two
|
|
515
|
+
paths read alike: the class raises
|
|
516
|
+
[`LLM::NoSuchToolError`](https://r.uby.dev/api-docs/llm.rb/LLM/NoSuchToolError.html)
|
|
517
|
+
rather than the `no such tool "exec"` it used to.
|
|
518
|
+
|
|
11
519
|
## v16.0.0
|
|
12
520
|
|
|
13
521
|
Changes since `v15.5.0`.
|