a2a-ruby 1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +137 -0
- data/.simplecov +46 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +33 -0
- data/CODE_OF_CONDUCT.md +128 -0
- data/CONTRIBUTING.md +165 -0
- data/Gemfile +43 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/PUBLISHING_CHECKLIST.md +214 -0
- data/README.md +171 -0
- data/Rakefile +165 -0
- data/docs/agent_execution.md +309 -0
- data/docs/api_reference.md +792 -0
- data/docs/configuration.md +780 -0
- data/docs/events.md +475 -0
- data/docs/getting_started.md +668 -0
- data/docs/integration.md +262 -0
- data/docs/server_apps.md +621 -0
- data/docs/troubleshooting.md +765 -0
- data/lib/a2a/client/api_methods.rb +263 -0
- data/lib/a2a/client/auth/api_key.rb +161 -0
- data/lib/a2a/client/auth/interceptor.rb +288 -0
- data/lib/a2a/client/auth/jwt.rb +189 -0
- data/lib/a2a/client/auth/oauth2.rb +146 -0
- data/lib/a2a/client/auth.rb +137 -0
- data/lib/a2a/client/base.rb +316 -0
- data/lib/a2a/client/config.rb +210 -0
- data/lib/a2a/client/connection_pool.rb +233 -0
- data/lib/a2a/client/http_client.rb +524 -0
- data/lib/a2a/client/json_rpc_handler.rb +136 -0
- data/lib/a2a/client/middleware/circuit_breaker_interceptor.rb +245 -0
- data/lib/a2a/client/middleware/logging_interceptor.rb +371 -0
- data/lib/a2a/client/middleware/rate_limit_interceptor.rb +142 -0
- data/lib/a2a/client/middleware/retry_interceptor.rb +161 -0
- data/lib/a2a/client/middleware.rb +116 -0
- data/lib/a2a/client/performance_tracker.rb +60 -0
- data/lib/a2a/configuration/defaults.rb +34 -0
- data/lib/a2a/configuration/environment_loader.rb +76 -0
- data/lib/a2a/configuration/file_loader.rb +115 -0
- data/lib/a2a/configuration/inheritance.rb +101 -0
- data/lib/a2a/configuration/validator.rb +180 -0
- data/lib/a2a/configuration.rb +201 -0
- data/lib/a2a/errors.rb +291 -0
- data/lib/a2a/modules.rb +50 -0
- data/lib/a2a/monitoring/alerting.rb +490 -0
- data/lib/a2a/monitoring/distributed_tracing.rb +398 -0
- data/lib/a2a/monitoring/health_endpoints.rb +204 -0
- data/lib/a2a/monitoring/metrics_collector.rb +438 -0
- data/lib/a2a/monitoring.rb +463 -0
- data/lib/a2a/plugin.rb +358 -0
- data/lib/a2a/plugin_manager.rb +159 -0
- data/lib/a2a/plugins/example_auth.rb +81 -0
- data/lib/a2a/plugins/example_middleware.rb +118 -0
- data/lib/a2a/plugins/example_transport.rb +76 -0
- data/lib/a2a/protocol/agent_card.rb +8 -0
- data/lib/a2a/protocol/agent_card_server.rb +584 -0
- data/lib/a2a/protocol/capability.rb +496 -0
- data/lib/a2a/protocol/json_rpc.rb +254 -0
- data/lib/a2a/protocol/message.rb +8 -0
- data/lib/a2a/protocol/task.rb +8 -0
- data/lib/a2a/rails/a2a_controller.rb +258 -0
- data/lib/a2a/rails/controller_helpers.rb +499 -0
- data/lib/a2a/rails/engine.rb +167 -0
- data/lib/a2a/rails/generators/agent_generator.rb +311 -0
- data/lib/a2a/rails/generators/install_generator.rb +209 -0
- data/lib/a2a/rails/generators/migration_generator.rb +232 -0
- data/lib/a2a/rails/generators/templates/add_a2a_indexes.rb +57 -0
- data/lib/a2a/rails/generators/templates/agent_controller.rb +122 -0
- data/lib/a2a/rails/generators/templates/agent_controller_spec.rb +160 -0
- data/lib/a2a/rails/generators/templates/agent_readme.md +200 -0
- data/lib/a2a/rails/generators/templates/create_a2a_push_notification_configs.rb +68 -0
- data/lib/a2a/rails/generators/templates/create_a2a_tasks.rb +83 -0
- data/lib/a2a/rails/generators/templates/example_agent_controller.rb +228 -0
- data/lib/a2a/rails/generators/templates/initializer.rb +108 -0
- data/lib/a2a/rails/generators/templates/push_notification_config_model.rb +228 -0
- data/lib/a2a/rails/generators/templates/task_model.rb +200 -0
- data/lib/a2a/rails/tasks/a2a.rake +228 -0
- data/lib/a2a/server/a2a_methods.rb +520 -0
- data/lib/a2a/server/agent.rb +537 -0
- data/lib/a2a/server/agent_execution/agent_executor.rb +279 -0
- data/lib/a2a/server/agent_execution/request_context.rb +219 -0
- data/lib/a2a/server/apps/rack_app.rb +311 -0
- data/lib/a2a/server/apps/sinatra_app.rb +261 -0
- data/lib/a2a/server/default_request_handler.rb +350 -0
- data/lib/a2a/server/events/event_consumer.rb +116 -0
- data/lib/a2a/server/events/event_queue.rb +226 -0
- data/lib/a2a/server/example_agent.rb +248 -0
- data/lib/a2a/server/handler.rb +281 -0
- data/lib/a2a/server/middleware/authentication_middleware.rb +212 -0
- data/lib/a2a/server/middleware/cors_middleware.rb +171 -0
- data/lib/a2a/server/middleware/logging_middleware.rb +362 -0
- data/lib/a2a/server/middleware/rate_limit_middleware.rb +382 -0
- data/lib/a2a/server/middleware.rb +213 -0
- data/lib/a2a/server/push_notification_manager.rb +327 -0
- data/lib/a2a/server/request_handler.rb +136 -0
- data/lib/a2a/server/storage/base.rb +141 -0
- data/lib/a2a/server/storage/database.rb +266 -0
- data/lib/a2a/server/storage/memory.rb +274 -0
- data/lib/a2a/server/storage/redis.rb +320 -0
- data/lib/a2a/server/storage.rb +38 -0
- data/lib/a2a/server/task_manager.rb +534 -0
- data/lib/a2a/transport/grpc.rb +481 -0
- data/lib/a2a/transport/http.rb +415 -0
- data/lib/a2a/transport/sse.rb +499 -0
- data/lib/a2a/types/agent_card.rb +540 -0
- data/lib/a2a/types/artifact.rb +99 -0
- data/lib/a2a/types/base_model.rb +223 -0
- data/lib/a2a/types/events.rb +117 -0
- data/lib/a2a/types/message.rb +106 -0
- data/lib/a2a/types/part.rb +288 -0
- data/lib/a2a/types/push_notification.rb +139 -0
- data/lib/a2a/types/security.rb +167 -0
- data/lib/a2a/types/task.rb +154 -0
- data/lib/a2a/types.rb +88 -0
- data/lib/a2a/utils/helpers.rb +245 -0
- data/lib/a2a/utils/message_buffer.rb +278 -0
- data/lib/a2a/utils/performance.rb +247 -0
- data/lib/a2a/utils/rails_detection.rb +97 -0
- data/lib/a2a/utils/structured_logger.rb +306 -0
- data/lib/a2a/utils/time_helpers.rb +167 -0
- data/lib/a2a/utils/validation.rb +8 -0
- data/lib/a2a/version.rb +6 -0
- data/lib/a2a-rails.rb +58 -0
- data/lib/a2a.rb +198 -0
- metadata +437 -0
@@ -0,0 +1,309 @@
|
|
1
|
+
# Agent Execution Framework
|
2
|
+
|
3
|
+
The A2A Ruby SDK provides a powerful agent execution framework that allows you to create custom agents with sophisticated processing logic. This framework is event-driven and supports both synchronous and streaming responses.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The agent execution framework consists of several key components:
|
8
|
+
|
9
|
+
- **AgentExecutor**: Abstract base class for implementing agent logic
|
10
|
+
- **RequestContext**: Contains all information needed to process a request
|
11
|
+
- **EventQueue**: Manages event publishing and consumption
|
12
|
+
- **DefaultRequestHandler**: Integrates everything together
|
13
|
+
|
14
|
+
## Creating Custom Agent Executors
|
15
|
+
|
16
|
+
### Basic Agent Executor
|
17
|
+
|
18
|
+
```ruby
|
19
|
+
class MyAgentExecutor < A2A::Server::AgentExecution::SimpleAgentExecutor
|
20
|
+
def process_message(message, task, context)
|
21
|
+
# Extract text from message parts
|
22
|
+
text_parts = message.parts.select { |part| part.is_a?(A2A::Types::TextPart) }
|
23
|
+
input_text = text_parts.map(&:text).join(" ")
|
24
|
+
|
25
|
+
# Process the input
|
26
|
+
response_text = generate_response(input_text)
|
27
|
+
|
28
|
+
# Create response message
|
29
|
+
response_message = A2A::Types::Message.new(
|
30
|
+
message_id: SecureRandom.uuid,
|
31
|
+
context_id: message.context_id,
|
32
|
+
role: "assistant",
|
33
|
+
parts: [A2A::Types::TextPart.new(text: response_text)]
|
34
|
+
)
|
35
|
+
|
36
|
+
# Return result
|
37
|
+
{
|
38
|
+
message: response_message.to_h,
|
39
|
+
processed_input: input_text,
|
40
|
+
timestamp: Time.now.utc.iso8601
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def generate_response(input)
|
47
|
+
# Your custom logic here
|
48
|
+
"I received: #{input}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
```
|
52
|
+
|
53
|
+
### Advanced Agent Executor with Streaming
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
class StreamingAgentExecutor < A2A::Server::AgentExecution::AgentExecutor
|
57
|
+
def execute(context, event_queue)
|
58
|
+
# Create or get task
|
59
|
+
task = ensure_task(context)
|
60
|
+
publish_task(event_queue, task)
|
61
|
+
|
62
|
+
# Update to working state
|
63
|
+
publish_task_status_update(
|
64
|
+
event_queue,
|
65
|
+
task.id,
|
66
|
+
task.context_id,
|
67
|
+
A2A::Types::TaskStatus.new(
|
68
|
+
state: A2A::Types::TASK_STATE_WORKING,
|
69
|
+
message: "Processing request",
|
70
|
+
updated_at: Time.now.utc.iso8601
|
71
|
+
)
|
72
|
+
)
|
73
|
+
|
74
|
+
# Process message with streaming
|
75
|
+
if context.message
|
76
|
+
process_streaming_message(context.message, task, context, event_queue)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Complete task
|
80
|
+
publish_task_status_update(
|
81
|
+
event_queue,
|
82
|
+
task.id,
|
83
|
+
task.context_id,
|
84
|
+
A2A::Types::TaskStatus.new(
|
85
|
+
state: A2A::Types::TASK_STATE_COMPLETED,
|
86
|
+
message: "Processing complete",
|
87
|
+
updated_at: Time.now.utc.iso8601
|
88
|
+
)
|
89
|
+
)
|
90
|
+
end
|
91
|
+
|
92
|
+
def cancel(context, event_queue)
|
93
|
+
publish_task_status_update(
|
94
|
+
event_queue,
|
95
|
+
context.task_id,
|
96
|
+
context.context_id,
|
97
|
+
A2A::Types::TaskStatus.new(
|
98
|
+
state: A2A::Types::TASK_STATE_CANCELED,
|
99
|
+
message: "Task canceled",
|
100
|
+
updated_at: Time.now.utc.iso8601
|
101
|
+
)
|
102
|
+
)
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
def process_streaming_message(message, task, context, event_queue)
|
108
|
+
text = message.parts.first.text
|
109
|
+
words = text.split
|
110
|
+
|
111
|
+
words.each_with_index do |word, index|
|
112
|
+
# Publish partial response
|
113
|
+
partial_message = A2A::Types::Message.new(
|
114
|
+
message_id: SecureRandom.uuid,
|
115
|
+
context_id: message.context_id,
|
116
|
+
role: "assistant",
|
117
|
+
parts: [A2A::Types::TextPart.new(text: word)]
|
118
|
+
)
|
119
|
+
|
120
|
+
publish_message(event_queue, partial_message)
|
121
|
+
|
122
|
+
# Simulate processing delay
|
123
|
+
sleep 0.1
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def ensure_task(context)
|
128
|
+
# Create new task if needed
|
129
|
+
task_id = context.task_id || SecureRandom.uuid
|
130
|
+
context_id = context.context_id || SecureRandom.uuid
|
131
|
+
|
132
|
+
A2A::Types::Task.new(
|
133
|
+
id: task_id,
|
134
|
+
context_id: context_id,
|
135
|
+
status: A2A::Types::TaskStatus.new(
|
136
|
+
state: A2A::Types::TASK_STATE_SUBMITTED,
|
137
|
+
message: "Task created",
|
138
|
+
updated_at: Time.now.utc.iso8601
|
139
|
+
),
|
140
|
+
history: context.message ? [context.message] : [],
|
141
|
+
metadata: {
|
142
|
+
created_at: Time.now.utc.iso8601,
|
143
|
+
executor: self.class.name
|
144
|
+
}
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
```
|
149
|
+
|
150
|
+
## Request Context
|
151
|
+
|
152
|
+
The `RequestContext` provides all the information needed to process a request:
|
153
|
+
|
154
|
+
```ruby
|
155
|
+
# Access message data
|
156
|
+
if context.has_message?
|
157
|
+
message = context.message
|
158
|
+
text = message.parts.first.text
|
159
|
+
end
|
160
|
+
|
161
|
+
# Check if this is a new or continuing task
|
162
|
+
if context.new_task?
|
163
|
+
# Handle new task creation
|
164
|
+
else
|
165
|
+
# Continue existing task
|
166
|
+
task_id = context.task_id
|
167
|
+
end
|
168
|
+
|
169
|
+
# Access user information
|
170
|
+
if context.authenticated?
|
171
|
+
user = context.user
|
172
|
+
auth_data = context.authentication("oauth2")
|
173
|
+
end
|
174
|
+
|
175
|
+
# Access metadata
|
176
|
+
custom_data = context.get_metadata(:custom_key)
|
177
|
+
context.set_metadata(:processed_at, Time.now)
|
178
|
+
```
|
179
|
+
|
180
|
+
## Event System Integration
|
181
|
+
|
182
|
+
The agent executor publishes events that are automatically processed:
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
class MyExecutor < A2A::Server::AgentExecution::AgentExecutor
|
186
|
+
def execute(context, event_queue)
|
187
|
+
# Publish task events
|
188
|
+
publish_task(event_queue, task)
|
189
|
+
|
190
|
+
# Publish status updates
|
191
|
+
publish_task_status_update(event_queue, task_id, context_id, status)
|
192
|
+
|
193
|
+
# Publish artifact updates
|
194
|
+
publish_task_artifact_update(event_queue, task_id, context_id, artifact)
|
195
|
+
|
196
|
+
# Publish messages
|
197
|
+
publish_message(event_queue, message)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
## Integration with Request Handler
|
203
|
+
|
204
|
+
```ruby
|
205
|
+
# Create executor
|
206
|
+
executor = MyAgentExecutor.new
|
207
|
+
|
208
|
+
# Create request handler
|
209
|
+
handler = A2A::Server::DefaultRequestHandler.new(executor)
|
210
|
+
|
211
|
+
# Use with server applications
|
212
|
+
app = A2A::Server::Apps::RackApp.new(
|
213
|
+
agent_card: agent_card,
|
214
|
+
request_handler: handler
|
215
|
+
)
|
216
|
+
```
|
217
|
+
|
218
|
+
## Error Handling
|
219
|
+
|
220
|
+
```ruby
|
221
|
+
class RobustAgentExecutor < A2A::Server::AgentExecution::SimpleAgentExecutor
|
222
|
+
def process_message(message, task, context)
|
223
|
+
begin
|
224
|
+
# Your processing logic
|
225
|
+
result = complex_processing(message)
|
226
|
+
|
227
|
+
# Return successful result
|
228
|
+
{ result: result, status: "success" }
|
229
|
+
rescue StandardError => e
|
230
|
+
# Log error
|
231
|
+
A2A.logger.error("Processing failed", error: e.message)
|
232
|
+
|
233
|
+
# Return error result
|
234
|
+
{
|
235
|
+
error: {
|
236
|
+
message: e.message,
|
237
|
+
type: e.class.name
|
238
|
+
},
|
239
|
+
status: "error"
|
240
|
+
}
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
private
|
245
|
+
|
246
|
+
def complex_processing(message)
|
247
|
+
# Your complex logic that might fail
|
248
|
+
raise "Something went wrong" if message.parts.empty?
|
249
|
+
|
250
|
+
"Processed successfully"
|
251
|
+
end
|
252
|
+
end
|
253
|
+
```
|
254
|
+
|
255
|
+
## Best Practices
|
256
|
+
|
257
|
+
1. **Keep Executors Focused**: Each executor should handle a specific type of agent logic
|
258
|
+
2. **Use Events Properly**: Publish appropriate events for task lifecycle management
|
259
|
+
3. **Handle Errors Gracefully**: Always catch and handle exceptions appropriately
|
260
|
+
4. **Support Cancellation**: Implement the `cancel` method for long-running tasks
|
261
|
+
5. **Use Context Effectively**: Leverage the request context for user and metadata access
|
262
|
+
6. **Test Thoroughly**: Write comprehensive tests for your agent executors
|
263
|
+
|
264
|
+
## Testing Agent Executors
|
265
|
+
|
266
|
+
```ruby
|
267
|
+
RSpec.describe MyAgentExecutor do
|
268
|
+
let(:executor) { described_class.new }
|
269
|
+
let(:event_queue) { A2A::Server::Events::InMemoryEventQueue.new }
|
270
|
+
let(:message) { create_test_message("Hello, agent!") }
|
271
|
+
let(:context) { create_test_context(message: message) }
|
272
|
+
|
273
|
+
describe "#execute" do
|
274
|
+
it "processes messages correctly" do
|
275
|
+
events = []
|
276
|
+
event_queue.subscribe { |event| events << event }
|
277
|
+
|
278
|
+
executor.execute(context, event_queue)
|
279
|
+
|
280
|
+
expect(events).to include(have_attributes(type: "task"))
|
281
|
+
expect(events).to include(have_attributes(type: "task_status_update"))
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
describe "#cancel" do
|
286
|
+
it "cancels tasks properly" do
|
287
|
+
events = []
|
288
|
+
event_queue.subscribe { |event| events << event }
|
289
|
+
|
290
|
+
executor.cancel(context, event_queue)
|
291
|
+
|
292
|
+
canceled_event = events.find { |e| e.type == "task_status_update" }
|
293
|
+
expect(canceled_event.data.status.state).to eq("canceled")
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
```
|
298
|
+
|
299
|
+
This framework provides the flexibility to create sophisticated agents while maintaining consistency with the A2A protocol and ensuring proper integration with the broader SDK ecosystem.
|
300
|
+
|
301
|
+
## Complete Examples
|
302
|
+
|
303
|
+
For complete working examples of agent executors in action, see the [A2A Ruby Samples Repository](https://github.com/a2aproject/a2a-ruby-samples), which includes:
|
304
|
+
|
305
|
+
- **Hello World Agent** - Basic agent executor implementation
|
306
|
+
- **Dice Agent** - Interactive agent with function calling
|
307
|
+
- **Weather Agent** - Real-world service integration
|
308
|
+
- **Rails Integration** - Production Rails applications
|
309
|
+
- **Streaming Chat Agent** - Real-time communication examples
|