llm.rb 11.3.1 → 12.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +354 -2
- data/LICENSE +93 -17
- data/README.md +226 -616
- data/data/anthropic.json +322 -426
- data/data/bedrock.json +2634 -1144
- data/data/deepinfra.json +1513 -0
- data/data/deepseek.json +57 -28
- data/data/google.json +411 -771
- data/data/openai.json +1104 -771
- data/data/xai.json +141 -292
- data/data/zai.json +263 -141
- data/lib/llm/active_record/acts_as_agent.rb +3 -41
- data/lib/llm/active_record/acts_as_llm.rb +18 -0
- data/lib/llm/active_record.rb +3 -3
- data/lib/llm/agent.rb +25 -1
- data/lib/llm/context.rb +17 -5
- data/lib/llm/contract/completion.rb +2 -2
- data/lib/llm/json_adapter.rb +29 -3
- data/lib/llm/provider.rb +3 -3
- data/lib/llm/providers/deepinfra/audio.rb +66 -0
- data/lib/llm/providers/deepinfra/images.rb +90 -0
- data/lib/llm/providers/deepinfra/response_adapter.rb +36 -0
- data/lib/llm/providers/deepinfra.rb +100 -0
- data/lib/llm/providers/deepseek/images.rb +109 -0
- data/lib/llm/providers/deepseek/request_adapter.rb +32 -0
- data/lib/llm/providers/deepseek/response_adapter/image.rb +9 -0
- data/lib/llm/providers/deepseek/response_adapter.rb +29 -0
- data/lib/llm/providers/deepseek.rb +4 -2
- data/lib/llm/providers/google/request_adapter.rb +22 -5
- data/lib/llm/providers/google.rb +4 -4
- data/lib/llm/providers/llamacpp.rb +5 -5
- data/lib/llm/providers/openai/audio.rb +6 -2
- data/lib/llm/providers/openai/images.rb +9 -50
- data/lib/llm/providers/openai/request_adapter/respond.rb +38 -4
- data/lib/llm/providers/openai/response_adapter/audio.rb +5 -1
- data/lib/llm/providers/openai/response_adapter/completion.rb +1 -1
- data/lib/llm/providers/openai/response_adapter/image.rb +0 -4
- data/lib/llm/providers/openai/responses.rb +1 -0
- data/lib/llm/providers/openai/stream_parser.rb +5 -6
- data/lib/llm/providers/openai.rb +2 -2
- data/lib/llm/providers/xai/images.rb +49 -26
- data/lib/llm/providers/xai.rb +2 -2
- data/lib/llm/repl/input.rb +64 -0
- data/lib/llm/repl/status.rb +30 -0
- data/lib/llm/repl/stream.rb +46 -0
- data/lib/llm/repl/transcript.rb +61 -0
- data/lib/llm/repl/window.rb +107 -0
- data/lib/llm/repl.rb +78 -0
- data/lib/llm/response.rb +10 -0
- data/lib/llm/schema/leaf.rb +7 -1
- data/lib/llm/schema/renderer.rb +121 -0
- data/lib/llm/schema.rb +30 -0
- data/lib/llm/sequel/agent.rb +2 -43
- data/lib/llm/sequel/plugin.rb +25 -7
- data/lib/llm/tools/chdir.rb +23 -0
- data/lib/llm/tools/git.rb +41 -0
- data/lib/llm/tools/mkdir.rb +32 -0
- data/lib/llm/tools/pwd.rb +20 -0
- data/lib/llm/tools/read_file.rb +40 -0
- data/lib/llm/tools/rg.rb +46 -0
- data/lib/llm/tools/shell.rb +48 -0
- data/lib/llm/tools/swap_text.rb +25 -0
- data/lib/llm/tools/write_file.rb +24 -0
- data/lib/llm/tools.rb +5 -0
- data/lib/llm/tracer/telemetry.rb +4 -6
- data/lib/llm/tracer.rb +9 -21
- data/lib/llm/transport/execution.rb +16 -1
- data/lib/llm/transport/net_http_adapter.rb +1 -1
- data/lib/llm/uridata.rb +16 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +9 -0
- data/llm.gemspec +5 -18
- data/resources/deepdive.md +829 -263
- metadata +31 -18
- data/lib/llm/tracer/langsmith.rb +0 -144
data/README.md
CHANGED
|
@@ -10,115 +10,77 @@
|
|
|
10
10
|
</a>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
|
-
> A [r.uby.dev](https://r.uby.dev) project.
|
|
13
|
+
> A [r.uby.dev](https://r.uby.dev/llm) project.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Welcome to the canonical llm.rb repository.
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
17
|
+
llm.rb is not a library, framework or toolkit but an advanced runtime
|
|
18
|
+
for building highly capable AI applications on CRuby. By default
|
|
19
|
+
it has zero runtime dependencies although certain functionality –
|
|
20
|
+
such as ActiveRecord support – require optional dependencies
|
|
21
|
+
that are opt-in.
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
integrations only when needed. It supports OpenAI, OpenAI-compatible
|
|
22
|
-
endpoints, Anthropic, Google Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock,
|
|
23
|
-
Ollama, and llama.cpp, with built-in ActiveRecord and Sequel support.
|
|
23
|
+
## Features
|
|
24
24
|
|
|
25
|
-
|
|
25
|
+
The runtime supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
|
|
26
|
+
Gemini, DeepSeek, DeepInfra, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp.
|
|
27
|
+
It has first-class support for streaming, tool calls, MCP
|
|
28
|
+
and A2A, embeddings, vector stores and the RAG pattern.
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
SSH services.
|
|
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.
|
|
31
33
|
|
|
32
|
-
|
|
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.
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
## Install
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
> ssh robert@4.4bsd.dev
|
|
41
|
-
|
|
42
|
-
See [https://4.4bsd.dev/robert](https://4.4bsd.dev/robert) for more information.
|
|
41
|
+
```bash
|
|
42
|
+
gem install llm.rb
|
|
43
|
+
```
|
|
43
44
|
|
|
44
45
|
## Quick start
|
|
45
46
|
|
|
46
|
-
#### LLM::Context
|
|
47
|
-
|
|
48
|
-
The
|
|
49
|
-
[LLM::Context](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
|
|
50
|
-
object is at the heart of the runtime. Almost all other features build
|
|
51
|
-
on top of it. It is a low-level interface to a model, and requires tool
|
|
52
|
-
execution to be managed manually. The
|
|
53
|
-
[LLM::Agent](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
54
|
-
class is almost the same as
|
|
55
|
-
[LLM::Context](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
|
|
56
|
-
but it manages tool execution for you - we'll cover agents next:
|
|
57
|
-
|
|
58
|
-
```ruby
|
|
59
|
-
require "llm"
|
|
60
|
-
|
|
61
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
62
|
-
ctx = LLM::Context.new(llm, stream: $stdout)
|
|
63
|
-
ctx.talk "Hello world"
|
|
64
|
-
```
|
|
65
|
-
|
|
66
47
|
#### LLM::Agent
|
|
67
48
|
|
|
68
|
-
The
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
It provides the same interface, but manages tool execution for you. It
|
|
73
|
-
also has builtin features such as a loop guard that detects repeated
|
|
74
|
-
tool call patterns, and another guard that detects infinite tool call
|
|
75
|
-
loops. Both guards advise the model to change course rather than raise
|
|
76
|
-
an error:
|
|
49
|
+
The [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) class is the default high-level interface,
|
|
50
|
+
and it is recommended for most use-cases. It manages tool execution
|
|
51
|
+
automatically, guards against infinite loops, manages conversation
|
|
52
|
+
state, and much more.
|
|
77
53
|
|
|
78
54
|
```ruby
|
|
79
55
|
require "llm"
|
|
80
56
|
|
|
81
|
-
llm = LLM.
|
|
57
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
82
58
|
agent = LLM::Agent.new(llm, stream: $stdout)
|
|
83
59
|
agent.talk "Hello world"
|
|
84
60
|
```
|
|
85
61
|
|
|
86
|
-
####
|
|
62
|
+
#### LLM::Context
|
|
87
63
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
[`LLM::Function::Return`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Return.html):
|
|
64
|
+
The [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) class is at the heart of the runtime
|
|
65
|
+
and it is what [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) uses under the hood.
|
|
66
|
+
It requires that the tool call loop be managed manually -
|
|
67
|
+
sometimes that can be useful, but usually for advanced use-cases.
|
|
68
|
+
If you're new to llm.rb, try [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) first.
|
|
94
69
|
|
|
95
70
|
```ruby
|
|
96
71
|
require "llm"
|
|
97
72
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
def on_tool_confirmation(fn, strategy)
|
|
103
|
-
path = fn.arguments.path
|
|
104
|
-
if path.start_with?("/tmp/")
|
|
105
|
-
fn.spawn(strategy).wait
|
|
106
|
-
else
|
|
107
|
-
fn.cancel(reason: "Deletion requires approval")
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
|
|
112
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
113
|
-
Agent.new(llm, stream: $stdout).talk("Delete /tmp/example.txt.")
|
|
73
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
74
|
+
ctx = LLM::Context.new(llm, stream: $stdout)
|
|
75
|
+
ctx.talk "Hello world"
|
|
114
76
|
```
|
|
115
77
|
|
|
116
|
-
####
|
|
78
|
+
#### LLM::Tool
|
|
117
79
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
abilities of a model
|
|
80
|
+
Subclasses of [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) are plain Ruby classes with
|
|
81
|
+
an optional set of typed parameters. <br> The model can choose to
|
|
82
|
+
call them on your behalf, and they're one of the most powerful features
|
|
83
|
+
for extending the feature set or abilities of a model.
|
|
122
84
|
|
|
123
85
|
```ruby
|
|
124
86
|
class ReadFile < LLM::Tool
|
|
@@ -128,629 +90,277 @@ class ReadFile < LLM::Tool
|
|
|
128
90
|
required %i[path]
|
|
129
91
|
|
|
130
92
|
def call(path:)
|
|
131
|
-
{
|
|
93
|
+
{contents: File.read(path)}
|
|
132
94
|
end
|
|
133
95
|
end
|
|
134
96
|
```
|
|
135
97
|
|
|
136
|
-
#### MCP
|
|
137
|
-
|
|
138
|
-
The
|
|
139
|
-
[LLM::MCP](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html)
|
|
140
|
-
object lets llm.rb use tools provided by an MCP server. Those tools are
|
|
141
|
-
exposed through the same runtime as local tools, so you can pass them
|
|
142
|
-
to either
|
|
143
|
-
[LLM::Context](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
|
|
144
|
-
or
|
|
145
|
-
[LLM::Agent](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
146
|
-
In this example, the MCP server runs over stdio and
|
|
147
|
-
[LLM::Agent](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
148
|
-
manages the tool loop. For **stdio**, `mcp.session` is the preferred
|
|
149
|
-
pattern because it keeps one MCP session alive across discovery and
|
|
150
|
-
tool calls:
|
|
151
|
-
|
|
152
|
-
```ruby
|
|
153
|
-
require "llm"
|
|
154
|
-
|
|
155
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
156
|
-
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
157
|
-
|
|
158
|
-
mcp.session do
|
|
159
|
-
agent = LLM::Agent.new(llm, stream: $stdout, tools: mcp.tools)
|
|
160
|
-
agent.talk "Use the available tools to inspect the environment."
|
|
161
|
-
end
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
MCP can also be used without `session`. Although it works it is generally
|
|
165
|
-
not recommended for the **stdio** transport because it is inefficient
|
|
166
|
-
to start and stop a fresh MCP process for tool discovery and each tool
|
|
167
|
-
call:
|
|
168
|
-
|
|
169
|
-
```ruby
|
|
170
|
-
require "llm"
|
|
171
|
-
|
|
172
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
173
|
-
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
174
|
-
|
|
175
|
-
agent = LLM::Agent.new(llm, tools: mcp.tools)
|
|
176
|
-
agent.talk("Use the available tools to inspect the environment.")
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
The HTTP transport can be used with or without the `session` method,
|
|
180
|
-
and unlike the stdio transport it can remain efficient without the
|
|
181
|
-
`session` method through a persistent connection pool that is available
|
|
182
|
-
through the
|
|
183
|
-
[LLM::Transport.net_http_persistent](https://r.uby.dev/api-docs/llm.rb/LLM/Transport.html#method-c-net_http_persistent)
|
|
184
|
-
transport:
|
|
185
|
-
|
|
186
|
-
```ruby
|
|
187
|
-
require "llm"
|
|
188
|
-
|
|
189
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
190
|
-
mcp = LLM::MCP.http(
|
|
191
|
-
url: "https://remote-mcp.example.com",
|
|
192
|
-
transport: :net_http_persistent
|
|
193
|
-
)
|
|
194
|
-
|
|
195
|
-
agent = LLM::Agent.new(llm, tools: mcp.tools)
|
|
196
|
-
agent.talk("Use the available tools to inspect the environment.")
|
|
197
|
-
```
|
|
198
|
-
|
|
199
|
-
#### A2A (Agent 2 Agent)
|
|
200
|
-
|
|
201
|
-
The
|
|
202
|
-
[LLM::A2A](https://r.uby.dev/api-docs/llm.rb/LLM/A2A.html)
|
|
203
|
-
object lets llm.rb use skills provided by a remote A2A agent. Those
|
|
204
|
-
skills are exposed through the same runtime as local tools, so you can
|
|
205
|
-
pass them to either
|
|
206
|
-
[LLM::Context](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
|
|
207
|
-
or
|
|
208
|
-
[LLM::Agent](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
209
|
-
|
|
210
|
-
Use remote skills as local tools:
|
|
211
|
-
|
|
212
|
-
```ruby
|
|
213
|
-
require "llm"
|
|
214
|
-
|
|
215
|
-
a2a = LLM::A2A.rest(
|
|
216
|
-
url: "https://remote-agent.example.com",
|
|
217
|
-
headers: { "Authorization" => "Bearer token" }
|
|
218
|
-
)
|
|
219
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
220
|
-
agent = LLM::Agent.new(llm, tools: a2a.skills)
|
|
221
|
-
agent.talk "Analyze this CSV and summarize the trends."
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
Use persistent HTTP connections:
|
|
225
|
-
|
|
226
|
-
```ruby
|
|
227
|
-
require "llm"
|
|
228
|
-
|
|
229
|
-
a2a = LLM::A2A.rest(
|
|
230
|
-
url: "https://remote-agent.example.com",
|
|
231
|
-
transport: :net_http_persistent
|
|
232
|
-
)
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
For more on direct messaging, task operations, push notification
|
|
236
|
-
configs, and JSON-RPC, see the
|
|
237
|
-
[LLM::A2A API docs](https://r.uby.dev/api-docs/llm.rb/LLM/A2A.html).
|
|
238
|
-
|
|
239
|
-
#### Transports
|
|
240
|
-
|
|
241
|
-
Providers use Ruby's standard library Net::HTTP transport by default.
|
|
242
|
-
You can opt into persistent Net::HTTP connections with `persistent: true`,
|
|
243
|
-
or provide a transport shortcut when you want a different backend.
|
|
244
|
-
`transport: :curb` uses libcurl through the optional `curb` gem.
|
|
245
|
-
|
|
246
|
-
Custom transports can implement the
|
|
247
|
-
[LLM::Transport](https://r.uby.dev/api-docs/llm.rb/LLM/Transport.html)
|
|
248
|
-
interface and receive transport-agnostic
|
|
249
|
-
[LLM::Transport::Request](https://r.uby.dev/api-docs/llm.rb/LLM/Transport/Request.html)
|
|
250
|
-
objects from providers.
|
|
251
|
-
|
|
252
|
-
```ruby
|
|
253
|
-
require "llm"
|
|
254
|
-
|
|
255
|
-
llm = LLM.openai(key: ENV["KEY"], persistent: true)
|
|
256
|
-
llm = LLM.openai(key: ENV["KEY"], transport: :net_http_persistent)
|
|
257
|
-
llm = LLM.openai(key: ENV["KEY"], transport: :curb)
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
#### Skills
|
|
261
|
-
|
|
262
|
-
Skills are reusable instructions loaded from a `SKILL.md` directory. They let
|
|
263
|
-
you package behavior and tool access together, and they plug into the
|
|
264
|
-
same runtime as tools, agents, MCP, and A2A. When a skill runs, llm.rb
|
|
265
|
-
spawns a subagent with the skill instructions, access to only the tools
|
|
266
|
-
listed in the skill, and recent conversation context:
|
|
267
|
-
|
|
268
|
-
```yaml
|
|
269
|
-
---
|
|
270
|
-
name: release
|
|
271
|
-
description: Prepare a release
|
|
272
|
-
tools: ["search-docs", "git"]
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## Task
|
|
276
|
-
|
|
277
|
-
Review the release state, summarize what changed, and prepare the release.
|
|
278
|
-
```
|
|
279
|
-
|
|
280
|
-
```ruby
|
|
281
|
-
require "llm"
|
|
282
|
-
|
|
283
|
-
class ReleaseAgent < LLM::Agent
|
|
284
|
-
model "gpt-5.4-mini"
|
|
285
|
-
skills "./skills/release"
|
|
286
|
-
end
|
|
287
|
-
|
|
288
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
289
|
-
ReleaseAgent.new(llm, stream: $stdout).talk("Prepare the next release.")
|
|
290
|
-
```
|
|
291
|
-
|
|
292
|
-
A skill can also have its sub-agent inherit the parents tools through the
|
|
293
|
-
`inherit` directive. The `inherit` directive has coverage for the "classic"
|
|
294
|
-
tools (a subclass of [LLM::Tool](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)),
|
|
295
|
-
MCP tools, and A2A tools that a parent context or agent has access to:
|
|
296
|
-
|
|
297
|
-
```yaml
|
|
298
|
-
---
|
|
299
|
-
name: release
|
|
300
|
-
description: Prepare a release
|
|
301
|
-
tools: inherit
|
|
302
|
-
---
|
|
303
|
-
```
|
|
304
|
-
|
|
305
98
|
#### LLM::Stream
|
|
306
99
|
|
|
307
|
-
|
|
308
|
-
[LLM::Stream](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
|
|
309
|
-
|
|
310
|
-
can subclass it to handle streamed content in your own application:
|
|
100
|
+
Streams can be simple IO objects or subclasses of
|
|
101
|
+
[`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html) with structured callbacks for content,
|
|
102
|
+
reasoning, tool calls, tool returns, and compaction.
|
|
311
103
|
|
|
312
104
|
```ruby
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
class Stream < LLM::Stream
|
|
105
|
+
class MyStream < LLM::Stream
|
|
316
106
|
def on_content(content)
|
|
317
|
-
|
|
107
|
+
print content
|
|
318
108
|
end
|
|
319
|
-
end
|
|
320
|
-
|
|
321
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
322
|
-
agent = LLM::Agent.new(llm, stream: Stream.new)
|
|
323
|
-
agent.talk "Write a haiku about Ruby."
|
|
324
|
-
```
|
|
325
109
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
The
|
|
329
|
-
[LLM::Stream](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
|
|
330
|
-
object can also resolve tool calls while output is still streaming. In
|
|
331
|
-
`on_tool_call`, you can spawn the tool, push the work onto the stream
|
|
332
|
-
queue, and later drain it with `wait`:
|
|
333
|
-
|
|
334
|
-
```ruby
|
|
335
|
-
require "llm"
|
|
336
|
-
|
|
337
|
-
class Stream < LLM::Stream
|
|
338
|
-
def on_content(content)
|
|
339
|
-
$stdout << content
|
|
340
|
-
end
|
|
341
|
-
|
|
342
|
-
def on_tool_call(tool, error)
|
|
343
|
-
return queue << error if error
|
|
344
|
-
queue << ctx.spawn(tool, :thread)
|
|
110
|
+
def on_reasoning_content(content)
|
|
111
|
+
warn content
|
|
345
112
|
end
|
|
346
113
|
end
|
|
347
114
|
|
|
348
|
-
llm = LLM.
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
ctx.talk(ctx.wait) while ctx.functions?
|
|
115
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
116
|
+
agent = LLM::Agent.new(llm, stream: MyStream.new)
|
|
117
|
+
agent.talk "Explain Ruby fibers."
|
|
352
118
|
```
|
|
353
119
|
|
|
354
|
-
####
|
|
120
|
+
#### LLM::REPL
|
|
355
121
|
|
|
356
|
-
llm.rb
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
`:ractor` or `:fork` for more isolated CPU-bound work:
|
|
122
|
+
The [LLM::Agent#repl](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
|
|
123
|
+
method allows an agent to spawn a read-eval-print loop
|
|
124
|
+
that can be useful during the development and operation
|
|
125
|
+
of agents. It can be used to debug tool calls, confirm an
|
|
126
|
+
agent has done what was expected, or improve the agent
|
|
127
|
+
by asking questions about what it has done up to that point.
|
|
363
128
|
|
|
364
|
-
|
|
365
|
-
require
|
|
366
|
-
|
|
367
|
-
class Agent < LLM::Agent
|
|
368
|
-
model "gpt-5.4-mini"
|
|
369
|
-
tools ReadFile
|
|
370
|
-
concurrency :thread
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
374
|
-
agent = Agent.new(llm, stream: $stdout)
|
|
375
|
-
agent.talk "Read README.md and CHANGELOG.md and compare them."
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
#### Serialization
|
|
379
|
-
|
|
380
|
-
The [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
381
|
-
object can be serialized to JSON, which makes it suitable for storing
|
|
382
|
-
in a file, a database column, or a Redis queue. The built-in
|
|
383
|
-
ActiveRecord and Sequel plugins are built on top of the same underlying
|
|
384
|
-
serialization feature:
|
|
385
|
-
|
|
386
|
-
```ruby
|
|
387
|
-
require "llm"
|
|
388
|
-
|
|
389
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
390
|
-
|
|
391
|
-
# Serialize an agent
|
|
392
|
-
agent1 = LLM::Agent.new(llm)
|
|
393
|
-
agent1.talk "Remember that my favorite language is Ruby"
|
|
394
|
-
string = agent1.to_json
|
|
395
|
-
|
|
396
|
-
# Restore an agent (from JSON)
|
|
397
|
-
agent2 = LLM::Agent.new(llm, stream: $stdout)
|
|
398
|
-
agent2.restore(string:)
|
|
399
|
-
agent2.talk "What is my favorite language?"
|
|
400
|
-
```
|
|
401
|
-
|
|
402
|
-
#### ask
|
|
403
|
-
|
|
404
|
-
[`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
|
|
405
|
-
also provides `ask`, a convenience interface that is compatible with
|
|
406
|
-
RubyLLM's `ask` method. It accepts a prompt, an optional `with:`
|
|
407
|
-
attachment path or paths, an optional `stream:` target, and an optional
|
|
408
|
-
block that chunks are yielded to. It returns an
|
|
409
|
-
[`LLM::Response`](https://r.uby.dev/api-docs/llm.rb/LLM/Response.html),
|
|
410
|
-
so use `.content` when you want the text directly:
|
|
129
|
+
This feature requires that the [curses](https://github.com/ruby/curses)
|
|
130
|
+
library is installed and available to require.
|
|
411
131
|
|
|
412
132
|
```ruby
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
133
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
416
134
|
agent = LLM::Agent.new(llm)
|
|
417
|
-
|
|
418
|
-
puts agent.ask("Hello world").content
|
|
419
|
-
puts agent.ask("Summarize this document.", with: "README.md").content
|
|
420
|
-
agent.ask("Stream this reply.") { $stdout << _1 }
|
|
421
|
-
```
|
|
422
|
-
|
|
423
|
-
## Installation
|
|
424
|
-
|
|
425
|
-
```bash
|
|
426
|
-
gem install llm.rb
|
|
135
|
+
agent.repl
|
|
427
136
|
```
|
|
428
137
|
|
|
429
|
-
|
|
138
|
+
#### LLM::MCP
|
|
430
139
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
[
|
|
436
|
-
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
140
|
+
The Model Context Protocol (MCP) has first-class support
|
|
141
|
+
in llm.rb. The stdio and http transports work out of the
|
|
142
|
+
box. MCP tools are translated into subclasses of
|
|
143
|
+
[`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)
|
|
144
|
+
or [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
437
145
|
|
|
438
146
|
```ruby
|
|
439
147
|
require "llm"
|
|
440
148
|
|
|
441
|
-
llm
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
print "> "
|
|
446
|
-
agent.talk(STDIN.gets || break)
|
|
447
|
-
puts
|
|
448
|
-
end
|
|
149
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
150
|
+
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
151
|
+
agent = LLM::Agent.new(llm, stream: $stdout, tools: mcp.tools)
|
|
152
|
+
agent.talk "Run the tool"
|
|
449
153
|
```
|
|
450
154
|
|
|
451
|
-
####
|
|
452
|
-
|
|
453
|
-
In llm.rb, a prompt can be a string, an [`LLM::Prompt`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html), or an array.
|
|
454
|
-
When you use an array, each element can be plain text or a tagged object such as
|
|
455
|
-
[`agent.image_url(...)`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#image_url-instance_method),
|
|
456
|
-
[`agent.local_file(...)`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#local_file-instance_method),
|
|
457
|
-
or [`agent.remote_file(...)`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#remote_file-instance_method).
|
|
458
|
-
Those tagged objects carry the metadata the provider adapter needs to turn one
|
|
459
|
-
Ruby prompt into the provider-specific multimodal request schema.
|
|
460
|
-
|
|
461
|
-
If the model understands that file type, you can attach a local file directly
|
|
462
|
-
with `agent.ask(..., with: path)` instead of uploading it first through a
|
|
463
|
-
provider Files API. Under the hood, llm.rb tags the path as a
|
|
464
|
-
[`agent.local_file(...)`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#local_file-instance_method)
|
|
465
|
-
object:
|
|
466
|
-
|
|
467
|
-
```ruby
|
|
468
|
-
require "llm"
|
|
469
|
-
|
|
470
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
471
|
-
agent = LLM::Agent.new(llm)
|
|
472
|
-
puts agent.ask("Summarize this document.", with: "README.md").content
|
|
473
|
-
```
|
|
155
|
+
#### LLM::A2A
|
|
474
156
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
[`LLM::
|
|
479
|
-
[`LLM::
|
|
480
|
-
long-lived conversations can summarize older history and expose the lifecycle
|
|
481
|
-
through stream hooks. This approach is inspired by General Intelligence
|
|
482
|
-
Systems. The
|
|
483
|
-
compactor can also use its own `model:` if you want summarization to run on a
|
|
484
|
-
different model from the main conversation. `token_threshold:` accepts either a
|
|
485
|
-
fixed token count or a percentage string like `"90%"`, which resolves
|
|
486
|
-
against the active model context window and triggers compaction once total
|
|
487
|
-
token usage goes over that percentage. See the
|
|
488
|
-
[deepdive (web)](https://r.uby.dev/llm/) or
|
|
489
|
-
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
157
|
+
The Agent 2 Agent (A2A) protocol has first-class support
|
|
158
|
+
in llm.rb. The http and jsonrpc transports work out of the
|
|
159
|
+
box. A2A skills are translated into subclasses of
|
|
160
|
+
[`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)
|
|
161
|
+
or [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
|
|
490
162
|
|
|
491
163
|
```ruby
|
|
492
164
|
require "llm"
|
|
493
165
|
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
def on_compaction_finish(ctx, compactor)
|
|
500
|
-
puts "Compacted to #{ctx.messages.size} messages."
|
|
501
|
-
end
|
|
502
|
-
end
|
|
503
|
-
|
|
504
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
505
|
-
agent = LLM::Agent.new(
|
|
506
|
-
llm,
|
|
507
|
-
stream: Stream.new,
|
|
508
|
-
compactor: {
|
|
509
|
-
token_threshold: "90%",
|
|
510
|
-
retention_window: 8,
|
|
511
|
-
model: "gpt-5.4-mini"
|
|
512
|
-
}
|
|
513
|
-
)
|
|
166
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
167
|
+
a2a = LLM::A2A.rest(url: "https://remote-agent.example.com")
|
|
168
|
+
agent = LLM::Agent.new(llm, stream: $stdout, tools: a2a.skills)
|
|
169
|
+
agent.talk "Run the skill"
|
|
514
170
|
```
|
|
515
171
|
|
|
516
|
-
####
|
|
172
|
+
#### RAG
|
|
517
173
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
[
|
|
174
|
+
Most providers offer an embedding model that can be
|
|
175
|
+
used for semantic search, or similarity search. An
|
|
176
|
+
embedding model can generate embeddings that can then
|
|
177
|
+
be stored in a database that is optimized for storing
|
|
178
|
+
and querying vectors, such as SQLite's [sqlite-vec](https://github.com/asg017/sqlite-vec)
|
|
179
|
+
or PostgreSQL's [pg-vector](https://github.com/pgvector/pgvector).
|
|
523
180
|
|
|
524
|
-
|
|
525
|
-
|
|
181
|
+
llm.rb also includes support for OpenAI's vector store API. It
|
|
182
|
+
provides a vector database as a HTTP service but we won't cover
|
|
183
|
+
that here.
|
|
526
184
|
|
|
527
185
|
```ruby
|
|
528
186
|
require "llm"
|
|
529
187
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
end
|
|
534
|
-
|
|
535
|
-
def on_reasoning_content(content)
|
|
536
|
-
$stderr << content
|
|
537
|
-
end
|
|
538
|
-
end
|
|
188
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
189
|
+
body = "llm.rb is Ruby's capable AI runtime."
|
|
190
|
+
embedding = llm.embed([body]).embeddings.first
|
|
539
191
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
mode: :responses,
|
|
545
|
-
reasoning: { effort: "medium" },
|
|
546
|
-
stream: Stream.new
|
|
192
|
+
Document.create!(
|
|
193
|
+
title: "llm.rb",
|
|
194
|
+
body:,
|
|
195
|
+
embedding:,
|
|
547
196
|
)
|
|
548
|
-
agent.talk("Solve 17 * 19 and show your work.")
|
|
549
197
|
```
|
|
550
198
|
|
|
551
|
-
####
|
|
552
|
-
|
|
553
|
-
Need to cancel a stream? llm.rb has you covered through
|
|
554
|
-
[`LLM::Agent#interrupt!`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#interrupt-21-instance_method).
|
|
555
|
-
<br> See the [deepdive (web)](https://r.uby.dev/llm/)
|
|
556
|
-
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
557
|
-
|
|
558
|
-
```ruby
|
|
559
|
-
require "llm"
|
|
560
|
-
require "io/console"
|
|
561
|
-
|
|
562
|
-
llm = LLM.openai(key: ENV["KEY"])
|
|
563
|
-
agent = LLM::Agent.new(llm, stream: $stdout)
|
|
564
|
-
worker = Thread.new do
|
|
565
|
-
agent.talk("Write a very long essay about network protocols.")
|
|
566
|
-
rescue LLM::Interrupt
|
|
567
|
-
puts "Request was interrupted!"
|
|
568
|
-
end
|
|
569
|
-
|
|
570
|
-
STDIN.getch
|
|
571
|
-
agent.interrupt!
|
|
572
|
-
worker.join
|
|
573
|
-
```
|
|
199
|
+
#### Concurrency
|
|
574
200
|
|
|
575
|
-
|
|
201
|
+
The runtime supports five different concurrency strategies that have
|
|
202
|
+
different attributes. The choice between all of them often depends
|
|
203
|
+
on the requirements of your application.
|
|
576
204
|
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
`
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
injects defaults such as `model:`. <br> See the
|
|
583
|
-
[deepdive (web)](https://r.uby.dev/llm/) or
|
|
584
|
-
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
205
|
+
IO-bound tools are a good fit for the `:task`, `:thread`,
|
|
206
|
+
and `:fiber` strategies while true parallelism can be achieved
|
|
207
|
+
with the `:fork` and `:ractor` strategies. The
|
|
208
|
+
`:fork` strategy also provides a separate process that offers
|
|
209
|
+
isolation from its parent.
|
|
585
210
|
|
|
586
211
|
```ruby
|
|
587
212
|
require "llm"
|
|
588
|
-
require "net/http/persistent"
|
|
589
|
-
require "sequel"
|
|
590
|
-
require "sequel/plugins/llm"
|
|
591
|
-
|
|
592
|
-
class Context < Sequel::Model
|
|
593
|
-
plugin :llm, provider: :set_provider, context: :set_context
|
|
594
|
-
|
|
595
|
-
private
|
|
596
|
-
|
|
597
|
-
def set_provider
|
|
598
|
-
LLM.openai(key: ENV["OPENAI_SECRET"], persistent: true)
|
|
599
|
-
end
|
|
600
|
-
|
|
601
|
-
def set_context
|
|
602
|
-
{ model: "gpt-5.4-mini", mode: :responses, store: false }
|
|
603
|
-
end
|
|
604
|
-
end
|
|
605
213
|
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
214
|
+
llm = LLM.deepseek(key: ENV["KEY"])
|
|
215
|
+
tools = [FetchNews, FetchStocks, FetchFeeds]
|
|
216
|
+
agent = LLM::Agent.new(llm, tools:, concurrency: :fork)
|
|
217
|
+
agent.talk "Run the tools in parallel"
|
|
609
218
|
```
|
|
610
219
|
|
|
611
|
-
####
|
|
220
|
+
#### ORM
|
|
612
221
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
columns, provide them to llm.rb through `provider:` and `context:` instead of
|
|
617
|
-
relying on reserved wrapper columns.
|
|
222
|
+
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)
|
|
223
|
+
can be serialized to JSON and stored in a simple string, both ActiveRecord
|
|
224
|
+
and Sequel support can be implemented within a single column on a single row.
|
|
618
225
|
|
|
619
|
-
|
|
620
|
-
|
|
226
|
+
The runtime includes first-class support for both ActiveRecord *and* Sequel, and
|
|
227
|
+
for both Rack-based applications *and* Rails-based applications. On databases
|
|
228
|
+
where it is supported, such as PostgreSQL, the column can be optimized by using
|
|
229
|
+
the `jsonb` type.
|
|
621
230
|
|
|
622
231
|
```ruby
|
|
623
|
-
require "llm"
|
|
624
232
|
require "active_record"
|
|
625
|
-
require "llm/active_record"
|
|
626
|
-
|
|
627
|
-
class Context < ApplicationRecord
|
|
628
|
-
acts_as_llm provider: :set_provider, context: :set_context
|
|
629
|
-
|
|
630
|
-
private
|
|
631
|
-
|
|
632
|
-
def set_provider
|
|
633
|
-
LLM.openai(key: ENV["OPENAI_SECRET"])
|
|
634
|
-
end
|
|
635
|
-
|
|
636
|
-
def set_context
|
|
637
|
-
{ model: "gpt-5.4-mini", mode: :responses, store: false }
|
|
638
|
-
end
|
|
639
|
-
end
|
|
640
|
-
|
|
641
|
-
ctx = Context.create!
|
|
642
|
-
ctx.talk("Remember that my favorite language is Ruby")
|
|
643
|
-
puts ctx.talk("What is my favorite language?").content
|
|
644
|
-
```
|
|
645
|
-
|
|
646
|
-
```ruby
|
|
647
233
|
require "llm"
|
|
648
|
-
require "active_record"
|
|
649
234
|
require "llm/active_record"
|
|
650
235
|
|
|
651
|
-
class
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
236
|
+
class Agent < ApplicationRecord
|
|
237
|
+
acts_as_agent do |agent|
|
|
238
|
+
agent.model "deepseek-v4-pro"
|
|
239
|
+
agent.instructions "solve the user's query"
|
|
240
|
+
agent.tools [Research, FinalizeResearch, ActOnResearch]
|
|
241
|
+
end
|
|
656
242
|
|
|
657
243
|
private
|
|
658
244
|
|
|
245
|
+
# By convention, this method defines the provider for a model.
|
|
246
|
+
# If necessary, it can be renamed with: provider: :your_method.
|
|
659
247
|
def set_provider
|
|
660
|
-
LLM.
|
|
248
|
+
LLM.deepseek(key: ENV["KEY"])
|
|
661
249
|
end
|
|
662
250
|
|
|
251
|
+
# By convention, this method returns the context options given
|
|
252
|
+
# to LLM::Context or LLM::Agent.
|
|
663
253
|
def set_context
|
|
664
|
-
{
|
|
254
|
+
{}
|
|
665
255
|
end
|
|
666
256
|
end
|
|
667
|
-
```
|
|
668
|
-
|
|
669
|
-
#### ActiveRecord (ORM): acts_as_agent
|
|
670
257
|
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
columns, provide them to llm.rb through your hooks and agent DSL.
|
|
675
|
-
|
|
676
|
-
See the [deepdive (web)](https://r.uby.dev/llm/)
|
|
677
|
-
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
258
|
+
agent = Agent.create!
|
|
259
|
+
agent.talk "perform research"
|
|
260
|
+
```
|
|
678
261
|
|
|
679
|
-
|
|
680
|
-
require "llm"
|
|
681
|
-
require "active_record"
|
|
682
|
-
require "llm/active_record"
|
|
262
|
+
## FAQ
|
|
683
263
|
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
concurrency :thread
|
|
264
|
+
<details>
|
|
265
|
+
<summary>What providers does llm.rb support?</summary>
|
|
266
|
+
<br>
|
|
267
|
+
<p>
|
|
268
|
+
China-based
|
|
690
269
|
|
|
691
|
-
|
|
270
|
+
* DeepSeek
|
|
271
|
+
* zAI
|
|
692
272
|
|
|
693
|
-
|
|
694
|
-
LLM.openai(key: ENV["OPENAI_SECRET"])
|
|
695
|
-
end
|
|
273
|
+
US-based
|
|
696
274
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
275
|
+
* OpenAI
|
|
276
|
+
* Google (Gemini)
|
|
277
|
+
* xAI
|
|
278
|
+
* AWS bedrock
|
|
279
|
+
* DeepInfra
|
|
280
|
+
* Anthropic
|
|
701
281
|
|
|
702
|
-
|
|
703
|
-
puts ticket.talk("How do I rotate my API key?").content
|
|
704
|
-
```
|
|
282
|
+
Openweights
|
|
705
283
|
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
284
|
+
* DeepSeek
|
|
285
|
+
* zAI
|
|
286
|
+
* DeepInfra
|
|
287
|
+
* AWS bedrock
|
|
710
288
|
|
|
711
|
-
|
|
712
|
-
acts_as_agent provider: :set_provider, context: :set_context
|
|
713
|
-
model "gpt-5.4-mini"
|
|
714
|
-
instructions "You are a concise support assistant."
|
|
289
|
+
Host your own
|
|
715
290
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
end
|
|
291
|
+
* Ollama
|
|
292
|
+
* Llamacpp
|
|
293
|
+
</p>
|
|
294
|
+
</details>
|
|
721
295
|
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
296
|
+
<details>
|
|
297
|
+
<summary>I have a limited budget. What should I do?</summary>
|
|
298
|
+
<br>
|
|
299
|
+
<p>
|
|
300
|
+
There a few options. The first option is to host
|
|
301
|
+
your own model, and use the ollama or llamacpp
|
|
302
|
+
providers. This can be diffilcult though because
|
|
303
|
+
a capable model requires hardware that can
|
|
304
|
+
match it. If you have the ability to self-host,
|
|
305
|
+
this would be my first option.
|
|
306
|
+
</p>
|
|
307
|
+
<p>
|
|
308
|
+
The second option is DeepSeek. <br>
|
|
309
|
+
The deepseek-v4-flash model costs pennies to use. <br>
|
|
310
|
+
And llm.rb has been optimized for deepseek. For example,
|
|
311
|
+
DeepSeek does not have image generation capabilities
|
|
312
|
+
but on the llm.rb runtime it does (vector graphics only,
|
|
313
|
+
though).
|
|
314
|
+
</p>
|
|
315
|
+
<p>
|
|
316
|
+
The same is true for structured outputs. DeepSeek does
|
|
317
|
+
not support structured outputs in the same way as OpenAI or
|
|
318
|
+
Google, but the llm.rb runtime makes it appear as
|
|
319
|
+
though it does, through the `json_object` response
|
|
320
|
+
type.
|
|
321
|
+
</p>
|
|
322
|
+
If you're on a budget, DeepSeek is hard to beat.
|
|
323
|
+
</details>
|
|
324
|
+
<details>
|
|
325
|
+
<summary>Can I download llm.rb via a decentralized network?</summary>
|
|
326
|
+
<br>
|
|
327
|
+
You can!
|
|
328
|
+
<br>
|
|
329
|
+
We are on the <a href="https://radicle.network">radicle.network</a>
|
|
330
|
+
<br>
|
|
331
|
+
Every commit that lands on GitHub also lands on Radicle.
|
|
332
|
+
<br>
|
|
333
|
+
Our repository ID is z2PtfQ6dYwyYaW2aGrztG1sMyDmCE.
|
|
334
|
+
<br>
|
|
335
|
+
Browse on <a href="https://radicle.network/nodes/iris.radicle.network/z2PtfQ6dYwyYaW2aGrztG1sMyDmCE">the web</a>.
|
|
336
|
+
</details>
|
|
727
337
|
|
|
728
|
-
|
|
338
|
+
## Resources
|
|
729
339
|
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
340
|
+
If you like what you read so far, check out the [deepdive.md](https://r.uby.dev/llm/deepdive/)
|
|
341
|
+
to learn more. Unfortunately it
|
|
342
|
+
wasn't possible to cover every feature without the README becoming a small book.
|
|
343
|
+
The [r.uby.dev](https://r.uby.dev) homepage also includes more learning material
|
|
344
|
+
and resources.
|
|
736
345
|
|
|
737
|
-
|
|
738
|
-
require "llm"
|
|
739
|
-
require "net/http/persistent"
|
|
346
|
+
## License
|
|
740
347
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
348
|
+
[Business Source License 1.1](./LICENSE)
|
|
349
|
+
<br>
|
|
350
|
+
Commercial production use requires a commercial license.
|
|
351
|
+
<br>
|
|
352
|
+
Each version converts to the [BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
|
|
353
|
+
four years after its first public release.
|
|
354
|
+
<br>
|
|
355
|
+
Contact [robert@r.uby.dev](mailto:robert@r.uby.dev) for a commercial license.
|
|
747
356
|
|
|
748
|
-
|
|
749
|
-
agent.talk("Pull information about my GitHub account.")
|
|
750
|
-
```
|
|
357
|
+
### Waivers
|
|
751
358
|
|
|
752
|
-
|
|
359
|
+
Waivers are automatically granted for: <br>
|
|
753
360
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
361
|
+
* Personal use
|
|
362
|
+
* Students
|
|
363
|
+
* Teachers
|
|
364
|
+
* Evaluation, development, and testing
|
|
365
|
+
* Non-profits and charities
|
|
366
|
+
* Companies with less than or equal to 50 employees
|