llm.rb 12.0.0 → 12.1.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 +112 -1
- data/LICENSE +5 -4
- data/README.md +30 -1
- data/data/anthropic.json +83 -371
- data/data/bedrock.json +451 -3
- data/data/deepinfra.json +566 -46
- data/data/deepseek.json +4 -0
- data/data/google.json +22 -0
- data/data/openai.json +51 -0
- data/data/xai.json +8 -0
- data/data/zai.json +14 -0
- data/lib/llm/agent.rb +25 -1
- data/lib/llm/context.rb +8 -0
- data/lib/llm/json_adapter.rb +29 -3
- data/lib/llm/provider.rb +1 -1
- data/lib/llm/providers/llamacpp.rb +5 -5
- data/lib/llm/repl/input.rb +64 -0
- data/lib/llm/repl/status.rb +30 -0
- data/lib/llm/repl/stream.rb +46 -0
- data/lib/llm/repl/transcript.rb +61 -0
- data/lib/llm/repl/window.rb +107 -0
- data/lib/llm/repl.rb +78 -0
- data/lib/llm/tools/chdir.rb +23 -0
- data/lib/llm/tools/git.rb +41 -0
- data/lib/llm/tools/mkdir.rb +32 -0
- data/lib/llm/tools/pwd.rb +20 -0
- data/lib/llm/tools/read_file.rb +40 -0
- data/lib/llm/tools/rg.rb +46 -0
- data/lib/llm/tools/shell.rb +48 -0
- data/lib/llm/tools/swap_text.rb +25 -0
- data/lib/llm/tools/write_file.rb +24 -0
- data/lib/llm/tools.rb +5 -0
- data/lib/llm/version.rb +1 -1
- data/resources/deepdive.md +32 -0
- metadata +17 -1
data/data/zai.json
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"glm-4.7": {
|
|
12
12
|
"id": "glm-4.7",
|
|
13
13
|
"name": "GLM-4.7",
|
|
14
|
+
"description": "Mature GLM model for dependable coding, reasoning, and structured agent tasks",
|
|
14
15
|
"family": "glm",
|
|
15
16
|
"attachment": false,
|
|
16
17
|
"reasoning": true,
|
|
@@ -50,6 +51,7 @@
|
|
|
50
51
|
"glm-4.5v": {
|
|
51
52
|
"id": "glm-4.5v",
|
|
52
53
|
"name": "GLM-4.5V",
|
|
54
|
+
"description": "GLM vision model for visual reasoning, documents, and multimodal agents",
|
|
53
55
|
"family": "glm",
|
|
54
56
|
"attachment": true,
|
|
55
57
|
"reasoning": true,
|
|
@@ -86,6 +88,7 @@
|
|
|
86
88
|
"glm-4.5": {
|
|
87
89
|
"id": "glm-4.5",
|
|
88
90
|
"name": "GLM-4.5",
|
|
91
|
+
"description": "Hybrid-reasoning GLM release that made the 4.5 line broadly useful",
|
|
89
92
|
"family": "glm",
|
|
90
93
|
"attachment": false,
|
|
91
94
|
"reasoning": true,
|
|
@@ -122,6 +125,7 @@
|
|
|
122
125
|
"glm-4.7-flashx": {
|
|
123
126
|
"id": "glm-4.7-flashx",
|
|
124
127
|
"name": "GLM-4.7-FlashX",
|
|
128
|
+
"description": "Efficient GLM model for fast reasoning, coding, and agent workflows",
|
|
125
129
|
"family": "glm-flash",
|
|
126
130
|
"attachment": false,
|
|
127
131
|
"reasoning": true,
|
|
@@ -158,6 +162,7 @@
|
|
|
158
162
|
"glm-5.1": {
|
|
159
163
|
"id": "glm-5.1",
|
|
160
164
|
"name": "GLM-5.1",
|
|
165
|
+
"description": "Strong GLM coding model for agentic engineering, terminals, and repository generation",
|
|
161
166
|
"family": "glm",
|
|
162
167
|
"attachment": false,
|
|
163
168
|
"reasoning": true,
|
|
@@ -197,6 +202,7 @@
|
|
|
197
202
|
"glm-4.6": {
|
|
198
203
|
"id": "glm-4.6",
|
|
199
204
|
"name": "GLM-4.6",
|
|
205
|
+
"description": "Late GLM-4 workhorse for coding agents, reasoning, and structured tasks",
|
|
200
206
|
"family": "glm",
|
|
201
207
|
"attachment": false,
|
|
202
208
|
"reasoning": true,
|
|
@@ -233,6 +239,7 @@
|
|
|
233
239
|
"glm-5.2": {
|
|
234
240
|
"id": "glm-5.2",
|
|
235
241
|
"name": "GLM-5.2",
|
|
242
|
+
"description": "Open flagship GLM for long-horizon coding agents and million-token context work",
|
|
236
243
|
"family": "glm",
|
|
237
244
|
"attachment": false,
|
|
238
245
|
"reasoning": true,
|
|
@@ -276,6 +283,7 @@
|
|
|
276
283
|
"glm-4.6v": {
|
|
277
284
|
"id": "glm-4.6v",
|
|
278
285
|
"name": "GLM-4.6V",
|
|
286
|
+
"description": "GLM vision model for visual reasoning, documents, and multimodal agents",
|
|
279
287
|
"family": "glm",
|
|
280
288
|
"attachment": true,
|
|
281
289
|
"reasoning": true,
|
|
@@ -312,6 +320,7 @@
|
|
|
312
320
|
"glm-5v-turbo": {
|
|
313
321
|
"id": "glm-5v-turbo",
|
|
314
322
|
"name": "GLM-5V-Turbo",
|
|
323
|
+
"description": "Fast GLM vision model for screenshots, documents, and multimodal agent tasks",
|
|
315
324
|
"family": "glm",
|
|
316
325
|
"attachment": true,
|
|
317
326
|
"reasoning": true,
|
|
@@ -353,6 +362,7 @@
|
|
|
353
362
|
"glm-4.5-air": {
|
|
354
363
|
"id": "glm-4.5-air",
|
|
355
364
|
"name": "GLM-4.5-Air",
|
|
365
|
+
"description": "Lighter GLM-4.5 variant for fast coding assistance and cheaper agents",
|
|
356
366
|
"family": "glm-air",
|
|
357
367
|
"attachment": false,
|
|
358
368
|
"reasoning": true,
|
|
@@ -389,6 +399,7 @@
|
|
|
389
399
|
"glm-4.7-flash": {
|
|
390
400
|
"id": "glm-4.7-flash",
|
|
391
401
|
"name": "GLM-4.7-Flash",
|
|
402
|
+
"description": "Budget GLM lane for fast coding help, routing, and everyday automation",
|
|
392
403
|
"family": "glm-flash",
|
|
393
404
|
"attachment": false,
|
|
394
405
|
"reasoning": true,
|
|
@@ -425,6 +436,7 @@
|
|
|
425
436
|
"glm-4.5-flash": {
|
|
426
437
|
"id": "glm-4.5-flash",
|
|
427
438
|
"name": "GLM-4.5-Flash",
|
|
439
|
+
"description": "Efficient GLM model for fast reasoning, coding, and agent workflows",
|
|
428
440
|
"family": "glm-flash",
|
|
429
441
|
"attachment": false,
|
|
430
442
|
"reasoning": true,
|
|
@@ -461,6 +473,7 @@
|
|
|
461
473
|
"glm-5": {
|
|
462
474
|
"id": "glm-5",
|
|
463
475
|
"name": "GLM-5",
|
|
476
|
+
"description": "General GLM flagship for coding, analysis, and tool-heavy engineering workflows",
|
|
464
477
|
"family": "glm",
|
|
465
478
|
"attachment": false,
|
|
466
479
|
"reasoning": true,
|
|
@@ -499,6 +512,7 @@
|
|
|
499
512
|
"glm-5-turbo": {
|
|
500
513
|
"id": "glm-5-turbo",
|
|
501
514
|
"name": "GLM-5-Turbo",
|
|
515
|
+
"description": "Faster GLM-5 lane for coding agents that need lower latency",
|
|
502
516
|
"family": "glm",
|
|
503
517
|
"attachment": false,
|
|
504
518
|
"reasoning": true,
|
data/lib/llm/agent.rb
CHANGED
|
@@ -61,7 +61,11 @@ module LLM
|
|
|
61
61
|
# Returns the current tools when no argument is provided
|
|
62
62
|
def self.tools(*tools, &block)
|
|
63
63
|
return @tools || [] if tools.empty? && !block
|
|
64
|
-
|
|
64
|
+
if tools.size == 1 and tools.grep(Symbol).any?
|
|
65
|
+
@tools = tools.first
|
|
66
|
+
else
|
|
67
|
+
@tools = block || tools.flatten
|
|
68
|
+
end
|
|
65
69
|
end
|
|
66
70
|
|
|
67
71
|
##
|
|
@@ -334,6 +338,15 @@ module LLM
|
|
|
334
338
|
@tracer || @ctx.tracer
|
|
335
339
|
end
|
|
336
340
|
|
|
341
|
+
##
|
|
342
|
+
# @param [LLM::Tracer, nil] other
|
|
343
|
+
# A tracer, or nil.
|
|
344
|
+
# @return [void]
|
|
345
|
+
def tracer=(other)
|
|
346
|
+
@ctx.tracer = other
|
|
347
|
+
@tracer = other
|
|
348
|
+
end
|
|
349
|
+
|
|
337
350
|
##
|
|
338
351
|
# @return [LLM::Stream, #<<, nil]
|
|
339
352
|
# Returns a stream object, or nil
|
|
@@ -375,6 +388,17 @@ module LLM
|
|
|
375
388
|
@ctx.context_window
|
|
376
389
|
end
|
|
377
390
|
|
|
391
|
+
##
|
|
392
|
+
# Start a minimalist repl that can interact
|
|
393
|
+
# with the agent and its current state. This
|
|
394
|
+
# method requires the 'curses' gem to be installed
|
|
395
|
+
# and available to require.
|
|
396
|
+
# @return [void]
|
|
397
|
+
def repl
|
|
398
|
+
require_relative "repl" unless defined?(::LLM::Repl)
|
|
399
|
+
LLM::Repl.new(self).start
|
|
400
|
+
end
|
|
401
|
+
|
|
378
402
|
##
|
|
379
403
|
# @see LLM::Context#params
|
|
380
404
|
# @return [Hash]
|
data/lib/llm/context.rb
CHANGED
|
@@ -421,6 +421,14 @@ module LLM
|
|
|
421
421
|
@llm.tracer
|
|
422
422
|
end
|
|
423
423
|
|
|
424
|
+
##
|
|
425
|
+
# @param [LLM::Tracer, nil] other
|
|
426
|
+
# A tracer, or nil.
|
|
427
|
+
# @return [void]
|
|
428
|
+
def tracer=(other)
|
|
429
|
+
@llm.tracer = nil
|
|
430
|
+
end
|
|
431
|
+
|
|
424
432
|
##
|
|
425
433
|
# @return [LLM::Stream, #<<, nil]
|
|
426
434
|
# Returns a stream object, or nil
|
data/lib/llm/json_adapter.rb
CHANGED
|
@@ -38,11 +38,11 @@ module LLM
|
|
|
38
38
|
def self.dump(obj, state = nil, **options)
|
|
39
39
|
require "json" unless defined?(::JSON)
|
|
40
40
|
if ::JSON::State === state
|
|
41
|
-
::JSON.generate(obj, state)
|
|
41
|
+
::JSON.generate(normalize(obj), state)
|
|
42
42
|
elsif state
|
|
43
|
-
::JSON.dump(obj, state, **options)
|
|
43
|
+
::JSON.dump(normalize(obj), state, **options)
|
|
44
44
|
else
|
|
45
|
-
::JSON.dump(obj, **options)
|
|
45
|
+
::JSON.dump(normalize(obj), **options)
|
|
46
46
|
end
|
|
47
47
|
end
|
|
48
48
|
|
|
@@ -59,6 +59,32 @@ module LLM
|
|
|
59
59
|
require "json" unless defined?(::JSON)
|
|
60
60
|
[::JSON::ParserError]
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
##
|
|
64
|
+
# JSON 3.0 compat
|
|
65
|
+
# Walks `obj` and encodes every string that is
|
|
66
|
+
# found into a UTF-8 compatible string.
|
|
67
|
+
def self.normalize(obj)
|
|
68
|
+
case obj
|
|
69
|
+
when String then normalize_string(obj)
|
|
70
|
+
when Array then obj.map { normalize(_1) }
|
|
71
|
+
when Hash then obj.map { [_1, normalize(_2)] }.to_h
|
|
72
|
+
when LLM::Object then obj.map { [_1, normalize(_2)] }.to_h
|
|
73
|
+
else obj
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
private_class_method :normalize
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# JSON 3.0 compat
|
|
80
|
+
# Normalizes a string as a UTF-8 encoded string
|
|
81
|
+
# that's compatible with the JSON spec.
|
|
82
|
+
def self.normalize_string(str)
|
|
83
|
+
return str if str.encoding == Encoding::UTF_8
|
|
84
|
+
str = (+str).force_encoding("UTF-8")
|
|
85
|
+
str.valid_encoding? ? str : str.scrub
|
|
86
|
+
end
|
|
87
|
+
private_class_method :normalize_string
|
|
62
88
|
end
|
|
63
89
|
|
|
64
90
|
##
|
data/lib/llm/provider.rb
CHANGED
|
@@ -23,7 +23,7 @@ module LLM
|
|
|
23
23
|
##
|
|
24
24
|
# @param (see LLM::Provider#initialize)
|
|
25
25
|
# @return [LLM::LlamaCpp]
|
|
26
|
-
def initialize(host: "localhost", port:
|
|
26
|
+
def initialize(host: "localhost", port: 8013, ssl: false, **)
|
|
27
27
|
super
|
|
28
28
|
end
|
|
29
29
|
|
|
@@ -71,11 +71,11 @@ module LLM
|
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
##
|
|
74
|
-
# Returns
|
|
75
|
-
#
|
|
76
|
-
# @return [
|
|
74
|
+
# Returns nil.
|
|
75
|
+
# Whatever model is served by llamacpp acts as the default.
|
|
76
|
+
# @return [nil]
|
|
77
77
|
def default_model
|
|
78
|
-
|
|
78
|
+
nil
|
|
79
79
|
end
|
|
80
80
|
end
|
|
81
81
|
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Input LLM::Repl::Input} class manages
|
|
6
|
+
# the editable input line shown at the bottom of the REPL.
|
|
7
|
+
# @api private
|
|
8
|
+
class Input
|
|
9
|
+
UP = Curses::Key::UP
|
|
10
|
+
DOWN = Curses::Key::DOWN
|
|
11
|
+
ENTER = [Curses::Key::ENTER, 10, 13]
|
|
12
|
+
BACKSPACE = [Curses::Key::BACKSPACE, 127]
|
|
13
|
+
EOF = [nil, 4]
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# @param [String, Symbol] provider
|
|
17
|
+
# @return [LLM::Repl::Input]
|
|
18
|
+
def initialize(provider)
|
|
19
|
+
@provider = provider
|
|
20
|
+
@buffer = +""
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# @param [LLM::Repl::Window] window
|
|
25
|
+
# @return [String, nil]
|
|
26
|
+
def readline(window)
|
|
27
|
+
catch(:done) do
|
|
28
|
+
@buffer.clear
|
|
29
|
+
loop do
|
|
30
|
+
on_char(window, window.getch)
|
|
31
|
+
window.redraw
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @return [String]
|
|
38
|
+
def to_s
|
|
39
|
+
"> #{@buffer}"
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def on_char(window, char)
|
|
45
|
+
if EOF.include?(char)
|
|
46
|
+
throw(:done, nil)
|
|
47
|
+
elsif BACKSPACE.include?(char)
|
|
48
|
+
@buffer.chop!
|
|
49
|
+
elsif ENTER.include?(char)
|
|
50
|
+
buf = @buffer.dup
|
|
51
|
+
@buffer.clear
|
|
52
|
+
throw(:done, buf)
|
|
53
|
+
elsif char == UP
|
|
54
|
+
window.scroll_up
|
|
55
|
+
elsif char == DOWN
|
|
56
|
+
window.scroll_down
|
|
57
|
+
elsif String === char
|
|
58
|
+
@buffer << char
|
|
59
|
+
else
|
|
60
|
+
# ???
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Status LLM::Repl::Status} class stores
|
|
6
|
+
# the small status line shown at the top of the REPL.
|
|
7
|
+
# @api private
|
|
8
|
+
class Status
|
|
9
|
+
##
|
|
10
|
+
# @param [String, Symbol] provider
|
|
11
|
+
# @return [LLM::Repl::Status]
|
|
12
|
+
def initialize(provider)
|
|
13
|
+
@provider = provider
|
|
14
|
+
@text = "idle"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @return [String]
|
|
19
|
+
attr_reader :provider
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# @param [String] value
|
|
23
|
+
# @return [void]
|
|
24
|
+
attr_accessor :text
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @return [String]
|
|
28
|
+
alias_method :to_s, :text
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Stream LLM::Repl::Stream} class manages
|
|
6
|
+
# the stream for the {LLM::Repl LLM::Repl} class. This class
|
|
7
|
+
# has defined hooks that receive text, tool calls, and
|
|
8
|
+
# tool returns.
|
|
9
|
+
# @api private
|
|
10
|
+
class Stream < LLM::Stream
|
|
11
|
+
##
|
|
12
|
+
# @param [LLM::Repl] repl
|
|
13
|
+
# @return [LLM::Repl::Stream]
|
|
14
|
+
def initialize(repl)
|
|
15
|
+
@repl = repl
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# @param [String] chars
|
|
20
|
+
# One or more chars
|
|
21
|
+
# @return [void]
|
|
22
|
+
def on_content(chars)
|
|
23
|
+
@repl.write(chars)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# @param [LLM::Function] tool
|
|
28
|
+
# @param [LLM::Function::Return, nil] error
|
|
29
|
+
# @return [void]
|
|
30
|
+
def on_tool_call(tool, error)
|
|
31
|
+
if error
|
|
32
|
+
@repl.status = "tool error: #{tool.name}"
|
|
33
|
+
else
|
|
34
|
+
@repl.status = "tool: #{tool.name}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @param [LLM::Function] _tool
|
|
40
|
+
# @param [LLM::Function::Return] result
|
|
41
|
+
# @return [void]
|
|
42
|
+
def on_tool_return(_tool, result)
|
|
43
|
+
@repl.status = "tool done: #{result.name}"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Transcript LLM::Repl::Transcript} class
|
|
6
|
+
# stores streamed output for the REPL.
|
|
7
|
+
# @api private
|
|
8
|
+
class Transcript
|
|
9
|
+
WIDTH = 80
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# @return [LLM::Repl::Transcript]
|
|
13
|
+
def initialize
|
|
14
|
+
@lines = [+""]
|
|
15
|
+
@offset = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# @param [String] chars
|
|
20
|
+
# @return [void]
|
|
21
|
+
def write(chars)
|
|
22
|
+
chars.each_char { write_char(_1) }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# @return [void]
|
|
27
|
+
def scroll_up(height)
|
|
28
|
+
max = [@lines.size - height, 0].max
|
|
29
|
+
@offset = [@offset + 1, max].min
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @return [void]
|
|
34
|
+
def scroll_down
|
|
35
|
+
@offset = [@offset - 1, 0].max
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @param [Integer] height
|
|
40
|
+
# @return [Array<String>]
|
|
41
|
+
def visible(height)
|
|
42
|
+
last = @lines.size - 1 - @offset
|
|
43
|
+
first = [last - height + 1, 0].max
|
|
44
|
+
@lines[first..last] || []
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def write_char(char)
|
|
50
|
+
if char == "\n"
|
|
51
|
+
@offset += 1 if @offset > 0
|
|
52
|
+
@lines << +""
|
|
53
|
+
elsif char == " " and @lines.last.length >= WIDTH
|
|
54
|
+
@offset += 1 if @offset > 0
|
|
55
|
+
@lines << +""
|
|
56
|
+
else
|
|
57
|
+
@lines.last << char
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::Repl
|
|
4
|
+
##
|
|
5
|
+
# The {LLM::Repl::Window LLM::Repl::Window} class draws the
|
|
6
|
+
# curses screen for the REPL.
|
|
7
|
+
# @api private
|
|
8
|
+
class Window
|
|
9
|
+
##
|
|
10
|
+
# @return [LLM::Repl::Status]
|
|
11
|
+
attr_reader :status
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# @return [LLM::Repl::Transcript]
|
|
15
|
+
attr_reader :transcript
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# @return [LLM::Repl::Input]
|
|
19
|
+
attr_reader :input
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# @param [LLM::Repl::Status] status
|
|
23
|
+
# @param [LLM::Repl::Transcript] transcript
|
|
24
|
+
# @param [LLM::Repl::Input] input
|
|
25
|
+
# @return [LLM::Repl::Window]
|
|
26
|
+
def initialize(status, transcript, input)
|
|
27
|
+
@status = status
|
|
28
|
+
@transcript = transcript
|
|
29
|
+
@input = input
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
##
|
|
33
|
+
# @yield
|
|
34
|
+
# @return [void]
|
|
35
|
+
def open
|
|
36
|
+
Curses.init_screen
|
|
37
|
+
Curses.cbreak
|
|
38
|
+
Curses.noecho
|
|
39
|
+
Curses.stdscr.keypad(true)
|
|
40
|
+
yield
|
|
41
|
+
ensure
|
|
42
|
+
Curses.close_screen
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# @return [void]
|
|
47
|
+
def redraw
|
|
48
|
+
Curses.clear
|
|
49
|
+
draw_status
|
|
50
|
+
draw_transcript
|
|
51
|
+
draw_input
|
|
52
|
+
Curses.refresh
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# @return [Integer]
|
|
57
|
+
def rows
|
|
58
|
+
[Curses.lines - 3, 1].max
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# @return [Object]
|
|
63
|
+
def getch
|
|
64
|
+
Curses.getch
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
##
|
|
68
|
+
# @return [void]
|
|
69
|
+
def scroll_up
|
|
70
|
+
transcript.scroll_up(rows)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# @return [void]
|
|
75
|
+
def scroll_down
|
|
76
|
+
transcript.scroll_down
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def draw_status
|
|
82
|
+
Curses.setpos(0, 0)
|
|
83
|
+
Curses.addstr(status.to_s)
|
|
84
|
+
provider = status.provider.to_s
|
|
85
|
+
Curses.setpos(0, [columns - provider.length, 0].max)
|
|
86
|
+
Curses.addstr(provider)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def draw_transcript
|
|
90
|
+
visible = transcript.visible(rows)
|
|
91
|
+
visible.each_with_index do |row, index|
|
|
92
|
+
Curses.setpos(index + 2, 0)
|
|
93
|
+
Curses.addstr(row)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def draw_input
|
|
98
|
+
Curses.setpos(Curses.lines - 1, 0)
|
|
99
|
+
Curses.clrtoeol
|
|
100
|
+
Curses.addstr(input.to_s)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def columns
|
|
104
|
+
Curses.cols
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/llm/repl.rb
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
LLM.require "curses"
|
|
4
|
+
|
|
5
|
+
module LLM
|
|
6
|
+
##
|
|
7
|
+
# The {LLM::Repl LLM::Repl} class provides a small
|
|
8
|
+
# read-eval-print loop around an instance of
|
|
9
|
+
# {LLM::Agent LLM::Agent}.
|
|
10
|
+
#
|
|
11
|
+
# It can be used to keep talking to an agent after it
|
|
12
|
+
# has been set up or has performed a task. This can be
|
|
13
|
+
# useful when you want to confirm the agent handled the
|
|
14
|
+
# task correctly, or for it to correct course after a
|
|
15
|
+
# mistake was made.
|
|
16
|
+
class Repl
|
|
17
|
+
require_relative "repl/window"
|
|
18
|
+
require_relative "repl/status"
|
|
19
|
+
require_relative "repl/transcript"
|
|
20
|
+
require_relative "repl/input"
|
|
21
|
+
require_relative "repl/stream"
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# @param [LLM::Agent] agent
|
|
25
|
+
# @return [LLM::Repl]
|
|
26
|
+
def initialize(agent)
|
|
27
|
+
@agent = agent
|
|
28
|
+
@provider = agent.llm.name
|
|
29
|
+
@status = Status.new(@provider)
|
|
30
|
+
@transcript = Transcript.new
|
|
31
|
+
@input = Input.new(@provider)
|
|
32
|
+
@window = Window.new(@status, @transcript, @input)
|
|
33
|
+
@stream = Stream.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# @return [void]
|
|
38
|
+
def start
|
|
39
|
+
window.open do
|
|
40
|
+
loop do
|
|
41
|
+
window.redraw
|
|
42
|
+
text = input.readline(window)
|
|
43
|
+
break if text.nil?
|
|
44
|
+
next if text.empty?
|
|
45
|
+
status.text = "thinking"
|
|
46
|
+
write("user: #{text}\n")
|
|
47
|
+
window.redraw
|
|
48
|
+
write("agent: ")
|
|
49
|
+
agent.talk(text, stream:)
|
|
50
|
+
status.text = "idle"
|
|
51
|
+
write("\n\n")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# @param [String] chars
|
|
58
|
+
# @return [void]
|
|
59
|
+
def write(chars)
|
|
60
|
+
transcript.write(chars)
|
|
61
|
+
window.redraw
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
##
|
|
65
|
+
# @param [String] value
|
|
66
|
+
# @return [void]
|
|
67
|
+
def status=(value)
|
|
68
|
+
status.text = value
|
|
69
|
+
window.redraw
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
attr_reader :agent, :provider, :stream,
|
|
75
|
+
:status, :transcript, :input,
|
|
76
|
+
:window
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# frozen_string_literal
|
|
4
|
+
|
|
5
|
+
class LLM::Tool
|
|
6
|
+
##
|
|
7
|
+
# The {LLM::Tool::Chdir LLM::Tool::Chdir} class implements
|
|
8
|
+
# a tool that can change the current working directory.
|
|
9
|
+
class Chdir < self
|
|
10
|
+
name "chdir"
|
|
11
|
+
description "change the current working directory"
|
|
12
|
+
parameter :path, String, "the new working directory"
|
|
13
|
+
required %i[path]
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# @param [String] path
|
|
17
|
+
# @return [Hash]
|
|
18
|
+
def call(path:)
|
|
19
|
+
Dir.chdir(path)
|
|
20
|
+
{ok: true, cwd: path}
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|