simple_acp 0.0.1
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/.envrc +1 -0
- data/CHANGELOG.md +5 -0
- data/COMMITS.md +196 -0
- data/LICENSE.txt +21 -0
- data/README.md +385 -0
- data/Rakefile +13 -0
- data/docs/api/client-base.md +383 -0
- data/docs/api/index.md +159 -0
- data/docs/api/models.md +286 -0
- data/docs/api/server-base.md +379 -0
- data/docs/api/storage.md +347 -0
- data/docs/assets/images/simple_acp.jpg +0 -0
- data/docs/client/index.md +279 -0
- data/docs/client/sessions.md +324 -0
- data/docs/client/streaming.md +345 -0
- data/docs/client/sync-async.md +308 -0
- data/docs/core-concepts/agents.md +253 -0
- data/docs/core-concepts/events.md +337 -0
- data/docs/core-concepts/index.md +147 -0
- data/docs/core-concepts/messages.md +211 -0
- data/docs/core-concepts/runs.md +278 -0
- data/docs/core-concepts/sessions.md +281 -0
- data/docs/examples.md +659 -0
- data/docs/getting-started/configuration.md +166 -0
- data/docs/getting-started/index.md +62 -0
- data/docs/getting-started/installation.md +95 -0
- data/docs/getting-started/quick-start.md +189 -0
- data/docs/index.md +119 -0
- data/docs/server/creating-agents.md +360 -0
- data/docs/server/http-endpoints.md +411 -0
- data/docs/server/index.md +218 -0
- data/docs/server/multi-turn.md +329 -0
- data/docs/server/streaming.md +315 -0
- data/docs/storage/custom.md +414 -0
- data/docs/storage/index.md +176 -0
- data/docs/storage/memory.md +198 -0
- data/docs/storage/postgresql.md +350 -0
- data/docs/storage/redis.md +287 -0
- data/examples/01_basic/client.rb +88 -0
- data/examples/01_basic/server.rb +100 -0
- data/examples/02_async_execution/client.rb +107 -0
- data/examples/02_async_execution/server.rb +56 -0
- data/examples/03_run_management/client.rb +115 -0
- data/examples/03_run_management/server.rb +84 -0
- data/examples/04_rich_messages/client.rb +160 -0
- data/examples/04_rich_messages/server.rb +180 -0
- data/examples/05_await_resume/client.rb +164 -0
- data/examples/05_await_resume/server.rb +114 -0
- data/examples/06_agent_metadata/client.rb +188 -0
- data/examples/06_agent_metadata/server.rb +192 -0
- data/examples/README.md +252 -0
- data/examples/run_demo.sh +137 -0
- data/lib/simple_acp/client/base.rb +448 -0
- data/lib/simple_acp/client/sse.rb +141 -0
- data/lib/simple_acp/models/agent_manifest.rb +129 -0
- data/lib/simple_acp/models/await.rb +123 -0
- data/lib/simple_acp/models/base.rb +147 -0
- data/lib/simple_acp/models/errors.rb +102 -0
- data/lib/simple_acp/models/events.rb +256 -0
- data/lib/simple_acp/models/message.rb +235 -0
- data/lib/simple_acp/models/message_part.rb +225 -0
- data/lib/simple_acp/models/metadata.rb +161 -0
- data/lib/simple_acp/models/run.rb +298 -0
- data/lib/simple_acp/models/session.rb +137 -0
- data/lib/simple_acp/models/types.rb +210 -0
- data/lib/simple_acp/server/agent.rb +116 -0
- data/lib/simple_acp/server/app.rb +264 -0
- data/lib/simple_acp/server/base.rb +510 -0
- data/lib/simple_acp/server/context.rb +210 -0
- data/lib/simple_acp/server/falcon_runner.rb +61 -0
- data/lib/simple_acp/storage/base.rb +129 -0
- data/lib/simple_acp/storage/memory.rb +108 -0
- data/lib/simple_acp/storage/postgresql.rb +233 -0
- data/lib/simple_acp/storage/redis.rb +178 -0
- data/lib/simple_acp/version.rb +5 -0
- data/lib/simple_acp.rb +91 -0
- data/mkdocs.yml +152 -0
- data/sig/simple_acp.rbs +4 -0
- metadata +418 -0
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
# Creating Agents
|
|
2
|
+
|
|
3
|
+
This guide covers patterns and best practices for building effective agents.
|
|
4
|
+
|
|
5
|
+
## Basic Agent Registration
|
|
6
|
+
|
|
7
|
+
### Block Syntax
|
|
8
|
+
|
|
9
|
+
The simplest way to create an agent:
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
server.agent("echo", description: "Echoes input") do |context|
|
|
13
|
+
text = context.input.first&.text_content
|
|
14
|
+
SimpleAcp::Models::Message.agent("Echo: #{text}")
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Class-Based Agents
|
|
19
|
+
|
|
20
|
+
For complex logic, use a class:
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
class TranslationAgent
|
|
24
|
+
def initialize(api_key)
|
|
25
|
+
@translator = TranslationService.new(api_key)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def call(context)
|
|
29
|
+
text = context.input.first&.text_content
|
|
30
|
+
target_lang = context.state&.dig("language") || "en"
|
|
31
|
+
|
|
32
|
+
translated = @translator.translate(text, to: target_lang)
|
|
33
|
+
SimpleAcp::Models::Message.agent(translated)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
server.register(
|
|
38
|
+
"translate",
|
|
39
|
+
TranslationAgent.new(ENV['TRANSLATION_API_KEY']),
|
|
40
|
+
description: "Translates text"
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Agent Options
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
server.agent(
|
|
48
|
+
"analyzer",
|
|
49
|
+
description: "Analyzes JSON data",
|
|
50
|
+
input_content_types: ["application/json"],
|
|
51
|
+
output_content_types: ["application/json", "text/plain"],
|
|
52
|
+
metadata: {
|
|
53
|
+
version: "2.0",
|
|
54
|
+
capabilities: ["sentiment", "entities", "keywords"]
|
|
55
|
+
}
|
|
56
|
+
) do |context|
|
|
57
|
+
# Handler
|
|
58
|
+
end
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## The Context Object
|
|
62
|
+
|
|
63
|
+
Every agent receives a `Context` with:
|
|
64
|
+
|
|
65
|
+
```ruby
|
|
66
|
+
server.agent("inspector") do |context|
|
|
67
|
+
# Input messages
|
|
68
|
+
context.input # => Array<Message>
|
|
69
|
+
|
|
70
|
+
# Session data
|
|
71
|
+
context.session # => Session or nil
|
|
72
|
+
context.session_id # => String or nil
|
|
73
|
+
context.history # => Array<Message>
|
|
74
|
+
context.state # => Any JSON-serializable data
|
|
75
|
+
|
|
76
|
+
# Run metadata
|
|
77
|
+
context.run_id # => String (UUID)
|
|
78
|
+
context.agent_name # => String
|
|
79
|
+
|
|
80
|
+
# State management
|
|
81
|
+
context.set_state(new_state) # Updates session state
|
|
82
|
+
|
|
83
|
+
# Await pattern
|
|
84
|
+
context.await_message(prompt_message) # Request input
|
|
85
|
+
context.resume_message # Get resume input
|
|
86
|
+
end
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Return Values
|
|
90
|
+
|
|
91
|
+
### Single Message
|
|
92
|
+
|
|
93
|
+
```ruby
|
|
94
|
+
server.agent("simple") do |context|
|
|
95
|
+
SimpleAcp::Models::Message.agent("Single response")
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Multiple Messages
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
server.agent("multi") do |context|
|
|
103
|
+
[
|
|
104
|
+
SimpleAcp::Models::Message.agent("First"),
|
|
105
|
+
SimpleAcp::Models::Message.agent("Second"),
|
|
106
|
+
SimpleAcp::Models::Message.agent("Third")
|
|
107
|
+
]
|
|
108
|
+
end
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Streaming with Enumerator
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
server.agent("stream") do |context|
|
|
115
|
+
Enumerator.new do |yielder|
|
|
116
|
+
10.times do |i|
|
|
117
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
118
|
+
SimpleAcp::Models::Message.agent("Item #{i + 1}")
|
|
119
|
+
)
|
|
120
|
+
sleep 0.1
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Common Patterns
|
|
127
|
+
|
|
128
|
+
### Input Validation
|
|
129
|
+
|
|
130
|
+
```ruby
|
|
131
|
+
server.agent("validated") do |context|
|
|
132
|
+
input = context.input.first&.text_content
|
|
133
|
+
|
|
134
|
+
if input.nil? || input.empty?
|
|
135
|
+
return SimpleAcp::Models::Message.agent("Error: Input required")
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
if input.length > 1000
|
|
139
|
+
return SimpleAcp::Models::Message.agent("Error: Input too long")
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Process valid input
|
|
143
|
+
process(input)
|
|
144
|
+
end
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### JSON Processing
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
server.agent("json-processor") do |context|
|
|
151
|
+
json_part = context.input.first&.parts&.find(&:json?)
|
|
152
|
+
|
|
153
|
+
unless json_part
|
|
154
|
+
return SimpleAcp::Models::Message.agent("Error: JSON input required")
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
data = JSON.parse(json_part.content)
|
|
158
|
+
|
|
159
|
+
result = transform(data)
|
|
160
|
+
|
|
161
|
+
SimpleAcp::Models::Message.agent(
|
|
162
|
+
SimpleAcp::Models::MessagePart.json(result)
|
|
163
|
+
)
|
|
164
|
+
end
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Multi-Part Responses
|
|
168
|
+
|
|
169
|
+
```ruby
|
|
170
|
+
server.agent("analysis") do |context|
|
|
171
|
+
text = context.input.first&.text_content
|
|
172
|
+
|
|
173
|
+
SimpleAcp::Models::Message.agent(
|
|
174
|
+
SimpleAcp::Models::MessagePart.text("Analysis complete:"),
|
|
175
|
+
SimpleAcp::Models::MessagePart.json({
|
|
176
|
+
word_count: text.split.length,
|
|
177
|
+
char_count: text.length,
|
|
178
|
+
sentiment: analyze_sentiment(text)
|
|
179
|
+
})
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Chaining Agents
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
server.agent("pipeline") do |context|
|
|
188
|
+
# Process through multiple stages
|
|
189
|
+
text = context.input.first&.text_content
|
|
190
|
+
|
|
191
|
+
# Stage 1: Clean
|
|
192
|
+
cleaned = clean_text(text)
|
|
193
|
+
|
|
194
|
+
# Stage 2: Transform
|
|
195
|
+
transformed = transform(cleaned)
|
|
196
|
+
|
|
197
|
+
# Stage 3: Format
|
|
198
|
+
formatted = format_output(transformed)
|
|
199
|
+
|
|
200
|
+
SimpleAcp::Models::Message.agent(formatted)
|
|
201
|
+
end
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
### Error Handling
|
|
205
|
+
|
|
206
|
+
```ruby
|
|
207
|
+
server.agent("robust") do |context|
|
|
208
|
+
begin
|
|
209
|
+
result = external_api_call(context.input)
|
|
210
|
+
SimpleAcp::Models::Message.agent(result)
|
|
211
|
+
rescue Timeout::Error
|
|
212
|
+
SimpleAcp::Models::Message.agent(
|
|
213
|
+
"Service timeout. Please try again."
|
|
214
|
+
)
|
|
215
|
+
rescue RateLimitError
|
|
216
|
+
SimpleAcp::Models::Message.agent(
|
|
217
|
+
"Rate limit exceeded. Please wait before retrying."
|
|
218
|
+
)
|
|
219
|
+
rescue => e
|
|
220
|
+
logger.error("Agent error: #{e.message}")
|
|
221
|
+
SimpleAcp::Models::Message.agent(
|
|
222
|
+
"An error occurred processing your request."
|
|
223
|
+
)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### Conditional Logic
|
|
229
|
+
|
|
230
|
+
```ruby
|
|
231
|
+
server.agent("router") do |context|
|
|
232
|
+
command = context.input.first&.text_content&.downcase
|
|
233
|
+
|
|
234
|
+
case command
|
|
235
|
+
when /^help/
|
|
236
|
+
SimpleAcp::Models::Message.agent(help_text)
|
|
237
|
+
when /^status/
|
|
238
|
+
SimpleAcp::Models::Message.agent(status_report)
|
|
239
|
+
when /^search (.+)/
|
|
240
|
+
SimpleAcp::Models::Message.agent(search($1))
|
|
241
|
+
else
|
|
242
|
+
SimpleAcp::Models::Message.agent("Unknown command. Type 'help' for options.")
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
## Advanced Patterns
|
|
248
|
+
|
|
249
|
+
### Stateful Agents
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
server.agent("wizard") do |context|
|
|
253
|
+
state = context.state || { step: 1, data: {} }
|
|
254
|
+
|
|
255
|
+
case state[:step]
|
|
256
|
+
when 1
|
|
257
|
+
context.set_state(state.merge(step: 2))
|
|
258
|
+
SimpleAcp::Models::Message.agent("What is your name?")
|
|
259
|
+
when 2
|
|
260
|
+
state[:data][:name] = context.input.first&.text_content
|
|
261
|
+
context.set_state(state.merge(step: 3))
|
|
262
|
+
SimpleAcp::Models::Message.agent("What is your email?")
|
|
263
|
+
when 3
|
|
264
|
+
state[:data][:email] = context.input.first&.text_content
|
|
265
|
+
context.set_state(state.merge(step: :complete))
|
|
266
|
+
SimpleAcp::Models::Message.agent(
|
|
267
|
+
"Complete! Name: #{state[:data][:name]}, Email: #{state[:data][:email]}"
|
|
268
|
+
)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Background Processing
|
|
274
|
+
|
|
275
|
+
```ruby
|
|
276
|
+
server.agent("async-processor") do |context|
|
|
277
|
+
job_id = BackgroundJob.enqueue(context.input)
|
|
278
|
+
|
|
279
|
+
SimpleAcp::Models::Message.agent(
|
|
280
|
+
"Processing started. Job ID: #{job_id}"
|
|
281
|
+
)
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
### External API Integration
|
|
286
|
+
|
|
287
|
+
```ruby
|
|
288
|
+
class WeatherAgent
|
|
289
|
+
def initialize
|
|
290
|
+
@api = WeatherAPI.new(ENV['WEATHER_API_KEY'])
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def call(context)
|
|
294
|
+
location = context.input.first&.text_content
|
|
295
|
+
|
|
296
|
+
weather = @api.current(location)
|
|
297
|
+
|
|
298
|
+
SimpleAcp::Models::Message.agent(
|
|
299
|
+
SimpleAcp::Models::MessagePart.text(
|
|
300
|
+
"Weather in #{location}: #{weather[:conditions]}, #{weather[:temp]}°F"
|
|
301
|
+
),
|
|
302
|
+
SimpleAcp::Models::MessagePart.json(weather)
|
|
303
|
+
)
|
|
304
|
+
rescue WeatherAPI::LocationNotFound
|
|
305
|
+
SimpleAcp::Models::Message.agent("Location not found: #{location}")
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
## Testing Agents
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
require 'minitest/autorun'
|
|
314
|
+
|
|
315
|
+
class EchoAgentTest < Minitest::Test
|
|
316
|
+
def setup
|
|
317
|
+
@server = SimpleAcp::Server::Base.new
|
|
318
|
+
|
|
319
|
+
@server.agent("echo") do |context|
|
|
320
|
+
text = context.input.first&.text_content
|
|
321
|
+
SimpleAcp::Models::Message.agent("Echo: #{text}")
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
def test_echoes_input
|
|
326
|
+
run = @server.run_sync(
|
|
327
|
+
agent_name: "echo",
|
|
328
|
+
input: [SimpleAcp::Models::Message.user("Hello")]
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
assert_equal "completed", run.status
|
|
332
|
+
assert_equal "Echo: Hello", run.output.first.text_content
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def test_handles_empty_input
|
|
336
|
+
run = @server.run_sync(
|
|
337
|
+
agent_name: "echo",
|
|
338
|
+
input: [SimpleAcp::Models::Message.user("")]
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
assert_equal "completed", run.status
|
|
342
|
+
assert_equal "Echo: ", run.output.first.text_content
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Best Practices
|
|
348
|
+
|
|
349
|
+
1. **Keep agents focused** - One agent, one responsibility
|
|
350
|
+
2. **Validate inputs** - Check for required data early
|
|
351
|
+
3. **Handle errors gracefully** - Return helpful error messages
|
|
352
|
+
4. **Use descriptive names** - Clear agent and parameter names
|
|
353
|
+
5. **Document behavior** - Use description and metadata
|
|
354
|
+
6. **Test thoroughly** - Unit test agent logic
|
|
355
|
+
|
|
356
|
+
## Next Steps
|
|
357
|
+
|
|
358
|
+
- Learn about [Streaming Responses](streaming.md)
|
|
359
|
+
- Explore [Multi-Turn Conversations](multi-turn.md)
|
|
360
|
+
- Review [HTTP Endpoints](http-endpoints.md)
|