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.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +387 -0
  3. data/LICENSE +21 -93
  4. data/README.md +46 -155
  5. data/data/deepinfra.json +3 -0
  6. data/data/xai.json +1 -1
  7. data/lib/llm/a2a.rb +1 -1
  8. data/lib/llm/active_record/acts_as_llm.rb +6 -6
  9. data/lib/llm/agent.rb +62 -23
  10. data/lib/llm/buffer.rb +85 -3
  11. data/lib/llm/compactor/null.rb +19 -0
  12. data/lib/llm/compactor/truncate.rb +80 -0
  13. data/lib/llm/compactor.rb +42 -124
  14. data/lib/llm/context.rb +31 -37
  15. data/lib/llm/contract.rb +4 -25
  16. data/lib/llm/function/array.rb +15 -14
  17. data/lib/llm/function/async/group.rb +54 -0
  18. data/lib/llm/function/async/reactor.rb +48 -0
  19. data/lib/llm/function/async/task.rb +83 -0
  20. data/lib/llm/function/fiber/group.rb +46 -0
  21. data/lib/llm/function/fiber/task.rb +62 -0
  22. data/lib/llm/function/{fork_group.rb → fork/group.rb} +14 -5
  23. data/lib/llm/function/fork/job.rb +2 -2
  24. data/lib/llm/function/fork/task.rb +19 -10
  25. data/lib/llm/function/group.rb +40 -0
  26. data/lib/llm/function/{ractor_group.rb → ractor/group.rb} +13 -5
  27. data/lib/llm/function/ractor/job.rb +9 -3
  28. data/lib/llm/function/ractor/mailbox.rb +2 -0
  29. data/lib/llm/function/ractor/task.rb +23 -15
  30. data/lib/llm/function/{call_group.rb → sequential/group.rb} +12 -8
  31. data/lib/llm/function/sequential/task.rb +49 -0
  32. data/lib/llm/function/task.rb +25 -48
  33. data/lib/llm/function/thread/group.rb +46 -0
  34. data/lib/llm/function/thread/task.rb +60 -0
  35. data/lib/llm/function.rb +54 -64
  36. data/lib/llm/loop_guard.rb +1 -2
  37. data/lib/llm/mcp.rb +22 -0
  38. data/lib/llm/object.rb +2 -1
  39. data/lib/llm/provider.rb +6 -3
  40. data/lib/llm/providers/google.rb +2 -2
  41. data/lib/llm/repl/command.rb +35 -8
  42. data/lib/llm/repl/commands/compact.rb +33 -0
  43. data/lib/llm/repl/input.rb +80 -15
  44. data/lib/llm/repl/markdown/table.rb +76 -0
  45. data/lib/llm/repl/markdown.rb +31 -1
  46. data/lib/llm/repl/status.rb +1 -1
  47. data/lib/llm/repl/stream.rb +10 -3
  48. data/lib/llm/repl/transcript.rb +1 -1
  49. data/lib/llm/repl/walker.rb +46 -0
  50. data/lib/llm/repl.rb +18 -12
  51. data/lib/llm/response.rb +10 -0
  52. data/lib/llm/schema/leaf.rb +5 -0
  53. data/lib/llm/schema/object.rb +11 -5
  54. data/lib/llm/sequel/plugin.rb +6 -6
  55. data/lib/llm/stream.rb +24 -17
  56. data/lib/llm/tool.rb +20 -4
  57. data/lib/llm/tools/chdir.rb +0 -2
  58. data/lib/llm/tools/git.rb +8 -4
  59. data/lib/llm/tools/mkdir.rb +1 -1
  60. data/lib/llm/tools/pwd.rb +0 -2
  61. data/lib/llm/tools/read_file.rb +0 -2
  62. data/lib/llm/tools/rg.rb +8 -4
  63. data/lib/llm/tools/shell.rb +8 -4
  64. data/lib/llm/tools/utils.rb +31 -0
  65. data/lib/llm/version.rb +1 -1
  66. data/lib/llm.rb +25 -5
  67. data/llm.gemspec +3 -3
  68. data/resources/deepdive.md +645 -58
  69. metadata +24 -13
  70. data/lib/llm/function/call_task.rb +0 -46
  71. data/lib/llm/function/fiber_group.rb +0 -105
  72. data/lib/llm/function/task_group.rb +0 -97
  73. data/lib/llm/function/thread_group.rb +0 -102
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 12.6.0
4
+ version: 13.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - robert
@@ -73,14 +73,14 @@ dependencies:
73
73
  requirements:
