llm.rb 12.6.0 → 13.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 +387 -0
- data/LICENSE +21 -93
- data/README.md +46 -155
- data/data/deepinfra.json +3 -0
- data/data/xai.json +1 -1
- data/lib/llm/a2a.rb +1 -1
- data/lib/llm/active_record/acts_as_llm.rb +6 -6
- data/lib/llm/agent.rb +62 -23
- data/lib/llm/buffer.rb +85 -3
- data/lib/llm/compactor/null.rb +19 -0
- data/lib/llm/compactor/truncate.rb +80 -0
- data/lib/llm/compactor.rb +42 -124
- data/lib/llm/context.rb +31 -37
- data/lib/llm/contract.rb +4 -25
- data/lib/llm/function/array.rb +15 -14
- data/lib/llm/function/async/group.rb +54 -0
- data/lib/llm/function/async/reactor.rb +48 -0
- data/lib/llm/function/async/task.rb +83 -0
- data/lib/llm/function/fiber/group.rb +46 -0
- data/lib/llm/function/fiber/task.rb +62 -0
- data/lib/llm/function/{fork_group.rb → fork/group.rb} +14 -5
- data/lib/llm/function/fork/job.rb +2 -2
- data/lib/llm/function/fork/task.rb +19 -10
- data/lib/llm/function/group.rb +40 -0
- data/lib/llm/function/{ractor_group.rb → ractor/group.rb} +13 -5
- data/lib/llm/function/ractor/job.rb +9 -3
- data/lib/llm/function/ractor/mailbox.rb +2 -0
- data/lib/llm/function/ractor/task.rb +23 -15
- data/lib/llm/function/{call_group.rb → sequential/group.rb} +12 -8
- data/lib/llm/function/sequential/task.rb +49 -0
- data/lib/llm/function/task.rb +25 -48
- data/lib/llm/function/thread/group.rb +46 -0
- data/lib/llm/function/thread/task.rb +60 -0
- data/lib/llm/function.rb +54 -64
- data/lib/llm/loop_guard.rb +1 -2
- data/lib/llm/mcp.rb +22 -0
- data/lib/llm/object.rb +2 -1
- data/lib/llm/provider.rb +6 -3
- data/lib/llm/providers/google.rb +2 -2
- data/lib/llm/repl/command.rb +35 -8
- data/lib/llm/repl/commands/compact.rb +33 -0
- data/lib/llm/repl/input.rb +80 -15
- data/lib/llm/repl/markdown/table.rb +76 -0
- data/lib/llm/repl/markdown.rb +31 -1
- data/lib/llm/repl/status.rb +1 -1
- data/lib/llm/repl/stream.rb +10 -3
- data/lib/llm/repl/transcript.rb +1 -1
- data/lib/llm/repl/walker.rb +46 -0
- data/lib/llm/repl.rb +18 -12
- data/lib/llm/response.rb +10 -0
- data/lib/llm/schema/leaf.rb +5 -0
- data/lib/llm/schema/object.rb +11 -5
- data/lib/llm/sequel/plugin.rb +6 -6
- data/lib/llm/stream.rb +24 -17
- data/lib/llm/tool.rb +20 -4
- data/lib/llm/tools/chdir.rb +0 -2
- data/lib/llm/tools/git.rb +8 -4
- data/lib/llm/tools/mkdir.rb +1 -1
- data/lib/llm/tools/pwd.rb +0 -2
- data/lib/llm/tools/read_file.rb +0 -2
- data/lib/llm/tools/rg.rb +8 -4
- data/lib/llm/tools/shell.rb +8 -4
- data/lib/llm/tools/utils.rb +31 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +25 -5
- data/llm.gemspec +3 -3
- data/resources/deepdive.md +645 -58
- metadata +24 -13
- data/lib/llm/function/call_task.rb +0 -46
- data/lib/llm/function/fiber_group.rb +0 -105
- data/lib/llm/function/task_group.rb +0 -97
- data/lib/llm/function/thread_group.rb +0 -102
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Function::Async
|
|
4
|
+
##
|
|
5
|
+
# {LLM::Function::Async::Task} wraps a function call in an
|
|
6
|
+
# {Async::Task} running on a shared
|
|
7
|
+
# {LLM::Function::Async::Reactor}. The task is spawned lazily
|
|
8
|
+
# in {#wait} or explicitly in {#spawn}.
|
|
9
|
+
#
|
|
10
|
+
# Work is submitted to the reactor through its inbox queue.
|
|
11
|
+
# Results are bridged back through the task's own queue.
|
|
12
|
+
class Task < LLM::Function::Task
|
|
13
|
+
##
|
|
14
|
+
# @param [LLM::Function] fn
|
|
15
|
+
# @param [Hash] options
|
|
16
|
+
# @option options [LLM::Function::Async::Reactor] :reactor
|
|
17
|
+
def initialize(fn, options = {})
|
|
18
|
+
super
|
|
19
|
+
@reactor = options[:reactor]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Assign the reactor. Used when a task is created before its
|
|
24
|
+
# reactor is available.
|
|
25
|
+
# @param [LLM::Function::Async::Reactor] reactor
|
|
26
|
+
def reactor=(reactor)
|
|
27
|
+
@reactor = reactor
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Submit the function call to the reactor. The result is
|
|
32
|
+
# pushed to a queue that {#wait} consumes.
|
|
33
|
+
# @return [nil]
|
|
34
|
+
def spawn
|
|
35
|
+
@queue = Queue.new
|
|
36
|
+
@alive = true
|
|
37
|
+
@reactor.submit do
|
|
38
|
+
@queue << function.call
|
|
39
|
+
rescue LLM::Interrupt => e
|
|
40
|
+
@queue << e
|
|
41
|
+
raise
|
|
42
|
+
end
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
def alive?
|
|
49
|
+
@alive || false
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Push an interrupt sentinel to the result queue. The reactor
|
|
54
|
+
# thread continues running but the result is discarded.
|
|
55
|
+
# @return [nil]
|
|
56
|
+
def interrupt!
|
|
57
|
+
if @queue
|
|
58
|
+
@alive = false
|
|
59
|
+
@queue << LLM::Interrupt.new
|
|
60
|
+
end
|
|
61
|
+
nil
|
|
62
|
+
end
|
|
63
|
+
alias_method :cancel!, :interrupt!
|
|
64
|
+
|
|
65
|
+
##
|
|
66
|
+
# Wait for the result queue to contain a value.
|
|
67
|
+
# @return [LLM::Function::Return]
|
|
68
|
+
def wait
|
|
69
|
+
spawn unless @queue
|
|
70
|
+
result = @queue.pop
|
|
71
|
+
@alive = false
|
|
72
|
+
raise result if LLM::Interrupt === result
|
|
73
|
+
result
|
|
74
|
+
end
|
|
75
|
+
alias_method :value, :wait
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# @return [Class]
|
|
79
|
+
def group_class
|
|
80
|
+
LLM::Function::Async::Group
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Function::Fiber
|
|
4
|
+
##
|
|
5
|
+
# Wraps an array of {Fiber::Task} objects for cooperative
|
|
6
|
+
# concurrent execution. Interrupts all tasks and waits
|
|
7
|
+
# for them to complete.
|
|
8
|
+
class Group < LLM::Function::Group
|
|
9
|
+
##
|
|
10
|
+
# @param [Array<LLM::Function::Fiber::Task>] tasks
|
|
11
|
+
def initialize(tasks)
|
|
12
|
+
@tasks = tasks
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# @return [nil]
|
|
17
|
+
def spawn
|
|
18
|
+
@tasks.each(&:spawn)
|
|
19
|
+
nil
|
|
20
|
+
ensure
|
|
21
|
+
@spawned = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# @return [Boolean]
|
|
26
|
+
def alive?
|
|
27
|
+
@tasks.any?(&:alive?)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# @return [nil]
|
|
32
|
+
def interrupt!
|
|
33
|
+
@tasks.each(&:interrupt!)
|
|
34
|
+
nil
|
|
35
|
+
end
|
|
36
|
+
alias_method :cancel!, :interrupt!
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @return [Array<LLM::Function::Return>]
|
|
40
|
+
def wait
|
|
41
|
+
spawn unless @spawned
|
|
42
|
+
@tasks.map(&:wait)
|
|
43
|
+
end
|
|
44
|
+
alias_method :value, :wait
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Function::Fiber
|
|
4
|
+
##
|
|
5
|
+
# {LLM::Function::Fiber::Task LLM::Function::Fiber::Task}
|
|
6
|
+
# wraps a function call in a scheduler-backed fiber for
|
|
7
|
+
# cooperative concurrent execution. The fiber is created
|
|
8
|
+
# lazily when {#wait} is called, not at construction time.
|
|
9
|
+
#
|
|
10
|
+
# Requires `Fiber.scheduler` — without one, raise early in
|
|
11
|
+
# {#wait}. Interrupting a running task raises
|
|
12
|
+
# {LLM::Interrupt} on the fiber, which stops it at the next
|
|
13
|
+
# yield point.
|
|
14
|
+
class Task < LLM::Function::Task
|
|
15
|
+
##
|
|
16
|
+
# @param [LLM::Function] fn
|
|
17
|
+
# @param [Hash] options
|
|
18
|
+
def initialize(fn, options = {})
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# @return [nil]
|
|
24
|
+
def spawn
|
|
25
|
+
if Fiber.scheduler.nil?
|
|
26
|
+
raise ArgumentError, "Fiber concurrency requires Fiber.scheduler"
|
|
27
|
+
else
|
|
28
|
+
@fiber = Fiber.schedule { function.call }
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# @return [Boolean]
|
|
35
|
+
def alive?
|
|
36
|
+
@fiber&.alive? || false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# @return [nil]
|
|
41
|
+
def interrupt!
|
|
42
|
+
@fiber&.raise(LLM::Interrupt) if @fiber&.alive?
|
|
43
|
+
function.interrupt!
|
|
44
|
+
nil
|
|
45
|
+
end
|
|
46
|
+
alias_method :cancel!, :interrupt!
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# @return [LLM::Function::Return]
|
|
50
|
+
def wait
|
|
51
|
+
spawn unless @fiber
|
|
52
|
+
@result ||= @fiber.value
|
|
53
|
+
end
|
|
54
|
+
alias_method :value, :wait
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @return [Class]
|
|
58
|
+
def group_class
|
|
59
|
+
LLM::Function::Fiber::Group
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -1,17 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module LLM::Function::Fork
|
|
4
4
|
##
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
class
|
|
5
|
+
# Wraps an array of {Fork::Task} objects that are running
|
|
6
|
+
# in forked child processes.
|
|
7
|
+
class Group < LLM::Function::Group
|
|
8
8
|
##
|
|
9
9
|
# @param [Array<LLM::Function::Task>] tasks
|
|
10
|
-
# @return [LLM::Function::Fork::Group]
|
|
11
10
|
def initialize(tasks)
|
|
12
11
|
@tasks = tasks
|
|
13
12
|
end
|
|
14
13
|
|
|
14
|
+
##
|
|
15
|
+
# @return [nil]
|
|
16
|
+
def spawn
|
|
17
|
+
@tasks.each(&:spawn)
|
|
18
|
+
nil
|
|
19
|
+
ensure
|
|
20
|
+
@spawned = true
|
|
21
|
+
end
|
|
22
|
+
|
|
15
23
|
##
|
|
16
24
|
# @return [Boolean]
|
|
17
25
|
def alive?
|
|
@@ -29,6 +37,7 @@ class LLM::Function
|
|
|
29
37
|
##
|
|
30
38
|
# @return [Array<LLM::Function::Return>]
|
|
31
39
|
def wait
|
|
40
|
+
spawn unless @spawned
|
|
32
41
|
@tasks.map(&:wait)
|
|
33
42
|
end
|
|
34
43
|
alias_method :value, :wait
|
|
@@ -54,11 +54,11 @@ class LLM::Function
|
|
|
54
54
|
|
|
55
55
|
def setup(runner)
|
|
56
56
|
ready = Queue.new
|
|
57
|
-
thread = Thread.new do
|
|
57
|
+
thread = ::Thread.new do
|
|
58
58
|
ready << true
|
|
59
59
|
kind = @ch.control.recv
|
|
60
60
|
next unless kind == :interrupt
|
|
61
|
-
Thread.main.raise(LLM::Interrupt)
|
|
61
|
+
::Thread.main.raise(LLM::Interrupt)
|
|
62
62
|
rescue IOError, ArgumentError
|
|
63
63
|
end
|
|
64
64
|
ready.pop
|
|
@@ -4,31 +4,39 @@ class LLM::Function
|
|
|
4
4
|
##
|
|
5
5
|
# The {LLM::Function::Fork::Task} class wraps a fork-backed function call
|
|
6
6
|
# and exchanges control and result messages with the child process.
|
|
7
|
-
class Fork::Task
|
|
7
|
+
class Fork::Task < LLM::Function::Task
|
|
8
8
|
##
|
|
9
|
-
# @param [LLM::Function]
|
|
10
|
-
# @param [
|
|
11
|
-
# @
|
|
9
|
+
# @param [LLM::Function] fn
|
|
10
|
+
# @param [Hash] options
|
|
11
|
+
# @option options [LLM::Tracer, nil] :tracer
|
|
12
12
|
# @return [LLM::Function::Fork::Task]
|
|
13
|
-
def initialize(
|
|
14
|
-
|
|
15
|
-
@tracer = tracer
|
|
16
|
-
@
|
|
13
|
+
def initialize(fn, options = {})
|
|
14
|
+
super
|
|
15
|
+
@tracer = options.fetch(:tracer, nil)
|
|
16
|
+
@spawned = false
|
|
17
17
|
@waited = false
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
##
|
|
21
21
|
# @return [LLM::Function::Fork::Task]
|
|
22
22
|
def spawn
|
|
23
|
-
@
|
|
23
|
+
@span = @tracer&.on_tool_start(
|
|
24
|
+
id: @function.id, name: @function.name,
|
|
25
|
+
arguments: @function.arguments, model: @function.model
|
|
26
|
+
)
|
|
27
|
+
@ch = LLM::Object.from(
|
|
28
|
+
control: xchan(:marshal),
|
|
29
|
+
result: xchan(:marshal, sock: Socket::SOCK_STREAM)
|
|
30
|
+
)
|
|
24
31
|
@pid = Kernel.fork { Fork::Job.new(@function, @ch).call }
|
|
32
|
+
@spawned = true
|
|
25
33
|
self
|
|
26
34
|
end
|
|
27
35
|
|
|
28
36
|
##
|
|
29
37
|
# @return [Boolean]
|
|
30
38
|
def alive?
|
|
31
|
-
return false if @waited
|
|
39
|
+
return false if @waited || !@pid
|
|
32
40
|
result = ::Process.waitpid(@pid, ::Process::WNOHANG)
|
|
33
41
|
@waited = !result.nil?
|
|
34
42
|
!@waited
|
|
@@ -51,6 +59,7 @@ class LLM::Function
|
|
|
51
59
|
##
|
|
52
60
|
# @return [LLM::Function::Return]
|
|
53
61
|
def wait
|
|
62
|
+
spawn unless @spawned
|
|
54
63
|
kind, data = @ch.result.recv
|
|
55
64
|
raise LLM::Interrupt if kind == :interrupt
|
|
56
65
|
raise ArgumentError, "Unknown fork message: #{kind.inspect}" unless kind == :result
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Function
|
|
4
|
+
##
|
|
5
|
+
# Abstract base class for all concurrency strategy groups.
|
|
6
|
+
# Each strategy (sequential, thread, fiber, async, fork, ractor)
|
|
7
|
+
# subclasses this and implements {#alive?}, {#interrupt!}, and
|
|
8
|
+
# {#wait}.
|
|
9
|
+
class Group
|
|
10
|
+
##
|
|
11
|
+
# @abstract
|
|
12
|
+
# @return [nil]
|
|
13
|
+
def spawn
|
|
14
|
+
raise NotImplementedError
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @abstract
|
|
19
|
+
# @return [Boolean]
|
|
20
|
+
def alive?
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
##
|
|
25
|
+
# @abstract
|
|
26
|
+
# @return [nil]
|
|
27
|
+
def interrupt!
|
|
28
|
+
raise NotImplementedError
|
|
29
|
+
end
|
|
30
|
+
alias_method :cancel!, :interrupt!
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @abstract
|
|
34
|
+
# @return [Array<LLM::Function::Return>]
|
|
35
|
+
def wait
|
|
36
|
+
raise NotImplementedError
|
|
37
|
+
end
|
|
38
|
+
alias_method :value, :wait
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -1,18 +1,25 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module LLM::Function::Ractor
|
|
4
4
|
##
|
|
5
|
-
#
|
|
6
|
-
# {LLM::Function::Ractor::Task} objects that are running
|
|
5
|
+
# Wraps an array of {Ractor::Task} objects that are running
|
|
7
6
|
# {LLM::Function} calls concurrently.
|
|
8
|
-
class
|
|
7
|
+
class Group < LLM::Function::Group
|
|
9
8
|
##
|
|
10
9
|
# @param [Array<LLM::Function::Task>] tasks
|
|
11
|
-
# @return [LLM::Function::Ractor::Group]
|
|
12
10
|
def initialize(tasks)
|
|
13
11
|
@tasks = tasks
|
|
14
12
|
end
|
|
15
13
|
|
|
14
|
+
##
|
|
15
|
+
# @return [nil]
|
|
16
|
+
def spawn
|
|
17
|
+
@tasks.each(&:spawn)
|
|
18
|
+
nil
|
|
19
|
+
ensure
|
|
20
|
+
@spawned = true
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
##
|
|
17
24
|
# @return [Boolean]
|
|
18
25
|
def alive?
|
|
@@ -30,6 +37,7 @@ class LLM::Function
|
|
|
30
37
|
##
|
|
31
38
|
# @return [Array<LLM::Function::Return>]
|
|
32
39
|
def wait
|
|
40
|
+
spawn unless @spawned
|
|
33
41
|
@tasks.map(&:wait)
|
|
34
42
|
end
|
|
35
43
|
alias_method :value, :wait
|
|
@@ -39,11 +39,17 @@ class LLM::Function
|
|
|
39
39
|
result ||= data
|
|
40
40
|
done = true
|
|
41
41
|
waiters.each { _1.send(result) }
|
|
42
|
+
break unless waiters.empty?
|
|
42
43
|
waiters.clear
|
|
43
44
|
in [:alive?, reply]
|
|
44
45
|
reply.send(!done)
|
|
45
46
|
in [:wait, reply]
|
|
46
|
-
done
|
|
47
|
+
if done
|
|
48
|
+
reply.send(result)
|
|
49
|
+
break
|
|
50
|
+
else
|
|
51
|
+
waiters << reply
|
|
52
|
+
end
|
|
47
53
|
in [:interrupt]
|
|
48
54
|
@tool&.send(:interrupt)
|
|
49
55
|
end
|
|
@@ -52,9 +58,9 @@ class LLM::Function
|
|
|
52
58
|
|
|
53
59
|
def spawn
|
|
54
60
|
@tool = ::Ractor.new(@mailbox, @runner_class, @id, @name, @arguments) do |mailbox, runner_class, id, name, arguments|
|
|
55
|
-
Thread.new do
|
|
61
|
+
::Thread.new do
|
|
56
62
|
::Ractor.receive == :interrupt or next
|
|
57
|
-
Thread.main.raise(LLM::Interrupt)
|
|
63
|
+
::Thread.main.raise(LLM::Interrupt)
|
|
58
64
|
rescue ::Ractor::Error
|
|
59
65
|
end
|
|
60
66
|
kwargs = Hash === arguments ? arguments.transform_keys(&:to_sym) : arguments
|
|
@@ -5,31 +5,38 @@ class LLM::Function
|
|
|
5
5
|
# The {LLM::Function::Ractor::Task} class wraps a ractor-backed function
|
|
6
6
|
# call and delegates mailbox coordination to
|
|
7
7
|
# {LLM::Function::Ractor::Mailbox}.
|
|
8
|
-
class Ractor::Task
|
|
8
|
+
class Ractor::Task < LLM::Function::Task
|
|
9
9
|
##
|
|
10
10
|
# @return [LLM::Function::Ractor::Mailbox]
|
|
11
11
|
attr_reader :mailbox
|
|
12
12
|
|
|
13
13
|
##
|
|
14
|
-
# @param [
|
|
15
|
-
# @param [
|
|
16
|
-
# @
|
|
17
|
-
# @
|
|
18
|
-
# @
|
|
19
|
-
# @
|
|
14
|
+
# @param [LLM::Function] fn
|
|
15
|
+
# @param [Hash] options
|
|
16
|
+
# @option options [LLM::Tracer, nil] :tracer
|
|
17
|
+
# @option options [Class] :runner_class
|
|
18
|
+
# @option options [String, nil] :id
|
|
19
|
+
# @option options [String] :name
|
|
20
|
+
# @option options [Hash, Array, nil] :arguments
|
|
21
|
+
# @option options [String, nil] :model
|
|
20
22
|
# @return [LLM::Function::Ractor::Task]
|
|
21
|
-
def initialize(
|
|
22
|
-
|
|
23
|
-
@
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
@
|
|
27
|
-
@
|
|
23
|
+
def initialize(fn, options = {})
|
|
24
|
+
super
|
|
25
|
+
@runner_class = options.fetch(:runner_class)
|
|
26
|
+
@id = options.fetch(:id)
|
|
27
|
+
@name = options.fetch(:name)
|
|
28
|
+
@arguments = options.fetch(:arguments)
|
|
29
|
+
@model = options.fetch(:model, nil)
|
|
30
|
+
@tracer = options.fetch(:tracer, nil)
|
|
28
31
|
end
|
|
29
32
|
|
|
30
33
|
##
|
|
31
34
|
# @return [LLM::Function::Ractor::Task]
|
|
32
35
|
def spawn
|
|
36
|
+
@span = @tracer&.on_tool_start(
|
|
37
|
+
id: @id, name: @name,
|
|
38
|
+
arguments: @arguments, model: @model
|
|
39
|
+
)
|
|
33
40
|
@mailbox = Ractor::Mailbox.new(build_task)
|
|
34
41
|
self
|
|
35
42
|
end
|
|
@@ -37,7 +44,7 @@ class LLM::Function
|
|
|
37
44
|
##
|
|
38
45
|
# @return [Boolean]
|
|
39
46
|
def alive?
|
|
40
|
-
mailbox
|
|
47
|
+
@mailbox&.alive? || false
|
|
41
48
|
end
|
|
42
49
|
|
|
43
50
|
##
|
|
@@ -51,6 +58,7 @@ class LLM::Function
|
|
|
51
58
|
##
|
|
52
59
|
# @return [LLM::Function::Return]
|
|
53
60
|
def wait
|
|
61
|
+
spawn unless @mailbox
|
|
54
62
|
id, name, value = mailbox.wait
|
|
55
63
|
result = Return.new(id, name, value)
|
|
56
64
|
@tracer&.on_tool_finish(result:, span: @span)
|
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
module LLM::Function::Sequential
|
|
4
4
|
##
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# It provides the same basic interface as the concurrent group
|
|
5
|
+
# Wraps an array of {LLM::Function} objects for sequential
|
|
6
|
+
# execution. Provides the same interface as concurrent group
|
|
9
7
|
# wrappers so callers can flow through `spawn(strategy).wait`
|
|
10
|
-
# uniformly
|
|
11
|
-
class
|
|
8
|
+
# uniformly.
|
|
9
|
+
class Group < LLM::Function::Group
|
|
12
10
|
##
|
|
13
11
|
# @param [Array<LLM::Function>] functions
|
|
14
|
-
# @return [LLM::Function::CallGroup]
|
|
15
12
|
def initialize(functions)
|
|
16
13
|
@functions = functions
|
|
17
14
|
@owner = nil
|
|
18
15
|
end
|
|
19
16
|
|
|
17
|
+
##
|
|
18
|
+
# @return [nil]
|
|
19
|
+
def spawn
|
|
20
|
+
# no-op — execution happens in wait
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
20
24
|
##
|
|
21
25
|
# @return [Boolean]
|
|
22
26
|
def alive?
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module LLM::Function::Sequential
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Function::Sequential::Task} class wraps a single
|
|
6
|
+
# direct function call behind the same task-like interface
|
|
7
|
+
# used by spawned concurrency modes.
|
|
8
|
+
class Task < LLM::Function::Task
|
|
9
|
+
##
|
|
10
|
+
# @param [LLM::Function] fn
|
|
11
|
+
# @param [Hash] options
|
|
12
|
+
def initialize(fn, options = {})
|
|
13
|
+
@function = fn
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# @return [nil]
|
|
18
|
+
def spawn
|
|
19
|
+
# no-op — execution happens in wait
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# @return [Boolean]
|
|
24
|
+
def alive?
|
|
25
|
+
false
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# @return [nil]
|
|
30
|
+
def interrupt!
|
|
31
|
+
function.interrupt!
|
|
32
|
+
nil
|
|
33
|
+
end
|
|
34
|
+
alias_method :cancel!, :interrupt!
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @return [LLM::Function::Return]
|
|
38
|
+
def wait
|
|
39
|
+
@result ||= function.call
|
|
40
|
+
end
|
|
41
|
+
alias_method :value, :wait
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# @return [Class]
|
|
45
|
+
def group_class
|
|
46
|
+
LLM::Function::Sequential::Group
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
data/lib/llm/function/task.rb
CHANGED
|
@@ -2,82 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
class LLM::Function
|
|
4
4
|
##
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# This class is the superclass that all concurrency
|
|
6
|
+
# strategies must subclass in order to implement their
|
|
7
|
+
# own Task class. It provides a common interface that
|
|
8
|
+
# is the same across all concurrency strategies.
|
|
8
9
|
class Task
|
|
9
10
|
##
|
|
10
|
-
# @return [
|
|
11
|
-
attr_reader :
|
|
11
|
+
# @return [LLM::Function]
|
|
12
|
+
attr_reader :function
|
|
12
13
|
|
|
13
14
|
##
|
|
14
|
-
# @
|
|
15
|
-
|
|
15
|
+
# @param [LLM::Function] fn
|
|
16
|
+
# @param [Hash] options
|
|
17
|
+
# An optional set of options that are specific
|
|
18
|
+
# to a given concurrency strategy.
|
|
19
|
+
def initialize(fn, options = {})
|
|
20
|
+
@function = fn
|
|
21
|
+
end
|
|
16
22
|
|
|
17
23
|
##
|
|
18
|
-
# @
|
|
19
|
-
# @
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@task = task
|
|
23
|
-
@function = function
|
|
24
|
+
# @abstract
|
|
25
|
+
# @return [nil]
|
|
26
|
+
def spawn
|
|
27
|
+
raise NotImplementedError
|
|
24
28
|
end
|
|
25
29
|
|
|
26
30
|
##
|
|
31
|
+
# @abstract
|
|
27
32
|
# @return [Boolean]
|
|
28
33
|
def alive?
|
|
29
|
-
|
|
30
|
-
false
|
|
34
|
+
raise NotImplementedError
|
|
31
35
|
end
|
|
32
36
|
|
|
33
37
|
##
|
|
38
|
+
# @abstract
|
|
34
39
|
# @return [nil]
|
|
35
40
|
def interrupt!
|
|
36
|
-
|
|
37
|
-
when Thread
|
|
38
|
-
task.raise(LLM::Interrupt)
|
|
39
|
-
when Fiber
|
|
40
|
-
task.raise(LLM::Interrupt) if task.alive?
|
|
41
|
-
else
|
|
42
|
-
if defined?(::Async::Task) and ::Async::Task === task
|
|
43
|
-
task.fiber&.raise(LLM::Interrupt) if task.alive?
|
|
44
|
-
elsif task.respond_to?(:interrupt!)
|
|
45
|
-
task.interrupt!
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
function&.interrupt!
|
|
49
|
-
nil
|
|
41
|
+
raise NotImplementedError
|
|
50
42
|
end
|
|
51
43
|
alias_method :cancel!, :interrupt!
|
|
52
44
|
|
|
53
45
|
##
|
|
46
|
+
# @abstract
|
|
54
47
|
# @return [LLM::Function::Return]
|
|
55
48
|
def wait
|
|
56
|
-
|
|
57
|
-
task.value
|
|
58
|
-
elsif Fiber === task
|
|
59
|
-
fiber.alive? ? scheduler.run : nil
|
|
60
|
-
task.value
|
|
61
|
-
else
|
|
62
|
-
task.wait
|
|
63
|
-
end
|
|
49
|
+
raise NotImplementedError
|
|
64
50
|
end
|
|
65
51
|
alias_method :value, :wait
|
|
66
52
|
|
|
67
53
|
##
|
|
54
|
+
# @abstract
|
|
68
55
|
# @return [Class]
|
|
69
56
|
def group_class
|
|
70
|
-
|
|
71
|
-
when Thread then LLM::Function::ThreadGroup
|
|
72
|
-
when Fiber then LLM::Function::FiberGroup
|
|
73
|
-
else LLM::Function::TaskGroup
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
private
|
|
78
|
-
|
|
79
|
-
def scheduler
|
|
80
|
-
Fiber.scheduler
|
|
57
|
+
raise NotImplementedError
|
|
81
58
|
end
|
|
82
59
|
end
|
|
83
60
|
end
|