Agents
Agents are the central orchestrator in Riffer. They manage the conversation flow, call LLM providers, and handle tool execution.
When to Use Agents
Use an agent when the task is open-ended and the LLM needs to reason, iterate, or call tools to produce a result. If your task follows a fixed sequence of steps with no LLM decision-making, consider a simpler pipeline instead.
Defining an Agent
Create an agent by subclassing Riffer::Agent:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' instructions 'You are a helpful assistant.' end
Configuration Methods
model
Sets the provider and model in provider/model format:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' # OpenAI # or model 'amazon_bedrock/us.anthropic.claude-haiku-4-5-20251001-v1:0' # Bedrock # or model 'mock/any' # Mock provider end
Models can also be resolved dynamically with a lambda:
class MyAgent < Riffer::Agent model -> { "anthropic/claude-haiku-4-5-20251001" } end
When the lambda accepts a parameter, it receives the context:
class MyAgent < Riffer::Agent model ->(context) { context&.dig(:premium) ? "anthropic/claude-sonnet-4-5-20250929" : "anthropic/claude-haiku-4-5-20251001" } end
The lambda is re-evaluated on each generate or stream call, so the model can change between calls based on runtime context.
instructions
Sets system instructions for the agent:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' instructions 'You are an expert Ruby programmer. Provide concise answers.' end
Instructions can also be resolved dynamically with a lambda:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' instructions -> { "Today is #{Date.today}. You are a helpful assistant." } end
When the lambda accepts a parameter, it receives the context:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' instructions ->(ctx) { "You are assisting #{ctx[:name]}" } end MyAgent.generate('Hello!', context: { name: 'Jane' })
The lambda is re-evaluated on each generate or stream call, so instructions can change between calls based on runtime context.
identifier
Sets a custom identifier (defaults to snake_case class name):
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' identifier 'custom_agent_name' end MyAgent.identifier # => "custom_agent_name"
uses_tools
Registers tools the agent can use:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' uses_tools [WeatherTool, TimeTool] end
Tools can also be resolved dynamically with a lambda:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' uses_tools ->(context) { tools = [PublicTool] tools << AdminTool if context&.dig(:user)&.admin? tools } end
provider_options
Passes options to the provider client:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' provider_options api_key: ENV['CUSTOM_OPENAI_KEY'] end
model_options
Passes options to each LLM request:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' model_options reasoning: 'medium', temperature: 0.7, web_search: true end
max_steps
Sets the maximum number of LLM call steps in the tool-use loop. When the limit is reached, the loop interrupts with reason :max_steps. Defaults to 16. Set to Float::INFINITY for unlimited steps:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' max_steps 8 end
structured_output
Configures the agent to return structured JSON responses conforming to a schema. Accepts a Riffer::Params instance or a block DSL:
class SentimentAgent < Riffer::Agent model 'openai/gpt-5-mini' instructions 'Analyze the sentiment of the given text.' structured_output do required :sentiment, String, description: "positive, negative, or neutral" required :score, Float, description: "Confidence score between 0 and 1" optional :explanation, String, description: "Brief explanation" end end
The LLM response is automatically parsed and validated against the schema. Access the result via response.structured_output.
Nested Objects
Use Hash with a block to define nested object schemas:
structured_output do required :name, String, description: "Person name" required :address, Hash, description: "Mailing address" do required :street, String, description: "Street address" required :city, String, description: "City" optional :postal_code, String, description: "Postal or zip code" end end
Validation errors use dot-path notation: address.city is required.
Typed Arrays
Use Array with the of: keyword for arrays of primitive types:
structured_output do required :tags, Array, of: String, description: "Tags" required :scores, Array, of: Float, description: "Scores" end
Only primitive types are allowed with of:: String, Integer, Float, TrueClass, FalseClass.
Arrays of Objects
Use Array with a block to define arrays of objects:
structured_output do required :items, Array, description: "Line items" do required :name, String, description: "Product name" required :price, Float, description: "Price" optional :quantity, Integer, description: "Quantity" end end
Validation errors include the array index: items[1].price is required.
Deep Nesting
Blocks can be nested arbitrarily deep:
structured_output do required :orders, Array, description: "Orders" do required :id, String, description: "Order ID" required :shipping, Hash, description: "Shipping info" do required :address, Hash, description: "Address" do required :street, String required :city, String end end end end
Limitations
Using both of: and a block raises Riffer::ArgumentError. Using of: with a non-primitive type (e.g. of: Hash) also raises Riffer::ArgumentError.
Structured output is not compatible with streaming — calling stream on an agent with structured output configured raises Riffer::ArgumentError.
tool_runtime (Experimental)
Warning: This feature is experimental and may be removed or changed without warning in a future release.
Configures how tool calls are executed. Defaults to sequential (inline) execution:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' uses_tools [WeatherTool, SearchTool] tool_runtime Riffer::ToolRuntime::Threaded end
Accepts a Riffer::ToolRuntime subclass, a Riffer::ToolRuntime instance, or a Proc. Inherited by subclasses. When unset, falls back to Riffer.config.tool_runtime. See Tools — Tool Runtime for details.
guardrail
Registers guardrails for pre/post processing of messages. Pass the guardrail class and any options:
class MyAgent < Riffer::Agent model 'openai/gpt-5-mini' # Input-only guardrail guardrail :before, with: InputValidator # Output-only guardrail guardrail :after, with: ResponseFilter # Both input and output, with options guardrail :around, with: MaxLengthGuardrail, max: 1000 end
See Guardrails for detailed documentation.
Expand Your Agent
| Goal | Feature | Guide |
|---|---|---|
| Call APIs or run functions | Tools | Tools |
| Return structured JSON | Structured Output | structured_output |
| Validate or filter content | Guardrails | Guardrails |
| Measure output quality | Evals | Evals |
| Add packaged capabilities | Skills | Skills |
| Control the tool-use loop | Agent Loop | Agent Loop |
| Human-in-the-loop approval | Interrupts | Agent Lifecycle |
| Run tools concurrently | Tool Runtime | Advanced Tools |
| Stream responses in real time | Streaming | Agent Lifecycle |