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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +158 -10
  3. data/README.md +145 -44
  4. data/lib/llm/a2a/card/capabilities.rb +41 -0
  5. data/lib/llm/a2a/card/interface.rb +34 -0
  6. data/lib/llm/a2a/card/provider.rb +27 -0
  7. data/lib/llm/a2a/card/skill.rb +68 -0
  8. data/lib/llm/a2a/card.rb +144 -0
  9. data/lib/llm/a2a/error.rb +49 -0
  10. data/lib/llm/a2a/notifications.rb +53 -0
  11. data/lib/llm/a2a/tasks.rb +55 -0
  12. data/lib/llm/a2a/transport/http.rb +131 -0
  13. data/lib/llm/a2a.rb +452 -0
  14. data/lib/llm/active_record/acts_as_agent.rb +15 -9
  15. data/lib/llm/active_record/acts_as_llm.rb +4 -4
  16. data/lib/llm/agent.rb +15 -9
  17. data/lib/llm/buffer.rb +1 -2
  18. data/lib/llm/context.rb +52 -5
  19. data/lib/llm/file.rb +7 -0
  20. data/lib/llm/function.rb +13 -5
  21. data/lib/llm/mcp/transport/http.rb +5 -18
  22. data/lib/llm/mcp/transport/stdio.rb +7 -0
  23. data/lib/llm/mcp.rb +20 -17
  24. data/lib/llm/message.rb +1 -1
  25. data/lib/llm/object/builder.rb +1 -0
  26. data/lib/llm/object/kernel.rb +1 -1
  27. data/lib/llm/object.rb +9 -0
  28. data/lib/llm/provider.rb +2 -9
  29. data/lib/llm/response.rb +1 -1
  30. data/lib/llm/schema.rb +23 -5
  31. data/lib/llm/sequel/agent.rb +14 -9
  32. data/lib/llm/sequel/plugin.rb +8 -7
  33. data/lib/llm/skill.rb +44 -14
  34. data/lib/llm/tool.rb +57 -27
  35. data/lib/llm/tracer/telemetry.rb +3 -1
  36. data/lib/llm/tracer.rb +1 -1
  37. data/lib/llm/transport/http.rb +1 -1
  38. data/lib/llm/transport/stream_decoder.rb +6 -3
  39. data/lib/llm/transport/utils.rb +35 -0
  40. data/lib/llm/transport.rb +1 -0
  41. data/lib/llm/utils.rb +44 -0
  42. data/lib/llm/version.rb +1 -1
  43. data/lib/llm.rb +23 -4
  44. data/llm.gemspec +16 -1
  45. metadata +26 -3
  46. 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: 6ba756238fa72e58ba774567a0c8e2a6d7351cb6313f9c8c08cbdeec8ec9cfa4
4
- data.tar.gz: cba8295670dab2843cec902ae97b7ae14e775359380ba401ca5a0066eb60ad0e
3
+ metadata.gz: 5bb91948d8cfa006f7512dd0a4fa62f90b42360e3f11a57074870470fdc70d3f
4
+ data.tar.gz: 64b49f633318bc0439252cebca4c3886db4c7676f3cb9a78f4945eefe58b4356
5
5
  SHA512:
6
- metadata.gz: b8347b2adfe05a4700ec42e0ed5992a1332355bd20330590d8b3de214d980476a490855ff7e69b5b36c75f3684304c4ee61bdff9ecbcf8001f0b477b8010d064
7
- data.tar.gz: a41512ffbc52b3665118161251441152389ca9daba1a6f4e010303490938dc33393da62f5e821521b2a9f4b45d85fd219b558fa7d2e185c24f43777d26e36a14
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
- ## v10.0.0
5
+ ## v11.1.0
6
6
 
7
- Changes since `v9.0.0`.
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
- This release unifies context turns under `#talk`, removes the
10
- deprecated `LLM::Bot` alias, and adds shared option resolution
11
- through `LLM::Utils`.
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
- Class-level agent tunables can now be resolved lazily via Proc,
14
- `Array[...]` schema/tool param types are supported, and a `key?`
15
- method has been added on providers.
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
- Agent tool confirmation hooks let selected tools be approved or
18
- cancelled before execution. Keep reading to learn more.
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://0x1eef.github.io/x/llm.rb?rebuild=1">
8
- <img src="https://img.shields.io/badge/docs-0x1eef.github.io-blue.svg" alt="RubyDoc">
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-10.0.0-green.svg?" alt="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, streaming, files, and persisted state. As a bonus,
25
- llm.rb is also [available for mruby](https://github.com/llmrb/mruby-llm).
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["path"] || fn.arguments[:path]
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.run do
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 same
156
- runtime as tools, agents, and MCP. When a skill runs, llm.rb spawns a
157
- subagent with the skill instructions, access to only the tools listed in the
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
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
- `ctx.local_file(path)` tags a local path as a `:local_file` object around
319
- `LLM.File(path)`. If the model understands that file type, you can include it
320
- directly in the prompt array instead of uploading it first through a provider
321
- Files API:
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.talk ["Summarize this document.", ctx.local_file("README.md")]
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
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://0x1eef.github.io/x/llm.rb/file.deepdive.html)
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
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://0x1eef.github.io/x/llm.rb/file.deepdive.html)
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://0x1eef.github.io/x/llm.rb/file.deepdive.html)
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) or
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://0x1eef.github.io/x/llm.rb/file.deepdive.html) and
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