llm.rb 12.4.0 → 12.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cd29af290d188d501b82ef31aae35258d7b6987b14f51bdce68dfbcc3bdca671
4
- data.tar.gz: 4def59348a5f33bd66248e654f75de05adc7bd8d188686c9464bcfca12a61dcb
3
+ metadata.gz: 5eec18b65456b95a668fd5552fbd1c73464e26c55d07cc980efe5973e82fd548
4
+ data.tar.gz: 4ab12f000f12f64e65afbca3ac4b08d79751f6d860c7cc9cab685c0e688d20c7
5
5
  SHA512:
6
- metadata.gz: da29e5f492f1130249d5db09ce098aff34d6805d16ed1d8e6df120a3038c4e013dc4dc95a16fcf39dd68f5eacc813715a41c1de7b61befa28206914a26c1aa32
7
- data.tar.gz: 310c5dab18e187fd8aabd66c363e192a0b556b7f26efd2d7c553416b60782921da94feec04b834549b023ac5f3064c74e411aa32e5981782b237aabe9938d1fb
6
+ metadata.gz: 86a7207e852129c3e22a747e74c8f5bcf12666886786104a38b01d2d80cc8cf82a008678fc7c5817203e138b6929aeeaba090af87f95fbea4185e7b733bbc8ff
7
+ data.tar.gz: 40c2df5d31af69ff01c666259c2f39c54f545a9bb1115e4419266c2ce3a467467236ac3f2c9d39154d3242ebf9d12cb1541dad3c21f1b60290c1a816ac09003e
data/CHANGELOG.md CHANGED
@@ -15,8 +15,263 @@
15
15
 
16
16
  ## What's next
17
17
 
18
+ ## v12.5.1
19
+
20
+ Changes since `v12.5.0`.
21
+
22
+ This release reverts the global `LLM::Function` registry fallback for tool
23
+ resolution that was added in v12.5.0.
24
+
25
+ ### Change
26
+
27
+ * **function: remove the global registry fallback for tool resolution** <br>
28
+ Remove the `LLM::Function.find_by_name` fallback that was added in
29
+ v12.5.0 as an intermediate step between available-tools lookup and
30
+ raising `LLM::NoSuchToolError`. Tool calls not found in the available
31
+ tools list now go directly to `function_missing` (which raises
32
+ `LLM::NoSuchToolError`) without first checking the global
33
+ `LLM::Function` registry.
34
+
35
+ ## v12.5.0
36
+
18
37
  Changes since `v12.4.0`.
19
38
 
