Class: Riffer::Agent
- Inherits:
-
Object
- Object
- Riffer::Agent
- Extended by:
- Helpers::ClassNameConverter, Helpers::Validations
- Includes:
- Messages::Converter
- Defined in:
- lib/riffer/agent.rb
Overview
Riffer::Agent is the base class for all agents in the Riffer framework.
Provides orchestration for LLM calls, tool use, and message management. Subclass this to create your own agents.
See Riffer::Messages and Riffer::Providers.
class MyAgent < Riffer::Agent
model 'openai/gpt-4o'
instructions 'You are a helpful assistant.'
end
agent = MyAgent.new
agent.generate('Hello!')
Defined Under Namespace
Classes: Response
Constant Summary
Constants included from Helpers::ClassNameConverter
Helpers::ClassNameConverter::DEFAULT_SEPARATOR
Instance Attribute Summary collapse
-
#messages ⇒ Object
readonly
The message history for the agent.
-
#token_usage ⇒ Object
readonly
Cumulative token usage across all LLM calls.
Class Method Summary collapse
-
.all ⇒ Object
Returns all agent subclasses.
-
.find(identifier) ⇒ Object
Finds an agent class by identifier.
-
.generate ⇒ Object
Generates a response using a new agent instance.
-
.guardrail(phase, with:, **options) ⇒ Object
Registers a guardrail for input, output, or both phases.
-
.guardrails_for(phase) ⇒ Object
Returns the registered guardrail configs for a given phase.
-
.identifier(value = nil) ⇒ Object
Gets or sets the agent identifier.
-
.instructions(instructions_text = nil) ⇒ Object
Gets or sets the agent instructions.
-
.model(model_string = nil) ⇒ Object
Gets or sets the model string (e.g., “openai/gpt-4o”).
-
.model_options(options = nil) ⇒ Object
Gets or sets model options passed to generate_text/stream_text.
-
.provider_options(options = nil) ⇒ Object
Gets or sets provider options passed to the provider client.
-
.stream ⇒ Object
Streams a response using a new agent instance.
-
.uses_tools(tools_or_lambda = nil) ⇒ Object
Gets or sets the tools used by this agent.
Instance Method Summary collapse
-
#generate(prompt_or_messages, tool_context: nil) ⇒ Object
Generates a response from the agent.
-
#initialize ⇒ Agent
constructor
Initializes a new agent.
-
#on_message(&block) ⇒ Object
Registers a callback to be invoked when messages are added during generation.
-
#stream(prompt_or_messages, tool_context: nil) ⇒ Object
Streams a response from the agent.
Methods included from Helpers::ClassNameConverter
Methods included from Helpers::Validations
Methods included from Messages::Converter
Constructor Details
#initialize ⇒ Agent
Initializes a new agent.
Raises Riffer::ArgumentError if the configured model string is invalid (must be “provider/model” format).
: () -> void
157 158 159 160 161 162 163 164 165 166 167 168 169 170 |
# File 'lib/riffer/agent.rb', line 157 def initialize @messages = [] @message_callbacks = [] @token_usage = nil @model_string = self.class.model @instructions_text = self.class.instructions provider_name, model_name = @model_string.split("/", 2) raise Riffer::ArgumentError, "Invalid model string: #{@model_string}" unless [provider_name, model_name].all? { |part| part.is_a?(String) && !part.strip.empty? } @provider_name = provider_name @model_name = model_name end |
Instance Attribute Details
#messages ⇒ Object (readonly)
The message history for the agent.
146 147 148 |
# File 'lib/riffer/agent.rb', line 146 def @messages end |
#token_usage ⇒ Object (readonly)
Cumulative token usage across all LLM calls.
149 150 151 |
# File 'lib/riffer/agent.rb', line 149 def token_usage @token_usage end |
Class Method Details
.all ⇒ Object
Returns all agent subclasses.
: () -> Array
86 87 88 |
# File 'lib/riffer/agent.rb', line 86 def self.all subclasses end |
.find(identifier) ⇒ Object
Finds an agent class by identifier.
: (String) -> singleton(Riffer::Agent)?
79 80 81 |
# File 'lib/riffer/agent.rb', line 79 def self.find(identifier) subclasses.find { |agent_class| agent_class.identifier == identifier.to_s } end |
.generate ⇒ Object
Generates a response using a new agent instance.
See #generate for parameters and return value.
: (*untyped, **untyped) -> Riffer::Agent::Response
95 96 97 |
# File 'lib/riffer/agent.rb', line 95 def self.generate(...) new.generate(...) end |
.guardrail(phase, with:, **options) ⇒ Object
Registers a guardrail for input, output, or both phases.
phase - :before, :after, or :around. with - the guardrail class (must be subclass of Riffer::Guardrail). options - additional options passed to the guardrail.
Raises Riffer::ArgumentError if phase is invalid or guardrail is not a Guardrail class. : (Symbol, with: singleton(Riffer::Guardrail), **untyped) -> void
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 |
# File 'lib/riffer/agent.rb', line 116 def self.guardrail(phase, with:, **) valid_phases = [*Riffer::Guardrails::PHASES, :around] raise Riffer::ArgumentError, "Invalid guardrail phase: #{phase}" unless valid_phases.include?(phase) raise Riffer::ArgumentError, "Guardrail must be a Riffer::Guardrail subclass" unless with.is_a?(Class) && with <= Riffer::Guardrail @guardrails ||= {before: [], after: []} config = {class: with, options: } case phase when :before @guardrails[:before] << config when :after @guardrails[:after] << config when :around @guardrails[:before] << config @guardrails[:after] << config end end |
.guardrails_for(phase) ⇒ Object
Returns the registered guardrail configs for a given phase.
phase - :before or :after.
: (Symbol) -> Array[Hash[Symbol, untyped]]
140 141 142 143 |
# File 'lib/riffer/agent.rb', line 140 def self.guardrails_for(phase) @guardrails ||= {before: [], after: []} @guardrails[phase] || [] end |
.identifier(value = nil) ⇒ Object
Gets or sets the agent identifier.
: (?String?) -> String
29 30 31 32 |
# File 'lib/riffer/agent.rb', line 29 def self.identifier(value = nil) return @identifier || class_name_to_path(name) if value.nil? @identifier = value.to_s end |
.instructions(instructions_text = nil) ⇒ Object
Gets or sets the agent instructions.
: (?String?) -> String?
46 47 48 49 50 |
# File 'lib/riffer/agent.rb', line 46 def self.instructions(instructions_text = nil) return @instructions if instructions_text.nil? validate_is_string!(instructions_text, "instructions") @instructions = instructions_text end |
.model(model_string = nil) ⇒ Object
Gets or sets the model string (e.g., “openai/gpt-4o”).
: (?String?) -> String?
37 38 39 40 41 |
# File 'lib/riffer/agent.rb', line 37 def self.model(model_string = nil) return @model if model_string.nil? validate_is_string!(model_string, "model") @model = model_string end |
.model_options(options = nil) ⇒ Object
Gets or sets model options passed to generate_text/stream_text.
: (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
63 64 65 66 |
# File 'lib/riffer/agent.rb', line 63 def self.( = nil) return @model_options || {} if .nil? @model_options = end |
.provider_options(options = nil) ⇒ Object
Gets or sets provider options passed to the provider client.
: (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]
55 56 57 58 |
# File 'lib/riffer/agent.rb', line 55 def self.( = nil) return @provider_options || {} if .nil? @provider_options = end |
.stream ⇒ Object
Streams a response using a new agent instance.
See #stream for parameters and return value.
: (*untyped, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]
104 105 106 |
# File 'lib/riffer/agent.rb', line 104 def self.stream(...) new.stream(...) end |
.uses_tools(tools_or_lambda = nil) ⇒ Object
71 72 73 74 |
# File 'lib/riffer/agent.rb', line 71 def self.uses_tools(tools_or_lambda = nil) return @tools_config if tools_or_lambda.nil? @tools_config = tools_or_lambda end |
Instance Method Details
#generate(prompt_or_messages, tool_context: nil) ⇒ Object
Generates a response from the agent.
: ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?tool_context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response
175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 |
# File 'lib/riffer/agent.rb', line 175 def generate(, tool_context: nil) @tool_context = tool_context @resolved_tools = nil () all_modifications = [] #: Array[Riffer::Guardrails::Modification] tripwire, modifications = run_before_guardrails all_modifications.concat(modifications) return build_response("", tripwire: tripwire, modifications: all_modifications) if tripwire loop do response = call_llm track_token_usage(response.token_usage) processed_response, tripwire, modifications = run_after_guardrails(response) all_modifications.concat(modifications) return build_response("", tripwire: tripwire, modifications: all_modifications) if tripwire (processed_response) break unless has_tool_calls?(processed_response) execute_tool_calls(processed_response) end build_response(extract_final_response, modifications: all_modifications) end |
#on_message(&block) ⇒ Object
Registers a callback to be invoked when messages are added during generation.
Raises Riffer::ArgumentError if no block is given.
: () { (Riffer::Messages::Base) -> void } -> self
284 285 286 287 288 |
# File 'lib/riffer/agent.rb', line 284 def (&block) raise Riffer::ArgumentError, "on_message requires a block" unless block_given? @message_callbacks << block self end |
#stream(prompt_or_messages, tool_context: nil) ⇒ Object
Streams a response from the agent.
: ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?tool_context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 |
# File 'lib/riffer/agent.rb', line 209 def stream(, tool_context: nil) @tool_context = tool_context @resolved_tools = nil () Enumerator.new do |yielder| tripwire, modifications = run_before_guardrails modifications.each { |m| yielder << Riffer::StreamEvents::GuardrailModification.new(m) } if tripwire yielder << Riffer::StreamEvents::GuardrailTripwire.new(tripwire) next end loop do accumulated_content = "" accumulated_tool_calls = [] accumulated_token_usage = nil current_tool_call = nil call_llm_stream.each do |event| yielder << event case event when Riffer::StreamEvents::TextDelta accumulated_content += event.content when Riffer::StreamEvents::TextDone accumulated_content = event.content when Riffer::StreamEvents::ToolCallDelta current_tool_call ||= {item_id: event.item_id, name: event.name, arguments: ""} current_tool_call[:arguments] += event.arguments_delta current_tool_call[:name] ||= event.name when Riffer::StreamEvents::ToolCallDone accumulated_tool_calls << Riffer::Messages::Assistant::ToolCall.new( id: event.item_id, call_id: event.call_id, name: event.name, arguments: event.arguments ) current_tool_call = nil when Riffer::StreamEvents::TokenUsageDone accumulated_token_usage = event.token_usage end end response = Riffer::Messages::Assistant.new( accumulated_content, tool_calls: accumulated_tool_calls, token_usage: accumulated_token_usage ) track_token_usage(accumulated_token_usage) processed_response, tripwire, modifications = run_after_guardrails(response) modifications.each { |m| yielder << Riffer::StreamEvents::GuardrailModification.new(m) } if tripwire yielder << Riffer::StreamEvents::GuardrailTripwire.new(tripwire) break end (processed_response) break unless has_tool_calls?(processed_response) execute_tool_calls(processed_response) end end end |