llm.rb 4.20.0 → 4.20.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 +47 -0
- data/README.md +93 -7
- data/lib/llm/active_record/acts_as_agent.rb +2 -2
- data/lib/llm/active_record/acts_as_llm.rb +2 -2
- data/lib/llm/agent.rb +11 -7
- data/lib/llm/context.rb +6 -3
- data/lib/llm/providers/google/response_adapter/completion.rb +6 -0
- data/lib/llm/sequel/plugin.rb +3 -3
- data/lib/llm/stream/queue.rb +36 -6
- 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: a182d595ad65c1cb2f1a796b83e48cba4f1038031ec140709e902734051a8b46
|
|
4
|
+
data.tar.gz: b8cdb2e051bc620f111a97236bd64fe7940ff9f3d5b44c9f07b115641d74abcd
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a6fd61aaa9479ec34af93a1e732acf553a055e36a4f5e822a2c643ef2bf537923a7d0a968b40c6a8cfa9a09af8186ba31467fe627462da49389f1c6594d7ee41
|
|
7
|
+
data.tar.gz: df56a4624eca8f7007ea2054d79812df553df69d867297230c9b38368c87e67c06187dbf03195b5fcaae1b1701b82a79cd7be10ed86364a49802573367910d10
|
data/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
Changes since `v4.20.2`.
|
|
6
|
+
|
|
7
|
+
## v4.20.2
|
|
8
|
+
|
|
9
|
+
Changes since `v4.20.1`.
|
|
10
|
+
|
|
11
|
+
This patch release improves runtime behavior around interruption and mixed
|
|
12
|
+
concurrency waits. It also rounds out response API uniformity for Google
|
|
13
|
+
completion responses.
|
|
14
|
+
|
|
15
|
+
### Fix
|
|
16
|
+
|
|
17
|
+
* **Expose Google completion response IDs through `.id`** <br>
|
|
18
|
+
Add `LLM::Response#id` support to Google completion responses so tracer
|
|
19
|
+
and caller code can rely on the same API used by other providers.
|
|
20
|
+
|
|
21
|
+
* **Track interrupt ownership on the active request** <br>
|
|
22
|
+
Bind `LLM::Context` interruption to the fiber running `talk` or `respond`
|
|
23
|
+
so `interrupt!` works correctly when requests are started outside the
|
|
24
|
+
context's initialization fiber.
|
|
25
|
+
|
|
26
|
+
### Change
|
|
27
|
+
|
|
28
|
+
* **Allow mixed concurrency strategies in `wait(...)`** <br>
|
|
29
|
+
Let `LLM::Context#wait`, `LLM::Stream#wait`, and `LLM::Agent.concurrency`
|
|
30
|
+
accept arrays such as `[:thread, :ractor]` so mixed tool sets can wait on
|
|
31
|
+
more than one concurrency strategy.
|
|
32
|
+
|
|
33
|
+
## v4.20.1
|
|
34
|
+
|
|
5
35
|
Changes since `v4.20.0`.
|
|
6
36
|
|
|
37
|
+
This patch release fixes ORM option resolution in the Sequel and
|
|
38
|
+
ActiveRecord wrappers. Symbol-based `provider:` and `context:` hooks now
|
|
39
|
+
resolve correctly, and internal default option constants are referenced
|
|
40
|
+
explicitly instead of relying on nested constant lookup.
|
|
41
|
+
|
|
42
|
+
### Fix
|
|
43
|
+
|
|
44
|
+
* **Fix symbol-based ORM option hooks for provider and context hashes** <br>
|
|
45
|
+
Make `provider:` and `context:` resolve symbol hooks through the model in
|
|
46
|
+
the Sequel plugin and ActiveRecord wrappers instead of falling back to an
|
|
47
|
+
empty hash.
|
|
48
|
+
|
|
49
|
+
* **Fix ORM wrapper constant lookup for option defaults** <br>
|
|
50
|
+
Qualify internal `EMPTY_HASH` / `DEFAULTS` references in the Sequel plugin
|
|
51
|
+
and ActiveRecord wrappers so option resolution does not depend on nested
|
|
52
|
+
constant lookup quirks.
|
|
53
|
+
|
|
7
54
|
## v4.20.0
|
|
8
55
|
|
|
9
56
|
Changes since `v4.19.0`.
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<p align="center">
|
|
5
5
|
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"><img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc"></a>
|
|
6
6
|
<a href="https://opensource.org/license/0bsd"><img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License"></a>
|
|
7
|
-
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.20.
|
|
7
|
+
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.20.2-green.svg?" alt="Version"></a>
|
|
8
8
|
</p>
|
|
9
9
|
|
|
10
10
|
## About
|
|
@@ -23,7 +23,8 @@ pieces only when needed, includes built-in ActiveRecord support through
|
|
|
23
23
|
long-lived, tool-capable, stateful AI workflows instead of just
|
|
24
24
|
request/response helpers.
|
|
25
25
|
|
|
26
|
-
Want to see some code? Jump to [the examples](#examples) section.
|
|
26
|
+
Want to see some code? Jump to [the examples](#examples) section. <br>
|
|
27
|
+
Want a taste of what llm.rb can build? See [the screencast](#screencast).
|
|
27
28
|
|
|
28
29
|
## Architecture
|
|
29
30
|
|
|
@@ -186,7 +187,7 @@ gem install llm.rb
|
|
|
186
187
|
|
|
187
188
|
## Examples
|
|
188
189
|
|
|
189
|
-
|
|
190
|
+
#### REPL
|
|
190
191
|
|
|
191
192
|
This example uses [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) directly for an interactive REPL. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
192
193
|
|
|
@@ -203,7 +204,61 @@ loop do
|
|
|
203
204
|
end
|
|
204
205
|
```
|
|
205
206
|
|
|
206
|
-
|
|
207
|
+
#### Streaming
|
|
208
|
+
|
|
209
|
+
This example uses [`LLM::Stream`](https://0x1eef.github.io/x/llm.rb/LLM/Stream.html) directly so visible output and tool execution can happen together. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
require "llm"
|
|
213
|
+
|
|
214
|
+
class Stream < LLM::Stream
|
|
215
|
+
def on_content(content)
|
|
216
|
+
$stdout << content
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def on_tool_call(tool, error)
|
|
220
|
+
return queue << error if error
|
|
221
|
+
$stdout << "\nRunning tool #{tool.name}...\n"
|
|
222
|
+
queue << tool.spawn(:thread)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def on_tool_return(tool, result)
|
|
226
|
+
if result.error?
|
|
227
|
+
$stdout << "Tool #{tool.name} failed\n"
|
|
228
|
+
else
|
|
229
|
+
$stdout << "Finished tool #{tool.name}\n"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
235
|
+
ctx = LLM::Context.new(llm, stream: Stream.new, tools: [System])
|
|
236
|
+
|
|
237
|
+
ctx.talk("Run `date` and `uname -a`.")
|
|
238
|
+
ctx.talk(ctx.wait(:thread)) while ctx.functions.any?
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Request Cancellation
|
|
242
|
+
|
|
243
|
+
Need to cancel a stream? llm.rb has you covered through [`LLM::Context#interrupt!`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#interrupt-21-instance_method). <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
244
|
+
|
|
245
|
+
```ruby
|
|
246
|
+
require "llm"
|
|
247
|
+
require "io/console"
|
|
248
|
+
|
|
249
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
250
|
+
ctx = LLM::Context.new(llm, stream: $stdout)
|
|
251
|
+
|
|
252
|
+
worker = Thread.new do
|
|
253
|
+
ctx.talk("Write a very long essay about network protocols.")
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
STDIN.getch
|
|
257
|
+
ctx.interrupt!
|
|
258
|
+
worker.join
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
#### Sequel (ORM)
|
|
207
262
|
|
|
208
263
|
The `plugin :llm` integration wraps [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) on a `Sequel::Model` and keeps tool execution explicit. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
209
264
|
|
|
@@ -222,7 +277,7 @@ ctx.talk("Remember that my favorite language is Ruby")
|
|
|
222
277
|
puts ctx.talk("What is my favorite language?").content
|
|
223
278
|
```
|
|
224
279
|
|
|
225
|
-
|
|
280
|
+
#### ActiveRecord (ORM): acts_as_llm
|
|
226
281
|
|
|
227
282
|
The `acts_as_llm` method wraps [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) and
|
|
228
283
|
provides full control over tool execution. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
@@ -242,7 +297,7 @@ ctx.talk("Remember that my favorite language is Ruby")
|
|
|
242
297
|
puts ctx.talk("What is my favorite language?").content
|
|
243
298
|
```
|
|
244
299
|
|
|
245
|
-
|
|
300
|
+
#### ActiveRecord (ORM): acts_as_agent
|
|
246
301
|
|
|
247
302
|
The `acts_as_agent` method wraps [`LLM::Agent`](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html) and
|
|
248
303
|
manages tool execution for you. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
@@ -272,7 +327,7 @@ ticket = Ticket.create!(provider: "openai", model: "gpt-5.4-mini")
|
|
|
272
327
|
puts ticket.talk("How do I rotate my API key?").content
|
|
273
328
|
```
|
|
274
329
|
|
|
275
|
-
|
|
330
|
+
#### Agent
|
|
276
331
|
|
|
277
332
|
This example uses [`LLM::Agent`](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html) directly and lets the agent manage tool execution. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
278
333
|
|
|
@@ -291,6 +346,37 @@ agent = ShellAgent.new(llm)
|
|
|
291
346
|
puts agent.talk("What time is it on this system?").content
|
|
292
347
|
```
|
|
293
348
|
|
|
349
|
+
#### MCP
|
|
350
|
+
|
|
351
|
+
This example uses [`LLM::MCP`](https://0x1eef.github.io/x/llm.rb/LLM/MCP.html) over HTTP so remote GitHub MCP tools run through the same `LLM::Context` tool path as local tools. <br> See the [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) for more examples.
|
|
352
|
+
|
|
353
|
+
```ruby
|
|
354
|
+
require "llm"
|
|
355
|
+
require "net/http/persistent"
|
|
356
|
+
|
|
357
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
358
|
+
mcp = LLM::MCP.http(
|
|
359
|
+
url: "https://api.githubcopilot.com/mcp/",
|
|
360
|
+
headers: {"Authorization" => "Bearer #{ENV.fetch("GITHUB_PAT")}"}
|
|
361
|
+
).persistent
|
|
362
|
+
|
|
363
|
+
begin
|
|
364
|
+
mcp.start
|
|
365
|
+
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
366
|
+
ctx.talk("Pull information about my GitHub account.")
|
|
367
|
+
ctx.talk(ctx.call(:functions)) while ctx.functions.any?
|
|
368
|
+
ensure
|
|
369
|
+
mcp.stop
|
|
370
|
+
end
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
## Screencast
|
|
374
|
+
|
|
375
|
+
This screencast was built on an older version of llm.rb, but it still shows
|
|
376
|
+
how capable the runtime can be in a real application:
|
|
377
|
+
|
|
378
|
+
[](https://www.youtube.com/watch?v=x1K4wMeO_QA)
|
|
379
|
+
|
|
294
380
|
## Resources
|
|
295
381
|
|
|
296
382
|
- [deepdive](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) is the
|
|
@@ -150,8 +150,8 @@ module LLM::ActiveRecord
|
|
|
150
150
|
# @return [Hash]
|
|
151
151
|
def resolve_options(option)
|
|
152
152
|
case option
|
|
153
|
-
when Proc, Hash then resolve_option(option)
|
|
154
|
-
else EMPTY_HASH.dup
|
|
153
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
154
|
+
else ActsAsAgent::EMPTY_HASH.dup
|
|
155
155
|
end
|
|
156
156
|
end
|
|
157
157
|
|
|
@@ -270,8 +270,8 @@ module LLM::ActiveRecord
|
|
|
270
270
|
# @return [Hash]
|
|
271
271
|
def resolve_options(option)
|
|
272
272
|
case option
|
|
273
|
-
when Proc, Hash then resolve_option(option)
|
|
274
|
-
else EMPTY_HASH.dup
|
|
273
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
274
|
+
else ActsAsLLM::EMPTY_HASH.dup
|
|
275
275
|
end
|
|
276
276
|
end
|
|
277
277
|
|
data/lib/llm/agent.rb
CHANGED
|
@@ -17,7 +17,8 @@ module LLM
|
|
|
17
17
|
# * Instructions are injected only on the first request.
|
|
18
18
|
# * An agent automatically executes tool loops (unlike {LLM::Context LLM::Context}).
|
|
19
19
|
# * Tool loop execution can be configured with `concurrency :call`,
|
|
20
|
-
# `:thread`, `:task`, `:fiber`, or
|
|
20
|
+
# `:thread`, `:task`, `:fiber`, `:ractor`, or a list of queued task
|
|
21
|
+
# types such as `[:thread, :ractor]`.
|
|
21
22
|
#
|
|
22
23
|
# @example
|
|
23
24
|
# class SystemAdmin < LLM::Agent
|
|
@@ -83,7 +84,7 @@ module LLM
|
|
|
83
84
|
##
|
|
84
85
|
# Set or get the tool execution concurrency.
|
|
85
86
|
#
|
|
86
|
-
# @param [Symbol, nil] concurrency
|
|
87
|
+
# @param [Symbol, Array<Symbol>, nil] concurrency
|
|
87
88
|
# Controls how pending tool loops are executed:
|
|
88
89
|
# - `:call`: sequential calls
|
|
89
90
|
# - `:thread`: concurrent threads
|
|
@@ -91,7 +92,10 @@ module LLM
|
|
|
91
92
|
# - `:fiber`: concurrent raw fibers
|
|
92
93
|
# - `:ractor`: concurrent Ruby ractors for class-based tools; MCP tools are not supported,
|
|
93
94
|
# and this mode is especially useful for CPU-bound tool work
|
|
94
|
-
#
|
|
95
|
+
# - `[:thread, :ractor]`: the possible concurrency strategies to wait on, in the
|
|
96
|
+
# given order. This is useful for mixed tool sets or when work may have been
|
|
97
|
+
# spawned with more than one concurrency strategy.
|
|
98
|
+
# @return [Symbol, Array<Symbol>, nil]
|
|
95
99
|
def self.concurrency(concurrency = nil)
|
|
96
100
|
return @concurrency if concurrency.nil?
|
|
97
101
|
@concurrency = concurrency
|
|
@@ -107,7 +111,7 @@ module LLM
|
|
|
107
111
|
# @option params [String] :model Defaults to the provider's default model
|
|
108
112
|
# @option params [Array<LLM::Function>, nil] :tools Defaults to nil
|
|
109
113
|
# @option params [#to_json, nil] :schema Defaults to nil
|
|
110
|
-
# @option params [Symbol, nil] :concurrency Defaults to the agent class concurrency
|
|
114
|
+
# @option params [Symbol, Array<Symbol>, nil] :concurrency Defaults to the agent class concurrency
|
|
111
115
|
def initialize(llm, params = {})
|
|
112
116
|
defaults = {model: self.class.model, tools: self.class.tools, schema: self.class.schema}.compact
|
|
113
117
|
@concurrency = params.delete(:concurrency) || self.class.concurrency
|
|
@@ -270,7 +274,7 @@ module LLM
|
|
|
270
274
|
|
|
271
275
|
##
|
|
272
276
|
# Returns the configured tool execution concurrency.
|
|
273
|
-
# @return [Symbol, nil]
|
|
277
|
+
# @return [Symbol, Array<Symbol>, nil]
|
|
274
278
|
def concurrency
|
|
275
279
|
@concurrency
|
|
276
280
|
end
|
|
@@ -348,8 +352,8 @@ module LLM
|
|
|
348
352
|
def call_functions
|
|
349
353
|
case concurrency || :call
|
|
350
354
|
when :call then call(:functions)
|
|
351
|
-
when :thread, :task, :fiber, :ractor then wait(concurrency)
|
|
352
|
-
else raise ArgumentError, "Unknown concurrency: #{concurrency.inspect}. Expected :call, :thread, :task, :fiber, or
|
|
355
|
+
when :thread, :task, :fiber, :ractor, Array then wait(concurrency)
|
|
356
|
+
else raise ArgumentError, "Unknown concurrency: #{concurrency.inspect}. Expected :call, :thread, :task, :fiber, :ractor, or an array of queued task types"
|
|
353
357
|
end
|
|
354
358
|
end
|
|
355
359
|
end
|
data/lib/llm/context.rb
CHANGED
|
@@ -69,7 +69,6 @@ module LLM
|
|
|
69
69
|
@mode = params.delete(:mode) || :completions
|
|
70
70
|
@params = {model: llm.default_model, schema: nil}.compact.merge!(params)
|
|
71
71
|
@messages = LLM::Buffer.new(llm)
|
|
72
|
-
@owner = Fiber.current
|
|
73
72
|
end
|
|
74
73
|
|
|
75
74
|
##
|
|
@@ -86,6 +85,7 @@ module LLM
|
|
|
86
85
|
# puts res.messages[0].content
|
|
87
86
|
def talk(prompt, params = {})
|
|
88
87
|
return respond(prompt, params) if mode == :responses
|
|
88
|
+
@owner = Fiber.current
|
|
89
89
|
params = params.merge(messages: @messages.to_a)
|
|
90
90
|
params = @params.merge(params)
|
|
91
91
|
bind!(params[:stream], params[:model])
|
|
@@ -112,6 +112,7 @@ module LLM
|
|
|
112
112
|
# res = ctx.respond("What is the capital of France?")
|
|
113
113
|
# puts res.output_text
|
|
114
114
|
def respond(prompt, params = {})
|
|
115
|
+
@owner = Fiber.current
|
|
115
116
|
params = @params.merge(params)
|
|
116
117
|
bind!(params[:stream], params[:model])
|
|
117
118
|
res_id = params[:store] == false ? nil : @messages.find(&:assistant?)&.response&.response_id
|
|
@@ -182,8 +183,10 @@ module LLM
|
|
|
182
183
|
# exposes a non-empty queue. Otherwise it falls back to waiting on
|
|
183
184
|
# the context's pending functions directly.
|
|
184
185
|
#
|
|
185
|
-
# @param [Symbol] strategy
|
|
186
|
-
# The concurrency strategy to use
|
|
186
|
+
# @param [Symbol, Array<Symbol>] strategy
|
|
187
|
+
# The concurrency strategy to use, or the possible concurrency strategies to
|
|
188
|
+
# wait on. For example, `[:thread, :ractor]` waits for any queued thread or
|
|
189
|
+
# ractor work, in that order.
|
|
187
190
|
# @return [Array<LLM::Function::Return>]
|
|
188
191
|
def wait(strategy)
|
|
189
192
|
stream = @params[:stream]
|
data/lib/llm/sequel/plugin.rb
CHANGED
|
@@ -79,7 +79,7 @@ module LLM::Sequel
|
|
|
79
79
|
##
|
|
80
80
|
# @return [Hash]
|
|
81
81
|
def llm_plugin_options
|
|
82
|
-
@llm_plugin_options || DEFAULTS
|
|
82
|
+
@llm_plugin_options || Plugin::DEFAULTS
|
|
83
83
|
end
|
|
84
84
|
end
|
|
85
85
|
|
|
@@ -287,8 +287,8 @@ module LLM::Sequel
|
|
|
287
287
|
# @return [Hash]
|
|
288
288
|
def resolve_options(option)
|
|
289
289
|
case option
|
|
290
|
-
when Proc, Hash then resolve_option(option)
|
|
291
|
-
else EMPTY_HASH.dup
|
|
290
|
+
when Proc, Symbol, Hash then resolve_option(option)
|
|
291
|
+
else Plugin::EMPTY_HASH.dup
|
|
292
292
|
end
|
|
293
293
|
end
|
|
294
294
|
|
data/lib/llm/stream/queue.rb
CHANGED
|
@@ -33,27 +33,57 @@ class LLM::Stream
|
|
|
33
33
|
|
|
34
34
|
##
|
|
35
35
|
# Waits for queued work to finish and returns function results.
|
|
36
|
-
# @param [Symbol] strategy
|
|
37
|
-
# Controls concurrency strategy
|
|
36
|
+
# @param [Symbol, Array<Symbol>] strategy
|
|
37
|
+
# Controls concurrency strategy, or lists the possible concurrency strategies
|
|
38
|
+
# to wait on:
|
|
38
39
|
# - `:thread`: Use threads
|
|
39
40
|
# - `:task`: Use async tasks (requires async gem)
|
|
40
41
|
# - `:fiber`: Use raw fibers
|
|
41
42
|
# - `:ractor`: Use Ruby ractors (class-based tools only; MCP tools are not supported)
|
|
43
|
+
# - `[:thread, :ractor]`: Wait for any queued thread or ractor work, in the
|
|
44
|
+
# given order. This is useful when different tools were spawned with
|
|
45
|
+
# different concurrency strategies.
|
|
42
46
|
# @return [Array<LLM::Function::Return>]
|
|
43
47
|
def wait(strategy)
|
|
44
48
|
returns, tasks = @items.shift(@items.length).partition { LLM::Function::Return === _1 }
|
|
45
|
-
results =
|
|
49
|
+
results = wait_tasks(tasks, strategy)
|
|
50
|
+
returns.concat fire_hooks(tasks, results)
|
|
51
|
+
end
|
|
52
|
+
alias_method :value, :wait
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def wait_tasks(tasks, strategy)
|
|
57
|
+
strategies = Array(strategy)
|
|
58
|
+
return wait_group(tasks, strategies.first) unless strategies.length > 1
|
|
59
|
+
grouped = strategies.to_h { [_1, []] }
|
|
60
|
+
tasks.each do |task|
|
|
61
|
+
grouped[task_strategy(task)] << task
|
|
62
|
+
end
|
|
63
|
+
strategies.flat_map do |name|
|
|
64
|
+
selected = grouped.fetch(name)
|
|
65
|
+
selected.empty? ? [] : wait_group(selected, name)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def wait_group(tasks, strategy)
|
|
70
|
+
case strategy
|
|
46
71
|
when :thread then LLM::Function::ThreadGroup.new(tasks).wait
|
|
47
72
|
when :task then LLM::Function::TaskGroup.new(tasks).wait
|
|
48
73
|
when :fiber then LLM::Function::FiberGroup.new(tasks).wait
|
|
49
74
|
when :ractor then LLM::Function::Ractor::Group.new(tasks).wait
|
|
50
75
|
else raise ArgumentError, "Unknown strategy: #{strategy.inspect}. Expected :thread, :task, :fiber, or :ractor"
|
|
51
76
|
end
|
|
52
|
-
returns.concat fire_hooks(tasks, results)
|
|
53
77
|
end
|
|
54
|
-
alias_method :value, :wait
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
def task_strategy(task)
|
|
80
|
+
case task.task
|
|
81
|
+
when Thread then :thread
|
|
82
|
+
when Fiber then :fiber
|
|
83
|
+
when LLM::Function::Ractor::Task then :ractor
|
|
84
|
+
else :task
|
|
85
|
+
end
|
|
86
|
+
end
|
|
57
87
|
|
|
58
88
|
def fire_hooks(tasks, results)
|
|
59
89
|
results.each_with_index do |result, idx|
|
data/lib/llm/version.rb
CHANGED