39
+ This release extends the REPL command system with typed parameters, a
40
+ built-in `/help` command, command aliases (`/quit`), and cancellation
41
+ via the 'Esc' key.
42
+
43
+ The default HTTP timeout is increased to 15 minutes (900s) to better
44
+ accommodate reasoning models and large structured outputs.
45
+ `LLM::Agent#deserialize` and `LLM::Agent#restore` now return `self` for
46
+ method chaining, and `LLM::Buffer#pop` is added for tail-end message
47
+ removal.
48
+
49
+ Tool resolution gains a fallback to the global `LLM::Function` registry
50
+ before raising `LLM::NoSuchToolError`, and `pending_functions` aliases
51
+ are added on both contexts and agents for a consistent interface.
52
+
53
+ Several REPL bugs are fixed including parameter state leakage across
54
+ turns and invalid tool-call error routing.
55
+
56
+ Model metadata is refreshed across all providers with new Anthropic,
57
+ OpenAI, Google, DeepInfra, DeepSeek, and xAI model entries.
58
+
59
+ ### Add
60
+
61
+ #### Buffer & function internals
62
+
63
+ * **buffer: add `LLM::Buffer#pop`** <br>
64
+ Add `LLM::Buffer#pop` for removing the last message from the tail
65
+ of the buffer, complementing the existing `#<<` and array-style
66
+ message management.
67
+
68
+ * **function: add registry fallback for tool resolution** <br>
69
+ When resolving tool calls from a message, if the tool is not found
70
+ in the available tools list, it now also looks up the global
71
+ `LLM::Function` registry via `LLM::Function.find_by_name` before
72
+ creating a placeholder function. This improves tool resolution for
73
+ tools that are registered globally but not passed directly through
74
+ the request tool set.
75
+
76
+ #### Consistent `pending_functions` aliases
77
+
78
+ * **context: alias `LLM::Context#functions` as `LLM::Context#pending_functions`** <br>
79
+ Add `LLM::Context#pending_functions` as an alias for `LLM::Context#functions`,
80
+ so callers that prefer the more descriptive `pending_functions` name can use
81
+ it instead of `functions` when checking for unresolved tool work.
82
+
83
+ * **agent: alias `LLM::Agent#functions` as `LLM::Agent#pending_functions`** <br>
84
+ Add `LLM::Agent#pending_functions` as an alias for `LLM::Agent#functions`,
85
+ matching the same alias on `LLM::Context`, so callers have a consistent
86
+ `pending_functions` interface across both contexts and agents.
87
+
88
+ #### REPL command system
89
+
90
+ * **repl: extend command system with parameter support** <br>
91
+ Commands can now declare typed parameters using the `parameter`
92
+ DSL, modelled after `LLM::Tool` and `LLM::Schema` conventions.
93
+ Parameters can be marked as required with `required %i[...]`,
94
+ and values are type-checked before being passed to `call`.
95
+ Argument parsing is handled by the repl: arguments are split
96
+ from the input string and assigned to parameters by position.
97
+
98
+ ```ruby
99
+ class Greeter < LLM::Command
100
+ name "greet"
101
+ description "Greets the given name"
102
+ parameter :name, String, "The person's name"
103
+ required %i[name]
104
+
105
+ def call(name:)
106
+ write("Welcome #{name}!\n")
107
+ end
108
+ end
109
+ ```
110
+
111
+ * **repl: add `help` command** <br>
112
+ Add `LLM::Repl::Help` as a new built-in command, registered
113
+ automatically via the command registry. Typing `/help` shows
114
+ the `help` command's own name, description, and parameters,
115
+ while `/help <name>` shows details for a specific command,
116
+ including its parameters and whether each is required or
117
+ optional. Unknown command names produce an error message.
118
+
119
+ ```ruby
120
+ class Help < Command
121
+ name "help"
122
+ description "show help for a given command"
123
+ parameter :name, String, "The name of a command"
124
+
125
+ def call(name: nil)
126
+ if name.nil?
127
+ write("\n#{self.class.help}\n\n")
128
+ elsif command = LLM::Command.find_by(name:)
129
+ write("\n#{command.help}\n\n")
130
+ else
131
+ write "\nNo help for #{name} was found" \
132
+ "\nThat command doesn't exist.\n\n"
133
+ end
134
+ end
135
+ end
136
+ ```
137
+
138
+ * **repl: add support for command aliases** <br>
139
+ Commands can now be aliased by creating a subclass of another
140
+ command (with `LLM::Command` as an indirect ancestor). The
141
+ first alias introduced is `/quit` as an alias of `/exit`.
142
+
143
+ ```ruby
144
+ class Quit < Command::Exit
145
+ name "quit"
146
+ end
147
+ ```
148
+
149
+ * **repl: add `Command::Parameter#optional?`** <br>
150
+ Parameters now expose an `#optional?` method that returns `true`
151
+ when a parameter has not been marked as required, making it
152
+ possible to query parameter optionality programmatically.
153
+
154
+ * **repl: add `LLM::Repl::Command#write`** <br>
155
+ Commands can now write output to the transcript via the `write`
156
+ method. Commands also receive a reference to the active repl
157
+ through their `#initialize` method, making it possible to
158
+ interact with the repl window from within a command.
159
+
160
+ * **repl: display command errors in the curses UI** <br>
161
+ Commands invoked with too few arguments now display an error
162
+ message — `command(<name>): too few arguments` — directly in
163
+ the curses transcript area, giving immediate feedback instead
164
+ of silently failing.
165
+
166
+ * **repl: add `LLM::Command` convenience constant** <br>
167
+ Add `LLM::Command = LLM::Repl::Command` as a shorter alias,
168
+ available once `"llm/repl"` is required.
169
+
170
+ #### Misc
171
+
172
+ * **repl: implement cancellation with the 'Esc' key** <br>
173
+ The curses-based REPL now supports cancelling an active model
174
+ request by pressing the 'Esc' key. When a request is in progress,
175
+ the status line shows `thinking • Esc to cancel`, and pressing
176
+ Esc calls `LLM::Agent#cancel!` to interrupt the request. The
177
+ transcript displays `request cancelled!` to confirm the
178
+ cancellation.
179
+
180
+ ### Change
181
+
182
+ #### Misc
183
+
184
+ * **provider: increase default timeout to 900s** <br>
185
+ The default HTTP timeout for all providers has been increased from
186
+ 180 to 900 seconds (15 minutes) to better accommodate long-running
187
+ requests such as reasoning models and large structured outputs.
188
+
189
+ * **agent: `deserialize` and `restore` return `self`** <br>
190
+ `LLM::Agent#deserialize` and `LLM::Agent#restore` now return `self`
191
+ (the agent instance) instead of forwarding the context's return
192
+ value, enabling method chaining after restoring agent state.
193
+
194
+ * **context: discard all messages from a cancelled turn** <br>
195
+ When `LLM::Context#cancel!` is called, all messages added during
196
+ that turn are now discarded via `Buffer#slice!`, preventing edge
197
+ cases where dangling tool calls between turns caused repeated
198
+ cancellation loops. The `#repair!` method now handles tool call
199
+ cancellations on the next turn instead of mutating the conversation
200
+ buffer directly at cancellation time.
201
+
202
+ * **stream: drop the `error` argument from `on_tool_call`** <br>
203
+ The `on_tool_call` callback no longer accepts an `error` argument.
204
+ Previously, stream parsers passed both a tool and an optional error,
205
+ requiring boilerplate like `if error; queue << error; end` in every
206
+ callback. Error handling is now pushed directly onto the stream queue
207
+ inside each provider's stream parser, so `on_tool_call(tool)` is the
208
+ only signature. The REPL stream and base `LLM::Stream` class have
209
+ been updated accordingly.
210
+
211
+ #### REPL internals
212
+
213
+ * **repl: pass the repl instance to command constructors** <br>
214
+ `LLM::Repl::Command` subclasses now receive the active repl
215
+ instance via `initialize(repl)`, enabling commands to write
216
+ to the transcript and interact with the repl window.
217
+
218
+ * **repl: `Command#write` prefixes messages with the command name** <br>
219
+ The `#write` method now prefixes output with `command(<name>): `
220
+ so command messages are consistent with the `user:` and `agent:`
221
+ labels in the transcript. The prefix can be customised with the
222
+ `who:` keyword argument, or set to `who: nil` to disable it
223
+ entirely.
224
+
225
+ ### Fix
226
+
227
+ #### Misc
228
+
229
+ * **function: avoid silent skip of tools not found in available tools** <br>
230
+ When a model calls a tool that is not present in the available tools
231
+ list, instead of silently skipping the tool call (via `next`), a
232
+ `LLM::NoSuchToolError` is now raised so the model receives feedback
233
+ about the invalid tool call and can correct course.
234
+ <br><br>
235
+ An additional fallback to the global `LLM::Function` registry is
236
+ tried before raising, so globally registered tools are still
237
+ resolved even when not in the per-request tool set.
238
+
239
+ #### REPL bugs
240
+
241
+ * **repl: don't persist parameter state between turns** <br>
242
+ Parameter state (such as `Parameter#value`) was leaking across
243
+ turns because the same parameter objects were being mutated
244
+ in place. A duplicate set of parameters is now created for each
245
+ turn, keeping the original parameter definitions intact and
246
+ preventing stale state from carrying over.
247
+
248
+ * **repl: reply with error when given an invalid tool** <br>
249
+ When the model tries to call a tool that does not exist, the
250
+ error is now pushed onto the stream queue so the model can
251
+ see the error and correct course, instead of silently dropping
252
+ the invalid tool call and leaving it to `Context#repair` to
253
+ remove it from history.
254
+
255
+ * **repl: fix save of initial runtime state** <br>
256
+ Fix a bug in `LLM::Repl#configure` where a non-existent path
257
+ argument was treated as no path at all, preventing the initial
258
+ runtime state from being saved after the first turn. The correct
259
+ behavior is to create the file so it can be written to after
260
+ the first turn completes.
261
+
262
+ ### Refresh
263
+
264
+ * **Refresh model metadata across all providers** <br>
265
+ Update model listings, pricing, capabilities, reasoning options,
266
+ modality support, context limits, and release dates across all
267
+ provider registries (Anthropic, AWS Bedrock, DeepInfra, DeepSeek,
268
+ Google, Mistral, OpenAI, xAI, and ZAI). Notable changes include
269
+ Anthropic claude-opus-4-8 and claude-sonnet-4-6 additions with
270
+ effort-based reasoning, OpenAI gpt-5.6-sol/terra/luna and
271
+ gpt-5-codex additions, Google gemini-3-pro-preview and
272
+ gemini-3-flash-preview additions, DeepInfra Qwen3.5 and DeepSeek
273
+ V4 model additions, and updated xAI Grok model entries.
274
+
20
275
  ## v12.4.0
