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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +505 -14
- data/README.md +484 -50
- data/bin/llm.rb +148 -0
- data/data/anthropic.json +206 -263
- data/data/bedrock.json +2138 -1860
- data/data/deepinfra.json +1003 -624
- data/data/deepseek.json +38 -34
- data/data/google.json +1079 -371
- data/data/mistral.json +448 -368
- data/data/moonshot.json +384 -0
- data/data/openai.json +974 -1343
- data/data/xai.json +154 -126
- data/data/zai.json +191 -191
- data/lib/llm/agent.rb +123 -20
- data/lib/llm/context.rb +71 -88
- data/lib/llm/cost.rb +23 -17
- data/lib/llm/error.rb +0 -8
- data/lib/llm/function/array.rb +3 -3
- data/lib/llm/function/async/task.rb +2 -0
- data/lib/llm/function/fiber/task.rb +2 -0
- data/lib/llm/function/fork/task.rb +2 -0
- data/lib/llm/function/ractor/task.rb +2 -0
- data/lib/llm/function/sequential/group.rb +4 -1
- data/lib/llm/function/sequential/task.rb +1 -1
- data/lib/llm/function/task.rb +4 -0
- data/lib/llm/function/thread/task.rb +2 -0
- data/lib/llm/function.rb +33 -6
- data/lib/llm/guard/loop.rb +89 -0
- data/lib/llm/guard/null.rb +19 -0
- data/lib/llm/guard.rb +61 -0
- data/lib/llm/provider.rb +36 -0
- data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
- data/lib/llm/providers/anthropic.rb +2 -9
- data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
- data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
- data/lib/llm/providers/bedrock.rb +1 -8
- data/lib/llm/providers/google/stream_parser.rb +1 -0
- data/lib/llm/providers/google.rb +1 -8
- data/lib/llm/providers/mistral.rb +1 -1
- data/lib/llm/providers/moonshot.rb +76 -0
- data/lib/llm/providers/ollama.rb +2 -9
- data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
- data/lib/llm/providers/openai/responses.rb +7 -9
- data/lib/llm/providers/openai/stream_parser.rb +1 -0
- data/lib/llm/providers/openai.rb +4 -11
- data/lib/llm/repl/bar.rb +4 -3
- data/lib/llm/repl/{transcript.rb → buffer.rb} +69 -29
- data/lib/llm/repl/color.rb +78 -0
- data/lib/llm/repl/command.rb +12 -5
- data/lib/llm/repl/commands/compact.rb +2 -2
- data/lib/llm/repl/commands/help.rb +3 -5
- data/lib/llm/repl/input/char.rb +46 -0
- data/lib/llm/repl/input/row.rb +39 -0
- data/lib/llm/repl/input.rb +251 -66
- data/lib/llm/repl/markdown/table.rb +11 -3
- data/lib/llm/repl/markdown.rb +34 -8
- data/lib/llm/repl/node.rb +37 -0
- data/lib/llm/repl/status.rb +42 -7
- data/lib/llm/repl/stream.rb +18 -6
- data/lib/llm/repl/walker.rb +3 -2
- data/lib/llm/repl/window.rb +54 -35
- data/lib/llm/repl.rb +74 -32
- data/lib/llm/skill.rb +20 -4
- data/lib/llm/stream.rb +8 -7
- data/lib/llm/tool.rb +29 -0
- data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
- data/lib/llm/tools/git.rb +3 -0
- data/lib/llm/tools/mkdir.rb +3 -0
- data/lib/llm/tools/rg.rb +3 -0
- data/lib/llm/tools/ruby.rb +46 -0
- data/lib/llm/tools/shell.rb +3 -0
- data/lib/llm/tracer/pretty_logger.rb +127 -0
- data/lib/llm/tracer.rb +1 -0
- data/lib/llm/transformer/null.rb +21 -0
- data/lib/llm/transformer.rb +55 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +12 -2
- data/llm.gemspec +9 -2
- data/resources/deepdive/advanced/cancellation.md +74 -0
- data/resources/deepdive/advanced/compaction.md +83 -0
- data/resources/deepdive/advanced/context.md +267 -0
- data/resources/deepdive/advanced/guard.md +371 -0
- data/resources/deepdive/advanced/tracer.md +180 -0
- data/resources/deepdive/advanced/transformer.md +67 -0
- data/resources/deepdive/advanced/transports.md +45 -0
- data/resources/deepdive/everything_else/audio.md +122 -0
- data/resources/deepdive/everything_else/cost.md +99 -0
- data/resources/deepdive/everything_else/images.md +89 -0
- data/resources/deepdive/everything_else/object.md +108 -0
- data/resources/deepdive/everything_else/ocr.md +48 -0
- data/resources/deepdive/fundamentals/agents.md +202 -0
- data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
- data/resources/deepdive/fundamentals/concurrency.md +104 -0
- data/resources/deepdive/fundamentals/database.md +449 -0
- data/resources/deepdive/fundamentals/embeddings.md +157 -0
- data/resources/deepdive/fundamentals/repl.md +87 -0
- data/resources/deepdive/fundamentals/schema.md +61 -0
- data/resources/deepdive/fundamentals/skills.md +106 -0
- data/resources/deepdive/fundamentals/stream.md +110 -0
- data/resources/deepdive/fundamentals/tools.md +265 -0
- data/resources/deepdive/protocols/a2a.md +106 -0
- data/resources/deepdive/protocols/mcp.md +111 -0
- data/resources/deepdive.md +58 -1792
- metadata +51 -7
- 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.
|
|
19
|
-
|
|
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
|
-
|
|
25
|
-
Gemini, Mistral, DeepSeek, DeepInfra, xAI, Z.ai,
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 & 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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
55
|
-
|
|
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
|
|
68
|
-
|
|
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
|
|
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
|
|
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)
|
|
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)
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
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.
|
|
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.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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(
|
|
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
|
|
175
|
-
|
|
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
|
|
192
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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>
|