llm.rb 10.0.0 → 11.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +158 -10
- data/README.md +145 -44
- 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 +15 -9
- data/lib/llm/active_record/acts_as_llm.rb +4 -4
- data/lib/llm/agent.rb +15 -9
- data/lib/llm/buffer.rb +1 -2
- data/lib/llm/context.rb +52 -5
- data/lib/llm/file.rb +7 -0
- data/lib/llm/function.rb +13 -5
- 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/builder.rb +1 -0
- data/lib/llm/object/kernel.rb +1 -1
- data/lib/llm/object.rb +9 -0
- data/lib/llm/provider.rb +2 -9
- data/lib/llm/response.rb +1 -1
- data/lib/llm/schema.rb +23 -5
- data/lib/llm/sequel/agent.rb +14 -9
- data/lib/llm/sequel/plugin.rb +8 -7
- data/lib/llm/skill.rb +44 -14
- data/lib/llm/tool.rb +57 -27
- data/lib/llm/tracer/telemetry.rb +3 -1
- 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 +44 -0
- data/lib/llm/version.rb +1 -1
- data/lib/llm.rb +23 -4
- data/llm.gemspec +16 -1
- metadata +26 -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: 5bb91948d8cfa006f7512dd0a4fa62f90b42360e3f11a57074870470fdc70d3f
|
|
4
|
+
data.tar.gz: 64b49f633318bc0439252cebca4c3886db4c7676f3cb9a78f4945eefe58b4356
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c56b48185604b22c44f7b4697da56a5fda8359a69e110392abf2510b47a4dc9aedbe5c6a9e64e733fafb5672c34d4a0833448fab5ca9de52c453c8a906080174
|
|
7
|
+
data.tar.gz: a2a9241da0e8749569111573451c99c92ed3569fa7cee0c9178b8aa884a72b27a24f18e19566cef8a16c2e460420a392b83d441708da67a571a9e2648d4d81e2
|
data/CHANGELOG.md
CHANGED
|
@@ -2,20 +2,168 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## v11.1.0
|
|
6
6
|
|
|
7
|
-
Changes since `
|
|
7
|
+
Changes since `v11.0.0`.
|
|
8
|
+
|
|
9
|
+
This release adds the `inherit` directive for skill sub-agents so they can
|
|
10
|
+
inherit access to the local, MCP, and A2A tools available to their parent
|
|
11
|
+
agent. It introduces class-level `required %i[...]` declarations to
|
|
12
|
+
`LLM::Schema` and wraps `LLM::Function#arguments` in `LLM::Object` for
|
|
13
|
+
method-style argument access. The OpenTelemetry tracer now samples all spans
|
|
14
|
+
regardless of environment, and the tool-call loop repair step prevents stale
|
|
15
|
+
history from being sent on follow-up requests.
|
|
16
|
+
|
|
17
|
+
### Add
|
|
8
18
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
* **Add support for the `inherit` directive in skills** <br>
|
|
20
|
+
Add support for the `inherit` directive so a skill sub-agent can
|
|
21
|
+
inherit access to the local, MCP, and A2A tools available to its
|
|
22
|
+
parent agent.
|
|
23
|
+
|
|
24
|
+
* **Add class-level `required %i[...]` support to `LLM::Schema`** <br>
|
|
25
|
+
Add class-level `required %i[...]` declarations to `LLM::Schema`, so
|
|
26
|
+
schema classes can mark existing properties as required the same way
|
|
27
|
+
`LLM::Tool` params already can.
|
|
28
|
+
|
|
29
|
+
* **Wrap function arguments in `LLM::Object`** <br>
|
|
30
|
+
Wrap `LLM::Function#arguments` in `LLM::Object`, so function
|
|
31
|
+
implementations can read arguments with method-style access while
|
|
32
|
+
still invoking runners with keyword arguments.
|
|
33
|
+
|
|
34
|
+
### Fix
|
|
35
|
+
|
|
36
|
+
* **Ensure all traces are sampled regardless of environment** <br>
|
|
37
|
+
Explicitly pass `Samplers::ALWAYS_ON` when creating the OpenTelemetry
|
|
38
|
+
`TracerProvider` so the in-memory exporter always captures every span,
|
|
39
|
+
regardless of the `OTEL_TRACES_SAMPLER` environment variable.
|
|
40
|
+
|
|
41
|
+
* **Always close the tool call loop before sending follow-up requests** <br>
|
|
42
|
+
Add a repair step in `Context#talk` that closes assistant tool-call
|
|
43
|
+
messages without matching tool responses before the next provider
|
|
44
|
+
request is sent. This prevents stale tool-call history from being sent
|
|
45
|
+
on follow-up requests, which some providers reject as invalid.
|
|
46
|
+
|
|
47
|
+
## v11.0.0
|
|
48
|
+
|
|
49
|
+
Changes since `v10.0.0`.
|
|
50
|
+
|
|
51
|
+
This release removes several deprecated or unused APIs, including the `#chat`
|
|
52
|
+
alias from contexts and agents, the `LLM::Function#register` alias, and the
|
|
53
|
+
unused positional `llm` argument from MCP constructors. Generated MCP and A2A
|
|
54
|
+
tools are no longer added to the global tool registry by default.
|
|
55
|
+
|
|
56
|
+
On the additions side, it introduces the A2A (Agent2Agent) protocol client,
|
|
57
|
+
a new `#ask` convenience interface on contexts and agents, one-shot stdio MCP
|
|
58
|
+
requests outside `#session`, `LLM::Function#def` as a short alias for
|
|
59
|
+
`LLM::Function#define`, `LLM::File#exist?`, and `LLM::Tool.a2a?`.
|
|
60
|
+
|
|
61
|
+
### Breaking
|
|
12
62
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
63
|
+
* **Remove the unused `llm` argument from MCP clients** <br>
|
|
64
|
+
Remove the unused positional `llm` argument from `LLM::MCP.new`,
|
|
65
|
+
`LLM::MCP.stdio`, `LLM::MCP.http`, and `LLM.mcp`.
|
|
66
|
+
|
|
67
|
+
* **Stop globally registering generated MCP and A2A tools** <br>
|
|
68
|
+
Generated tools returned by `LLM::Tool.mcp(...)` and
|
|
69
|
+
`LLM::Tool.a2a(...)` are no longer added to the global
|
|
70
|
+
`LLM::Tool.registry` or `LLM::Function.registry`. They still work
|
|
71
|
+
when passed directly to a context or agent, but registry-based lookup
|
|
72
|
+
now only sees normal loaded `LLM::Tool` subclasses.
|
|
73
|
+
|
|
74
|
+
* **Remove `LLM::Function#register`** <br>
|
|
75
|
+
Remove the `LLM::Function#register` alias and prefer
|
|
76
|
+
`LLM::Function#define` or `LLM::Function#def` when binding a
|
|
77
|
+
function to its implementation. The `register` alias was too easy to
|
|
78
|
+
confuse with the class-level `LLM::Tool.register` and
|
|
79
|
+
`LLM::Function.register` registry APIs.
|
|
80
|
+
|
|
81
|
+
* **Remove the `#chat` alias from contexts and agents** <br>
|
|
82
|
+
Remove the `LLM::Context#chat` and `LLM::Agent#chat` aliases. Prefer
|
|
83
|
+
`#talk` for all context and agent turns.
|
|
84
|
+
|
|
85
|
+
### Add
|
|
86
|
+
|
|
87
|
+
* **Add `LLM::Function#def`** <br>
|
|
88
|
+
Add `LLM::Function#def` as a short alias for
|
|
89
|
+
`LLM::Function#define` when binding a function instance to its
|
|
90
|
+
implementation.
|
|
91
|
+
|
|
92
|
+
* **Add `LLM::MCP#session`** <br>
|
|
93
|
+
Add `LLM::MCP#session` as an alias for `LLM::MCP#run`, and prefer it
|
|
94
|
+
in examples for scoped stdio MCP sessions that should stay alive
|
|
95
|
+
across discovery and tool calls.
|
|
96
|
+
|
|
97
|
+
* **Add `#ask` to contexts and agents** <br>
|
|
98
|
+
Add `LLM::Context#ask` and `LLM::Agent#ask` as a RubyLLM-compatible
|
|
99
|
+
convenience interface over `#talk`. `#ask` accepts a prompt, optional
|
|
100
|
+
`with:` attachments, an optional `stream:` target, and an optional
|
|
101
|
+
block for streamed chunks, and returns an `LLM::Response`.
|
|
102
|
+
|
|
103
|
+
* **Add `LLM::File#exist?`** <br>
|
|
104
|
+
Add `LLM::File#exist?` as a small convenience wrapper for checking
|
|
105
|
+
whether a local file exists on disk.
|
|
106
|
+
|
|
107
|
+
* **Allow one-shot stdio MCP requests outside `#session`** <br>
|
|
108
|
+
Allow `mcp.tools`, `mcp.prompts`, `mcp.find_prompt(...)`, and
|
|
109
|
+
`mcp.call_tool(...)` to work outside `mcp.session` by starting and
|
|
110
|
+
stopping a stdio transport on demand when needed. This makes stdio
|
|
111
|
+
MCP usable without an explicit session block, while keeping
|
|
112
|
+
`mcp.session` as the preferred pattern for efficient, stateful
|
|
113
|
+
stdio workflows.
|
|
114
|
+
|
|
115
|
+
* **Add A2A client support** <br>
|
|
116
|
+
Add `LLM::A2A`, a client for the Agent2Agent (A2A) protocol with
|
|
117
|
+
REST and JSON-RPC bindings. Remote agent skills can be exposed as
|
|
118
|
+
`LLM::Tool` classes and used through `LLM::Context` or `LLM::Agent`,
|
|
119
|
+
and the client also supports direct messaging, streaming, task
|
|
120
|
+
operations, push notification configuration, extended agent cards,
|
|
121
|
+
persistent HTTP transport selection, and optional REST `base_path`
|
|
122
|
+
prefixing.
|
|
123
|
+
|
|
124
|
+
Refactor shared MCP/A2A HTTP transport setup into
|
|
125
|
+
`LLM::Transport::Utils`, and extend
|
|
126
|
+
`LLM::Transport::StreamDecoder` to accept a callback block directly.
|
|
127
|
+
|
|
128
|
+
* **Add `LLM::Tool.a2a?`** <br>
|
|
129
|
+
Add `LLM::Tool.a2a?` and mark generated A2A-backed tool classes so
|
|
130
|
+
callers can distinguish them from local or MCP tools.
|
|
131
|
+
|
|
132
|
+
### Fix
|
|
133
|
+
|
|
134
|
+
* **Fix context and agent JSON serialization through `LLM.json`** <br>
|
|
135
|
+
Fix `LLM::Context#to_json` and `LLM::Agent#to_json` to serialize
|
|
136
|
+
through `LLM.json.dump(...)` instead of plain `to_json`.
|
|
137
|
+
|
|
138
|
+
* **Fix block-form ORM agent DSL forwarding** <br>
|
|
139
|
+
Fix block-form `model { ... }`, `tools { ... }`, and
|
|
140
|
+
`schema { ... }` declarations in the ActiveRecord and Sequel agent
|
|
141
|
+
wrappers so persisted agent models configure the internal agent class
|
|
142
|
+
the same way as `LLM::Agent`.
|
|
143
|
+
|
|
144
|
+
* **Fix missing `skills` in ORM agent wrappers** <br>
|
|
145
|
+
Fix the ActiveRecord and Sequel agent wrappers to expose `skills`, so
|
|
146
|
+
persisted agent models can declare skills the same way as
|
|
147
|
+
`LLM::Agent`.
|
|
148
|
+
|
|
149
|
+
* **Fix `acts_as_agent#ctx` return type** <br>
|
|
150
|
+
Fix the ActiveRecord `acts_as_agent` wrapper so its `ctx` helper
|
|
151
|
+
returns the wrapped `LLM::Agent` instead of returning the underlying
|
|
152
|
+
`LLM::Context` directly.
|
|
153
|
+
|
|
154
|
+
## v10.0.0
|
|
155
|
+
|
|
156
|
+
Changes since `v9.0.0`.
|
|
16
157
|
|
|
17
|
-
|
|
18
|
-
|
|
158
|
+
This release removes the `LLM::Context#respond` method, and
|
|
159
|
+
also removes the deprecated `LLM::Bot` alias. **All** class-level
|
|
160
|
+
agent tunables can now be resolved lazily via a Symbol (method name),
|
|
161
|
+
or a Proc. The `LLM::Agent` class can now confirm a tool call
|
|
162
|
+
before it happens, and the `LLM::Schema` class has been extended
|
|
163
|
+
to support `Array[String,Integer]` as a shorthand for
|
|
164
|
+
`Array[AnyOf[String, Integer]]`. The `LLM::Stream` class has
|
|
165
|
+
had its public method surface reduced to help avoid accidental
|
|
166
|
+
collisions.
|
|
19
167
|
|
|
20
168
|
### Breaking
|
|
21
169
|
|
data/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
<p align="center">
|
|
2
|
-
<a href="llm.rb">
|
|
2
|
+
<a href="https://github.com/llmrb/llm.rb">
|
|
3
3
|
<img src="https://github.com/llmrb/llm.rb/raw/main/llm.png" width="200" height="200" border="0" alt="llm.rb">
|
|
4
4
|
</a>
|
|
5
5
|
</p>
|
|
6
6
|
<p align="center">
|
|
7
|
-
<a href="https://
|
|
8
|
-
<img src="https://img.shields.io/badge/docs-
|
|
7
|
+
<a href="https://llmrb.github.io/llm.rb">
|
|
8
|
+
<img src="https://img.shields.io/badge/docs-llmrb.github.io-blue.svg" alt="Official llm.rb website">
|
|
9
9
|
</a>
|
|
10
10
|
<a href="https://opensource.org/license/0bsd">
|
|
11
11
|
<img src="https://img.shields.io/badge/License-0BSD-orange.svg?" alt="License">
|
|
12
12
|
</a>
|
|
13
13
|
<a href="https://github.com/llmrb/llm.rb/tags">
|
|
14
|
-
<img src="https://img.shields.io/badge/version-
|
|
14
|
+
<img src="https://img.shields.io/badge/version-11.1.0-green.svg?" alt="Version">
|
|
15
15
|
</a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -21,8 +21,8 @@ llm.rb is Ruby's most capable AI runtime.
|
|
|
21
21
|
|
|
22
22
|
It runs on Ruby's standard library by default. loads optional pieces
|
|
23
23
|
only when needed, and offers a single runtime for providers, agents,
|
|
24
|
-
tools, skills, MCP,
|
|
25
|
-
|
|
24
|
+
tools, skills, MCP, A2A (Agent2Agent), RAG (vector stores & embeddings),
|
|
25
|
+
streaming, files, and persisted state.
|
|
26
26
|
|
|
27
27
|
It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
|
|
28
28
|
Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. It
|
|
@@ -30,6 +30,11 @@ also includes built-in ActiveRecord and Sequel support, plus concurrent
|
|
|
30
30
|
tool execution through threads, tasks (via async gem), fibers, ractors,
|
|
31
31
|
and fork (via xchan.rb gem).
|
|
32
32
|
|
|
33
|
+
As a bonus, llm.rb is also available to embedded systems [via mruby](https://github.com/llmrb/mruby-llm#readme),
|
|
34
|
+
to the browser and edge devices [via WebAssembly](https://github.com/llmrb/wasm-llm#readme),
|
|
35
|
+
and has first-class [Rails support](https://github.com/llmrb/rails-llm#readme)
|
|
36
|
+
via a separate gem.
|
|
37
|
+
|
|
33
38
|
## Quick start
|
|
34
39
|
|
|
35
40
|
#### LLM::Context
|
|
@@ -89,7 +94,7 @@ class Agent < LLM::Agent
|
|
|
89
94
|
confirm "delete-file"
|
|
90
95
|
|
|
91
96
|
def on_tool_confirmation(fn, strategy)
|
|
92
|
-
path = fn.arguments
|
|
97
|
+
path = fn.arguments.path
|
|
93
98
|
if path.start_with?("/tmp/")
|
|
94
99
|
fn.spawn(strategy).wait
|
|
95
100
|
else
|
|
@@ -134,7 +139,9 @@ or
|
|
|
134
139
|
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
|
|
135
140
|
In this example, the MCP server runs over stdio and
|
|
136
141
|
[LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
137
|
-
uses the same tool loop as local tools
|
|
142
|
+
uses the same tool loop as local tools. For **stdio**, `mcp.session`
|
|
143
|
+
is the preferred pattern because it keeps one MCP session alive across
|
|
144
|
+
discovery and tool calls:
|
|
138
145
|
|
|
139
146
|
```ruby
|
|
140
147
|
require "llm"
|
|
@@ -142,20 +149,96 @@ require "llm"
|
|
|
142
149
|
llm = LLM.openai(key: ENV["KEY"])
|
|
143
150
|
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
144
151
|
|
|
145
|
-
mcp.
|
|
152
|
+
mcp.session do
|
|
146
153
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
147
154
|
ctx.talk "Use the available tools to inspect the environment."
|
|
148
155
|
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
149
156
|
end
|
|
150
157
|
```
|
|
151
158
|
|
|
159
|
+
MCP can also be used without `session`. Although it works it is generally
|
|
160
|
+
not recommended for the **stdio** transport because it is inefficient
|
|
161
|
+
to start and stop a fresh MCP process for tool discovery and each tool
|
|
162
|
+
call:
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
require "llm"
|
|
166
|
+
|
|
167
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
168
|
+
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
169
|
+
|
|
170
|
+
ctx = LLM::Context.new(llm, tools: mcp.tools)
|
|
171
|
+
ctx.talk("Use the available tools to inspect the environment.")
|
|
172
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
The HTTP transport can be used with or without the `session` method,
|
|
176
|
+
and unlike the stdio transport it can remain efficient without the
|
|
177
|
+
`session` method through a persistent connection pool that is available
|
|
178
|
+
through the [LLM::Transport.net_http_persistent](https://0x1eef.github.io/x/llm.rb/LLM/Transport.html#method-c-net_http_persistent) transport:
|
|
179
|
+
|
|
180
|
+
```ruby
|
|
181
|
+
require "llm"
|
|
182
|
+
|
|
183
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
184
|
+
mcp = LLM::MCP.http(
|
|
185
|
+
url: "https://remote-mcp.example.com",
|
|
186
|
+
transport: LLM::Transport.net_http_persistent
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
ctx = LLM::Context.new(llm, tools: mcp.tools)
|
|
190
|
+
ctx.talk("Use the available tools to inspect the environment.")
|
|
191
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
#### A2A (Agent 2 Agent)
|
|
195
|
+
|
|
196
|
+
The
|
|
197
|
+
[LLM::A2A](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html)
|
|
198
|
+
object lets llm.rb use skills provided by a remote A2A agent. Those
|
|
199
|
+
skills are exposed through the same runtime as local tools, so you can
|
|
200
|
+
pass them to either
|
|
201
|
+
[LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
202
|
+
or
|
|
203
|
+
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
|
|
204
|
+
|
|
205
|
+
Use remote skills as local tools:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
require "llm"
|
|
209
|
+
|
|
210
|
+
a2a = LLM::A2A.rest(
|
|
211
|
+
url: "https://remote-agent.example.com",
|
|
212
|
+
headers: {"Authorization" => "Bearer token"}
|
|
213
|
+
)
|
|
214
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
215
|
+
ctx = LLM::Context.new(llm, tools: a2a.skills)
|
|
216
|
+
ctx.talk "Analyze this CSV and summarize the trends."
|
|
217
|
+
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Use persistent HTTP connections:
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
require "llm"
|
|
224
|
+
|
|
225
|
+
a2a = LLM::A2A.rest(
|
|
226
|
+
url: "https://remote-agent.example.com",
|
|
227
|
+
transport: LLM::Transport.net_http_persistent
|
|
228
|
+
)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
For more on direct messaging, task operations, push notification
|
|
232
|
+
configs, and JSON-RPC, see the
|
|
233
|
+
[LLM::A2A API docs](https://0x1eef.github.io/x/llm.rb/LLM/A2A.html).
|
|
234
|
+
|
|
152
235
|
#### Skills
|
|
153
236
|
|
|
154
237
|
Skills are reusable instructions loaded from a `SKILL.md` directory. They let
|
|
155
|
-
you package behavior and tool access together, and they plug into the
|
|
156
|
-
runtime as tools, agents, and
|
|
157
|
-
subagent with the skill instructions, access to only the tools
|
|
158
|
-
skill, and recent conversation context:
|
|
238
|
+
you package behavior and tool access together, and they plug into the
|
|
239
|
+
same runtime as tools, agents, MCP, and A2A. When a skill runs, llm.rb
|
|
240
|
+
spawns a subagent with the skill instructions, access to only the tools
|
|
241
|
+
listed in the skill, and recent conversation context:
|
|
159
242
|
|
|
160
243
|
```yaml
|
|
161
244
|
---
|
|
@@ -181,6 +264,19 @@ llm = LLM.openai(key: ENV["KEY"])
|
|
|
181
264
|
ReleaseAgent.new(llm, stream: $stdout).talk("Prepare the next release.")
|
|
182
265
|
```
|
|
183
266
|
|
|
267
|
+
A skill can also have its sub-agent inherit the parents tools through the
|
|
268
|
+
`inherit` directive. The `inherit` directive has coverage for the "classic"
|
|
269
|
+
tools (a subclass of [LLM::Tool](https://0x1eef.github.io/x/llm.rb/LLM/Tool.html)),
|
|
270
|
+
MCP tools, and A2A tools that a parent context or agent has access to:
|
|
271
|
+
|
|
272
|
+
```yaml
|
|
273
|
+
---
|
|
274
|
+
name: release
|
|
275
|
+
description: Prepare a release
|
|
276
|
+
tools: inherit
|
|
277
|
+
---
|
|
278
|
+
```
|
|
279
|
+
|
|
184
280
|
#### LLM::Stream
|
|
185
281
|
|
|
186
282
|
The
|
|
@@ -277,6 +373,27 @@ ctx2.restore(string:)
|
|
|
277
373
|
ctx2.talk "What is my favorite language?"
|
|
278
374
|
```
|
|
279
375
|
|
|
376
|
+
#### ask
|
|
377
|
+
|
|
378
|
+
[`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
379
|
+
also provides `ask`, a convenience interface that is compatible with
|
|
380
|
+
RubyLLM's `ask` method. It accepts a prompt, an optional `with:`
|
|
381
|
+
attachment path or paths, an optional `stream:` target, and an optional
|
|
382
|
+
block that chunks are yielded to. It returns an
|
|
383
|
+
[`LLM::Response`](https://0x1eef.github.io/x/llm.rb/LLM/Response.html),
|
|
384
|
+
so use `.content` when you want the text directly:
|
|
385
|
+
|
|
386
|
+
```ruby
|
|
387
|
+
require "llm"
|
|
388
|
+
|
|
389
|
+
llm = LLM.openai(key: ENV["KEY"])
|
|
390
|
+
ctx = LLM::Context.new(llm)
|
|
391
|
+
|
|
392
|
+
puts ctx.ask("Hello world").content
|
|
393
|
+
puts ctx.ask("Summarize this document.", with: "README.md").content
|
|
394
|
+
ctx.ask("Stream this reply.") { $stdout << _1 }
|
|
395
|
+
```
|
|
396
|
+
|
|
280
397
|
## Installation
|
|
281
398
|
|
|
282
399
|
```bash
|
|
@@ -289,7 +406,7 @@ gem install llm.rb
|
|
|
289
406
|
|
|
290
407
|
This example uses [`LLM::Context`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
291
408
|
directly for an interactive REPL. <br> See the
|
|
292
|
-
[deepdive (web)](https://
|
|
409
|
+
[deepdive (web)](https://llmrb.github.io/llm.rb/) or
|
|
293
410
|
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
294
411
|
|
|
295
412
|
```ruby
|
|
@@ -315,17 +432,18 @@ or [`ctx.remote_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#r
|
|
|
315
432
|
Those tagged objects carry the metadata the provider adapter needs to turn one
|
|
316
433
|
Ruby prompt into the provider-specific multimodal request schema.
|
|
317
434
|
|
|
318
|
-
|
|
319
|
-
`
|
|
320
|
-
|
|
321
|
-
|
|
435
|
+
If the model understands that file type, you can attach a local file directly
|
|
436
|
+
with `ctx.ask(..., with: path)` instead of uploading it first through a
|
|
437
|
+
provider Files API. Under the hood, llm.rb tags the path as a
|
|
438
|
+
[`ctx.local_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#local_file-instance_method)
|
|
439
|
+
object:
|
|
322
440
|
|
|
323
441
|
```ruby
|
|
324
442
|
require "llm"
|
|
325
443
|
|
|
326
444
|
llm = LLM.openai(key: ENV["KEY"])
|
|
327
445
|
ctx = LLM::Context.new(llm)
|
|
328
|
-
ctx.
|
|
446
|
+
puts ctx.ask("Summarize this document.", with: "README.md").content
|
|
329
447
|
```
|
|
330
448
|
|
|
331
449
|
#### Context Compaction
|
|
@@ -341,7 +459,7 @@ different model from the main context. `token_threshold:` accepts either a
|
|
|
341
459
|
fixed token count or a percentage string like `"90%"`, which resolves
|
|
342
460
|
against the active model context window and triggers compaction once total
|
|
343
461
|
token usage goes over that percentage. See the
|
|
344
|
-
[deepdive (web)](https://
|
|
462
|
+
[deepdive (web)](https://llmrb.github.io/llm.rb/) or
|
|
345
463
|
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
346
464
|
|
|
347
465
|
```ruby
|
|
@@ -374,7 +492,7 @@ ctx = LLM::Context.new(
|
|
|
374
492
|
This example uses [`LLM::Stream`](https://0x1eef.github.io/x/llm.rb/LLM/Stream.html)
|
|
375
493
|
with the OpenAI Responses API so reasoning output is streamed separately from
|
|
376
494
|
visible assistant output. See the
|
|
377
|
-
[deepdive (web)](https://
|
|
495
|
+
[deepdive (web)](https://llmrb.github.io/llm.rb/) or
|
|
378
496
|
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
379
497
|
|
|
380
498
|
To use the Responses API (OpenAI-specific), initialize a
|
|
@@ -409,7 +527,7 @@ ctx.talk("Solve 17 * 19 and show your work.")
|
|
|
409
527
|
|
|
410
528
|
Need to cancel a stream? llm.rb has you covered through
|
|
411
529
|
[`LLM::Context#interrupt!`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#interrupt-21-instance_method).
|
|
412
|
-
<br> See the [deepdive (web)](https://
|
|
530
|
+
<br> See the [deepdive (web)](https://llmrb.github.io/llm.rb/)
|
|
413
531
|
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
414
532
|
|
|
415
533
|
```ruby
|
|
@@ -437,7 +555,7 @@ The `plugin :llm` integration wraps
|
|
|
437
555
|
wrappers, its built-in persistence contract is the serialized `data` column,
|
|
438
556
|
while `provider:` resolves a real `LLM::Provider` instance and `context:`
|
|
439
557
|
injects defaults such as `model:`. <br> See the
|
|
440
|
-
[deepdive (web)](https://
|
|
558
|
+
[deepdive (web)](https://llmrb.github.io/llm.rb/) or
|
|
441
559
|
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
442
560
|
|
|
443
561
|
```ruby
|
|
@@ -473,7 +591,7 @@ one serialized `data` column. If your app has provider, model, or usage
|
|
|
473
591
|
columns, provide them to llm.rb through `provider:` and `context:` instead of
|
|
474
592
|
relying on reserved wrapper columns.
|
|
475
593
|
|
|
476
|
-
See the [deepdive (web)](https://
|
|
594
|
+
See the [deepdive (web)](https://llmrb.github.io/llm.rb/)
|
|
477
595
|
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
478
596
|
|
|
479
597
|
```ruby
|
|
@@ -530,7 +648,7 @@ manages tool execution for you. Like `acts_as_llm`, its built-in persistence
|
|
|
530
648
|
contract is one serialized `data` column. If your app has provider or model
|
|
531
649
|
columns, provide them to llm.rb through your hooks and agent DSL.
|
|
532
650
|
|
|
533
|
-
See the [deepdive (web)](https://
|
|
651
|
+
See the [deepdive (web)](https://llmrb.github.io/llm.rb/)
|
|
534
652
|
or [deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
535
653
|
|
|
536
654
|
```ruby
|
|
@@ -588,7 +706,7 @@ This example uses [`LLM::MCP`](https://0x1eef.github.io/x/llm.rb/LLM/MCP.html)
|
|
|
588
706
|
over HTTP so remote GitHub MCP tools run through the same
|
|
589
707
|
`LLM::Context` tool path as local tools. It expects a GitHub token in
|
|
590
708
|
`ENV["GITHUB_PAT"]`. See the
|
|
591
|
-
[deepdive (web)](https://
|
|
709
|
+
[deepdive (web)](https://llmrb.github.io/llm.rb/) or
|
|
592
710
|
[deepdive (markdown)](resources/deepdive.md) for more examples.
|
|
593
711
|
|
|
594
712
|
```ruby
|
|
@@ -602,31 +720,14 @@ mcp = LLM::MCP.http(
|
|
|
602
720
|
persistent: true
|
|
603
721
|
)
|
|
604
722
|
|
|
605
|
-
mcp.start
|
|
606
723
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
607
724
|
ctx.talk("Pull information about my GitHub account.")
|
|
608
725
|
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
609
|
-
mcp.stop
|
|
610
|
-
```
|
|
611
|
-
|
|
612
|
-
For scoped work, `mcp.run do ... end` is shorter and handles cleanup for you:
|
|
613
|
-
|
|
614
|
-
```ruby
|
|
615
|
-
mcp = LLM::MCP.http(
|
|
616
|
-
url: "https://api.githubcopilot.com/mcp/",
|
|
617
|
-
headers: {"Authorization" => "Bearer #{ENV["GITHUB_PAT"]}"},
|
|
618
|
-
persistent: true
|
|
619
|
-
)
|
|
620
|
-
mcp.run do
|
|
621
|
-
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
622
|
-
ctx.talk("Pull information about my GitHub account.")
|
|
623
|
-
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
624
|
-
end
|
|
625
726
|
```
|
|
626
727
|
|
|
627
728
|
## Resources
|
|
628
729
|
|
|
629
|
-
- [deepdive (web)](https://
|
|
730
|
+
- [deepdive (web)](https://llmrb.github.io/llm.rb/) and
|
|
630
731
|
[deepdive (markdown)](resources/deepdive.md) are the examples guide.
|
|
631
732
|
- [relay](https://github.com/llmrb/relay) shows a real application built on
|
|
632
733
|
top of llm.rb.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A::Card
|
|
4
|
+
##
|
|
5
|
+
# Represents the agent's optional capabilities.
|
|
6
|
+
class Capabilities
|
|
7
|
+
##
|
|
8
|
+
# @param [Hash] data
|
|
9
|
+
def initialize(data)
|
|
10
|
+
@data = LLM::Object.from(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Returns whether the agent supports streaming.
|
|
15
|
+
# @return [Boolean]
|
|
16
|
+
def streaming
|
|
17
|
+
@data.streaming == true
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Returns whether the agent supports push notifications.
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def push_notifications
|
|
24
|
+
@data.pushNotifications == true
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Returns whether the agent exposes an extended card.
|
|
29
|
+
# @return [Boolean]
|
|
30
|
+
def extended_agent_card
|
|
31
|
+
@data.extendedAgentCard == true
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Returns the declared agent extensions.
|
|
36
|
+
# @return [Array<LLM::Object>]
|
|
37
|
+
def extensions
|
|
38
|
+
@extensions ||= (@data.extensions || []).map { LLM::Object.from(_1) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A::Card
|
|
4
|
+
##
|
|
5
|
+
# Represents a protocol interface supported by the agent.
|
|
6
|
+
class Interface
|
|
7
|
+
##
|
|
8
|
+
# @param [Hash] data
|
|
9
|
+
def initialize(data)
|
|
10
|
+
@data = LLM::Object.from(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Returns the interface URL.
|
|
15
|
+
# @return [String]
|
|
16
|
+
def url
|
|
17
|
+
@data.url
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Returns the protocol binding name.
|
|
22
|
+
# @return [String]
|
|
23
|
+
def protocol_binding
|
|
24
|
+
@data.protocolBinding || @data.protocol_binding
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Returns the A2A protocol version.
|
|
29
|
+
# @return [String]
|
|
30
|
+
def protocol_version
|
|
31
|
+
@data.protocolVersion || @data.protocol_version
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A::Card
|
|
4
|
+
##
|
|
5
|
+
# Represents the service provider of an agent.
|
|
6
|
+
class Provider
|
|
7
|
+
##
|
|
8
|
+
# @param [Hash] data
|
|
9
|
+
def initialize(data)
|
|
10
|
+
@data = LLM::Object.from(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Returns the provider website URL.
|
|
15
|
+
# @return [String]
|
|
16
|
+
def url
|
|
17
|
+
@data.url
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Returns the provider organization name.
|
|
22
|
+
# @return [String]
|
|
23
|
+
def organization
|
|
24
|
+
@data.organization
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|