llm.rb 15.1.0 → 15.2.1
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 +336 -11
- data/README.md +90 -52
- data/bin/llm.rb +12 -4
- data/data/alibaba.json +912 -823
- data/data/anthropic.json +234 -187
- data/data/bedrock.json +3611 -2217
- data/data/deepinfra.json +1164 -989
- data/data/deepseek.json +65 -81
- data/data/google.json +668 -666
- data/data/mistral.json +501 -460
- data/data/moonshot.json +43 -248
- data/data/openai.json +1008 -914
- data/data/openrouter.json +7798 -7661
- data/data/xai.json +194 -194
- data/data/zai.json +242 -149
- data/docs/deepdive/advanced/compaction.md +5 -5
- data/docs/deepdive/advanced/guard.md +2 -2
- data/docs/deepdive/features/builtin_tools.md +93 -22
- data/docs/deepdive/features/{repl.md → console.md} +28 -28
- data/docs/deepdive/features/database.md +3 -3
- data/docs/deepdive/fundamentals/agents.md +2 -2
- data/docs/deepdive/fundamentals/providers.md +91 -6
- data/docs/deepdive/fundamentals/skills.md +14 -6
- data/docs/deepdive/fundamentals/tools.md +63 -31
- data/docs/deepdive/reference/cost.md +2 -2
- data/docs/deepdive/reference/model_registry.md +2 -2
- data/docs/deepdive/reference/tracer.md +15 -13
- data/docs/deepdive.md +2 -2
- data/lib/llm/active_record/acts_as_agent.rb +10 -6
- data/lib/llm/agent.rb +64 -30
- data/lib/llm/{repl → console}/bar.rb +3 -3
- data/lib/llm/{repl → console}/buffer.rb +4 -4
- data/lib/llm/{repl → console}/color.rb +2 -2
- data/lib/llm/{repl → console}/command.rb +12 -12
- data/lib/llm/{repl → console}/commands/exit.rb +4 -4
- data/lib/llm/{repl → console}/commands/help.rb +1 -1
- data/lib/llm/{repl/commands/compact.rb → console/commands/keep.rb} +11 -9
- data/lib/llm/{repl → console}/commands/model.rb +2 -2
- data/lib/llm/{repl → console}/input/cache.rb +2 -2
- data/lib/llm/{repl → console}/input/char.rb +2 -2
- data/lib/llm/{repl → console}/input/row.rb +1 -1
- data/lib/llm/{repl → console}/input.rb +13 -6
- data/lib/llm/console/markdown/parser.rb +78 -0
- data/lib/llm/{repl → console}/markdown/table.rb +3 -3
- data/lib/llm/{repl → console}/markdown.rb +13 -30
- data/lib/llm/{repl → console}/node.rb +3 -3
- data/lib/llm/{repl → console}/status.rb +11 -11
- data/lib/llm/{repl → console}/stream.rb +9 -9
- data/lib/llm/{repl → console}/walker.rb +1 -1
- data/lib/llm/{repl → console}/window.rb +10 -10
- data/lib/llm/{repl.rb → console.rb} +38 -19
- data/lib/llm/context/deserializer.rb +2 -1
- data/lib/llm/context.rb +1 -0
- data/lib/llm/function/async/reactor.rb +20 -1
- data/lib/llm/function.rb +8 -9
- data/lib/llm/json_adapter.rb +40 -28
- data/lib/llm/message.rb +7 -0
- data/lib/llm/provider.rb +2 -2
- data/lib/llm/providers/alibaba.rb +1 -1
- data/lib/llm/providers/deepseek.rb +1 -1
- data/lib/llm/providers/openai.rb +1 -0
- data/lib/llm/schema/leaf.rb +34 -2
- data/lib/llm/schema.rb +4 -2
- data/lib/llm/sequel/agent.rb +10 -6
- data/lib/llm/tool/param.rb +5 -1
- data/lib/llm/tool.rb +5 -0
- data/lib/llm/tools/bundle.rb +53 -0
- data/lib/llm/tools/edit-file.rb +7 -2
- data/lib/llm/tools/exec.rb +78 -0
- data/lib/llm/tools/git.rb +27 -26
- data/lib/llm/tools/mkdir.rb +12 -19
- data/lib/llm/tools/read_file.rb +69 -9
- data/lib/llm/tools/rg.rb +20 -24
- data/lib/llm/tools/ruby.rb +17 -25
- data/lib/llm/tools/utils.rb +74 -1
- data/lib/llm/tools/write_file.rb +4 -1
- data/lib/llm/tracer/logger.rb +2 -2
- data/lib/llm/tracer/pretty_logger.rb +4 -4
- data/lib/llm/tracer/telemetry.rb +2 -2
- data/lib/llm/tracer.rb +33 -0
- data/lib/llm/transport/utils.rb +1 -1
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +4 -13
- data/llm.gemspec +7 -8
- metadata +64 -37
- data/lib/llm/tools/shell.rb +0 -55
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<p align="center">
|
|
2
2
|
<a href="https://r.uby.dev">
|
|
3
3
|
<img
|
|
4
|
-
src="
|
|
4
|
+
src="rubydev.svg"
|
|
5
5
|
width="400"
|
|
6
6
|
height="200"
|
|
7
7
|
border="0"
|
|
@@ -10,21 +10,19 @@
|
|
|
10
10
|
</a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
>
|
|
13
|
+
> [r.uby.dev](https://r.uby.dev/llm) project.
|
|
14
14
|
|
|
15
15
|
Welcome to the canonical llm.rb repository.
|
|
16
16
|
|
|
17
17
|
llm.rb is an advanced runtime for building agentic AI applications
|
|
18
18
|
on CRuby. It has zero runtime dependencies by default, supports
|
|
19
19
|
concurrent and parallel tool execution and has a single coherent API
|
|
20
|
-
that spans 14+ providers.
|
|
21
|
-
REPL, builtin MCP/A2A support and the database integrations all build
|
|
22
|
-
on the same three concepts: providers, contexts, and agents.
|
|
20
|
+
that spans 14+ providers.
|
|
23
21
|
|
|
24
|
-
The
|
|
22
|
+
The most effective way to learn about llm.rb is to ask [the r.uby.dev chatbot](https://r.uby.dev)
|
|
25
23
|
a question. It is connected to the llm.rb GitHub repository, backed by
|
|
26
|
-
ActiveRecord and uses the builtin MCP feature to connect to GitHub.
|
|
27
|
-
|
|
24
|
+
ActiveRecord and uses the builtin MCP feature to connect to GitHub. The chatbot
|
|
25
|
+
is an llm.rb agent that is deployed with [roda-llm](https://github.com/r-uby-dev/roda-llm#readme).
|
|
28
26
|
|
|
29
27
|
## Install
|
|
30
28
|
|
|
@@ -39,9 +37,22 @@ gem install llm.rb
|
|
|
39
37
|
The
|
|
40
38
|
[`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
41
39
|
class is the default high-level interface,
|
|
42
|
-
and it is recommended for most use-cases. It manages tool
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
and it is recommended for most use-cases. It manages the tool loop
|
|
41
|
+
and provides configurable features on top of it. For example you can
|
|
42
|
+
manage the tool loop with a retry budget alongside a tool call budget,
|
|
43
|
+
among other features.
|
|
44
|
+
|
|
45
|
+
The runtime is designed to keep the tool loop alive and it will
|
|
46
|
+
avoid exceptions. When an error is encountered in a tool or during
|
|
47
|
+
the lifecycle of an agent it is almost always reported back to the
|
|
48
|
+
model as an in-band error that allows the model to correct course.
|
|
49
|
+
|
|
50
|
+
A lot of care also goes into keeping the tool loop from entering
|
|
51
|
+
an invalid state that would lead to API-level errors. For example,
|
|
52
|
+
when a tool call is interrupted it could leave an unanswered tool
|
|
53
|
+
call that a model will reject on the next turn. The runtime takes
|
|
54
|
+
care of this by pruning orphaned tool calls and ensuring that the
|
|
55
|
+
tool loop always remains valid.
|
|
45
56
|
|
|
46
57
|
```ruby
|
|
47
58
|
require "llm"
|
|
@@ -206,7 +217,7 @@ isolation from its parent.
|
|
|
206
217
|
|
|
207
218
|
A couple of concurrency strategies require optional, opt-in dependencies.
|
|
208
219
|
The `async` strategy requires the [async](https://github.com/socketry/async)
|
|
209
|
-
gem and the `fork` strategy requires the [xchan.rb](https://github.com/
|
|
220
|
+
gem and the `fork` strategy requires the [xchan.rb](https://github.com/r-uby-dev/xchan.rb)
|
|
210
221
|
gem. The `fiber` strategy requires a scheduler (`Fiber.scheduler`) but by
|
|
211
222
|
default Ruby does not provide one.
|
|
212
223
|
|
|
@@ -247,27 +258,25 @@ end
|
|
|
247
258
|
```
|
|
248
259
|
</details>
|
|
249
260
|
<details>
|
|
250
|
-
<summary>Console (<code>binding.
|
|
261
|
+
<summary>Console (<code>binding.irb</code> for agents)</summary>
|
|
251
262
|
<br>
|
|
252
263
|
|
|
253
|
-
The [LLM::Agent#
|
|
254
|
-
method drops you into
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
264
|
+
The [LLM::Agent#console](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#console-instance_method)
|
|
265
|
+
method drops you into an interactive console that is built on
|
|
266
|
+
top of curses. It can help you debug agents, test your tools,
|
|
267
|
+
connect to MCP servers, and other A2A agents. The console stands
|
|
268
|
+
out because it connects to the surrounding runtime and it can
|
|
269
|
+
be extended by your code. Think of it as `binding.irb` but
|
|
270
|
+
for agents.
|
|
260
271
|
|
|
261
272
|
##### Demo
|
|
262
273
|
|
|
263
|
-
[
|
|
264
|
-
|
|
265
|
-

|
|
274
|
+

|
|
266
275
|
|
|
267
276
|
|
|
268
277
|
##### Installation
|
|
269
278
|
|
|
270
|
-
The
|
|
279
|
+
The console is distributed with llm.rb so you don't have to install
|
|
271
280
|
a separate gem but it requires a number of optional dependencies
|
|
272
281
|
to be installed separately. The following gems provide the full
|
|
273
282
|
experience:
|
|
@@ -276,8 +285,8 @@ experience:
|
|
|
276
285
|
|
|
277
286
|
##### Persistence
|
|
278
287
|
|
|
279
|
-
|
|
280
|
-
across
|
|
288
|
+
the `path:` option can be set on an agent for automatic persistence
|
|
289
|
+
across console sessions. The `tools:` option attaches extra tools
|
|
281
290
|
for the duration of the session. Recall previous turns with Ctrl+P and
|
|
282
291
|
Ctrl+N.
|
|
283
292
|
|
|
@@ -287,21 +296,26 @@ require "llm/tools"
|
|
|
287
296
|
|
|
288
297
|
llm = LLM.deepseek(key: ENV["KEY"])
|
|
289
298
|
agent = LLM::Agent.new(llm, name: "my-agent", path: "agent.json")
|
|
290
|
-
agent.
|
|
299
|
+
agent.console(tools: LLM::Tool.subclasses)
|
|
291
300
|
```
|
|
292
301
|
|
|
293
302
|
##### CLI
|
|
294
303
|
|
|
295
304
|
The `llm.rb` executable is available on your PATH after installation.
|
|
296
|
-
It starts a
|
|
305
|
+
It starts a console session from any directory. The CLI auto-detects your
|
|
297
306
|
provider from standard environment variables (`DEEPSEEK_API_KEY`,
|
|
298
307
|
`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.). Persistent sessions are
|
|
299
308
|
stored under `~/.llm.rb/` and restored automatically on your next visit.
|
|
300
309
|
|
|
301
310
|
```bash
|
|
302
|
-
llm.rb # auto-detect from $
|
|
311
|
+
llm.rb # auto-detect from $PROVIDER_API_KEY
|
|
303
312
|
llm.rb -p openai # use OpenAI explicitly
|
|
313
|
+
llm.rb -m gpt-5.6 # use a model other than the provider default
|
|
314
|
+
llm.rb -c thread # run tool calls on a separate thread
|
|
315
|
+
llm.rb -n curb # use libcurl as the HTTP transport
|
|
316
|
+
llm.rb -x 900 # read timeout of 15 minutes
|
|
304
317
|
llm.rb -t # temporary session, no persistence
|
|
318
|
+
llm.rb -v # print the version
|
|
305
319
|
```
|
|
306
320
|
</details>
|
|
307
321
|
<details>
|
|
@@ -423,8 +437,8 @@ agent = Raven.find(agent.id).tap(&:research_codebase)
|
|
|
423
437
|
##
|
|
424
438
|
# Start an agent console.
|
|
425
439
|
# Query agent's state, debug, etc.
|
|
426
|
-
# The
|
|
427
|
-
agent.
|
|
440
|
+
# The console does not persist back to the database.
|
|
441
|
+
agent.console
|
|
428
442
|
```
|
|
429
443
|
</details>
|
|
430
444
|
|
|
@@ -534,15 +548,15 @@ the call, or `nil` to let it run:
|
|
|
534
548
|
```ruby
|
|
535
549
|
class PolicyGuard < LLM::Guard
|
|
536
550
|
def call(function:)
|
|
537
|
-
if function.name == "
|
|
551
|
+
if function.name == "exec"
|
|
538
552
|
function.return(error: true, type: "policy_error",
|
|
539
|
-
message: "
|
|
553
|
+
message: "exec is disabled")
|
|
540
554
|
end
|
|
541
555
|
end
|
|
542
556
|
end
|
|
543
557
|
|
|
544
558
|
llm = LLM.deepseek(key: ENV["KEY"])
|
|
545
|
-
agent = LLM::Agent.new(llm, tools: [
|
|
559
|
+
agent = LLM::Agent.new(llm, tools: [LLM::Tool::Exec, ReadFile], guard: PolicyGuard)
|
|
546
560
|
```
|
|
547
561
|
</details>
|
|
548
562
|
|
|
@@ -629,21 +643,28 @@ agent.talk "Hello"
|
|
|
629
643
|
<summary>Observability</summary>
|
|
630
644
|
<br>
|
|
631
645
|
|
|
632
|
-
|
|
633
|
-
requests, tool calls, and other
|
|
634
|
-
|
|
635
|
-
observability backend. All built-in tracers
|
|
636
|
-
so switching between them means changing a
|
|
646
|
+
It is possible to trace what an agent is doing by attaching a
|
|
647
|
+
tracer. A tracer can hook into requests, tool calls, and other
|
|
648
|
+
runtime events to debug an agent, provide insights, monitor latency,
|
|
649
|
+
or export spans to an observability backend. All built-in tracers
|
|
650
|
+
share one interface, so switching between them means changing a
|
|
651
|
+
factory method:
|
|
637
652
|
|
|
638
|
-
* [`LLM::Tracer
|
|
639
|
-
* [`LLM::Tracer
|
|
653
|
+
* [`LLM::Tracer.pretty_logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#pretty_logger-class_method): human-readable single-line logs to stderr, ideal during development.
|
|
654
|
+
* [`LLM::Tracer.telemetry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#telemetry-class_method):
|
|
640
655
|
exports spans via OTLP for OpenTelemetry in production.
|
|
641
|
-
* [`LLM::Tracer
|
|
656
|
+
* [`LLM::Tracer.logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html#logger-class_method):
|
|
642
657
|
structured JSON to stdout or a file.
|
|
643
658
|
|
|
659
|
+
It is also possible to create your own tracer by creating a subclass
|
|
660
|
+
of [`LLM::Tracer`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html)
|
|
661
|
+
that implements a number of callbacks that cover an agent's lifecycle.
|
|
662
|
+
The tracer feature provides visibility into what the runtime is doing,
|
|
663
|
+
and the tracer API lets other code hook into that feature.
|
|
664
|
+
|
|
644
665
|
```ruby
|
|
645
666
|
llm = LLM.deepseek(key: ENV["KEY"])
|
|
646
|
-
agent = LLM::Agent.new(llm, tracer: LLM::Tracer
|
|
667
|
+
agent = LLM::Agent.new(llm, tracer: LLM::Tracer.pretty_logger(llm))
|
|
647
668
|
agent.talk "Hello"
|
|
648
669
|
```
|
|
649
670
|
</details>
|
|
@@ -668,7 +689,7 @@ class Agent < LLM::Agent
|
|
|
668
689
|
set name: "sysadmin",
|
|
669
690
|
description: "system administration agent",
|
|
670
691
|
model: "deepseek-v4-pro",
|
|
671
|
-
tools: [LLM::Tool::
|
|
692
|
+
tools: [LLM::Tool::Exec]
|
|
672
693
|
end
|
|
673
694
|
|
|
674
695
|
llm = LLM.deepseek(key: ENV["KEY"])
|
|
@@ -756,7 +777,7 @@ require "llm"
|
|
|
756
777
|
|
|
757
778
|
llm = LLM.openai
|
|
758
779
|
registry = llm.registry # => LLM::Provider#registry
|
|
759
|
-
cheapest = registry.models.sort.first # => LLM::Model
|
|
780
|
+
cheapest = registry.models.sort.first # => LLM::Registry::Model
|
|
760
781
|
cheapest.id # => "text-embedding-3-small"
|
|
761
782
|
cheapest.context_window # => 8191
|
|
762
783
|
cheapest.structured_output? # => false
|
|
@@ -895,6 +916,17 @@ IO.copy_stream res.images[0], "rocket-with-dog.svg"
|
|
|
895
916
|
|
|
896
917
|
## FAQ
|
|
897
918
|
|
|
919
|
+
<details>
|
|
920
|
+
<summary>Where can I see llm.rb in action?</summary>
|
|
921
|
+
<br>
|
|
922
|
+
<p>
|
|
923
|
+
|
|
924
|
+
The [r.uby.dev](https://r.uby.dev) website is powered
|
|
925
|
+
by llm.rb and its builtin MCP feature. It is connected
|
|
926
|
+
to this very GitHub repository. It is designed to help
|
|
927
|
+
you learn and troubleshoot llm.rb.
|
|
928
|
+
</p>
|
|
929
|
+
</details>
|
|
898
930
|
<details>
|
|
899
931
|
<summary>What about local LLM support?</summary>
|
|
900
932
|
<br>
|
|
@@ -962,7 +994,7 @@ than three years and over that time multiple other
|
|
|
962
994
|
contributors have contributed to llm.rb as well. New
|
|
963
995
|
contributors are always welcome.
|
|
964
996
|
|
|
965
|
-
I use the
|
|
997
|
+
I use the console that is distributed with llm.rb to build
|
|
966
998
|
llm.rb itself so there is a healthy feedback loop and
|
|
967
999
|
llm.rb has also been battle tested in production
|
|
968
1000
|
environments.
|
|
@@ -975,13 +1007,19 @@ I am constantly focused on improving llm.rb by using
|
|
|
975
1007
|
it as my primary driver for development.
|
|
976
1008
|
</details>
|
|
977
1009
|
|
|
978
|
-
##
|
|
1010
|
+
## See also
|
|
1011
|
+
|
|
1012
|
+
The [roda-llm](https://github.com/r-uby-dev/roda-llm#readme) project
|
|
1013
|
+
is how I deploy multiple ActiveRecord-backed llm.rb agents over HTTP.
|
|
1014
|
+
Each agent has an identical interface at a unique path that provide
|
|
1015
|
+
CRUD operations and stream support (via SSE - Server Side Events).
|
|
1016
|
+
It lets you focus on implementing agents rather than the glue that
|
|
1017
|
+
brings them together. It is implemented as a Roda plugin that could
|
|
1018
|
+
be hosted within a Rails application or other Rack-based applications.
|
|
979
1019
|
|
|
980
|
-
The [
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
directory contains the full documentation and the chatbot
|
|
984
|
-
can find the answers to your questions there.
|
|
1020
|
+
The [docs/](docs/) directory contains the full documentation and
|
|
1021
|
+
the chatbot can find the answers to your questions there. Or you
|
|
1022
|
+
can read them yourself.
|
|
985
1023
|
|
|
986
1024
|
## License
|
|
987
1025
|
|
data/bin/llm.rb
CHANGED
|
@@ -42,6 +42,10 @@ def wrap(text, width)
|
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
+
def version
|
|
46
|
+
warn "llm.rb v#{LLM::VERSION}"
|
|
47
|
+
end
|
|
48
|
+
|
|
45
49
|
def help
|
|
46
50
|
prog = File.basename($PROGRAM_NAME)
|
|
47
51
|
warn ""
|
|
@@ -54,6 +58,7 @@ def help
|
|
|
54
58
|
warn " -n TRANSPORT HTTP transports - net-http (default), net-http-persistent, and curb"
|
|
55
59
|
warn " -x TIMEOUT The default read timeout (in seconds)"
|
|
56
60
|
warn " -t Temporary session that doesn't persist to disk"
|
|
61
|
+
warn " -v Print version information"
|
|
57
62
|
warn " -h Show this help"
|
|
58
63
|
warn ""
|
|
59
64
|
warn "Examples:"
|
|
@@ -74,12 +79,12 @@ def loaderror(ex)
|
|
|
74
79
|
warn ""
|
|
75
80
|
warn_title "Missing dependency: #{gem}"
|
|
76
81
|
warn ""
|
|
77
|
-
warn " The
|
|
82
|
+
warn " The console needs this gem, but it's not installed."
|
|
78
83
|
warn ""
|
|
79
84
|
wrapped "Fix: gem install #{gem}", " "
|
|
80
85
|
wrapped "Or: bundle add #{gem}", " "
|
|
81
86
|
warn ""
|
|
82
|
-
warn " Tip: If you don't need the
|
|
87
|
+
warn " Tip: If you don't need the console, you can use the"
|
|
83
88
|
wrapped "library directly with: require \"llm\"", " "
|
|
84
89
|
warn ""
|
|
85
90
|
warn " ───────────────────────────────────────────────────────"
|
|
@@ -120,7 +125,7 @@ def main(argv)
|
|
|
120
125
|
# Make sure the dependencies are satisified first
|
|
121
126
|
begin
|
|
122
127
|
require "llm/tools"
|
|
123
|
-
require "llm/
|
|
128
|
+
require "llm/console"
|
|
124
129
|
rescue LLM::LoadError => ex
|
|
125
130
|
loaderror(ex)
|
|
126
131
|
exit 1
|
|
@@ -135,6 +140,9 @@ def main(argv)
|
|
|
135
140
|
when '-h'
|
|
136
141
|
help
|
|
137
142
|
exit 0
|
|
143
|
+
when '-v'
|
|
144
|
+
version
|
|
145
|
+
exit 0
|
|
138
146
|
when '-t'
|
|
139
147
|
temp = true
|
|
140
148
|
when '-c'
|
|
@@ -249,7 +257,7 @@ def main(argv)
|
|
|
249
257
|
concurrency ||= :sequential
|
|
250
258
|
path = temp ? nil : data[Dir.getwd]
|
|
251
259
|
agent = LLM::Agent.new(llm, model:, path:, concurrency:, tools: LLM::Tool.subclasses)
|
|
252
|
-
agent.
|
|
260
|
+
agent.console
|
|
253
261
|
rescue Interrupt
|
|
254
262
|
warn "llm.rb: Bye!"
|
|
255
263
|
rescue => ex
|