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,324 @@
|
|
|
1
|
+
# Session Management
|
|
2
|
+
|
|
3
|
+
Sessions enable stateful interactions across multiple runs, maintaining conversation history and custom state.
|
|
4
|
+
|
|
5
|
+
## Using Sessions
|
|
6
|
+
|
|
7
|
+
### Enable Session
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
client = SimpleAcp::Client::Base.new(base_url: "http://localhost:8000")
|
|
11
|
+
|
|
12
|
+
# Set the session to use
|
|
13
|
+
client.use_session("my-session-id")
|
|
14
|
+
|
|
15
|
+
# All subsequent runs use this session
|
|
16
|
+
client.run_sync(agent: "chat", input: [...])
|
|
17
|
+
client.run_sync(agent: "chat", input: [...])
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Clear Session
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
# Stop using the session
|
|
24
|
+
client.clear_session
|
|
25
|
+
|
|
26
|
+
# Runs are now sessionless
|
|
27
|
+
client.run_sync(agent: "chat", input: [...])
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Check Active Session
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
if client.session_id
|
|
34
|
+
puts "Using session: #{client.session_id}"
|
|
35
|
+
else
|
|
36
|
+
puts "No session active"
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Session IDs
|
|
41
|
+
|
|
42
|
+
### Generate Session IDs
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
# User-specific session
|
|
46
|
+
client.use_session("user-#{user_id}")
|
|
47
|
+
|
|
48
|
+
# Conversation-specific
|
|
49
|
+
client.use_session("chat-#{SecureRandom.uuid}")
|
|
50
|
+
|
|
51
|
+
# Time-based
|
|
52
|
+
client.use_session("session-#{Time.now.to_i}")
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Meaningful Session IDs
|
|
56
|
+
|
|
57
|
+
```ruby
|
|
58
|
+
# Good: Traceable, meaningful
|
|
59
|
+
client.use_session("user-42-support-ticket-123")
|
|
60
|
+
client.use_session("api-key-abc123-chat")
|
|
61
|
+
|
|
62
|
+
# Avoid: Random, untraceable
|
|
63
|
+
client.use_session(SecureRandom.hex(32))
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Retrieving Session Data
|
|
67
|
+
|
|
68
|
+
### Get Session Info
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
session = client.session("my-session-id")
|
|
72
|
+
|
|
73
|
+
puts "Session ID: #{session.id}"
|
|
74
|
+
puts "History length: #{session.history.length}"
|
|
75
|
+
puts "State: #{session.state.inspect}"
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Access History
|
|
79
|
+
|
|
80
|
+
```ruby
|
|
81
|
+
session = client.session("my-session-id")
|
|
82
|
+
|
|
83
|
+
session.history.each do |message|
|
|
84
|
+
puts "#{message.role}: #{message.text_content}"
|
|
85
|
+
end
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Access State
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
session = client.session("my-session-id")
|
|
92
|
+
|
|
93
|
+
if session.state
|
|
94
|
+
puts "Current step: #{session.state['step']}"
|
|
95
|
+
puts "User data: #{session.state['data'].inspect}"
|
|
96
|
+
end
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Conversation Patterns
|
|
100
|
+
|
|
101
|
+
### Multi-Turn Chat
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
def chat_loop(client, agent)
|
|
105
|
+
client.use_session("chat-#{SecureRandom.uuid}")
|
|
106
|
+
|
|
107
|
+
loop do
|
|
108
|
+
print "You: "
|
|
109
|
+
input = gets.chomp
|
|
110
|
+
break if input.downcase == 'quit'
|
|
111
|
+
|
|
112
|
+
run = client.run_sync(
|
|
113
|
+
agent: agent,
|
|
114
|
+
input: [SimpleAcp::Models::Message.user(input)]
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
puts "Agent: #{run.output.first&.text_content}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
client.clear_session
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Context-Aware Responses
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
client.use_session("support-123")
|
|
128
|
+
|
|
129
|
+
# First message
|
|
130
|
+
client.run_sync(
|
|
131
|
+
agent: "support",
|
|
132
|
+
input: [SimpleAcp::Models::Message.user("My order is late")]
|
|
133
|
+
)
|
|
134
|
+
# Agent: "I'm sorry to hear that. Can you provide your order number?"
|
|
135
|
+
|
|
136
|
+
# Follow-up (agent remembers context)
|
|
137
|
+
client.run_sync(
|
|
138
|
+
agent: "support",
|
|
139
|
+
input: [SimpleAcp::Models::Message.user("It's #12345")]
|
|
140
|
+
)
|
|
141
|
+
# Agent: "I found order #12345. It's expected to arrive tomorrow."
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Resuming Conversations
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Save session ID for later
|
|
148
|
+
session_id = "user-#{user_id}-conversation"
|
|
149
|
+
client.use_session(session_id)
|
|
150
|
+
|
|
151
|
+
# ... user interacts ...
|
|
152
|
+
|
|
153
|
+
# Later, resume the same conversation
|
|
154
|
+
client = SimpleAcp::Client::Base.new(base_url: "...")
|
|
155
|
+
client.use_session(session_id)
|
|
156
|
+
|
|
157
|
+
# Continues where it left off
|
|
158
|
+
run = client.run_sync(agent: "chat", input: [...])
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
## Session Lifecycle
|
|
162
|
+
|
|
163
|
+
```mermaid
|
|
164
|
+
sequenceDiagram
|
|
165
|
+
participant Client
|
|
166
|
+
participant Server
|
|
167
|
+
participant Storage
|
|
168
|
+
|
|
169
|
+
Client->>Client: use_session("id")
|
|
170
|
+
Client->>Server: run_sync (with session_id)
|
|
171
|
+
Server->>Storage: get_session("id")
|
|
172
|
+
Storage-->>Server: Session (or nil)
|
|
173
|
+
Server->>Server: Execute with history
|
|
174
|
+
Server->>Storage: save_session
|
|
175
|
+
Server-->>Client: Run result
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## Managing Multiple Sessions
|
|
179
|
+
|
|
180
|
+
### Per-User Sessions
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
class ChatService
|
|
184
|
+
def initialize(base_url)
|
|
185
|
+
@clients = {}
|
|
186
|
+
@base_url = base_url
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def get_client(user_id)
|
|
190
|
+
@clients[user_id] ||= begin
|
|
191
|
+
client = SimpleAcp::Client::Base.new(base_url: @base_url)
|
|
192
|
+
client.use_session("user-#{user_id}")
|
|
193
|
+
client
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def chat(user_id, message)
|
|
198
|
+
client = get_client(user_id)
|
|
199
|
+
client.run_sync(
|
|
200
|
+
agent: "chat",
|
|
201
|
+
input: [SimpleAcp::Models::Message.user(message)]
|
|
202
|
+
)
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Session Pool
|
|
208
|
+
|
|
209
|
+
```ruby
|
|
210
|
+
class SessionPool
|
|
211
|
+
def initialize(client)
|
|
212
|
+
@client = client
|
|
213
|
+
@sessions = {}
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def with_session(session_id)
|
|
217
|
+
@client.use_session(session_id)
|
|
218
|
+
yield @client
|
|
219
|
+
ensure
|
|
220
|
+
@client.clear_session
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Usage
|
|
225
|
+
pool = SessionPool.new(client)
|
|
226
|
+
|
|
227
|
+
pool.with_session("user-123") do |c|
|
|
228
|
+
c.run_sync(agent: "chat", input: [...])
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Session Expiration
|
|
233
|
+
|
|
234
|
+
Sessions may expire based on storage backend:
|
|
235
|
+
|
|
236
|
+
### Redis (TTL-based)
|
|
237
|
+
|
|
238
|
+
```ruby
|
|
239
|
+
# Sessions expire after TTL (default 24 hours)
|
|
240
|
+
client.use_session("short-lived")
|
|
241
|
+
# ... interact ...
|
|
242
|
+
|
|
243
|
+
# After TTL, session is gone
|
|
244
|
+
# New run starts fresh session
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### PostgreSQL (Persistent)
|
|
248
|
+
|
|
249
|
+
```ruby
|
|
250
|
+
# Sessions persist until explicitly deleted
|
|
251
|
+
client.use_session("persistent")
|
|
252
|
+
# ... can resume days later ...
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Memory (Process Lifetime)
|
|
256
|
+
|
|
257
|
+
```ruby
|
|
258
|
+
# Sessions lost when server restarts
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Error Handling
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
begin
|
|
265
|
+
session = client.session("unknown-session")
|
|
266
|
+
rescue SimpleAcp::Error => e
|
|
267
|
+
if e.message.include?("not found")
|
|
268
|
+
puts "Session doesn't exist"
|
|
269
|
+
else
|
|
270
|
+
raise
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
## Best Practices
|
|
276
|
+
|
|
277
|
+
1. **Use meaningful IDs** - Include user/context info for debugging
|
|
278
|
+
2. **Clean up sessions** - Delete when conversations end
|
|
279
|
+
3. **Handle expiration** - Gracefully handle missing sessions
|
|
280
|
+
4. **Scope appropriately** - One session per conversation, not per user
|
|
281
|
+
5. **Monitor size** - Long sessions may have large history
|
|
282
|
+
|
|
283
|
+
## Session Cleanup
|
|
284
|
+
|
|
285
|
+
```ruby
|
|
286
|
+
# After conversation ends
|
|
287
|
+
def end_conversation(client, session_id)
|
|
288
|
+
client.clear_session
|
|
289
|
+
|
|
290
|
+
# Optionally delete server-side
|
|
291
|
+
# (if supported by your storage)
|
|
292
|
+
end
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## Testing with Sessions
|
|
296
|
+
|
|
297
|
+
```ruby
|
|
298
|
+
def test_multi_turn_conversation
|
|
299
|
+
client = SimpleAcp::Client::Base.new(base_url: test_server_url)
|
|
300
|
+
client.use_session("test-#{SecureRandom.uuid}")
|
|
301
|
+
|
|
302
|
+
# First turn
|
|
303
|
+
run1 = client.run_sync(
|
|
304
|
+
agent: "counter",
|
|
305
|
+
input: [SimpleAcp::Models::Message.user("increment")]
|
|
306
|
+
)
|
|
307
|
+
assert_includes run1.output.first.text_content, "1"
|
|
308
|
+
|
|
309
|
+
# Second turn (should remember)
|
|
310
|
+
run2 = client.run_sync(
|
|
311
|
+
agent: "counter",
|
|
312
|
+
input: [SimpleAcp::Models::Message.user("increment")]
|
|
313
|
+
)
|
|
314
|
+
assert_includes run2.output.first.text_content, "2"
|
|
315
|
+
ensure
|
|
316
|
+
client.clear_session
|
|
317
|
+
end
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
## Next Steps
|
|
321
|
+
|
|
322
|
+
- Learn about [Sessions](../core-concepts/sessions.md) concept
|
|
323
|
+
- Explore [Multi-Turn Conversations](../server/multi-turn.md)
|
|
324
|
+
- Review [Storage Backends](../storage/index.md) for persistence options
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
# Streaming
|
|
2
|
+
|
|
3
|
+
Streaming enables real-time delivery of agent responses via Server-Sent Events (SSE), providing immediate feedback and better user experience.
|
|
4
|
+
|
|
5
|
+
## Basic Streaming
|
|
6
|
+
|
|
7
|
+
```ruby
|
|
8
|
+
client.run_stream(
|
|
9
|
+
agent: "chat",
|
|
10
|
+
input: [SimpleAcp::Models::Message.user("Tell me a story")]
|
|
11
|
+
) do |event|
|
|
12
|
+
case event
|
|
13
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
14
|
+
print event.part.content
|
|
15
|
+
$stdout.flush
|
|
16
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
17
|
+
puts "\n--- Complete ---"
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Event Types
|
|
23
|
+
|
|
24
|
+
Handle different event types for full control:
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
client.run_stream(agent: "processor", input: [...]) do |event|
|
|
28
|
+
case event
|
|
29
|
+
when SimpleAcp::Models::RunStartedEvent
|
|
30
|
+
puts "Run started: #{event.run_id}"
|
|
31
|
+
|
|
32
|
+
when SimpleAcp::Models::MessageCreatedEvent
|
|
33
|
+
puts "New message from: #{event.message.role}"
|
|
34
|
+
|
|
35
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
36
|
+
# Content chunk - stream to output
|
|
37
|
+
print event.part.content
|
|
38
|
+
|
|
39
|
+
when SimpleAcp::Models::MessageCompletedEvent
|
|
40
|
+
puts "\n[Message complete]"
|
|
41
|
+
|
|
42
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
43
|
+
puts "Run completed!"
|
|
44
|
+
puts "Output messages: #{event.run.output.length}"
|
|
45
|
+
|
|
46
|
+
when SimpleAcp::Models::RunFailedEvent
|
|
47
|
+
puts "Run failed: #{event.error.message}"
|
|
48
|
+
|
|
49
|
+
when SimpleAcp::Models::RunAwaitingEvent
|
|
50
|
+
puts "Run awaiting input..."
|
|
51
|
+
puts event.await_request.message.text_content
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Collecting Output
|
|
57
|
+
|
|
58
|
+
### Accumulate All Content
|
|
59
|
+
|
|
60
|
+
```ruby
|
|
61
|
+
output = ""
|
|
62
|
+
|
|
63
|
+
client.run_stream(agent: "chat", input: [...]) do |event|
|
|
64
|
+
case event
|
|
65
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
66
|
+
output << event.part.content
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
puts "Full response: #{output}"
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Collect Messages
|
|
74
|
+
|
|
75
|
+
```ruby
|
|
76
|
+
messages = []
|
|
77
|
+
|
|
78
|
+
client.run_stream(agent: "multi", input: [...]) do |event|
|
|
79
|
+
case event
|
|
80
|
+
when SimpleAcp::Models::MessageCompletedEvent
|
|
81
|
+
messages << event.message
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
puts "Received #{messages.length} messages"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Progress Tracking
|
|
89
|
+
|
|
90
|
+
### Simple Progress
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
parts_received = 0
|
|
94
|
+
|
|
95
|
+
client.run_stream(agent: "processor", input: [...]) do |event|
|
|
96
|
+
case event
|
|
97
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
98
|
+
parts_received += 1
|
|
99
|
+
print "\rReceived: #{parts_received} parts"
|
|
100
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
101
|
+
puts "\nDone!"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Detailed Progress
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
def stream_with_progress(client, agent:, input:)
|
|
110
|
+
state = { started_at: nil, parts: 0, messages: 0 }
|
|
111
|
+
|
|
112
|
+
client.run_stream(agent: agent, input: input) do |event|
|
|
113
|
+
case event
|
|
114
|
+
when SimpleAcp::Models::RunStartedEvent
|
|
115
|
+
state[:started_at] = Time.now
|
|
116
|
+
puts "Processing..."
|
|
117
|
+
|
|
118
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
119
|
+
state[:parts] += 1
|
|
120
|
+
print "."
|
|
121
|
+
$stdout.flush
|
|
122
|
+
|
|
123
|
+
when SimpleAcp::Models::MessageCompletedEvent
|
|
124
|
+
state[:messages] += 1
|
|
125
|
+
|
|
126
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
127
|
+
elapsed = Time.now - state[:started_at]
|
|
128
|
+
puts "\nComplete in #{elapsed.round(2)}s"
|
|
129
|
+
puts "#{state[:messages]} messages, #{state[:parts]} parts"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## Error Handling
|
|
136
|
+
|
|
137
|
+
### Basic Error Handling
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
begin
|
|
141
|
+
client.run_stream(agent: "risky", input: [...]) do |event|
|
|
142
|
+
case event
|
|
143
|
+
when SimpleAcp::Models::RunFailedEvent
|
|
144
|
+
puts "Agent error: #{event.error.message}"
|
|
145
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
146
|
+
print event.part.content
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
rescue Faraday::TimeoutError
|
|
150
|
+
puts "Stream timed out"
|
|
151
|
+
rescue Faraday::ConnectionFailed
|
|
152
|
+
puts "Connection lost"
|
|
153
|
+
end
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Graceful Recovery
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
def stream_with_retry(client, agent:, input:, max_retries: 3)
|
|
160
|
+
retries = 0
|
|
161
|
+
|
|
162
|
+
begin
|
|
163
|
+
client.run_stream(agent: agent, input: input) do |event|
|
|
164
|
+
yield event
|
|
165
|
+
end
|
|
166
|
+
rescue Faraday::ConnectionFailed, Faraday::TimeoutError => e
|
|
167
|
+
retries += 1
|
|
168
|
+
if retries <= max_retries
|
|
169
|
+
puts "Connection issue, retrying (#{retries}/#{max_retries})..."
|
|
170
|
+
sleep 2 ** retries # Exponential backoff
|
|
171
|
+
retry
|
|
172
|
+
else
|
|
173
|
+
raise e
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Usage
|
|
179
|
+
stream_with_retry(client, agent: "chat", input: [...]) do |event|
|
|
180
|
+
# Handle events
|
|
181
|
+
end
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## Streaming with Sessions
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
client.use_session("conversation-123")
|
|
188
|
+
|
|
189
|
+
# Stream with session context
|
|
190
|
+
client.run_stream(agent: "chat", input: [Message.user("Hello")]) do |event|
|
|
191
|
+
# Handle events...
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Subsequent streams maintain history
|
|
195
|
+
client.run_stream(agent: "chat", input: [Message.user("Continue")]) do |event|
|
|
196
|
+
# Has access to previous conversation
|
|
197
|
+
end
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Resume Streaming
|
|
201
|
+
|
|
202
|
+
For awaiting runs, resume with streaming:
|
|
203
|
+
|
|
204
|
+
```ruby
|
|
205
|
+
run = client.run_sync(agent: "questioner", input: [...])
|
|
206
|
+
|
|
207
|
+
if run.awaiting?
|
|
208
|
+
puts run.await_request.message.text_content
|
|
209
|
+
|
|
210
|
+
# Resume with streaming
|
|
211
|
+
client.run_resume_stream(
|
|
212
|
+
run_id: run.run_id,
|
|
213
|
+
await_resume: SimpleAcp::Models::MessageAwaitResume.new(
|
|
214
|
+
message: SimpleAcp::Models::Message.user("My answer")
|
|
215
|
+
)
|
|
216
|
+
) do |event|
|
|
217
|
+
case event
|
|
218
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
219
|
+
print event.part.content
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
## UI Integration
|
|
226
|
+
|
|
227
|
+
### Terminal UI
|
|
228
|
+
|
|
229
|
+
```ruby
|
|
230
|
+
require 'io/console'
|
|
231
|
+
|
|
232
|
+
def chat_interface(client, agent)
|
|
233
|
+
puts "Chat with #{agent} (type 'quit' to exit)"
|
|
234
|
+
puts "-" * 40
|
|
235
|
+
|
|
236
|
+
client.use_session("chat-#{SecureRandom.uuid}")
|
|
237
|
+
|
|
238
|
+
loop do
|
|
239
|
+
print "\nYou: "
|
|
240
|
+
input = gets.chomp
|
|
241
|
+
break if input.downcase == 'quit'
|
|
242
|
+
|
|
243
|
+
print "Agent: "
|
|
244
|
+
client.run_stream(
|
|
245
|
+
agent: agent,
|
|
246
|
+
input: [SimpleAcp::Models::Message.user(input)]
|
|
247
|
+
) do |event|
|
|
248
|
+
case event
|
|
249
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
250
|
+
print event.part.content
|
|
251
|
+
$stdout.flush
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
puts
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
chat_interface(client, "chat")
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### Web Application
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# Sinatra example
|
|
265
|
+
get '/stream' do
|
|
266
|
+
content_type 'text/event-stream'
|
|
267
|
+
|
|
268
|
+
stream(:keep_open) do |out|
|
|
269
|
+
client.run_stream(
|
|
270
|
+
agent: params[:agent],
|
|
271
|
+
input: [SimpleAcp::Models::Message.user(params[:input])]
|
|
272
|
+
) do |event|
|
|
273
|
+
case event
|
|
274
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
275
|
+
out << "data: #{event.part.content.to_json}\n\n"
|
|
276
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
277
|
+
out << "event: done\ndata: {}\n\n"
|
|
278
|
+
end
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
end
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Performance Tips
|
|
285
|
+
|
|
286
|
+
### Buffer Output
|
|
287
|
+
|
|
288
|
+
```ruby
|
|
289
|
+
buffer = []
|
|
290
|
+
|
|
291
|
+
client.run_stream(agent: "fast", input: [...]) do |event|
|
|
292
|
+
case event
|
|
293
|
+
when SimpleAcp::Models::MessagePartEvent
|
|
294
|
+
buffer << event.part.content
|
|
295
|
+
|
|
296
|
+
# Flush periodically
|
|
297
|
+
if buffer.length >= 10
|
|
298
|
+
print buffer.join
|
|
299
|
+
buffer.clear
|
|
300
|
+
end
|
|
301
|
+
when SimpleAcp::Models::RunCompletedEvent
|
|
302
|
+
print buffer.join unless buffer.empty?
|
|
303
|
+
end
|
|
304
|
+
end
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### Async Processing
|
|
308
|
+
|
|
309
|
+
```ruby
|
|
310
|
+
require 'concurrent'
|
|
311
|
+
|
|
312
|
+
def stream_async(client, agent:, input:)
|
|
313
|
+
future = Concurrent::Future.execute do
|
|
314
|
+
result = []
|
|
315
|
+
client.run_stream(agent: agent, input: input) do |event|
|
|
316
|
+
result << event
|
|
317
|
+
end
|
|
318
|
+
result
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
future
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# Start streaming in background
|
|
325
|
+
future = stream_async(client, agent: "processor", input: [...])
|
|
326
|
+
|
|
327
|
+
# Do other work...
|
|
328
|
+
|
|
329
|
+
# Get results when ready
|
|
330
|
+
events = future.value
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
## Best Practices
|
|
334
|
+
|
|
335
|
+
1. **Flush output** - Call `$stdout.flush` for real-time display
|
|
336
|
+
2. **Handle all events** - Don't ignore error events
|
|
337
|
+
3. **Use timeouts** - Configure appropriate connection timeouts
|
|
338
|
+
4. **Buffer intelligently** - Balance responsiveness with efficiency
|
|
339
|
+
5. **Clean up** - Handle connection errors gracefully
|
|
340
|
+
|
|
341
|
+
## Next Steps
|
|
342
|
+
|
|
343
|
+
- Learn about [Session Management](sessions.md)
|
|
344
|
+
- Explore [Events](../core-concepts/events.md) in depth
|
|
345
|
+
- See [Server Streaming](../server/streaming.md) for server-side details
|