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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +336 -11
  3. data/README.md +90 -52
  4. data/bin/llm.rb +12 -4
  5. data/data/alibaba.json +912 -823
  6. data/data/anthropic.json +234 -187
  7. data/data/bedrock.json +3611 -2217
  8. data/data/deepinfra.json +1164 -989
  9. data/data/deepseek.json +65 -81
  10. data/data/google.json +668 -666
  11. data/data/mistral.json +501 -460
  12. data/data/moonshot.json +43 -248
  13. data/data/openai.json +1008 -914
  14. data/data/openrouter.json +7798 -7661
  15. data/data/xai.json +194 -194
  16. data/data/zai.json +242 -149
  17. data/docs/deepdive/advanced/compaction.md +5 -5
  18. data/docs/deepdive/advanced/guard.md +2 -2
  19. data/docs/deepdive/features/builtin_tools.md +93 -22
  20. data/docs/deepdive/features/{repl.md → console.md} +28 -28
  21. data/docs/deepdive/features/database.md +3 -3
  22. data/docs/deepdive/fundamentals/agents.md +2 -2
  23. data/docs/deepdive/fundamentals/providers.md +91 -6
  24. data/docs/deepdive/fundamentals/skills.md +14 -6
  25. data/docs/deepdive/fundamentals/tools.md +63 -31
  26. data/docs/deepdive/reference/cost.md +2 -2
  27. data/docs/deepdive/reference/model_registry.md +2 -2
  28. data/docs/deepdive/reference/tracer.md +15 -13
  29. data/docs/deepdive.md +2 -2
  30. data/lib/llm/active_record/acts_as_agent.rb +10 -6
  31. data/lib/llm/agent.rb +64 -30
  32. data/lib/llm/{repl → console}/bar.rb +3 -3
  33. data/lib/llm/{repl → console}/buffer.rb +4 -4
  34. data/lib/llm/{repl → console}/color.rb +2 -2
  35. data/lib/llm/{repl → console}/command.rb +12 -12
  36. data/lib/llm/{repl → console}/commands/exit.rb +4 -4
  37. data/lib/llm/{repl → console}/commands/help.rb +1 -1
  38. data/lib/llm/{repl/commands/compact.rb → console/commands/keep.rb} +11 -9
  39. data/lib/llm/{repl → console}/commands/model.rb +2 -2
  40. data/lib/llm/{repl → console}/input/cache.rb +2 -2
  41. data/lib/llm/{repl → console}/input/char.rb +2 -2
  42. data/lib/llm/{repl → console}/input/row.rb +1 -1
  43. data/lib/llm/{repl → console}/input.rb +13 -6
  44. data/lib/llm/console/markdown/parser.rb +78 -0
  45. data/lib/llm/{repl → console}/markdown/table.rb +3 -3
  46. data/lib/llm/{repl → console}/markdown.rb +13 -30
  47. data/lib/llm/{repl → console}/node.rb +3 -3
  48. data/lib/llm/{repl → console}/status.rb +11 -11
  49. data/lib/llm/{repl → console}/stream.rb +9 -9
  50. data/lib/llm/{repl → console}/walker.rb +1 -1
  51. data/lib/llm/{repl → console}/window.rb +10 -10
  52. data/lib/llm/{repl.rb → console.rb} +38 -19
  53. data/lib/llm/context/deserializer.rb +2 -1
  54. data/lib/llm/context.rb +1 -0
  55. data/lib/llm/function/async/reactor.rb +20 -1
  56. data/lib/llm/function.rb +8 -9
  57. data/lib/llm/json_adapter.rb +40 -28
  58. data/lib/llm/message.rb +7 -0
  59. data/lib/llm/provider.rb +2 -2
  60. data/lib/llm/providers/alibaba.rb +1 -1
  61. data/lib/llm/providers/deepseek.rb +1 -1
  62. data/lib/llm/providers/openai.rb +1 -0
  63. data/lib/llm/schema/leaf.rb +34 -2
  64. data/lib/llm/schema.rb +4 -2
  65. data/lib/llm/sequel/agent.rb +10 -6
  66. data/lib/llm/tool/param.rb +5 -1
  67. data/lib/llm/tool.rb +5 -0
  68. data/lib/llm/tools/bundle.rb +53 -0
  69. data/lib/llm/tools/edit-file.rb +7 -2
  70. data/lib/llm/tools/exec.rb +78 -0
  71. data/lib/llm/tools/git.rb +27 -26
  72. data/lib/llm/tools/mkdir.rb +12 -19
  73. data/lib/llm/tools/read_file.rb +69 -9
  74. data/lib/llm/tools/rg.rb +20 -24
  75. data/lib/llm/tools/ruby.rb +17 -25
  76. data/lib/llm/tools/utils.rb +74 -1
  77. data/lib/llm/tools/write_file.rb +4 -1
  78. data/lib/llm/tracer/logger.rb +2 -2
  79. data/lib/llm/tracer/pretty_logger.rb +4 -4
  80. data/lib/llm/tracer/telemetry.rb +2 -2
  81. data/lib/llm/tracer.rb +33 -0
  82. data/lib/llm/transport/utils.rb +1 -1
  83. data/lib/llm/version.rb +1 -1
  84. data/lib/llm.rb +4 -13
  85. data/llm.gemspec +7 -8
  86. metadata +64 -37
  87. 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="https://github.com/r-uby-dev/llm.rb/raw/main/rubydev.svg"
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
- > A [r.uby.dev](https://r.uby.dev/llm) project.
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. Streaming, tools, guards, compaction, the
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 easiest way to learn about llm.rb is to ask [the r.uby.dev chatbot](https://r.uby.dev)
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. All
27
- answers are grounded in the llm.rb source code.
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 execution
43
- automatically and guards against infinite loops,
44
- manages conversation state, and much more.
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/0x1eef/xchan.rb)
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.pry</code> for agents)</summary>
261
+ <summary>Console (<code>binding.irb</code> for agents)</summary>
251
262
  <br>
