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.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.envrc +1 -0
  3. data/CHANGELOG.md +5 -0
  4. data/COMMITS.md +196 -0
  5. data/LICENSE.txt +21 -0
  6. data/README.md +385 -0
  7. data/Rakefile +13 -0
  8. data/docs/api/client-base.md +383 -0
  9. data/docs/api/index.md +159 -0
  10. data/docs/api/models.md +286 -0
  11. data/docs/api/server-base.md +379 -0
  12. data/docs/api/storage.md +347 -0
  13. data/docs/assets/images/simple_acp.jpg +0 -0
  14. data/docs/client/index.md +279 -0
  15. data/docs/client/sessions.md +324 -0
  16. data/docs/client/streaming.md +345 -0
  17. data/docs/client/sync-async.md +308 -0
  18. data/docs/core-concepts/agents.md +253 -0
  19. data/docs/core-concepts/events.md +337 -0
  20. data/docs/core-concepts/index.md +147 -0
  21. data/docs/core-concepts/messages.md +211 -0
  22. data/docs/core-concepts/runs.md +278 -0
  23. data/docs/core-concepts/sessions.md +281 -0
  24. data/docs/examples.md +659 -0
  25. data/docs/getting-started/configuration.md +166 -0
  26. data/docs/getting-started/index.md +62 -0
  27. data/docs/getting-started/installation.md +95 -0
  28. data/docs/getting-started/quick-start.md +189 -0
  29. data/docs/index.md +119 -0
  30. data/docs/server/creating-agents.md +360 -0
  31. data/docs/server/http-endpoints.md +411 -0
  32. data/docs/server/index.md +218 -0
  33. data/docs/server/multi-turn.md +329 -0
  34. data/docs/server/streaming.md +315 -0
  35. data/docs/storage/custom.md +414 -0
  36. data/docs/storage/index.md +176 -0
  37. data/docs/storage/memory.md +198 -0
  38. data/docs/storage/postgresql.md +350 -0
  39. data/docs/storage/redis.md +287 -0
  40. data/examples/01_basic/client.rb +88 -0
  41. data/examples/01_basic/server.rb +100 -0
  42. data/examples/02_async_execution/client.rb +107 -0
  43. data/examples/02_async_execution/server.rb +56 -0
  44. data/examples/03_run_management/client.rb +115 -0
  45. data/examples/03_run_management/server.rb +84 -0
  46. data/examples/04_rich_messages/client.rb +160 -0
  47. data/examples/04_rich_messages/server.rb +180 -0
  48. data/examples/05_await_resume/client.rb +164 -0
  49. data/examples/05_await_resume/server.rb +114 -0
  50. data/examples/06_agent_metadata/client.rb +188 -0
  51. data/examples/06_agent_metadata/server.rb +192 -0
  52. data/examples/README.md +252 -0
  53. data/examples/run_demo.sh +137 -0
  54. data/lib/simple_acp/client/base.rb +448 -0
  55. data/lib/simple_acp/client/sse.rb +141 -0
  56. data/lib/simple_acp/models/agent_manifest.rb +129 -0
  57. data/lib/simple_acp/models/await.rb +123 -0
  58. data/lib/simple_acp/models/base.rb +147 -0
  59. data/lib/simple_acp/models/errors.rb +102 -0
  60. data/lib/simple_acp/models/events.rb +256 -0
  61. data/lib/simple_acp/models/message.rb +235 -0
  62. data/lib/simple_acp/models/message_part.rb +225 -0
  63. data/lib/simple_acp/models/metadata.rb +161 -0
  64. data/lib/simple_acp/models/run.rb +298 -0
  65. data/lib/simple_acp/models/session.rb +137 -0
  66. data/lib/simple_acp/models/types.rb +210 -0
  67. data/lib/simple_acp/server/agent.rb +116 -0
  68. data/lib/simple_acp/server/app.rb +264 -0
  69. data/lib/simple_acp/server/base.rb +510 -0
  70. data/lib/simple_acp/server/context.rb +210 -0
  71. data/lib/simple_acp/server/falcon_runner.rb +61 -0
  72. data/lib/simple_acp/storage/base.rb +129 -0
  73. data/lib/simple_acp/storage/memory.rb +108 -0
  74. data/lib/simple_acp/storage/postgresql.rb +233 -0
  75. data/lib/simple_acp/storage/redis.rb +178 -0
  76. data/lib/simple_acp/version.rb +5 -0
  77. data/lib/simple_acp.rb +91 -0
  78. data/mkdocs.yml +152 -0
  79. data/sig/simple_acp.rbs +4 -0
  80. 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)