llm.rb 12.6.0 → 13.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.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +571 -13
  3. data/LICENSE +21 -93
  4. data/README.md +183 -167
  5. data/bin/llm.rb +124 -0
  6. data/data/deepinfra.json +3 -0
  7. data/data/xai.json +1 -1
  8. data/lib/llm/a2a.rb +1 -1
  9. data/lib/llm/active_record/acts_as_llm.rb +6 -6
  10. data/lib/llm/agent.rb +136 -27
  11. data/lib/llm/buffer.rb +85 -3
  12. data/lib/llm/compactor/null.rb +19 -0
  13. data/lib/llm/compactor/truncate.rb +80 -0
  14. data/lib/llm/compactor.rb +42 -124
  15. data/lib/llm/context.rb +31 -37
  16. data/lib/llm/contract.rb +4 -25
  17. data/lib/llm/function/array.rb +18 -17
  18. data/lib/llm/function/async/group.rb +54 -0
  19. data/lib/llm/function/async/reactor.rb +48 -0
  20. data/lib/llm/function/async/task.rb +83 -0
  21. data/lib/llm/function/fiber/group.rb +46 -0
  22. data/lib/llm/function/fiber/task.rb +62 -0
  23. data/lib/llm/function/{fork_group.rb → fork/group.rb} +14 -5
  24. data/lib/llm/function/fork/job.rb +2 -2
  25. data/lib/llm/function/fork/task.rb +19 -10
  26. data/lib/llm/function/group.rb +40 -0
  27. data/lib/llm/function/{ractor_group.rb → ractor/group.rb} +13 -5
  28. data/lib/llm/function/ractor/job.rb +9 -3
  29. data/lib/llm/function/ractor/mailbox.rb +2 -0
  30. data/lib/llm/function/ractor/task.rb +23 -15
  31. data/lib/llm/function/{call_group.rb → sequential/group.rb} +12 -8
  32. data/lib/llm/function/sequential/task.rb +49 -0
  33. data/lib/llm/function/task.rb +25 -48
  34. data/lib/llm/function/thread/group.rb +46 -0
  35. data/lib/llm/function/thread/task.rb +60 -0
  36. data/lib/llm/function.rb +54 -65
  37. data/lib/llm/loop_guard.rb +1 -2
  38. data/lib/llm/mcp.rb +22 -0
  39. data/lib/llm/object.rb +2 -1
  40. data/lib/llm/provider.rb +6 -3
  41. data/lib/llm/providers/anthropic.rb +1 -1
  42. data/lib/llm/providers/bedrock/request_adapter.rb +1 -1
  43. data/lib/llm/providers/google.rb +2 -2
  44. data/lib/llm/providers/mistral.rb +1 -1
  45. data/lib/llm/providers/ollama.rb +1 -1
  46. data/lib/llm/providers/openai/responses.rb +1 -1
  47. data/lib/llm/providers/openai.rb +1 -1
  48. data/lib/llm/repl/{transcript.rb → buffer.rb} +34 -21
  49. data/lib/llm/repl/command.rb +47 -13
  50. data/lib/llm/repl/commands/compact.rb +33 -0
  51. data/lib/llm/repl/commands/help.rb +3 -5
  52. data/lib/llm/repl/input.rb +80 -15
  53. data/lib/llm/repl/markdown/table.rb +80 -0
  54. data/lib/llm/repl/markdown.rb +33 -3
  55. data/lib/llm/repl/node.rb +37 -0
  56. data/lib/llm/repl/status.rb +4 -4
  57. data/lib/llm/repl/stream.rb +12 -5
  58. data/lib/llm/repl/walker.rb +46 -0
  59. data/lib/llm/repl/window.rb +31 -32
  60. data/lib/llm/repl.rb +70 -38
  61. data/lib/llm/response.rb +10 -0
  62. data/lib/llm/schema/leaf.rb +5 -0
  63. data/lib/llm/schema/object.rb +11 -5
  64. data/lib/llm/sequel/plugin.rb +6 -6
  65. data/lib/llm/skill.rb +20 -4
  66. data/lib/llm/stream.rb +24 -17
  67. data/lib/llm/tool.rb +20 -4
  68. data/lib/llm/tools/chdir.rb +0 -2
  69. data/lib/llm/tools/{swap_text.rb → edit-file.rb} +3 -3
  70. data/lib/llm/tools/git.rb +11 -4
  71. data/lib/llm/tools/mkdir.rb +4 -1
  72. data/lib/llm/tools/pwd.rb +0 -2
  73. data/lib/llm/tools/read_file.rb +0 -2
  74. data/lib/llm/tools/rg.rb +11 -4
  75. data/lib/llm/tools/ruby.rb +46 -0
  76. data/lib/llm/tools/shell.rb +11 -4
  77. data/lib/llm/tools/utils.rb +31 -0
  78. data/lib/llm/tracer/pretty_logger.rb +127 -0
  79. data/lib/llm/tracer.rb +1 -0
  80. data/lib/llm/version.rb +1 -1
  81. data/lib/llm.rb +25 -5
  82. data/llm.gemspec +11 -5
  83. data/resources/deepdive.md +45 -1198
  84. metadata +39 -17
  85. data/lib/llm/function/call_task.rb +0 -46
  86. data/lib/llm/function/fiber_group.rb +0 -105
  87. data/lib/llm/function/task_group.rb +0 -97
  88. data/lib/llm/function/thread_group.rb +0 -102
@@ -14,1220 +14,67 @@
14
14
 
15
15
  ## Welcome
16
16
 