21
276
 
22
277
  Changes since `v12.3.1`.
data/README.md CHANGED
@@ -141,6 +141,8 @@ instance. The session inherits the agent's model, tools,
141
141
  skills, and instructions.
142
142
 
143
143
  ```ruby
144
+ require "llm"
145
+
144
146
  llm = LLM.deepseek(key: ENV["KEY"])
145
147
  agent = LLM::Agent.new(llm)
146
148
  agent.repl
@@ -153,6 +155,8 @@ is read from and written to. This lets you resume a
153
155
  conversation across REPL sessions.
154
156
 
155
157
  ```ruby
158
+ require "llm"
159
+
156
160
  llm = LLM.deepseek(key: ENV["KEY"])
157
161
  agent = LLM::Agent.new(llm)
158
162
  agent.repl(path: "session.json")
@@ -169,6 +173,8 @@ llm.rb. They power the agents that can be found in the
169
173
  [agents/](agents/) directory.
170
174
 
171
175
  ```ruby
176
+ require "llm"
177
+
172
178
  llm = LLM.deepseek(key: ENV["KEY"])
173
179
  agent = LLM::Agent.new(llm)
174
180
  agent.repl(tools: [Debugger])
@@ -179,7 +185,7 @@ with all of the builtin tools available.
179
185
 
180
186
  ```ruby
