Streaming Responses¶
Stream LLM responses in real-time for better user experience.
Streaming via Callbacks¶
RobotLab robots support streaming through callback methods inherited from RubyLLM::Agent. Register callbacks before calling run:
robot = RobotLab.build(
name: "storyteller",
system_prompt: "You are a creative storyteller."
)
# Register streaming callback
robot.on_new_message do |message|
print message.content if message.content
end
result = robot.run("Tell me a story about a brave robot")
Available Callbacks¶
on_new_message¶
Called when the assistant starts generating a new message, with streaming chunks:
on_end_message¶
Called when the assistant finishes a message:
robot.on_end_message do |message|
puts "\n--- Response complete ---"
puts "Content length: #{message.content&.length}"
end
on_tool_call¶
Called when the LLM invokes a tool:
on_tool_result¶
Called when a tool returns its result:
Comprehensive Callback Setup¶
Register all callbacks for full visibility:
robot = RobotLab.build(
name: "assistant",
system_prompt: "You are helpful.",
local_tools: [WeatherTool]
)
robot.on_new_message do |message|
print message.content if message.content
end
robot.on_end_message do |_message|
puts "\n--- Done ---"
end
robot.on_tool_call do |tool_call|
puts "\n[Tool] Calling: #{tool_call.name}"
end
robot.on_tool_result do |tool_call, result|
puts "[Tool] #{tool_call.name} returned: #{result}"
end
result = robot.run("What's the weather in Tokyo?")
Streaming via Chat Block¶
For more control, pass a block directly to chat.ask (the underlying RubyLLM method):
robot = RobotLab.build(
name: "chat_bot",
system_prompt: "You are a helpful assistant."
)
# Use the underlying chat directly with a streaming block
robot.chat.ask("Tell me a story") do |chunk|
print chunk.content if chunk.content
end
Note: Using chat.ask directly bypasses Robot's memory resolution and tool hierarchy. Use callbacks with robot.run when you need those features.
Web Integration¶
Rails Action Cable¶
class ChatChannel < ApplicationCable::Channel
def receive(data)
robot = RobotLab.build(
name: "chat_bot",
system_prompt: "You are a helpful chat assistant."
)
robot.on_new_message do |message|
transmit({ event: "text.delta", content: message.content }) if message.content
end
robot.on_end_message do |_message|
transmit({ event: "run.completed" })
end
robot.run(data["message"])
end
end
Server-Sent Events¶
class StreamController < ApplicationController
include ActionController::Live
def create
response.headers["Content-Type"] = "text/event-stream"
robot = RobotLab.build(
name: "stream_bot",
system_prompt: "You are helpful."
)
robot.on_new_message do |message|
response.stream.write("data: #{message.content}\n\n") if message.content
end
robot.on_end_message do |_message|
response.stream.write("data: [DONE]\n\n")
end
robot.run(params[:message])
ensure
response.stream.close
end
end
WebSocket¶
# Using Faye WebSocket
ws.on :message do |msg|
robot.on_new_message do |message|
ws.send(message.content) if message.content
end
robot.run(msg.data)
end
Progress Tracking¶
Track streaming progress with callbacks:
class StreamProgress
def initialize
@chars = 0
@tools = 0
end
attr_reader :chars, :tools
def attach(robot)
robot.on_new_message do |message|
@chars += message.content.length if message.content
print "\rReceived #{@chars} characters..."
end
robot.on_tool_call do |tool_call|
@tools += 1
puts "\nTool call ##{@tools}: #{tool_call.name}"
end
end
end
progress = StreamProgress.new
progress.attach(robot)
result = robot.run("Process this complex request")
puts "\nTotal: #{progress.chars} chars, #{progress.tools} tool calls"
Without Streaming¶
When streaming is not needed, simply call run without registering callbacks:
# No streaming - returns RobotResult directly
result = robot.run("Hello!")
puts result.last_text_content
Best Practices¶
1. Register Callbacks Before Run¶
# Correct: register first, then run
robot.on_new_message { |msg| print msg.content if msg.content }
robot.run("Hello")
2. Handle Errors in Callbacks¶
robot.on_new_message do |message|
begin
broadcast(message.content) if message.content
rescue BroadcastError => e
# Client disconnected, but continue processing
logger.warn "Broadcast failed: #{e.message}"
end
end
3. Clean Up Resources¶
begin
robot.on_new_message do |message|
stream_to_client(message.content) if message.content
end
robot.run("Hello")
ensure
close_stream_connection
end
Next Steps¶
- Building Robots - Robot creation
- Creating Networks - Network patterns
- API Reference: Streaming - Complete API