74
74
  - - "~>"
75
75
  - !ruby/object:Gem::Version
76
- version: 0.12.0
76
+ version: 1.1.0
77
77
  type: :development
78
78
  prerelease: false
79
79
  version_requirements: !ruby/object:Gem::Requirement
80
80
  requirements:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
- version: 0.12.0
83
+ version: 1.1.0
84
84
  - !ruby/object:Gem::Dependency
85
85
  name: rake
86
86
  requirement: !ruby/object:Gem::Requirement
@@ -241,14 +241,14 @@ dependencies:
241
241
  requirements:
242
242
  - - "~>"
243
243
  - !ruby/object:Gem::Version
244
- version: '0.20'
244
+ version: '0.22'
245
245
  type: :development
246
246
  prerelease: false
247
247
  version_requirements: !ruby/object:Gem::Requirement
248
248
  requirements:
249
249
  - - "~>"
250
250
  - !ruby/object:Gem::Version
251
- version: '0.20'
251
+ version: '0.22'
252
252
  - !ruby/object:Gem::Dependency
253
253
  name: pg
254
254
  requirement: !ruby/object:Gem::Requirement
@@ -317,6 +317,8 @@ files:
317
317
  - lib/llm/agent.rb
318
318
  - lib/llm/buffer.rb
319
319
  - lib/llm/compactor.rb
320
+ - lib/llm/compactor/null.rb
321
+ - lib/llm/compactor/truncate.rb
320
322
  - lib/llm/context.rb
321
323
  - lib/llm/context/deserializer.rb
322
324
  - lib/llm/context/serializer.rb
@@ -331,22 +333,27 @@ files:
331
333
  - lib/llm/file.rb
332
334
  - lib/llm/function.rb
333
335
  - lib/llm/function/array.rb
334
- - lib/llm/function/call_group.rb
335
- - lib/llm/function/call_task.rb
336
- - lib/llm/function/fiber_group.rb
336
+ - lib/llm/function/async/group.rb
337
+ - lib/llm/function/async/reactor.rb
338
+ - lib/llm/function/async/task.rb
339
+ - lib/llm/function/fiber/group.rb
340
+ - lib/llm/function/fiber/task.rb
337
341
  - lib/llm/function/fork.rb
342
+ - lib/llm/function/fork/group.rb
338
343
  - lib/llm/function/fork/job.rb
339
344
  - lib/llm/function/fork/task.rb
340
- - lib/llm/function/fork_group.rb
345
+ - lib/llm/function/group.rb
341
346
  - lib/llm/function/ractor.rb
347
+ - lib/llm/function/ractor/group.rb
342
348
  - lib/llm/function/ractor/job.rb
343
349
  - lib/llm/function/ractor/mailbox.rb
344
350
  - lib/llm/function/ractor/task.rb
345
- - lib/llm/function/ractor_group.rb
346
351
  - lib/llm/function/registry.rb
352
+ - lib/llm/function/sequential/group.rb
353
+ - lib/llm/function/sequential/task.rb
347
354
  - lib/llm/function/task.rb
348
- - lib/llm/function/task_group.rb
349
- - lib/llm/function/thread_group.rb
355
+ - lib/llm/function/thread/group.rb
356
+ - lib/llm/function/thread/task.rb
350
357
  - lib/llm/function/tracing.rb
351
358
  - lib/llm/json_adapter.rb
352
359
  - lib/llm/loop_guard.rb
@@ -469,13 +476,16 @@ files:
469
476
  - lib/llm/repl.rb
470
477
  - lib/llm/repl/bar.rb
471
478
  - lib/llm/repl/command.rb
479
+ - lib/llm/repl/commands/compact.rb
472
480
  - lib/llm/repl/commands/exit.rb
473
481
  - lib/llm/repl/commands/help.rb
474
482
  - lib/llm/repl/input.rb
475
483
  - lib/llm/repl/markdown.rb
484
+ - lib/llm/repl/markdown/table.rb
476
485
  - lib/llm/repl/status.rb
