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
data/lib/llm/a2a.rb
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# The {LLM::A2A} class provides access to agents that implement the
|
|
5
|
+
# Agent2Agent (A2A) Protocol. A2A defines a standard way for
|
|
6
|
+
# independent AI agents to discover each other's capabilities,
|
|
7
|
+
# negotiate interaction modalities, and collaborate on tasks.
|
|
8
|
+
#
|
|
9
|
+
# In llm.rb, {LLM::A2A} supports both HTTP+JSON/REST and JSON-RPC 2.0
|
|
10
|
+
# protocol bindings and focuses on discovering agent skills that can be
|
|
11
|
+
# used through {LLM::Context} and {LLM::Agent}.
|
|
12
|
+
#
|
|
13
|
+
# Requests can be made concurrently and responses are matched by task id.
|
|
14
|
+
#
|
|
15
|
+
# @example REST binding (default)
|
|
16
|
+
# a2a = LLM::A2A.rest(url: "https://agent.example.com")
|
|
17
|
+
# card = a2a.card
|
|
18
|
+
# puts card.skills.map(&:name)
|
|
19
|
+
# task = a2a.send_message("What is the weather in Tokyo?").task
|
|
20
|
+
# a2a.tasks.get(task.id)
|
|
21
|
+
#
|
|
22
|
+
# @example JSON-RPC binding
|
|
23
|
+
# a2a = LLM::A2A.jsonrpc(url: "https://agent.example.com")
|
|
24
|
+
#
|
|
25
|
+
# @example Using skills as tools in a context
|
|
26
|
+
# llm = LLM.openai(key: ENV["KEY"])
|
|
27
|
+
# a2a = LLM::A2A.rest(url: "https://agent.example.com")
|
|
28
|
+
# ctx = LLM::Context.new(llm, tools: a2a.skills)
|
|
29
|
+
# ctx.talk("Analyze this data using the remote agent.")
|
|
30
|
+
# ctx.talk(ctx.wait(:call)) while ctx.functions?
|
|
31
|
+
class LLM::A2A
|
|
32
|
+
require_relative "a2a/card"
|
|
33
|
+
require_relative "a2a/error"
|
|
34
|
+
require_relative "a2a/tasks"
|
|
35
|
+
require_relative "a2a/notifications"
|
|
36
|
+
require_relative "a2a/transport/http"
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# @param [Symbol] binding
|
|
40
|
+
# The protocol binding to use. One of `:rest` (HTTP+JSON/REST) or
|
|
41
|
+
# `:jsonrpc` (JSON-RPC 2.0). Defaults to `:rest`.
|
|
42
|
+
# @param [Object] transport
|
|
43
|
+
# The transport used to communicate with the remote A2A agent
|
|
44
|
+
# @param [String] base_path
|
|
45
|
+
# Optional base path prefix for REST endpoints
|
|
46
|
+
# @param [String] protocol_version
|
|
47
|
+
# The expected A2A protocol version. Defaults to `"1.0"`.
|
|
48
|
+
# @return [LLM::A2A]
|
|
49
|
+
def initialize(transport:, binding: :rest, base_path: "", protocol_version: "1.0")
|
|
50
|
+
@binding = binding
|
|
51
|
+
@base_path = LLM::Utils.normalize_base_path(base_path)
|
|
52
|
+
@protocol_version = protocol_version
|
|
53
|
+
@transport = transport
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
##
|
|
57
|
+
# Builds an A2A client over HTTP.
|
|
58
|
+
# @param [String] url
|
|
59
|
+
# The base URL of the A2A agent (e.g., "https://agent.example.com")
|
|
60
|
+
# @param [Hash<String, String>] headers
|
|
61
|
+
# Extra HTTP headers to include in requests (e.g., Authorization)
|
|
62
|
+
# @param [Integer, nil] timeout
|
|
63
|
+
# The timeout in seconds for HTTP requests
|
|
64
|
+
# @param [LLM::Transport, Class, nil] transport
|
|
65
|
+
# Optional override with any {LLM::Transport} instance or subclass
|
|
66
|
+
# @param [Symbol] binding
|
|
67
|
+
# The protocol binding to use. One of `:rest` or `:jsonrpc`
|
|
68
|
+
# @param [String] base_path
|
|
69
|
+
# Optional base path prefix for REST endpoints
|
|
70
|
+
# @param [String] protocol_version
|
|
71
|
+
# The expected A2A protocol version. Defaults to `"1.0"`.
|
|
72
|
+
# @return [LLM::A2A]
|
|
73
|
+
def self.http(url:, headers: {}, timeout: 30, transport: nil, binding: :rest, base_path: "", protocol_version: "1.0")
|
|
74
|
+
new(
|
|
75
|
+
binding:,
|
|
76
|
+
base_path:,
|
|
77
|
+
protocol_version:,
|
|
78
|
+
transport: Transport::HTTP.new(
|
|
79
|
+
url:,
|
|
80
|
+
headers:,
|
|
81
|
+
timeout:,
|
|
82
|
+
transport:,
|
|
83
|
+
protocol_version:
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Builds an A2A client over HTTP+JSON/REST.
|
|
90
|
+
# @param [String] url
|
|
91
|
+
# @param [Hash<String, String>] headers
|
|
92
|
+
# @param [Integer, nil] timeout
|
|
93
|
+
# @param [LLM::Transport, Class, nil] transport
|
|
94
|
+
# @return [LLM::A2A]
|
|
95
|
+
def self.rest(url:, headers: {}, timeout: 30, transport: nil, base_path: "", protocol_version: "1.0")
|
|
96
|
+
http(
|
|
97
|
+
url:,
|
|
98
|
+
headers:,
|
|
99
|
+
timeout:,
|
|
100
|
+
transport:,
|
|
101
|
+
binding: :rest,
|
|
102
|
+
base_path:,
|
|
103
|
+
protocol_version:
|
|
104
|
+
)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
##
|
|
108
|
+
# Builds an A2A client over HTTP+JSON with JSON-RPC 2.0.
|
|
109
|
+
# @param [String] url
|
|
110
|
+
# @param [Hash<String, String>] headers
|
|
111
|
+
# @param [Integer, nil] timeout
|
|
112
|
+
# @param [LLM::Transport, Class, nil] transport
|
|
113
|
+
# @return [LLM::A2A]
|
|
114
|
+
def self.jsonrpc(url:, headers: {}, timeout: 30, transport: nil, base_path: "", protocol_version: "1.0")
|
|
115
|
+
http(
|
|
116
|
+
url:,
|
|
117
|
+
headers:,
|
|
118
|
+
timeout:,
|
|
119
|
+
transport:,
|
|
120
|
+
binding: :jsonrpc,
|
|
121
|
+
base_path:,
|
|
122
|
+
protocol_version:
|
|
123
|
+
)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
##
|
|
127
|
+
# Returns the active protocol binding.
|
|
128
|
+
# @return [Symbol]
|
|
129
|
+
attr_reader :binding
|
|
130
|
+
|
|
131
|
+
##
|
|
132
|
+
# Returns the remote agent card.
|
|
133
|
+
#
|
|
134
|
+
# The agent card is fetched from `/.well-known/agent-card.json` and
|
|
135
|
+
# cached for the lifetime of this client instance.
|
|
136
|
+
# @return [LLM::A2A::Card]
|
|
137
|
+
def card
|
|
138
|
+
return @card if defined?(@card)
|
|
139
|
+
@card = LLM::A2A::Card.new(transport.get("/.well-known/agent-card.json"))
|
|
140
|
+
end
|
|
141
|
+
alias_method :agent_card, :card
|
|
142
|
+
|
|
143
|
+
##
|
|
144
|
+
# Returns the agent's skills adapted as callable tools.
|
|
145
|
+
#
|
|
146
|
+
# Each skill in the agent card is mapped to an {LLM::Tool} subclass
|
|
147
|
+
# that wraps a {#send_message} call. When the tool is called, it
|
|
148
|
+
# sends a message to the remote agent and returns the task artifacts
|
|
149
|
+
# as the result.
|
|
150
|
+
# @return [Array<Class<LLM::Tool>>]
|
|
151
|
+
def skills
|
|
152
|
+
@skills ||= card.skills.map { LLM::Tool.a2a(self, _1) }
|
|
153
|
+
end
|
|
154
|
+
alias_method :tools, :skills
|
|
155
|
+
|
|
156
|
+
##
|
|
157
|
+
# Returns task-oriented A2A operations.
|
|
158
|
+
# @return [LLM::A2A::Tasks]
|
|
159
|
+
def tasks
|
|
160
|
+
@tasks ||= LLM::A2A::Tasks.new(self)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
##
|
|
164
|
+
# Returns push notification configuration operations.
|
|
165
|
+
# @return [LLM::A2A::Notifications]
|
|
166
|
+
def notifications
|
|
167
|
+
@notifications ||= LLM::A2A::Notifications.new(self)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
##
|
|
171
|
+
# Sends a message to the agent and returns the response.
|
|
172
|
+
# @param [String] text The message text to send
|
|
173
|
+
# @param [Hash] config
|
|
174
|
+
# Optional configuration (accepted_output_modes, return_immediately)
|
|
175
|
+
# @param [Hash, nil] metadata
|
|
176
|
+
# Optional metadata to attach to the request
|
|
177
|
+
# @return [LLM::Object] The task or message response
|
|
178
|
+
def send_message(text, configuration = {}, metadata: nil)
|
|
179
|
+
body = build_request(
|
|
180
|
+
"SendMessage",
|
|
181
|
+
message: {role: "ROLE_USER", parts: [{text:}], messageId: SecureRandom.uuid},
|
|
182
|
+
configuration:,
|
|
183
|
+
metadata:
|
|
184
|
+
)
|
|
185
|
+
execute_request(body)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
##
|
|
189
|
+
# Sends a streaming message to the agent.
|
|
190
|
+
#
|
|
191
|
+
# The block is called for each {LLM::Object} event in the stream
|
|
192
|
+
# (Task, Message, TaskStatusUpdateEvent, TaskArtifactUpdateEvent).
|
|
193
|
+
# @param [String] text The message text to send
|
|
194
|
+
# @param [Hash] config Optional configuration
|
|
195
|
+
# @yieldparam [LLM::Object] event A stream event
|
|
196
|
+
# @return [void]
|
|
197
|
+
def send_streaming_message(text, configuration = {}, &on_event)
|
|
198
|
+
body = build_request(
|
|
199
|
+
"SendStreamingMessage",
|
|
200
|
+
message: {role: "ROLE_USER", parts: [{text:}], messageId: SecureRandom.uuid},
|
|
201
|
+
configuration:
|
|
202
|
+
)
|
|
203
|
+
execute_stream(body, &on_event)
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
##
|
|
207
|
+
# Gets the current state of a task.
|
|
208
|
+
# @param [String] task_id The task ID to retrieve
|
|
209
|
+
# @param [Integer, nil] history_length
|
|
210
|
+
# Optional limit on recent messages to include
|
|
211
|
+
# @return [LLM::Object]
|
|
212
|
+
def get_task(task_id, history_length: nil)
|
|
213
|
+
case @binding
|
|
214
|
+
when :rest
|
|
215
|
+
path = rest_path("/tasks/#{task_id}")
|
|
216
|
+
path = "#{path}?historyLength=#{history_length}" if history_length
|
|
217
|
+
res = transport.get(path)
|
|
218
|
+
when :jsonrpc
|
|
219
|
+
body = build_request("GetTask", id: task_id, historyLength: history_length)
|
|
220
|
+
res = transport.post("/", body)
|
|
221
|
+
else
|
|
222
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
223
|
+
end
|
|
224
|
+
LLM::Object.from(res)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
##
|
|
228
|
+
# Cancels a task in progress.
|
|
229
|
+
# @param [String] task_id The task ID to cancel
|
|
230
|
+
# @param [Hash, nil] metadata Optional metadata to attach to the request
|
|
231
|
+
# @return [LLM::Object]
|
|
232
|
+
def cancel_task(task_id, metadata: nil)
|
|
233
|
+
body = build_request("CancelTask", id: task_id, metadata:)
|
|
234
|
+
case @binding
|
|
235
|
+
when :rest
|
|
236
|
+
res = transport.post(rest_path("/tasks/#{task_id}:cancel"), body)
|
|
237
|
+
when :jsonrpc
|
|
238
|
+
res = transport.post("/", body)
|
|
239
|
+
else
|
|
240
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
241
|
+
end
|
|
242
|
+
LLM::Object.from(res)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
##
|
|
246
|
+
# Subscribes to streaming updates for an existing task.
|
|
247
|
+
# @param [String] task_id The task ID to subscribe to
|
|
248
|
+
# @yieldparam [LLM::Object] event A stream event
|
|
249
|
+
# @return [void]
|
|
250
|
+
def subscribe_to_task(task_id, &on_event)
|
|
251
|
+
case @binding
|
|
252
|
+
when :rest
|
|
253
|
+
transport.get_stream(rest_path("/tasks/#{task_id}:subscribe")) { on_event&.call(LLM::Object.from(_1)) }
|
|
254
|
+
when :jsonrpc
|
|
255
|
+
body = build_request("SubscribeToTask", id: task_id)
|
|
256
|
+
transport.post_stream("/", body) { on_event&.call(LLM::Object.from(_1)) }
|
|
257
|
+
else
|
|
258
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
##
|
|
263
|
+
# Lists tasks with optional filtering.
|
|
264
|
+
# @param [String, nil] context_id Optional context ID to filter by
|
|
265
|
+
# @param [String, nil] status Optional task state to filter by
|
|
266
|
+
# @param [Integer, nil] history_length Optional limit on recent messages to include
|
|
267
|
+
# @param [String, nil] status_timestamp_after Optional lower bound for status timestamp filtering
|
|
268
|
+
# @param [Boolean, nil] include_artifacts Whether to include task artifacts
|
|
269
|
+
# @param [Integer] page_size Maximum number of tasks to return
|
|
270
|
+
# @param [String, nil] page_token Pagination cursor
|
|
271
|
+
# @return [LLM::Object]
|
|
272
|
+
def list_tasks(context_id: nil, status: nil, history_length: nil, status_timestamp_after: nil,
|
|
273
|
+
include_artifacts: nil, page_size: 20, page_token: nil)
|
|
274
|
+
case @binding
|
|
275
|
+
when :rest
|
|
276
|
+
params = {}
|
|
277
|
+
params[:contextId] = context_id if context_id
|
|
278
|
+
params[:status] = status if status
|
|
279
|
+
params[:historyLength] = history_length if history_length
|
|
280
|
+
params[:statusTimestampAfter] = status_timestamp_after if status_timestamp_after
|
|
281
|
+
params[:includeArtifacts] = include_artifacts unless include_artifacts.nil?
|
|
282
|
+
params[:pageSize] = page_size if page_size
|
|
283
|
+
params[:pageToken] = page_token if page_token
|
|
284
|
+
query = URI.encode_www_form(params)
|
|
285
|
+
path = rest_path("/tasks")
|
|
286
|
+
path = "#{path}?#{query}" unless query.empty?
|
|
287
|
+
res = transport.get(path)
|
|
288
|
+
when :jsonrpc
|
|
289
|
+
body = build_request("ListTasks", contextId: context_id, status: status,
|
|
290
|
+
historyLength: history_length,
|
|
291
|
+
statusTimestampAfter: status_timestamp_after,
|
|
292
|
+
includeArtifacts: include_artifacts,
|
|
293
|
+
pageSize: page_size, pageToken: page_token)
|
|
294
|
+
res = transport.post("/", body)
|
|
295
|
+
else
|
|
296
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
297
|
+
end
|
|
298
|
+
LLM::Object.from(res)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
##
|
|
302
|
+
# Creates a push notification configuration for a task.
|
|
303
|
+
# @param [String] task_id The parent task ID
|
|
304
|
+
# @param [String] url The callback URL
|
|
305
|
+
# @param [String, nil] token Optional token to include with notifications
|
|
306
|
+
# @param [Hash, nil] authentication Optional authentication information
|
|
307
|
+
# @param [String, nil] id Optional configuration ID
|
|
308
|
+
# @return [LLM::Object]
|
|
309
|
+
def create_task_push_notification_config(task_id, url:, token: nil, authentication: nil, id: nil)
|
|
310
|
+
body = build_request("CreateTaskPushNotificationConfig", taskId: task_id, url:, token:, authentication:, id:)
|
|
311
|
+
case @binding
|
|
312
|
+
when :rest
|
|
313
|
+
res = transport.post(rest_path("/tasks/#{task_id}/pushNotificationConfigs"), body)
|
|
314
|
+
when :jsonrpc
|
|
315
|
+
res = transport.post("/", body)
|
|
316
|
+
else
|
|
317
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
318
|
+
end
|
|
319
|
+
LLM::Object.from(res)
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
##
|
|
323
|
+
# Retrieves a push notification configuration for a task.
|
|
324
|
+
# @param [String] task_id The parent task ID
|
|
325
|
+
# @param [String] id The configuration ID
|
|
326
|
+
# @return [LLM::Object]
|
|
327
|
+
def get_task_push_notification_config(task_id, id)
|
|
328
|
+
case @binding
|
|
329
|
+
when :rest
|
|
330
|
+
res = transport.get(rest_path("/tasks/#{task_id}/pushNotificationConfigs/#{id}"))
|
|
331
|
+
when :jsonrpc
|
|
332
|
+
body = build_request("GetTaskPushNotificationConfig", taskId: task_id, id:)
|
|
333
|
+
res = transport.post("/", body)
|
|
334
|
+
else
|
|
335
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
336
|
+
end
|
|
337
|
+
LLM::Object.from(res)
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
##
|
|
341
|
+
# Lists push notification configurations for a task.
|
|
342
|
+
# @param [String] task_id The parent task ID
|
|
343
|
+
# @param [Integer, nil] page_size Maximum number of configurations to return
|
|
344
|
+
# @param [String, nil] page_token Pagination cursor
|
|
345
|
+
# @return [LLM::Object]
|
|
346
|
+
def list_task_push_notification_configs(task_id, page_size: nil, page_token: nil)
|
|
347
|
+
case @binding
|
|
348
|
+
when :rest
|
|
349
|
+
params = {}
|
|
350
|
+
params[:pageSize] = page_size if page_size
|
|
351
|
+
params[:pageToken] = page_token if page_token
|
|
352
|
+
query = URI.encode_www_form(params)
|
|
353
|
+
path = rest_path("/tasks/#{task_id}/pushNotificationConfigs")
|
|
354
|
+
path = "#{path}?#{query}" unless query.empty?
|
|
355
|
+
res = transport.get(path)
|
|
356
|
+
when :jsonrpc
|
|
357
|
+
body = build_request("ListTaskPushNotificationConfigs", taskId: task_id, pageSize: page_size, pageToken: page_token)
|
|
358
|
+
res = transport.post("/", body)
|
|
359
|
+
else
|
|
360
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
361
|
+
end
|
|
362
|
+
LLM::Object.from(res)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
##
|
|
366
|
+
# Deletes a push notification configuration for a task.
|
|
367
|
+
# @param [String] task_id The parent task ID
|
|
368
|
+
# @param [String] id The configuration ID
|
|
369
|
+
# @return [LLM::Object]
|
|
370
|
+
def delete_task_push_notification_config(task_id, id)
|
|
371
|
+
case @binding
|
|
372
|
+
when :rest
|
|
373
|
+
res = transport.delete(rest_path("/tasks/#{task_id}/pushNotificationConfigs/#{id}"))
|
|
374
|
+
when :jsonrpc
|
|
375
|
+
body = build_request("DeleteTaskPushNotificationConfig", taskId: task_id, id:)
|
|
376
|
+
res = transport.post("/", body)
|
|
377
|
+
else
|
|
378
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
379
|
+
end
|
|
380
|
+
LLM::Object.from(res)
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
##
|
|
384
|
+
# Returns the authenticated extended agent card.
|
|
385
|
+
# @return [LLM::A2A::Card]
|
|
386
|
+
def extended_card
|
|
387
|
+
case @binding
|
|
388
|
+
when :rest
|
|
389
|
+
res = transport.get(rest_path("/extendedAgentCard"))
|
|
390
|
+
when :jsonrpc
|
|
391
|
+
body = build_request("GetExtendedAgentCard")
|
|
392
|
+
res = transport.post("/", body)
|
|
393
|
+
else
|
|
394
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
395
|
+
end
|
|
396
|
+
LLM::A2A::Card.new(res)
|
|
397
|
+
end
|
|
398
|
+
alias_method :get_extended_agent_card, :extended_card
|
|
399
|
+
|
|
400
|
+
##
|
|
401
|
+
# @return [String]
|
|
402
|
+
def inspect
|
|
403
|
+
"#<#{LLM::Utils.object_id(self)} @binding=#{@binding.inspect}>"
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private
|
|
407
|
+
|
|
408
|
+
attr_reader :transport
|
|
409
|
+
|
|
410
|
+
def build_request(method, **params)
|
|
411
|
+
case @binding
|
|
412
|
+
when :rest
|
|
413
|
+
params
|
|
414
|
+
when :jsonrpc
|
|
415
|
+
{jsonrpc: "2.0", method:, params: params.compact, id: SecureRandom.uuid}
|
|
416
|
+
else
|
|
417
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
418
|
+
end
|
|
419
|
+
end
|
|
420
|
+
|
|
421
|
+
def execute_request(body)
|
|
422
|
+
res = case @binding
|
|
423
|
+
when :rest
|
|
424
|
+
transport.post(rest_path("/message:send"), body)
|
|
425
|
+
when :jsonrpc
|
|
426
|
+
res = transport.post("/", body)
|
|
427
|
+
if res["error"]
|
|
428
|
+
raise LLM::A2A::Error.new(res["error"]["message"], res["error"]["code"])
|
|
429
|
+
end
|
|
430
|
+
res["result"] || res
|
|
431
|
+
else
|
|
432
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
433
|
+
end
|
|
434
|
+
LLM::Object.from(res)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
def execute_stream(body, &on_event)
|
|
438
|
+
case @binding
|
|
439
|
+
when :rest
|
|
440
|
+
transport.post_stream(rest_path("/message:stream"), body) { on_event&.call(LLM::Object.from(_1)) }
|
|
441
|
+
when :jsonrpc
|
|
442
|
+
transport.post_stream("/", body) { on_event&.call(LLM::Object.from(_1)) }
|
|
443
|
+
else
|
|
444
|
+
raise LLM::A2A::Error, "Invalid A2A binding: #{@binding.inspect}"
|
|
445
|
+
end
|
|
446
|
+
end
|
|
447
|
+
|
|
448
|
+
def rest_path(path)
|
|
449
|
+
return path if @base_path.empty?
|
|
450
|
+
"#{@base_path}#{path}"
|
|
451
|
+
end
|
|
452
|
+
end
|
|
@@ -11,19 +11,24 @@ module LLM::ActiveRecord
|
|
|
11
11
|
# class and forwarded to an internal agent subclass.
|
|
12
12
|
module ActsAsAgent
|
|
13
13
|
module ClassMethods
|
|
14
|
-
def model(model = nil)
|
|
15
|
-
return agent.model if model.nil?
|
|
16
|
-
agent.model(model)
|
|
14
|
+
def model(model = nil, &block)
|
|
15
|
+
return agent.model if model.nil? && !block
|
|
16
|
+
agent.model(model, &block)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
def tools(*tools)
|
|
20
|
-
return agent.tools if tools.empty?
|
|
21
|
-
agent.tools(*tools)
|
|
19
|
+
def tools(*tools, &block)
|
|
20
|
+
return agent.tools if tools.empty? && !block
|
|
21
|
+
agent.tools(*tools, &block)
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
def
|
|
25
|
-
return agent.
|
|
26
|
-
agent.
|
|
24
|
+
def skills(*skills, &block)
|
|
25
|
+
return agent.skills if skills.empty? && !block
|
|
26
|
+
agent.skills(*skills, &block)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def schema(schema = nil, &block)
|
|
30
|
+
return agent.schema if schema.nil? && !block
|
|
31
|
+
agent.schema(schema, &block)
|
|
27
32
|
end
|
|
28
33
|
|
|
29
34
|
def instructions(instructions = nil)
|
|
@@ -36,6 +41,11 @@ module LLM::ActiveRecord
|
|
|
36
41
|
agent.concurrency(concurrency)
|
|
37
42
|
end
|
|
38
43
|
|
|
44
|
+
def confirm(*tool_names, &block)
|
|
45
|
+
return agent.confirm if tool_names.empty? && !block
|
|
46
|
+
agent.confirm(*tool_names, &block)
|
|
47
|
+
end
|
|
48
|
+
|
|
39
49
|
def tracer(tracer = nil, &block)
|
|
40
50
|
return agent.tracer if tracer.nil? && !block
|
|
41
51
|
agent.tracer(tracer, &block)
|
|
@@ -116,6 +126,7 @@ module LLM::ActiveRecord
|
|
|
116
126
|
when :json, :jsonb then ctx.restore(data:)
|
|
117
127
|
else raise ArgumentError, "Unknown format: #{options[:format].inspect}"
|
|
118
128
|
end
|
|
129
|
+
ctx
|
|
119
130
|
end
|
|
120
131
|
end
|
|
121
132
|
end
|
|
@@ -59,12 +59,12 @@ module LLM::ActiveRecord
|
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
##
|
|
62
|
-
# Continues the stored context
|
|
63
|
-
# @see LLM::Context#
|
|
62
|
+
# Continues the stored context with new input and flushes it.
|
|
63
|
+
# @see LLM::Context#ask
|
|
64
64
|
# @return [LLM::Response]
|
|
65
|
-
def
|
|
65
|
+
def ask(...)
|
|
66
66
|
options = self.class.llm_plugin_options
|
|
67
|
-
ctx.
|
|
67
|
+
ctx.ask(...).tap { Utils.save!(self, ctx, options) }
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
##
|
data/lib/llm/active_record.rb
CHANGED
|
@@ -20,12 +20,7 @@ module LLM::ActiveRecord
|
|
|
20
20
|
# Resolves a single configured option against a model instance.
|
|
21
21
|
# @return [Object]
|
|
22
22
|
def self.resolve_option(obj, option)
|
|
23
|
-
|
|
24
|
-
when Proc then obj.instance_exec(&option)
|
|
25
|
-
when Symbol then obj.send(option)
|
|
26
|
-
when Hash then option.dup
|
|
27
|
-
else option
|
|
28
|
-
end
|
|
23
|
+
LLM::Utils.resolve_option(obj, option)
|
|
29
24
|
end
|
|
30
25
|
|
|
31
26
|
##
|