Messages
Messages represent the conversation between users and the assistant. Riffer uses strongly-typed message objects to ensure consistency and type safety.
Message Types
System
System messages provide instructions to the LLM:
msg = Riffer::Messages::System.new("You are a helpful assistant.") msg.role # => :system msg.content # => "You are a helpful assistant." msg.to_h # => {role: :system, content: "You are a helpful assistant."}
System messages are typically set via agent instructions and automatically prepended to conversations.
User
User messages represent input from the user:
msg = Riffer::Messages::User.new("Hello, how are you?") msg.role # => :user msg.content # => "Hello, how are you?" msg.files # => [] msg.to_h # => {role: :user, content: "Hello, how are you?"}
User messages can include file attachments:
file = Riffer::FilePart.from_path("photo.jpg") msg = Riffer::Messages::User.new("Describe this image", files: [file]) msg.files # => [#<Riffer::FilePart ...>] msg.to_h # => {role: :user, content: "Describe this image", files: [{...}]}
Assistant
Assistant messages represent LLM responses, potentially including tool calls and token usage data:
# Text-only response msg = Riffer::Messages::Assistant.new("I'm doing well, thank you!") msg.role # => :assistant msg.content # => "I'm doing well, thank you!" msg.tool_calls # => [] msg.token_usage # => nil or Riffer::TokenUsage # Response with tool calls msg = Riffer::Messages::Assistant.new("", tool_calls: [ {id: "call_123", call_id: "call_123", name: "weather_tool", arguments: '{"city":"Tokyo"}'} ]) msg.tool_calls # => [{id: "call_123", ...}] msg.to_h # => {role: "assistant", content: "", tool_calls: [...]} # Accessing token usage data (when available from provider) if msg.token_usage puts "Input tokens: #{msg.token_usage.input_tokens}" puts "Output tokens: #{msg.token_usage.output_tokens}" puts "Total tokens: #{msg.token_usage.total_tokens}" end
Structured Output on Messages
When an agent has structured_output configured, the final assistant message stores the parsed hash directly. The structured_output? predicate checks for a non-nil value:
msg = Riffer::Messages::Assistant.new('{"sentiment":"positive"}', structured_output: {sentiment: "positive"}) msg.structured_output? # => true msg.structured_output # => {sentiment: "positive"} # When not provided, structured_output returns nil msg = Riffer::Messages::Assistant.new('{"sentiment":"positive"}') msg.structured_output? # => false msg.structured_output # => nil
The to_h representation includes structured_output only when present:
msg = Riffer::Messages::Assistant.new('{"sentiment":"positive"}', structured_output: {sentiment: "positive"}) msg.to_h # => {role: :assistant, content: '{"sentiment":"positive"}', structured_output: {sentiment: "positive"}}
Tool
Tool messages contain the results of tool executions:
msg = Riffer::Messages::Tool.new( "The weather in Tokyo is 22C and sunny.", tool_call_id: "call_123", name: "weather_tool" ) msg.role # => :tool msg.content # => "The weather in Tokyo is 22C and sunny." msg.tool_call_id # => "call_123" msg.name # => "weather_tool" msg.error? # => false # Error result msg = Riffer::Messages::Tool.new( "API rate limit exceeded", tool_call_id: "call_123", name: "weather_tool", error: "API rate limit exceeded", error_type: :execution_error ) msg.error? # => true msg.error # => "API rate limit exceeded" msg.error_type # => :execution_error
File Parts
Riffer::FilePart represents a file attachment (image or document) that can be included with user messages.
Supported Media Types
Images: image/jpeg, image/png, image/gif, image/webp
Documents: application/pdf, text/plain, text/csv, text/html
Creating File Parts
# From a file path (reads eagerly, detects media type from extension) file = Riffer::FilePart.from_path("photo.jpg") file.media_type # => "image/jpeg" file.filename # => "photo.jpg" file.image? # => true # From a URL (stored directly, resolved lazily if provider needs bytes) file = Riffer::FilePart.from_url("https://example.com/doc.pdf") file.url? # => true file.document? # => true # From raw base64 data file = Riffer::FilePart.new(media_type: "image/png", data: base64_string, filename: "chart.png")
Hash Shorthand
When passing files to agents or messages, hashes are automatically converted:
# Path shorthand {path: "photo.jpg"} # URL shorthand (media_type auto-detected from extension, or provide explicitly) {url: "https://example.com/photo.jpg"} {url: "https://example.com/file", media_type: "application/pdf"} # Data shorthand {data: base64_string, media_type: "image/png", filename: "chart.png"}
Predicates
file.image? # true for image/* media types file.document? # true for non-image media types file.url? # true when source was a URL
Using Messages with Agents
String Prompts
The simplest way to interact with an agent:
agent = MyAgent.new response = agent.generate("Hello!")
This creates a User message internally.
Message Arrays
For multi-turn conversations, pass an array of messages:
messages = [ {role: :user, content: "What's the weather?"}, {role: :assistant, content: "I'll check that for you."}, {role: :user, content: "Thanks, I meant in Tokyo specifically."} ] response = agent.generate(messages)
Messages can be hashes or Riffer::Messages::Base objects:
messages = [ Riffer::Messages::User.new("Hello"), Riffer::Messages::Assistant.new("Hi there!"), Riffer::Messages::User.new("How are you?") ] response = agent.generate(messages)
Accessing Message History
After calling generate or stream, access the full conversation:
agent = MyAgent.new agent.generate("Hello!") agent.messages.each do |msg| puts "[#{msg.role}] #{msg.content}" end # [system] You are a helpful assistant. # [user] Hello! # [assistant] Hi there! How can I help you today?
Tool Call Structure
Tool calls in assistant messages have this structure:
{
id: "item_123", # Item identifier
call_id: "call_456", # Call identifier for response matching
name: "weather_tool", # Tool name
arguments: '{"city":"Tokyo"}' # JSON string of arguments
}
When creating tool result messages, use the id as tool_call_id.
Message Emission
Agents can emit messages as they’re added during generation via the on_message callback. This is useful for persistence or real-time logging. Only agent-generated messages (Assistant, Tool) are emitted—not inputs (System, User).
See Agents - on_message for details.
Base Class
All messages inherit from Riffer::Messages::Base:
class Riffer::Messages::Base attr_reader :content def role raise NotImplementedError end def to_h {role: role, content: content} end end
Subclasses implement role and optionally extend to_h with additional fields.