llm.rb 11.3.0 → 11.3.1

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.
@@ -0,0 +1,432 @@
1
+ <p align="center">
2
+ <a href="https://r.uby.dev/llm/">
3
+ <img
4
+ src="https://github.com/r-uby-dev/llm.rb/raw/main/rubydev.svg"
5
+ width="400"
6
+ height="200"
7
+ border="0"
8
+ alt="a r.uby.dev project"
9
+ >
10
+ </a>
11
+ </p>
12
+
13
+ > A [r.uby.dev](https://r.uby.dev) project.
14
+
15
+ ## Intro
16
+
17
+ This guide is a practical walkthrough of [llm.rb](https://github.com/r-uby-dev/llm.rb#readme) —
18
+ Ruby's capable AI runtime.
19
+
20
+ llm.rb runs on Ruby's standard library by default and loads optional pieces
21
+ only when needed. You can start with a provider and a single context, then add
22
+ agents, tools, streaming, persistence, embeddings, and protocol clients
23
+ without changing the shape of your code.
24
+
25
+ It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google Gemini,
26
+ DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. ActiveRecord and
27
+ Sequel support are built in, along with concurrent tool execution through
28
+ threads, tasks, fibers, ractors, and fork.
29
+
30
+ ## Install
31
+
32
+ ```bash
33
+ gem install llm.rb
34
+ ```
35
+
36
+ ## Quick Start
37
+
38
+ #### Agent
39
+
40
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html) is the
41
+ recommended starting point.
42
+ <br>
43
+ It manages tool execution for you and keeps conversation state across turns.
44
+
45
+ ```ruby
46
+ require "llm"
47
+
48
+ llm = LLM.openai(key: ENV["KEY"])
49
+ agent = LLM::Agent.new(llm, stream: $stdout)
50
+ agent.talk "Hello world"
51
+ ```
52
+
53
+ #### REPL
54
+
55
+ A read-eval-print loop is the simplest way to interact with an agent.
56
+ <br>
57
+ The loop reads input, sends it to the model, and prints the response as it
58
+ arrives:
59
+
60
+ ```ruby
61
+ require "llm"
62
+
63
+ llm = LLM.openai(key: ENV["KEY"])
64
+ agent = LLM::Agent.new(llm, stream: $stdout)
65
+
66
+ loop do
67
+ print "> "
68
+ agent.talk(STDIN.gets || break)
69
+ puts
70
+ end
71
+ ```
72
+
73
+ #### Context
74
+
75
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) is the
76
+ lower-level runtime object.
77
+ <br>
78
+ It holds the same conversation state but leaves tool execution up to you.
79
+ Use it when you want to decide when and how tools run.
80
+
81
+ ```ruby
82
+ require "llm"
83
+
84
+ llm = LLM.openai(key: ENV["KEY"])
85
+ ctx = LLM::Context.new(llm, stream: $stdout)
86
+ ctx.talk "Hello world"
87
+ ```
88
+
89
+ With tools, the manual loop is explicit:
90
+
91
+ ```ruby
92
+ ctx = LLM::Context.new(llm, tools: [ReadFile])
93
+ ctx.talk("Read README.md and summarize it.")
94
+ ctx.talk(ctx.wait(:call)) while ctx.functions?
95
+ ```
96
+
97
+ For ordinary application code, prefer
98
+ [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
99
+ It does the same thing but manages the loop for you.
100
+
101
+ ## Tools
102
+
103
+ #### Definition
104
+
105
+ Tools extend what the model can do.
106
+ <br>
107
+ They are plain Ruby classes with typed parameters. Define one, attach it to
108
+ an agent, and the model can call it when it makes sense.
109
+
110
+ ```ruby
111
+ class ReadFile < LLM::Tool
112
+ name "read-file"
113
+ description "Read a file"
114
+ parameter :path, String, "The filename or path"
115
+ required %i[path]
116
+
117
+ def call(path:)
118
+ {contents: File.read(path)}
119
+ end
120
+ end
121
+ ```
122
+
123
+ Attach the tool to an agent:
124
+
125
+ ```ruby
126
+ agent = LLM::Agent.new(llm, stream: $stdout, tools: [ReadFile])
127
+ agent.talk "Read README.md and summarize the project."
128
+ ```
129
+
130
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html) handles the
131
+ Ruby-side definition. llm.rb adapts the tool schema to the provider at request
132
+ time.
133
+
134
+ #### Concurrency
135
+
136
+ When an agent calls several tools at once, you can run them in parallel.
137
+ <br>
138
+ This cuts down waiting time when tools do independent work like reading
139
+ files or calling APIs.
140
+
141
+ ```ruby
142
+ class Agent < LLM::Agent
143
+ model "gpt-5.4-mini"
144
+ tools ReadFile
145
+ concurrency :thread
146
+ end
147
+
148
+ llm = LLM.openai(key: ENV["KEY"])
149
+ agent = Agent.new(llm, stream: $stdout)
150
+ agent.talk "Read README.md and CHANGELOG.md and compare them."
151
+ ```
152
+
153
+ ## Structured Output
154
+
155
+ #### Schema
156
+
157
+ When you need JSON with a known shape, use
158
+ [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html).
159
+ <br>
160
+ The model will return data that matches your schema instead of free text.
161
+
162
+ ```ruby
163
+ class Report < LLM::Schema
164
+ property :category, Enum["performance", "security", "outage"]
165
+ property :summary, String, "Short summary"
166
+ property :services, Array[String], "Impacted services"
167
+ required %i[category summary services]
168
+ end
169
+
170
+ agent = LLM::Agent.new(llm, schema: Report)
171
+ res = agent.talk("Classify: 'API latency spiked for the billing service.'")
172
+ puts res.content!
173
+ ```
174
+
175
+ For one-off schemas, build the shape inline:
176
+
177
+ ```ruby
178
+ schema = LLM::Schema.new.object(
179
+ category: LLM::Schema.new.string.enum("bug", "feature").required,
180
+ summary: LLM::Schema.new.string.required
181
+ )
182
+
183
+ agent = LLM::Agent.new(llm, schema:)
184
+ res = agent.talk("Classify: add a dark mode toggle.")
185
+ puts res.content
186
+ ```
187
+
188
+ ## Streaming
189
+
190
+ #### Stream
191
+
192
+ Streaming works with any object that responds to `#<<`, like `$stdout`.
193
+ <br>
194
+ For more control, subclass
195
+ [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html) and
196
+ override its callbacks:
197
+
198
+ ```ruby
199
+ class MyStream < LLM::Stream
200
+ def on_content(content)
201
+ print content
202
+ end
203
+
204
+ def on_reasoning_content(content)
205
+ warn content
206
+ end
207
+ end
208
+
209
+ llm = LLM.openai(key: ENV["KEY"])
210
+ agent = LLM::Agent.new(llm, stream: MyStream.new)
211
+ agent.talk "Explain Ruby fibers."
212
+ ```
213
+
214
+ ## Skills
215
+
216
+ #### Release
217
+
218
+ Skills package repeatable instructions and scoped tool access into
219
+ `SKILL.md` directories.
220
+ <br>
221
+ They turn common workflows into named capabilities that agents can load
222
+ on demand.
223
+
224
+ ```yaml
225
+ ---
226
+ name: release
227
+ description: Prepare a release
228
+ tools: ["search-docs", "git"]
229
+ ---
230
+
231
+ ## Task
232
+
233
+ Review the release state, summarize what changed, and prepare the release.
234
+ ```
235
+
236
+ ```ruby
237
+ class ReleaseAgent < LLM::Agent
238
+ model "gpt-5.4-mini"
239
+ skills "./skills/release"
240
+ end
241
+
242
+ llm = LLM.openai(key: ENV["KEY"])
243
+ ReleaseAgent.new(llm, stream: $stdout).talk("Prepare the next release.")
244
+ ```
245
+
246
+ When a skill runs, llm.rb starts a subagent with the skill's instructions,
247
+ its allowed tools, and recent conversation context. Skills can also use
248
+ `tools: inherit` to run with the parent agent's full toolset.
249
+
250
+ ## MCP
251
+
252
+ #### Stdio
253
+
254
+ [`LLM::MCP`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html) lets llm.rb use
255
+ tools provided by local stdio servers or remote HTTP servers.
256
+ <br>
257
+ This is how you connect your agent to GitHub, databases, or anything else
258
+ that speaks the Model Context Protocol.
259
+
260
+ ```ruby
261
+ require "llm"
262
+
263
+ llm = LLM.openai(key: ENV["KEY"])
264
+ mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
265
+
266
+ mcp.session do
267
+ agent = LLM::Agent.new(llm, stream: $stdout, tools: mcp.tools)
268
+ agent.talk "Use the available tools to inspect the environment."
269
+ end
270
+ ```
271
+
272
+ #### Remote
273
+
274
+ For HTTP MCP servers, use persistent connections when you make repeated
275
+ tool calls:
276
+
277
+ ```ruby
278
+ mcp = LLM::MCP.http(
279
+ url: "https://remote-mcp.example.com",
280
+ transport: :net_http_persistent
281
+ )
282
+
283
+ agent = LLM::Agent.new(llm, stream: $stdout, tools: mcp.tools)
284
+ agent.talk "Use the remote tools to inspect the repository."
285
+ ```
286
+
287
+ ## Persistence
288
+
289
+ #### Overview
290
+
291
+ Agents and contexts serialize to JSON and restore later.
292
+ <br>
293
+ The same serialized state powers the ActiveRecord and Sequel integrations.
294
+
295
+ #### Filesystem
296
+
297
+ Persist agent state to a JSON file on disk.
298
+
299
+ ```ruby
300
+ require "llm"
301
+
302
+ llm = LLM.openai(key: ENV["KEY"])
303
+ agent = LLM::Agent.new(llm)
304
+ agent.talk "Remember that my favorite language is Ruby"
305
+
306
+ # Save
307
+ File.write("agent.json", agent.to_json)
308
+
309
+ # Restore later
310
+ agent2 = LLM::Agent.new(llm, stream: $stdout)
311
+ agent2.restore(path: "agent.json")
312
+ agent2.talk "What is my favorite language?"
313
+ ```
314
+
315
+ #### ActiveRecord
316
+
317
+ [`acts_as_agent`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord/ActsAsAgent.html)
318
+ wraps an agent directly on an ActiveRecord model.
319
+ <br>
320
+ Serialized state lives in a single `data` column while your application
321
+ controls provider, model, and tool configuration.
322
+
323
+ ```ruby
324
+ require "llm"
325
+ require "active_record"
326
+ require "llm/active_record"
327
+
328
+ class Ticket < ApplicationRecord
329
+ acts_as_agent provider: :set_provider, context: :set_context
330
+ model "gpt-5.4-mini"
331
+ instructions "You are a concise support assistant."
332
+ tools SearchDocs, Escalate
333
+ concurrency :thread
334
+
335
+ private
336
+
337
+ def set_provider
338
+ LLM.openai(key: ENV["OPENAI_SECRET"])
339
+ end
340
+
341
+ def set_context
342
+ {mode: :responses, store: false}
343
+ end
344
+ end
345
+
346
+ ticket = Ticket.create!
347
+ puts ticket.talk("How do I rotate my API key?").content
348
+ ```
349
+
350
+ If you need manual control over tool execution, use
351
+ [`acts_as_llm`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord/ActsAsLLM.html)
352
+ instead. It wraps
353
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) with the
354
+ same persistence contract.
355
+
356
+ ## Embeddings
357
+
358
+ #### Vector
359
+
360
+ Embeddings turn text into vectors. Call `.embed` on any provider that supports
361
+ it. The returned vectors can be stored in a vector-aware database (PostgreSQL
362
+ with pgvector, SQLite with `vec0`, or a dedicated vector database) and
363
+ compared by semantic similarity.
364
+
365
+ ```ruby
366
+ llm = LLM.openai(key: ENV["KEY"])
367
+ res = llm.embed("llm.rb manages providers, agents, tools, and state")
368
+ puts res.model
369
+ puts res.embeddings.first.size
370
+ ```
371
+
372
+ Embed multiple texts at once:
373
+
374
+ ```ruby
375
+ chunks = [
376
+ "LLM::Agent manages the tool loop automatically.",
377
+ "LLM::Context exposes the low-level tool loop.",
378
+ "MCP tools can be passed to agents as local tools."
379
+ ]
380
+
381
+ res = llm.embed(chunks)
382
+ res.embeddings.each_with_index { |vec, i| puts "Vector #{i}: #{vec.size} dimensions" }
383
+ ```
384
+
385
+ ## Multimodal
386
+
387
+ #### Image
388
+
389
+ Prompts can be strings, arrays, or
390
+ [`LLM::Prompt`](https://r.uby.dev/api-docs/llm.rb/LLM/Prompt.html) objects.
391
+ <br>
392
+ Arrays let you mix text with images and other content.
393
+
394
+ ```ruby
395
+ agent = LLM::Agent.new(llm)
396
+ agent.talk [
397
+ "Describe this image",
398
+ agent.image_url("https://example.com/image.png")
399
+ ]
400
+ ```
401
+
402
+ Attach local files directly with
403
+ [`LLM::Agent#ask`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#ask-instance_method):
404
+
405
+ ```ruby
406
+ agent = LLM::Agent.new(llm)
407
+ puts agent.ask("Summarize this document.", with: "README.md").content
408
+ ```
409
+
410
+ ## Tracing
411
+
412
+ #### Logger
413
+
414
+ Attach a tracer at the provider level to log requests and tool calls:
415
+
416
+ ```ruby
417
+ llm.tracer = LLM::Tracer::Logger.new(llm, io: $stdout)
418
+ agent = LLM::Agent.new(llm)
419
+ agent.talk("Hello")
420
+ ```
421
+
422
+ ## Applications
423
+
424
+ #### SSH
425
+
426
+ The llm.rb runtime powers small terminal applications that you can try over
427
+ SSH right now.
428
+
429
+ | Application | Try it | Runtime |
430
+ |---|---|---|
431
+ | [matz](https://r.uby.dev/matz/) | `ssh matz@r.uby.dev` | [mruby-llm](https://r.uby.dev/mruby-llm/) |
432
+ | [robert](https://4.4bsd.dev/robert) | `ssh robert@4.4bsd.dev` | [mruby-llm](https://r.uby.dev/mruby-llm/) |
metadata CHANGED
@@ -1,13 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: llm.rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 11.3.0
4
+ version: 11.3.1
5
5
  platform: ruby
6
6
  authors:
7
- - 0x1eef (Robert)
7
+ - Robert (0x1eef)
8
8
  - Antar Azri
9
9
  - Rodrigo Serrano
10
- - Christos Maris
11
10
  bindir: bin
12
11
  cert_chain: []
13
12
  date: 1980-01-02 00:00:00.000000000 Z
@@ -41,19 +40,19 @@ dependencies:
41
40
  - !ruby/object:Gem::Version
42
41
  version: 0.9.37
43
42
  - !ruby/object:Gem::Dependency
44
- name: kramdown
43
+ name: redcarpet
45
44
  requirement: !ruby/object:Gem::Requirement
46
45
  requirements:
47
46
  - - "~>"
48
47
  - !ruby/object:Gem::Version
49
- version: '2.4'
48
+ version: '3.6'
50
49
  type: :development
51
50
  prerelease: false
52
51
  version_requirements: !ruby/object:Gem::Requirement
53
52
  requirements:
54
53
  - - "~>"
55
54
  - !ruby/object:Gem::Version
56
- version: '2.4'
55
+ version: '3.6'
57
56
  - !ruby/object:Gem::Dependency
58
57
  name: webrick
59
58
  requirement: !ruby/object:Gem::Requirement
@@ -279,7 +278,7 @@ dependencies:
279
278
  - !ruby/object:Gem::Version
280
279
  version: '1.18'
281
280
  description: |
282
- llm.rb is Ruby's most capable AI runtime.
281
+ llm.rb is Ruby's capable AI runtime.
283
282
 
284
283
  It runs on Ruby's standard library by default. loads optional pieces
285
284
  only when needed, and offers a single runtime for providers, agents,
@@ -293,7 +292,7 @@ description: |
293
292
  tool execution through threads, tasks (via async gem), fibers, ractors,
294
293
  and fork (via xchan.rb gem).
295
294
  email:
296
- - robert@4.4bsd.dev
295
+ - robert@r.uby.dev
297
296
  executables: []
298
297
  extensions: []
299
298
  extra_rdoc_files: []
@@ -512,14 +511,15 @@ files:
512
511
  - lib/sequel/plugins/agent.rb
513
512
  - lib/sequel/plugins/llm.rb
514
513
  - llm.gemspec
515
- homepage: https://llmrb.github.io
514
+ - resources/deepdive.md
515
+ homepage: https://r.uby.dev/llm/
516
516
  licenses:
517
517
  - 0BSD
518
518
  metadata:
519
- homepage_uri: https://llmrb.github.io
520
- source_code_uri: https://github.com/llmrb/llm.rb
521
- documentation_uri: https://llmrb.github.io/llm.rb
522
- changelog_uri: https://0x1eef.github.io/x/llm.rb/file.CHANGELOG.html
519
+ homepage_uri: https://r.uby.dev/llm/
520
+ source_code_uri: https://github.com/r-uby-dev/llm.rb
521
+ documentation_uri: https://r.uby.dev/llm/
522
+ changelog_uri: https://r.uby.dev/api-docs/llm.rb/file.CHANGELOG.html
523
523
  rdoc_options: []
524
524
  require_paths:
525
525
  - lib
@@ -536,5 +536,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
536
536
  requirements: []
537
537
  rubygems_version: 4.0.6
538
538
  specification_version: 4
539
- summary: Ruby's most capable AI runtime
539
+ summary: Ruby's capable AI runtime
540
540
  test_files: []