Providers · 03
Anthropic Provider
The Anthropic provider connects to Claude models via the Anthropic API.
Installation
Add the Anthropic gem to your Gemfile:
gem 'anthropic'
Configuration
Configure your Anthropic API key:
Riffer.configure do |config|
config.anthropic.api_key = ENV['ANTHROPIC_API_KEY']
end
For anything beyond the API key — timeouts, retries, proxies — supply your own Anthropic::Client:
Riffer.configure do |config|
config.anthropic.client = Anthropic::Client.new(
api_key: ENV['ANTHROPIC_API_KEY'],
timeout: 60
)
end
The setting accepts a client instance or a no-argument Proc, resolved on every LLM call — see Configuration → Provider Clients.
Supported Models
Use Anthropic model IDs in the anthropic/model format:
model 'anthropic/claude-haiku-4-5-20251001'
model 'anthropic/claude-sonnet-4-5-20250929'
model 'anthropic/claude-opus-4-5-20251101'
Model Options
temperature
Controls randomness:
model_options temperature: 0.7
max_tokens
Maximum tokens in response:
model_options max_tokens: 4096
top_p
Nucleus sampling parameter:
model_options top_p: 0.95
top_k
Top-k sampling parameter:
model_options top_k: 250
thinking
Enable extended thinking (reasoning) for supported models. Pass the thinking configuration hash directly as Anthropic expects:
# Enable with budget tokens
model_options thinking: {type: "enabled", budget_tokens: 10000}
web_search
Enable server-side web search using Anthropic’s web_search_20250305 tool. Pass true to use defaults or a hash to merge with the tool definition:
# Enable with defaults
model_options web_search: true
# With custom configuration
model_options web_search: {max_uses: 3}
Example
Riffer.configure do |config|
config.anthropic.api_key = ENV['ANTHROPIC_API_KEY']
end
class AssistantAgent < Riffer::Agent
model 'anthropic/claude-haiku-4-5-20251001'
instructions 'You are a helpful assistant.'
model_options temperature: 0.7, max_tokens: 4096
end
agent = AssistantAgent.new
puts agent.generate("Explain quantum computing")
Streaming
agent.stream("Tell me about Claude models").each do |event|
case event
when Riffer::StreamEvents::TextDelta
print event.content
when Riffer::StreamEvents::TextDone
puts "\n[Complete]"
when Riffer::StreamEvents::ReasoningDelta
print "[Thinking] #{event.content}"
when Riffer::StreamEvents::ReasoningDone
puts "\n[Thinking Complete]"
when Riffer::StreamEvents::ToolCallDone
puts "[Tool: #{event.name}]"
end
end
Tool Calling
Anthropic provider converts tools to the Anthropic tool format:
class WeatherTool < Riffer::Tool
description "Gets the current weather for a location"
params do
required :city, String, description: "The city name"
optional :unit, String, description: "Temperature unit (celsius or fahrenheit)"
end
def call(context:, city:, unit: "celsius")
# Implementation
text("It's 22 degrees #{unit} in #{city}")
end
end
class WeatherAgent < Riffer::Agent
model 'anthropic/claude-haiku-4-5-20251001'
uses_tools [WeatherTool]
end
Extended Thinking
Extended thinking enables Claude to reason through complex problems before responding. This is available on supported models (Claude 3.7+).
class ReasoningAgent < Riffer::Agent
model 'anthropic/claude-haiku-4-5-20251001'
model_options thinking: {type: "enabled", budget_tokens: 10000}
end
When streaming with extended thinking enabled, you’ll receive ReasoningDelta events containing the model’s thought process, followed by a ReasoningDone event when thinking completes:
agent.stream("Solve this complex math problem").each do |event|
case event
when Riffer::StreamEvents::ReasoningDelta
# Model's internal reasoning
print "[Thinking] #{event.content}"
when Riffer::StreamEvents::ReasoningDone
puts "\n[Thinking complete]"
when Riffer::StreamEvents::TextDelta
# Final response
print event.content
end
end
The reasoning is kept on the assistant message as reasoning parts, whether you call generate or stream. Read it with response.reasoning, or as plain text with reasoning_text on the message. Each thinking block becomes a :text part carrying its signature, and each redacted_thinking block becomes an :encrypted part whose data is the block’s opaque payload. reasoning_text leaves the redacted parts out, since they have no readable text. Every part is tagged format: "anthropic-messages-v1" (Riffer::Providers::Anthropic::REASONING_FORMAT).
Reasoning Replay
Riffer sends the reasoning back to Anthropic on every later turn, as thinking and redacted_thinking blocks ahead of the message’s text and tool_use blocks, in their original order and unchanged. For tool calls, sending it back is required: when thinking is enabled, Claude needs its earlier thinking blocks back to carry on after a tool result. You don’t have to do anything; it happens as long as the assistant messages stay in the history.
If you persist sessions, keep the reasoning key when you store messages (see Messages — Reasoning). If you drop it, later turns lose the model’s earlier reasoning, and a tool-calling turn with thinking enabled may be rejected.
Only reasoning that this provider produced is sent back to Anthropic. A conversation that switches providers midway still works: reasoning from other providers (including OpenRouter’s anthropic-claude-v1 parts) is kept on the messages but left out of Anthropic requests.
Web Search
Web search allows Claude to search the web for up-to-date information. When enabled, the provider injects the web_search_20250305 server tool into the request.
class SearchAgent < Riffer::Agent
model 'anthropic/claude-haiku-4-5-20251001'
model_options web_search: true
end
agent = SearchAgent.new
agent.stream("What happened in tech news today?").each do |event|
case event
when Riffer::StreamEvents::WebSearchStatus
puts "[search: #{event.status}]"
puts " query: #{event.query}" if event.query
when Riffer::StreamEvents::WebSearchDone
puts "[search complete: #{event.query}]"
event.sources.each { |s| puts " - #{s[:title]}: #{s[:url]}" }
when Riffer::StreamEvents::TextDelta
print event.content
end
end
Anthropic emits sources with title and url on the WebSearchDone event.
Message Format
The provider converts Riffer messages to Anthropic format:
| Riffer Message | Anthropic Format |
|---|---|
System |
Added to system array as {type: "text", text: ...} |
User |
{role: "user", content: "..."} |
Assistant |
{role: "assistant", content: [...]} with text/tool_use blocks |
Tool |
{role: "user", content: [{type: "tool_result", ...}]} |
Direct Provider Usage
provider = Riffer::Providers::Anthropic.new
response = provider.generate_text(
prompt: "Hello!",
model: "claude-haiku-4-5-20251001",
temperature: 0.7
)
puts response.content
With extended thinking:
response = provider.generate_text(
prompt: "Explain step by step how to solve a Rubik's cube",
model: "claude-haiku-4-5-20251001",
thinking: { type: "enabled", budget_tokens: 10000 }
)
puts response.content