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
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A
|
|
4
|
+
##
|
|
5
|
+
# Generic A2A protocol error.
|
|
6
|
+
Error = Class.new(LLM::Error) do
|
|
7
|
+
##
|
|
8
|
+
# @return [Integer, nil]
|
|
9
|
+
attr_reader :code
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# @return [Object, nil]
|
|
13
|
+
attr_reader :data
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# @param [String] message
|
|
17
|
+
# @param [Integer, nil] code
|
|
18
|
+
# @param [Object, nil] data
|
|
19
|
+
def initialize(message, code = nil, data = nil)
|
|
20
|
+
super(message)
|
|
21
|
+
@code = code
|
|
22
|
+
@data = data
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Raised when the agent card cannot be fetched or parsed.
|
|
28
|
+
AgentCardError = Class.new(Error)
|
|
29
|
+
|
|
30
|
+
##
|
|
31
|
+
# Raised when a task is not found.
|
|
32
|
+
TaskNotFoundError = Class.new(Error)
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Raised when a task cannot be cancelled.
|
|
36
|
+
TaskNotCancelableError = Class.new(Error)
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Raised when the agent does not support the requested operation.
|
|
40
|
+
UnsupportedOperationError = Class.new(Error)
|
|
41
|
+
|
|
42
|
+
##
|
|
43
|
+
# Raised when a content type is not supported.
|
|
44
|
+
ContentTypeNotSupportedError = Class.new(Error)
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Raised when the A2A protocol version is not supported.
|
|
48
|
+
VersionNotSupportedError = Class.new(Error)
|
|
49
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A
|
|
4
|
+
##
|
|
5
|
+
# Groups push notification configuration operations.
|
|
6
|
+
class Notifications
|
|
7
|
+
##
|
|
8
|
+
# @param [LLM::A2A] a2a
|
|
9
|
+
def initialize(a2a)
|
|
10
|
+
@a2a = a2a
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Creates a push notification configuration for a task.
|
|
15
|
+
# @param [String] task_id
|
|
16
|
+
# @param [String] url
|
|
17
|
+
# @param [String, nil] token
|
|
18
|
+
# @param [Hash, nil] authentication
|
|
19
|
+
# @param [String, nil] id
|
|
20
|
+
# @return [LLM::Object]
|
|
21
|
+
def create(task_id, url:, token: nil, authentication: nil, id: nil)
|
|
22
|
+
@a2a.create_task_push_notification_config(task_id, url:, token:, authentication:, id:)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# Retrieves a push notification configuration for a task.
|
|
27
|
+
# @param [String] task_id
|
|
28
|
+
# @param [String] id
|
|
29
|
+
# @return [LLM::Object]
|
|
30
|
+
def get(task_id, id)
|
|
31
|
+
@a2a.get_task_push_notification_config(task_id, id)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
##
|
|
35
|
+
# Lists push notification configurations for a task.
|
|
36
|
+
# @param [String] task_id
|
|
37
|
+
# @param [Integer, nil] page_size
|
|
38
|
+
# @param [String, nil] page_token
|
|
39
|
+
# @return [LLM::Object]
|
|
40
|
+
def list(task_id, page_size: nil, page_token: nil)
|
|
41
|
+
@a2a.list_task_push_notification_configs(task_id, page_size:, page_token:)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
##
|
|
45
|
+
# Deletes a push notification configuration for a task.
|
|
46
|
+
# @param [String] task_id
|
|
47
|
+
# @param [String] id
|
|
48
|
+
# @return [LLM::Object]
|
|
49
|
+
def delete(task_id, id)
|
|
50
|
+
@a2a.delete_task_push_notification_config(task_id, id)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A
|
|
4
|
+
##
|
|
5
|
+
# Groups task-oriented A2A operations.
|
|
6
|
+
class Tasks
|
|
7
|
+
##
|
|
8
|
+
# @param [LLM::A2A] a2a
|
|
9
|
+
def initialize(a2a)
|
|
10
|
+
@a2a = a2a
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Returns the current state of a task.
|
|
15
|
+
# @param [String] task_id
|
|
16
|
+
# @param [Integer, nil] history_length
|
|
17
|
+
# @return [LLM::Object]
|
|
18
|
+
def get(task_id, history_length: nil)
|
|
19
|
+
@a2a.get_task(task_id, history_length:)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Lists tasks with optional filtering.
|
|
24
|
+
# @param [String, nil] context_id
|
|
25
|
+
# @param [String, nil] status
|
|
26
|
+
# @param [Integer, nil] history_length
|
|
27
|
+
# @param [String, nil] status_timestamp_after
|
|
28
|
+
# @param [Boolean, nil] include_artifacts
|
|
29
|
+
# @param [Integer] page_size
|
|
30
|
+
# @param [String, nil] page_token
|
|
31
|
+
# @return [LLM::Object]
|
|
32
|
+
def list(context_id: nil, status: nil, history_length: nil, status_timestamp_after: nil,
|
|
33
|
+
include_artifacts: nil, page_size: 20, page_token: nil)
|
|
34
|
+
@a2a.list_tasks(context_id:, status:, history_length:, status_timestamp_after:,
|
|
35
|
+
include_artifacts:, page_size:, page_token:)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
##
|
|
39
|
+
# Cancels a task in progress.
|
|
40
|
+
# @param [String] task_id
|
|
41
|
+
# @return [LLM::Object]
|
|
42
|
+
def cancel(task_id, metadata: nil)
|
|
43
|
+
@a2a.cancel_task(task_id, metadata:)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
##
|
|
47
|
+
# Subscribes to streaming updates for an existing task.
|
|
48
|
+
# @param [String] task_id
|
|
49
|
+
# @yieldparam [LLM::Object] event
|
|
50
|
+
# @return [void]
|
|
51
|
+
def subscribe(task_id, &on_event)
|
|
52
|
+
@a2a.subscribe_to_task(task_id, &on_event)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class LLM::A2A
|
|
4
|
+
module Transport
|
|
5
|
+
##
|
|
6
|
+
# The {LLM::A2A::Transport::HTTP} class provides the HTTP+JSON/REST
|
|
7
|
+
# protocol binding for the A2A protocol. It sends JSON payloads to
|
|
8
|
+
# A2A agent endpoints and handles both synchronous responses and
|
|
9
|
+
# Server-Sent Events for streaming operations.
|
|
10
|
+
#
|
|
11
|
+
# This transport implements the HTTP+JSON/REST binding as defined
|
|
12
|
+
# in the A2A specification (Section 11).
|
|
13
|
+
class HTTP
|
|
14
|
+
include LLM::Transport::Utils
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# @param [String] url The base URL of the A2A agent
|
|
18
|
+
# @param [Hash<String, String>] headers Extra HTTP headers
|
|
19
|
+
# @param [Integer, nil] timeout The timeout in seconds
|
|
20
|
+
# @param [LLM::Transport, Class, nil] transport Override transport
|
|
21
|
+
# @param [String] protocol_version The A2A protocol version header
|
|
22
|
+
def initialize(url:, headers: {}, timeout: nil, transport: nil, protocol_version: "1.0")
|
|
23
|
+
@uri = URI.parse(url)
|
|
24
|
+
@headers = headers
|
|
25
|
+
@protocol_version = protocol_version
|
|
26
|
+
@transport = resolve_transport(@uri, transport, timeout)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
##
|
|
30
|
+
# Sends a GET request.
|
|
31
|
+
# @param [String] path The URL path
|
|
32
|
+
# @return [Hash]
|
|
33
|
+
def get(path, accept: "application/json")
|
|
34
|
+
req = Net::HTTP::Get.new(request_path(path), headers(accept:))
|
|
35
|
+
res = transport.request(req, owner: self)
|
|
36
|
+
parse_response(res)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Sends a POST request.
|
|
41
|
+
# @param [String] path The URL path
|
|
42
|
+
# @param [Hash] body The JSON body
|
|
43
|
+
# @return [Hash]
|
|
44
|
+
def post(path, body, content_type: "application/json", accept: "application/json")
|
|
45
|
+
req = Net::HTTP::Post.new(request_path(path), headers(content_type:, accept:))
|
|
46
|
+
req.body = LLM.json.dump(body)
|
|
47
|
+
res = transport.request(req, owner: self)
|
|
48
|
+
parse_response(res)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Sends a DELETE request.
|
|
53
|
+
# @param [String] path The URL path
|
|
54
|
+
# @return [Hash]
|
|
55
|
+
def delete(path, accept: "application/json")
|
|
56
|
+
req = Net::HTTP::Delete.new(request_path(path), headers(accept:))
|
|
57
|
+
res = transport.request(req, owner: self)
|
|
58
|
+
parse_response(res)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Sends a GET request with a streaming (SSE) response.
|
|
63
|
+
#
|
|
64
|
+
# The block is called for each event parsed from the event stream.
|
|
65
|
+
# @param [String] path The URL path
|
|
66
|
+
# @yieldparam [LLM::Object] event A stream event
|
|
67
|
+
# @return [void]
|
|
68
|
+
def get_stream(path, &on_event)
|
|
69
|
+
req = Net::HTTP::Get.new(request_path(path), headers(accept: "text/event-stream"))
|
|
70
|
+
stream(req, &on_event)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
##
|
|
74
|
+
# Sends a POST request with a streaming (SSE) response.
|
|
75
|
+
#
|
|
76
|
+
# The block is called for each event parsed from the event stream.
|
|
77
|
+
# @param [String] path The URL path
|
|
78
|
+
# @param [Hash] body The JSON body
|
|
79
|
+
# @yieldparam [LLM::Object] event A stream event
|
|
80
|
+
# @return [void]
|
|
81
|
+
def post_stream(path, body, content_type: "application/json", &on_event)
|
|
82
|
+
req = Net::HTTP::Post.new(request_path(path), headers(content_type:, accept: "text/event-stream"))
|
|
83
|
+
req.body = LLM.json.dump(body)
|
|
84
|
+
stream(req, &on_event)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
private
|
|
88
|
+
|
|
89
|
+
attr_reader :transport
|
|
90
|
+
|
|
91
|
+
def headers(content_type: "application/json", accept: "application/json")
|
|
92
|
+
{
|
|
93
|
+
"A2A-Version" => @protocol_version,
|
|
94
|
+
"content-type" => content_type,
|
|
95
|
+
"accept" => accept
|
|
96
|
+
}.merge(@headers)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def request_path(path)
|
|
100
|
+
path = LLM::Utils.normalize_base_path(path)
|
|
101
|
+
path.empty? ? "/" : path
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def stream(req, &on_event)
|
|
105
|
+
transport.request(req, owner: self) do |raw|
|
|
106
|
+
raw = LLM::Transport::Response.from(raw)
|
|
107
|
+
next raw unless raw.success?
|
|
108
|
+
decoder = LLM::Transport::StreamDecoder.new(&on_event)
|
|
109
|
+
raw.read_body { decoder << _1 }
|
|
110
|
+
decoder.free
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def parse_response(res)
|
|
115
|
+
res = LLM::Transport::Response.from(res)
|
|
116
|
+
body = res.body.to_s
|
|
117
|
+
if res.success?
|
|
118
|
+
body.empty? ? {} : LLM.json.load(body)
|
|
119
|
+
else
|
|
120
|
+
handle_error(res.code, body)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def handle_error(code, body)
|
|
125
|
+
data = LLM.json.load(body) rescue {}
|
|
126
|
+
msg = data.dig("error", "message") || data["message"] || "HTTP #{code}"
|
|
127
|
+
raise LLM::A2A::Error.new(msg, code.to_i, data)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|