llm.rb 4.1.0 → 4.3.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/LICENSE +2 -2
- data/README.md +241 -166
- data/lib/llm/agent.rb +65 -37
- data/lib/llm/bot.rb +118 -30
- data/lib/llm/buffer.rb +6 -0
- data/lib/llm/function/tracing.rb +19 -0
- data/lib/llm/function.rb +28 -3
- data/lib/llm/json_adapter.rb +12 -12
- data/lib/llm/message.rb +19 -4
- data/lib/llm/prompt.rb +85 -0
- data/lib/llm/provider.rb +62 -10
- data/lib/llm/providers/anthropic/error_handler.rb +27 -5
- data/lib/llm/providers/anthropic/files.rb +22 -16
- data/lib/llm/providers/anthropic/models.rb +4 -3
- data/lib/llm/providers/anthropic.rb +6 -5
- data/lib/llm/providers/deepseek.rb +3 -3
- data/lib/llm/providers/gemini/error_handler.rb +34 -12
- data/lib/llm/providers/gemini/files.rb +19 -14
- data/lib/llm/providers/gemini/images.rb +4 -3
- data/lib/llm/providers/gemini/models.rb +4 -3
- data/lib/llm/providers/gemini.rb +9 -7
- data/lib/llm/providers/llamacpp.rb +3 -3
- data/lib/llm/providers/ollama/error_handler.rb +28 -6
- data/lib/llm/providers/ollama/models.rb +4 -3
- data/lib/llm/providers/ollama.rb +9 -7
- data/lib/llm/providers/openai/audio.rb +10 -7
- data/lib/llm/providers/openai/error_handler.rb +41 -14
- data/lib/llm/providers/openai/files.rb +19 -14
- data/lib/llm/providers/openai/images.rb +10 -7
- data/lib/llm/providers/openai/models.rb +4 -3
- data/lib/llm/providers/openai/moderations.rb +4 -3
- data/lib/llm/providers/openai/responses.rb +10 -7
- data/lib/llm/providers/openai/vector_stores.rb +34 -23
- data/lib/llm/providers/openai.rb +9 -7
- data/lib/llm/providers/xai.rb +3 -3
- data/lib/llm/providers/zai.rb +2 -2
- data/lib/llm/schema/object.rb +4 -4
- data/lib/llm/schema.rb +16 -2
- data/lib/llm/server_tool.rb +3 -3
- data/lib/llm/session/deserializer.rb +36 -0
- data/lib/llm/session.rb +3 -0
- data/lib/llm/tracer/logger.rb +192 -0
- data/lib/llm/tracer/null.rb +49 -0
- data/lib/llm/tracer/telemetry.rb +255 -0
- data/lib/llm/tracer.rb +134 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +4 -3
- data/llm.gemspec +6 -3
- metadata +41 -5
- data/lib/llm/builder.rb +0 -79
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: f35a8e2b21d2bf7914c9b261897eaa6d9c6d0ff03dc51c0e751f86d22e08f093
|
|
4
|
+
data.tar.gz: 92ffab3eab12fd6393ef8e324360d13e9725403a9571cf6c28427be090021965
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 02cdeb6b969b4ec2d76ffce29236fb8c189cdf3a71ea121e2b218ffb8fdadff0005cfd4211bcdac6e17b9814f9533949f0bdec19efdd8fb2262096d5bd440dde
|
|
7
|
+
data.tar.gz: 3cf0abeefd9927df9e600a694387e0f7d334f9c614b6e64cd54cf3e5f82b6a73895d74c392a5a6f68a92c529155cfb3db067bf8b2ef85e0df4505105c759870c
|
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
>
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="llm.rb"><img src="https://github.com/llmrb/llm.rb/raw/main/llm.png" width="200" height="200" border="0" alt="llm.rb"></a>
|
|
3
|
+
</p>
|
|
4
|
+
<p align="center">
|
|
5
|
+
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"><img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc"></a>
|
|
6
|
+
<a href="https://opensource.org/license/0bsd"><img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License"></a>
|
|
7
|
+
<a href="https://github.com/llmrb/llm.rb/tags"><img src="https://img.shields.io/badge/version-4.3.0-green.svg?" alt="Version"></a>
|
|
8
|
+
</p>
|
|
4
9
|
|
|
5
10
|
## About
|
|
6
11
|
|
|
@@ -9,30 +14,39 @@ includes OpenAI, Gemini, Anthropic, xAI (Grok), zAI, DeepSeek, Ollama,
|
|
|
9
14
|
and LlamaCpp. The toolkit includes full support for chat, streaming,
|
|
10
15
|
tool calling, audio, images, files, and structured outputs.
|
|
11
16
|
|
|
17
|
+
And it is licensed under the [0BSD License](https://choosealicense.com/licenses/0bsd/) –
|
|
18
|
+
one of the most permissive open source licenses, with minimal conditions for use,
|
|
19
|
+
modification, and/or distribution. Attribution is appreciated, but not required
|
|
20
|
+
by the license. Built with [good music](https://www.youtube.com/watch?v=SNvaqwTbn14)
|
|
21
|
+
and a lot of ☕️.
|
|
22
|
+
|
|
12
23
|
## Quick start
|
|
13
24
|
|
|
14
25
|
#### REPL
|
|
15
26
|
|
|
16
|
-
The [LLM::
|
|
27
|
+
The [LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html) class provides
|
|
17
28
|
a session with an LLM provider that maintains conversation history and context across
|
|
18
|
-
multiple requests. The following example implements a simple REPL loop
|
|
29
|
+
multiple requests. The following example implements a simple REPL loop, and the response
|
|
30
|
+
is streamed to the terminal in real-time as it arrives from the provider. The provider
|
|
31
|
+
happens to be OpenAI in this case but it could be any other provider, and `$stdout`
|
|
32
|
+
could be any object that implements the `#<<` method:
|
|
19
33
|
|
|
20
34
|
```ruby
|
|
21
35
|
#!/usr/bin/env ruby
|
|
22
36
|
require "llm"
|
|
23
37
|
|
|
24
38
|
llm = LLM.openai(key: ENV["KEY"])
|
|
25
|
-
|
|
39
|
+
ses = LLM::Session.new(llm, stream: $stdout)
|
|
26
40
|
loop do
|
|
27
41
|
print "> "
|
|
28
|
-
|
|
42
|
+
ses.talk(STDIN.gets || break)
|
|
29
43
|
puts
|
|
30
44
|
end
|
|
31
45
|
```
|
|
32
46
|
|
|
33
47
|
#### Schema
|
|
34
48
|
|
|
35
|
-
The [LLM::Schema](https://0x1eef.github.io/x/llm.rb/LLM/
|
|
49
|
+
The [LLM::Schema](https://0x1eef.github.io/x/llm.rb/LLM/Schema.html) class provides
|
|
36
50
|
a simple DSL for describing the structure of a response that an LLM emits according
|
|
37
51
|
to a JSON schema. The schema lets a client describe what JSON object an LLM should
|
|
38
52
|
emit, and the LLM will abide by the schema to the best of its ability:
|
|
@@ -40,21 +54,32 @@ emit, and the LLM will abide by the schema to the best of its ability:
|
|
|
40
54
|
```ruby
|
|
41
55
|
#!/usr/bin/env ruby
|
|
42
56
|
require "llm"
|
|
57
|
+
require "pp"
|
|
43
58
|
|
|
44
|
-
class
|
|
45
|
-
property :
|
|
46
|
-
property :
|
|
47
|
-
property :
|
|
59
|
+
class Report < LLM::Schema
|
|
60
|
+
property :category, String, "Report category", required: true
|
|
61
|
+
property :summary, String, "Short summary", required: true
|
|
62
|
+
property :services, Array[String], "Impacted services", required: true
|
|
63
|
+
property :timestamp, String, "When it happened", optional: true
|
|
48
64
|
end
|
|
49
65
|
|
|
50
66
|
llm = LLM.openai(key: ENV["KEY"])
|
|
51
|
-
|
|
52
|
-
|
|
67
|
+
ses = LLM::Session.new(llm, schema: Report)
|
|
68
|
+
res = ses.talk("Structure this report: 'Database latency spiked at 10:42 UTC, causing 5% request timeouts for 12 minutes.'")
|
|
69
|
+
pp res.messages.first(&:assistant?).content!
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# {
|
|
73
|
+
# "category" => "Performance Incident",
|
|
74
|
+
# "summary" => "Database latency spiked, causing 5% request timeouts for 12 minutes.",
|
|
75
|
+
# "services" => ["Database"],
|
|
76
|
+
# "timestamp" => "2024-06-05T10:42:00Z"
|
|
77
|
+
# }
|
|
53
78
|
```
|
|
54
79
|
|
|
55
80
|
#### Tools
|
|
56
81
|
|
|
57
|
-
The [LLM::Tool](https://0x1eef.github.io/x/llm.rb/LLM/
|
|
82
|
+
The [LLM::Tool](https://0x1eef.github.io/x/llm.rb/LLM/Tool.html) class lets you
|
|
58
83
|
define callable tools for the model. Each tool is described to the LLM as a function
|
|
59
84
|
it can invoke to fetch information or perform an action. The model decides when to
|
|
60
85
|
call tools based on the conversation; when it does, llm.rb runs the tool and sends
|
|
@@ -76,19 +101,19 @@ class System < LLM::Tool
|
|
|
76
101
|
end
|
|
77
102
|
|
|
78
103
|
llm = LLM.openai(key: ENV["KEY"])
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
104
|
+
ses = LLM::Session.new(llm, tools: [System])
|
|
105
|
+
ses.talk("Run `date`.")
|
|
106
|
+
ses.talk(ses.functions.map(&:call)) # report return value to the LLM
|
|
82
107
|
```
|
|
83
108
|
|
|
84
109
|
#### Agents
|
|
85
110
|
|
|
86
|
-
The [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/
|
|
111
|
+
The [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html)
|
|
87
112
|
class provides a class-level DSL for defining reusable, preconfigured
|
|
88
113
|
assistants with defaults for model, tools, schema, and instructions.
|
|
89
114
|
Instructions are injected only on the first request, and unlike
|
|
90
|
-
[LLM::
|
|
91
|
-
an [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/
|
|
115
|
+
[LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html),
|
|
116
|
+
an [LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html)
|
|
92
117
|
will automatically call tools when needed:
|
|
93
118
|
|
|
94
119
|
```ruby
|
|
@@ -104,28 +129,52 @@ end
|
|
|
104
129
|
|
|
105
130
|
llm = LLM.openai(key: ENV["KEY"])
|
|
106
131
|
agent = SystemAdmin.new(llm)
|
|
107
|
-
res = agent.
|
|
132
|
+
res = agent.talk("Run 'date'")
|
|
108
133
|
```
|
|
109
134
|
|
|
110
135
|
#### Prompts
|
|
111
136
|
|
|
112
|
-
The [LLM::
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
137
|
+
The [LLM::Prompt](https://0x1eef.github.io/x/llm.rb/LLM/Prompt.html)
|
|
138
|
+
class represents a single request composed of multiple messages.
|
|
139
|
+
It is useful when a single turn needs more than one message, for example:
|
|
140
|
+
system instructions plus one or more user messages, or a replay of
|
|
141
|
+
prior context:
|
|
116
142
|
|
|
117
143
|
```ruby
|
|
118
144
|
#!/usr/bin/env ruby
|
|
119
145
|
require "llm"
|
|
120
146
|
|
|
121
147
|
llm = LLM.openai(key: ENV["KEY"])
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
148
|
+
ses = LLM::Session.new(llm)
|
|
149
|
+
|
|
150
|
+
prompt = ses.prompt do
|
|
151
|
+
system "Be concise and show your reasoning briefly."
|
|
152
|
+
user "If a train goes 60 mph for 1.5 hours, how far does it travel?"
|
|
153
|
+
user "Now double the speed for the same time."
|
|
127
154
|
end
|
|
128
|
-
|
|
155
|
+
|
|
156
|
+
ses.talk(prompt)
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
But prompts are not session-scoped. [LLM::Prompt](https://0x1eef.github.io/x/llm.rb/LLM/Prompt.html)
|
|
160
|
+
is a first-class object that you can build and pass around independently of a session.
|
|
161
|
+
This enables patterns where you compose a prompt in one part of your code,
|
|
162
|
+
and execute it through a session elsewhere:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
#!/usr/bin/env ruby
|
|
166
|
+
require "llm"
|
|
167
|
+
|
|
168
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
169
|
+
ses = LLM::Session.new(llm)
|
|
170
|
+
|
|
171
|
+
prompt = LLM::Prompt.new(llm) do
|
|
172
|
+
system "Be concise and show your reasoning briefly."
|
|
173
|
+
user "If a train goes 60 mph for 1.5 hours, how far does it travel?"
|
|
174
|
+
user "Now double the speed for the same time."
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
ses.talk(prompt)
|
|
129
178
|
```
|
|
130
179
|
|
|
131
180
|
## Features
|
|
@@ -134,10 +183,17 @@ bot.chat(prompt)
|
|
|
134
183
|
- ✅ Unified API across providers
|
|
135
184
|
- 📦 Zero runtime deps (stdlib-only)
|
|
136
185
|
- 🧩 Pluggable JSON adapters (JSON, Oj, Yajl, etc)
|
|
137
|
-
-
|
|
186
|
+
- 🧱 Builtin tracer API ([LLM::Tracer](https://0x1eef.github.io/x/llm.rb/LLM/Tracer.html))
|
|
187
|
+
|
|
188
|
+
#### Optionals
|
|
189
|
+
|
|
190
|
+
- ♻️ Optional persistent HTTP pool via net-http-persistent ([net-http-persistent](https://github.com/drbrain/net-http-persistent))
|
|
191
|
+
- 📈 Optional telemetry support via OpenTelemetry ([opentelemetry-sdk](https://github.com/open-telemetry/opentelemetry-ruby))
|
|
192
|
+
- 🪵 Optional logging support via Ruby's standard library ([ruby/logger](https://github.com/ruby/logger))
|
|
138
193
|
|
|
139
194
|
#### Chat, Agents
|
|
140
195
|
- 🧠 Stateless + stateful chat (completions + responses)
|
|
196
|
+
- 💾 Save and restore sessions across processes
|
|
141
197
|
- 🤖 Tool calling / function execution
|
|
142
198
|
- 🔁 Agent tool-call auto-execution (bounded)
|
|
143
199
|
- 🗂️ JSON Schema structured output
|
|
@@ -250,115 +306,151 @@ res3 = llm.responses.create "message 3", previous_response_id: res2.response_id
|
|
|
250
306
|
puts res3.output_text
|
|
251
307
|
```
|
|
252
308
|
|
|
253
|
-
####
|
|
254
|
-
|
|
255
|
-
The llm.rb library is thread-safe and can be used in a multi-threaded
|
|
256
|
-
environments but it is important to keep in mind that the
|
|
257
|
-
[LLM::Provider](https://0x1eef.github.io/x/llm.rb/LLM/Provider.html)
|
|
258
|
-
and
|
|
259
|
-
[LLM::Bot](https://0x1eef.github.io/x/llm.rb/LLM/Bot.html)
|
|
260
|
-
classes should be instantiated once per thread, and not shared
|
|
261
|
-
between threads. Generally the library tries to avoid global or
|
|
262
|
-
shared state but where it exists reentrant locks are used to
|
|
263
|
-
ensure thread-safety.
|
|
264
|
-
|
|
265
|
-
### Conversations
|
|
309
|
+
#### Telemetry
|
|
266
310
|
|
|
267
|
-
|
|
311
|
+
The llm.rb library includes telemetry support through its tracer API, and it
|
|
312
|
+
can be used to trace LLM requests. It can be useful for debugging, monitoring,
|
|
313
|
+
and observability. The primary use case in mind is integration with tools like
|
|
314
|
+
[LangSmith](https://www.langsmith.com/).
|
|
268
315
|
|
|
269
|
-
The
|
|
270
|
-
[
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
316
|
+
The telemetry implementation uses the [opentelemetry-sdk](https://github.com/open-telemetry/opentelemetry-ruby)
|
|
317
|
+
and is based on the [gen-ai telemetry spec(s)](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/).
|
|
318
|
+
This feature is optional, disabled by default, and the [opentelemetry-sdk](https://github.com/open-telemetry/opentelemetry-ruby)
|
|
319
|
+
gem should be installed separately. Please also note that llm.rb will take care of
|
|
320
|
+
loading and configuring the [opentelemetry-sdk](https://github.com/open-telemetry/opentelemetry-ruby)
|
|
321
|
+
library for you, and llm.rb configures an in-memory exporter that doesn't have
|
|
322
|
+
external dependencies by default:
|
|
276
323
|
|
|
277
324
|
```ruby
|
|
278
325
|
#!/usr/bin/env ruby
|
|
279
326
|
require "llm"
|
|
327
|
+
require "pp"
|
|
280
328
|
|
|
281
|
-
llm
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
it.user ["Tell me about this image", bot.image_url(image_url)]
|
|
289
|
-
it.user ["Tell me about this image", bot.local_file(image_path)]
|
|
290
|
-
it.user ["Tell me about this PDF", bot.local_file(pdf_path)]
|
|
291
|
-
end
|
|
292
|
-
bot.chat(prompt)
|
|
293
|
-
bot.messages.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
329
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
330
|
+
llm.tracer = LLM::Tracer::Telemetry.new(llm)
|
|
331
|
+
|
|
332
|
+
ses = LLM::Session.new(llm)
|
|
333
|
+
ses.talk "Hello world!"
|
|
334
|
+
ses.talk "Adios."
|
|
335
|
+
ses.tracer.spans.each { |span| pp span }
|
|
294
336
|
```
|
|
295
337
|
|
|
296
|
-
|
|
338
|
+
The llm.rb library also supports export through the OpenTelemetry Protocol (OTLP).
|
|
339
|
+
OTLP is a standard protocol for exporting telemetry data, and it is supported by
|
|
340
|
+
multiple observability tools. By default the export is batched in the background,
|
|
341
|
+
and happens automatically but short lived scripts might need to
|
|
342
|
+
[explicitly flush](https://0x1eef.github.io/x/llm.rb/LLM/Tracer/Telemetry#flush!-instance_method)
|
|
343
|
+
the exporter before they exit – otherwise some telemetry data could be lost:
|
|
344
|
+
|
|
345
|
+
```ruby
|
|
346
|
+
#!/usr/bin/env ruby
|
|
347
|
+
require "llm"
|
|
348
|
+
require "opentelemetry-exporter-otlp"
|
|
349
|
+
|
|
350
|
+
endpoint = "https://api.smith.langchain.com/otel/v1/traces"
|
|
351
|
+
exporter = OpenTelemetry::Exporter::OTLP::Exporter.new(endpoint:)
|
|
352
|
+
|
|
353
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
354
|
+
llm.tracer = LLM::Tracer::Telemetry.new(llm, exporter:)
|
|
355
|
+
|
|
356
|
+
ses = LLM::Session.new(llm)
|
|
357
|
+
ses.talk "hello"
|
|
358
|
+
ses.talk "how are you?"
|
|
297
359
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
360
|
+
at_exit do
|
|
361
|
+
# Helpful for short-lived scripts, otherwise the exporter
|
|
362
|
+
# might not have time to flush pending telemetry data
|
|
363
|
+
ses.tracer.flush!
|
|
364
|
+
end
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### Logger
|
|
368
|
+
|
|
369
|
+
The llm.rb library includes simple logging support through its
|
|
370
|
+
tracer API, and Ruby's standard library ([ruby/logger](https://github.com/ruby/logger)).
|
|
371
|
+
This feature is optional, disabled by default, and it can be useful for debugging and/or
|
|
372
|
+
monitoring requests to LLM providers. The `path` or `io` options can be used to choose
|
|
373
|
+
where logs are written to, and by default it is set to `$stdout`:
|
|
304
374
|
|
|
305
375
|
```ruby
|
|
306
376
|
#!/usr/bin/env ruby
|
|
307
377
|
require "llm"
|
|
308
378
|
|
|
309
379
|
llm = LLM.openai(key: ENV["KEY"])
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
prompt = bot.build_prompt do
|
|
316
|
-
it.user ["Tell me about this image", bot.image_url(image_url)]
|
|
317
|
-
it.user ["Tell me about this image", bot.local_file(image_path)]
|
|
318
|
-
it.user ["Tell me about the PDF", bot.local_file(pdf_path)]
|
|
319
|
-
end
|
|
320
|
-
bot.chat(prompt)
|
|
380
|
+
llm.tracer = LLM::Tracer::Logger.new(llm, io: $stdout)
|
|
381
|
+
|
|
382
|
+
ses = LLM::Session.new(llm)
|
|
383
|
+
ses.talk "Hello world!"
|
|
384
|
+
ses.talk "Adios."
|
|
321
385
|
```
|
|
322
386
|
|
|
323
|
-
|
|
387
|
+
#### Serialization
|
|
324
388
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
389
|
+
[LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html) can be
|
|
390
|
+
serialized and deserialized across process boundaries and persisted to
|
|
391
|
+
storage such as files, a `jsonb` column (PostgreSQL), or other backends
|
|
392
|
+
through a JSON representation of the history encapsulated by
|
|
393
|
+
[LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html)
|
|
394
|
+
– inclusive of tool metadata as well:
|
|
330
395
|
|
|
396
|
+
* Process 1
|
|
331
397
|
```ruby
|
|
332
398
|
#!/usr/bin/env ruby
|
|
333
399
|
require "llm"
|
|
334
400
|
|
|
335
|
-
class Player < LLM::Schema
|
|
336
|
-
property :name, String, "The player's name", required: true
|
|
337
|
-
property :position, Array[Number], "The player's [x, y] position", required: true
|
|
338
|
-
end
|
|
339
|
-
|
|
340
401
|
llm = LLM.openai(key: ENV["KEY"])
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
402
|
+
ses = LLM::Session.new(llm)
|
|
403
|
+
ses.talk "Howdy partner"
|
|
404
|
+
ses.talk "I'll see you later"
|
|
405
|
+
ses.save(path: "session.json")
|
|
406
|
+
```
|
|
407
|
+
* Process 2
|
|
408
|
+
```ruby
|
|
409
|
+
#!/usr/bin/env ruby
|
|
410
|
+
require "llm"
|
|
411
|
+
require "pp"
|
|
346
412
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
413
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
414
|
+
ses = LLM::Session.new(llm)
|
|
415
|
+
ses.restore(path: "session.json")
|
|
416
|
+
ses.talk "Howdy partner. I'm back"
|
|
417
|
+
pp ses.messages
|
|
350
418
|
```
|
|
351
419
|
|
|
352
|
-
|
|
420
|
+
But how does it work without a file ? The [LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html)
|
|
421
|
+
class implements `#to_json` and it can be used to obtain a JSON representation
|
|
422
|
+
of a session that can be stored in a `jsonb` column in PostgreSQL, or any
|
|
423
|
+
other storage backend. The session can then be restored from the JSON
|
|
424
|
+
representation via the restore method and its `string` argument:
|
|
425
|
+
|
|
426
|
+
```ruby
|
|
427
|
+
#!/usr/bin/env ruby
|
|
428
|
+
require "llm"
|
|
429
|
+
|
|
430
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
431
|
+
ses1 = LLM::Session.new(llm)
|
|
432
|
+
ses1.talk "Howdy partner"
|
|
433
|
+
ses1.talk "I'll see you later"
|
|
434
|
+
|
|
435
|
+
json = ses1.to_json
|
|
436
|
+
ses2 = LLM::Session.new(llm)
|
|
437
|
+
ses2.restore(string: json)
|
|
438
|
+
ses2.talk "Howdy partner. I'm back"
|
|
439
|
+
```
|
|
353
440
|
|
|
354
|
-
####
|
|
441
|
+
#### Thread Safety
|
|
355
442
|
|
|
356
|
-
|
|
357
|
-
it is
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
[LLM::
|
|
443
|
+
The llm.rb library is thread-safe and can be used in a multi-threaded
|
|
444
|
+
environments but it is important to keep in mind that the
|
|
445
|
+
[LLM::Provider](https://0x1eef.github.io/x/llm.rb/LLM/Provider.html)
|
|
446
|
+
and
|
|
447
|
+
[LLM::Session](https://0x1eef.github.io/x/llm.rb/LLM/Session.html)
|
|
448
|
+
classes should be instantiated once per thread, and not shared
|
|
449
|
+
between threads. Generally the library tries to avoid global or
|
|
450
|
+
shared state but where it exists reentrant locks are used to
|
|
451
|
+
ensure thread-safety.
|
|
361
452
|
|
|
453
|
+
### Tools
|
|
362
454
|
|
|
363
455
|
#### LLM::Function
|
|
364
456
|
|
|
@@ -366,13 +458,7 @@ The following example demonstrates [LLM::Function](https://0x1eef.github.io/x/ll
|
|
|
366
458
|
and how it can define a local function (which happens to be a tool), and how
|
|
367
459
|
a provider (such as OpenAI) can then detect when we should call the function.
|
|
368
460
|
Its most notable feature is that it can act as a closure and has access to
|
|
369
|
-
its surrounding scope, which can be useful in some situations
|
|
370
|
-
|
|
371
|
-
The
|
|
372
|
-
[LLM::Bot#functions](https://0x1eef.github.io/x/llm.rb/LLM/Bot.html#functions-instance_method)
|
|
373
|
-
method returns an array of functions that can be called after a `chat` interaction
|
|
374
|
-
if the LLM detects a function should be called. You would then typically call these
|
|
375
|
-
functions and send their results back to the LLM in a subsequent `chat` call:
|
|
461
|
+
its surrounding scope, which can be useful in some situations:
|
|
376
462
|
|
|
377
463
|
```ruby
|
|
378
464
|
#!/usr/bin/env ruby
|
|
@@ -393,14 +479,14 @@ tool = LLM.function(:system) do |fn|
|
|
|
393
479
|
end
|
|
394
480
|
end
|
|
395
481
|
|
|
396
|
-
|
|
397
|
-
|
|
482
|
+
ses = LLM::Session.new(llm, tools: [tool])
|
|
483
|
+
ses.talk "Your task is to run shell commands via a tool.", role: :user
|
|
398
484
|
|
|
399
|
-
|
|
400
|
-
|
|
485
|
+
ses.talk "What is the current date?", role: :user
|
|
486
|
+
ses.talk ses.functions.map(&:call) # report return value to the LLM
|
|
401
487
|
|
|
402
|
-
|
|
403
|
-
|
|
488
|
+
ses.talk "What operating system am I running?", role: :user
|
|
489
|
+
ses.talk ses.functions.map(&:call) # report return value to the LLM
|
|
404
490
|
|
|
405
491
|
##
|
|
406
492
|
# {stderr: "", stdout: "Thu May 1 10:01:02 UTC 2025"}
|
|
@@ -440,14 +526,14 @@ class System < LLM::Tool
|
|
|
440
526
|
end
|
|
441
527
|
|
|
442
528
|
llm = LLM.openai(key: ENV["KEY"])
|
|
443
|
-
|
|
444
|
-
|
|
529
|
+
ses = LLM::Session.new(llm, tools: [System])
|
|
530
|
+
ses.talk "Your task is to run shell commands via a tool.", role: :user
|
|
445
531
|
|
|
446
|
-
|
|
447
|
-
|
|
532
|
+
ses.talk "What is the current date?", role: :user
|
|
533
|
+
ses.talk ses.functions.map(&:call) # report return value to the LLM
|
|
448
534
|
|
|
449
|
-
|
|
450
|
-
|
|
535
|
+
ses.talk "What operating system am I running?", role: :user
|
|
536
|
+
ses.talk ses.functions.map(&:call) # report return value to the LLM
|
|
451
537
|
|
|
452
538
|
##
|
|
453
539
|
# {stderr: "", stdout: "Thu May 1 10:01:02 UTC 2025"}
|
|
@@ -470,53 +556,36 @@ it has been uploaded. The file (a specialized instance of
|
|
|
470
556
|
require "llm"
|
|
471
557
|
|
|
472
558
|
llm = LLM.openai(key: ENV["KEY"])
|
|
473
|
-
|
|
559
|
+
ses = LLM::Session.new(llm)
|
|
474
560
|
file = llm.files.create(file: "/tmp/llm-book.pdf")
|
|
475
|
-
res =
|
|
476
|
-
res.
|
|
561
|
+
res = ses.talk ["Tell me about this file", file]
|
|
562
|
+
res.messages.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
477
563
|
```
|
|
478
564
|
|
|
479
565
|
### Prompts
|
|
480
566
|
|
|
481
567
|
#### Multimodal
|
|
482
568
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
provider.
|
|
569
|
+
LLMs are great with text, but many can also handle images, audio, video,
|
|
570
|
+
and URLs. With llm.rb you pass those inputs by tagging them with one of
|
|
571
|
+
the following methods. And for multipart prompts, we can pass an array
|
|
572
|
+
where each element is a part of the input. See the example below for
|
|
573
|
+
details, in the meantime here are the methods to know for multimodal
|
|
574
|
+
inputs:
|
|
490
575
|
|
|
491
|
-
|
|
492
|
-
`
|
|
493
|
-
|
|
494
|
-
`bot.remote_file`. This approach ensures clarity and allows
|
|
495
|
-
llm.rb to correctly format the input for each provider's
|
|
496
|
-
specific requirements.
|
|
497
|
-
|
|
498
|
-
An array can be used for a prompt with multiple parts, where each
|
|
499
|
-
element contributes to the overall input:
|
|
576
|
+
* `ses.image_url` for an image URL
|
|
577
|
+
* `ses.local_file` for a local file
|
|
578
|
+
* `ses.remote_file` for a file already uploaded via the provider's Files API
|
|
500
579
|
|
|
501
580
|
```ruby
|
|
502
581
|
#!/usr/bin/env ruby
|
|
503
582
|
require "llm"
|
|
504
583
|
|
|
505
584
|
llm = LLM.openai(key: ENV["KEY"])
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
res1 = bot.chat ["Tell me about this image URL", bot.image_url(image_url)]
|
|
512
|
-
res1.choices.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
513
|
-
|
|
514
|
-
file = llm.files.create(file: pdf_path)
|
|
515
|
-
res2 = bot.chat ["Tell me about this PDF", bot.remote_file(file)]
|
|
516
|
-
res2.choices.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
517
|
-
|
|
518
|
-
res3 = bot.chat ["Tell me about this image", bot.local_file(image_path)]
|
|
519
|
-
res3.choices.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
585
|
+
ses = LLM::Session.new(llm)
|
|
586
|
+
res = ses.talk ["Tell me about this image URL", ses.image_url(url)]
|
|
587
|
+
res = ses.talk ["Tell me about this PDF", ses.remote_file(file)]
|
|
588
|
+
res = ses.talk ["Tell me about this image", ses.local_file(path)]
|
|
520
589
|
```
|
|
521
590
|
|
|
522
591
|
### Audio
|
|
@@ -694,9 +763,9 @@ end
|
|
|
694
763
|
##
|
|
695
764
|
# Select a model
|
|
696
765
|
model = llm.models.all.find { |m| m.id == "gpt-3.5-turbo" }
|
|
697
|
-
|
|
698
|
-
res =
|
|
699
|
-
res.
|
|
766
|
+
ses = LLM::Session.new(llm, model: model.id)
|
|
767
|
+
res = ses.talk "Hello #{model.id} :)"
|
|
768
|
+
res.messages.each { |m| puts "[#{m.role}] #{m.content}" }
|
|
700
769
|
```
|
|
701
770
|
|
|
702
771
|
## Install
|
|
@@ -705,6 +774,12 @@ llm.rb can be installed via rubygems.org:
|
|
|
705
774
|
|
|
706
775
|
gem install llm.rb
|
|
707
776
|
|
|
777
|
+
## Sources
|
|
778
|
+
|
|
779
|
+
* [GitHub.com](https://github.com/llmrb/llm.rb)
|
|
780
|
+
* [GitLab.com](https://gitlab.com/llmrb/llm.rb)
|
|
781
|
+
* [Codeberg.org](https://codeberg.org/llmrb/llm.rb)
|
|
782
|
+
|
|
708
783
|
## License
|
|
709
784
|
|
|
710
785
|
[BSD Zero Clause](https://choosealicense.com/licenses/0bsd/)
|