llm.rb 10.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 +116 -10
- data/README.md +116 -32
- 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 +31 -5
- data/lib/llm/file.rb +7 -0
- data/lib/llm/function.rb +1 -1
- 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 +2 -9
- data/lib/llm/response.rb +1 -1
- data/lib/llm/sequel/agent.rb +14 -9
- data/lib/llm/sequel/plugin.rb +8 -7
- 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 +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: 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,20 +2,126 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## v11.0.0
|
|
6
6
|
|
|
7
|
-
Changes since `
|
|
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
|
|
8
20
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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
|
|
12
44
|
|
|
13
|
-
|
|
14
|
-
`
|
|
15
|
-
|
|
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`.
|
|
16
115
|
|
|
17
|
-
|
|
18
|
-
|
|
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.
|
|
19
125
|
|
|
20
126
|
### Breaking
|
|
21
127
|
|
data/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
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>
|
|
@@ -11,7 +11,7 @@
|
|
|
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.0.0-green.svg?" alt="Version">
|
|
15
15
|
</a>
|
|
16
16
|
</p>
|
|
17
17
|
|
|
@@ -21,8 +21,9 @@ 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
|
-
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).
|
|
26
27
|
|
|
27
28
|
It supports OpenAI, OpenAI-compatible endpoints, Anthropic, Google
|
|
28
29
|
Gemini, DeepSeek, xAI, Z.ai, AWS Bedrock, Ollama, and llama.cpp. It
|
|
@@ -134,7 +135,9 @@ or
|
|
|
134
135
|
[LLM::Agent](https://0x1eef.github.io/x/llm.rb/LLM/Agent.html).
|
|
135
136
|
In this example, the MCP server runs over stdio and
|
|
136
137
|
[LLM::Context](https://0x1eef.github.io/x/llm.rb/LLM/Context.html)
|
|
137
|
-
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:
|
|
138
141
|
|
|
139
142
|
```ruby
|
|
140
143
|
require "llm"
|
|
@@ -142,20 +145,96 @@ require "llm"
|
|
|
142
145
|
llm = LLM.openai(key: ENV["KEY"])
|
|
143
146
|
mcp = LLM::MCP.stdio(argv: ["ruby", "server.rb"])
|
|
144
147
|
|
|
145
|
-
mcp.
|
|
148
|
+
mcp.session do
|
|
146
149
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
147
150
|
ctx.talk "Use the available tools to inspect the environment."
|
|
148
151
|
ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
149
152
|
end
|
|
150
153
|
```
|
|
151
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
|
+
|
|
152
231
|
#### Skills
|
|
153
232
|
|
|
154
233
|
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:
|
|
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:
|
|
159
238
|
|
|
160
239
|
```yaml
|
|
161
240
|
---
|
|
@@ -277,6 +356,27 @@ ctx2.restore(string:)
|
|
|
277
356
|
ctx2.talk "What is my favorite language?"
|
|
278
357
|
```
|
|
279
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
|
+
|
|
280
380
|
## Installation
|
|
281
381
|
|
|
282
382
|
```bash
|
|
@@ -315,17 +415,18 @@ or [`ctx.remote_file(...)`](https://0x1eef.github.io/x/llm.rb/LLM/Context.html#r
|
|
|
315
415
|
Those tagged objects carry the metadata the provider adapter needs to turn one
|
|
316
416
|
Ruby prompt into the provider-specific multimodal request schema.
|
|
317
417
|
|
|
318
|
-
|
|
319
|
-
`
|
|
320
|
-
|
|
321
|
-
|
|
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:
|
|
322
423
|
|
|
323
424
|
```ruby
|
|
324
425
|
require "llm"
|
|
325
426
|
|
|
326
427
|
llm = LLM.openai(key: ENV["KEY"])
|
|
327
428
|
ctx = LLM::Context.new(llm)
|
|
328
|
-
ctx.
|
|
429
|
+
puts ctx.ask("Summarize this document.", with: "README.md").content
|
|
329
430
|
```
|
|
330
431
|
|
|
331
432
|
#### Context Compaction
|
|
@@ -602,26 +703,9 @@ mcp = LLM::MCP.http(
|
|
|
602
703
|
persistent: true
|
|
603
704
|
)
|
|
604
705
|
|
|
605
|
-
mcp.start
|
|
606
706
|
ctx = LLM::Context.new(llm, stream: $stdout, tools: mcp.tools)
|
|
607
707
|
ctx.talk("Pull information about my GitHub account.")
|
|
608
708
|
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
709
|
```
|
|
626
710
|
|
|
627
711
|
## Resources
|
|
@@ -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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A::Card
|
|
4
|
+
##
|
|
5
|
+
# Represents a single skill/capability of an agent.
|
|
6
|
+
class Skill
|
|
7
|
+
##
|
|
8
|
+
# @param [Hash] data
|
|
9
|
+
def initialize(data)
|
|
10
|
+
@data = LLM::Object.from(data)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Returns the unique identifier for the skill.
|
|
15
|
+
# @return [String]
|
|
16
|
+
def id
|
|
17
|
+
@data.id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Returns the human-readable skill name.
|
|
22
|
+
# @return [String]
|
|
23
|
+
def name
|
|
24
|
+
@data.name
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
##
|
|
28
|
+
# Returns the detailed skill description.
|
|
29
|
+
# @return [String]
|
|
30
|
+
def description
|
|
31
|
+
@data.description
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Returns capability tags for the skill.
|
|
36
|
+
# @return [Array<String>]
|
|
37
|
+
def tags
|
|
38
|
+
@data.tags || []
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Returns example prompts for the skill.
|
|
43
|
+
# @return [Array<String>]
|
|
44
|
+
def examples
|
|
45
|
+
@data.examples || []
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Returns the supported input media types.
|
|
50
|
+
# @return [Array<String>]
|
|
51
|
+
def input_modes
|
|
52
|
+
@data.inputModes || @data.input_modes || []
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# Returns the supported output media types.
|
|
57
|
+
# @return [Array<String>]
|
|
58
|
+
def output_modes
|
|
59
|
+
@data.outputModes || @data.output_modes || []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# @return [String]
|
|
64
|
+
def inspect
|
|
65
|
+
"#<#{LLM::Utils.object_id(self)} @id=#{id.inspect} @name=#{name.inspect}>"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
data/lib/llm/a2a/card.rb
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A
|
|
4
|
+
##
|
|
5
|
+
# Represents an A2A Agent Card — a self-describing manifest for an
|
|
6
|
+
# agent that provides metadata including the agent's identity,
|
|
7
|
+
# capabilities, skills, supported communication methods, and security
|
|
8
|
+
# requirements.
|
|
9
|
+
#
|
|
10
|
+
# Agent Cards are published at `/.well-known/agent-card.json` and
|
|
11
|
+
# allow clients to discover an agent's capabilities before interacting.
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# a2a = LLM::A2A.rest(url: "https://agent.example.com")
|
|
15
|
+
# card = a2a.card
|
|
16
|
+
# puts card.name # => "GeoSpatial Route Planner Agent"
|
|
17
|
+
# puts card.description # => "Provides advanced route planning..."
|
|
18
|
+
# card.skills.each { |s| puts "#{s.name}: #{s.description}" }
|
|
19
|
+
class Card
|
|
20
|
+
require_relative "card/skill"
|
|
21
|
+
require_relative "card/interface"
|
|
22
|
+
require_relative "card/capabilities"
|
|
23
|
+
require_relative "card/provider"
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# @param [Hash] data The raw Agent Card JSON data
|
|
27
|
+
def initialize(data)
|
|
28
|
+
@data = LLM::Object.from(data)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
##
|
|
32
|
+
# Returns a human-readable name for the agent.
|
|
33
|
+
# @return [String]
|
|
34
|
+
def name
|
|
35
|
+
@data.name
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Returns a human-readable description of the agent.
|
|
40
|
+
# @return [String]
|
|
41
|
+
def description
|
|
42
|
+
@data.description
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Returns the agent version.
|
|
47
|
+
# @return [String]
|
|
48
|
+
def version
|
|
49
|
+
@data.version
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
##
|
|
53
|
+
# Returns the advertised A2A protocol version.
|
|
54
|
+
# @return [String, nil]
|
|
55
|
+
def protocol_version
|
|
56
|
+
@data.protocolVersion || @data.protocol_version
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Returns the documentation URL, when present.
|
|
61
|
+
# @return [String, nil]
|
|
62
|
+
def documentation_url
|
|
63
|
+
@data.documentationUrl || @data.documentation_url
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
##
|
|
67
|
+
# Returns the icon URL, when present.
|
|
68
|
+
# @return [String, nil]
|
|
69
|
+
def icon_url
|
|
70
|
+
@data.iconUrl || @data.icon_url
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Returns the skills provided by the agent.
|
|
75
|
+
# @return [Array<LLM::A2A::Card::Skill>]
|
|
76
|
+
def skills
|
|
77
|
+
@skills ||= (@data.skills || []).map { Skill.new(_1) }
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
##
|
|
81
|
+
# Returns the interfaces supported by the agent.
|
|
82
|
+
# @return [Array<LLM::A2A::Card::Interface>]
|
|
83
|
+
def interfaces
|
|
84
|
+
@interfaces ||= (@data.supportedInterfaces || @data.supported_interfaces || []).map { Interface.new(_1) }
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
##
|
|
88
|
+
# Returns the optional capabilities declaration.
|
|
89
|
+
# @return [LLM::A2A::Card::Capabilities, nil]
|
|
90
|
+
def capabilities
|
|
91
|
+
raw = @data.capabilities
|
|
92
|
+
raw ? Capabilities.new(raw) : nil
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
##
|
|
96
|
+
# Returns the agent card signatures.
|
|
97
|
+
# @return [Array<LLM::Object>]
|
|
98
|
+
def signatures
|
|
99
|
+
@signatures ||= (@data.signatures || []).map { LLM::Object.from(_1) }
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
##
|
|
103
|
+
# Returns the security scheme definitions.
|
|
104
|
+
# @return [Hash<String, Hash>, nil]
|
|
105
|
+
def security_schemes
|
|
106
|
+
@data.securitySchemes || @data.security_schemes
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
##
|
|
110
|
+
# Returns the security requirements.
|
|
111
|
+
# @return [Array<Hash>, nil]
|
|
112
|
+
def security_requirements
|
|
113
|
+
@data.security || @data.security_requirements
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
##
|
|
117
|
+
# Returns the declared provider information.
|
|
118
|
+
# @return [LLM::A2A::Card::Provider, nil]
|
|
119
|
+
def provider
|
|
120
|
+
raw = @data.provider
|
|
121
|
+
raw ? Provider.new(raw) : nil
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
##
|
|
125
|
+
# Returns the default input media types.
|
|
126
|
+
# @return [Array<String>]
|
|
127
|
+
def default_input_modes
|
|
128
|
+
@data.defaultInputModes || @data.default_input_modes || []
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Returns the default output media types.
|
|
133
|
+
# @return [Array<String>]
|
|
134
|
+
def default_output_modes
|
|
135
|
+
@data.defaultOutputModes || @data.default_output_modes || []
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
##
|
|
139
|
+
# @return [String]
|
|
140
|
+
def inspect
|
|
141
|
+
"#<#{LLM::Utils.object_id(self)} @name=#{name.inspect} @skills=#{skills.size}>"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|