17
- Welcome to the llm.rb deepdive. You are reading this document
18
- in the markdown format. An optimized version exists
19
- at [https://r.uby.dev/llm/deepdive](https://r.uby.dev/llm/deepdive)
20
- and it is both easier to read and navigate.
21
-
22
- This document is a continuation of the [homepage documentation](https://r.uby.dev/llm).
23
- It assumes you are familiar with the basics already, and focuses on
24
- features that didn't make it into the homepage documentation.
25
-
26
- ## Table of contents
27
-
28
- **Overview**
29
-
30
- - [Welcome](#welcome)
31
-
32
- **Core**
33
-
34
- <details>
35
- <summary>Agents</summary>
36
-
37
- - [As a subclass](#as-a-subclass)
38
- - [As an object](#as-an-object)
39
- </details>
40
-
41
- <details>
42
- <summary>Tools</summary>
43
-
44
- - [LLM::Tool](#llmtool)
45
- - [Errors](#errors)
46
- </details>
47
-
48
- <details>
49
- <summary>Skills</summary>
50
-
51
- - [SKILL.md](#skillmd)
52
- - [Run it](#run-it)
53
- </details>
54
-
55
- <details>
56
- <summary>Schema</summary>
57
-
58
- - [Estimation](#estimation)
59
- </details>
60
-
61
- **Runtime**
62
-
63
- <details>
64
- <summary>Stream</summary>
65
-
66
- - [IO-like object](#io-like-object)
67
- - [LLM::Stream](#llmstream)
68
- </details>
69
-
70
- <details>
71
- <summary>Cancellation</summary>
72
-
73
- - [Cancel a request](#cancel-a-request)
74
- - [Tool interrupts](#tool-interrupts)
75
- </details>
76
-
77
- <details>
78
- <summary>Transports</summary>
79
-
80
- - [net/http](#nethttp)
81
- - [net/http/persistent](#nethttppersistent)
82
- - [curb](#curb)
83
- </details>
84
-
85
- <details>
86
- <summary>Tracer</summary>
87
-
88
- - [Provider-wide tracer](#provider-wide-tracer)
89
- - [Agent-local tracer](#agent-local-tracer)
90
- </details>
91
-
92
- <details>
93
- <summary>REPL</summary>
94
-
95
- - [LLM::Agent](#llmagent)
96
- - [State](#state)
97
- - [Tools](#tools)
98
- - [Skills](#skills-1)
99
- - [Tracer](#tracer-1)
100
- - [Input](#input)
101
- - [Commands](#commands)
102
- </details>
103
-
104
- **Persistence**
105
-
106
- <details>
107
- <summary>Serialization</summary>
108
-
109
- - [Save to disk](#save-to-disk)
110
- </details>
111
-
112
- <details>
113
- <summary>ORM</summary>
114
-
115
- - [ActiveRecord](#activerecord)
116
- - [Sequel](#sequel)
117
- </details>
118
-
119
- **Media**
120
-
121
- <details>
122
- <summary>Images</summary>
123
-
124
- - [Generation](#generation)
125
- - [Edits](#edits)
126
- - [DeepSeek](#deepseek)
127
- </details>
128
-
129
- <details>
130
- <summary>Audio</summary>
131
-
132
- - [text-to-speech](#text-to-speech)
133
- - [speech-to-text](#speech-to-text)
134
- - [translation](#translation)
135
- </details>
136
-
137
- **Protocols**
138
-
139
- <details>
140
- <summary>MCP</summary>
141
-
142
- - [stdio](#stdio)
143
- - [http](#http)
144
- </details>
145
-
146
- <details>
147
- <summary>A2A</summary>
148
-
149
- - [rest](#rest)
150
- - [jsonrpc](#jsonrpc)
151
- </details>
152
-
153
- ## Agents
154
-
155
- An agent is represented by the
156
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
157
- class, and it is built on top of
158
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html) -
159
- the heart of the runtime. An agent manages the tool loop automatically,
160
- implements a tool loop guard for misbehaving models, and
161
- it can use five different concurrency strategies to execute
162
- tools.
163
-
164
- An agent can be a subclass of
165
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html),
166
- or a direct
167
- instance of it. The subclass approach is useful when you
168
- want reusable agents that can attach behavior (as methods)
169
- to their own class.
170
-
171
- #### As a subclass
172
-
173
- A subclass of
174
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
175
- can define its model, tools,
176
- and other attributes at the class-level. All of these
177
- attributes are optional, and they act as defaults that
178
- can be overriden on the instance level.
179
-
180
- The example uses the `:fork` concurrency model. It has
181
- two primary benefits: tools are run in parallel, and in
182
- a separate process with a separate memory address space.
183
-
184
- The example purposefully demonstrates how the attributes
185
- can be lazily defined with a block, or a Symbol that is
186
- evaluated as an instance method on the subclass. It is
187
- not strictly neccessary, though, and the example would
188
- be simpler without it.
189
-
190
- ```ruby
191
- class Agent < LLM::Agent
192
- set model: "deepseek-v4-pro",
193
- tools: [DoResearch, FinalizeResearch, ActOnResearch],
194
- stream: -> { $stdout },
195
- tracer: :set_tracer,
196
- concurrency: :fork
197
-
198
- def research!
199
- talk "start the research"
200
- end
201
-
202
- private
203
-
204
- def set_tracer
205
- LLM::Tracer::Logger.new(llm, io: $stderr)
206
- end
207
- end
208
- llm = LLM.deepseek(key: ENV["KEY"])
209
- agent = Agent.new(llm).tap(&:research!)
210
- agent.talk "How did the research go?"
211
- ```
212
-
213
- #### As an object
214
-
215
- The more direct, and sometimes more convienent approach, is to
216
- create an instance of
217
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
218
- directly. The same attributes can be provided as the
219
- second argument given to
220
- [`LLM::Agent.new`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html),
221
- and the same lazy evaluation rules apply. This approach can be
222
- great for prototyping quickly, and you can always turn to a
223
- subclass later if that makes more sense.
224
-
225
- ```ruby
226
- llm = LLM.deepseek(key: ENV["KEY"])
227
- agent = LLM::Agent.new(llm, stream: $stdout)
228
- agent.talk "Hello, fellow agent"
229
- ```
230
-
231
- [Back to top](#table-of-contents)
232
-
233
- ## Tools
234
-
235
- A tool extends the capabilities of a model. <br>
236
- A tool is a subclass of
237
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
238
- that has a name,
239
- a description, and an optional set of typed parameters.
240
-
241
- A tool also has a method associated with it, and when the
242
- model calls a tool it will do so through this method &ndash;
243
- alongside any parameters the tool might have defined.
244
-
245
- In other words, a tool provides a way for a model to
246
- call a method you have written, and it returns a value
247
- to the model that is considered the tool's response.
248
- The model then proceeds to process the tool's response,
249
- and then might generate its own response, or perhaps call
250
- another tool.
251
-
252
- #### LLM::Tool
253
-
254
- A tool can be defined by subclassing
255
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
256
- with
257
- a name, description, and optional set of parameters. The
258
- tool name, and description should be informative so the
259
- model can understand what the tool does and how it can
260
- serve a user's query.
261
-
262
- ```ruby
263
- require "llm"
264
- require "shellwords"
265
-
266
- class Shell < LLM::Shell
267
- name "shell"
268
- description "execute a shell command"
269
- parameter :name, String, "the command's name"
270
- parameter :arguments, Array[String], "One or more arguments"
271
- required %i[name]
272
- defaults arguments: []
273
-
274
- def call(name:, arguments:)
275
- out = `#{name.shellscape} #{arguments.map(&:shellescape).join(" ")}`
276
- {ok: $?.success?, out:}
277
- end
278
- end
17
+ ### Introduction
279
18
 
280
- llm = LLM.deepseek(key: ENV["KEY"])
281
- agent = LLM::Agent.new(llm, tools: [Shell], stream: $stdout)
282
- agent.talk "What files are in the current working directory?"
283
- ```
19
+ #### Overview
284
20
 
285
- #### Errors
21
+ Welcome to the llm.rb deepdive. This document is a continuation
22
+ of the [homepage documentation](https://r.uby.dev/llm). It assumes
23
+ you are familiar with the basics already, and focuses on features
24
+ that didn't make it into the homepage documentation.
286
25
 
287
- Exceptions that might be raised by a tool are automatically
288
- rescued and returned to the model as a structured error.
289
- Otherwise &ndash; the conversation's history could be left
290
- in an invalid state.
291
-
292
- That's because a tool call must complete with a tool response,
293
- that's the only valid response a model expects, so even in the
294
- case of an error, something must be returned that communicates
295
- what happened.
296
-
297
- ```ruby
298
- class Error < LLM::Tool
299
- name "error"
300
- description "demo how errors are handled"
26
+ An optimized version exists
27
+ at [https://r.uby.dev/llm/deepdive](https://r.uby.dev/llm/deepdive)
28
+ that is both easier to read and navigate.
301
29
 
302
- ##
303
- # Returns
304
- # {error: true, kind: "RuntimeError", message: "boom"}
305
- def call
306
- raise "boom"
307
- end
308
- end
309
- ```
30
+ #### How it works
310
31
 
311
- ## Skills
32
+ Each topic file follows a consistent four-part pattern:
33
+ `#### Overview` introduces the concept, `#### How it works` shows
34
+ code, `#### Why would I use it?` explains the use case, and
35
+ `#### Notes` covers caveats and edge cases.
312
36
 
313
- The skill concept is borrowed from tools like Claude and
314
- Codex, but llm.rb gives it a runtime of its own. A skill
315
- is a directory with a `SKILL.md` file. That file contains
316
- frontmatter where the skill's name, description, and tools
317
- can be declared.
37
+ #### Why would I use it?
318
38
 
319
- #### SKILL.md
39
+ The deepdive documents everything the homepage leaves out:
40
+ advanced patterns, configuration options, ORM integrations,
41
+ protocol support, and edge cases. Read it when you need to go
42
+ beyond the basics.
320
43
 
321
- The `SKILL.md` file can look like this. When a skill runs,
322
- the runtime spawns a subagent with its own context window
323
- and message history. Some context is inherited from the
324
- parent agent, though.
44
+ #### Notes
325
45
 
326
- By default the subagent can only access the tools declared
327
- by the skill. The `inherit` directive lets it inherit the
328
- parent agent's tools instead, including A2A and MCP tools.
46
+ The deepdive is a living document. Sections are added as new
47
+ features land. The [homepage](https://r.uby.dev/llm) is the best
48
+ place to start if you are new to llm.rb.
329
49
 
330
- ```markdown
331
- ---
332
- name: git-skill
333
- description: reads my git history and writes a summary
334
- tools: ['git-log', 'git-show', 'write-file']
335
50
  ---
336
51
 
337
- ## Task
338
-
339
- Collect a log of recent history.
340
- Analyze each commit.
341
- Write a summary to summary.txt
342
- ```
343
-
344
- #### Run it
345
-
346
- Given the skill above, llm.rb only needs the path to the
347
- directory that contains `SKILL.md`. Under the hood, a skill
348
- is represented as a tool the model can call. That means
349
- a skill can be called whenever it satisfies the user's
350
- request &ndash; in the same way that a regular tool can.
351
-
352
- This feature also works with both the ActiveRecord, and
353
- Sequel integrations.
354
-
355
- ```ruby
356
- require "llm"
357
-
358
- llm = LLM.deepseek(key: ENV["KEY"])
359
- agent = LLM::Agent.new(llm, skills: [__dir__])
360
- agent.talk "run the git skill"
361
- ```
362
-
363
- [Back to top](#table-of-contents)
364
-
365
- ## MCP
366
-
367
- #### stdio
368
-
369
- The stdio transport connects to an MCP server that is launched as a
370
- separate process, and both its standard input and standard output
371
- streams are used for communication. It is recommended but not
372
- required to execute commands for a stdio transport over a
373
- persistent session via the
374
- [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session-instance_method)
375
- method &ndash; otherwise
376
- you could end up launching the same process multiple times.
377
-
378
- ```ruby
379
- require "llm"
380
-
381
- llm = LLM.deepseek(key: ENV["KEY"])
382
- mcp = LLM::MCP.stdio(argv: ["npx", "-y", "@forgejo/mcp-server"])
383
- agent = LLM::Agent.new(llm)
384
-
385
- mcp.session do
386
- agent.talk "What's happening on forgejo?", tools: mcp.tools
387
- end
388
- ```
389
-
390
- #### http
391
-
392
- The http transport connects to an MCP server over HTTP, and unlike
393
- the stdio transport, the MCP server does not have to be running
394
- locally. Popular services like GitHub provide their own MCP server
395
- over HTTP, and it is one of the most capable MCP servers I have
396
- used.
397
-
398
- Unlike the stdio transport,
399
- [`LLM::MCP#session`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html#session-instance_method)
400
- carries little benefit for the http transport and it can be
401
- omitted. It is recommended to consider the `net_http_persistent`
402
- transport for MCP interactions that run over HTTP, otherwise
403
- you could end up tearing down and setting up the same connection
404
- multiple times.
405
-
406
- ```ruby
407
- require "llm"
408
-
409
- llm = LLM.deepseek(key: ENV["KEY"])
410
- mcp = LLM::MCP.http(
411
- url: "https://api.githubcopilot.com/mcp/",
412
- headers: {
413
- "Authorization" => "Bearer #{ENV.fetch('GITHUB_PAT')}"
414
- },
415
- transport: :net_http_persistent
416
- )
417
- agent = LLM::Agent.new(llm)
418
- agent.talk "What's happening on GitHub?", tools: mcp.tools
419
- ```
420
-
421
- [Back to top](#table-of-contents)
422
-
423
- ## A2A
424
-
425
- #### rest
426
-
427
- The rest transport communicates with other agents via A2A
428
- endpoints that speak both HTTP and JSON. The skills advertised
429
- by an agent become subclasses of
430
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
431
- that can be used by both
432
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html),
433
- and [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
434
- &ndash; similar to how MCP tools become subclasses of
435
- [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html).
436
-
437
- ```ruby
438
- require "llm"
439
-
440
- llm = LLM.deepseek(key: ENV["KEY"])
441
- a2a = LLM::A2A.rest(url: "https://agent.example.com")
442
- agent = LLM::Agent.new(llm, tools: a2a.skills)
443
- agent.talk "What's happening, fellow agent?"
444
- ```
445
-
446
- #### jsonrpc
447
-
448
- The jsonrpc transport communicates with other agents via HTTP
449
- and a protocol known as jsonrpc. Sometimes an agent will
450
- implement both, or just one of each. An agent's card, which
451
- is represented by an instance of
452
- [`LLM::A2A::Card`](https://r.uby.dev/api-docs/llm.rb/LLM/A2A/Card.html),
453
- can be
454
- used to discover available transports via the
455
- [`LLM::A2A::Card#interfaces`](https://r.uby.dev/api-docs/llm.rb/LLM/A2A/Card.html#interfaces-instance_method)
456
- method.
457
-
458
- ```ruby
459
- require "llm"
460
- llm = LLM.deepseek(key: ENV["KEY"])
461
- a2a = LLM::A2A.jsonrpc(url: "https://agent.example.com")
462
- agent = LLM::Agent.new(llm, tools: a2a.skills)
463
- agent.talk "What's happening, fellow agent?"
464
- ```
465
-
466
- [Back to top](#table-of-contents)
467
-
468
- ## Transports
469
-
470
- The [`LLM::Provider`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html),
471
- [`LLM::MCP`](https://r.uby.dev/api-docs/llm.rb/LLM/MCP.html), and
472
- [`LLM::A2A`](https://r.uby.dev/api-docs/llm.rb/LLM/A2A.html) classes
473
- all accept a `transport` option that decides which library
474
- will be used for HTTP communication. There are three options out
475
- of the box:
476
- [`net-http`](https://github.com/ruby/net-http),
477
- [`net-http-persistent`](https://github.com/drbrain/net-http-persistent),
478
- and [`curb`](https://github.com/taf2/curb).
479
-
480
- #### net/http
481
-
482
- The [`net/http`](https://github.com/ruby/net-http) transport is represented by the symbol `:net_http`. <br>
483
- It is the default transport.
484
-
485
- ```ruby
486
- require "llm"
487
-
488
- llm = LLM.deepseek(key: "...", transport: :net_http)
489
- mcp = LLM::MCP.http(url: "...", transport: :net_http)
490
- a2a = LLM::A2A.rest(url: "...", transport: :net_http)
491
- ```
492
-
493
- #### net/http/persistent
494
-
495
- The [`net/http/persistent`](https://github.com/drbrain/net-http-persistent) transport is represented by the symbol `:net_http_persistent`. <br>
496
- It maintains a connection pool so the cost of tearing down and
497
- setting up a connection repeatedly is kept low, and it is built
498
- on top of [`net/http`](https://github.com/ruby/net-http).
499
-
500
- ```ruby
501
- require "llm"
502
-
503
- llm = LLM.deepseek(key: "...", transport: :net_http_persistent)
504
- mcp = LLM::MCP.http(url: "...", transport: :net_http_persistent)
505
- a2a = LLM::A2A.rest(url: "...", transport: :net_http_persistent)
506
- ```
507
-
508
- #### curb
509
-
510
- The [`curb`](https://github.com/taf2/curb) transport is represented by the symbol `:curb`. <br>
511
- It provides bindings for libcurl &ndash; a widely used, highly portable
512
- and feature-rich HTTP library written in C.
513
-
514
- ```ruby
515
- require "llm"
516
-
517
- llm = LLM.deepseek(key: "...", transport: :curb)
518
- mcp = LLM::MCP.http(url: "...", transport: :curb)
519
- a2a = LLM::A2A.rest(url: "...", transport: :curb)
520
- ```
521
-
522
- [Back to top](#table-of-contents)
523
-
524
- ## Stream
525
-
526
- #### IO-like object
527
-
528
- Any object that implements the `#<<` method can receive
529
- chunks from a stream. That includes objects like `$stdout`.
530
- This form of streaming is simple and limited. It is the
531
- equivalent of
532
- [`LLM::Stream#on_content`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html#on_content-instance_method),
533
- and doesn't include
534
- any of the other
535
- [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
536
- hooks.
537
-
538
- ```ruby
539
- require "llm"
540
-
541
- llm = LLM.deepseek(key: ENV["KEY"])
542
- agent = LLM::Agent.new(llm, stream: $stdout)
543
- agent.talk "hello world"
544
- ```
545
-
546
- #### LLM::Stream
547
-
548
- The [`LLM::Stream`](https://r.uby.dev/api-docs/llm.rb/LLM/Stream.html)
549
- class provides many hooks that a subclass
550
- can implement. They range from being notified when a tool call
551
- starts to when a tool call finishes, or when a conversation is
552
- due to be compacted because the context window exceeded a defined
553
- limit. All these callbacks support a responsive user interface
554
- where the user is always aware of what is happening behind the
555
- scenes.
556
-
557
- ```ruby
558
- class Stream < LLM::Stream
559
- def on_content(content)
560
- puts content
561
- end
562
-
563
- def on_reasoning_content(content)
564
- puts content
565
- end
566
-
567
- def on_tool_call(tool)
568
- # this callback can be used to either log a tool call,
569
- # or execute a tool call during a stream.
570
- end
571
-
572
- def on_tool_return(tool, result)
573
- end
574
-
575
- def on_compaction(ctx, compactor)
576
- # this callback is called *before* a compact happens
577
- end
578
-
579
- def on_compaction_finish(ctx, compactor)
580
- # this callback is called *after* a compact happens
581
- end
582
- end
583
- ```
584
-
585
- [Back to top](#table-of-contents)
586
-
587
- ## Serialization
588
-
589
- The [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
590
- class can be serialized to JSON and stored in a string or on disk.
591
- That is powerful because a context contains runtime state that can
592
- be restored later, in a different process or even on a different
593
- machine. And because an agent is implemented on top of
594
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html)
595
- this feature works for [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html),
596
- too.
597
-
598
- #### Save to disk
599
-
600
- The runtime can serialize its state to a string, a text file, or
601
- a database column. The option that fits best depends on your application
602
- and environment. Web applications might be more interested in the [ORM](#orm)
603
- feature, which is built on top of the serialization feature.
604
-
605
- ```ruby
606
- ##
607
- # Create a provider
608
- llm = LLM.deepseek(key: ENV["KEY"])
609
-
610
- ##
611
- # Save agent
612
- agent1 = LLM::Agent.new(llm)
613
- agent1.talk "remember my name is robert"
614
- agent1.save(path: "agent.json")
615
-
616
- ##
617
- # Restore agent
618
- agent2 = LLM::Agent.new(llm, stream: $stdout)
619
- agent2.restore(path: "agent.json")
620
- agent2.talk "what's my name?"
621
- ```
622
-
623
- ## ORM
624
-
625
- Both ActiveRecord, and Sequel have first-class support on the
626
- llm.rb runtime. In both cases an ActiveRecord or Sequel model
627
- can be turned into a model that has the same capabilities as
628
- [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html),
629
- or [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
630
-
631
- The main difference is that the runtime persists directly into
632
- the database with no requirements beyond a single column on a
633
- single row. That means it is usually trivial to turn an existing
634
- model into an AI-aware model.
635
-
636
- #### ActiveRecord
637
-
638
- The ActiveRecord interface for
639
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)
640
- is
641
- [`acts_as_agent`](https://r.uby.dev/api-docs/llm.rb/LLM/ActiveRecord/ActsAsAgent.html).
642
- It yields an instance of
643
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html),
644
- and that can be used
645
- to configure the agent (eg which model, instructions, skills,
646
- tools, etc).
647
-
648
- An interesting option is the `format` option, by default it
649
- defaults to `:string` but it can also be changed to `:json`
650
- or `:jsonb` depending on the configuration and type of underlying
651
- column. The JSONB column type is recommended.
652
-
653
- ```ruby
654
- require "active_record"
655
- require "llm"
656
- require "llm/active_record"
657
-
658
- class Agent < ApplicationRecord
659
- acts_as_agent(format: :jsonb) do |agent|
660
- agent.model "deepseek-v4-pro"
661
- agent.instructions "solve the user's query"
662
- agent.tools [Research, FinalizeResearch, ActOnResearch]
663
- end
664
-
665
- private
666
-
667
- ##
668
- # By convention, this method defines the provider
669
- # for a model. If neccessary, it can be renamed and
670
- # configured via `provider: :your_method` instead.
671
- def set_provider
672
- LLM.deepseek(key: ENV["KEY"])
673
- end
674
-
675
- ##
676
- # By convention, this method should return what is
677
- # given as the second argument to `LLM::Context` or
678
- # `LLM::Agent`.
679
- #
680
- # Often, there is no need to set it, so it can be left
681
- # undefined or it can be reassigned in the same way as
682
- # `set_provider`. For example: `context: :your_method`
683
- def set_context
684
- {}
685
- end
686
- end
687
-
688
- agent = Agent.create!
689
- agent.talk "perform research"
690
- ```
691
-
692
- #### Sequel
693
-
694
- The following is a Sequel equivalent to the ActiveRecord example,
695
- but to keep it interesting and informative, this example also
696
- configures a per-model tracer that logs to `$stdout`. Works the
697
- same for ActiveRecord.
698
-
699
- ```ruby
700
- require "sequel"
701
- require "llm"
702
- require "llm/sequel/plugin"
703
-
704
- class Agent < Sequel::Model
705
- plugin(:agent, format: :jsonb) do |agent|
706
- agent.model "deepseek-v4-pro"
707
- agent.instructions "solve the user's query"
708
- agent.tools [Research, FinalizeResearch, ActOnResearch]
709
- agent.tracer { LLM::Tracer::Logger.new(llm, io: $stdout) }
710
- end
711
-
712
- private
713
-
714
- def set_provider
715
- LLM.deepseek(key: ENV["KEY"])
716
- end
717
- end
718
-
719
- agent = Agent.create
720
- agent.talk "perform research"
721
- ```
722
-
723
- [Back to top](#table-of-contents)
724
-
725
- ## Schema
726
-
727
- The [`LLM::Schema`](https://r.uby.dev/api-docs/llm.rb/LLM/Schema.html)
728
- class can be subclassed to describe
729
- the shape of a JSON object or objects that you expect
730
- the model to respond with.
731
-
732
- It can be useful for a wide range of use cases but the
733
- most popular might be classification, data extraction,
734
- and transferring structured data between different software
735
- rather than blobs of text that a machine cannot easily parse
736
- in a structured way.
737
-
738
- #### Estimation
739
-
740
- The following example asks the model to estimate the age
741
- of a person in a photo. The model provides a structured response
742
- that's represented by an instance of
743
- [`LLM::Object`](https://r.uby.dev/api-docs/llm.rb/LLM/Object.html).
744
-
745
- The object returned by
746
- [`LLM::Response#content!`](https://r.uby.dev/api-docs/llm.rb/LLM/Contract/Completion.html#content!-instance_method)
747
- has methods that can access the age, confidence, and comments
748
- properties.
749
- This approach can also work for extracting data or an analysis
750
- from a PDF, and other file types.
751
-
752
- ```ruby
753
- require "llm"
754
- require "pp"
755
-
756
- class Estimation < LLM::Schema
757
- property :age, Integer, "The estimated age of the person"
758
- property :confidence, Number, "Your confidence in the estimate"
759
- property :applicable, Boolean, "True when the photo contains a person"
760
- property :comments, String, "Any additional comments or input"
761
- required %i[age confidence applicable comments]
762
- end
763
-
764
- llm = LLM.openai(key: ENV["KEY"])
765
- agent = LLM::Agent.new(llm, schema: Estimation)
766
- res = agent.ask "Given this photo, provide an age estimate", with: "photo.jpg"
767
-
768
- ##
769
- # Coerces the model's response from a JSON string
770
- # to an instance of LLM::Object.
771
- estimate = res.content!
772
-
773
- ##
774
- # Let's print the estimate
775
- if estimate.applicable
776
- print "The person is approx ", estimate.age.to_s, " years old", "\n"
777
- print "I have a confidence rating of ", estimate.confidence.to_s, "\n"
778
- else
779
- print "This photo is not applicable:", "\n"
780
- print estimate.comments
781
- end
782
- ```
783
-
784
- [Back to top](#table-of-contents)
785
-
786
- ## Cancellation
787
-
788
- #### Cancel a request
789
-
790
- A common scenario when communicating with a model is to
791
- want to cancel the request mid-stream. This could be done
792
- for a number of different reasons, most often because the
793
- user made a mistake, or the model is making a mistake and
794
- the user wants to cancel the action.
795
-
796
- The runtime has built-in support for cancellation. So for
797
- example it is possible to cancel a request on the main
798
- thread from a secondary thread. A number of things happen
799
- when a request is cancelled. First the request is cancelled
800
- at the transport level, and each transport handles it a little
801
- differently. The net effect in every case is that the connection
802
- is closed.
803
-
804
- The runtime then notifies the rest of the system. so for example,
805
- if a tool was running, it will receive the `on_interrupt` / `on_cancel`
806
- callback that lets the tool do any necessary cleanup, or execute its own
807
- cancellation plan. Tools that were pending (not yet run but requetsed to
808
- run) are cancelled through
809
- [`LLM::Function#cancel`](https://r.uby.dev/api-docs/llm.rb/LLM/Function.html#cancel-instance_method).
810
-
811
- ```ruby
812
- require "llm"
813
-
814
- llm = LLM.deepseek(key: ENV["DEEPSEEK_SECRET"])
815
- agent = LLM::Agent.new(llm)
816
- queue = Queue.new
817
-
818
- Thread.new do
819
- queue.push(nil)
820
- sleep(2)
821
- agent.cancel!
822
- end
823
-
824
- begin
825
- queue.pop
826
- agent.talk "write me a very long poem", stream: $stdout
827
- rescue LLM::Interrupt
828
- puts "request cancelled!"
829
- end
830
- ```
831
-
832
- #### Tool interrupts
833
-
834
- When a running tool is interrupted &ndash; for example the user presses
835
- ESC in the [REPL](#repl) &ndash; the runtime raises
836
- [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
837
- on the tool's execution context. This behavior is uniform across
838
- all six concurrency strategies (`:call`, `:thread`, `:fiber`,
839
- `:task`, `:fork`, and `:ractor`).
840
-
841
- A tool has two choices:
842
-
843
- **Re-raise** `LLM::Interrupt` to cancel the entire turn. The
844
- exception propagates out of the tool loop and the request is
845
- aborted. This is the default when you don't rescue the exception.
846
-
847
- ```ruby
848
- def call
849
- # ... do work ...
850
- rescue LLM::Interrupt
851
- cleanup
852
- raise # cancel the turn
853
- end
854
- ```
855
-
856
- **Return a value** to continue the tool loop. The model receives
857
- the result and decides what to do next, aware that the tool was
858
- interrupted.
859
-
860
- ```ruby
861
- def call
862
- # ... do work ...
863
- rescue LLM::Interrupt
864
- cleanup
865
- {ok: false, reason: "interrupted"} # continue the loop
866
- end
867
- ```
868
-
869
- The right choice depends on the situation. A hard cancel aborts
870
- the request outright &ndash; useful when continuing would produce
871
- garbage. Returning a value lets the model adapt, which can be
872
- helpful when the interrupt is temporary (e.g. a timeout).
873
-
874
- The `:ractor` strategy is cooperative by nature &ndash; the ractor
875
- must check for a cancel message at safe points in its execution.
876
- The `:fork` strategy delivers the interrupt via a signal, which
877
- interrupts blocking syscalls immediately. All other strategies
878
- raise the exception directly on the executing thread or fiber.
879
-
880
- [Back to top](#table-of-contents)
881
-
882
- ## Tracer
883
-
884
- The runtime can be observed by subclasses of
885
- [`LLM::Tracer`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html). <br>
886
- The default tracers include a tracer that can write to standard
887
- output
888
- ([`LLM::Tracer::Logger`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Logger.html)),
889
- and a generic OpenTelemetry tracer that can export spans via OTLP
890
- ([`LLM::Tracer::Telemetry`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer/Telemetry.html)).
891
-
892
- llm.rb has numerous hooks implemented throughout the runtime that
893
- [`LLM::Tracer`](https://r.uby.dev/api-docs/llm.rb/LLM/Tracer.html)
894
- subclasses can hook into, and the tracer is
895
- purposefully designed to be extensible. The scope of a trace
896
- can vary from an individual agent (an instance of
897
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html)),
898
- or for every request a provider makes (an indirect instance of
899
- [`LLM::Provider`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html)).
900
-
901
- #### Provider-wide tracer
902
-
903
- The following two examples demonstrate provider-wide tracers that
904
- cover every request made for a single provider.
905
-
906
- ```ruby
907
- ##
908
- # Provider-wide tracer
909
- # Writes to $stdout
910
- llm = LLM.deepseek(key: ENV["KEY"])
911
- llm.tracer = LLM::Tracer::Logger.new(llm, io: $stdout)
912
-
913
- ##
914
- # Provider-wide tracer
915
- # Writes to deepseek.log
916
- llm = LLM.deepseek(key: ENV["KEY"])
917
- llm.tracer = LLM::Tracer::Logger.new(llm, path: "deepseek.log")
918
- ```
919
-
920
- #### Agent-local tracer
921
-
922
- The next two examples demonstrate a tracer that is local
923
- to an agent.
924
-
925
- ```ruby
926
- ##
927
- # Agent-local
928
- # Writes to $stdout
929
- llm = LLM.deepseek(key: ENV["KEY"])
930
- agent = LLM::Agent.new(llm, tracer: LLM::Tracer::Logger.new(llm, io: $stdout))
931
-
932
- ##
933
- # Agent-local
934
- # Writes to deepseek-agent.log
935
- llm = LLM.deepseek(key: ENV["KEY"])
936
- agent = LLM::Agent.new(llm, tracer: LLM::Tracer::Logger.new(llm, path: "deepseek-agent.log"))
937
- ```
938
-
939
- [Back to top](#table-of-contents)
940
-
941
- ## REPL
942
-
943
- During the development and operation of agents it can often
944
- be helpful to drop into a read-eval-print loop. This gives
945
- you a way to confirm the work was successful, inspect
946
- anything that went wrong, and keep talking to the same
947
- agent while its state is still intact.
948
-
949
- The REPL is a curses-based TUI with a status line showing
950
- a context-usage bar and cost counter, a scrollable transcript
951
- that renders markdown, and a multi-line input area. The UI
952
- thread stays responsive while a second thread communicates
953
- with the model.
954
-
955
- #### LLM::Agent
956
-
957
- The [LLM::Agent#repl](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#repl-instance_method)
958
- method allows an agent to spawn a read-eval-print loop
959
- that can be useful while developing or operating agents.
960
- It can be used to debug tool calls, confirm an
961
- agent has done what was expected, or improve an agent by
962
- asking questions about what it has done up to that point.
963
-
964
- This feature requires that the [curses](https://github.com/ruby/curses)
965
- and [kramdown](https://github.com/gettalong/kramdown) libraries are
966
- installed and available to require.
967
-
968
- ```ruby
969
- require "llm"
970
-
971
- llm = LLM.deepseek(key: ENV["KEY"])
972
- agent = LLM::Agent.new(llm)
973
- agent.repl
974
- ```
975
-
976
- #### State
977
-
978
- The `path:` option accepts a file path where runtime state
979
- is read from and written to. This lets you resume a
980
- conversation across REPL sessions. When the file does not
981
- exist the agent starts fresh; when it does, the agent
982
- restores its previous state.
983
-
984
- ```ruby
985
- llm = LLM.deepseek(key: ENV["KEY"])
986
- agent = LLM::Agent.new(llm)
987
- agent.repl(path: "session.json")
988
- ```
989
-
990
- #### Tools
991
-
992
- The read-eval-print loop accepts a `tools` option that lets
993
- you attach additional tools for the duration of the session.
994
-
995
- ```ruby
996
- llm = LLM.deepseek(key: ENV["KEY"])
997
- agent = LLM::Agent.new(llm)
998
- agent.repl(tools: [Debugger])
999
- ```
1000
-
1001
- #### Skills
1002
-
1003
- The read-eval-print loop also accepts a `skills` option.
1004
- This can be useful when you want to load extra skills
1005
- without attaching them to an agent permanently.
1006
-
1007
- ```ruby
1008
- llm = LLM.deepseek(key: ENV["KEY"])
1009
- agent = LLM::Agent.new(llm)
1010
- agent.repl(skills: [__dir__])
1011
- ```
1012
-
1013
- #### Tracer
1014
-
1015
- By default the tracer is disabled for the duration of the
1016
- session. This can be configured through the
1017
- `tracer` option. Setting it to `true` will configure
1018
- the REPL to use the tracer associated with an instance
1019
- of [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html).
1020
-
1021
- ```ruby
1022
- llm = LLM.deepseek(key: ENV["KEY"])
1023
- agent = LLM::Agent.new(llm, tracer: LLM.logger(llm, path: "agent.log"))
1024
- agent.repl(tracer: true, tools: [Debugger])
1025
- ```
1026
-
1027
- #### Input
1028
-
1029
- The input area supports several keyboard shortcuts:
1030
-
1031
- | Key | Action |
1032
- |---|---|
1033
- | `Enter` | Submit the current prompt |
1034
- | `Ctrl+A` | Jump to the start of the line |
1035
- | `Ctrl+E` | Jump to the end of the line |
1036
- | `Ctrl+F` | Move the cursor forward |
1037
- | `Ctrl+K` | Erase from cursor to the end of the line |
1038
- | `Ctrl+Y` | Paste previously killed text |
1039
- | `Ctrl+D` | Delete the character at the cursor |
1040
- | `Left / Right` | Move the cursor |
1041
- | `Up / Down` | Scroll the transcript |
1042
-
1043
- When characters arrive faster than a threshold the REPL
1044
- detects that text is being pasted rather than typed. In
1045
- paste mode pressing `Enter` inserts a newline instead of
1046
- submitting, allowing multi-line prompts.
1047
-
1048
- #### Commands
1049
-
1050
- Commands are recognized by a `/` prefix on the input line.
1051
- The built-in command is `/exit`, which leaves the REPL.
1052
- The [`LLM::Repl::Command`](https://r.uby.dev/api-docs/llm.rb/LLM/Repl/Command.html)
1053
- class can be subclassed to add custom commands &ndash;
1054
- any subclass that defines a `name`, `description`, and
1055
- `call` method is automatically registered and available.
1056
-
1057
- ```ruby
1058
- class LLM::Repl::Command
1059
- class Clear < self
1060
- name "clear"
1061
- description "clears the transcript"
1062
-
1063
- def call
1064
- system("clear") || system("cls")
1065
- end
1066
- end
1067
- end
1068
- ```
1069
-
1070
- [Back to top](#table-of-contents)
1071
-
1072
- ## Images
1073
-
1074
- The OpenAI, Google, xAI, DeepInfra, and DeepSeek providers have
1075
- builtin image generation capabilities. OpenAI, xAI, and DeepInfra
1076
- also support image edits. Google only supports image generation.
1077
- DeepSeek supports generation and edits too, but only through SVG
1078
- output rather than raster image models.
1079
-
1080
- #### Generation
1081
-
1082
- The [`LLM::Provider#images`](https://r.uby.dev/api-docs/llm.rb/LLM/Provider.html#images-instance_method)
1083
- method returns an Image
1084
- object that a subset of providers implement. At the
1085
- moment Google, xAI, OpenAI, DeepInfra, and DeepSeek have image
1086
- generation capabilities. DeepSeek is the odd one out: it generates
1087
- SVG documents rather than raster images.
1088
-
1089
- ```ruby
1090
- require "llm"
1091
-
1092
- ##
1093
- # Store dogrocket.png
1094
- llm = LLM.openai(key: ENV["KEY"])
1095
- res = llm.images.create(prompt: "a dog on a rocket to the moon")
1096
- IO.copy_stream res.images[0], "dogrocket.png"
1097
- ```
1098
-
1099
- The API is the same across providers. <br>
1100
- For example &ndash; xAI:
1101
-
1102
- ```ruby
1103
- require "llm"
1104
-
1105
- ##
1106
- # Store dogrocket.png
1107
- # Same API as OpenAI
1108
- llm = LLM.xai(key: ENV["KEY"])
1109
- res = llm.images.create(prompt: "a dog on a rocket to the moon")
1110
- IO.copy_stream res.images[0], "dogrocket.png"
1111
- ```
1112
-
1113
- #### Edits
1114
-
1115
- OpenAI, xAI, and DeepInfra have the same interface for image edits. <br>
1116
- DeepSeek also supports edits, but only for SVG files. <br>
1117
- Google does not have edit image support. <br>
1118
-
1119
- ```ruby
1120
- require "llm"
1121
-
1122
- ##
1123
- # Edit self.jpg and add a mustache
1124
- # Save to mustache.png
1125
- llm = LLM.openai(key: ENV["KEY"])
1126
- res = llm.images.edit(prompt: "add a mustache", image: "self.jpg")
1127
- IO.copy_stream res.images[0], "mustache.png"
1128
- ```
1129
-
1130
- #### DeepSeek
1131
-
1132
- The DeepSeek provider does not provide an image generation model
1133
- but it is possible to ask a text-to-text model to produce
1134
- vector graphics (SVGs), and in that limited sense, it can become
1135
- a capable text-to-image model.
1136
-
1137
- ```ruby
1138
- require "llm"
1139
-
1140
- ##
1141
- # Edit rocket.svg and change its color
1142
- # Save to rocket-edited.svg
1143
- llm = LLM.deepseek(key: ENV["KEY"])
1144
- res = llm.images.edit(prompt: "make the rocket red", image: "rocket.svg")
1145
- IO.copy_stream res.images[0], "rocket-edited.svg"
1146
- ```
1147
-
1148
- An interesting property of the DeepSeek implementation is that
1149
- it can maintain a session that can perform multiple image generations
1150
- or edits rather than just one-shot generations.
1151
-
1152
- It's possible because under the hood
1153
- [`LLM::Agent`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html),
1154
- is attached to the
1155
- [`LLM::Response`](https://r.uby.dev/api-docs/llm.rb/LLM/Response.html)
1156
- object that is returned to the caller. So the response includes an
1157
- `agent` method, and it can be carried across multiple generations.
1158
- It is specific to this endpoint though. It works like this:
1159
-
1160
- ```ruby
1161
- require "llm"
1162
-
1163
- llm = LLM.deepseek(key: ENV["DEEPSEEK_SECRET"])
1164
- agent = nil
1165
- loop do
1166
- print "> "
1167
- prompt = $stdin.gets
1168
- res = llm.images.create(prompt:, agent:)
1169
- agent = res.agent
1170
- IO.copy_stream res.images[0], "image.svg"
1171
- print "ok: saved image.svg", "\n"
1172
- end
1173
- ```
1174
-
1175
- [Back to top](#table-of-contents)
1176
-
1177
- ## Audio
1178
-
1179
- The audio interface defined by llm.rb describes three methods,
1180
- although not every provider implements all of them. Generally
1181
- speaking the audio interface is for text-to-speech, and
1182
- speech-to-text models.
1183
-
1184
- The following providers have audio support:
1185
-
1186
- * OpenAI - full support
1187
- * Google - partial support
1188
- * DeepInfra - partial support
1189
-
1190
- #### text-to-speech
1191
-
1192
- The `create_speech` method generates an audio clip based
1193
- on the given input. This method returns a
1194
- [`LLM::URIData`](https://r.uby.dev/api-docs/llm.rb/LLM/URIData.html)
1195
- object. OpenAI, and DeepInfra support this method.
1196
-
1197
- ```ruby
1198
- require "llm"
1199
-
1200
- llm = LLM.openai(key: ENV["KEY"])
1201
- res = llm.audio.create_speech(input: "Hello world")
1202
- IO.copy_stream res.audio.decoded, "helloworld.mp3"
1203
- ```
1204
-
1205
- #### speech-to-text
1206
-
1207
- The `create_transcription` method transcribes a given
1208
- audio clip as text. OpenAI, Google and DeepInfra support
1209
- this method.
52
+ ## Fundamentals
1210
53
 
1211
- ```ruby
1212
- require "llm"
54
+ - [Agents](deepdive/fundamentals/agents.md)
55
+ - [Tools](deepdive/fundamentals/tools.md)
56
+ - [Skills](deepdive/fundamentals/skills.md)
57
+ - [Schema](deepdive/fundamentals/schema.md)
58
+ - [Stream](deepdive/fundamentals/stream.md)
59
+ - [Database](deepdive/fundamentals/database.md)
60
+ - [Concurrency](deepdive/fundamentals/concurrency.md)
61
+ - [REPL](deepdive/fundamentals/repl.md)
1213
62
 
1214
- llm = LLM.google(key: ENV["KEY"])
1215
- res = llm.audio.create_transcription(file: "helloworld.mp3")
1216
- res.text # => "Hello world"
1217
- ```
63
+ ## Advanced
1218
64
 
1219
- #### translation
65
+ - [Context](deepdive/advanced/context.md)
66
+ - [Compaction](deepdive/advanced/compaction.md)
67
+ - [Cancellation](deepdive/advanced/cancellation.md)
68
+ - [Transports](deepdive/advanced/transports.md)
69
+ - [Tracer](deepdive/advanced/tracer.md)
1220
70
 
1221
- The `create_translation` method translates a given audio
1222
- clip, then transcribes it as text. OpenAI, and Google
1223
- support this method.
71
+ ## Protocols
1224
72
 
1225
- ```ruby
1226
- require "llm"
73
+ - [MCP](deepdive/protocols/mcp.md)
74
+ - [A2A](deepdive/protocols/a2a.md)
1227
75
 
1228
- llm = LLM.google(key: ENV["KEY"])
1229
- res = llm.audio.create_translation(file: "bomdia.mp3")
1230
- res.text # => "Good day"
1231
- ```
76
+ ## Everything else
1232
77
 
1233
- [Back to top](#table-of-contents)
78
+ - [Images](deepdive/everything_else/images.md)
79
+ - [Audio](deepdive/everything_else/audio.md)
80
+ - [OCR](deepdive/everything_else/ocr.md)