252
263
 
253
- The [LLM::Agent#repl](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
254
- method drops you into a highly capable read-eval-print loop (REPL)
255
- that is built on top of curses. It can help you debug agents,
256
- test your tools, connect to MCP servers, and even A2A agents.
257
- The REPL stands out because it connects to the surrounding
258
- runtime and it can be extended by your code. Think of it as
259
- `binding.pry` but for agents.
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
- [Watch in high quality on asciinema](https://asciinema.org/a/OsS8wwaasKasoDDz)
264
-
265
- ![llm.rb REPL demo](demo.gif)
274
+ ![llm.rb console demo](demo.gif)
266
275
 
267
276
 
268
277
  ##### Installation
269
278
 
270
- The REPL is distributed with llm.rb so you don't have to install
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
- The `path:` option can be set on an agent for automatic persistence
280
- across REPL sessions. The `tools:` option attaches extra tools
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.repl(tools: LLM::Tool.subclasses)
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 REPL session from any directory.The CLI auto-detects your
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 $DEEPSEEK_API_KEY
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 REPL does not persist back to the database.
427
- agent.repl
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 == "shell"
551
+ if function.name == "exec"
538
552
  function.return(error: true, type: "policy_error",
539
- message: "shell is disabled")
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: [Shell, ReadFile], guard: PolicyGuard)
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
- Trace what an agent is doing by attaching a tracer. Hook into
633
- requests, tool calls, and other runtime events to debug a
634
- misbehaving agent, monitor latency, or export spans to an
635
- observability backend. All built-in tracers share one interface,
636
- so switching between them means changing a class name:
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::PrettyLogger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/PrettyLogger.html): human-readable single-line logs to stderr, ideal during development.
639
- * [`LLM::Tracer::Telemetry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Telemetry.html):
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::Logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Logger.html):
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::PrettyLogger.new(llm))
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::Shell]
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 repl that is distributed with llm.rb to build
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
- ## Resources
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 [r.uby.dev chatbot](https://r.uby.dev) is connected
981
- to this very GitHub repository. It can read documentation,
982
- source code, issues, and pull requests. The [docs/](docs/)
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 repl needs this gem, but it's not installed."
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 repl, you can use 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/repl"
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.repl
260
+ agent.console
253
261
  rescue Interrupt
254
262
  warn "llm.rb: Bye!"
255
263
  rescue => ex