llm.rb 0.4.2 → 0.6.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/README.md +173 -115
- data/lib/json/schema/array.rb +5 -0
- data/lib/json/schema/boolean.rb +4 -0
- data/lib/json/schema/integer.rb +23 -1
- data/lib/json/schema/leaf.rb +11 -0
- data/lib/json/schema/null.rb +4 -0
- data/lib/json/schema/number.rb +23 -1
- data/lib/json/schema/object.rb +6 -2
- data/lib/json/schema/string.rb +26 -1
- data/lib/json/schema/version.rb +2 -0
- data/lib/json/schema.rb +10 -10
- data/lib/llm/buffer.rb +31 -12
- data/lib/llm/chat.rb +56 -29
- data/lib/llm/core_ext/ostruct.rb +14 -8
- data/lib/llm/file.rb +6 -1
- data/lib/llm/function.rb +86 -0
- data/lib/llm/message.rb +54 -2
- data/lib/llm/provider.rb +32 -46
- data/lib/llm/providers/anthropic/format/completion_format.rb +73 -0
- data/lib/llm/providers/anthropic/format.rb +8 -33
- data/lib/llm/providers/anthropic/response_parser/completion_parser.rb +51 -0
- data/lib/llm/providers/anthropic/response_parser.rb +1 -9
- data/lib/llm/providers/anthropic.rb +14 -14
- data/lib/llm/providers/gemini/audio.rb +9 -9
- data/lib/llm/providers/gemini/files.rb +11 -10
- data/lib/llm/providers/gemini/format/completion_format.rb +54 -0
- data/lib/llm/providers/gemini/format.rb +20 -27
- data/lib/llm/providers/gemini/images.rb +12 -7
- data/lib/llm/providers/gemini/models.rb +3 -3
- data/lib/llm/providers/gemini/response_parser/completion_parser.rb +46 -0
- data/lib/llm/providers/gemini/response_parser.rb +13 -20
- data/lib/llm/providers/gemini.rb +10 -20
- data/lib/llm/providers/ollama/format/completion_format.rb +72 -0
- data/lib/llm/providers/ollama/format.rb +11 -30
- data/lib/llm/providers/ollama/response_parser/completion_parser.rb +42 -0
- data/lib/llm/providers/ollama/response_parser.rb +8 -11
- data/lib/llm/providers/ollama.rb +9 -17
- data/lib/llm/providers/openai/audio.rb +6 -6
- data/lib/llm/providers/openai/files.rb +3 -3
- data/lib/llm/providers/openai/format/completion_format.rb +83 -0
- data/lib/llm/providers/openai/format/respond_format.rb +69 -0
- data/lib/llm/providers/openai/format.rb +27 -58
- data/lib/llm/providers/openai/images.rb +4 -2
- data/lib/llm/providers/openai/response_parser/completion_parser.rb +55 -0
- data/lib/llm/providers/openai/response_parser/respond_parser.rb +56 -0
- data/lib/llm/providers/openai/response_parser.rb +8 -44
- data/lib/llm/providers/openai/responses.rb +13 -14
- data/lib/llm/providers/openai.rb +11 -23
- data/lib/llm/providers/voyageai.rb +4 -4
- data/lib/llm/response/{output.rb → respond.rb} +2 -2
- data/lib/llm/response.rb +1 -1
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +38 -10
- data/llm.gemspec +1 -0
- metadata +28 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3452ff48dff867c48be888eb5ae2fff97624b8e51029cd13a26844d67a7824cf
|
4
|
+
data.tar.gz: 51a65baeff8b026c6ea9fdda2063d14a7961dd902f94cccd34c7591f606a586f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a548f97a9019529146f0e6f7414239aaee5b0b6695c924f28f602de7a1ef2f390e1b0053545dab2ee6725f7dabd7a2c83e3b9181027a492630f12dda77870637
|
7
|
+
data.tar.gz: f71fbd16d3fb22a0ad37d59e93fc7b9e6093fc2b77ebf0e6b7eeca69cb227fd84867ab02c594c4b608ea9017eb6cc40d3fa5c2ff76ffb09fbc1a1b573823a84b
|
data/README.md
CHANGED
@@ -1,11 +1,31 @@
|
|
1
1
|
## About
|
2
2
|
|
3
|
-
llm.rb is a
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
llm.rb is a zero-dependency Ruby toolkit for Large Language Models like
|
4
|
+
OpenAI, Gemini, Anthropic, and more. It’s fast, clean, and composable –
|
5
|
+
with full support for chat, tool calling, audio, images, files, and
|
6
|
+
JSON Schema generation.
|
7
|
+
|
8
|
+
## Features
|
9
|
+
|
10
|
+
#### General
|
11
|
+
- ✅ Unified interface for OpenAI, Gemini, Anthropic, Ollama, and more
|
12
|
+
- 📦 Zero dependencies outside Ruby's standard library
|
13
|
+
- 🔌 Model introspection and selection
|
14
|
+
- 🚀 Optimized for performance and low memory usage
|
15
|
+
|
16
|
+
#### Chat, Agents
|
17
|
+
- 🧠 Stateless and stateful chat via completions and responses API
|
18
|
+
- 🤖 Tool calling and function execution
|
19
|
+
- 🗂️ JSON Schema support for structured, validated responses
|
20
|
+
|
21
|
+
#### Media
|
22
|
+
- 🗣️ Text-to-speech, transcription, and translation
|
23
|
+
- 🖼️ Image generation, editing, and variation support
|
24
|
+
- 📎 File uploads and prompt-aware file interaction
|
25
|
+
- 💡 Multimodal prompts (text, URLs, files)
|
26
|
+
|
27
|
+
#### Embeddings
|
28
|
+
- 🧮 Text embeddings and vector support
|
9
29
|
|
10
30
|
## Examples
|
11
31
|
|
@@ -22,11 +42,11 @@ using an API key (if required) and an optional set of configuration options via
|
|
22
42
|
#!/usr/bin/env ruby
|
23
43
|
require "llm"
|
24
44
|
|
25
|
-
llm = LLM.openai("yourapikey")
|
26
|
-
llm = LLM.gemini("yourapikey")
|
27
|
-
llm = LLM.anthropic("yourapikey")
|
28
|
-
llm = LLM.ollama(nil)
|
29
|
-
llm = LLM.voyageai("yourapikey")
|
45
|
+
llm = LLM.openai(key: "yourapikey")
|
46
|
+
llm = LLM.gemini(key: "yourapikey")
|
47
|
+
llm = LLM.anthropic(key: "yourapikey")
|
48
|
+
llm = LLM.ollama(key: nil)
|
49
|
+
llm = LLM.voyageai(key: "yourapikey")
|
30
50
|
```
|
31
51
|
|
32
52
|
### Conversations
|
@@ -46,12 +66,12 @@ all LLM providers support:
|
|
46
66
|
#!/usr/bin/env ruby
|
47
67
|
require "llm"
|
48
68
|
|
49
|
-
llm = LLM.openai(ENV["KEY"])
|
69
|
+
llm = LLM.openai(key: ENV["KEY"])
|
50
70
|
bot = LLM::Chat.new(llm).lazy
|
51
|
-
bot.chat File.read("./share/llm/prompts/system.txt"), :system
|
52
|
-
bot.chat "Tell me the answer to 5 + 15", :user
|
53
|
-
bot.chat "Tell me the answer to (5 + 15) * 2", :user
|
54
|
-
bot.chat "Tell me the answer to ((5 + 15) * 2) / 10", :user
|
71
|
+
bot.chat File.read("./share/llm/prompts/system.txt"), role: :system
|
72
|
+
bot.chat "Tell me the answer to 5 + 15", role: :user
|
73
|
+
bot.chat "Tell me the answer to (5 + 15) * 2", role: :user
|
74
|
+
bot.chat "Tell me the answer to ((5 + 15) * 2) / 10", role: :user
|
55
75
|
bot.messages.each { print "[#{_1.role}] ", _1.content, "\n" }
|
56
76
|
|
57
77
|
##
|
@@ -86,12 +106,12 @@ for the OpenAI provider:
|
|
86
106
|
#!/usr/bin/env ruby
|
87
107
|
require "llm"
|
88
108
|
|
89
|
-
llm = LLM.openai(ENV["KEY"])
|
109
|
+
llm = LLM.openai(key: ENV["KEY"])
|
90
110
|
bot = LLM::Chat.new(llm).lazy
|
91
|
-
bot.respond File.read("./share/llm/prompts/system.txt"), :developer
|
92
|
-
bot.respond "Tell me the answer to 5 + 15", :user
|
93
|
-
bot.respond "Tell me the answer to (5 + 15) * 2", :user
|
94
|
-
bot.respond "Tell me the answer to ((5 + 15) * 2) / 10", :user
|
111
|
+
bot.respond File.read("./share/llm/prompts/system.txt"), role: :developer
|
112
|
+
bot.respond "Tell me the answer to 5 + 15", role: :user
|
113
|
+
bot.respond "Tell me the answer to (5 + 15) * 2", role: :user
|
114
|
+
bot.respond "Tell me the answer to ((5 + 15) * 2) / 10", role: :user
|
95
115
|
bot.messages.each { print "[#{_1.role}] ", _1.content, "\n" }
|
96
116
|
|
97
117
|
##
|
@@ -132,24 +152,74 @@ The interface is designed so you could drop in any other library in its place:
|
|
132
152
|
#!/usr/bin/env ruby
|
133
153
|
require "llm"
|
134
154
|
|
135
|
-
llm = LLM.openai(ENV["KEY"])
|
155
|
+
llm = LLM.openai(key: ENV["KEY"])
|
136
156
|
schema = llm.schema.object({os: llm.schema.string.enum("OpenBSD", "FreeBSD", "NetBSD")})
|
137
157
|
bot = LLM::Chat.new(llm, schema:)
|
138
|
-
bot.chat "You secretly love NetBSD", :system
|
139
|
-
bot.chat "What operating system is the best?", :user
|
158
|
+
bot.chat "You secretly love NetBSD", role: :system
|
159
|
+
bot.chat "What operating system is the best?", role: :user
|
140
160
|
bot.messages.find(&:assistant?).content! # => {os: "NetBSD"}
|
141
161
|
|
142
162
|
schema = llm.schema.object({answer: llm.schema.integer.required})
|
143
163
|
bot = LLM::Chat.new(llm, schema:)
|
144
|
-
bot.chat "Tell me the answer to ((5 + 5) / 2)", :user
|
164
|
+
bot.chat "Tell me the answer to ((5 + 5) / 2)", role: :user
|
145
165
|
bot.messages.find(&:assistant?).content! # => {answer: 5}
|
146
166
|
|
147
167
|
schema = llm.schema.object({probability: llm.schema.number.required})
|
148
168
|
bot = LLM::Chat.new(llm, schema:)
|
149
|
-
bot.chat "Does the earth orbit the sun?", :user
|
169
|
+
bot.chat "Does the earth orbit the sun?", role: :user
|
150
170
|
bot.messages.find(&:assistant?).content! # => {probability: 1}
|
151
171
|
```
|
152
172
|
|
173
|
+
### Tools
|
174
|
+
|
175
|
+
#### Functions
|
176
|
+
|
177
|
+
The OpenAI, Anthropic, Gemini and Ollama providers support a powerful feature known as
|
178
|
+
tool calling, and although it is a little complex to understand at first,
|
179
|
+
it can be powerful for building agents. The following example demonstrates how we
|
180
|
+
can define a local function (which happens to be a tool), and OpenAI can
|
181
|
+
then detect when we should call the function.
|
182
|
+
|
183
|
+
The
|
184
|
+
[LLM::Chat#functions](https://0x1eef.github.io/x/llm.rb/LLM/Chat.html#functions-instance_method)
|
185
|
+
method returns an array of functions that can be called after sending a message and
|
186
|
+
it will only be populated if the LLM detects a function should be called. Each function
|
187
|
+
corresponds to an element in the "tools" array. The array is emptied after a function call,
|
188
|
+
and potentially repopulated on the next message.
|
189
|
+
|
190
|
+
The following example defines an agent that can run system commands based on natural language,
|
191
|
+
and it is only intended to be a fun demo of tool calling - it is not recommended to run
|
192
|
+
arbitrary commands from a LLM without sanitizing the input first :) Without further ado:
|
193
|
+
|
194
|
+
```ruby
|
195
|
+
#!/usr/bin/env ruby
|
196
|
+
require "llm"
|
197
|
+
|
198
|
+
llm = LLM.openai(key: ENV["KEY"])
|
199
|
+
tool = LLM.function(:system) do |fn|
|
200
|
+
fn.description "Run a shell command"
|
201
|
+
fn.params do |schema|
|
202
|
+
schema.object(command: schema.string.required)
|
203
|
+
end
|
204
|
+
fn.define do |params|
|
205
|
+
system(params.command)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
bot = LLM::Chat.new(llm, tools: [tool]).lazy
|
210
|
+
bot.chat "Your task is to run shell commands via a tool.", role: :system
|
211
|
+
|
212
|
+
bot.chat "What is the current date?", role: :user
|
213
|
+
bot.chat bot.functions.map(&:call) # report return value to the LLM
|
214
|
+
|
215
|
+
bot.chat "What operating system am I running? (short version please!)", role: :user
|
216
|
+
bot.chat bot.functions.map(&:call) # report return value to the LLM
|
217
|
+
|
218
|
+
##
|
219
|
+
# Thu May 1 10:01:02 UTC 2025
|
220
|
+
# FreeBSD
|
221
|
+
```
|
222
|
+
|
153
223
|
### Audio
|
154
224
|
|
155
225
|
#### Speech
|
@@ -159,14 +229,13 @@ can create speech from text, transcribe audio to text, or translate
|
|
159
229
|
audio to text (usually English). The following example uses the OpenAI provider
|
160
230
|
to create an audio file from a text prompt. The audio is then moved to
|
161
231
|
`${HOME}/hello.mp3` as the final step. As always, consult the provider's
|
162
|
-
documentation
|
163
|
-
for more information on how to use the audio generation API:
|
232
|
+
documentation for more information on how to use the audio generation API:
|
164
233
|
|
165
234
|
```ruby
|
166
235
|
#!/usr/bin/env ruby
|
167
236
|
require "llm"
|
168
237
|
|
169
|
-
llm = LLM.openai(ENV["KEY"])
|
238
|
+
llm = LLM.openai(key: ENV["KEY"])
|
170
239
|
res = llm.audio.create_speech(input: "Hello world")
|
171
240
|
IO.copy_stream res.audio, File.join(Dir.home, "hello.mp3")
|
172
241
|
```
|
@@ -177,24 +246,15 @@ The following example transcribes an audio file to text. The audio file
|
|
177
246
|
(`${HOME}/hello.mp3`) was theoretically created in the previous example,
|
178
247
|
and the result is printed to the console. The example uses the OpenAI
|
179
248
|
provider to transcribe the audio file. As always, consult the provider's
|
180
|
-
documentation
|
181
|
-
[OpenAI docs](https://platform.openai.com/docs/api-reference/audio/createTranscription),
|
182
|
-
[Gemini docs](https://ai.google.dev/gemini-api/docs/audio))
|
183
|
-
for more information on how to use the audio transcription API.
|
184
|
-
|
185
|
-
Please also see provider-specific documentation for more provider-specific
|
186
|
-
examples and documentation
|
187
|
-
(eg
|
188
|
-
[LLM::Gemini::Audio](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Audio.html),
|
189
|
-
[LLM::OpenAI::Audio](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Audio.html)):
|
249
|
+
documentation for more information on how to use the audio transcription API:
|
190
250
|
|
191
251
|
```ruby
|
192
252
|
#!/usr/bin/env ruby
|
193
253
|
require "llm"
|
194
254
|
|
195
|
-
llm = LLM.openai(ENV["KEY"])
|
255
|
+
llm = LLM.openai(key: ENV["KEY"])
|
196
256
|
res = llm.audio.create_transcription(
|
197
|
-
file:
|
257
|
+
file: File.join(Dir.home, "hello.mp3")
|
198
258
|
)
|
199
259
|
print res.text, "\n" # => "Hello world."
|
200
260
|
```
|
@@ -205,25 +265,16 @@ The following example translates an audio file to text. In this example
|
|
205
265
|
the audio file (`${HOME}/bomdia.mp3`) is theoretically in Portuguese,
|
206
266
|
and it is translated to English. The example uses the OpenAI provider,
|
207
267
|
and at the time of writing, it can only translate to English. As always,
|
208
|
-
consult the provider's documentation
|
209
|
-
|
210
|
-
[Gemini docs](https://ai.google.dev/gemini-api/docs/audio))
|
211
|
-
for more information on how to use the audio translation API.
|
212
|
-
|
213
|
-
Please also see provider-specific documentation for more provider-specific
|
214
|
-
examples and documentation
|
215
|
-
(eg
|
216
|
-
[LLM::Gemini::Audio](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Audio.html),
|
217
|
-
[LLM::OpenAI::Audio](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Audio.html)):
|
218
|
-
|
268
|
+
consult the provider's documentation for more information on how to use
|
269
|
+
the audio translation API:
|
219
270
|
|
220
271
|
```ruby
|
221
272
|
#!/usr/bin/env ruby
|
222
273
|
require "llm"
|
223
274
|
|
224
|
-
llm = LLM.openai(ENV["KEY"])
|
275
|
+
llm = LLM.openai(key: ENV["KEY"])
|
225
276
|
res = llm.audio.create_translation(
|
226
|
-
file:
|
277
|
+
file: File.join(Dir.home, "bomdia.mp3")
|
227
278
|
)
|
228
279
|
print res.text, "\n" # => "Good morning."
|
229
280
|
```
|
@@ -236,13 +287,7 @@ Some but not all LLM providers implement image generation capabilities that
|
|
236
287
|
can create new images from a prompt, or edit an existing image with a
|
237
288
|
prompt. The following example uses the OpenAI provider to create an
|
238
289
|
image of a dog on a rocket to the moon. The image is then moved to
|
239
|
-
`${HOME}/dogonrocket.png` as the final step
|
240
|
-
|
241
|
-
Please also see provider-specific documentation for more provider-specific
|
242
|
-
examples and documentation
|
243
|
-
(eg
|
244
|
-
[LLM::Gemini::Images](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Images.html),
|
245
|
-
[LLM::OpenAI::Images](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Images.html)):
|
290
|
+
`${HOME}/dogonrocket.png` as the final step:
|
246
291
|
|
247
292
|
```ruby
|
248
293
|
#!/usr/bin/env ruby
|
@@ -250,7 +295,7 @@ require "llm"
|
|
250
295
|
require "open-uri"
|
251
296
|
require "fileutils"
|
252
297
|
|
253
|
-
llm = LLM.openai(ENV["KEY"])
|
298
|
+
llm = LLM.openai(key: ENV["KEY"])
|
254
299
|
res = llm.images.create(prompt: "a dog on a rocket to the moon")
|
255
300
|
res.urls.each do |url|
|
256
301
|
FileUtils.mv OpenURI.open_uri(url).path,
|
@@ -266,17 +311,8 @@ now wearing a hat. The image is then moved to `${HOME}/catwithhat.png` as
|
|
266
311
|
the final step.
|
267
312
|
|
268
313
|
Results and quality may vary, consider prompt adjustments if the results
|
269
|
-
are not
|
270
|
-
|
271
|
-
[OpenAI docs](https://platform.openai.com/docs/api-reference/images/createEdit),
|
272
|
-
[Gemini docs](https://ai.google.dev/gemini-api/docs/image-generation))
|
273
|
-
for more information on how to use the image editing API.
|
274
|
-
|
275
|
-
Please also see provider-specific documentation for more provider-specific
|
276
|
-
examples and documentation
|
277
|
-
(eg
|
278
|
-
[LLM::Gemini::Images](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Images.html),
|
279
|
-
[LLM::OpenAI::Images](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Images.html)):
|
314
|
+
are not as expected, and consult the provider's documentation
|
315
|
+
for more information on how to use the image editing API:
|
280
316
|
|
281
317
|
```ruby
|
282
318
|
#!/usr/bin/env ruby
|
@@ -284,9 +320,9 @@ require "llm"
|
|
284
320
|
require "open-uri"
|
285
321
|
require "fileutils"
|
286
322
|
|
287
|
-
llm = LLM.openai(ENV["KEY"])
|
323
|
+
llm = LLM.openai(key: ENV["KEY"])
|
288
324
|
res = llm.images.edit(
|
289
|
-
image:
|
325
|
+
image: "/images/cat.png",
|
290
326
|
prompt: "a cat with a hat",
|
291
327
|
)
|
292
328
|
res.urls.each do |url|
|
@@ -300,9 +336,8 @@ end
|
|
300
336
|
The following example is focused on creating variations of a local image.
|
301
337
|
The image (`/images/cat.png`) is returned to us with five different variations.
|
302
338
|
The images are then moved to `${HOME}/catvariation0.png`, `${HOME}/catvariation1.png`
|
303
|
-
and so on as the final step. Consult the provider's documentation
|
304
|
-
|
305
|
-
for more information on how to use the image variations API:
|
339
|
+
and so on as the final step. Consult the provider's documentation for more information
|
340
|
+
on how to use the image variations API:
|
306
341
|
|
307
342
|
```ruby
|
308
343
|
#!/usr/bin/env ruby
|
@@ -310,9 +345,9 @@ require "llm"
|
|
310
345
|
require "open-uri"
|
311
346
|
require "fileutils"
|
312
347
|
|
313
|
-
llm = LLM.openai(ENV["KEY"])
|
348
|
+
llm = LLM.openai(key: ENV["KEY"])
|
314
349
|
res = llm.images.create_variation(
|
315
|
-
image:
|
350
|
+
image: "/images/cat.png",
|
316
351
|
n: 5
|
317
352
|
)
|
318
353
|
res.urls.each.with_index do |url, index|
|
@@ -331,21 +366,16 @@ for this feature. The following example uses the OpenAI provider to describe
|
|
331
366
|
the contents of a PDF file after it has been uploaded. The file (an instance
|
332
367
|
of [LLM::Response::File](https://0x1eef.github.io/x/llm.rb/LLM/Response/File.html))
|
333
368
|
is passed directly to the chat method, and generally any object a prompt supports
|
334
|
-
can be given to the chat method
|
369
|
+
can be given to the chat method:
|
335
370
|
|
336
|
-
Please also see provider-specific documentation for more provider-specific
|
337
|
-
examples and documentation
|
338
|
-
(eg
|
339
|
-
[LLM::Gemini::Files](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Files.html),
|
340
|
-
[LLM::OpenAI::Files](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Files.html)):
|
341
371
|
|
342
372
|
```ruby
|
343
373
|
#!/usr/bin/env ruby
|
344
374
|
require "llm"
|
345
375
|
|
346
|
-
llm = LLM.openai(ENV["KEY"])
|
376
|
+
llm = LLM.openai(key: ENV["KEY"])
|
347
377
|
bot = LLM::Chat.new(llm).lazy
|
348
|
-
file = llm.files.create(file:
|
378
|
+
file = llm.files.create(file: "/documents/openbsd_is_awesome.pdf")
|
349
379
|
bot.chat(file)
|
350
380
|
bot.chat("What is this file about?")
|
351
381
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
@@ -368,32 +398,26 @@ objects to describe links, `LLM::File` | `LLM::Response::File` objects
|
|
368
398
|
to describe files, `String` objects to describe text blobs, or an array
|
369
399
|
of the aforementioned objects to describe multiple objects in a single
|
370
400
|
prompt. Each object is a first class citizen that can be passed directly
|
371
|
-
to a prompt
|
372
|
-
|
373
|
-
For more depth and examples on how to use the multimodal API, please see
|
374
|
-
the [provider-specific documentation](https://0x1eef.github.io/x/llm.rb/)
|
375
|
-
for more provider-specific examples:
|
401
|
+
to a prompt:
|
376
402
|
|
377
403
|
```ruby
|
378
404
|
#!/usr/bin/env ruby
|
379
405
|
require "llm"
|
380
406
|
|
381
|
-
llm = LLM.openai(ENV["KEY"])
|
407
|
+
llm = LLM.openai(key: ENV["KEY"])
|
382
408
|
bot = LLM::Chat.new(llm).lazy
|
383
409
|
|
384
|
-
bot.chat URI("https://example.com/path/to/image.png")
|
385
|
-
bot.chat "Describe the above image"
|
410
|
+
bot.chat [URI("https://example.com/path/to/image.png"), "Describe the image in the link"]
|
386
411
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
387
412
|
|
388
|
-
file =
|
389
|
-
bot.chat file
|
390
|
-
bot.chat "What is this file about?"
|
413
|
+
file = llm.files.create(file: "/documents/openbsd_is_awesome.pdf")
|
414
|
+
bot.chat [file, "What is this file about?"]
|
391
415
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
392
416
|
|
393
|
-
bot.chat [LLM
|
417
|
+
bot.chat [LLM.File("/images/puffy.png"), "What is this image about?"]
|
394
418
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
395
419
|
|
396
|
-
bot.chat [LLM
|
420
|
+
bot.chat [LLM.File("/images/beastie.png"), "What is this image about?"]
|
397
421
|
bot.messages.select(&:assistant?).each { print "[#{_1.role}] ", _1.content, "\n" }
|
398
422
|
```
|
399
423
|
|
@@ -415,7 +439,7 @@ which will go on to generate a response:
|
|
415
439
|
#!/usr/bin/env ruby
|
416
440
|
require "llm"
|
417
441
|
|
418
|
-
llm = LLM.openai(ENV["KEY"])
|
442
|
+
llm = LLM.openai(key: ENV["KEY"])
|
419
443
|
res = llm.embed(["programming is fun", "ruby is a programming language", "sushi is art"])
|
420
444
|
print res.class, "\n"
|
421
445
|
print res.embeddings.size, "\n"
|
@@ -446,7 +470,7 @@ require "llm"
|
|
446
470
|
|
447
471
|
##
|
448
472
|
# List all models
|
449
|
-
llm = LLM.openai(ENV["KEY"])
|
473
|
+
llm = LLM.openai(key: ENV["KEY"])
|
450
474
|
llm.models.all.each do |model|
|
451
475
|
print "model: ", model.id, "\n"
|
452
476
|
end
|
@@ -477,7 +501,7 @@ demonstrates how that might look like in practice:
|
|
477
501
|
#!/usr/bin/env ruby
|
478
502
|
require "llm"
|
479
503
|
|
480
|
-
llm = LLM.gemini(ENV["KEY"])
|
504
|
+
llm = LLM.gemini(key: ENV["KEY"])
|
481
505
|
fork do
|
482
506
|
%w[dog cat sheep goat capybara].each do |animal|
|
483
507
|
res = llm.images.create(prompt: "a #{animal} on a rocket to the moon")
|
@@ -495,25 +519,59 @@ over or doesn't cover at all. The API reference is available at
|
|
495
519
|
[0x1eef.github.io/x/llm.rb](https://0x1eef.github.io/x/llm.rb).
|
496
520
|
|
497
521
|
|
522
|
+
### See also
|
523
|
+
|
524
|
+
#### Gemini
|
525
|
+
|
526
|
+
* [LLM::Gemini](https://0x1eef.github.io/x/llm.rb/LLM/Gemini.html)
|
527
|
+
* [LLM::Gemini::Images](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Images.html)
|
528
|
+
* [LLM::Gemini::Audio](https://0x1eef.github.io/x/llm.rb/LLM/Gemini/Audio.html)
|
529
|
+
|
530
|
+
#### OpenAI
|
531
|
+
|
532
|
+
* [LLM::OpenAI](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI.html)
|
533
|
+
* [LLM::OpenAI::Images](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Images.html)
|
534
|
+
* [LLM::OpenAI::Audio](https://0x1eef.github.io/x/llm.rb/LLM/OpenAI/Audio.html)
|
535
|
+
|
536
|
+
#### Anthropic
|
537
|
+
* [LLM::Anthropic](https://0x1eef.github.io/x/llm.rb/LLM/Anthropic.html)
|
538
|
+
|
539
|
+
#### Ollama
|
540
|
+
* [LLM::Ollama](https://0x1eef.github.io/x/llm.rb/LLM/Ollama.html)
|
541
|
+
|
498
542
|
## Install
|
499
543
|
|
500
544
|
llm.rb can be installed via rubygems.org:
|
501
545
|
|
502
546
|
gem install llm.rb
|
503
547
|
|
548
|
+
## See also
|
549
|
+
|
550
|
+
**[llmrb/llm-shell](https://github.com/llmrb/llm-shell)**
|
551
|
+
|
552
|
+
An extensible, developer-oriented command line utility that is powered by
|
553
|
+
llm.rb and serves as a demonstration of the library's capabilities. The
|
554
|
+
[demo](https://github.com/llmrb/llm-shell#demos) section has a number of GIF
|
555
|
+
previews might be especially interesting!
|
556
|
+
|
557
|
+
|
504
558
|
## Philosophy
|
505
559
|
|
506
|
-
llm.rb
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
560
|
+
llm.rb provides a clean, dependency-free interface to Large Language Models,
|
561
|
+
treating Ruby itself — not Rails or any specific framework — as the primary platform.
|
562
|
+
It avoids hidden magic, complex metaprogramming, and heavy DSLs. It is intentionally
|
563
|
+
simple and won't compromise on being a simple library, even if that means saying no to
|
564
|
+
certain features.
|
565
|
+
|
566
|
+
Instead, it embraces a general-purpose, object-oriented design that prioritizes
|
567
|
+
explicitness, composability, and clarity. Code should be easy to follow, test, and adapt.
|
568
|
+
For that reason we favor small, cooperating objects over deeply nested blocks — a pattern
|
569
|
+
that often emerges in DSL-heavy libraries.
|
570
|
+
|
571
|
+
Each part of llm.rb is designed to be conscious of memory, ready for production, and free
|
572
|
+
from global state or non-standard dependencies. While inspired by ideas from other ecosystems
|
573
|
+
(especially Python) it is not a port of any other library — it is a Ruby library written
|
574
|
+
by Rubyists who value borrowing good ideas from other languages and ecosystems.
|
517
575
|
|
518
576
|
## License
|
519
577
|
|
data/lib/json/schema/array.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Array JSON::Schema::Array} class represents an
|
6
|
+
# array value in a JSON schema. It is a subclass of
|
7
|
+
# {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
|
8
|
+
# can act as constraints.
|
4
9
|
class Array < Leaf
|
5
10
|
def initialize(*items)
|
6
11
|
@items = items
|
data/lib/json/schema/boolean.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Boolean JSON::Schema::Boolean} class represents a
|
6
|
+
# boolean value in a JSON schema. It is a subclass of
|
7
|
+
# {JSON::Schema::Leaf JSON::Schema::Leaf}.
|
4
8
|
class Booelean < Leaf
|
5
9
|
def to_h
|
6
10
|
super.merge!({type: "boolean"})
|
data/lib/json/schema/integer.rb
CHANGED
@@ -1,20 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Integer JSON::Schema::Integer} class represents a
|
6
|
+
# whole number value in a JSON schema. It is a subclass of
|
7
|
+
# {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
|
8
|
+
# can act as constraints.
|
4
9
|
class Integer < Leaf
|
10
|
+
##
|
11
|
+
# Constrain the number to a minimum value
|
12
|
+
# @param [Integer] i The minimum value
|
13
|
+
# @return [JSON::Schema::Number] Returns self
|
5
14
|
def min(i)
|
6
15
|
tap { @minimum = i }
|
7
16
|
end
|
8
17
|
|
18
|
+
##
|
19
|
+
# Constrain the number to a maximum value
|
20
|
+
# @param [Integer] i The maximum value
|
21
|
+
# @return [JSON::Schema::Number] Returns self
|
9
22
|
def max(i)
|
10
23
|
tap { @maximum = i }
|
11
24
|
end
|
12
25
|
|
26
|
+
##
|
27
|
+
# Constrain the number to a multiple of a given value
|
28
|
+
# @param [Integer] i The multiple
|
29
|
+
# @return [JSON::Schema::Number] Returns self
|
30
|
+
def multiple_of(i)
|
31
|
+
tap { @multiple_of = i }
|
32
|
+
end
|
33
|
+
|
13
34
|
def to_h
|
14
35
|
super.merge!({
|
15
36
|
type: "integer",
|
16
37
|
minimum: @minimum,
|
17
|
-
maximum: @maximum
|
38
|
+
maximum: @maximum,
|
39
|
+
multipleOf: @multiple_of
|
18
40
|
}).compact
|
19
41
|
end
|
20
42
|
end
|
data/lib/json/schema/leaf.rb
CHANGED
@@ -13,6 +13,7 @@ class JSON::Schema
|
|
13
13
|
@default = nil
|
14
14
|
@enum = nil
|
15
15
|
@required = nil
|
16
|
+
@const = nil
|
16
17
|
end
|
17
18
|
|
18
19
|
##
|
@@ -33,12 +34,22 @@ class JSON::Schema
|
|
33
34
|
|
34
35
|
##
|
35
36
|
# Set the allowed values of a leaf
|
37
|
+
# @see https://tour.json-schema.org/content/02-Primitive-Types/07-Enumerated-Values-II Enumerated Values
|
36
38
|
# @param [Array] values The allowed values
|
37
39
|
# @return [JSON::Schema::Leaf]
|
38
40
|
def enum(*values)
|
39
41
|
tap { @enum = values }
|
40
42
|
end
|
41
43
|
|
44
|
+
##
|
45
|
+
# Set the value of a leaf to be a constant value
|
46
|
+
# @see https://tour.json-schema.org/content/02-Primitive-Types/08-Defining-Constant-Values Constant Values
|
47
|
+
# @param [Object] value The constant value
|
48
|
+
# @return [JSON::Schema::Leaf]
|
49
|
+
def const(value)
|
50
|
+
tap { @const = value }
|
51
|
+
end
|
52
|
+
|
42
53
|
##
|
43
54
|
# Denote a leaf as required
|
44
55
|
# @return [JSON::Schema::Leaf]
|
data/lib/json/schema/null.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Null JSON::Schema::Null} class represents a
|
6
|
+
# null value in a JSON schema. It is a subclass of
|
7
|
+
# {JSON::Schema::Leaf JSON::Schema::Leaf}.
|
4
8
|
class Null < Leaf
|
5
9
|
def to_h
|
6
10
|
super.merge!({type: "null"})
|
data/lib/json/schema/number.rb
CHANGED
@@ -1,20 +1,42 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Number JSON::Schema::Number} class represents a
|
6
|
+
# a number (either whole or decimal) value in a JSON schema. It is a
|
7
|
+
# subclass of {JSON::Schema::Leaf JSON::Schema::Leaf} and provides
|
8
|
+
# methods that can act as constraints.
|
4
9
|
class Number < Leaf
|
10
|
+
##
|
11
|
+
# Constrain the number to a minimum value
|
12
|
+
# @param [Integer, Float] i The minimum value
|
13
|
+
# @return [JSON::Schema::Number] Returns self
|
5
14
|
def min(i)
|
6
15
|
tap { @minimum = i }
|
7
16
|
end
|
8
17
|
|
18
|
+
##
|
19
|
+
# Constrain the number to a maximum value
|
20
|
+
# @param [Integer, Float] i The maximum value
|
21
|
+
# @return [JSON::Schema::Number] Returns self
|
9
22
|
def max(i)
|
10
23
|
tap { @maximum = i }
|
11
24
|
end
|
12
25
|
|
26
|
+
##
|
27
|
+
# Constrain the number to a multiple of a given value
|
28
|
+
# @param [Integer, Float] i The multiple
|
29
|
+
# @return [JSON::Schema::Number] Returns self
|
30
|
+
def multiple_of(i)
|
31
|
+
tap { @multiple_of = i }
|
32
|
+
end
|
33
|
+
|
13
34
|
def to_h
|
14
35
|
super.merge!({
|
15
36
|
type: "number",
|
16
37
|
minimum: @minimum,
|
17
|
-
maximum: @maximum
|
38
|
+
maximum: @maximum,
|
39
|
+
multipleOf: @multiple_of
|
18
40
|
}).compact
|
19
41
|
end
|
20
42
|
end
|
data/lib/json/schema/object.rb
CHANGED
@@ -1,10 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
class JSON::Schema
|
4
|
+
##
|
5
|
+
# The {JSON::Schema::Object JSON::Schema::Object} class represents an
|
6
|
+
# object value in a JSON schema. It is a subclass of
|
7
|
+
# {JSON::Schema::Leaf JSON::Schema::Leaf} and provides methods that
|
8
|
+
# can act as constraints.
|
4
9
|
class Object < Leaf
|
5
|
-
def initialize(properties
|
10
|
+
def initialize(properties)
|
6
11
|
@properties = properties
|
7
|
-
super(**rest)
|
8
12
|
end
|
9
13
|
|
10
14
|
def to_h
|