llm.rb 13.0.0 → 14.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 (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +505 -14
  3. data/README.md +484 -50
  4. data/bin/llm.rb +148 -0
  5. data/data/anthropic.json +206 -263
  6. data/data/bedrock.json +2138 -1860
  7. data/data/deepinfra.json +1003 -624
  8. data/data/deepseek.json +38 -34
  9. data/data/google.json +1079 -371
  10. data/data/mistral.json +448 -368
  11. data/data/moonshot.json +384 -0
  12. data/data/openai.json +974 -1343
  13. data/data/xai.json +154 -126
  14. data/data/zai.json +191 -191
  15. data/lib/llm/agent.rb +123 -20
  16. data/lib/llm/context.rb +71 -88
  17. data/lib/llm/cost.rb +23 -17
  18. data/lib/llm/error.rb +0 -8
  19. data/lib/llm/function/array.rb +3 -3
  20. data/lib/llm/function/async/task.rb +2 -0
  21. data/lib/llm/function/fiber/task.rb +2 -0
  22. data/lib/llm/function/fork/task.rb +2 -0
  23. data/lib/llm/function/ractor/task.rb +2 -0
  24. data/lib/llm/function/sequential/group.rb +4 -1
  25. data/lib/llm/function/sequential/task.rb +1 -1
  26. data/lib/llm/function/task.rb +4 -0
  27. data/lib/llm/function/thread/task.rb +2 -0
  28. data/lib/llm/function.rb +33 -6
  29. data/lib/llm/guard/loop.rb +89 -0
  30. data/lib/llm/guard/null.rb +19 -0
  31. data/lib/llm/guard.rb +61 -0
  32. data/lib/llm/provider.rb +36 -0
  33. data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
  34. data/lib/llm/providers/anthropic.rb +2 -9
  35. data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
  36. data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
  37. data/lib/llm/providers/bedrock.rb +1 -8
  38. data/lib/llm/providers/google/stream_parser.rb +1 -0
  39. data/lib/llm/providers/google.rb +1 -8
  40. data/lib/llm/providers/mistral.rb +1 -1
  41. data/lib/llm/providers/moonshot.rb +76 -0
  42. data/lib/llm/providers/ollama.rb +2 -9
  43. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
  44. data/lib/llm/providers/openai/responses.rb +7 -9
  45. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  46. data/lib/llm/providers/openai.rb +4 -11
  47. data/lib/llm/repl/bar.rb +4 -3
  48. data/lib/llm/repl/{transcript.rb → buffer.rb} +69 -29
  49. data/lib/llm/repl/color.rb +78 -0
  50. data/lib/llm/repl/command.rb +12 -5
  51. data/lib/llm/repl/commands/compact.rb +2 -2
  52. data/lib/llm/repl/commands/help.rb +3 -5
  53. data/lib/llm/repl/input/char.rb +46 -0
  54. data/lib/llm/repl/input/row.rb +39 -0
  55. data/lib/llm/repl/input.rb +251 -66
  56. data/lib/llm/repl/markdown/table.rb +11 -3
  57. data/lib/llm/repl/markdown.rb +34 -8
  58. data/lib/llm/repl/node.rb +37 -0
  59. data/lib/llm/repl/status.rb +42 -7
  60. data/lib/llm/repl/stream.rb +18 -6
  61. data/lib/llm/repl/walker.rb +3 -2
  62. data/lib/llm/repl/window.rb +54 -35
  63. data/lib/llm/repl.rb +74 -32
  64. data/lib/llm/skill.rb +20 -4
  65. data/lib/llm/stream.rb +8 -7
  66. data/lib/llm/tool.rb +29 -0
  67. data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
  68. data/lib/llm/tools/git.rb +3 -0
  69. data/lib/llm/tools/mkdir.rb +3 -0
  70. data/lib/llm/tools/rg.rb +3 -0
  71. data/lib/llm/tools/ruby.rb +46 -0
  72. data/lib/llm/tools/shell.rb +3 -0
  73. data/lib/llm/tracer/pretty_logger.rb +127 -0
  74. data/lib/llm/tracer.rb +1 -0
  75. data/lib/llm/transformer/null.rb +21 -0
  76. data/lib/llm/transformer.rb +55 -0
  77. data/lib/llm/version.rb +1 -1
  78. data/lib/llm.rb +12 -2
  79. data/llm.gemspec +9 -2
  80. data/resources/deepdive/advanced/cancellation.md +74 -0
  81. data/resources/deepdive/advanced/compaction.md +83 -0
  82. data/resources/deepdive/advanced/context.md +267 -0
  83. data/resources/deepdive/advanced/guard.md +371 -0
  84. data/resources/deepdive/advanced/tracer.md +180 -0
  85. data/resources/deepdive/advanced/transformer.md +67 -0
  86. data/resources/deepdive/advanced/transports.md +45 -0
  87. data/resources/deepdive/everything_else/audio.md +122 -0
  88. data/resources/deepdive/everything_else/cost.md +99 -0
  89. data/resources/deepdive/everything_else/images.md +89 -0
  90. data/resources/deepdive/everything_else/object.md +108 -0
  91. data/resources/deepdive/everything_else/ocr.md +48 -0
  92. data/resources/deepdive/fundamentals/agents.md +202 -0
  93. data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
  94. data/resources/deepdive/fundamentals/concurrency.md +104 -0
  95. data/resources/deepdive/fundamentals/database.md +449 -0
  96. data/resources/deepdive/fundamentals/embeddings.md +157 -0
  97. data/resources/deepdive/fundamentals/repl.md +87 -0
  98. data/resources/deepdive/fundamentals/schema.md +61 -0
  99. data/resources/deepdive/fundamentals/skills.md +106 -0
  100. data/resources/deepdive/fundamentals/stream.md +110 -0
  101. data/resources/deepdive/fundamentals/tools.md +265 -0
  102. data/resources/deepdive/protocols/a2a.md +106 -0
  103. data/resources/deepdive/protocols/mcp.md +111 -0
  104. data/resources/deepdive.md +58 -1792
  105. metadata +51 -7
  106. data/lib/llm/loop_guard.rb +0 -107
data/README.md CHANGED
@@ -15,29 +15,153 @@
15
15
  Welcome to the canonical llm.rb repository.
16
16
 
17
17
  llm.rb is an advanced runtime for building capable AI applications
18
- on CRuby. By default it has zero runtime dependencies although certain
19
- functionality – such as ActiveRecord support – require
18
+ on CRuby. It has zero runtime dependencies by default, and a single
19
+ coherent API that spans 12+ providers. Streaming, tools, guards,
20
+ compaction, the REPL, builtin MCP/A2A support and the database
21
+ integrations all build on the same three concepts: providers,
22
+ contexts, and agents.
23
+
24
+ Once you learn the fundamentals, everything else falls into place
25
+ naturally. Some features, such as ActiveRecord support, require
20
26
  optional dependencies that are opt-in.
21
27
 
22
28
  ## Features
23
29
 
24
- The runtime supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
25
- Gemini, Mistral, DeepSeek, DeepInfra, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp.
26
- It has first-class support for streaming, tool calls, MCP
27
- and A2A, embeddings, vector stores, OCR, context compaction,
28
- and the RAG pattern.
30
+ One runtime, 12+ providers. The same API drives OpenAI, Anthropic,
31
+ Google Gemini, Moonshot (kimi), Mistral, DeepSeek, DeepInfra, xAI, Z.ai,
32
+ AWS Bedrock, Ollama, and llama.cpp, so switching models or providers
33
+ can be done with minimal code change.
29
34
 
30
- There are multiple HTTP backends to choose from, tools can be run concurrently
31
- or in parallel via threads, async tasks, fibers, ractors, and fork, and it is
32
- also possible to make a tool call while the model is still streaming.
35
+ <details>
36
+ <summary><b>Agents</b></summary>
37
+
38
+ * **First-class support** <br>
39
+ llm.rb is designed to build agents. They can be attached to a
40
+ terminal-based read-eval-print loop (repl), persisted to disk
41
+ or a database column, run tools concurrently and be safely
42
+ interrupted.
43
+
44
+ * **Builtin REPL** <br>
45
+ A curses-based TUI for talking to an agent interactively. It
46
+ renders markdown, shows a live status line with context usage
47
+ and running cost, and recalls previous turns, so a
48
+ conversation survives a restart.
49
+
50
+ * **Persistence** <br>
51
+ Set `path:` and the agent saves its conversation to disk
52
+ automatically. ActiveRecord and Sequel support keep the same
53
+ state in a single database column, so you pick the storage
54
+ and the API stays identical.
55
+
56
+ </details>
57
+
58
+ <details>
59
+ <summary><b>MCP &amp; A2A</b></summary>
60
+
61
+ * **MCP** <br>
62
+ The Model Context Protocol is first-class. Point an MCP client
63
+ at any tool server over stdio or HTTP, and its tools translate
64
+ into local `LLM::Tool` subclasses, with the same tracing and
65
+ error handling.
66
+
67
+ * **A2A** <br>
68
+ The Agent 2 Agent protocol is first-class. Point an A2A client
69
+ at another agent over HTTP or JSON-RPC, and call its skills
70
+ exactly like local tools.
71
+
72
+ </details>
73
+
74
+ <details>
75
+ <summary><b>ORM</b></summary>
76
+
77
+ * **ActiveRecord** <br>
78
+ Add `acts_as_agent` to a model and the agent state lives in a
79
+ single database column, saved after every turn and restored
80
+ on load. Works in Rack and Rails apps, with `jsonb` on
81
+ PostgreSQL.
82
+
83
+ * **Sequel** <br>
84
+ Add `plugin :agent` to a Sequel model for the same single-
85
+ column persistence, with the `pg_json` extension loaded
86
+ automatically on PostgreSQL.
87
+
88
+ </details>
89
+
90
+ <details>
91
+ <summary><b>RAG</b></summary>
92
+
93
+ * **RAG, out of the box** <br>
94
+ Embeddings, OCR, and OpenAI's vector stores API come first-
95
+ class. Ground answers in your own documents, with vectors in
96
+ a managed store or in your own database such as sqlite-vec
97
+ or pgvector.
98
+
99
+ </details>
100
+
101
+ <details>
102
+ <summary><b>Runtime</b></summary>
103
+
104
+ * **Streaming** <br>
105
+ Streaming is first-class, with structured callbacks for
106
+ content, reasoning, and tool calls. Tools can start while
107
+ the model is still talking, so the first result lands
108
+ before the response finishes.
109
+
110
+ * **Concurrency** <br>
111
+ Six ways to run tools: sequential, threads, async, fibers,
112
+ forks, and ractors. Plus three HTTP backends, so you pick
113
+ the concurrency model that fits the workload, not the other
114
+ way around.
33
115
 
34
- The runtime builds on top of three core concepts: providers, contexts, and agents,
35
- so once you learn the fundamentals, everything else falls into place naturally. And once
36
- you learn llm.rb, you will also be able to use <a href="https://r.uby.dev/mruby-llm">mruby-llm</a> and
37
- <a href="https://r.uby.dev/wasm-llm">wasm-llm</a> because the API is pretty much identical.
116
+ * **Interruption** <br>
117
+ Cancel an in-flight request or a running tool at any moment,
118
+ on any transport or concurrency strategy. A stuck call never
119
+ leaves a thread running that you can't stop.
120
+
121
+ </details>
122
+
123
+ <details>
124
+ <summary><b>Provider extras</b></summary>
125
+
126
+ * **DeepSeek-optimized** <br>
127
+ DeepSeek is the most cost-effective option for API users, and the
128
+ runtime closes its gaps: [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html)
129
+ makes structured outputs work despite no official API, and
130
+ `images.create`/`edit` produce SVG vector graphics.
131
+ </details>
132
+
133
+ <details>
134
+ <summary><b>Portable</b></summary>
38
135
 
39
- For detailed explanations, configuration, and advanced patterns, see the
40
- [deepdive.md](https://r.uby.dev/llm/deepdive/).
136
+ * **mruby-llm** <br>
137
+ The same runtime runs on mruby as
138
+ [mruby-llm](https://github.com/r-uby-dev/mruby-llm), with an
139
+ almost identical interface and the same set of capabilities.
140
+
141
+ </details>
142
+
143
+ <details>
144
+ <summary><b>Everything else</b></summary>
145
+
146
+ * **Skills** <br>
147
+ Write a SKILL.md, get a tool. The runtime spawns a
148
+ disposable subagent with the skill's instructions and tool
149
+ set for one turn, then discards it. Fresh and stateless
150
+ every call.
151
+
152
+ * **A unified plugin family** <br>
153
+ Compactors, transformers, and guards all share one
154
+ interface. Context management, message rewriting, and tool
155
+ supervision (policy, quotas, loop detection) plug in the
156
+ same way and compose freely.
157
+
158
+ * **Cost and usage tracking** <br>
159
+ Every context tracks its own cost and token usage, per turn.
160
+ Break the spend down by input, output, cache, and reasoning,
161
+ so the exact cost of any conversation is visible at a
162
+ glance.
163
+
164
+ </details>
41
165
 
42
166
  ## Install
43
167
 
@@ -49,10 +173,13 @@ gem install llm.rb
49
173
 
50
174
  #### LLM::Agent
51
175
 
52
- The [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) class is the default high-level interface,
176
+ The
177
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
178
+ class is the default high-level interface,
53
179
  and it is recommended for most use-cases. It manages tool execution
54
- automatically, guards against infinite loops, manages conversation
55
- state, and much more.
180
+ automatically and
181
+ [guards against infinite loops](https://r.uby.dev/llm/deepdive/advanced/guard),
182
+ manages conversation state, and much more.
56
183
 
57
184
  ```ruby
58
185
  require "llm"
@@ -62,13 +189,69 @@ agent = LLM::Agent.new(llm, stream: $stdout)
62
189
  agent.talk "Hello world"
63
190
  ```
64
191
 
192
+ ##### set
193
+
194
+ [`LLM::Agent.set`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#set-class_method)
195
+ is a class-level DSL that accepts a Hash of properties. Each key resolves to a
196
+ corresponding class accessor: `name`, `description`, `model`, `tools`,
197
+ `instructions`, `schema`, `stream`, `tracer`, `concurrency`, `confirm`,
198
+ `path`, `skills`, and `tool_budget`. All options are optional; zero or
199
+ more can be set.
200
+ An error is raised for unknown keys so that typos are caught early.
201
+
202
+ ```ruby
203
+ class SystemAdmin < LLM::Agent
204
+ set name: "sysadmin",
205
+ description: "system administration agent",
206
+ model: "deepseek-v4-pro",
207
+ tools: [Shell]
208
+ end
209
+
210
+ llm = LLM.deepseek(key: ENV["KEY"])
211
+ agent = SystemAdmin.new(llm)
212
+ agent.talk "Run 'date'"
213
+ ```
214
+
215
+ ##### Persistence
216
+
217
+ Set `path:` on an agent for automatic filesystem persistence;
218
+ the agent restores conversation history from the file on startup
219
+ and saves it back after every turn, with no manual serialization
220
+ code. For database-backed persistence, ActiveRecord and Sequel
221
+ integrations are also available (see the
222
+ [database deepdive](https://r.uby.dev/llm/deepdive/advanced/database)
223
+ for details). All persistence options use the same underlying
224
+ serialization.
225
+
226
+ ```ruby
227
+ require "llm"
228
+
229
+ llm = LLM.deepseek(key: ENV["KEY"])
230
+ agent = LLM::Agent.new(llm, path: "session.json")
231
+ agent.talk "remember my name is robert"
232
+
233
+ # Next time, the conversation is restored automatically:
234
+ agent = LLM::Agent.new(llm, path: "session.json")
235
+ agent.talk "what's my name?"
236
+ ```
237
+
65
238
  #### LLM::Context
66
239
 
67
- The [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) class is at the heart of the runtime
68
- and it is what [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) uses under the hood.
240
+ The
241
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
242
+ class is at the heart of the runtime
243
+ and it is what
244
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
245
+ uses under the hood.
69
246
  It requires that the tool call loop be managed manually -
70
247
  sometimes that can be useful, but usually for advanced use-cases.
71
- If you're new to llm.rb, try [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) first.
248
+ If you're new to llm.rb, try
249
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) first.
250
+
251
+ Every context tracks its own token usage and estimated cost. After any
252
+ turn, you can read the cost breakdown through
253
+ [`LLM::Context#cost`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#cost-instance_method),
254
+ and the REPL shows the running total in the status line.
72
255
 
73
256
  ```ruby
74
257
  require "llm"
@@ -80,11 +263,18 @@ ctx.talk "Hello world"
80
263
 
81
264
  #### LLM::Tool
82
265
 
83
- Subclasses of [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) are plain Ruby classes with
266
+ Subclasses of
267
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
268
+ are plain Ruby classes with
84
269
  an optional set of typed parameters. <br> The model can choose to
85
270
  call them on your behalf, and they're one of the most powerful features
86
271
  for extending the feature set or abilities of a model.
87
272
 
273
+ The runtime also ships with a catalog of built-in tools for
274
+ filesystem, search, and shell operations. See the
275
+ [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/builtin_tools)
276
+ for details.
277
+
88
278
  ```ruby
89
279
  class ReadFile < LLM::Tool
90
280
  name "read-file"
@@ -98,11 +288,39 @@ class ReadFile < LLM::Tool
98
288
  end
99
289
  ```
100
290
 
291
+ ##### set
292
+
293
+ [`LLM::Tool.set`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html#set-class_method)
294
+ is an alternative way to define tool properties using a Hash. It works
295
+ the same way as
296
+ [`LLM::Agent.set`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#set-class_method)
297
+ and accepts the same keys that the individual methods do: `name`,
298
+ `description`, `parameters`, `required`, and `defaults`:
299
+
300
+ ```ruby
301
+ class MathTool < LLM::Tool
302
+ set name: "math",
303
+ description: "Performs arithmetic",
304
+ parameters: [
305
+ [:x, Integer, "first number" , {required: true}],
306
+ [:y, Integer, "second number", {default: 0}]
307
+ ]
308
+
309
+ def call(x:, y: 0)
310
+ {result: x + y}
311
+ end
312
+ end
313
+ ```
314
+
101
315
  #### LLM::Stream
102
316
 
103
317
  Streams can be simple IO objects or subclasses of
104
- [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html) with structured callbacks for content,
318
+ [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
319
+ with structured callbacks for content,
105
320
  reasoning, tool calls, tool returns, and compaction.
321
+ Streams can also observe message transformers, which rewrite
322
+ outgoing messages before they reach the provider (see the
323
+ [deepdive.md](https://r.uby.dev/llm/deepdive/advanced/transformer)).
106
324
 
107
325
  ```ruby
108
326
  class MyStream < LLM::Stream
@@ -122,16 +340,30 @@ agent.talk "Explain Ruby fibers."
122
340
 
123
341
  #### LLM::Schema
124
342
 
125
- [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html) subclasses produce typed, structured
126
- output from any model call. Pass a schema to `LLM::Context#talk`,
127
- `LLM::Agent#talk`, or `LLM::Provider#complete` to receive validated
128
- JSON instead of free text. Schemas work alongside tools and streams.
129
-
130
- [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html) can define objects, arrays, enums, nested schemas,
131
- and more. It is also used internally by [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) for parameter
343
+ [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html)
344
+ subclasses produce typed, structured
345
+ output from any model call. Pass a schema to
346
+ [`LLM::Context#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html#talk-instance_method),
347
+ [`LLM::Agent#talk`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#talk-instance_method),
348
+ or
349
+ [`LLM::Provider#complete`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html#complete-instance_method)
350
+ to receive validated JSON instead of free text. Schemas work alongside tools and streams.
351
+
352
+ [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html)
353
+ can define objects, arrays, enums, nested schemas,
354
+ and more. It is also used internally by
355
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) for parameter
132
356
  definitions, so you already benefit from it when you declare tool
133
357
  parameters.
134
358
 
359
+ The
360
+ [`LLM::DeepSeek`](https://r.uby.dev/api-docs/llm.rb/LLM/DeepSeek.html)
361
+ provider includes runtime-level optimisations such as structured
362
+ output support (despite no official structured outputs API) and
363
+ SVG image generation. This example uses
364
+ [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html) with
365
+ DeepSeek:
366
+
135
367
  ```ruby
136
368
  class Weather < LLM::Schema
137
369
  property :city, String, "The city name"
@@ -140,7 +372,7 @@ class Weather < LLM::Schema
140
372
  required %i[city temperature conditions]
141
373
  end
142
374
 
143
- llm = LLM.openai(key: ENV["KEY"])
375
+ llm = LLM.deepseek(key: ENV["KEY"])
144
376
  agent = LLM::Agent.new(llm, schema: Weather)
145
377
  res = agent.talk "Weather in Paris?"
146
378
  res.content! # => {city: "Paris", temperature: 15.0, conditions: "Cloudy"}
@@ -150,11 +382,17 @@ res.content! # => {city: "Paris", temperature: 15.0, conditions: "Cloudy"}
150
382
 
151
383
  The [LLM::Agent#repl](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
152
384
  method drops you into a curses-based TUI for talking to an
153
- agent interactively. The `path:` option saves and restores
154
- runtime state across sessions. The `tools:` option attaches
155
- extra tools for the duration of the session. It is like
156
- `binding.pry` but for agents. For the full reference see the
157
- [REPL section](https://r.uby.dev/llm/deepdive/#repl) in the
385
+ agent interactively. It renders markdown directly in the
386
+ terminal and shows a live status line with context usage,
387
+ running cost, and the current tool call. A second thread keeps
388
+ the UI responsive while the model works. Think of it as
389
+ `binding.pry` but for agents.
390
+
391
+ Set `path:` on the agent for automatic persistence across REPL
392
+ sessions. The `tools:` option attaches extra tools for the
393
+ duration of the session. Recall previous turns with Ctrl+P and
394
+ Ctrl+N. For the full reference, see the
395
+ [REPL section](https://r.uby.dev/llm/deepdive/fundamentals/repl) in the
158
396
  deepdive.
159
397
 
160
398
  ```ruby
@@ -162,17 +400,35 @@ require "llm"
162
400
  require "llm/tools"
163
401
 
164
402
  llm = LLM.deepseek(key: ENV["KEY"])
165
- agent = LLM::Agent.new(llm, name: "my-agent")
166
- agent.repl(path: "agent.json", tools: LLM::Tool.subclasses)
403
+ agent = LLM::Agent.new(llm, name: "my-agent", path: "agent.json")
404
+ agent.repl(tools: LLM::Tool.subclasses)
405
+ ```
406
+
407
+ ##### CLI
408
+
409
+ The `llm.rb` executable is available on your PATH after installation.
410
+ It starts a REPL session from any directory:
411
+
412
+ ```bash
413
+ llm.rb # auto-detect from $DEEPSEEK_API_KEY
414
+ llm.rb -p openai # use OpenAI explicitly
415
+ llm.rb -t # temporary session, no persistence
167
416
  ```
168
417
 
418
+ The CLI auto-detects your provider from standard environment variables
419
+ (`DEEPSEEK_API_KEY`, `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.).
420
+ Persistent sessions are stored under `~/.llm.rb/` and restored
421
+ automatically on your next visit.
422
+
169
423
  #### LLM::MCP
170
424
 
171
425
  The Model Context Protocol (MCP) has first-class support
172
426
  in llm.rb. The stdio and http transports work out of the
173
427
  box. MCP tools are translated into subclasses of
174
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) that can be used with [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
175
- or [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
428
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) that can be
429
+ used with
430
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) or
431
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
176
432
 
177
433
  ```ruby
178
434
  require "llm"
@@ -183,13 +439,31 @@ agent = LLM::Agent.new(llm, stream: $stdout, tools: mcp.tools)
183
439
  agent.talk "Run the tool"
184
440
  ```
185
441
 
442
+ ##### Persistent connections
443
+
444
+ Set `persistent: true` on HTTP transports to reuse connections
445
+ across requests. This uses
446
+ [`Net::HTTP::Persistent`](https://github.com/drbrain/net-http-persistent)
447
+ under the hood and avoids opening a new TCP connection for every
448
+ request:
449
+
450
+ ```ruby
451
+ mcp = LLM::MCP.http(
452
+ url: "https://api.githubcopilot.com/mcp/",
453
+ headers: {"Authorization" => "Bearer #{ENV.fetch('GITHUB_PAT')}"},
454
+ persistent: true
455
+ )
456
+ ```
457
+
186
458
  #### LLM::A2A
187
459
 
188
460
  The Agent 2 Agent (A2A) protocol has first-class support
189
461
  in llm.rb. The http and jsonrpc transports work out of the
190
462
  box. A2A skills are translated into subclasses of
191
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) that can be used with [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
192
- or [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
463
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) that can be
464
+ used with
465
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) or
466
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
193
467
 
194
468
  ```ruby
195
469
  require "llm"
@@ -200,6 +474,84 @@ agent = LLM::Agent.new(llm, stream: $stdout, tools: a2a.skills)
200
474
  agent.talk "Run the skill"
201
475
  ```
202
476
 
477
+ ##### Persistent connections
478
+
479
+ Set `persistent: true` on HTTP transports to reuse connections
480
+ across requests. This uses
481
+ [`Net::HTTP::Persistent`](https://github.com/drbrain/net-http-persistent)
482
+ under the hood and avoids opening a new TCP connection for every
483
+ request:
484
+
485
+ ```ruby
486
+ a2a = LLM::A2A.rest(url: "https://agent.example.com", persistent: true)
487
+ a2a = LLM::A2A.jsonrpc(url: "https://agent.example.com", persistent: true)
488
+ ```
489
+
490
+ #### LLM::Guard
491
+
492
+ [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
493
+ is the hook that sees every tool call before it runs. A guard
494
+ can let a call through, cancel it, block it with an error, or
495
+ even answer for it. Because it runs before the tool, anything
496
+ it intercepts never executes. Policy, validation, quotas, and
497
+ cost ceilings all live here.
498
+
499
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
500
+ enables
501
+ [`LLM::Guard::Loop`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard/Loop.html)
502
+ by default, so agents get loop protection out of the box. To
503
+ write your own guard, subclass
504
+ [`LLM::Guard`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html)
505
+ and implement
506
+ [`LLM::Guard#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Guard.html#call-instance_method).
507
+ The pending call arrives as `function:`. Return a value to close
508
+ the call, or `nil` to let it run:
509
+
510
+ ```ruby
511
+ class PolicyGuard < LLM::Guard
512
+ def call(function:)
513
+ if function.name == "shell"
514
+ function.return(error: true, type: "policy_error",
515
+ message: "shell is disabled")
516
+ end
517
+ end
518
+ end
519
+
520
+ agent = LLM::Agent.new(llm, guard: PolicyGuard)
521
+ ```
522
+
523
+ #### LLM::Skill
524
+
525
+ A skill turns a markdown file into a callable tool. When the model
526
+ calls it, the runtime spawns a subagent with the skill's instructions
527
+ as its system prompt and the skill's own tool set. The subagent runs
528
+ one turn and returns the result, then is discarded. Each call
529
+ is fresh and stateless. For a deeper explanation see the
530
+ [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/skills).
531
+
532
+ ##### SKILL.md
533
+
534
+ ```markdown
535
+ ---
536
+ name: summary
537
+ description: Reads recent git history and writes a summary
538
+ tools: all
539
+ ---
540
+
541
+ Collect the recent git log, analyze each commit,
542
+ and write a summary to summary.txt.
543
+ ```
544
+
545
+ ##### agent.rb
546
+
547
+ ```ruby
548
+ require "llm"
549
+
550
+ llm = LLM.deepseek(key: ENV["KEY"])
551
+ agent = LLM::Agent.new(llm, skills: ["./skills/summary"])
552
+ agent.talk "Summarize the last week of work"
553
+ ```
554
+
203
555
  #### RAG
204
556
 
205
557
  Most providers offer an embedding model that can be
@@ -211,7 +563,8 @@ or PostgreSQL's [pg-vector](https://github.com/pgvector/pgvector).
211
563
 
212
564
  llm.rb also includes support for OpenAI's vector store API. It
213
565
  provides a vector database as a HTTP service but we won't cover
214
- that here.
566
+ that here. For a deeper explanation see the
567
+ [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/embeddings).
215
568
 
216
569
  ```ruby
217
570
  require "llm"
@@ -220,6 +573,8 @@ llm = LLM.openai(key: ENV["KEY"])
220
573
  body = "llm.rb is Ruby's capable AI runtime."
221
574
  embedding = llm.embed([body]).embeddings.first
222
575
 
576
+ # Document is your ActiveRecord or Sequel model
577
+ # with a vector column (e.g. sqlite-vec or pgvector)
223
578
  Document.create!(
224
579
  title: "llm.rb",
225
580
  body:,
@@ -241,7 +596,7 @@ The `:fork` strategy also provides a separate process that offers
241
596
  isolation from its parent.
242
597
 
243
598
  You can learn more about the llm.rb concurrency model in the
244
- [deepdive.md](https://r.uby.dev/llm/deepdive/#concurrency).
599
+ [deepdive.md](https://r.uby.dev/llm/deepdive/fundamentals/concurrency).
245
600
 
246
601
  ```ruby
247
602
  require "llm"
@@ -254,7 +609,9 @@ agent.talk "Run the tools in parallel"
254
609
 
255
610
  #### ORM
256
611
 
257
- Because both [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html), and [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
612
+ Because both
613
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) and
614
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
258
615
  can be serialized to JSON and stored in a simple string, both ActiveRecord
259
616
  and Sequel support can be implemented within a single column on a single row.
260
617
 
@@ -294,6 +651,44 @@ agent = Agent.create!
294
651
  agent.talk "perform research"
295
652
  ```
296
653
 
654
+ #### Images
655
+
656
+ A handful of providers can generate images from a text prompt.
657
+ OpenAI, Google, xAI, and DeepInfra all support it. The API is
658
+ the same across providers:
659
+
660
+ ```ruby
661
+ require "llm"
662
+
663
+ llm = LLM.openai(key: ENV["KEY"])
664
+ res = llm.images.create(prompt: "a dog on a rocket to the moon")
665
+ IO.copy_stream res.images[0], "rocket.png"
666
+ ```
667
+
668
+ ##### DeepSeek
669
+
670
+ DeepSeek does not have a dedicated image model, but the runtime
671
+ generates SVG vector graphics through its text model. Each
672
+ generation produces a valid SVG document that can be converted
673
+ to PNG with tools like `rsvg-convert`. Pass an existing agent
674
+ to maintain a session across generations:
675
+
676
+ ```ruby
677
+ require "llm"
678
+ llm = LLM.deepseek(key: ENV["KEY"])
679
+
680
+ ##
681
+ # First generation
682
+ res = llm.images.create(prompt: "a rocket on the moon")
683
+ IO.copy_stream res.images[0], "rocket.svg"
684
+
685
+ ##
686
+ # Refine with follow-up prompts (shares context)
687
+ res = llm.images.create(prompt: "add a dog next to the rocket",
688
+ agent: res.agent)
689
+ IO.copy_stream res.images[0], "rocket-with-dog.svg"
690
+ ```
691
+
297
692
  ## FAQ
298
693
 
299
694
  <details>
@@ -314,6 +709,7 @@ In no particular order:
314
709
  🇺🇸 Anthropic <br>
315
710
  🇨🇳 DeepSeek <br>
316
711
  🇨🇳 zAI <br>
712
+ 🇨🇳 Moonshot AI (Kimi) <br>
317
713
  🇪🇺 Mistral <br>
318
714
 
319
715
  **Weights**
@@ -325,6 +721,7 @@ In no particular order:
325
721
  🇺🇸 AWS bedrock <br>
326
722
  🇨🇳 DeepSeek <br>
327
723
  🇨🇳 zAI <br>
724
+ 🇨🇳 Moonshot AI (Kimi) <br>
328
725
  🇪🇺 Mistral <br>
329
726
 
330
727
  **Local**
@@ -341,9 +738,9 @@ In no particular order:
341
738
  <summary>I have a limited budget. What should I do?</summary>
342
739
  <br>
343
740
  <p>
344
- There a few options. The first option is to host
741
+ There are a few options. The first option is to host
345
742
  your own model, and use the ollama or llamacpp
346
- providers. This can be diffilcult though because
743
+ providers. This can be difficult though because
347
744
  a capable model requires hardware that can
348
745
  match it. If you have the ability to self-host,
349
746
  this would be my first option.
@@ -368,7 +765,7 @@ If you're on a budget, DeepSeek is hard to beat.
368
765
  <details>
369
766
  <summary>Can I download llm.rb via a decentralized network?</summary>
370
767
  <br>
371
- You can!
768
+ Yes.
372
769
  <br>
373
770
  We are on the <a href="https://radicle.network">radicle.network</a>
374
771
  <br>
@@ -376,7 +773,9 @@ Every commit that lands on GitHub also lands on Radicle.
376
773
  <br>
377
774
  Our repository ID is z2PtfQ6dYwyYaW2aGrztG1sMyDmCE.
378
775
  <br>
379
- Browse on <a href="https://radicle.network/nodes/iris.radicle.network/z2PtfQ6dYwyYaW2aGrztG1sMyDmCE">the web</a>.
776
+ Browse on <a
777
+ href="https://radicle.network/nodes/iris.radicle.network/z2PtfQ6dYwyYaW2aGrztG1sMyDmCE">the
778
+ web</a>.
380
779
  </details>
381
780
 
382
781
  ## Resources
@@ -387,6 +786,41 @@ wasn't possible to cover every feature without the README becoming a small book.
387
786
  The [r.uby.dev](https://r.uby.dev) homepage also includes more learning material
388
787
  and resources.
389
788
 
789
+ ## Developers
790
+
791
+ The llm.rb project is quite large and maintained primarily by one
792
+ person. It would be near impossible for me to maintain both the codebase
793
+ and its documentation, especially the [deepdive.md](https://r.uby.dev/llm/deepdive/)
794
+ so I have written agents that maintain the documentation assets and that
795
+ allows me to put more focus on the code.
796
+
797
+ The following agents are available for those tasks, and all of them
798
+ use the most cost effective option: DeepSeek. Feel free to use them
799
+ in your own fork.
800
+
801
+ ```sh
802
+ ##
803
+ # Maintains the deepdive and API docs
804
+ rake agents:scribe:yardoc
805
+ rake agents:scribe:coverage
806
+ rake agents:scribe:regressions
807
+ rake agents:scribe:style
808
+
809
+ ##
810
+ # Maintains the release
811
+ rake agents:dexter:changelog
812
+ rake agents:dexter:release
813
+
814
+ ##
815
+ # Maintains mruby-llm backports
816
+ rake agents:mruby:research
817
+ rake agents:mruby:implement
818
+
819
+ ##
820
+ # Refresh the data/ registry
821
+ rake models.dev:download
822
+ ```
823
+
390
824
  ## License
391
825
 
392
826
  This software is released under the terms of the MIT license. <br>