stream = anthropic.messages.stream(
max_tokens: 1024,
messages: [{role: :user, content: "Say hello there!"}],
model: :"claude-3-7-sonnet-latest"
)
stream.text.each do |text|
print(text)
end
putsclient.messages.stream returns a MessageStream which is an Enumerable that emits events and accumulates messages.
The HTTP stream will be cancelled when the MessageStream's events are exhausted, or if you call break while consuming the stream. You can also close it prematurely by calling stream.close.
See examples of streaming helpers in action in:
examples/messages_stream.rb- Basic streamingexamples/messages_stream_advanced.rb- Advanced streaming patternsexamples/thinking_stream.rb- Thinking events streamingexamples/tools_stream.rb- Tool use streamingexamples/citations_stream.rb- Document citations streamingexamples/text_stream.rb- Text streamingexamples/web_search_stream.rb- Web search streaming
Iterate over just the text deltas in the stream:
stream.text.each do |text|
print(text)
end
putsAborts the request.
Blocks until the stream has been read to completion.
Blocks until the stream has been read to completion and returns the accumulated Message object.
Blocks until the stream has been read to completion and returns all text content blocks concatenated together.
Returns the HTTP response headers from the streaming request. Provides access to rate limit information, request IDs, and other metadata.
Returns the HTTP status code from the streaming request.
The events listed here are just the event types that the SDK extends, for a full list of the events returned by the API, see these docs.
require "anthropic"
stream = anthropic.messages.stream(
max_tokens: 1024,
messages: [{role: :user, content: "Say hello there!"}],
model: :"claude-3-7-sonnet-latest"
)
stream.each do |event|
case event
when Anthropic::Streaming::TextEvent
print(event.text)
when Anthropic::Streaming::ContentBlockStopEvent
print("\n\ncontent block finished accumulating #{event.content_block}")
end
end
puts
# you can still get the final accumulated message outside of
# the context manager, as long as the entire stream was consumed
# inside of the context manager
accumulated = stream.accumulated_message
puts("accumulated message: #{accumulated.to_json}")This event is yielded whenever a text content_block_delta event is returned by the API & includes the delta and the accumulated snapshot, e.g.
when Anthropic::Streaming::TextEvent
event.text # " there"
event.snapshot # "Hello, there"This event is yielded whenever a JSON content_block_delta event is returned by the API & includes the delta and the accumulated snapshot, e.g.
when Anthropic::Streaming::InputJsonEvent
event.partial_json # ' there"'
event.snapshot # '{"message": "Hello, there"'The event is yielded when a full Message object has been accumulated.
when Anthropic::Streaming::MessageStopEvent
event.message # MessageThe event is yielded when a full ContentBlock object has been accumulated.
when Anthropic::Streaming::ContentBlockStopEvent
event.content_block # ContentBlockThe event is yielded when a full Message object has been accumulated.
when Anthropic::Streaming::MessageStopEvent
event.message # Messagewhen Anthropic::Streaming::ContentBlockStopEvent
event.citation # Citation
event.snapshot # Array[Citation] including all of the accumulated citations so farwhen Anthropic::Streaming::ThinkingEvent
event.thinking # String
event.snapshot # The accumulated thinking so farwhen Anthropic::Streaming::SignatureEvent
event.signature # Signature from a signature_delta eventInput schemas define structured data classes for tools and structured outputs using Anthropic::BaseModel.
class GetWeatherInput < Anthropic::BaseModel
required :location, String, doc: "The city and state, e.g. San Francisco, CA"
optional :unit, Anthropic::EnumOf[:celsius, :fahrenheit], doc: "Temperature unit"
end
class GetWeather
doc "Get the current weather in a given location"
def call(input) = ...
endrequired :name, Type- Required fieldoptional :name, Type, doc: "description"- Optional fieldrequired :name, Type, doc: "description", nil?: true- Required but nullable
String - Text values with optional validation:
required :name, String, doc: "User's full name"
required :code, String, min_length: 3, doc: "Airport code (e.g., JFK)"Integer - Whole numbers:
required :age, Integer, doc: "Age in years"
optional :max_stops, Integer, nil?: trueFloat - Decimal numbers:
required :price, Float, doc: "Price in USD"
required :latitude, Float, doc: "GPS latitude"Anthropic::Boolean - true/false values:
optional :flexible_dates, Anthropic::Boolean
optional :nonstop_only, Anthropic::Boolean, nil?: trueAnthropic::EnumOf[:option1, :option2] - Limited set of values:
required :cabin, Anthropic::EnumOf[:economy, :premium, :business, :first]
required :unit, Anthropic::EnumOf[:celsius, :fahrenheit], nil?: trueAnthropic::ArrayOf[Type] - Arrays of any supported type:
required :passengers, Anthropic::ArrayOf[Passenger, nil?: true]
optional :preferred_airlines, Anthropic::ArrayOf[String], nil?: true
required :coordinates, Anthropic::ArrayOf[Float]Anthropic::UnionOf[Type1, Type2] - Multiple possible types:
required :origin, Anthropic::UnionOf[String, Airport] # Either "JFK" or Airport object
required :value, Anthropic::UnionOf[Integer, String] # Either 42 or "42"Nested Models - Other Anthropic::BaseModel subclasses:
class Airport < Anthropic::BaseModel
required :code, String
required :name, String
end
class FlightSearch < Anthropic::BaseModel
required :origin, Airport # Nested object
optional :alternate, Airport, nil?: true
endAdd nil?: true to make any field accept nil values:
class Passenger < Anthropic::BaseModel
required :name, String # Cannot be nil
required :seat, Anthropic::EnumOf[:window, :aisle], nil?: true # Can be nil
optional :bags, Integer, nil?: true # Optional AND nullable
endThis works with ArrayOf[...] as well as HashOf[...]
Anthropic::ArrayOf[String, nil?: true]You can also use Anthropic::UnionOf[..., NilClass] to construct an arbitrary nilable type
Anthropic::UnionOf[String, NilClass]Usage patterns:
required :field, Type- Must be provided, cannot be nilrequired :field, Type, nil?: true- Must be provided, but can be niloptional :field, Type- May be omitted, cannot be nil if providedoptional :field, Type, nil?: true- May be omitted or be nil
Tools let Claude call external functions. There are three approaches:
Handle tool calls yourself:
message = client.messages.create(
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [user_message],
tools: [GetWeather.new]
)
if message.stop_reason == :tool_use
tool = message.content.grep(Anthropic::Models::ToolUseBlock).first
# Execute your tool logic here
# Then send tool_result back to continue conversation
endGet tool input as it streams:
stream = client.messages.stream(
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
tools: [GetWeather.new],
messages: [...]
)
stream.each do |event|
case event
in Anthropic::Streaming::InputJsonEvent
puts(event.partial_json) # Incremental JSON
puts(event.snapshot) # Full JSON so far
else
end
end
# Get parsed tool calls
tool_uses = stream.accumulated_message.content.grep(Anthropic::ToolUseBlock)Automatically execute tools and continue conversation:
class CalculatorInput < Anthropic::BaseModel
required :lhs, Float
required :rhs, Float
required :operator, Anthropic::InputSchema::EnumOf[:+, :-, :*, :/]
end
class Calculator < Anthropic::BaseTool
doc "i am a calculator and i am good at math"
# you must specify the input schema to the tool
input_schema CalculatorInput
# you can override `#parse` to pre-process the tool call arguments prior to `#call`
def parse(value) = value
def call(expr)
expr.lhs.public_send(expr.operator, expr.rhs)
end
end
tool = Calculator.new
# Automatically handles tool execution loop
client.beta.messages.tool_runner(
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [{role: "user", content: "What's 15 * 7?"}],
tools: [tool]
).each_message { puts _1.content }Process each message after Claude responds. Good for filtering content or triggering actions:
runner.each_message do |message|
text_blocks = message.content.grep_v(Anthropic::Models::Beta::BetaToolUseBlock)
puts "Claude says: #{text_blocks.first&.text}" unless text_blocks.empty?
endSee text and tool calls as they happen in real-time:
runner.each_streaming do |stream|
stream.each { |event| print(event.text) if event.respond_to?(:text) }
puts "\nFinal: #{stream.accumulated_text}"
endGet one message at a time for fine-grained control:
loop do
message = runner.next_message
break if message.nil?
# Process message and decide whether to continue
endAdd your own messages to guide the conversation:
runner.each_message do |message|
if message.content.any? { |block| block.text&.include?("let me") }
runner.feed_messages({role: :user, content: "Say 'allow me' instead"}, ...)
end
endGet the current parameter of the runner. You can inspect #params to see what was the previous request parameter that resulted in the current response.
And by modifying #params, you can customize the next request parameters as well.
runner.each_message do |message|
puts runner.params
runner.params.update(max_tokens: 9999)
endLet the conversation finish, then process all messages at once:
first_msg = runner.next_message
runner.feed_messages({role: :user, content: "Be more confident"}) if needed
all_messages = runner.run_until_finishedSee example files: examples/tools*.rb and examples/auto_looping*.rb
Structured outputs allow you to constrain Claude's responses to follow a specific JSON schema, making it easier to extract structured data. Use the output_config parameter with a BaseModel class to define the expected output format.
class FamousNumber < Anthropic::BaseModel
required :value, Float
optional :reason, String, doc: "why is this number mathematically significant?"
end
class Output < Anthropic::BaseModel
doc "some famous numbers"
required :numbers, Anthropic::ArrayOf[FamousNumber], min_length: 3, max_length: 5
end
message = anthropic.messages.create(
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [{role: "user", content: "give me some famous numbers"}],
output_config: {format: Output}
)
# Access the parsed output directly
message.parsed_output
# => #<Output numbers=[#<FamousNumber value=3.14159... reason="Pi is the ratio...">...]>The output_config parameter accepts:
{format: MyModel}- Pass aBaseModelclass directly{format: {type: :json_schema, schema: {...}}}- Pass a raw JSON schema
When using output_config, the response is automatically parsed and validated against your model:
The Message object provides a convenience method:
message.parsed_output # Returns the parsed model instance or nilYou can also access the parsed content directly from the text block:
text_block = message.content.first
text_block.parsed # Returns the parsed model instanceIf parsing fails, parsed will contain {error: "error message"}.
Structured outputs work with streaming. The parsed output is available after the stream completes:
stream = anthropic.messages.stream(
model: "claude-sonnet-4-5-20250929",
max_tokens: 1024,
messages: [{role: "user", content: "give me some famous numbers"}],
output_config: {format: Output}
)
# Stream the raw text as it arrives
stream.text.each { |text| print(text) }
# Get the parsed output from the accumulated message
stream.accumulated_message.parsed_output
# => #<Output numbers=[...]>The count_tokens method also supports output_config:
result = anthropic.messages.count_tokens(
model: "claude-sonnet-4-5-20250929",
messages: [{role: "user", content: "give me some famous numbers"}],
output_config: {format: Output}
)See example files:
examples/structured_output.rb- Basic structured outputexamples/structured_output_stream.rb- Streaming structured output