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!')

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Messages::Converter

#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).



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/riffer/agent.rb', line 134

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

#messagesObject (readonly)

The message history for the agent.

Returns Array of Riffer::Messages::Base.



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

def messages
  @messages
end

#token_usageObject (readonly)

Cumulative token usage across all LLM calls.

Returns Riffer::TokenUsage or nil.



128
129
130
# File 'lib/riffer/agent.rb', line 128

def token_usage
  @token_usage
end

Class Method Details

.allObject

Returns all agent subclasses.

Returns Array of Class - all agent subclasses.



101
102
103
# File 'lib/riffer/agent.rb', line 101

def all
  subclasses
end

.find(identifier) ⇒ Object

Finds an agent class by identifier.

identifier

String - the identifier to search for

Returns Class or nil - the agent class, or nil if not found.



94
95
96
# File 'lib/riffer/agent.rb', line 94

def 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.



108
109
110
# File 'lib/riffer/agent.rb', line 108

def generate(...)
  new.generate(...)
end

.identifier(value = nil) ⇒ Object

Gets or sets the agent identifier.

value

String or nil - the identifier to set, or nil to get

Returns String - the agent identifier.



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

def 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.

instructions_text

String or nil - the instructions to set, or nil to get

Returns String - the agent instructions.



53
54
55
56
57
# File 'lib/riffer/agent.rb', line 53

def 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”).

model_string

String or nil - the model string to set, or nil to get

Returns String - the model string.



42
43
44
45
46
# File 'lib/riffer/agent.rb', line 42

def 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.

options

Hash or nil - the options to set, or nil to get

Returns Hash - the model options.



74
75
76
77
# File 'lib/riffer/agent.rb', line 74

def 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.

options

Hash or nil - the options to set, or nil to get

Returns Hash - the provider options.



64
65
66
67
# File 'lib/riffer/agent.rb', line 64

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

.streamObject

Streams a response using a new agent instance.

See #stream for parameters and return value.



115
116
117
# File 'lib/riffer/agent.rb', line 115

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

.uses_tools(tools_or_lambda = nil) ⇒ Object

Gets or sets the tools used by this agent.

tools_or_lambda

Array of Tool classes, Proc, or nil - tools array or lambda returning tools

Returns Array, Proc, or nil - the tools configuration.



84
85
86
87
# File 'lib/riffer/agent.rb', line 84

def 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.

prompt_or_messages

String or Array - a string prompt or array of message hashes/objects

tool_context

Object or nil - optional context object passed to all tool calls

Returns String - the final response content.



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# File 'lib/riffer/agent.rb', line 155

def generate(prompt_or_messages, tool_context: nil)
  @tool_context = tool_context
  @resolved_tools = nil
  initialize_messages(prompt_or_messages)

  loop do
    response = call_llm
    add_message(response)
    track_token_usage(response.token_usage)

    break unless has_tool_calls?(response)

    execute_tool_calls(response)
  end

  extract_final_response
end

#on_message(&block) ⇒ Object

Registers a callback to be invoked when messages are added during generation.

block

Block - callback receiving a Riffer::Messages::Base subclass

Raises Riffer::ArgumentError if no block is given.

Returns self for method chaining.



238
239
240
241
242
# File 'lib/riffer/agent.rb', line 238

def on_message(&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.

prompt_or_messages

String or Array - a string prompt or array of message hashes/objects

tool_context

Object or nil - optional context object passed to all tool calls

Returns Enumerator - an enumerator yielding stream events.



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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/riffer/agent.rb', line 179

def stream(prompt_or_messages, tool_context: nil)
  @tool_context = tool_context
  @resolved_tools = nil
  initialize_messages(prompt_or_messages)

  Enumerator.new do |yielder|
    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 << {
            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
      )
      add_message(response)
      track_token_usage(accumulated_token_usage)

      break unless has_tool_calls?(response)

      execute_tool_calls(response)
    end
  end
end