llm.rb 12.1.0 → 12.3.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 +248 -0
- data/README.md +77 -27
- data/data/anthropic.json +3 -2
- data/data/bedrock.json +47 -0
- data/data/deepinfra.json +2 -2
- data/data/google.json +32 -0
- data/data/mistral.json +968 -0
- data/data/openai.json +422 -0
- data/data/xai.json +60 -1
- data/lib/llm/active_record/acts_as_agent.rb +11 -0
- data/lib/llm/agent.rb +27 -3
- data/lib/llm/compactor.rb +2 -2
- data/lib/llm/context.rb +18 -26
- data/lib/llm/function.rb +6 -0
- data/lib/llm/object.rb +13 -0
- data/lib/llm/provider.rb +12 -9
- data/lib/llm/providers/anthropic.rb +4 -5
- data/lib/llm/providers/bedrock.rb +4 -5
- data/lib/llm/providers/google.rb +7 -3
- data/lib/llm/providers/mistral/request_adapter/completion.rb +122 -0
- data/lib/llm/providers/mistral/request_adapter.rb +20 -0
- data/lib/llm/providers/mistral.rb +145 -0
- data/lib/llm/providers/ollama/response_adapter/completion.rb +39 -0
- data/lib/llm/providers/ollama.rb +2 -3
- data/lib/llm/providers/openai/responses.rb +6 -3
- data/lib/llm/providers/openai.rb +2 -3
- data/lib/llm/repl/bar.rb +52 -0
- data/lib/llm/repl/input.rb +143 -30
- data/lib/llm/repl/markdown.rb +85 -0
- data/lib/llm/repl/status.rb +16 -5
- data/lib/llm/repl/stream.rb +15 -5
- data/lib/llm/repl/transcript.rb +107 -18
- data/lib/llm/repl/window.rb +43 -17
- data/lib/llm/repl.rb +99 -18
- data/lib/llm/sequel/agent.rb +11 -0
- data/lib/llm/skill.rb +1 -1
- data/lib/llm/stream/disabled.rb +23 -0
- data/lib/llm/stream/io.rb +43 -0
- data/lib/llm/stream.rb +34 -0
- data/lib/llm/tools/git.rb +2 -3
- data/lib/llm/tools/pwd.rb +0 -1
- data/lib/llm/tools/rg.rb +2 -1
- data/lib/llm/tools/swap_text.rb +6 -0
- data/lib/llm/transport/execution.rb +1 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +19 -0
- data/llm.gemspec +8 -4
- data/resources/deepdive.md +170 -50
- metadata +15 -4
data/lib/llm/repl.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
LLM.require "curses"
|
|
4
|
+
LLM.require "kramdown"
|
|
4
5
|
|
|
5
6
|
module LLM
|
|
6
7
|
##
|
|
@@ -18,19 +19,37 @@ module LLM
|
|
|
18
19
|
require_relative "repl/status"
|
|
19
20
|
require_relative "repl/transcript"
|
|
20
21
|
require_relative "repl/input"
|
|
22
|
+
require_relative "repl/bar"
|
|
21
23
|
require_relative "repl/stream"
|
|
24
|
+
require_relative "repl/markdown"
|
|
22
25
|
|
|
23
26
|
##
|
|
24
27
|
# @param [LLM::Agent] agent
|
|
28
|
+
# @param [Array<LLM::Tool>] tools
|
|
29
|
+
# Zero or more tools
|
|
30
|
+
# @param [Array<String>] skills
|
|
31
|
+
# Zero or more skills
|
|
25
32
|
# @return [LLM::Repl]
|
|
26
|
-
def initialize(agent)
|
|
33
|
+
def initialize(agent:, tools:, skills:)
|
|
27
34
|
@agent = agent
|
|
28
35
|
@provider = agent.llm.name
|
|
29
|
-
@status = Status.new(@
|
|
36
|
+
@status = Status.new(@agent)
|
|
30
37
|
@transcript = Transcript.new
|
|
31
|
-
@input = Input.new(@
|
|
38
|
+
@input = Input.new(@agent, height: 3)
|
|
32
39
|
@window = Window.new(@status, @transcript, @input)
|
|
33
|
-
@
|
|
40
|
+
@thread = nil
|
|
41
|
+
@queue = Queue.new
|
|
42
|
+
@stream = Stream.new(self, @queue)
|
|
43
|
+
@skills = skills.map do |path|
|
|
44
|
+
##
|
|
45
|
+
# I'm not sure it would make sense to expose
|
|
46
|
+
# the underlying context or not. In the meantime,
|
|
47
|
+
# this works and meets the expectations of the
|
|
48
|
+
# LLM::Skill class.
|
|
49
|
+
ctx = agent.instance_variable_get(:@ctx)
|
|
50
|
+
LLM::Skill.load(path).to_tool(ctx)
|
|
51
|
+
end
|
|
52
|
+
@tools = [agent.params[:tools], @skills, tools].flatten.compact
|
|
34
53
|
end
|
|
35
54
|
|
|
36
55
|
##
|
|
@@ -38,26 +57,33 @@ module LLM
|
|
|
38
57
|
def start
|
|
39
58
|
window.open do
|
|
40
59
|
loop do
|
|
41
|
-
window.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
status.text = "idle"
|
|
51
|
-
write("\n\n")
|
|
60
|
+
case input.on_char(window, window.getch)
|
|
61
|
+
when :exit then break
|
|
62
|
+
when :submit then submit
|
|
63
|
+
when :up, :down, :backspace, :char then window.redraw
|
|
64
|
+
else
|
|
65
|
+
window.redraw
|
|
66
|
+
read!
|
|
67
|
+
sleep 0.01
|
|
68
|
+
end
|
|
52
69
|
end
|
|
53
70
|
end
|
|
54
71
|
end
|
|
55
72
|
|
|
73
|
+
##
|
|
74
|
+
# @param [String] chars
|
|
75
|
+
# @param [Object] attrs
|
|
76
|
+
# @return [void]
|
|
77
|
+
def write(chars, attrs = nil)
|
|
78
|
+
transcript.write(chars, attrs)
|
|
79
|
+
window.redraw
|
|
80
|
+
end
|
|
81
|
+
|
|
56
82
|
##
|
|
57
83
|
# @param [String] chars
|
|
58
84
|
# @return [void]
|
|
59
|
-
def
|
|
60
|
-
transcript.
|
|
85
|
+
def markdown(chars)
|
|
86
|
+
transcript.markdown(chars)
|
|
61
87
|
window.redraw
|
|
62
88
|
end
|
|
63
89
|
|
|
@@ -71,8 +97,63 @@ module LLM
|
|
|
71
97
|
|
|
72
98
|
private
|
|
73
99
|
|
|
100
|
+
##
|
|
101
|
+
# This method is called when the user submits their input.
|
|
102
|
+
# It spawns a second thread that maintains a line of
|
|
103
|
+
# communication with a model and the main thread - where
|
|
104
|
+
# the UI runs - remains responsive.
|
|
105
|
+
# @api private
|
|
106
|
+
def submit
|
|
107
|
+
return if thread&.alive?
|
|
108
|
+
text = input.take
|
|
109
|
+
return if text.empty?
|
|
110
|
+
status.text = "thinking"
|
|
111
|
+
write("user: ", Curses::A_BOLD)
|
|
112
|
+
markdown(text)
|
|
113
|
+
write("\nagent: ", Curses::A_BOLD)
|
|
114
|
+
@thread = Thread.new do
|
|
115
|
+
@queue << [:start]
|
|
116
|
+
agent.talk(text, tools:, stream:)
|
|
117
|
+
@queue << [:done]
|
|
118
|
+
rescue => e
|
|
119
|
+
@queue << [:error, e]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
##
|
|
124
|
+
# This method reads from the queue that is written to
|
|
125
|
+
# by a second thread. The queue is managed or written
|
|
126
|
+
# to by a subclass of {LLM::Stream LLM::Stream}.
|
|
127
|
+
# @api private
|
|
128
|
+
def read!
|
|
129
|
+
loop do
|
|
130
|
+
type, value = @queue.pop(true)
|
|
131
|
+
case type
|
|
132
|
+
when :start
|
|
133
|
+
transcript.start
|
|
134
|
+
stream.empty!
|
|
135
|
+
when :stream
|
|
136
|
+
transcript.markdown(value, method: :replace)
|
|
137
|
+
when :status
|
|
138
|
+
self.status = value
|
|
139
|
+
when :done
|
|
140
|
+
status.text = "idle"
|
|
141
|
+
@thread = nil
|
|
142
|
+
transcript.finish
|
|
143
|
+
write("\n\n")
|
|
144
|
+
when :error
|
|
145
|
+
# Do this better
|
|
146
|
+
status.text = "error"
|
|
147
|
+
transcript.finish
|
|
148
|
+
write("\nerror: #{value.message}\n", Curses::A_BOLD)
|
|
149
|
+
@thread = nil
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
rescue ThreadError
|
|
153
|
+
end
|
|
154
|
+
|
|
74
155
|
attr_reader :agent, :provider, :stream,
|
|
75
156
|
:status, :transcript, :input,
|
|
76
|
-
:window
|
|
157
|
+
:window, :tools, :thread
|
|
77
158
|
end
|
|
78
159
|
end
|
data/lib/llm/sequel/agent.rb
CHANGED
|
@@ -38,6 +38,17 @@ module LLM::Sequel
|
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
module InstanceMethods
|
|
41
|
+
##
|
|
42
|
+
# @note
|
|
43
|
+
# This method does not persist to the database,
|
|
44
|
+
# but it can inspect and alter runtime state in
|
|
45
|
+
# a way that is temporary.
|
|
46
|
+
# @param (see LLM::Agent#repl)
|
|
47
|
+
# @return (see LLM::Agent#repl)
|
|
48
|
+
def repl(**params)
|
|
49
|
+
ctx.repl(**params)
|
|
50
|
+
end
|
|
51
|
+
|
|
41
52
|
private
|
|
42
53
|
|
|
43
54
|
def ctx
|
data/lib/llm/skill.rb
CHANGED
|
@@ -155,7 +155,7 @@ module LLM
|
|
|
155
155
|
def agent(ctx)
|
|
156
156
|
instructions, tools, tracer, inherit_tools = self.instructions, self.tools, ctx.llm.tracer, inherit_tools?
|
|
157
157
|
params = ctx.params.merge(mode: ctx.mode).reject { [:tools, :schema].include?(_1) }
|
|
158
|
-
concurrency = params[:stream].extra[:concurrency]
|
|
158
|
+
concurrency = params[:stream].extra[:concurrency]
|
|
159
159
|
params[:concurrency] = concurrency if concurrency
|
|
160
160
|
agent = Class.new(LLM::Agent) do
|
|
161
161
|
instructions(instructions)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Stream
|
|
4
|
+
##
|
|
5
|
+
# An {LLM::Stream::Disabled LLM::Stream::Disabled} represents a stream
|
|
6
|
+
# that is explicitly turned off.
|
|
7
|
+
#
|
|
8
|
+
# {LLM::Stream.try} returns an instance of this class when given `false`
|
|
9
|
+
# or `nil`. Providers check {#enabled?} to decide whether to request a
|
|
10
|
+
# streaming response from the API. When disabled, the stream is replaced
|
|
11
|
+
# with `nil` before the transport layer executes the request.
|
|
12
|
+
#
|
|
13
|
+
# All callback methods inherited from {LLM::Stream LLM::Stream} are no-ops,
|
|
14
|
+
# so a disabled stream can be used safely wherever a stream object is
|
|
15
|
+
# expected.
|
|
16
|
+
class Disabled < self
|
|
17
|
+
##
|
|
18
|
+
# @return [false]
|
|
19
|
+
def enabled?
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Stream
|
|
4
|
+
##
|
|
5
|
+
# An {LLM::Stream::IO LLM::Stream::IO} wraps an object that responds to
|
|
6
|
+
# `#<<` and forwards streamed content to it.
|
|
7
|
+
#
|
|
8
|
+
# This enables any object that implements `#<<` - such as an IO, StringIO,
|
|
9
|
+
# or a custom logger - to be used as a stream target. {LLM::Stream.try}
|
|
10
|
+
# creates instances of this class automatically when given an IO-like
|
|
11
|
+
# object.
|
|
12
|
+
#
|
|
13
|
+
# @example Using an IO object as a stream
|
|
14
|
+
# File.open("output.txt", "w") do |file|
|
|
15
|
+
# ctx.ask("Tell me a story", stream: file)
|
|
16
|
+
# end
|
|
17
|
+
class IO < self
|
|
18
|
+
##
|
|
19
|
+
# @param [#<<] io
|
|
20
|
+
# Any object that implements `#<<`.
|
|
21
|
+
def initialize(io)
|
|
22
|
+
@io = io
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Writes a chunk of content to the wrapped IO object.
|
|
27
|
+
# @param [String] content
|
|
28
|
+
# @return [void]
|
|
29
|
+
def on_content(content)
|
|
30
|
+
@io << content
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Forwards streamed content to the wrapped IO object.
|
|
35
|
+
# Redefined here because +alias_method+ in the parent class
|
|
36
|
+
# captures the base implementation, not the overridden one.
|
|
37
|
+
# @param [String] content
|
|
38
|
+
# @return [void]
|
|
39
|
+
def <<(content)
|
|
40
|
+
on_content(content)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/llm/stream.rb
CHANGED
|
@@ -21,6 +21,34 @@ module LLM
|
|
|
21
21
|
# also emit lifecycle callbacks like {#on_transform} or {#on_compaction}.
|
|
22
22
|
class Stream
|
|
23
23
|
require_relative "stream/queue"
|
|
24
|
+
require_relative "stream/io"
|
|
25
|
+
require_relative "stream/disabled"
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# This method will try to convert its argument into
|
|
29
|
+
# an instance of {LLM::Stream LLM::Stream} or a
|
|
30
|
+
# subclass of it.
|
|
31
|
+
#
|
|
32
|
+
# Acceptable inputs include: {LLM::Stream LLM::Stream}
|
|
33
|
+
# objects, IO objects who implement `#<<`, true, false,
|
|
34
|
+
# and nil. Anything else raises a TypeError.
|
|
35
|
+
#
|
|
36
|
+
# @raise [TypeError]
|
|
37
|
+
# @param [LLM::Stream, #<<, Boolean, NilClass] obj
|
|
38
|
+
# @return [LLM::Stream]
|
|
39
|
+
def self.try(obj, extra: {})
|
|
40
|
+
if LLM::Stream === obj
|
|
41
|
+
obj.tap { _1.extra.merge!(extra) }
|
|
42
|
+
elsif obj.respond_to?(:<<)
|
|
43
|
+
LLM::Stream::IO.new(obj).tap { _1.extra.merge!(extra) }
|
|
44
|
+
elsif obj == true
|
|
45
|
+
LLM::Stream.new.tap { _1.extra.merge!(extra) }
|
|
46
|
+
elsif obj.nil? || obj == false
|
|
47
|
+
LLM::Stream::Disabled.new.tap { _1.extra.merge!(extra) }
|
|
48
|
+
else
|
|
49
|
+
raise TypeError, "invalid stream object"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
24
52
|
|
|
25
53
|
##
|
|
26
54
|
# Returns extra context associated with the current streamed request.
|
|
@@ -52,6 +80,12 @@ module LLM
|
|
|
52
80
|
queue.wait
|
|
53
81
|
end
|
|
54
82
|
|
|
83
|
+
##
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def enabled?
|
|
86
|
+
true
|
|
87
|
+
end
|
|
88
|
+
|
|
55
89
|
# @group Public callbacks
|
|
56
90
|
|
|
57
91
|
##
|
data/lib/llm/tools/git.rb
CHANGED
|
@@ -16,9 +16,8 @@ class LLM::Tool
|
|
|
16
16
|
required %i[action]
|
|
17
17
|
|
|
18
18
|
##
|
|
19
|
-
# @param [String]
|
|
20
|
-
# @param [
|
|
21
|
-
# @param [Integer] stop
|
|
19
|
+
# @param [String] action
|
|
20
|
+
# @param [Array<String>, nil] arguments
|
|
22
21
|
# @return [Hash]
|
|
23
22
|
def call(action:, arguments: nil)
|
|
24
23
|
command = spawn(action:, arguments:)
|
data/lib/llm/tools/pwd.rb
CHANGED
data/lib/llm/tools/rg.rb
CHANGED
data/lib/llm/tools/swap_text.rb
CHANGED
|
@@ -14,6 +14,12 @@ class LLM::Tool
|
|
|
14
14
|
parameter :expected_count, Integer, "How many matches should be replaced"
|
|
15
15
|
required %i[path before after]
|
|
16
16
|
|
|
17
|
+
##
|
|
18
|
+
# @param [String] path
|
|
19
|
+
# @param [String] before
|
|
20
|
+
# @param [String] after
|
|
21
|
+
# @param [Integer] expected_count
|
|
22
|
+
# @return [Hash]
|
|
17
23
|
def call(path:, before:, after:, expected_count: 1)
|
|
18
24
|
content = File.read(path)
|
|
19
25
|
count = content.scan(before).length
|
|
@@ -29,6 +29,7 @@ class LLM::Transport
|
|
|
29
29
|
# When there is a network error at the operating system level
|
|
30
30
|
# @return [LLM::Transport::Response]
|
|
31
31
|
def execute(request:, operation:, stream: nil, stream_parser: self.stream_parser, model: nil, inputs: nil, &b)
|
|
32
|
+
stream = nil if !stream&.enabled?
|
|
32
33
|
stream &&= LLM::Object.from(streamer: stream, parser: stream_parser, decoder: stream_decoder)
|
|
33
34
|
owner = transport.request_owner
|
|
34
35
|
tracer = self.tracer
|
data/lib/llm/version.rb
CHANGED
data/lib/llm.rb
CHANGED
|
@@ -180,6 +180,15 @@ module LLM
|
|
|
180
180
|
LLM::XAI.new(**)
|
|
181
181
|
end
|
|
182
182
|
|
|
183
|
+
##
|
|
184
|
+
# @param key (see LLM::Mistral#initialize)
|
|
185
|
+
# @param host (see LLM::Mistral#initialize)
|
|
186
|
+
# @return (see LLM::Mistral#initialize)
|
|
187
|
+
def mistral(**)
|
|
188
|
+
lock(:require) { require_relative "llm/providers/mistral" unless defined?(LLM::Mistral) }
|
|
189
|
+
LLM::Mistral.new(**)
|
|
190
|
+
end
|
|
191
|
+
|
|
183
192
|
##
|
|
184
193
|
# @param key (see LLM::ZAI#initialize)
|
|
185
194
|
# @param host (see LLM::ZAI#initialize)
|
|
@@ -243,6 +252,16 @@ module LLM
|
|
|
243
252
|
LLM::Function.new(key, &b)
|
|
244
253
|
end
|
|
245
254
|
|
|
255
|
+
##
|
|
256
|
+
# @param [LLM::Provider] llm
|
|
257
|
+
# A provider
|
|
258
|
+
# @param [Hash] params
|
|
259
|
+
# Forwarded to LLM::Tracer::Logger
|
|
260
|
+
# @return [LLM::Tracer::Logger]
|
|
261
|
+
def logger(llm, **params)
|
|
262
|
+
LLM::Tracer::Logger.new(llm, params)
|
|
263
|
+
end
|
|
264
|
+
|
|
246
265
|
##
|
|
247
266
|
# Provides a thread-safe lock
|
|
248
267
|
# @param [Symbol] name The name of the lock
|
data/llm.gemspec
CHANGED
|
@@ -5,13 +5,17 @@ require_relative "lib/llm/version"
|
|
|
5
5
|
Gem::Specification.new do |spec|
|
|
6
6
|
spec.name = "llm.rb"
|
|
7
7
|
spec.version = LLM::VERSION
|
|
8
|
-
spec.authors = ["
|
|
8
|
+
spec.authors = ["robert", "Antar Azri", "Rodrigo Serrano"]
|
|
9
9
|
spec.email = ["robert@r.uby.dev"]
|
|
10
10
|
|
|
11
11
|
spec.summary = "Ruby's capable AI runtime"
|
|
12
|
-
spec.description =
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
spec.description = <<~DESCRIPTION
|
|
13
|
+
llm.rb is an advanced runtime for building capable AI applications
|
|
14
|
+
on CRuby. By default it has zero runtime dependencies although certain
|
|
15
|
+
functionality – such as ActiveRecord support – require
|
|
16
|
+
optional dependencies that are opt-in.
|
|
17
|
+
DESCRIPTION
|
|
18
|
+
|
|
15
19
|
spec.license = "BUSL-1.1"
|
|
16
20
|
spec.required_ruby_version = ">= 3.3.0"
|
|
17
21
|
|
data/resources/deepdive.md
CHANGED
|
@@ -25,44 +25,126 @@ features that didn't make it into the homepage documentation.
|
|
|
25
25
|
|
|
26
26
|
## Table of contents
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- [
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
- [
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
28
|
+
**Overview**
|
|
29
|
+
|
|
30
|
+
- [Welcome](#welcome)
|
|
31
|
+
|
|
32
|
+
**Core**
|
|
33
|
+
|
|
34
|
+
<details>
|
|
35
|
+
<summary>Agents</summary>
|
|
36
|
+
|
|
37
|
+
- [As a subclass](#as-a-subclass)
|
|
38
|
+
- [As an object](#as-an-object)
|
|
39
|
+
</details>
|
|
40
|
+
|
|
41
|
+
<details>
|
|
42
|
+
<summary>Tools</summary>
|
|
43
|
+
|
|
44
|
+
- [LLM::Tool](#llmtool)
|
|
45
|
+
- [Errors](#errors)
|
|
46
|
+
</details>
|
|
47
|
+
|
|
48
|
+
<details>
|
|
49
|
+
<summary>Skills</summary>
|
|
50
|
+
|
|
51
|
+
- [SKILL.md](#skillmd)
|
|
52
|
+
- [Run it](#run-it)
|
|
53
|
+
</details>
|
|
54
|
+
|
|
55
|
+
<details>
|
|
56
|
+
<summary>Schema</summary>
|
|
57
|
+
|
|
58
|
+
- [Estimation](#estimation)
|
|
59
|
+
</details>
|
|
60
|
+
|
|
61
|
+
**Runtime**
|
|
62
|
+
|
|
63
|
+
<details>
|
|
64
|
+
<summary>Stream</summary>
|
|
65
|
+
|
|
66
|
+
- [IO-like object](#io-like-object)
|
|
67
|
+
- [LLM::Stream](#llmstream)
|
|
68
|
+
</details>
|
|
69
|
+
|
|
70
|
+
<details>
|
|
71
|
+
<summary>Cancellation</summary>
|
|
72
|
+
|
|
73
|
+
- [Cancel a request](#cancel-a-request)
|
|
74
|
+
</details>
|
|
75
|
+
|
|
76
|
+
<details>
|
|
77
|
+
<summary>Transports</summary>
|
|
78
|
+
|
|
79
|
+
- [net/http](#nethttp)
|
|
80
|
+
- [net/http/persistent](#nethttppersistent)
|
|
81
|
+
- [curb](#curb)
|
|
82
|
+
</details>
|
|
83
|
+
|
|
84
|
+
<details>
|
|
85
|
+
<summary>Tracer</summary>
|
|
86
|
+
|
|
87
|
+
- [Provider-wide tracer](#provider-wide-tracer)
|
|
88
|
+
- [Agent-local tracer](#agent-local-tracer)
|
|
89
|
+
</details>
|
|
90
|
+
|
|
91
|
+
<details>
|
|
92
|
+
<summary>REPL</summary>
|
|
93
|
+
|
|
94
|
+
- [LLM::Agent](#llmagent)
|
|
95
|
+
- [Tools](#tools)
|
|
96
|
+
- [Skills](#skills-1)
|
|
97
|
+
- [Tracer](#tracer-1)
|
|
98
|
+
</details>
|
|
99
|
+
|
|
100
|
+
**Persistence**
|
|
101
|
+
|
|
102
|
+
<details>
|
|
103
|
+
<summary>Serialization</summary>
|
|
104
|
+
|
|
105
|
+
- [Save to disk](#save-to-disk)
|
|
106
|
+
</details>
|
|
107
|
+
|
|
108
|
+
<details>
|
|
109
|
+
<summary>ORM</summary>
|
|
110
|
+
|
|
111
|
+
- [ActiveRecord](#activerecord)
|
|
112
|
+
- [Sequel](#sequel)
|
|
113
|
+
</details>
|
|
114
|
+
|
|
115
|
+
**Media**
|
|
116
|
+
|
|
117
|
+
<details>
|
|
118
|
+
<summary>Images</summary>
|
|
119
|
+
|
|
120
|
+
- [Generation](#generation)
|
|
121
|
+
- [Edits](#edits)
|
|
122
|
+
- [DeepSeek](#deepseek)
|
|
123
|
+
</details>
|
|
124
|
+
|
|
125
|
+
<details>
|
|
126
|
+
<summary>Audio</summary>
|
|
127
|
+
|
|
128
|
+
- [text-to-speech](#text-to-speech)
|
|
129
|
+
- [speech-to-text](#speech-to-text)
|
|
130
|
+
- [translation](#translation)
|
|
131
|
+
</details>
|
|
132
|
+
|
|
133
|
+
**Protocols**
|
|
134
|
+
|
|
135
|
+
<details>
|
|
136
|
+
<summary>MCP</summary>
|
|
137
|
+
|
|
138
|
+
- [stdio](#stdio)
|
|
139
|
+
- [http](#http)
|
|
140
|
+
</details>
|
|
141
|
+
|
|
142
|
+
<details>
|
|
143
|
+
<summary>A2A</summary>
|
|
144
|
+
|
|
145
|
+
- [rest](#rest)
|
|
146
|
+
- [jsonrpc](#jsonrpc)
|
|
147
|
+
</details>
|
|
66
148
|
|
|
67
149
|
## Agents
|
|
68
150
|
|
|
@@ -808,21 +890,22 @@ agent = LLM::Agent.new(llm, tracer: LLM::Tracer::Logger.new(llm, path: "deepseek
|
|
|
808
890
|
|
|
809
891
|
During the development and operation of agents it can often
|
|
810
892
|
be helpful to drop into a read-eval-print loop. This gives
|
|
811
|
-
you a
|
|
812
|
-
anything that went wrong
|
|
813
|
-
|
|
814
|
-
decision-making process and make improvements for future runs.
|
|
815
|
-
It can also be used to ask the agent to correct any errors
|
|
816
|
-
that might have made.
|
|
893
|
+
you a way to confirm the work was successful, inspect
|
|
894
|
+
anything that went wrong, and keep talking to the same
|
|
895
|
+
agent while its state is still intact.
|
|
817
896
|
|
|
818
|
-
|
|
897
|
+
#### LLM::Agent
|
|
819
898
|
|
|
820
899
|
The [LLM::Agent#repl](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
|
|
821
|
-
method
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
900
|
+
method allows an agent to spawn a read-eval-print loop
|
|
901
|
+
that can be useful while developing or operating agents.
|
|
902
|
+
It can be used to debug tool calls, confirm an
|
|
903
|
+
agent has done what was expected, or improve an agent by
|
|
904
|
+
asking questions about what it has done up to that point.
|
|
905
|
+
|
|
906
|
+
This feature requires that the [curses](https://github.com/ruby/curses)
|
|
907
|
+
and [kramdown](https://github.com/gettalong/kramdown) libraries are
|
|
908
|
+
installed and available to require.
|
|
826
909
|
|
|
827
910
|
```ruby
|
|
828
911
|
require "llm"
|
|
@@ -832,6 +915,43 @@ agent = LLM::Agent.new(llm)
|
|
|
832
915
|
agent.repl
|
|
833
916
|
```
|
|
834
917
|
|
|
918
|
+
#### Tools
|
|
919
|
+
|
|
920
|
+
The read-eval-print loop accepts a `tools` option that lets
|
|
921
|
+
you attach additional tools for the duration of the session.
|
|
922
|
+
|
|
923
|
+
```ruby
|
|
924
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
925
|
+
agent = LLM::Agent.new(llm)
|
|
926
|
+
agent.repl(tools: [Debugger])
|
|
927
|
+
```
|
|
928
|
+
|
|
929
|
+
#### Skills
|
|
930
|
+
|
|
931
|
+
The read-eval-print loop also accepts a `skills` option.
|
|
932
|
+
This can be useful when you want to load extra skills
|
|
933
|
+
without attaching them to an agent permanently.
|
|
934
|
+
|
|
935
|
+
```ruby
|
|
936
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
937
|
+
agent = LLM::Agent.new(llm)
|
|
938
|
+
agent.repl(skills: [__dir__])
|
|
939
|
+
```
|
|
940
|
+
|
|
941
|
+
#### Tracer
|
|
942
|
+
|
|
943
|
+
By default the tracer is disabled for the duration of the
|
|
944
|
+
session. This can be configured through the
|
|
945
|
+
`tracer` option. Setting it to `true` will configure
|
|
946
|
+
the REPL to use the tracer associated with an instance
|
|
947
|
+
of [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
948
|
+
|
|
949
|
+
```ruby
|
|
950
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
951
|
+
agent = LLM::Agent.new(llm, tracer: LLM.logger(llm, path: "agent.log"))
|
|
952
|
+
agent.repl(tracer: true, tools: [Debugger])
|
|
953
|
+
```
|
|
954
|
+
|
|
835
955
|
[Back to top](#table-of-contents)
|
|
836
956
|
|
|
837
957
|
## Images
|