robot_lab 0.3.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +12 -0
- data/README.md +1 -1
- data/lib/robot_lab/mcp/transports/streamable_http.rb +1 -1
- data/lib/robot_lab/version.rb +1 -1
- metadata +3 -253
- data/.architecture/AGENTS.md +0 -32
- data/.architecture/config.yml +0 -8
- data/.architecture/members.yml +0 -60
- data/.architecture/reviews/feature-free-will.md +0 -490
- data/.architecture/reviews/overall-codebase.md +0 -427
- data/.claude/settings.local.json +0 -57
- data/.codex/config.toml +0 -2
- data/.envrc +0 -6
- data/.github/workflows/deploy-github-pages.yml +0 -52
- data/.irbrc +0 -6
- data/.loki +0 -65
- data/.quality/reek_baseline.txt +0 -43
- data/.rubocop.yml +0 -10
- data/Archspec.rb +0 -44
- data/CLAUDE.md +0 -141
- data/COMMITS.md +0 -196
- data/Rakefile +0 -193
- data/_typos.toml +0 -21
- data/agent2agent_review.md +0 -192
- data/agentf_improvements.md +0 -253
- data/agents.md +0 -14
- data/docs/api/core/index.md +0 -147
- data/docs/api/core/memory.md +0 -578
- data/docs/api/core/network.md +0 -553
- data/docs/api/core/result.md +0 -211
- data/docs/api/core/robot.md +0 -1667
- data/docs/api/core/state.md +0 -145
- data/docs/api/core/tool.md +0 -520
- data/docs/api/errors.md +0 -323
- data/docs/api/hooks.md +0 -469
- data/docs/api/index.md +0 -137
- data/docs/api/mcp/client.md +0 -387
- data/docs/api/mcp/index.md +0 -267
- data/docs/api/mcp/server.md +0 -252
- data/docs/api/mcp/transports.md +0 -339
- data/docs/api/messages/index.md +0 -126
- data/docs/api/messages/text-message.md +0 -170
- data/docs/api/messages/tool-call-message.md +0 -206
- data/docs/api/messages/tool-result-message.md +0 -242
- data/docs/api/messages/user-message.md +0 -232
- data/docs/api/skills.md +0 -423
- data/docs/api/streaming/context.md +0 -280
- data/docs/api/streaming/events.md +0 -185
- data/docs/api/streaming/index.md +0 -156
- data/docs/api/support.md +0 -846
- data/docs/architecture/core-concepts.md +0 -575
- data/docs/architecture/index.md +0 -252
- data/docs/architecture/message-flow.md +0 -366
- data/docs/architecture/network-orchestration.md +0 -478
- data/docs/architecture/robot-execution.md +0 -396
- data/docs/architecture/state-management.md +0 -410
- data/docs/assets/css/custom.css +0 -56
- data/docs/assets/images/robot_lab.jpg +0 -0
- data/docs/concepts.md +0 -641
- data/docs/examples/basic-chat.md +0 -258
- data/docs/examples/index.md +0 -425
- data/docs/examples/mcp-server.md +0 -403
- data/docs/examples/multi-robot-network.md +0 -476
- data/docs/examples/tool-usage.md +0 -468
- data/docs/getting-started/configuration.md +0 -645
- data/docs/getting-started/index.md +0 -56
- data/docs/getting-started/installation.md +0 -197
- data/docs/getting-started/quick-start.md +0 -293
- data/docs/guides/building-robots.md +0 -1187
- data/docs/guides/creating-networks.md +0 -736
- data/docs/guides/hooks.md +0 -1206
- data/docs/guides/index.md +0 -78
- data/docs/guides/knowledge.md +0 -219
- data/docs/guides/mcp-integration.md +0 -740
- data/docs/guides/memory.md +0 -458
- data/docs/guides/observability.md +0 -733
- data/docs/guides/streaming.md +0 -323
- data/docs/guides/using-tools.md +0 -780
- data/docs/index.md +0 -280
- data/docs/superpowers/plans/2026-05-06-agentskills.md +0 -1303
- data/docs/superpowers/specs/2026-05-06-agentskills-design.md +0 -247
- data/examples/.envrc +0 -3
- data/examples/01_simple_robot.rb +0 -31
- data/examples/02_tools.rb +0 -109
- data/examples/03_network.rb +0 -118
- data/examples/04_mcp.rb +0 -217
- data/examples/05_streaming.rb +0 -137
- data/examples/06_prompt_templates.rb +0 -325
- data/examples/07_network_memory.rb +0 -313
- data/examples/08_llm_config.rb +0 -162
- data/examples/09_chaining.rb +0 -251
- data/examples/10_memory.rb +0 -315
- data/examples/11_network_introspection.rb +0 -239
- data/examples/12_message_bus.rb +0 -67
- data/examples/13_spawn.rb +0 -85
- data/examples/14_rusty_circuit/.envrc +0 -1
- data/examples/14_rusty_circuit/.gitignore +0 -1
- data/examples/14_rusty_circuit/README.md +0 -1
- data/examples/14_rusty_circuit/comic.rb +0 -149
- data/examples/14_rusty_circuit/display.rb +0 -217
- data/examples/14_rusty_circuit/heckler.rb +0 -65
- data/examples/14_rusty_circuit/open_mic.rb +0 -132
- data/examples/14_rusty_circuit/prompts/open_mic_comic.md +0 -20
- data/examples/14_rusty_circuit/prompts/open_mic_heckler.md +0 -23
- data/examples/14_rusty_circuit/prompts/open_mic_scout.md +0 -20
- data/examples/14_rusty_circuit/scout.rb +0 -165
- data/examples/14_rusty_circuit/show.log +0 -234
- data/examples/15_memory_network_and_bus/.envrc +0 -1
- data/examples/15_memory_network_and_bus/README.md +0 -66
- data/examples/15_memory_network_and_bus/editor_in_chief.rb +0 -24
- data/examples/15_memory_network_and_bus/editorial_pipeline.rb +0 -213
- data/examples/15_memory_network_and_bus/linux_writer.rb +0 -80
- data/examples/15_memory_network_and_bus/os_editor.rb +0 -48
- data/examples/15_memory_network_and_bus/os_writer.rb +0 -49
- data/examples/15_memory_network_and_bus/output/combined_article.md +0 -11
- data/examples/15_memory_network_and_bus/output/final_article.md +0 -8
- data/examples/15_memory_network_and_bus/output/linux_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/mac_draft.md +0 -7
- data/examples/15_memory_network_and_bus/output/memory.json +0 -13
- data/examples/15_memory_network_and_bus/output/revision_1.md +0 -29
- data/examples/15_memory_network_and_bus/output/revision_2.md +0 -50
- data/examples/15_memory_network_and_bus/output/revision_3.md +0 -8
- data/examples/15_memory_network_and_bus/output/windows_draft.md +0 -7
- data/examples/15_memory_network_and_bus/prompts/os_advocate.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_chief.md +0 -13
- data/examples/15_memory_network_and_bus/prompts/os_editor.md +0 -13
- data/examples/16_writers_room/.envrc +0 -1
- data/examples/16_writers_room/display.rb +0 -158
- data/examples/16_writers_room/output/.gitignore +0 -4
- data/examples/16_writers_room/output/README.md +0 -69
- data/examples/16_writers_room/output/opus_001.md +0 -263
- data/examples/16_writers_room/output/opus_001_notes.log +0 -470
- data/examples/16_writers_room/output/opus_002.md +0 -245
- data/examples/16_writers_room/output/opus_002_notes.log +0 -546
- data/examples/16_writers_room/output/opus_002_screenplay.md +0 -7989
- data/examples/16_writers_room/output/opus_002_screenplay_notes.md +0 -993
- data/examples/16_writers_room/prompts/screenplay_writer.md +0 -66
- data/examples/16_writers_room/prompts/writer.md +0 -37
- data/examples/16_writers_room/room.rb +0 -186
- data/examples/16_writers_room/tools.rb +0 -173
- data/examples/16_writers_room/writer.rb +0 -121
- data/examples/16_writers_room/writers_room.rb +0 -256
- data/examples/17_skills.rb +0 -234
- data/examples/18_rails/.envrc +0 -3
- data/examples/18_rails/.gitignore +0 -5
- data/examples/18_rails/Gemfile +0 -11
- data/examples/18_rails/README.md +0 -67
- data/examples/18_rails/Rakefile +0 -4
- data/examples/18_rails/app/controllers/application_controller.rb +0 -4
- data/examples/18_rails/app/controllers/chat_controller.rb +0 -50
- data/examples/18_rails/app/jobs/application_job.rb +0 -4
- data/examples/18_rails/app/jobs/robot_run_job.rb +0 -25
- data/examples/18_rails/app/models/application_record.rb +0 -5
- data/examples/18_rails/app/models/robot_lab_result.rb +0 -36
- data/examples/18_rails/app/models/robot_lab_thread.rb +0 -23
- data/examples/18_rails/app/robots/chat_robot.rb +0 -25
- data/examples/18_rails/app/tools/time_tool.rb +0 -9
- data/examples/18_rails/app/views/chat/_user_message.html.erb +0 -1
- data/examples/18_rails/app/views/chat/index.html.erb +0 -67
- data/examples/18_rails/app/views/layouts/application.html.erb +0 -49
- data/examples/18_rails/bin/dev +0 -7
- data/examples/18_rails/bin/rails +0 -6
- data/examples/18_rails/bin/setup +0 -15
- data/examples/18_rails/config/application.rb +0 -33
- data/examples/18_rails/config/cable.yml +0 -2
- data/examples/18_rails/config/database.yml +0 -5
- data/examples/18_rails/config/environment.rb +0 -4
- data/examples/18_rails/config/initializers/robot_lab.rb +0 -11
- data/examples/18_rails/config/routes.rb +0 -6
- data/examples/18_rails/config.ru +0 -4
- data/examples/18_rails/db/migrate/001_create_robot_lab_tables.rb +0 -32
- data/examples/19_token_tracking.rb +0 -138
- data/examples/20_circuit_breaker.rb +0 -147
- data/examples/21_learning_loop.rb +0 -181
- data/examples/22_context_compression.rb +0 -172
- data/examples/23_convergence.rb +0 -133
- data/examples/24_structured_delegation.rb +0 -151
- data/examples/25_history_search/conversation.jsonl +0 -30
- data/examples/25_history_search.rb +0 -133
- data/examples/26_document_store/api_versioning_adr.md +0 -52
- data/examples/26_document_store/incident_postmortem.md +0 -46
- data/examples/26_document_store/postgres_runbook.md +0 -49
- data/examples/26_document_store/redis_caching_guide.md +0 -48
- data/examples/26_document_store/sidekiq_guide.md +0 -51
- data/examples/26_document_store.rb +0 -140
- data/examples/27_incident_response/README.md +0 -65
- data/examples/27_incident_response/incident_response.rb +0 -261
- data/examples/28_mcp_discovery.rb +0 -113
- data/examples/29_ractor_tools.rb +0 -240
- data/examples/30_ractor_network.rb +0 -252
- data/examples/31_launch_assessment.rb +0 -246
- data/examples/32_newsletter_reader.rb +0 -193
- data/examples/33_stock_generator.rb +0 -80
- data/examples/33_stock_predictor.rb +0 -327
- data/examples/34_agentskills.rb +0 -76
- data/examples/35_hooks.rb +0 -266
- data/examples/README.md +0 -550
- data/examples/common.rb +0 -180
- data/examples/prompts/assistant.md +0 -5
- data/examples/prompts/audit_trail.md +0 -8
- data/examples/prompts/billing.md +0 -10
- data/examples/prompts/classifier.md +0 -7
- data/examples/prompts/comedian.md +0 -6
- data/examples/prompts/comedy_critic.md +0 -10
- data/examples/prompts/configurable.md +0 -9
- data/examples/prompts/dispatcher.md +0 -12
- data/examples/prompts/entity_extractor.md +0 -14
- data/examples/prompts/escalation.md +0 -42
- data/examples/prompts/frontmatter_mcp_test.md +0 -9
- data/examples/prompts/frontmatter_named_test.md +0 -5
- data/examples/prompts/frontmatter_tools_test.md +0 -6
- data/examples/prompts/general.md +0 -7
- data/examples/prompts/github_assistant.md +0 -14
- data/examples/prompts/helper.md +0 -4
- data/examples/prompts/incident_responder.md +0 -18
- data/examples/prompts/keyword_extractor.md +0 -11
- data/examples/prompts/llm_config_demo.md +0 -20
- data/examples/prompts/order_support.md +0 -35
- data/examples/prompts/os_advocate.md +0 -13
- data/examples/prompts/os_chief.md +0 -13
- data/examples/prompts/os_editor.md +0 -13
- data/examples/prompts/parameterized_main_test.md +0 -6
- data/examples/prompts/pii_redactor.md +0 -7
- data/examples/prompts/product_support.md +0 -37
- data/examples/prompts/runbook_protocol.md +0 -12
- data/examples/prompts/sentiment_analyzer.md +0 -12
- data/examples/prompts/skill_a_test.md +0 -4
- data/examples/prompts/skill_b_test.md +0 -4
- data/examples/prompts/skill_config_test.md +0 -5
- data/examples/prompts/skill_cycle_a_test.md +0 -6
- data/examples/prompts/skill_cycle_b_test.md +0 -6
- data/examples/prompts/skill_description_test.md +0 -4
- data/examples/prompts/skill_leaf_test.md +0 -4
- data/examples/prompts/skill_nested_test.md +0 -6
- data/examples/prompts/skill_refs_main_test.md +0 -6
- data/examples/prompts/skill_self_ref_test.md +0 -6
- data/examples/prompts/skill_with_mcp_test.md +0 -9
- data/examples/prompts/skill_with_params_test.md +0 -6
- data/examples/prompts/skill_with_robot_name_test.md +0 -5
- data/examples/prompts/skill_with_tools_test.md +0 -6
- data/examples/prompts/sre_compliance.md +0 -10
- data/examples/prompts/structured_output.md +0 -7
- data/examples/prompts/synthesizer.md +0 -17
- data/examples/prompts/technical.md +0 -10
- data/examples/prompts/template_with_skills_test.md +0 -6
- data/examples/prompts/triage.md +0 -22
- data/examples/ruboruby.md +0 -423
- data/examples/run_all.rb +0 -60
- data/examples/xyzzy.rb +0 -97
- data/logfile +0 -8
- data/mkdocs.yml +0 -209
- data/robot_concurrency.md +0 -38
- data/simple_acp_review.md +0 -298
- data/temp.md +0 -6
- data/tool_manifest_plan.md +0 -155
data/docs/guides/streaming.md
DELETED
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
# Streaming Responses
|
|
2
|
-
|
|
3
|
-
Stream LLM responses in real-time for better user experience.
|
|
4
|
-
|
|
5
|
-
RobotLab has exactly two ways to receive streaming content, and both hand you a
|
|
6
|
-
`RubyLLM::Chunk`:
|
|
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`:
|
|
18
|
-
|
|
19
|
-
```ruby
|
|
20
|
-
robot = RobotLab.build(
|
|
21
|
-
name: "storyteller",
|
|
22
|
-
system_prompt: "You are a creative storyteller.",
|
|
23
|
-
on_content: ->(chunk) { print chunk.content }
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
result = robot.run("Tell me a story about a brave robot")
|
|
27
|
-
puts
|
|
28
|
-
puts result.last_text_content
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
## Streaming via a Block on `run`
|
|
32
|
-
|
|
33
|
-
For one-off streaming, pass a block straight to `run`:
|
|
34
|
-
|
|
35
|
-
```ruby
|
|
36
|
-
robot = RobotLab.build(
|
|
37
|
-
name: "factbot",
|
|
38
|
-
system_prompt: "You are concise. Answer in one sentence."
|
|
39
|
-
)
|
|
40
|
-
|
|
41
|
-
robot.run("What year was Ruby created?") { |chunk| print chunk.content }
|
|
42
|
-
```
|
|
43
|
-
|
|
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.
|
|
47
|
-
|
|
48
|
-
## Using Both Together
|
|
49
|
-
|
|
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:
|
|
52
|
-
|
|
53
|
-
```ruby
|
|
54
|
-
stored_log = []
|
|
55
|
-
block_log = []
|
|
56
|
-
|
|
57
|
-
robot = RobotLab.build(
|
|
58
|
-
name: "combo",
|
|
59
|
-
system_prompt: "You are concise.",
|
|
60
|
-
on_content: ->(chunk) { stored_log << chunk.content }
|
|
61
|
-
)
|
|
62
|
-
|
|
63
|
-
robot.run("What is Matz's full name?") { |chunk| block_log << chunk.content }
|
|
64
|
-
|
|
65
|
-
stored_log == block_log # => true — both saw the same chunks
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Streaming via RunConfig
|
|
69
|
-
|
|
70
|
-
`on_content` is a `RunConfig` field, so it participates in the config cascade:
|
|
71
|
-
|
|
72
|
-
```ruby
|
|
73
|
-
config = RobotLab::RunConfig.new(
|
|
74
|
-
model: "claude-sonnet-4",
|
|
75
|
-
on_content: ->(chunk) { print chunk.content }
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
robot = RobotLab.build(
|
|
79
|
-
name: "config_bot",
|
|
80
|
-
system_prompt: "You are concise.",
|
|
81
|
-
config: config
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
robot.run("Who designed the Ruby programming language?")
|
|
85
|
-
```
|
|
86
|
-
|
|
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).
|
|
92
|
-
|
|
93
|
-
## Web Integration
|
|
94
|
-
|
|
95
|
-
### Rails Action Cable
|
|
96
|
-
|
|
97
|
-
```ruby
|
|
98
|
-
class ChatChannel < ApplicationCable::Channel
|
|
99
|
-
def receive(data)
|
|
100
|
-
robot = RobotLab.build(
|
|
101
|
-
name: "chat_bot",
|
|
102
|
-
system_prompt: "You are a helpful chat assistant.",
|
|
103
|
-
on_content: ->(chunk) {
|
|
104
|
-
transmit({ event: "text.delta", content: chunk.content })
|
|
105
|
-
}
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
robot.run(data["message"])
|
|
109
|
-
transmit({ event: "run.completed" })
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Server-Sent Events
|
|
115
|
-
|
|
116
|
-
```ruby
|
|
117
|
-
class StreamController < ApplicationController
|
|
118
|
-
include ActionController::Live
|
|
119
|
-
|
|
120
|
-
def create
|
|
121
|
-
response.headers["Content-Type"] = "text/event-stream"
|
|
122
|
-
|
|
123
|
-
robot = RobotLab.build(
|
|
124
|
-
name: "stream_bot",
|
|
125
|
-
system_prompt: "You are helpful."
|
|
126
|
-
)
|
|
127
|
-
|
|
128
|
-
robot.run(params[:message]) do |chunk|
|
|
129
|
-
response.stream.write("data: #{chunk.content}\n\n")
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
response.stream.write("data: [DONE]\n\n")
|
|
133
|
-
ensure
|
|
134
|
-
response.stream.close
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### WebSocket
|
|
140
|
-
|
|
141
|
-
```ruby
|
|
142
|
-
# Using Faye WebSocket
|
|
143
|
-
ws.on :message do |msg|
|
|
144
|
-
robot.run(msg.data) { |chunk| ws.send(chunk.content) }
|
|
145
|
-
end
|
|
146
|
-
```
|
|
147
|
-
|
|
148
|
-
## Progress Tracking
|
|
149
|
-
|
|
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:
|
|
153
|
-
|
|
154
|
-
```ruby
|
|
155
|
-
class StreamProgress
|
|
156
|
-
attr_reader :chars, :tools
|
|
157
|
-
|
|
158
|
-
def initialize
|
|
159
|
-
@chars = 0
|
|
160
|
-
@tools = 0
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# Returns the streaming block to hand to run().
|
|
164
|
-
def content_callback
|
|
165
|
-
->(chunk) {
|
|
166
|
-
@chars += chunk.content.to_s.length
|
|
167
|
-
print "\rReceived #{@chars} characters..."
|
|
168
|
-
}
|
|
169
|
-
end
|
|
170
|
-
|
|
171
|
-
def tool_callback
|
|
172
|
-
->(tool_call) {
|
|
173
|
-
@tools += 1
|
|
174
|
-
puts "\nTool call ##{@tools}: #{tool_call.name}"
|
|
175
|
-
}
|
|
176
|
-
end
|
|
177
|
-
end
|
|
178
|
-
|
|
179
|
-
progress = StreamProgress.new
|
|
180
|
-
|
|
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)
|
|
190
|
-
puts "\nTotal: #{progress.chars} chars, #{progress.tools} tool calls"
|
|
191
|
-
```
|
|
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
|
-
|
|
202
|
-
## Without Streaming
|
|
203
|
-
|
|
204
|
-
When streaming is not needed, call `run` with no `on_content:` and no block:
|
|
205
|
-
|
|
206
|
-
```ruby
|
|
207
|
-
result = robot.run("Hello!")
|
|
208
|
-
puts result.last_text_content
|
|
209
|
-
```
|
|
210
|
-
|
|
211
|
-
## Tool Callbacks
|
|
212
|
-
|
|
213
|
-
`on_tool_call:` and `on_tool_result:` are constructor/`RunConfig` callbacks
|
|
214
|
-
alongside `on_content:`. Each receives **one** argument:
|
|
215
|
-
|
|
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.
|
|
266
|
-
|
|
267
|
-
```ruby
|
|
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}" }
|
|
272
|
-
```
|
|
273
|
-
|
|
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:
|
|
285
|
-
|
|
286
|
-
```ruby
|
|
287
|
-
robot = RobotLab.build(
|
|
288
|
-
name: "broadcaster",
|
|
289
|
-
system_prompt: "You are helpful.",
|
|
290
|
-
on_content: ->(chunk) do
|
|
291
|
-
broadcast(chunk.content)
|
|
292
|
-
rescue BroadcastError => e
|
|
293
|
-
# Client disconnected, but let the run finish.
|
|
294
|
-
RobotLab.config.logger.warn("Broadcast failed: #{e.message}")
|
|
295
|
-
end
|
|
296
|
-
)
|
|
297
|
-
```
|
|
298
|
-
|
|
299
|
-
### 3. Clean up resources
|
|
300
|
-
|
|
301
|
-
```ruby
|
|
302
|
-
begin
|
|
303
|
-
robot.run("Hello") { |chunk| stream_to_client(chunk.content) }
|
|
304
|
-
ensure
|
|
305
|
-
close_stream_connection
|
|
306
|
-
end
|
|
307
|
-
```
|
|
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
|
-
|
|
318
|
-
## Next Steps
|
|
319
|
-
|
|
320
|
-
- [Building Robots](building-robots.md) - Robot creation
|
|
321
|
-
- [Creating Networks](creating-networks.md) - Network patterns
|
|
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
|