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,287 @@
|
|
|
1
|
+
# Redis Storage
|
|
2
|
+
|
|
3
|
+
Redis storage provides distributed, TTL-based storage suitable for production deployments with multiple server processes.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Distributed** - Share state across multiple processes
|
|
8
|
+
- **TTL-based** - Automatic expiration of old data
|
|
9
|
+
- **Fast** - Sub-millisecond operations
|
|
10
|
+
- **Scalable** - Supports Redis clustering
|
|
11
|
+
|
|
12
|
+
## Requirements
|
|
13
|
+
|
|
14
|
+
Add the Redis gem to your Gemfile:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'redis', '~> 5.0'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Usage
|
|
21
|
+
|
|
22
|
+
### Basic Setup
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
require 'simple_acp'
|
|
26
|
+
require 'simple_acp/storage/redis'
|
|
27
|
+
|
|
28
|
+
storage = SimpleAcp::Storage::Redis.new(
|
|
29
|
+
url: "redis://localhost:6379"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
server = SimpleAcp::Server::Base.new(storage: storage)
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Configuration Options
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
storage = SimpleAcp::Storage::Redis.new(
|
|
39
|
+
# Connection
|
|
40
|
+
url: "redis://localhost:6379", # Redis URL
|
|
41
|
+
host: "localhost", # Or specify individually
|
|
42
|
+
port: 6379,
|
|
43
|
+
db: 0,
|
|
44
|
+
password: "secret",
|
|
45
|
+
ssl: false,
|
|
46
|
+
|
|
47
|
+
# Or use existing connection
|
|
48
|
+
redis: existing_redis_client,
|
|
49
|
+
|
|
50
|
+
# Behavior
|
|
51
|
+
ttl: 86400, # TTL in seconds (default: 24 hours)
|
|
52
|
+
prefix: "acp:" # Key prefix (default: "acp:")
|
|
53
|
+
)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Environment Variables
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
# Uses REDIS_URL if not specified
|
|
60
|
+
storage = SimpleAcp::Storage::Redis.new
|
|
61
|
+
|
|
62
|
+
# Reads from:
|
|
63
|
+
# ENV['REDIS_URL'] || "redis://localhost:6379"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Data Model
|
|
67
|
+
|
|
68
|
+
### Key Structure
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
acp:run:{run_id} # Run data (JSON)
|
|
72
|
+
acp:session:{session_id} # Session data (JSON)
|
|
73
|
+
acp:events:{run_id} # Events list (JSON items)
|
|
74
|
+
acp:agent_runs:{agent_name} # Set of run IDs for agent
|
|
75
|
+
acp:session_runs:{session_id} # Set of run IDs for session
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### TTL Behavior
|
|
79
|
+
|
|
80
|
+
All keys expire after the configured TTL:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
storage = SimpleAcp::Storage::Redis.new(
|
|
84
|
+
ttl: 3600 # 1 hour
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Run expires 1 hour after last update
|
|
88
|
+
storage.save_run(run)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Operations
|
|
92
|
+
|
|
93
|
+
### Runs
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
# Save (sets TTL)
|
|
97
|
+
storage.save_run(run)
|
|
98
|
+
|
|
99
|
+
# Get
|
|
100
|
+
run = storage.get_run("run-id")
|
|
101
|
+
|
|
102
|
+
# Delete (also removes from indexes)
|
|
103
|
+
storage.delete_run("run-id")
|
|
104
|
+
|
|
105
|
+
# List by agent
|
|
106
|
+
result = storage.list_runs(agent_name: "echo")
|
|
107
|
+
|
|
108
|
+
# List by session
|
|
109
|
+
result = storage.list_runs(session_id: "session-123")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Sessions
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
# Save (sets TTL)
|
|
116
|
+
storage.save_session(session)
|
|
117
|
+
|
|
118
|
+
# Get
|
|
119
|
+
session = storage.get_session("session-id")
|
|
120
|
+
|
|
121
|
+
# Delete
|
|
122
|
+
storage.delete_session("session-id")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Events
|
|
126
|
+
|
|
127
|
+
```ruby
|
|
128
|
+
# Add (extends TTL)
|
|
129
|
+
storage.add_event("run-id", event)
|
|
130
|
+
|
|
131
|
+
# Get
|
|
132
|
+
events = storage.get_events("run-id", limit: 100, offset: 0)
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Connection Management
|
|
136
|
+
|
|
137
|
+
### Connection Pooling
|
|
138
|
+
|
|
139
|
+
Use connection pooling in multi-threaded environments:
|
|
140
|
+
|
|
141
|
+
```ruby
|
|
142
|
+
require 'connection_pool'
|
|
143
|
+
|
|
144
|
+
pool = ConnectionPool.new(size: 10) do
|
|
145
|
+
Redis.new(url: ENV['REDIS_URL'])
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Create storage with pooled connection
|
|
149
|
+
storage = SimpleAcp::Storage::Redis.new(redis: pool)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Close Connection
|
|
153
|
+
|
|
154
|
+
```ruby
|
|
155
|
+
storage.close
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Health Check
|
|
159
|
+
|
|
160
|
+
```ruby
|
|
161
|
+
if storage.ping
|
|
162
|
+
puts "Redis is healthy"
|
|
163
|
+
else
|
|
164
|
+
puts "Redis connection failed"
|
|
165
|
+
end
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Scaling
|
|
169
|
+
|
|
170
|
+
### Redis Cluster
|
|
171
|
+
|
|
172
|
+
For high availability and horizontal scaling:
|
|
173
|
+
|
|
174
|
+
```ruby
|
|
175
|
+
require 'redis-cluster-client'
|
|
176
|
+
|
|
177
|
+
redis = RedisClient.cluster(
|
|
178
|
+
nodes: [
|
|
179
|
+
{ host: "node1.example.com", port: 6379 },
|
|
180
|
+
{ host: "node2.example.com", port: 6379 },
|
|
181
|
+
{ host: "node3.example.com", port: 6379 }
|
|
182
|
+
]
|
|
183
|
+
).new_pool(timeout: 5, size: 10)
|
|
184
|
+
|
|
185
|
+
storage = SimpleAcp::Storage::Redis.new(redis: redis)
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### Multiple Processes
|
|
189
|
+
|
|
190
|
+
Redis naturally supports multiple processes:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
# Process 1
|
|
194
|
+
server1 = SimpleAcp::Server::Base.new(
|
|
195
|
+
storage: SimpleAcp::Storage::Redis.new(url: ENV['REDIS_URL'])
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
# Process 2 (shares data with Process 1)
|
|
199
|
+
server2 = SimpleAcp::Server::Base.new(
|
|
200
|
+
storage: SimpleAcp::Storage::Redis.new(url: ENV['REDIS_URL'])
|
|
201
|
+
)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Production Configuration
|
|
205
|
+
|
|
206
|
+
### Recommended Settings
|
|
207
|
+
|
|
208
|
+
```ruby
|
|
209
|
+
storage = SimpleAcp::Storage::Redis.new(
|
|
210
|
+
url: ENV['REDIS_URL'],
|
|
211
|
+
ttl: 86400 * 7, # 7 days
|
|
212
|
+
prefix: "myapp:acp:" # Namespace for your app
|
|
213
|
+
)
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### With Sentinel
|
|
217
|
+
|
|
218
|
+
```ruby
|
|
219
|
+
redis = Redis.new(
|
|
220
|
+
url: "redis://mymaster",
|
|
221
|
+
sentinels: [
|
|
222
|
+
{ host: "sentinel1", port: 26379 },
|
|
223
|
+
{ host: "sentinel2", port: 26379 },
|
|
224
|
+
{ host: "sentinel3", port: 26379 }
|
|
225
|
+
],
|
|
226
|
+
role: :master
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
storage = SimpleAcp::Storage::Redis.new(redis: redis)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Maintenance
|
|
233
|
+
|
|
234
|
+
### Clear All Data
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
storage.clear! # Deletes all keys with prefix
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Monitor Key Count
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
redis-cli KEYS "acp:*" | wc -l
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Check Memory Usage
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
redis-cli INFO memory
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Error Handling
|
|
253
|
+
|
|
254
|
+
```ruby
|
|
255
|
+
begin
|
|
256
|
+
run = storage.get_run("run-id")
|
|
257
|
+
rescue Redis::CannotConnectError
|
|
258
|
+
puts "Redis connection failed"
|
|
259
|
+
# Fallback logic...
|
|
260
|
+
rescue Redis::TimeoutError
|
|
261
|
+
puts "Redis timeout"
|
|
262
|
+
# Retry logic...
|
|
263
|
+
end
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
## Best Practices
|
|
267
|
+
|
|
268
|
+
1. **Use appropriate TTL** - Balance retention vs. memory usage
|
|
269
|
+
2. **Use key prefix** - Namespace to avoid collisions
|
|
270
|
+
3. **Monitor memory** - Watch Redis memory usage
|
|
271
|
+
4. **Use connection pooling** - For multi-threaded apps
|
|
272
|
+
5. **Set up replication** - For high availability
|
|
273
|
+
|
|
274
|
+
## Comparison with Other Backends
|
|
275
|
+
|
|
276
|
+
| Feature | Redis | Memory | PostgreSQL |
|
|
277
|
+
|---------|-------|--------|------------|
|
|
278
|
+
| Persistence | TTL | None | Permanent |
|
|
279
|
+
| Multi-process | Yes | No | Yes |
|
|
280
|
+
| Speed | Fast | Fastest | Moderate |
|
|
281
|
+
| Query flexibility | Limited | None | Full SQL |
|
|
282
|
+
|
|
283
|
+
## Next Steps
|
|
284
|
+
|
|
285
|
+
- Learn about [PostgreSQL](postgresql.md) for permanent storage
|
|
286
|
+
- Build a [Custom Backend](custom.md) for special needs
|
|
287
|
+
- Review [Memory](memory.md) for development
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Basic SimpleAcp Client Example
|
|
5
|
+
#
|
|
6
|
+
# First start the server: ruby examples/01_basic/server.rb
|
|
7
|
+
# Then run this client: ruby examples/01_basic/client.rb
|
|
8
|
+
|
|
9
|
+
require_relative "../../lib/simple_acp"
|
|
10
|
+
|
|
11
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
12
|
+
|
|
13
|
+
puts "=== SimpleAcp Client Example ==="
|
|
14
|
+
puts
|
|
15
|
+
|
|
16
|
+
# Check server health
|
|
17
|
+
if client.ping
|
|
18
|
+
puts "✓ Server is healthy"
|
|
19
|
+
else
|
|
20
|
+
puts "✗ Server is not responding"
|
|
21
|
+
exit 1
|
|
22
|
+
end
|
|
23
|
+
puts
|
|
24
|
+
|
|
25
|
+
# List available agents
|
|
26
|
+
puts "Available agents:"
|
|
27
|
+
agents = client.agents
|
|
28
|
+
agents.agents.each do |agent|
|
|
29
|
+
puts " - #{agent.name}: #{agent.description}"
|
|
30
|
+
end
|
|
31
|
+
puts
|
|
32
|
+
|
|
33
|
+
# Test echo agent
|
|
34
|
+
puts "--- Testing echo agent ---"
|
|
35
|
+
run = client.run_sync(agent: "echo", input: "Hello, SimpleAcp!")
|
|
36
|
+
puts "Input: Hello, SimpleAcp!"
|
|
37
|
+
puts "Output: #{run.output.first.text_content}"
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
# Test greeter agent
|
|
41
|
+
puts "--- Testing greeter agent ---"
|
|
42
|
+
run = client.run_sync(agent: "greeter", input: "Ruby Developer")
|
|
43
|
+
puts "Input: Ruby Developer"
|
|
44
|
+
puts "Output: #{run.output.first.text_content}"
|
|
45
|
+
puts
|
|
46
|
+
|
|
47
|
+
# Test counter agent with session
|
|
48
|
+
puts "--- Testing counter agent with session ---"
|
|
49
|
+
session_id = SecureRandom.uuid
|
|
50
|
+
client.use_session(session_id)
|
|
51
|
+
|
|
52
|
+
3.times do |i|
|
|
53
|
+
run = client.run_sync(agent: "counter", input: "increment")
|
|
54
|
+
puts "Call #{i + 1}: #{run.output.first.text_content}"
|
|
55
|
+
end
|
|
56
|
+
client.clear_session
|
|
57
|
+
puts
|
|
58
|
+
|
|
59
|
+
# Test streaming with Gettysburg Address (word by word with 0.5s delay)
|
|
60
|
+
puts "--- Testing streaming (Gettysburg Address) ---"
|
|
61
|
+
puts
|
|
62
|
+
print " "
|
|
63
|
+
client.run_stream(agent: "gettysburg", input: "recite") do |event|
|
|
64
|
+
case event
|
|
65
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
66
|
+
print event.part.content
|
|
67
|
+
$stdout.flush
|
|
68
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
69
|
+
puts
|
|
70
|
+
puts
|
|
71
|
+
puts "[Streaming complete]"
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
puts
|
|
75
|
+
|
|
76
|
+
# Test assistant with history
|
|
77
|
+
puts "--- Testing assistant with history ---"
|
|
78
|
+
session_id = SecureRandom.uuid
|
|
79
|
+
client.use_session(session_id)
|
|
80
|
+
|
|
81
|
+
["Hi!", "Tell me more", "Thanks!"].each do |msg|
|
|
82
|
+
run = client.run_sync(agent: "assistant", input: msg)
|
|
83
|
+
puts "User: #{msg}"
|
|
84
|
+
puts "Agent: #{run.output.first.text_content}"
|
|
85
|
+
puts
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
puts "=== All tests completed ==="
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Basic SimpleAcp Server Example
|
|
5
|
+
#
|
|
6
|
+
# Run with: ruby examples/01_basic/server.rb
|
|
7
|
+
# Then connect with: ruby examples/01_basic/client.rb
|
|
8
|
+
|
|
9
|
+
require_relative "../../lib/simple_acp"
|
|
10
|
+
|
|
11
|
+
server = SimpleAcp::Server::Base.new
|
|
12
|
+
|
|
13
|
+
# Simple echo agent
|
|
14
|
+
server.agent("echo", description: "Echoes everything you send") do |context|
|
|
15
|
+
Enumerator.new do |yielder|
|
|
16
|
+
context.input.each do |message|
|
|
17
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
18
|
+
SimpleAcp::Models::Message.agent(message.text_content)
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Greeting agent
|
|
25
|
+
server.agent("greeter", description: "Greets the user by name") do |context|
|
|
26
|
+
name = context.input.first&.text_content&.strip
|
|
27
|
+
name = "World" if name.nil? || name.empty?
|
|
28
|
+
|
|
29
|
+
SimpleAcp::Models::Message.agent("Hello, #{name}! Welcome to SimpleAcp.")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Stateful counter agent
|
|
33
|
+
server.agent("counter", description: "Counts how many times you've called it") do |context|
|
|
34
|
+
count = (context.state || 0) + 1
|
|
35
|
+
context.set_state(count)
|
|
36
|
+
|
|
37
|
+
SimpleAcp::Models::Message.agent("You have called me #{count} time(s).")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Streaming demonstration agent - recites the Gettysburg Address word by word
|
|
41
|
+
server.agent("gettysburg", description: "Recites the Gettysburg Address word by word") do |context|
|
|
42
|
+
address = <<~TEXT
|
|
43
|
+
Four score and seven years ago our fathers brought forth on this continent, a new nation, conceived in Liberty, and dedicated to the proposition that all men are created equal.
|
|
44
|
+
Now we are engaged in a great civil war, testing whether that nation, or any nation so conceived and so dedicated, can long endure.
|
|
45
|
+
We are met on a great battle-field of that war.
|
|
46
|
+
We have come to dedicate a portion of that field, as a final resting place for those who here gave their lives that that nation might live.
|
|
47
|
+
It is altogether fitting and proper that we should do this.
|
|
48
|
+
But, in a larger sense, we can not dedicate — we can not consecrate — we can not hallow — this ground.
|
|
49
|
+
The brave men, living and dead, who struggled here, have consecrated it, far above our poor power to add or detract.
|
|
50
|
+
The world will little note, nor long remember what we say here, but it can never forget what they did here.
|
|
51
|
+
It is for us the living, rather, to be dedicated here to the unfinished work which they who fought here have thus far so nobly advanced.
|
|
52
|
+
It is rather for us to be here dedicated to the great task remaining before us — that from these honored dead we take increased devotion to that cause for which they gave the last full measure of devotion — that we here highly resolve that these dead shall not have died in vain — that this nation, under God, shall have a new birth of freedom — and that government of the people, by the people, for the people, shall not perish from the earth.
|
|
53
|
+
TEXT
|
|
54
|
+
|
|
55
|
+
words = address.split
|
|
56
|
+
|
|
57
|
+
Enumerator.new do |yielder|
|
|
58
|
+
words.each do |word|
|
|
59
|
+
# Use fiber-aware sleep for Async/Falcon compatibility
|
|
60
|
+
if defined?(Async::Task) && Async::Task.current?
|
|
61
|
+
Async::Task.current.sleep(0.3)
|
|
62
|
+
else
|
|
63
|
+
sleep(0.3)
|
|
64
|
+
end
|
|
65
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
66
|
+
SimpleAcp::Models::Message.agent(word + " ")
|
|
67
|
+
)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Multi-turn conversation agent
|
|
73
|
+
server.agent("assistant",
|
|
74
|
+
description: "A simple assistant that remembers conversation history",
|
|
75
|
+
input_content_types: ["text/plain", "application/json"],
|
|
76
|
+
output_content_types: ["text/plain"]
|
|
77
|
+
) do |context|
|
|
78
|
+
# Access conversation history
|
|
79
|
+
history_count = context.history.length
|
|
80
|
+
|
|
81
|
+
response = if history_count.zero?
|
|
82
|
+
"Hello! This is our first conversation. How can I help you?"
|
|
83
|
+
else
|
|
84
|
+
"I see we've had #{history_count} previous messages. What else can I help with?"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
SimpleAcp::Models::Message.agent(response)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
########################################################
|
|
91
|
+
## main line
|
|
92
|
+
#
|
|
93
|
+
puts "Starting SimpleAcp Server..."
|
|
94
|
+
puts "Available agents:"
|
|
95
|
+
server.agents.each do |name, agent|
|
|
96
|
+
puts " - #{name}: #{agent.description}"
|
|
97
|
+
end
|
|
98
|
+
puts
|
|
99
|
+
|
|
100
|
+
server.run(port: 8000)
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Async Execution Example - Client
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates:
|
|
7
|
+
# - Async (non-blocking) execution with run_async
|
|
8
|
+
# - Polling run status with run_status
|
|
9
|
+
# - Waiting for completion with wait_for_run
|
|
10
|
+
# - Running multiple tasks concurrently
|
|
11
|
+
#
|
|
12
|
+
# First start the server: ruby examples/02_async_execution/server.rb
|
|
13
|
+
# Then run this client: ruby examples/02_async_execution/client.rb
|
|
14
|
+
|
|
15
|
+
require_relative "../../lib/simple_acp"
|
|
16
|
+
|
|
17
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
18
|
+
|
|
19
|
+
puts "=== Async Execution Demo ==="
|
|
20
|
+
puts
|
|
21
|
+
|
|
22
|
+
# Verify server is running
|
|
23
|
+
unless client.ping
|
|
24
|
+
puts "Server is not responding"
|
|
25
|
+
exit 1
|
|
26
|
+
end
|
|
27
|
+
puts "Server is healthy"
|
|
28
|
+
puts
|
|
29
|
+
|
|
30
|
+
# --- Demo 1: Async execution with manual polling ---
|
|
31
|
+
puts "--- Demo 1: Async Execution with Manual Polling ---"
|
|
32
|
+
puts
|
|
33
|
+
|
|
34
|
+
puts "Starting slow-worker asynchronously..."
|
|
35
|
+
run = client.run_async(agent: "slow-worker", input: "data analysis")
|
|
36
|
+
puts "Run started with ID: #{run.run_id}"
|
|
37
|
+
puts "Initial status: #{run.status}"
|
|
38
|
+
puts
|
|
39
|
+
|
|
40
|
+
puts "Polling for status..."
|
|
41
|
+
loop do
|
|
42
|
+
status = client.run_status(run.run_id)
|
|
43
|
+
puts " Status: #{status.status}"
|
|
44
|
+
|
|
45
|
+
break if status.terminal?
|
|
46
|
+
|
|
47
|
+
sleep(0.5)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
puts
|
|
51
|
+
puts "Run completed! Final output:"
|
|
52
|
+
final_run = client.run_status(run.run_id)
|
|
53
|
+
final_run.output.each do |message|
|
|
54
|
+
puts " #{message.text_content}"
|
|
55
|
+
end
|
|
56
|
+
puts
|
|
57
|
+
|
|
58
|
+
# --- Demo 2: Using wait_for_run for simpler polling ---
|
|
59
|
+
puts "--- Demo 2: Using wait_for_run ---"
|
|
60
|
+
puts
|
|
61
|
+
|
|
62
|
+
puts "Starting another slow-worker..."
|
|
63
|
+
run = client.run_async(agent: "slow-worker", input: "report generation")
|
|
64
|
+
puts "Run ID: #{run.run_id}"
|
|
65
|
+
puts
|
|
66
|
+
|
|
67
|
+
puts "Waiting for completion (timeout: 10s)..."
|
|
68
|
+
completed_run = client.wait_for_run(run.run_id, timeout: 10)
|
|
69
|
+
|
|
70
|
+
if completed_run.status == "completed"
|
|
71
|
+
puts "Success! Output:"
|
|
72
|
+
completed_run.output.each do |message|
|
|
73
|
+
puts " #{message.text_content}"
|
|
74
|
+
end
|
|
75
|
+
else
|
|
76
|
+
puts "Run ended with status: #{completed_run.status}"
|
|
77
|
+
end
|
|
78
|
+
puts
|
|
79
|
+
|
|
80
|
+
# --- Demo 3: Running multiple tasks concurrently ---
|
|
81
|
+
puts "--- Demo 3: Concurrent Async Execution ---"
|
|
82
|
+
puts
|
|
83
|
+
|
|
84
|
+
puts "Starting 3 tasks concurrently..."
|
|
85
|
+
runs = []
|
|
86
|
+
%w[task_alpha task_beta task_gamma].each do |task_name|
|
|
87
|
+
run = client.run_async(agent: "slow-worker", input: task_name)
|
|
88
|
+
runs << run
|
|
89
|
+
puts " Started: #{task_name} (#{run.run_id[0..7]}...)"
|
|
90
|
+
end
|
|
91
|
+
puts
|
|
92
|
+
|
|
93
|
+
puts "Waiting for all tasks to complete..."
|
|
94
|
+
start_time = Time.now
|
|
95
|
+
|
|
96
|
+
runs.each do |run|
|
|
97
|
+
completed = client.wait_for_run(run.run_id, timeout: 15)
|
|
98
|
+
task_name = completed.output.last&.text_content || "unknown"
|
|
99
|
+
puts " Completed: #{task_name}"
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
elapsed = (Time.now - start_time).round(2)
|
|
103
|
+
puts
|
|
104
|
+
puts "All 3 tasks completed in #{elapsed}s (ran concurrently, not 9s sequentially)"
|
|
105
|
+
puts
|
|
106
|
+
|
|
107
|
+
puts "=== Async Execution Demo Complete ==="
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# Async Execution Example - Server
|
|
5
|
+
#
|
|
6
|
+
# Demonstrates async (non-blocking) execution with polling.
|
|
7
|
+
#
|
|
8
|
+
# Run with: ruby examples/02_async_execution/server.rb
|
|
9
|
+
# Then connect with: ruby examples/02_async_execution/client.rb
|
|
10
|
+
|
|
11
|
+
require_relative "../../lib/simple_acp"
|
|
12
|
+
|
|
13
|
+
server = SimpleAcp::Server::Base.new
|
|
14
|
+
|
|
15
|
+
# Slow worker agent - simulates a long-running task
|
|
16
|
+
server.agent("slow-worker", description: "Simulates a long-running task (3 seconds)") do |context|
|
|
17
|
+
task_name = context.input.first&.text_content || "default task"
|
|
18
|
+
|
|
19
|
+
# Simulate work with progress updates
|
|
20
|
+
Enumerator.new do |yielder|
|
|
21
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
22
|
+
SimpleAcp::Models::Message.agent("Starting task: #{task_name}")
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Simulate 3 seconds of work
|
|
26
|
+
3.times do |i|
|
|
27
|
+
if defined?(Async::Task) && Async::Task.current?
|
|
28
|
+
Async::Task.current.sleep(1)
|
|
29
|
+
else
|
|
30
|
+
sleep(1)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
34
|
+
SimpleAcp::Models::Message.agent("Progress: #{(i + 1) * 33}%")
|
|
35
|
+
)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
yielder << SimpleAcp::Server::RunYield.new(
|
|
39
|
+
SimpleAcp::Models::Message.agent("Task '#{task_name}' completed!")
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Quick status agent - returns immediately
|
|
45
|
+
server.agent("quick-status", description: "Returns status immediately") do |context|
|
|
46
|
+
SimpleAcp::Models::Message.agent("Status: All systems operational at #{Time.now}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
puts "Starting Async Execution Demo Server..."
|
|
50
|
+
puts "Available agents:"
|
|
51
|
+
server.agents.each do |name, agent|
|
|
52
|
+
puts " - #{name}: #{agent.description}"
|
|
53
|
+
end
|
|
54
|
+
puts
|
|
55
|
+
|
|
56
|
+
server.run(port: 8000)
|