robot_lab 0.2.6 → 0.2.7
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 +4 -4
- data/README.md +124 -64
- data/docs/api/core/index.md +41 -15
- data/docs/api/core/memory.md +247 -29
- data/docs/api/core/network.md +255 -33
- data/docs/api/core/result.md +120 -32
- data/docs/api/core/robot.md +551 -61
- data/docs/api/core/state.md +87 -197
- data/docs/api/core/tool.md +165 -20
- data/docs/api/errors.md +110 -17
- data/docs/api/hooks.md +469 -0
- data/docs/api/index.md +80 -7
- data/docs/api/mcp/client.md +129 -35
- data/docs/api/mcp/index.md +164 -23
- data/docs/api/mcp/server.md +27 -3
- data/docs/api/mcp/transports.md +94 -22
- data/docs/api/messages/index.md +26 -3
- data/docs/api/messages/text-message.md +33 -11
- data/docs/api/messages/tool-call-message.md +27 -4
- data/docs/api/messages/tool-result-message.md +23 -4
- data/docs/api/messages/user-message.md +45 -8
- data/docs/api/skills.md +519 -0
- data/docs/api/streaming/context.md +28 -5
- data/docs/api/streaming/index.md +57 -11
- data/docs/api/support.md +846 -0
- data/docs/architecture/core-concepts.md +79 -31
- data/docs/architecture/index.md +86 -11
- data/docs/architecture/message-flow.md +66 -29
- data/docs/architecture/network-orchestration.md +145 -38
- data/docs/architecture/robot-execution.md +172 -90
- data/docs/architecture/state-management.md +31 -12
- data/docs/concepts.md +176 -21
- data/docs/examples/basic-chat.md +72 -19
- data/docs/examples/index.md +117 -31
- data/docs/examples/mcp-server.md +154 -45
- data/docs/examples/multi-robot-network.md +91 -21
- data/docs/examples/tool-usage.md +104 -37
- data/docs/getting-started/configuration.md +284 -97
- data/docs/getting-started/installation.md +53 -41
- data/docs/getting-started/quick-start.md +51 -6
- data/docs/guides/building-robots.md +258 -50
- data/docs/guides/creating-networks.md +214 -30
- data/docs/guides/hooks.md +141 -54
- data/docs/guides/knowledge.md +35 -4
- data/docs/guides/mcp-integration.md +211 -44
- data/docs/guides/memory.md +103 -12
- data/docs/guides/observability.md +95 -47
- data/docs/guides/streaming.md +184 -125
- data/docs/guides/using-tools.md +237 -17
- data/docs/index.md +36 -4
- data/examples/01_simple_robot.rb +2 -2
- data/examples/02_tools.rb +14 -4
- data/examples/03_network.rb +12 -7
- data/examples/04_mcp.rb +11 -4
- data/examples/05_streaming.rb +8 -5
- data/examples/06_prompt_templates.rb +13 -9
- data/examples/07_network_memory.rb +5 -5
- data/examples/08_llm_config.rb +20 -15
- data/examples/09_chaining.rb +4 -4
- data/examples/11_network_introspection.rb +4 -4
- data/examples/12_message_bus.rb +2 -2
- data/examples/13_spawn.rb +2 -2
- data/examples/14_rusty_circuit/README.md +1 -0
- data/examples/14_rusty_circuit/comic.rb +7 -3
- data/examples/14_rusty_circuit/display.rb +14 -0
- data/examples/14_rusty_circuit/heckler.rb +8 -6
- data/examples/14_rusty_circuit/open_mic.rb +17 -6
- data/examples/14_rusty_circuit/scout.rb +17 -10
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +14 -10
- data/examples/15_memory_network_and_bus/linux_writer.rb +2 -2
- data/examples/15_memory_network_and_bus/os_editor.rb +3 -1
- data/examples/15_memory_network_and_bus/os_writer.rb +4 -1
- data/examples/16_writers_room/writer.rb +22 -22
- data/examples/16_writers_room/writers_room.rb +2 -0
- data/examples/17_skills.rb +14 -13
- data/examples/18_rails/README.md +20 -1
- data/examples/18_rails/app/controllers/chat_controller.rb +5 -1
- data/examples/18_rails/app/jobs/robot_run_job.rb +11 -5
- data/examples/18_rails/app/robots/chat_robot.rb +11 -0
- data/examples/18_rails/config/initializers/robot_lab.rb +8 -0
- data/examples/19_token_tracking.rb +25 -9
- data/examples/20_circuit_breaker.rb +10 -7
- data/examples/21_learning_loop.rb +42 -16
- data/examples/22_context_compression.rb +23 -23
- data/examples/23_convergence.rb +24 -17
- data/examples/24_structured_delegation.rb +13 -8
- data/examples/25_history_search.rb +12 -8
- data/examples/27_incident_response/incident_response.rb +31 -13
- data/examples/28_mcp_discovery.rb +17 -13
- data/examples/29_ractor_tools.rb +4 -2
- data/examples/30_ractor_network.rb +22 -17
- data/examples/31_launch_assessment.rb +20 -9
- data/examples/32_newsletter_reader.rb +7 -2
- data/examples/33_stock_predictor.rb +34 -13
- data/examples/34_agentskills.rb +7 -3
- data/examples/35_hooks.rb +18 -8
- data/examples/README.md +199 -45
- data/examples/common.rb +79 -11
- data/examples/xyzzy.rb +8 -1
- data/lib/robot_lab/config.rb +10 -5
- data/lib/robot_lab/names.rb +402 -0
- data/lib/robot_lab/robot/agent_skill_matching.rb +1 -3
- data/lib/robot_lab/robot/bus_messaging.rb +16 -8
- data/lib/robot_lab/robot/template_rendering.rb +16 -3
- data/lib/robot_lab/robot.rb +23 -2
- data/lib/robot_lab/version.rb +1 -1
- data/lib/robot_lab.rb +21 -15
- data/mkdocs.yml +6 -1
- metadata +7 -2
data/docs/guides/streaming.md
CHANGED
|
@@ -2,114 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
Stream LLM responses in real-time for better user experience.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
RobotLab has exactly two ways to receive streaming content, and both hand you a
|
|
6
|
+
`RubyLLM::Chunk`:
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
1. **`on_content:`** — a callback wired at build time (or through `RunConfig`) that fires on *every* `run`
|
|
9
|
+
2. **A block passed to `run`** — `robot.run(msg) { |chunk| ... }`, for one-off streaming
|
|
10
|
+
|
|
11
|
+
> [!IMPORTANT]
|
|
12
|
+
> Read the content off the chunk with **`chunk.content`**. `RubyLLM::Chunk`
|
|
13
|
+
> has no `#text` method — `chunk.text` raises `NoMethodError`.
|
|
14
|
+
|
|
15
|
+
## Streaming via `on_content:`
|
|
16
|
+
|
|
17
|
+
Wire the callback once at build time; it fires on every subsequent `run`:
|
|
8
18
|
|
|
9
19
|
```ruby
|
|
10
20
|
robot = RobotLab.build(
|
|
11
21
|
name: "storyteller",
|
|
12
|
-
system_prompt: "You are a creative storyteller."
|
|
22
|
+
system_prompt: "You are a creative storyteller.",
|
|
23
|
+
on_content: ->(chunk) { print chunk.content }
|
|
13
24
|
)
|
|
14
25
|
|
|
15
|
-
# Register streaming callback
|
|
16
|
-
robot.on_new_message do |message|
|
|
17
|
-
print message.content if message.content
|
|
18
|
-
end
|
|
19
|
-
|
|
20
26
|
result = robot.run("Tell me a story about a brave robot")
|
|
27
|
+
puts
|
|
28
|
+
puts result.last_text_content
|
|
21
29
|
```
|
|
22
30
|
|
|
23
|
-
##
|
|
31
|
+
## Streaming via a Block on `run`
|
|
24
32
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Called when the assistant starts generating a new message, with streaming chunks:
|
|
33
|
+
For one-off streaming, pass a block straight to `run`:
|
|
28
34
|
|
|
29
35
|
```ruby
|
|
30
|
-
robot
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
### on_end_message
|
|
36
|
-
|
|
37
|
-
Called when the assistant finishes a message:
|
|
36
|
+
robot = RobotLab.build(
|
|
37
|
+
name: "factbot",
|
|
38
|
+
system_prompt: "You are concise. Answer in one sentence."
|
|
39
|
+
)
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
robot.on_end_message do |message|
|
|
41
|
-
puts "\n--- Response complete ---"
|
|
42
|
-
puts "Content length: #{message.content&.length}"
|
|
43
|
-
end
|
|
41
|
+
robot.run("What year was Ruby created?") { |chunk| print chunk.content }
|
|
44
42
|
```
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
The block goes to the same place `on_content:` does — no need to reach for
|
|
45
|
+
`robot.chat.ask` to get streaming, and reaching for it skips memory resolution,
|
|
46
|
+
tool resolution, hooks, and budget accounting.
|
|
49
47
|
|
|
50
|
-
|
|
51
|
-
robot.on_tool_call do |tool_call|
|
|
52
|
-
puts "Calling tool: #{tool_call.name}"
|
|
53
|
-
end
|
|
54
|
-
```
|
|
48
|
+
## Using Both Together
|
|
55
49
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
Called when a tool returns its result:
|
|
50
|
+
When a robot has a stored `on_content:` *and* a block is passed to `run`, both
|
|
51
|
+
fire for every chunk. The **stored callback fires first**, then the block:
|
|
59
52
|
|
|
60
53
|
```ruby
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
end
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
## Comprehensive Callback Setup
|
|
67
|
-
|
|
68
|
-
Register all callbacks for full visibility:
|
|
54
|
+
stored_log = []
|
|
55
|
+
block_log = []
|
|
69
56
|
|
|
70
|
-
```ruby
|
|
71
57
|
robot = RobotLab.build(
|
|
72
|
-
name: "
|
|
73
|
-
system_prompt: "You are
|
|
74
|
-
|
|
58
|
+
name: "combo",
|
|
59
|
+
system_prompt: "You are concise.",
|
|
60
|
+
on_content: ->(chunk) { stored_log << chunk.content }
|
|
75
61
|
)
|
|
76
62
|
|
|
77
|
-
robot.
|
|
78
|
-
print message.content if message.content
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
robot.on_end_message do |_message|
|
|
82
|
-
puts "\n--- Done ---"
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
robot.on_tool_call do |tool_call|
|
|
86
|
-
puts "\n[Tool] Calling: #{tool_call.name}"
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
robot.on_tool_result do |tool_call, result|
|
|
90
|
-
puts "[Tool] #{tool_call.name} returned: #{result}"
|
|
91
|
-
end
|
|
63
|
+
robot.run("What is Matz's full name?") { |chunk| block_log << chunk.content }
|
|
92
64
|
|
|
93
|
-
|
|
65
|
+
stored_log == block_log # => true — both saw the same chunks
|
|
94
66
|
```
|
|
95
67
|
|
|
96
|
-
## Streaming via
|
|
68
|
+
## Streaming via RunConfig
|
|
97
69
|
|
|
98
|
-
|
|
70
|
+
`on_content` is a `RunConfig` field, so it participates in the config cascade:
|
|
99
71
|
|
|
100
72
|
```ruby
|
|
73
|
+
config = RobotLab::RunConfig.new(
|
|
74
|
+
model: "claude-sonnet-4",
|
|
75
|
+
on_content: ->(chunk) { print chunk.content }
|
|
76
|
+
)
|
|
77
|
+
|
|
101
78
|
robot = RobotLab.build(
|
|
102
|
-
name: "
|
|
103
|
-
system_prompt: "You are
|
|
79
|
+
name: "config_bot",
|
|
80
|
+
system_prompt: "You are concise.",
|
|
81
|
+
config: config
|
|
104
82
|
)
|
|
105
83
|
|
|
106
|
-
|
|
107
|
-
robot.chat.ask("Tell me a story") do |chunk|
|
|
108
|
-
print chunk.content if chunk.content
|
|
109
|
-
end
|
|
84
|
+
robot.run("Who designed the Ruby programming language?")
|
|
110
85
|
```
|
|
111
86
|
|
|
112
|
-
|
|
87
|
+
> [!WARNING]
|
|
88
|
+
> `on_content` is read from the **robot's own** config when the robot is
|
|
89
|
+
> constructed. A network-level `config:` propagates only `mcp` and `tools` to
|
|
90
|
+
> member robots — it will **not** supply `on_content` to them. Put the callback
|
|
91
|
+
> on each robot (or on the `config:` you pass to that robot).
|
|
113
92
|
|
|
114
93
|
## Web Integration
|
|
115
94
|
|
|
@@ -120,18 +99,14 @@ class ChatChannel < ApplicationCable::Channel
|
|
|
120
99
|
def receive(data)
|
|
121
100
|
robot = RobotLab.build(
|
|
122
101
|
name: "chat_bot",
|
|
123
|
-
system_prompt: "You are a helpful chat assistant."
|
|
102
|
+
system_prompt: "You are a helpful chat assistant.",
|
|
103
|
+
on_content: ->(chunk) {
|
|
104
|
+
transmit({ event: "text.delta", content: chunk.content })
|
|
105
|
+
}
|
|
124
106
|
)
|
|
125
107
|
|
|
126
|
-
robot.on_new_message do |message|
|
|
127
|
-
transmit({ event: "text.delta", content: message.content }) if message.content
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
robot.on_end_message do |_message|
|
|
131
|
-
transmit({ event: "run.completed" })
|
|
132
|
-
end
|
|
133
|
-
|
|
134
108
|
robot.run(data["message"])
|
|
109
|
+
transmit({ event: "run.completed" })
|
|
135
110
|
end
|
|
136
111
|
end
|
|
137
112
|
```
|
|
@@ -150,15 +125,11 @@ class StreamController < ApplicationController
|
|
|
150
125
|
system_prompt: "You are helpful."
|
|
151
126
|
)
|
|
152
127
|
|
|
153
|
-
robot.
|
|
154
|
-
response.stream.write("data: #{
|
|
128
|
+
robot.run(params[:message]) do |chunk|
|
|
129
|
+
response.stream.write("data: #{chunk.content}\n\n")
|
|
155
130
|
end
|
|
156
131
|
|
|
157
|
-
|
|
158
|
-
response.stream.write("data: [DONE]\n\n")
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
robot.run(params[:message])
|
|
132
|
+
response.stream.write("data: [DONE]\n\n")
|
|
162
133
|
ensure
|
|
163
134
|
response.stream.close
|
|
164
135
|
end
|
|
@@ -170,95 +141,183 @@ end
|
|
|
170
141
|
```ruby
|
|
171
142
|
# Using Faye WebSocket
|
|
172
143
|
ws.on :message do |msg|
|
|
173
|
-
robot.
|
|
174
|
-
ws.send(message.content) if message.content
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
robot.run(msg.data)
|
|
144
|
+
robot.run(msg.data) { |chunk| ws.send(chunk.content) }
|
|
178
145
|
end
|
|
179
146
|
```
|
|
180
147
|
|
|
181
148
|
## Progress Tracking
|
|
182
149
|
|
|
183
|
-
|
|
150
|
+
Chunk callbacks are the right place to count characters. Tool activity is
|
|
151
|
+
tracked separately, through the robot's `on_tool_call:` / `on_tool_result:`
|
|
152
|
+
callbacks:
|
|
184
153
|
|
|
185
154
|
```ruby
|
|
186
155
|
class StreamProgress
|
|
156
|
+
attr_reader :chars, :tools
|
|
157
|
+
|
|
187
158
|
def initialize
|
|
188
159
|
@chars = 0
|
|
189
160
|
@tools = 0
|
|
190
161
|
end
|
|
191
162
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
@chars += message.content.length if message.content
|
|
163
|
+
# Returns the streaming block to hand to run().
|
|
164
|
+
def content_callback
|
|
165
|
+
->(chunk) {
|
|
166
|
+
@chars += chunk.content.to_s.length
|
|
197
167
|
print "\rReceived #{@chars} characters..."
|
|
198
|
-
|
|
168
|
+
}
|
|
169
|
+
end
|
|
199
170
|
|
|
200
|
-
|
|
171
|
+
def tool_callback
|
|
172
|
+
->(tool_call) {
|
|
201
173
|
@tools += 1
|
|
202
174
|
puts "\nTool call ##{@tools}: #{tool_call.name}"
|
|
203
|
-
|
|
175
|
+
}
|
|
204
176
|
end
|
|
205
177
|
end
|
|
206
178
|
|
|
207
179
|
progress = StreamProgress.new
|
|
208
|
-
progress.attach(robot)
|
|
209
180
|
|
|
210
|
-
|
|
181
|
+
robot = RobotLab.build(
|
|
182
|
+
name: "assistant",
|
|
183
|
+
system_prompt: "You are helpful.",
|
|
184
|
+
local_tools: [WeatherTool],
|
|
185
|
+
on_content: progress.content_callback,
|
|
186
|
+
on_tool_call: progress.tool_callback
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
robot.run("Process this complex request", tools: :inherit)
|
|
211
190
|
puts "\nTotal: #{progress.chars} chars, #{progress.tools} tool calls"
|
|
212
191
|
```
|
|
213
192
|
|
|
193
|
+
> [!NOTE]
|
|
194
|
+
> `chunk.content` can be `nil` on chunks that carry only metadata (tool-call
|
|
195
|
+
> deltas, usage). Guard with `to_s` when accumulating, as above.
|
|
196
|
+
|
|
197
|
+
> [!WARNING]
|
|
198
|
+
> `run` defaults to `tools: :none`, so the tool callback above never fires
|
|
199
|
+
> unless you pass `tools: :inherit` (or an explicit allowlist). See
|
|
200
|
+
> [Using Tools](using-tools.md#runtime-tool-filtering).
|
|
201
|
+
|
|
214
202
|
## Without Streaming
|
|
215
203
|
|
|
216
|
-
When streaming is not needed,
|
|
204
|
+
When streaming is not needed, call `run` with no `on_content:` and no block:
|
|
217
205
|
|
|
218
206
|
```ruby
|
|
219
|
-
# No streaming - returns RobotResult directly
|
|
220
207
|
result = robot.run("Hello!")
|
|
221
208
|
puts result.last_text_content
|
|
222
209
|
```
|
|
223
210
|
|
|
224
|
-
##
|
|
211
|
+
## Tool Callbacks
|
|
212
|
+
|
|
213
|
+
`on_tool_call:` and `on_tool_result:` are constructor/`RunConfig` callbacks
|
|
214
|
+
alongside `on_content:`. Each receives **one** argument:
|
|
225
215
|
|
|
226
|
-
|
|
216
|
+
```ruby
|
|
217
|
+
robot = RobotLab.build(
|
|
218
|
+
name: "assistant",
|
|
219
|
+
system_prompt: "You are helpful.",
|
|
220
|
+
local_tools: [WeatherTool],
|
|
221
|
+
on_tool_call: ->(tool_call) { puts "\n[Tool] Calling: #{tool_call.name}" },
|
|
222
|
+
on_tool_result: ->(result) { puts "[Tool] returned: #{result}" }
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
robot.run("What's the weather in Tokyo?", tools: :inherit)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Note that `on_tool_result` does **not** receive the originating tool call —
|
|
229
|
+
only the result. If you need to correlate the two, record the call in
|
|
230
|
+
`on_tool_call` and pair them up yourself.
|
|
231
|
+
|
|
232
|
+
> [!NOTE]
|
|
233
|
+
> RobotLab installs these two callbacks on the underlying chat's
|
|
234
|
+
> `on_tool_call` / `on_tool_result` hooks, which RubyLLM 1.16 has deprecated.
|
|
235
|
+
> Building a robot with either callback therefore prints a RubyLLM deprecation
|
|
236
|
+
> warning. The callbacks themselves work; the warning comes from the layer
|
|
237
|
+
> below.
|
|
238
|
+
|
|
239
|
+
## Legacy RubyLLM Chat Callbacks
|
|
240
|
+
|
|
241
|
+
`Robot` also exposes RubyLLM's chat-level callbacks
|
|
242
|
+
(`on_new_message`, `on_end_message`, `on_tool_call`, `on_tool_result`).
|
|
243
|
+
|
|
244
|
+
> [!CAUTION]
|
|
245
|
+
> All four are **deprecated in RubyLLM 1.16** and slated for removal in
|
|
246
|
+
> RubyLLM 2.0. Registering one emits a deprecation warning naming its
|
|
247
|
+
> replacement: `before_message`, `after_message`, `before_tool_call`, and
|
|
248
|
+
> `after_tool_result`. Prefer RobotLab's `on_content:` /
|
|
249
|
+
> `on_tool_call:` / `on_tool_result:` constructor callbacks instead.
|
|
250
|
+
|
|
251
|
+
Their arities are not what you might expect:
|
|
252
|
+
|
|
253
|
+
| Callback | Replacement | Arguments received |
|
|
254
|
+
|----------|-------------|--------------------|
|
|
255
|
+
| `on_new_message` | `before_message` | **none** |
|
|
256
|
+
| `on_end_message` | `after_message` | the completed `RubyLLM::Message` |
|
|
257
|
+
| `on_tool_call` | `before_tool_call` | the `ToolCall` |
|
|
258
|
+
| `on_tool_result` | `after_tool_result` | the **result only** |
|
|
259
|
+
|
|
260
|
+
> [!WARNING]
|
|
261
|
+
> `on_new_message` is **not** a streaming hook. It fires once when a message
|
|
262
|
+
> begins and is invoked with **zero arguments** — a block written as
|
|
263
|
+
> `do |message| ... end` receives `nil`, so `message.content` raises
|
|
264
|
+
> `NoMethodError: undefined method 'content' for nil`. Use `on_content:` or a
|
|
265
|
+
> `run` block for content.
|
|
227
266
|
|
|
228
267
|
```ruby
|
|
229
|
-
# Correct
|
|
230
|
-
robot.on_new_message {
|
|
231
|
-
robot.
|
|
268
|
+
# Correct usage of the legacy hooks (still deprecated):
|
|
269
|
+
robot.chat.on_new_message { puts "--- message starting ---" } # no argument
|
|
270
|
+
robot.chat.on_end_message { |message| puts "Length: #{message.content&.length}" }
|
|
271
|
+
robot.chat.on_tool_result { |result| puts "Tool returned: #{result}" }
|
|
232
272
|
```
|
|
233
273
|
|
|
234
|
-
|
|
274
|
+
## Best Practices
|
|
275
|
+
|
|
276
|
+
### 1. Wire streaming before the run
|
|
277
|
+
|
|
278
|
+
`on_content:` belongs in the constructor (or the `config:` you pass it); a
|
|
279
|
+
`run` block belongs on the call itself. There is no "register then run" step.
|
|
280
|
+
|
|
281
|
+
### 2. Handle errors inside the callback
|
|
282
|
+
|
|
283
|
+
An exception raised in a streaming callback propagates out of `run` and aborts
|
|
284
|
+
the turn. Swallow client-side failures you can survive:
|
|
235
285
|
|
|
236
286
|
```ruby
|
|
237
|
-
robot
|
|
238
|
-
|
|
239
|
-
|
|
287
|
+
robot = RobotLab.build(
|
|
288
|
+
name: "broadcaster",
|
|
289
|
+
system_prompt: "You are helpful.",
|
|
290
|
+
on_content: ->(chunk) do
|
|
291
|
+
broadcast(chunk.content)
|
|
240
292
|
rescue BroadcastError => e
|
|
241
|
-
# Client disconnected, but
|
|
242
|
-
logger.warn
|
|
293
|
+
# Client disconnected, but let the run finish.
|
|
294
|
+
RobotLab.config.logger.warn("Broadcast failed: #{e.message}")
|
|
243
295
|
end
|
|
244
|
-
|
|
296
|
+
)
|
|
245
297
|
```
|
|
246
298
|
|
|
247
|
-
### 3. Clean
|
|
299
|
+
### 3. Clean up resources
|
|
248
300
|
|
|
249
301
|
```ruby
|
|
250
302
|
begin
|
|
251
|
-
robot.
|
|
252
|
-
stream_to_client(message.content) if message.content
|
|
253
|
-
end
|
|
254
|
-
robot.run("Hello")
|
|
303
|
+
robot.run("Hello") { |chunk| stream_to_client(chunk.content) }
|
|
255
304
|
ensure
|
|
256
305
|
close_stream_connection
|
|
257
306
|
end
|
|
258
307
|
```
|
|
259
308
|
|
|
309
|
+
## What About `RobotLab::Streaming`?
|
|
310
|
+
|
|
311
|
+
`RobotLab::Streaming::Context`, `Streaming::Events`, and
|
|
312
|
+
`Streaming::SequenceCounter` exist in the codebase and are documented in the
|
|
313
|
+
[API reference](../api/streaming/index.md), but **nothing in `Robot` or
|
|
314
|
+
`Network` constructs or publishes to them**. They are a standalone event-object
|
|
315
|
+
toolkit you would have to drive yourself — not the path that `run` takes. For
|
|
316
|
+
actual token streaming, use `on_content:` or a `run` block as shown above.
|
|
317
|
+
|
|
260
318
|
## Next Steps
|
|
261
319
|
|
|
262
320
|
- [Building Robots](building-robots.md) - Robot creation
|
|
263
321
|
- [Creating Networks](creating-networks.md) - Network patterns
|
|
264
|
-
- [
|
|
322
|
+
- [examples/05_streaming.rb](https://github.com/MadBomber/robot_lab/blob/main/examples/05_streaming.rb) - Runnable streaming example
|
|
323
|
+
- [API Reference: Streaming](../api/streaming/index.md) - The standalone `Streaming::*` event objects
|