181
187
  require "llm"
182
- require "lll/tools"
188
+ require "llm/tools"
183
189
 
184
190
  llm = LLM.deepseek(key: ENV["KEY"])
185
191
  agent = LLM::Agent.new(llm)
@@ -192,6 +198,8 @@ The `skills` option lets you load extra skill directories
192
198
  without attaching them to an agent permanently.
193
199
 
194
200
  ```ruby
201
+ require "llm"
202
+
195
203
  llm = LLM.deepseek(key: ENV["KEY"])
196
204
  agent = LLM::Agent.new(llm)
197
205
  agent.repl(skills: [__dir__])
@@ -205,11 +213,39 @@ the tracer associated with an instance of
205
213
  [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
206
214
 
207
215
  ```ruby
208
- llm = LLM.deepseek(key: ENV["KEY"])
209
- agent = LLM::Agent.new(llm, tracer: LLM.logger(llm, path: "agent.log"))
216
+ require "llm"
217
+
218
+ llm = LLM.deepseek(key: ENV["KEY"])
219
+ tracer = LLM.logger(llm, path: "agent.log")
220
+ agent = LLM::Agent.new(llm, tracer:)
210
221
  agent.repl(tracer: true, tools: [Debugger])
211
222
  ```
212
223
 
224
+ ##### REPL: Commands
225
+
226
+ Commands are recognized by a `/` prefix and are backed by the
227
+ [`LLM::Repl::Command`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html)
228
+ class, which can be subclassed to add custom commands. Once you
229
+ create a subclass, it is automatically added to the repl. A command
230
+ can have zero or more parameters, and all parameters are presumed
231
+ to be a String (at least for now).
232
+
233
+ ```ruby
234
+ require "llm"
235
+ require "llm/repl"
236
+
237
+ class Greeter < LLM::Command
238
+ name "greet"
239
+ description "Greets the given name"
240
+ parameter :name, String, "The person's name"
241
+ required %i[name]
242
+
243
+ def call(name:)
244
+ write("Welcome #{name}!\n")
245
+ end
246
+ end
247
+ ```
248
+
213
249
  ##### REPL: Input
214
250
 
215
251
  The input area supports several keyboard shortcuts:
@@ -227,12 +263,6 @@ The input area supports several keyboard shortcuts:
227
263
  | `Up / Down` | Scroll the transcript |
228
264
  | `/exit` | Leave the REPL |
229
265
 
230
- ##### REPL: Commands
231
-
232
- Commands are recognized by a `/` prefix and are backed by the
233
- [`LLM::Repl::Command`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html)
234
- class, which can be subclassed to add custom commands.
235
-
236
266
  #### LLM::MCP
237
267
 
238
268
  The Model Context Protocol (MCP) has first-class support