llm.rb 13.1.0 → 14.0.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +320 -0
  3. data/README.md +340 -31
  4. data/bin/llm.rb +36 -12
  5. data/data/anthropic.json +206 -263
  6. data/data/bedrock.json +2138 -1860
  7. data/data/deepinfra.json +1003 -624
  8. data/data/deepseek.json +38 -34
  9. data/data/google.json +1079 -371
  10. data/data/mistral.json +448 -368
  11. data/data/moonshot.json +384 -0
  12. data/data/openai.json +974 -1343
  13. data/data/xai.json +154 -126
  14. data/data/zai.json +191 -191
  15. data/lib/llm/agent.rb +47 -14
  16. data/lib/llm/context.rb +71 -88
  17. data/lib/llm/cost.rb +23 -17
  18. data/lib/llm/error.rb +0 -8
  19. data/lib/llm/function/async/task.rb +2 -0
  20. data/lib/llm/function/fiber/task.rb +2 -0
  21. data/lib/llm/function/fork/task.rb +2 -0
  22. data/lib/llm/function/ractor/task.rb +2 -0
  23. data/lib/llm/function/sequential/group.rb +4 -1
  24. data/lib/llm/function/sequential/task.rb +1 -1
  25. data/lib/llm/function/task.rb +4 -0
  26. data/lib/llm/function/thread/task.rb +2 -0
  27. data/lib/llm/function.rb +32 -4
  28. data/lib/llm/guard/loop.rb +89 -0
  29. data/lib/llm/guard/null.rb +19 -0
  30. data/lib/llm/guard.rb +61 -0
  31. data/lib/llm/provider.rb +36 -0
  32. data/lib/llm/providers/anthropic/stream_parser.rb +1 -0
  33. data/lib/llm/providers/anthropic.rb +1 -8
  34. data/lib/llm/providers/bedrock/stream_parser.rb +1 -0
  35. data/lib/llm/providers/bedrock.rb +1 -8
  36. data/lib/llm/providers/google/stream_parser.rb +1 -0
  37. data/lib/llm/providers/google.rb +1 -8
  38. data/lib/llm/providers/moonshot.rb +76 -0
  39. data/lib/llm/providers/ollama.rb +1 -8
  40. data/lib/llm/providers/openai/responses/stream_parser.rb +1 -0
  41. data/lib/llm/providers/openai/responses.rb +6 -8
  42. data/lib/llm/providers/openai/stream_parser.rb +1 -0
  43. data/lib/llm/providers/openai.rb +3 -10
  44. data/lib/llm/repl/bar.rb +4 -3
  45. data/lib/llm/repl/buffer.rb +42 -15
  46. data/lib/llm/repl/color.rb +78 -0
  47. data/lib/llm/repl/input/char.rb +46 -0
  48. data/lib/llm/repl/input/row.rb +39 -0
  49. data/lib/llm/repl/input.rb +251 -66
  50. data/lib/llm/repl/markdown/table.rb +6 -2
  51. data/lib/llm/repl/markdown.rb +31 -5
  52. data/lib/llm/repl/status.rb +38 -3
  53. data/lib/llm/repl/stream.rb +16 -4
  54. data/lib/llm/repl/walker.rb +3 -2
  55. data/lib/llm/repl/window.rb +25 -5
  56. data/lib/llm/repl.rb +29 -13
  57. data/lib/llm/stream.rb +8 -7
  58. data/lib/llm/tool.rb +29 -0
  59. data/lib/llm/transformer/null.rb +21 -0
  60. data/lib/llm/transformer.rb +55 -0
  61. data/lib/llm/version.rb +1 -1
  62. data/lib/llm.rb +12 -2
  63. data/llm.gemspec +1 -0
  64. data/resources/deepdive/advanced/cancellation.md +74 -0
  65. data/resources/deepdive/advanced/compaction.md +83 -0
  66. data/resources/deepdive/advanced/context.md +267 -0
  67. data/resources/deepdive/advanced/guard.md +371 -0
  68. data/resources/deepdive/advanced/tracer.md +180 -0
  69. data/resources/deepdive/advanced/transformer.md +67 -0
  70. data/resources/deepdive/advanced/transports.md +45 -0
  71. data/resources/deepdive/everything_else/audio.md +122 -0
  72. data/resources/deepdive/everything_else/cost.md +99 -0
  73. data/resources/deepdive/everything_else/images.md +89 -0
  74. data/resources/deepdive/everything_else/object.md +108 -0
  75. data/resources/deepdive/everything_else/ocr.md +48 -0
  76. data/resources/deepdive/fundamentals/agents.md +202 -0
  77. data/resources/deepdive/fundamentals/builtin_tools.md +191 -0
  78. data/resources/deepdive/fundamentals/concurrency.md +104 -0
  79. data/resources/deepdive/fundamentals/database.md +449 -0
  80. data/resources/deepdive/fundamentals/embeddings.md +157 -0
  81. data/resources/deepdive/fundamentals/repl.md +87 -0
  82. data/resources/deepdive/fundamentals/schema.md +61 -0
  83. data/resources/deepdive/fundamentals/skills.md +106 -0
  84. data/resources/deepdive/fundamentals/stream.md +110 -0
  85. data/resources/deepdive/fundamentals/tools.md +265 -0
  86. data/resources/deepdive/protocols/a2a.md +106 -0
  87. data/resources/deepdive/protocols/mcp.md +111 -0
  88. data/resources/deepdive.md +7 -1
  89. metadata +36 -3
  90. data/lib/llm/loop_guard.rb +0 -107
