llm.rb 9.0.0 → 11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +182 -4
- data/README.md +194 -42
- data/data/anthropic.json +278 -258
- data/data/bedrock.json +1288 -1561
- data/data/deepseek.json +38 -38
- data/data/google.json +656 -579
- data/data/openai.json +860 -818
- data/data/xai.json +243 -552
- data/data/zai.json +168 -168
- data/lib/llm/a2a/card/capabilities.rb +41 -0
- data/lib/llm/a2a/card/interface.rb +34 -0
- data/lib/llm/a2a/card/provider.rb +27 -0
- data/lib/llm/a2a/card/skill.rb +68 -0
- data/lib/llm/a2a/card.rb +144 -0
- data/lib/llm/a2a/error.rb +49 -0
- data/lib/llm/a2a/notifications.rb +53 -0
- data/lib/llm/a2a/tasks.rb +55 -0
- data/lib/llm/a2a/transport/http.rb +131 -0
- data/lib/llm/a2a.rb +452 -0
- data/lib/llm/active_record/acts_as_agent.rb +20 -9
- data/lib/llm/active_record/acts_as_llm.rb +4 -4
- data/lib/llm/active_record.rb +1 -6
- data/lib/llm/agent.rb +96 -71
- data/lib/llm/buffer.rb +1 -2
- data/lib/llm/context.rb +77 -50
- data/lib/llm/file.rb +7 -0
- data/lib/llm/function/call_task.rb +46 -0
- data/lib/llm/function.rb +28 -2
- data/lib/llm/mcp/transport/http.rb +5 -18
- data/lib/llm/mcp/transport/stdio.rb +7 -0
- data/lib/llm/mcp.rb +20 -17
- data/lib/llm/message.rb +1 -1
- data/lib/llm/object/kernel.rb +1 -1
- data/lib/llm/provider.rb +9 -9
- data/lib/llm/providers/anthropic/stream_parser.rb +2 -2
- data/lib/llm/providers/bedrock/stream_parser.rb +2 -2
- data/lib/llm/providers/google/stream_parser.rb +2 -2
- data/lib/llm/providers/openai/responses/stream_parser.rb +2 -2
- data/lib/llm/providers/openai/stream_parser.rb +2 -2
- data/lib/llm/response.rb +1 -1
- data/lib/llm/schema.rb +11 -0
- data/lib/llm/sequel/agent.rb +19 -9
- data/lib/llm/sequel/plugin.rb +9 -13
- data/lib/llm/stream.rb +11 -36
- data/lib/llm/tool/param.rb +1 -8
- data/lib/llm/tool.rb +57 -27
- data/lib/llm/tracer.rb +1 -1
- data/lib/llm/transport/http.rb +1 -1
- data/lib/llm/transport/stream_decoder.rb +6 -3
- data/lib/llm/transport/utils.rb +35 -0
- data/lib/llm/transport.rb +1 -0
- data/lib/llm/utils.rb +73 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +24 -4
- data/llm.gemspec +16 -1
- metadata +29 -5
- data/lib/llm/bot.rb +0 -3
- data/lib/llm/mcp/transport/http/event_handler.rb +0 -68
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24c3c2930dd3ab321999075b34ef2e5c6d445fec5c873b00ef071caeef3c1406
|
|
4
|
+
data.tar.gz: 0d6921f20dc327f7c424f7282ff3c76f5073ad3eec7f21d483ee7623e4c782f7
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3b96ea3336114822ccb2defee4da43089df6e004cebdf27987562f7f339bc2a733cc169d64f9752cc51d0346a069ba3921e99db69c2de1f795abfa69f260a730
|
|
7
|
+
data.tar.gz: b4135fad5bd1c5499b1177c27d6eb9c2da5a84b50a5492e82fe6396821c2b4a5e0891e55cb557d07e5ee4ec912b7ecdd8c7df223ebe76109aa74b7ede5227195
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,184 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## v11.0.0
|
|
6
|
+
|
|
7
|
+
Changes since `v10.0.0`.
|
|
8
|
+
|
|
9
|
+
This release removes several deprecated or unused APIs, including the `#chat`
|
|
10
|
+
alias from contexts and agents, the `LLM::Function#register` alias, and the
|
|
11
|
+
unused positional `llm` argument from MCP constructors. Generated MCP and A2A
|
|
12
|
+
tools are no longer added to the global tool registry by default.
|
|
13
|
+
|
|
14
|
+
On the additions side, it introduces the A2A (Agent2Agent) protocol client,
|
|
15
|
+
a new `#ask` convenience interface on contexts and agents, one-shot stdio MCP
|
|
16
|
+
requests outside `#session`, `LLM::Function#def` as a short alias for
|
|
17
|
+
`LLM::Function#define`, `LLM::File#exist?`, and `LLM::Tool.a2a?`.
|
|
18
|
+
|
|
19
|
+
### Breaking
|
|
20
|
+
|
|
21
|
+
* **Remove the unused `llm` argument from MCP clients** <br>
|
|
22
|
+
Remove the unused positional `llm` argument from `LLM::MCP.new`,
|
|
23
|
+
`LLM::MCP.stdio`, `LLM::MCP.http`, and `LLM.mcp`.
|
|
24
|
+
|
|
25
|
+
* **Stop globally registering generated MCP and A2A tools** <br>
|
|
26
|
+
Generated tools returned by `LLM::Tool.mcp(...)` and
|
|
27
|
+
`LLM::Tool.a2a(...)` are no longer added to the global
|
|
28
|
+
`LLM::Tool.registry` or `LLM::Function.registry`. They still work
|
|
29
|
+
when passed directly to a context or agent, but registry-based lookup
|
|
30
|
+
now only sees normal loaded `LLM::Tool` subclasses.
|
|
31
|
+
|
|
32
|
+
* **Remove `LLM::Function#register`** <br>
|
|
33
|
+
Remove the `LLM::Function#register` alias and prefer
|
|
34
|
+
`LLM::Function#define` or `LLM::Function#def` when binding a
|
|
35
|
+
function to its implementation. The `register` alias was too easy to
|
|
36
|
+
confuse with the class-level `LLM::Tool.register` and
|
|
37
|
+
`LLM::Function.register` registry APIs.
|
|
38
|
+
|
|
39
|
+
* **Remove the `#chat` alias from contexts and agents** <br>
|
|
40
|
+
Remove the `LLM::Context#chat` and `LLM::Agent#chat` aliases. Prefer
|
|
41
|
+
`#talk` for all context and agent turns.
|
|
42
|
+
|
|
43
|
+
### Add
|
|
44
|
+
|
|
45
|
+
* **Add `LLM::Function#def`** <br>
|
|
46
|
+
Add `LLM::Function#def` as a short alias for
|
|
47
|
+
`LLM::Function#define` when binding a function instance to its
|
|
48
|
+
implementation.
|
|
49
|
+
|
|
50
|
+
* **Add `LLM::MCP#session`** <br>
|
|
51
|
+
Add `LLM::MCP#session` as an alias for `LLM::MCP#run`, and prefer it
|
|
52
|
+
in examples for scoped stdio MCP sessions that should stay alive
|
|
53
|
+
across discovery and tool calls.
|
|
54
|
+
|
|
55
|
+
* **Add `#ask` to contexts and agents** <br>
|
|
56
|
+
Add `LLM::Context#ask` and `LLM::Agent#ask` as a RubyLLM-compatible
|
|
57
|
+
convenience interface over `#talk`. `#ask` accepts a prompt, optional
|
|
58
|
+
`with:` attachments, an optional `stream:` target, and an optional
|
|
59
|
+
block for streamed chunks, and returns an `LLM::Response`.
|
|
60
|
+
|
|
61
|
+
* **Add `LLM::File#exist?`** <br>
|
|
62
|
+
Add `LLM::File#exist?` as a small convenience wrapper for checking
|
|
63
|
+
whether a local file exists on disk.
|
|
64
|
+
|
|
65
|
+
* **Allow one-shot stdio MCP requests outside `#session`** <br>
|
|
66
|
+
Allow `mcp.tools`, `mcp.prompts`, `mcp.find_prompt(...)`, and
|
|
67
|
+
`mcp.call_tool(...)` to work outside `mcp.session` by starting and
|
|
68
|
+
stopping a stdio transport on demand when needed. This makes stdio
|
|
69
|
+
MCP usable without an explicit session block, while keeping
|
|
70
|
+
`mcp.session` as the preferred pattern for efficient, stateful
|
|
71
|
+
stdio workflows.
|
|
72
|
+
|
|
73
|
+
* **Add A2A client support** <br>
|
|
74
|
+
Add `LLM::A2A`, a client for the Agent2Agent (A2A) protocol with
|
|
75
|
+
REST and JSON-RPC bindings. Remote agent skills can be exposed as
|
|
76
|
+
`LLM::Tool` classes and used through `LLM::Context` or `LLM::Agent`,
|
|
77
|
+
and the client also supports direct messaging, streaming, task
|
|
78
|
+
operations, push notification configuration, extended agent cards,
|
|
79
|
+
persistent HTTP transport selection, and optional REST `base_path`
|
|
80
|
+
prefixing.
|
|
81
|
+
|
|
82
|
+
Refactor shared MCP/A2A HTTP transport setup into
|
|
83
|
+
`LLM::Transport::Utils`, and extend
|
|
84
|
+
`LLM::Transport::StreamDecoder` to accept a callback block directly.
|
|
85
|
+
|
|
86
|
+
* **Add `LLM::Tool.a2a?`** <br>
|
|
87
|
+
Add `LLM::Tool.a2a?` and mark generated A2A-backed tool classes so
|
|
88
|
+
callers can distinguish them from local or MCP tools.
|
|
89
|
+
|
|
90
|
+
### Fix
|
|
91
|
+
|
|
92
|
+
* **Fix context and agent JSON serialization through `LLM.json`** <br>
|
|
93
|
+
Fix `LLM::Context#to_json` and `LLM::Agent#to_json` to serialize
|
|
94
|
+
through `LLM.json.dump(...)` instead of plain `to_json`.
|
|
95
|
+
|
|
96
|
+
* **Fix block-form ORM agent DSL forwarding** <br>
|
|
97
|
+
Fix block-form `model { ... }`, `tools { ... }`, and
|
|
98
|
+
`schema { ... }` declarations in the ActiveRecord and Sequel agent
|
|
99
|
+
wrappers so persisted agent models configure the internal agent class
|
|
100
|
+
the same way as `LLM::Agent`.
|
|
101
|
+
|
|
102
|
+
* **Fix missing `skills` in ORM agent wrappers** <br>
|
|
103
|
+
Fix the ActiveRecord and Sequel agent wrappers to expose `skills`, so
|
|
104
|
+
persisted agent models can declare skills the same way as
|
|
105
|
+
`LLM::Agent`.
|
|
106
|
+
|
|
107
|
+
* **Fix `acts_as_agent#ctx` return type** <br>
|
|
108
|
+
Fix the ActiveRecord `acts_as_agent` wrapper so its `ctx` helper
|
|
109
|
+
returns the wrapped `LLM::Agent` instead of returning the underlying
|
|
110
|
+
`LLM::Context` directly.
|
|
111
|
+
|
|
112
|
+
## v10.0.0
|
|
113
|
+
|
|
114
|
+
Changes since `v9.0.0`.
|
|
115
|
+
|
|
116
|
+
This release removes the `LLM::Context#respond` method, and
|
|
117
|
+
also removes the deprecated `LLM::Bot` alias. **All** class-level
|
|
118
|
+
agent tunables can now be resolved lazily via a Symbol (method name),
|
|
119
|
+
or a Proc. The `LLM::Agent` class can now confirm a tool call
|
|
120
|
+
before it happens, and the `LLM::Schema` class has been extended
|
|
121
|
+
to support `Array[String,Integer]` as a shorthand for
|
|
122
|
+
`Array[AnyOf[String, Integer]]`. The `LLM::Stream` class has
|
|
123
|
+
had its public method surface reduced to help avoid accidental
|
|
124
|
+
collisions.
|
|
125
|
+
|
|
126
|
+
### Breaking
|
|
127
|
+
|
|
128
|
+
* **Unify context turns under `#talk`** <br>
|
|
129
|
+
Remove `LLM::Context#respond` and route responses-mode turns through
|
|
130
|
+
`LLM::Context#talk` with `mode: :responses` instead.
|
|
131
|
+
|
|
132
|
+
* **Remove the `LLM::Bot` alias** <br>
|
|
133
|
+
Remove the backward-compatible `LLM::Bot` alias for `LLM::Context`.
|
|
134
|
+
Use `LLM::Context` directly instead.
|
|
135
|
+
|
|
136
|
+
### Add
|
|
137
|
+
|
|
138
|
+
* **Add shared option resolution through `LLM::Utils`** <br>
|
|
139
|
+
Add `LLM::Utils.resolve_option` for resolving configured values as
|
|
140
|
+
literals, procs, symbol-named methods, or duplicated hashes, and use
|
|
141
|
+
it in agent and ORM option resolution paths.
|
|
142
|
+
|
|
143
|
+
* **Resolve all class-level agent tunables via Proc** <br>
|
|
144
|
+
Let `model`, `tools`, `skills`, `schema`, `stream`, and `tracer`
|
|
145
|
+
declared with a block be lazily evaluated against the agent instance
|
|
146
|
+
at initialization time, matching how `stream` and `tracer` already
|
|
147
|
+
worked.
|
|
148
|
+
|
|
149
|
+
Add `LLM::Agent#params` for direct access to the underlying context
|
|
150
|
+
parameters.
|
|
151
|
+
|
|
152
|
+
Ported from mruby-llm.
|
|
153
|
+
|
|
154
|
+
* **Support `Array[...]` schema and tool param types** <br>
|
|
155
|
+
Let `LLM::Schema` properties and `LLM::Tool` params accept
|
|
156
|
+
`Array[...]` type declarations, including mixed item unions that are
|
|
157
|
+
serialized as `anyOf` array items.
|
|
158
|
+
|
|
159
|
+
* **Add `LLM::Provider#key?`** <br>
|
|
160
|
+
Add `key?` to providers so callers can check whether a non-blank API
|
|
161
|
+
key has been configured.
|
|
162
|
+
|
|
163
|
+
* **Add agent tool confirmation hooks** <br>
|
|
164
|
+
Add `LLM::Agent.confirm` and `LLM::Agent#on_tool_confirmation` so
|
|
165
|
+
selected tools can be approved or cancelled before execution. Pending
|
|
166
|
+
tool resolution now relies on `LLM::Context#functions` so confirmed
|
|
167
|
+
tools are not executed twice when mixed with unconfirmed tool calls.
|
|
168
|
+
|
|
169
|
+
* **Add `LLM::Function#spawn(:call).wait`** <br>
|
|
170
|
+
Add task-shaped sequential execution support for direct
|
|
171
|
+
`LLM::Function#spawn(:call).wait`.
|
|
172
|
+
|
|
173
|
+
### Fix
|
|
174
|
+
|
|
175
|
+
* **Reduce private internal methods on `LLM::Stream`** <br>
|
|
176
|
+
Remove `tool_not_found` and `__tools__` from `LLM::Stream`. The
|
|
177
|
+
`__tools__` logic is inlined directly into `__find__` since that
|
|
178
|
+
was its only caller. The `tool_not_found` utility method was unused
|
|
179
|
+
externally and added unnecessary surface to LLM::Stream.
|
|
180
|
+
|
|
181
|
+
Ported from mruby-llm.
|
|
182
|
+
|
|
5
183
|
## v9.0.0
|
|
6
184
|
|
|
7
185
|
Changes since `v8.1.0`.
|
|
@@ -162,7 +340,7 @@ DSML tool-marker filtering in streamed text.
|
|
|
162
340
|
blocks that Bedrock rejects.
|
|
163
341
|
|
|
164
342
|
* **Suppress Bedrock DSML tool markers in streamed text** <br>
|
|
165
|
-
Filter
|
|
343
|
+
Filter `\"<|DSML|function_calls\"` markers out of streamed Bedrock
|
|
166
344
|
assistant text so tool-call sentinels do not leak into user-visible
|
|
167
345
|
output.
|
|
168
346
|
|
|
@@ -313,7 +491,7 @@ provider usage has been recorded yet.
|
|
|
313
491
|
buffer API.
|
|
314
492
|
|
|
315
493
|
* **Support percentage compaction token thresholds** <br>
|
|
316
|
-
Let `LLM::Compactor` accept `token_threshold:` values like
|
|
494
|
+
Let `LLM::Compactor` accept `token_threshold:` values like `\"90%\"` so
|
|
317
495
|
compaction can trigger at a percentage of the active model context
|
|
318
496
|
window.
|
|
319
497
|
|
|
@@ -1096,7 +1274,7 @@ Changes since `v4.9.0`.
|
|
|
1096
1274
|
|
|
1097
1275
|
- Add HTTP transport for MCP with `LLM::MCP::Transport::HTTP` for remote servers
|
|
1098
1276
|
- Add JSON Schema union types (`any_of`, `all_of`, `one_of`) with parser integration
|
|
1099
|
-
- Add JSON Schema type array union support (e.g.,
|
|
1277
|
+
- Add JSON Schema type array union support (e.g., `\"type\": [\"object\", \"null\"]`)
|
|
1100
1278
|
- Add JSON Schema type inference from `const`, `enum`, or `default` fields
|
|
1101
1279
|
|
|
1102
1280
|
### Change
|
|
@@ -1197,7 +1375,7 @@ Notable merged work in this range includes:
|
|
|
1197
1375
|
- `Add rack + websocket example (#130)`
|
|
1198
1376
|
- `feat(gemspec): add changelog URI (#136)`
|
|
1199
1377
|
- `feat(function): alias ThreadGroup#wait as ThreadGroup#value (#62)`
|
|
1200
|
-
- README and screencast refresh across `#66`, `#
|
|
1378
|
+
- README and screencast refresh across `#66`, `#68`, `#71`, and
|
|
1201
1379
|
`#72`
|
|
1202
1380
|
- `chore(bot): update deprecation warning from v5.0 to v6.0`
|
|
1203
1381
|
- `fix(deepseek): tolerate malformed tool arguments`
|
data/README.md
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="
|
|
2
|
+
<a href="https://github.com/llmrb/llm.rb">
|
|
3
|
+
<img src="https://github.com/llmrb/llm.rb/raw/main/llm.png" width="200" height="200" border="0" alt="llm.rb">
|
|
4
|
+
</a>
|
|
3
5
|
</p>
|
|
4
6
|
<p align="center">
|
|
5
|
-
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1"
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
<a href="https://0x1eef.github.io/x/llm.rb?rebuild=1">
|
|
8
|
+
<img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc">
|
|
9
|
+
</a>
|
|
10
|
+
<a href="https://opensource.org/license/0bsd">
|
|
11
|
+
<img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License">
|
|
12
|
+
</a>
|
|
13
|
+
<a href="https://github.com/llmrb/llm.rb/tags">
|
|
14
|
+
<img src="https://img.shields.io/badge/version-11.0.0-green.svg?" alt="Version">
|
|
15
|
+
</a>
|
|
8
16
|
</p>
|
|
9
17
|
|
|
10
18
|
## About
|
|
@@ -13,8 +21,9 @@ llm.rb is Ruby's most capable AI runtime.
|
|
|
13
21
|
|
|
14
22
|
It runs on Ruby's standard library by default. loads optional pieces
|
|
15
23
|
only when needed, and offers a single runtime for providers, agents,
|
|
16
|
-
tools, skills, MCP,
|
|
17
|
-
llm.rb is also
|
|
24
|
+
tools, skills, MCP, A2A (Agent2Agent), RAG (vector stores & embeddings),
|
|
25
|
+
streaming, files, and persisted state. As a bonus, llm.rb is also
|
|
26
|
+
[available for mruby](https://github.com/llmrb/mruby-llm).
|
|
18
27
|
|
|
19
28
|
It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
|
|
20
29
|
Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. It
|
|
@@ -64,6 +73,36 @@ agent = LLM::Agent.new(llm, stream: $stdout)
|
|
|
64
73
|
agent.talk "Hello world"
|
|
65
74
|
```
|
|
66
75
|
|
|
76
|
+
#### Agents (Advanced)
|
|
77
|
+
|
|
78
|
+
An agent can be configured to require confirmation before a tool is
|
|
79
|
+
executed. When a matching tool is called, llm.rb runs
|
|
80
|
+
`on_tool_confirmation`. That callback must decide whether to cancel the
|
|
81
|
+
tool call or approve it and execute it by calling
|
|
82
|
+
`fn.spawn(strategy).wait`, and it must always return an instance of
|
|
83
|
+
[`LLM::Function::Return`](https://0x1eef.github.io/x/llm.rb/LLM/Function/Return.html):
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
require "llm"
|
|
87
|
+
|
|
88
|
+
class Agent < LLM::Agent
|
|
89
|
+
tools DeleteFile
|
|
90
|
+
confirm "delete-file"
|
|
91
|
+
|
|
92
|
+
def on_tool_confirmation(fn, strategy)
|
|
93
|
+
path = fn.arguments["path"] || fn.arguments[:path]
|
|
94
|
+
if path.start_with?("/tmp/")
|
|
95
|
+
fn.spawn(strategy).wait
|
|
96
|
+
else
|
|
97
|
+
fn.cancel(reason: "Deletion requires approval")
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
103
|
+
Agent.new(llm, stream: $stdout).talk("Delete /tmp/example.txt.")
|
|
104
|
+
```
|
|
105
|
+
|
|
67
106
|
#### Tools
|
|
68
107
|
|
|
69
108
|
The
|
|
@@ -96,7 +135,9 @@ or
|
|
|
96
135
|
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
|
|
97
136
|
In this example, the MCP server runs over stdio and
|
|
98
137
|
[LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
99
|
-
uses the same tool loop as local tools
|
|
138
|
+
uses the same tool loop as local tools. For **stdio**, `mcp.session`
|
|
139
|
+
is the preferred pattern because it keeps one MCP session alive across
|
|
140
|
+
discovery and tool calls:
|
|
100
141
|
|
|
101
142
|
```ruby
|
|
102
143
|
require "llm"
|
|
@@ -104,20 +145,96 @@ require "llm"
|
|
|
104
145
|
llm = LLM.openai(key: ENV["KEY"])
|
|
105
146
|
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
106
147
|
|
|
107
|
-
mcp.
|
|
148
|
+
mcp.session do
|
|
108
149
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
109
150
|
ctx.talk "Use the available tools to inspect the environment."
|
|
110
151
|
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
111
152
|
end
|
|
112
153
|
```
|
|
113
154
|
|
|
155
|
+
MCP can also be used without `session`. Although it works it is generally
|
|
156
|
+
not recommended for the **stdio** transport because it is inefficient
|
|
157
|
+
to start and stop a fresh MCP process for tool discovery and each tool
|
|
158
|
+
call:
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
require "llm"
|
|
162
|
+
|
|
163
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
164
|
+
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
165
|
+
|
|
166
|
+
ctx = LLM::Context.new(llm, tools: mcp.tools)
|
|
167
|
+
ctx.talk("Use the available tools to inspect the environment.")
|
|
168
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The HTTP transport can be used with or without the `session` method,
|
|
172
|
+
and unlike the stdio transport it can remain efficient without the
|
|
173
|
+
`session` method through a persistent connection pool that is available
|
|
174
|
+
through the [LLM::Transport.net_http_persistent](https://0x1eef.github.io/x/llm.rb/LLM/Transport.html#method-c-net_http_persistent) transport:
|
|
175
|
+
|
|
176
|
+
```ruby
|
|
177
|
+
require "llm"
|
|
178
|
+
|
|
179
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
180
|
+
mcp = LLM::MCP.http(
|
|
181
|
+
url: "https://remote-mcp.example.com",
|
|
182
|
+
transport: LLM::Transport.net_http_persistent
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
ctx = LLM::Context.new(llm, tools: mcp.tools)
|
|
186
|
+
ctx.talk("Use the available tools to inspect the environment.")
|
|
187
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
#### A2A (Agent 2 Agent)
|
|
191
|
+
|
|
192
|
+
The
|
|
193
|
+
[LLM::A2A](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html)
|
|
194
|
+
object lets llm.rb use skills provided by a remote A2A agent. Those
|
|
195
|
+
skills are exposed through the same runtime as local tools, so you can
|
|
196
|
+
pass them to either
|
|
197
|
+
[LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
198
|
+
or
|
|
199
|
+
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
|
|
200
|
+
|
|
201
|
+
Use remote skills as local tools:
|
|
202
|
+
|
|
203
|
+
```ruby
|
|
204
|
+
require "llm"
|
|
205
|
+
|
|
206
|
+
a2a = LLM::A2A.rest(
|
|
207
|
+
url: "https://remote-agent.example.com",
|
|
208
|
+
headers: {"Authorization" => "Bearer token"}
|
|
209
|
+
)
|
|
210
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
211
|
+
ctx = LLM::Context.new(llm, tools: a2a.skills)
|
|
212
|
+
ctx.talk "Analyze this CSV and summarize the trends."
|
|
213
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Use persistent HTTP connections:
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
require "llm"
|
|
220
|
+
|
|
221
|
+
a2a = LLM::A2A.rest(
|
|
222
|
+
url: "https://remote-agent.example.com",
|
|
223
|
+
transport: LLM::Transport.net_http_persistent
|
|
224
|
+
)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
For more on direct messaging, task operations, push notification
|
|
228
|
+
configs, and JSON-RPC, see the
|
|
229
|
+
[LLM::A2A API docs](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html).
|
|
230
|
+
|
|
114
231
|
#### Skills
|
|
115
232
|
|
|
116
233
|
Skills are reusable instructions loaded from a `SKILL.md` directory. They let
|
|
117
|
-
you package behavior and tool access together, and they plug into the
|
|
118
|
-
runtime as tools, agents, and
|
|
119
|
-
subagent with the skill instructions, access to only the tools
|
|
120
|
-
skill, and recent conversation context:
|
|
234
|
+
you package behavior and tool access together, and they plug into the
|
|
235
|
+
same runtime as tools, agents, MCP, and A2A. When a skill runs, llm.rb
|
|
236
|
+
spawns a subagent with the skill instructions, access to only the tools
|
|
237
|
+
listed in the skill, and recent conversation context:
|
|
121
238
|
|
|
122
239
|
```yaml
|
|
123
240
|
---
|
|
@@ -239,6 +356,27 @@ ctx2.restore(string:)
|
|
|
239
356
|
ctx2.talk "What is my favorite language?"
|
|
240
357
|
```
|
|
241
358
|
|
|
359
|
+
#### ask
|
|
360
|
+
|
|
361
|
+
[`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
362
|
+
also provides `ask`, a convenience interface that is compatible with
|
|
363
|
+
RubyLLM's `ask` method. It accepts a prompt, an optional `with:`
|
|
364
|
+
attachment path or paths, an optional `stream:` target, and an optional
|
|
365
|
+
block that chunks are yielded to. It returns an
|
|
366
|
+
[`LLM::Response`](https://0x1eef.github.io/x/llm.rb/LLM/Response.html),
|
|
367
|
+
so use `.content` when you want the text directly:
|
|
368
|
+
|
|
369
|
+
```ruby
|
|
370
|
+
require "llm"
|
|
371
|
+
|
|
372
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
373
|
+
ctx = LLM::Context.new(llm)
|
|
374
|
+
|
|
375
|
+
puts ctx.ask("Hello world").content
|
|
376
|
+
puts ctx.ask("Summarize this document.", with: "README.md").content
|
|
377
|
+
ctx.ask("Stream this reply.") { $stdout << _1 }
|
|
378
|
+
```
|
|
379
|
+
|
|
242
380
|
## Installation
|
|
243
381
|
|
|
244
382
|
```bash
|
|
@@ -249,7 +387,10 @@ gem install llm.rb
|
|
|
249
387
|
|
|
250
388
|
#### REPL
|
|
251
389
|
|
|
252
|
-
This example uses [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
390
|
+
This example uses [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
391
|
+
directly for an interactive REPL. <br> See the
|
|
392
|
+
[deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
|
|
393
|
+
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
253
394
|
|
|
254
395
|
```ruby
|
|
255
396
|
require "llm"
|
|
@@ -274,17 +415,18 @@ or [`ctx.remote_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#r
|
|
|
274
415
|
Those tagged objects carry the metadata the provider adapter needs to turn one
|
|
275
416
|
Ruby prompt into the provider-specific multimodal request schema.
|
|
276
417
|
|
|
277
|
-
|
|
278
|
-
`
|
|
279
|
-
|
|
280
|
-
|
|
418
|
+
If the model understands that file type, you can attach a local file directly
|
|
419
|
+
with `ctx.ask(..., with: path)` instead of uploading it first through a
|
|
420
|
+
provider Files API. Under the hood, llm.rb tags the path as a
|
|
421
|
+
[`ctx.local_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#local_file-instance_method)
|
|
422
|
+
object:
|
|
281
423
|
|
|
282
424
|
```ruby
|
|
283
425
|
require "llm"
|
|
284
426
|
|
|
285
427
|
llm = LLM.openai(key: ENV["KEY"])
|
|
286
428
|
ctx = LLM::Context.new(llm)
|
|
287
|
-
ctx.
|
|
429
|
+
puts ctx.ask("Summarize this document.", with: "README.md").content
|
|
288
430
|
```
|
|
289
431
|
|
|
290
432
|
#### Context Compaction
|
|
@@ -299,7 +441,9 @@ compactor can also use its own `model:` if you want summarization to run on a
|
|
|
299
441
|
different model from the main context. `token_threshold:` accepts either a
|
|
300
442
|
fixed token count or a percentage string like `"90%"`, which resolves
|
|
301
443
|
against the active model context window and triggers compaction once total
|
|
302
|
-
token usage goes over that percentage. See the
|
|
444
|
+
token usage goes over that percentage. See the
|
|
445
|
+
[deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
|
|
446
|
+
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
303
447
|
|
|
304
448
|
```ruby
|
|
305
449
|
require "llm"
|
|
@@ -328,7 +472,15 @@ ctx = LLM::Context.new(
|
|
|
328
472
|
|
|
329
473
|
#### Reasoning
|
|
330
474
|
|
|
331
|
-
This example uses [`LLM::Stream`](https://0x1eef.github.io/x/llm.rb/LLM/Stream.html)
|
|
475
|
+
This example uses [`LLM::Stream`](https://0x1eef.github.io/x/llm.rb/LLM/Stream.html)
|
|
476
|
+
with the OpenAI Responses API so reasoning output is streamed separately from
|
|
477
|
+
visible assistant output. See the
|
|
478
|
+
[deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
|
|
479
|
+
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
480
|
+
|
|
481
|
+
To use the Responses API (OpenAI-specific), initialize a
|
|
482
|
+
context or agent with `mode: :responses` and keep using
|
|
483
|
+
`talk` for turns.
|
|
332
484
|
|
|
333
485
|
```ruby
|
|
334
486
|
require "llm"
|
|
@@ -356,7 +508,10 @@ ctx.talk("Solve 17 * 19 and show your work.")
|
|
|
356
508
|
|
|
357
509
|
#### Request Cancellation
|
|
358
510
|
|
|
359
|
-
Need to cancel a stream? llm.rb has you covered through
|
|
511
|
+
Need to cancel a stream? llm.rb has you covered through
|
|
512
|
+
[`LLM::Context#interrupt!`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#interrupt-21-instance_method).
|
|
513
|
+
<br> See the [deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html)
|
|
514
|
+
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
360
515
|
|
|
361
516
|
```ruby
|
|
362
517
|
require "llm"
|
|
@@ -377,7 +532,14 @@ worker.join
|
|
|
377
532
|
|
|
378
533
|
#### Sequel (ORM)
|
|
379
534
|
|
|
380
|
-
The `plugin :llm` integration wraps
|
|
535
|
+
The `plugin :llm` integration wraps
|
|
536
|
+
[`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html) on a
|
|
537
|
+
`Sequel::Model` and keeps tool execution explicit. Like the ActiveRecord
|
|
538
|
+
wrappers, its built-in persistence contract is the serialized `data` column,
|
|
539
|
+
while `provider:` resolves a real `LLM::Provider` instance and `context:`
|
|
540
|
+
injects defaults such as `model:`. <br> See the
|
|
541
|
+
[deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
|
|
542
|
+
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
381
543
|
|
|
382
544
|
```ruby
|
|
383
545
|
require "llm"
|
|
@@ -412,7 +574,8 @@ one serialized `data` column. If your app has provider, model, or usage
|
|
|
412
574
|
columns, provide them to llm.rb through `provider:` and `context:` instead of
|
|
413
575
|
relying on reserved wrapper columns.
|
|
414
576
|
|
|
415
|
-
See the [deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html)
|
|
577
|
+
See the [deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html)
|
|
578
|
+
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
416
579
|
|
|
417
580
|
```ruby
|
|
418
581
|
require "llm"
|
|
@@ -468,7 +631,8 @@ manages tool execution for you. Like `acts_as_llm`, its built-in persistence
|
|
|
468
631
|
contract is one serialized `data` column. If your app has provider or model
|
|
469
632
|
columns, provide them to llm.rb through your hooks and agent DSL.
|
|
470
633
|
|
|
471
|
-
See the [deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html)
|
|
634
|
+
See the [deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html)
|
|
635
|
+
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
472
636
|
|
|
473
637
|
```ruby
|
|
474
638
|
require "llm"
|
|
@@ -521,7 +685,12 @@ end
|
|
|
521
685
|
|
|
522
686
|
#### MCP
|
|
523
687
|
|
|
524
|
-
This example uses [`LLM::MCP`](https://0x1eef.github.io/x/llm.rb/LLM/MCP.html)
|
|
688
|
+
This example uses [`LLM::MCP`](https://0x1eef.github.io/x/llm.rb/LLM/MCP.html)
|
|
689
|
+
over HTTP so remote GitHub MCP tools run through the same
|
|
690
|
+
`LLM::Context` tool path as local tools. It expects a GitHub token in
|
|
691
|
+
`ENV["GITHUB_PAT"]`. See the
|
|
692
|
+
[deepdive (web)](https://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
|
|
693
|
+
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
525
694
|
|
|
526
695
|
```ruby
|
|
527
696
|
require "llm"
|
|
@@ -534,26 +703,9 @@ mcp = LLM::MCP.http(
|
|
|
534
703
|
persistent: true
|
|
535
704
|
)
|
|
536
705
|
|
|
537
|
-
mcp.start
|
|
538
706
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
539
707
|
ctx.talk("Pull information about my GitHub account.")
|
|
540
708
|
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
541
|
-
mcp.stop
|
|
542
|
-
```
|
|
543
|
-
|
|
544
|
-
For scoped work, `mcp.run do ... end` is shorter and handles cleanup for you:
|
|
545
|
-
|
|
546
|
-
```ruby
|
|
547
|
-
mcp = LLM::MCP.http(
|
|
548
|
-
url: "https://api.githubcopilot.com/mcp/",
|
|
549
|
-
headers: {"Authorization" => "Bearer #{ENV["GITHUB_PAT"]}"},
|
|
550
|
-
persistent: true
|
|
551
|
-
)
|
|
552
|
-
mcp.run do
|
|
553
|
-
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
554
|
-
ctx.talk("Pull information about my GitHub account.")
|
|
555
|
-
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
556
|
-
end
|
|
557
709
|
```
|
|
558
710
|
|
|
559
711
|
## Resources
|