Class: Riffer::Agent

Inherits:
Object
  • Object
show all
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 collapse

DEFAULT_MAX_STEPS =

: Integer

16
INTERRUPT_MAX_STEPS =

: Symbol

:max_steps

Constants included from Helpers::ClassNameConverter

Helpers::ClassNameConverter::DEFAULT_SEPARATOR

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers::ClassNameConverter

class_name_to_path

Methods included from Helpers::Validations

validate_is_string!

Methods included from Messages::Converter

#convert_to_file_part, #convert_to_message_object

Constructor Details

#initializeAgent

Initializes a new agent.

Raises Riffer::ArgumentError if the configured model string is invalid (must be “provider/model” format).

– : () -> void



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/riffer/agent.rb', line 262

def initialize
  @messages = []
  @message_callbacks = []
  @token_usage = nil
  @interrupted = false
  @model_config = self.class.model
  @instructions_config = self.class.instructions

  if @model_config.is_a?(Proc)
    @provider_name = nil
    @model_name = nil
  else
    parse_model_string!(@model_config)
  end
end

Instance Attribute Details

#messagesObject (readonly)

The message history for the agent.



250
251
252
# File 'lib/riffer/agent.rb', line 250

def messages
  @messages
end

#token_usageObject (readonly)

Cumulative token usage across all LLM calls.



253
254
255
# File 'lib/riffer/agent.rb', line 253

def token_usage
  @token_usage
end

Class Method Details

.allObject

Returns all agent subclasses.

– : () -> Array



186
187
188
# File 'lib/riffer/agent.rb', line 186

def self.all
  subclasses
end

.find(identifier) ⇒ Object

Finds an agent class by identifier.

– : (String) -> singleton(Riffer::Agent)?



178
179
180
# File 'lib/riffer/agent.rb', line 178

def self.find(identifier)
  subclasses.find { |agent_class| agent_class.identifier == identifier.to_s }
end

.generateObject

Generates a response using a new agent instance.

See #generate for parameters and return value.

– : (*untyped, **untyped) -> Riffer::Agent::Response



196
197
198
# File 'lib/riffer/agent.rb', line 196

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



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/riffer/agent.rb', line 219

def self.guardrail(phase, with:, **options)
  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: 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]]



244
245
246
247
# File 'lib/riffer/agent.rb', line 244

def self.guardrails_for(phase)
  @guardrails ||= {before: [], after: []}
  @guardrails[phase] || []
end

.identifier(value = nil) ⇒ Object

Gets or sets the agent identifier.

– : (?String?) -> String



33
34
35
36
# File 'lib/riffer/agent.rb', line 33

def self.identifier(value = nil)
  return @identifier || class_name_to_path(name) if value.nil?
  @identifier = value.to_s
end

.instructions(instructions_or_proc = nil) ⇒ Object

Gets or sets the agent instructions.

Accepts a static string or a Proc for dynamic instructions. When a Proc is given, it is called at generate time and receives the context hash (which may be nil).

instructions "You are a helpful assistant."

instructions -> (context) {
  "You are assisting #{context[:name]}"
}

– : (?(String | Proc)?) -> (String | Proc)?



67
68
69
70
71
72
73
74
75
76
# File 'lib/riffer/agent.rb', line 67

def self.instructions(instructions_or_proc = nil)
  return @instructions if instructions_or_proc.nil?

  if instructions_or_proc.is_a?(Proc)
    @instructions = instructions_or_proc
  else
    validate_is_string!(instructions_or_proc, "instructions")
    @instructions = instructions_or_proc
  end
end

.max_steps(value = nil) ⇒ Object

Gets or sets the maximum number of LLM call steps in the tool-use loop.

Defaults to DEFAULT_MAX_STEPS (16). Set to Float::INFINITY for unlimited steps.

– : (?Numeric?) -> Numeric



121
122
123
124
# File 'lib/riffer/agent.rb', line 121

def self.max_steps(value = nil)
  return @max_steps || DEFAULT_MAX_STEPS if value.nil?
  @max_steps = value
end

.model(model_string_or_proc = nil) ⇒ Object

Gets or sets the model string (e.g., “openai/gpt-4o”) or Proc.

– : (?(String | Proc)?) -> (String | Proc)?



42
43
44
45
46
47
48
49
50
51
# File 'lib/riffer/agent.rb', line 42

def self.model(model_string_or_proc = nil)
  return @model if model_string_or_proc.nil?

  if model_string_or_proc.is_a?(Proc)
    @model = model_string_or_proc
  else
    validate_is_string!(model_string_or_proc, "model")
    @model = model_string_or_proc
  end
end

.model_options(options = nil) ⇒ Object

Gets or sets model options passed to generate_text/stream_text.

– : (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]



91
92
93
94
# File 'lib/riffer/agent.rb', line 91

def self.model_options(options = nil)
  return @model_options || {} if options.nil?
  @model_options = options
end

.provider_options(options = nil) ⇒ Object

Gets or sets provider options passed to the provider client.

– : (?Hash[Symbol, untyped]?) -> Hash[Symbol, untyped]



82
83
84
85
# File 'lib/riffer/agent.rb', line 82

def self.provider_options(options = nil)
  return @provider_options || {} if options.nil?
  @provider_options = options