477
486
  - lib/llm/repl/stream.rb
478
487
  - lib/llm/repl/transcript.rb
488
+ - lib/llm/repl/walker.rb
479
489
  - lib/llm/repl/window.rb
480
490
  - lib/llm/response.rb
481
491
  - lib/llm/schema.rb
@@ -515,6 +525,7 @@ files:
515
525
  - lib/llm/tools/rg.rb
516
526
  - lib/llm/tools/shell.rb
517
527
  - lib/llm/tools/swap_text.rb
528
+ - lib/llm/tools/utils.rb
518
529
  - lib/llm/tools/which.rb
519
530
  - lib/llm/tools/write_file.rb
520
531
  - lib/llm/tracer.rb
@@ -543,7 +554,7 @@ files:
543
554
  - resources/deepdive.md
544
555
  homepage: https://r.uby.dev/llm/
545
556
  licenses:
546
- - BUSL-1.1
557
+ - MIT
547
558
  metadata:
548
559
  homepage_uri: https://r.uby.dev/llm/
549
560
  source_code_uri: https://github.com/r-uby-dev/llm.rb
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # The {LLM::Function::CallTask} class wraps a single direct function call
6
- # behind the same task-like interface used by spawned concurrency modes.
7
- class CallTask
8
- ##
9
- # @return [LLM::Function]
10
- attr_reader :function
11
-
12
- ##
13
- # @param [LLM::Function] function
14
- # @return [LLM::Function::CallTask]
15
- def initialize(function)
16
- @function = function
17
- end
18
-
19
- ##
20
- # @return [Boolean]
21
- def alive?
22
- false
23
- end
24
-
25
- ##
26
- # @return [nil]
27
- def interrupt!
28
- function.interrupt!
29
- nil
30
- end
31
- alias_method :cancel!, :interrupt!
32
-
33
- ##
34
- # @return [LLM::Function::Return]
35
- def wait
36
- function.call
37
- end
38
- alias_method :value, :wait
39
-
40
- ##
41
- # @return [Class]
42
- def group_class
43
- LLM::Function::TaskGroup
44
- end
45
- end
46
- end
@@ -1,105 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # The {LLM::Function::FiberGroup} class wraps an array of
6
- # `Fiber` objects that are running {LLM::Function} calls
7
- # concurrently using scheduler-backed fibers.
8
- #
9
- # This class provides the same interface as {LLM::Function::ThreadGroup}
10
- # but uses scheduler-backed fibers for cooperative concurrency.
11
- #
12
- # @example
13
- # llm = LLM.openai(key: ENV["KEY"])
14
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
15
- # ctx.talk "Summarize the weather, headlines, and stock price."
16
- # grp = ctx.functions.spawn(:fiber)
17
- # # do other work while tools run...
18
- # ctx.talk(grp.wait)
19
- #
20
- # @see LLM::Function::Array#spawn
21
- # @see LLM::Function::ThreadGroup
22
- # @see LLM::Function::TaskGroup
23
- class FiberGroup
24
- ##
25
- # Creates a new {LLM::Function::FiberGroup} from an array
26
- # of fiber objects.
27
- #
28
- # @param [Array<Fiber>] fibers
29
- # An array of fibers, each running an `LLM::Function#spawn_fiber` call.
30
- #
31
- # @return [LLM::Function::FiberGroup]
32
- # Returns a new fiber group.
33
- def initialize(fibers)
34
- @fibers = fibers
35
- end
36
-
37
- ##
38
- # Returns whether any fiber in the group is still alive.
39
- #
40
- # This method checks if any of the fibers in the group are
41
- # still running. It can be useful for monitoring concurrent
42
- # tool execution without blocking.
43
- #
44
- # @example
45
- # llm = LLM.openai(key: ENV["KEY"])
46
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
47
- # ctx.talk "Summarize the weather, headlines, and stock price."
48
- # grp = ctx.functions.spawn(:fiber)
49
- # while grp.alive?
50
- # puts "Tools are still running..."
51
- # sleep 1
52
- # end
53
- # ctx.talk(grp.wait)
54
- #
55
- # @return [Boolean]
56
- # Returns true if any fiber in the group is still alive,
57
- # false otherwise.
58
- def alive?
59
- @fibers.any?(&:alive?)
60
- end
61
-
62
- ##
63
- # @return [nil]
64
- def interrupt!
65
- @fibers.each { _1.task.raise(LLM::Interrupt) if _1.task.alive? }
66
- nil
67
- end
68
- alias_method :cancel!, :interrupt!
69
-
70
- ##
71
- # Waits for all fibers in the group to finish and returns
72
- # their {LLM::Function::Return} values.
73
- #
74
- # This method blocks until every fiber in the group has
75
- # completed. If a fiber raised an exception, the exception
76
- # is caught and wrapped in an {LLM::Function::Return} with
77
- # error information.
78
- #
79
- # @example
80
- # llm = LLM.openai(key: ENV["KEY"])
81
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
82
- # ctx.talk "Summarize the weather, headlines, and stock price."
83
- # grp = ctx.functions.spawn(:fiber)
84
- # returns = grp.wait
85
- # # returns is now an array of LLM::Function::Return objects
86
- # ctx.talk(returns)
87
- #
88
- # @return [Array<LLM::Function::Return>]
89
- # Returns an array of function return values, in the same
90
- # order as the original fibers.
91
- def wait
92
- @fibers.map do |fiber|
93
- fiber.alive? ? scheduler.run : nil
94
- fiber.value
95
- end
96
- end
97
- alias_method :value, :wait
98
-
99
- private
100
-
101
- def scheduler
102
- Fiber.scheduler
103
- end
104
- end
105
- end
@@ -1,97 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # The {LLM::Function::TaskGroup} class wraps an array of
6
- # `Async::Task` objects that are running {LLM::Function} calls
7
- # concurrently using the async gem.
8
- #
9
- # This class provides the same interface as {LLM::Function::ThreadGroup}
10
- # but uses async tasks for lightweight concurrency with automatic
11
- # scheduling and I/O management.
12
- #
13
- # @example
14
- # llm = LLM.openai(key: ENV["KEY"])
15
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
16
- # ctx.talk "Summarize the weather, headlines, and stock price."
17
- # grp = ctx.functions.spawn(:task)
18
- # # do other work while tools run...
19
- # ctx.talk(grp.wait)
20
- #
21
- # @see LLM::Function::Array#spawn
22
- # @see LLM::Function::ThreadGroup
23
- # @see LLM::Function::FiberGroup
24
- class TaskGroup
25
- ##
26
- # Creates a new {LLM::Function::TaskGroup} from an array
27
- # of async task objects.
28
- #
29
- # @param [Array<Async::Task>] tasks
30
- # An array of async tasks, each running an `LLM::Function#spawn_async` call.
31
- #
32
- # @return [LLM::Function::TaskGroup]
33
- # Returns a new task group.
34
- def initialize(tasks)
35
- @tasks = tasks
36
- end
37
-
38
- ##
39
- # Returns whether any task in the group is still alive.
40
- #
41
- # This method checks if any of the tasks in the group are
42
- # still running. It can be useful for monitoring concurrent
43
- # tool execution without blocking.
44
- #
45
- # @example
46
- # llm = LLM.openai(key: ENV["KEY"])
47
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
48
- # ctx.talk "Summarize the weather, headlines, and stock price."
49
- # grp = ctx.functions.spawn(:task)
50
- # while grp.alive?
51
- # puts "Tools are still running..."
52
- # sleep 1
53
- # end
54
- # ctx.talk(grp.wait)
55
- #
56
- # @return [Boolean]
57
- # Returns true if any task in the group is still alive,
58
- # false otherwise.
59
- def alive?
60
- @tasks.any?(&:alive?)
61
- end
62
-
63
- ##
64
- # @return [nil]
65
- def interrupt!
66
- @tasks.each(&:interrupt!)
67
- nil
68
- end
69
- alias_method :cancel!, :interrupt!
70
-
71
- ##
72
- # Waits for all tasks in the group to finish and returns
73
- # their {LLM::Function::Return} values.
74
- #
75
- # This method blocks until every task in the group has
76
- # completed. If a task raised an exception, the exception
77
- # is caught and wrapped in an {LLM::Function::Return} with
78
- # error information.
79
- #
80
- # @example
81
- # llm = LLM.openai(key: ENV["KEY"])
82
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
83
- # ctx.talk "Summarize the weather, headlines, and stock price."
84
- # grp = ctx.functions.spawn(:task)
85
- # returns = grp.wait
86
- # # returns is now an array of LLM::Function::Return objects
87
- # ctx.talk(returns)
88
- #
89
- # @return [Array<LLM::Function::Return>]
90
- # Returns an array of function return values, in the same
91
- # order as the original tasks.
92
- def wait
93
- @tasks.map(&:wait)
94
- end
95
- alias_method :value, :wait
96
- end
97
- end
@@ -1,102 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class LLM::Function
4
- ##
5
- # The {LLM::Function::ThreadGroup} class wraps an array of
6
- # `Thread` objects that are running {LLM::Function} calls
7
- # concurrently. It provides a single {#wait} method that
8
- # collects the {LLM::Function::Return} values from those
9
- # threads.
10
- #
11
- # This class is returned by {LLM::Function::Array#spawn}
12
- # when you call `ctx.functions.spawn` on the collection
13
- # returned by {LLM::Context#functions}. It is a lightweight
14
- # wrapper that does not inherit from Ruby's built-in
15
- # {::ThreadGroup}.
16
- #
17
- # @example
18
- # llm = LLM.openai(key: ENV["KEY"])
19
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
20
- # ctx.talk "Summarize the weather, headlines, and stock price."
21
- # grp = ctx.functions.spawn
22
- # # do other work while tools run...
23
- # ctx.talk(grp.wait)
24
- #
25
- # @see LLM::Function::Array#spawn
26
- # @see LLM::Function::Array#wait
27
- class ThreadGroup
28
- ##
29
- # Creates a new {LLM::Function::ThreadGroup} from an array
30
- # of `Thread` objects.
31
- #
32
- # @param [Array<Thread>] threads
33
- # An array of threads, each running an {LLM::Function#spawn}
34
- # call. The thread's `Thread#value` will be an
35
- # {LLM::Function::Return}.
36
- #
37
- # @return [LLM::Function::ThreadGroup]
38
- # Returns a new thread group.
39
- def initialize(threads)
40
- @threads = threads
41
- end
42
-
43
- ##
44
- # Returns whether any thread in the group is still alive.
45
- #
46
- # This method checks if any of the threads in the group are
47
- # still running. It can be useful for monitoring concurrent
48
- # tool execution without blocking.
49
- #
50
- # @example
51
- # llm = LLM.openai(key: ENV["KEY"])
52
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
53
- # ctx.talk "Summarize the weather, headlines, and stock price."
54
- # grp = ctx.functions.spawn
55
- # while grp.alive?
56
- # puts "Tools are still running..."
57
- # sleep 1
58
- # end
59
- # ctx.talk(grp.wait)
60
- #
61
- # @return [Boolean]
62
- # Returns true if any thread in the group is still alive,
63
- # false otherwise.
64
- def alive?
65
- @threads.any?(&:alive?)
66
- end
67
-
68
- ##
69
- # @return [nil]
70
- def interrupt!
71
- @threads.each { _1.task.raise LLM::Interrupt }
72
- nil
73
- end
74
- alias_method :cancel!, :interrupt!
75
-
76
- ##
77
- # Waits for all threads in the group to finish and returns
78
- # their {LLM::Function::Return} values.
79
- #
80
- # This method blocks until every thread in the group has
81
- # completed. If a thread raised an exception, the exception
82
- # is caught and wrapped in an {LLM::Function::Return} with
83
- # error information.
84
- #
85
- # @example
86
- # llm = LLM.openai(key: ENV["KEY"])
87
- # ctx = LLM::Context.new(llm, tools: [Weather, News, Stocks])
88
- # ctx.talk "Summarize the weather, headlines, and stock price."
89
- # grp = ctx.functions.spawn
90
- # returns = grp.wait
91
- # # returns is now an array of LLM::Function::Return objects
92
- # ctx.talk(returns)
93
- #
94
- # @return [Array<LLM::Function::Return>]
95
- # Returns an array of function return values, in the same
96
- # order as the original threads.
97
- def wait
98
- @threads.map(&:value)
99
- end
100
- alias_method :value, :wait
101
- end
102
- end