@@ -0,0 +1,191 @@
1
+
2
+ ## Built-in tools
3
+
4
+ ### Introduction
5
+
6
+ #### Overview
7
+
8
+ llm.rb ships with twelve ready-made tools that cover the operations
9
+ a coding or system agent needs most: filesystem work, search, and
10
+ shell commands. Each tool is a subclass of
11
+ [`LLM::Tool`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool.html)
12
+ with a name, description, and typed parameters, exactly like a tool
13
+ you would write yourself. Load the whole catalog with
14
+ `require "llm/tools"`.
15
+
16
+ #### How it works
17
+
18
+ When you want to attach every built-in tool to an agent, require
19
+ the catalog and pass the full set of subclasses as the `tools:`
20
+ option. The runtime registers each tool's schema and lets the model
21
+ decide when to call it.
22
+
23
+ ```ruby
24
+ require "llm"
25
+ require "llm/tools"
26
+
27
+ llm = LLM.deepseek(key: ENV["KEY"])
28
+ agent = LLM::Agent.new(llm, tools: LLM::Tool.subclasses)
29
+ agent.talk "List the files in this repository"
30
+ ```
31
+
32
+ #### Why would I use it?
33
+
34
+ The built-in tools cover the operations a coding or system agent
35
+ needs most, so you rarely need to write your own. They also handle
36
+ edge cases correctly: subprocesses get timeouts, interrupts kill
37
+ the child process, and failed calls return structured errors
38
+ instead of crashing the conversation.
39
+
40
+ #### Notes
41
+
42
+ The tools that spawn subprocesses use the optional `test-cmd.rb`
43
+ gem for process management and interrupt handling. The gem is
44
+ required only when those tools load. Every tool returns a Hash with
45
+ an `ok:` key that tells the model whether the call succeeded.
46
+
47
+ ### Filesystem
48
+
49
+ #### Overview
50
+
51
+ The filesystem tools let the model read, write, and edit files,
52
+ list and create directories, and move around the working tree.
53
+ The most distinctive is
54
+ [`LLM::Tool::EditFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/EditFile.html),
55
+ which replaces an exact snippet and verifies the match count:
56
+
57
+ ```ruby
58
+ LLM::Tool::EditFile.new.call(
59
+ path: "config.yml",
60
+ before: "port: 3000",
61
+ after: "port: 4000"
62
+ )
63
+ # => {ok: true, replaced: 1}
64
+ ```
65
+
66
+ #### How it works
67
+
68
+ Each filesystem tool takes a path and returns a result Hash. The
69
+ `read-file` tool reads a whole file by default and accepts `start:`
70
+ and `stop:` to read a range of lines instead. The `edit-file` tool
71
+ counts occurrences of `before` and raises unless the count matches
72
+ `expected_count`, which defaults to 1.
73
+
74
+ | Tool | Name | Parameters | Purpose |
75
+ |---|---|---|---|
76
+ | [`LLM::Tool::Pwd`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Pwd.html) | `pwd` | none | Report the current working directory |
77
+ | [`LLM::Tool::Ls`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ls.html) | `ls` | `path`, `glob` | List files and directories, optionally matching a glob |
78
+ | [`LLM::Tool::Chdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Chdir.html) | `chdir` | `path` | Change the current working directory |
79
+ | [`LLM::Tool::Mkdir`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Mkdir.html) | `mkdir` | `path` | Create a tree of directories |
80
+ | [`LLM::Tool::ReadFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/ReadFile.html) | `read-file` | `path`, `start`, `stop` | Read a file, optionally a range of lines |
81
+ | [`LLM::Tool::WriteFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/WriteFile.html) | `write-file` | `path`, `content` | Write a string to a file |
82
+ | [`LLM::Tool::EditFile`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/EditFile.html) | `edit-file` | `path`, `before`, `after`, `expected_count` | Replace an exact snippet in a file |
83
+
84
+ #### Why would I use it?
85
+
86
+ Reading source files to answer questions, writing new files, and
87
+ editing a snippet in place are the bread and butter of a coding
88
+ agent. The filesystem tools give the model all of it without you
89
+ writing a single tool.
90
+
91
+ #### Notes
92
+
93
+ The `chdir` tool changes the working directory for the whole
94
+ process, so subsequent file operations see the new directory. The
95
+ `mkdir` tool creates parent directories, like `mkdir -p`. The `ls`
96
+ tool raises when the path does not exist.
97
+
98
+ ### Search
99
+
100
+ #### Overview
101
+
102
+ The search tools find things without reading the whole tree: `rg`
103
+ searches file contents, and `which` locates an executable on the
104
+ PATH. When a method name or a term is needed, the model searches
105
+ for it instead of guessing.
106
+
107
+ ```ruby
108
+ LLM::Tool::Rg.new.call(patterns: ["def talk"], path: "lib")
109
+ # => {ok: true, stdout: "lib/llm/context.rb:42:def talk", stderr: ""}
110
+ ```
111
+
112
+ #### How it works
113
+
114
+ When you want to search for one or more patterns, call the
115
+ [`LLM::Tool::Rg#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html#call-instance_method)
116
+ method with an Array of patterns, an optional `path:`, and a
117
+ `timeout:` in seconds. When you want to check whether a command is
118
+ installed, call the
119
+ [`LLM::Tool::Which#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Which.html#call-instance_method)
120
+ method with a `name:`.
121
+
122
+ | Tool | Name | Parameters | Purpose |
123
+ |---|---|---|---|
124
+ | [`LLM::Tool::Rg`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Rg.html) | `rg` | `patterns`, `path`, `timeout` | Recursively search for lines matching patterns |
125
+ | [`LLM::Tool::Which`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Which.html) | `which` | `name` | Locate an executable on the system PATH |
126
+
127
+ #### Why would I use it?
128
+
129
+ Search is how the model finds something without scanning files one
130
+ by one. Asked where a method is defined, the agent searches for it.
131
+ Before running a command, it checks with `which` that the binary
132
+ exists, and falls back to another approach when it does not.
133
+
134
+ #### Notes
135
+
136
+ The `rg` tool runs the ripgrep binary, so `rg` must be installed
137
+ and on the PATH. It refuses to search from the filesystem root and
138
+ rejects the pattern `.` to keep the model from dumping the entire
139
+ tree in one call. The `which` tool is pure Ruby: it scans the PATH
140
+ in order and returns the first directory that contains an
141
+ executable with the given name. When no match is found it returns
142
+ `{ok: false, path: nil}`.
143
+
144
+ ### Shell
145
+
146
+ #### Overview
147
+
148
+ The shell tools run real subprocesses: arbitrary commands through
149
+ `shell`, Ruby code through `ruby`, and a fixed set of git actions
150
+ through `git`. All three accept a `timeout:` and kill the child
151
+ process when the model interrupts the turn.
152
+
153
+ ```ruby
154
+ LLM::Tool::Shell.new.call(
155
+ name: "bundle",
156
+ arguments: ["exec", "rspec", "spec/llm"],
157
+ timeout: 30
158
+ )
159
+ ```
160
+
161
+ #### How it works
162
+
163
+ When you want to run a command and capture its output, call the
164
+ [`LLM::Tool::Shell#call`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html#call-instance_method)
165
+ method with a `name:` and optional `arguments:`. The `git` tool
166
+ accepts an `action:` from a fixed set, and the `ruby` tool runs its
167
+ code in a fresh process.
168
+
169
+ | Tool | Name | Parameters | Purpose |
170
+ |---|---|---|---|
171
+ | [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html) | `shell` | `name`, `arguments`, `timeout` | Run a shell command |
172
+ | [`LLM::Tool::Git`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Git.html) | `git` | `action`, `arguments`, `timeout` | Run a fixed set of git actions |
173
+ | [`LLM::Tool::Ruby`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Ruby.html) | `ruby` | `code`, `timeout` | Run a string of Ruby code |
174
+
175
+ #### Why would I use it?
176
+
177
+ Running tests, inspecting git history, and executing a snippet of
178
+ Ruby are things a coding agent needs to do. The shell tools make
179
+ those actions first-class, and the timeout keeps a hanging command
180
+ from stalling the conversation.
181
+
182
+ #### Notes
183
+
184
+ [`LLM::Tool::Shell`](https://r.uby.dev/api-docs/llm.rb/LLM/Tool/Shell.html)
185
+ can be dangerous given a low-quality model. Gate it behind
186
+ [`LLM::Agent#confirm`](https://r.uby.dev/api-docs/llm.rb/LLM/Agent.html#confirm)
187
+ or manage the tool loop manually through
188
+ [`LLM::Context`](https://r.uby.dev/api-docs/llm.rb/LLM/Context.html).
189
+ On interrupt, the running child process is killed. The `ruby` tool
190
+ uses the same Ruby that launched llm.rb. The `git` tool wraps the
191
+ actions `log`, `diff`, `commit`, `checkout`, `branch`, and `show`.
@@ -0,0 +1,104 @@
1
+
2
+ ## Concurrency
3
+
4
+ ### Introduction
5
+
6
+ #### Overview
7
+
8
+ llm.rb supports six concurrency strategies for tool execution:
9
+ `:sequential`, `:thread`, `:fiber`, `:async`, `:fork`, and
10
+ `:ractor`. Each implements the same interface
11
+ ([`LLM::Function::Task#spawn`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#spawn),
12
+ [`LLM::Function::Task#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#wait),
13
+ [`LLM::Function::Task#alive?`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#alive?),
14
+ [`LLM::Function::Task#interrupt!`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#interrupt!)),
15
+ so the caller never has to care which
16
+ strategy is behind a given task.
17
+
18
+ #### How it works
19
+
20
+ Each strategy creates a task object that wraps a function call.
21
+ [`LLM::Function::Task#spawn`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#spawn)
22
+ starts execution,
23
+ [`LLM::Function::Task#wait`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#wait)
24
+ collects the result,
25
+ [`LLM::Function::Task#alive?`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#alive?)
26
+ checks whether the task is still running, and
27
+ [`LLM::Function::Task#interrupt!`](https://r.uby.dev/api-docs/llm.rb/LLM/Function/Task.html#interrupt!)
28
+ signals the task to stop.
29
+
30
+ Interruption is reliable across all six.
31
+ [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
32
+ reaches the tool regardless of whether it runs on a thread, fiber, process,
33
+ or ractor.
34
+
35
+ A tool can rescue
36
+ [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
37
+ and return a structured error to cancel the tool call, or it can
38
+ re-raise to cancel the turn entirely. When you re-raise
39
+ [`LLM::Interrupt`](https://r.uby.dev/api-docs/llm.rb/LLM/Interrupt.html)
40
+ or leave it unrescued the exception is raised on the caller's thread
41
+ and that effectively terminates the turn.
42
+
43
+ The main benefit of rescuing yourself is to let the tool clean up
44
+ and free lingering resources, but you usually want to re-raise so
45
+ the cancel request ends the turn. Otherwise the tool call is
46
+ intercepted but the turn continues.
47
+
48
+ ```ruby
49
+ ##
50
+ # At a high-level
51
+ agent = LLM::Agent.new(llm, concurrency: :fork, tools: [...])
52
+ agent.talk "Run the tools"
53
+
54
+ ##
55
+ # At a low-level
56
+ fn = FetchStocks.function
57
+ fn.task(:thread).wait
58
+ ```
59
+
60
+ #### Why would I use it?
61
+
62
+ Different tools have different execution requirements. IO-bound
63
+ tools benefit from thread or async concurrency. CPU-bound tools
64
+ benefit from ractors or forks. Tools that might crash the process
65
+ should run in a fork for isolation.
66
+
67
+ The common interface means you can change strategies without
68
+ changing your tool code.
69
+
70
+ #### Notes
71
+
72
+ **sequential**: Tools run one at a time on the calling thread. No
73
+ overhead. Best for simple agents or when tool order matters.
74
+
75
+ **thread**: Each tool runs in its own Thread. Releases the GVL
76
+ during blocking IO. Best for IO-bound tools like HTTP calls and
77
+ database queries.
78
+
79
+ **fiber**: Each tool runs in a scheduler-backed Fiber. Requires
80
+ `Fiber.scheduler`. Best for IO-bound tools inside an async
81
+ framework. Much lighter than threads.
82
+
83
+ **async**: Each tool runs as an `Async::Task` inside a managed
84
+ background reactor. Best for IO-bound tools when you want Async's
85
+ structured concurrency model without running your whole application
86
+ inside a reactor. Requires the `async` gem.
87
+
88
+ **fork**: Each tool runs in a forked child process. True parallelism
89
+ and process isolation. Best for shell commands, native extensions,
90
+ or anything you do not want touching the parent's memory. Requires
91
+ the `xchan` gem.
92
+
93
+ **ractor**: Each class-based tool runs in a Ruby Ractor. True
94
+ parallelism without the overhead of forking full processes. Only
95
+ class-based tools are supported. Arguments must be ractor-shareable.
96
+
97
+ | Strategy | Backing | Parallel? | Isolation? | Requires |
98
+ |---|---|---|---|---|
99
+ | `:sequential` | direct call | No | No | None |
100
+ | `:thread` | `Thread` | IO only (GVL) | No | None |
101
+ | `:fiber` | `Fiber.schedule` | Cooperative | No | `Fiber.scheduler` |
102
+ | `:async` | `Async::Reactor` | Cooperative | No | `async` gem |
103
+ | `:fork` | `Kernel.fork` | Yes (process) | Yes (memory) | `xchan` gem |
104
+ | `:ractor` | `Ractor` | Yes (CPU) | Limited | None |