end

.skills(&block) ⇒ Object

Configures skills for this agent via a block DSL.

Returns the current Riffer::Skills::Config when called without a block.

skills do
  backend Riffer::Skills::FilesystemBackend.new(".skills")
  adapter Riffer::Skills::XmlAdapter
  activate ["code-review"]
end

– : () ?{ () -> void } -> Riffer::Skills::Config?



166
167
168
169
170
171
172
# File 'lib/riffer/agent.rb', line 166

def self.skills(&block)
  if block
    @skills_config = Riffer::Skills::Config.new
    @skills_config.instance_eval(&block)
  end
  @skills_config
end

.streamObject

Streams a response using a new agent instance.

See #stream for parameters and return value.

– : (*untyped, **untyped) -> Enumerator[Riffer::StreamEvents::Base, void]



206
207
208
# File 'lib/riffer/agent.rb', line 206

def self.stream(...)
  new.stream(...)
end

.structured_output(params = nil, &block) ⇒ Object

Gets or sets the structured output schema for this agent.

Accepts a Riffer::Params instance or a block evaluated against a new Params.

– : (?Riffer::Params?) ?{ () -> void } -> Riffer::Params?



102
103
104
105
106
107
108
109
110
111
112
# File 'lib/riffer/agent.rb', line 102

def self.structured_output(params = nil, &block)
  if block
    @structured_output = Riffer::Params.new
    @structured_output.instance_eval(&block)
  elsif params.nil?
    @structured_output
  else
    raise Riffer::ArgumentError, "structured_output must be a Riffer::Params" unless params.is_a?(Riffer::Params)
    @structured_output = params
  end
end

.tool_runtime(value = nil) ⇒ Object

Gets or sets the tool runtime for this agent.

Accepts a Riffer::ToolRuntime subclass, a Riffer::ToolRuntime instance, or a Proc.

Inherited by subclasses. When unset, walks the ancestor chain and falls back to the global Riffer.config.tool_runtime.

– : (?(singleton(Riffer::ToolRuntime) | Riffer::ToolRuntime | Proc)?) -> (singleton(Riffer::ToolRuntime) | Riffer::ToolRuntime | Proc)?



145
146
147
148
149
150
151
152
# File 'lib/riffer/agent.rb', line 145

def self.tool_runtime(value = nil)
  if value.nil?
    return @tool_runtime if instance_variable_defined?(:@tool_runtime)
    superclass.respond_to?(:tool_runtime) ? superclass.tool_runtime : nil
  else
    @tool_runtime = value
  end
end

.uses_tools(tools_or_lambda = nil) ⇒ Object

Gets or sets the tools used by this agent.

– : (?(Array | Proc)?) -> (Array | Proc)?



130
131
132
133
# File 'lib/riffer/agent.rb', line 130

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, files: nil, context: nil) ⇒ Object

Generates a response from the agent.

– : ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response



282
283
284
285
286
287
288
289
290
291
292
293
294
295
# File 'lib/riffer/agent.rb', line 282

def generate(prompt_or_messages, files: nil, context: nil)
  @context = context
  prepare_run
  @structured_output = resolve_structured_output
  initialize_messages(prompt_or_messages, files: files)

  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

  run_generate_loop(all_modifications)
end

#generate_instruction_message(context: nil) ⇒ Object

Generates the instruction system message for this agent.

Useful for database persistence workflows where the system messages need to be stored independently.

Returns nil when no instructions are configured.

– : (?context: Hash[Symbol, untyped]?) -> Riffer::Messages::System?



344
345
346
# File 'lib/riffer/agent.rb', line 344

def generate_instruction_message(context: nil)
  build_instruction_message(context)
end

#generate_skills_message(context: nil) ⇒ Object

Generates the skills catalog system message for this agent.

Useful for database persistence workflows where the system messages need to be stored independently.

Returns nil when no skills are configured or the catalog is empty.

– : (?context: Hash[Symbol, untyped]?) -> Riffer::Messages::System?



357
358
359
# File 'lib/riffer/agent.rb', line 357

def generate_skills_message(context: nil)
  build_skills_message(resolve_skills(context))
end

#interrupt!(reason = nil) ⇒ Object

Interrupts the agent loop.

Call from an on_message callback to cleanly interrupt the loop. Equivalent to throw :riffer_interrupt, reason.

– : (?(String | Symbol)?) -> void



368
369
370
# File 'lib/riffer/agent.rb', line 368

def interrupt!(reason = nil)
  throw :riffer_interrupt, reason
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



329
330
331
332
333
# File 'lib/riffer/agent.rb', line 329

def on_message(&block)
  raise Riffer::ArgumentError, "on_message requires a block" unless block_given?
  @message_callbacks << block
  self
end

#stream(prompt_or_messages, files: nil, context: nil) ⇒ Object

Streams a response from the agent.

Raises Riffer::ArgumentError if structured output is configured.

– : ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/riffer/agent.rb', line 303

def stream(prompt_or_messages, files: nil, context: nil)
  raise Riffer::ArgumentError, "Structured output is not supported with streaming. Use #generate instead." if self.class.structured_output

  @context = context
  prepare_run
  initialize_messages(prompt_or_messages, files: files)

  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

    run_stream_loop(yielder)
  end
end