Class: Riffer::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/riffer/agent.rb

Overview

Base class for all agents in the Riffer framework. Subclass it to define an agent’s model, instructions, tools, and guardrails.

class MyAgent < Riffer::Agent
  model 'openai/gpt-4o'
  instructions 'You are a helpful assistant.'
end

agent = MyAgent.new
agent.generate('Hello!')

Defined Under Namespace

Modules: Run, Serializer Classes: Config, Context, Response, Session, StructuredOutput

Constant Summary collapse

INTERRUPT_MAX_STEPS =
:max_steps

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(session: nil, context: nil, config: nil) ⇒ Agent

Initializes a new agent.

A provided session: is used as-is — the caller owns its contents (e.g. cross-process resume from persisted history); an omitted one is seeded with the instruction and skills messages.

Raises Riffer::ArgumentError unless the configured model string is “provider/model” format.

– : (?session: Riffer::Agent::Session?, ?context: Hash[Symbol, untyped]?, ?config: Riffer::Agent::Config?) -> void



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/riffer/agent.rb', line 264

def initialize(session: nil, context: nil, config: nil)
  @config = config || self.class.config
  @context = Riffer::Agent::Context.new(context || {})

  @provider_name, @model_name = resolve_provider_and_model
  @provider = build_provider

  @context.skills = resolve_skills

  @structured_output = resolve_structured_output
  @tools = resolve_tools
  @tool_runtime = resolve_tool_runtime

  @instruction_message = build_instruction_message
  @skills_message = build_skills_message

  @session = session || Riffer::Agent::Session.new(messages: [@instruction_message, @skills_message].compact)
  @session.set(Riffer::Agent::Session::Repair.prune_orphans(@session.messages))
end

Instance Attribute Details

#configObject (readonly)

The per-instance Riffer::Agent::Config.



217
218
219
# File 'lib/riffer/agent.rb', line 217

def config
  @config
end

#contextObject (readonly)

The mutable runtime context shared with every Riffer::Agent::Run this agent executes and threaded through all Proc-based settings.



229
230
231
# File 'lib/riffer/agent.rb', line 229

def context
  @context
end

#instruction_messageObject (readonly)

The system message built from the configured instructions, or nil when none are configured.



221
222
223
# File 'lib/riffer/agent.rb', line 221

def instruction_message
  @instruction_message
end

#model_nameObject (readonly)

The resolved model name (the part after “/” in the model string), used as the model argument on every LLM call.



237
238
239
# File 'lib/riffer/agent.rb', line 237

def model_name
  @model_name
end

#providerObject (readonly)

The provider client. Public so tests can pre-queue responses on Riffer::Providers::Mock before calling #generate.



241
242
243
# File 'lib/riffer/agent.rb', line 241

def provider
  @provider
end

#provider_nameObject (readonly)

The resolved provider name (the part before “/” in the model string), e.g. “openai”.



233
234
235
# File 'lib/riffer/agent.rb', line 233

def provider_name
  @provider_name
end

#sessionObject (readonly)

The conversation handle.



214
215
216
# File 'lib/riffer/agent.rb', line 214

def session
  @session
end

#skills_messageObject (readonly)

The system message describing the configured skills catalog, or nil when skills are unconfigured or the catalog is empty.



225
226
227
# File 'lib/riffer/agent.rb', line 225

def skills_message
  @skills_message
end

#structured_outputObject (readonly)

The Riffer::Agent::StructuredOutput wrapping the configured schema, or nil when not configured.



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

def structured_output
  @structured_output
end

#tool_runtimeObject (readonly)

The tool runtime instance used to execute tool calls.



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

def tool_runtime
  @tool_runtime
end

#toolsObject (readonly)

The tool classes the LLM sees on every call this agent makes.



248
249
250
# File 'lib/riffer/agent.rb', line 248

def tools
  @tools
end

Class Method Details

.allObject

Returns all agent subclasses.

– : () -> Array



166
167
168
# File 'lib/riffer/agent.rb', line 166

def self.all
  subclasses #: Array[singleton(Riffer::Agent)]
end

.configObject

Returns the per-class Riffer::Agent::Config holding every DSL setting. – : () -> Riffer::Agent::Config



25
26
27
# File 'lib/riffer/agent.rb', line 25

def self.config
  @config ||= Riffer::Agent::Config.new
end

.find(identifier) ⇒ Object

Finds an agent class by identifier.

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



158
159
160
# File 'lib/riffer/agent.rb', line 158

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

.from_h(hash, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil) ⇒ Object

Reconstructs a runnable agent from a wire hash produced by #to_h. – : (Hash[Symbol, untyped], ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent



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

def self.from_h(hash, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
  Riffer::Agent::Serializer.from_h(hash, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
end

.from_json(json, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil) ⇒ Object

Reconstructs a runnable agent from a JSON string produced by #to_json. – : (String, ?context: Hash[Symbol, untyped]?, ?session: Riffer::Agent::Session?, ?tool_resolver: ^(Hash[Symbol, untyped]) -> singleton(Riffer::Tool), ?tool_runtime: (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> Riffer::Agent



194
195
196
# File 'lib/riffer/agent.rb', line 194

def self.from_json(json, context: nil, session: nil, tool_resolver: Riffer::Agent::Serializer::DEFAULT_TOOL_RESOLVER, tool_runtime: nil)
  Riffer::Agent::Serializer.from_json(json, context: context, session: session, tool_resolver: tool_resolver, tool_runtime: tool_runtime)
end

.generate(prompt = nil, files: nil, context: nil) ⇒ Object

Generates a response using a new agent instance. – : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response



173
174
175
# File 'lib/riffer/agent.rb', line 173

def self.generate(prompt = nil, files: nil, context: nil)
  new(context: context).generate(prompt, files: files)
end

.guardrail(phase, with:, **options) ⇒ Object

Registers a guardrail for input, output, or both phases. Raises Riffer::ArgumentError unless phase is :before, :after, or :around. – : (Symbol, with: singleton(Riffer::Guardrail), **untyped) -> void



202
203
204
# File 'lib/riffer/agent.rb', line 202

def self.guardrail(phase, with:, **options)
  config.add_guardrail(phase, klass: with, options: options)
end

.guardrails_for(phase) ⇒ Object

Returns the registered guardrail configs for a given phase. – : (Symbol) -> Array[Hash[Symbol, untyped]]



209
210
211
# File 'lib/riffer/agent.rb', line 209

def self.guardrails_for(phase)
  config.guardrails_for(phase)
end

.identifier(value = nil) ⇒ Object

Gets or sets the agent identifier.

– : (?String?) -> String



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

def self.identifier(value = nil)
  value.nil? ? (config.identifier || Riffer::Helpers::ClassNameConverter.convert(name)) : (config.identifier = value)
end

.instructions(value = nil) ⇒ Object

Gets or sets the agent instructions. A Proc is called at generate time with the context hash (which may be nil).

instructions "You are a helpful assistant."

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

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



56
57
58
# File 'lib/riffer/agent.rb', line 56

def self.instructions(value = nil)
  value.nil? ? config.instructions : (config.instructions = value)
end

.max_steps(*value) ⇒ Object

Gets or sets the maximum number of LLM call steps in the tool-use loop. The splat distinguishes a getter (no argument) from setting the limit to nil (unlimited); it defaults to Riffer::Agent::Config::DEFAULT_MAX_STEPS.

max_steps        # reads the current limit
max_steps 8      # cap the loop at 8 steps
max_steps nil    # unlimited

– : (*Numeric?) -> Numeric?



98
99
100
101
# File 'lib/riffer/agent.rb', line 98

def self.max_steps(*value)
  return config.max_steps if value.empty?
  config.max_steps = value.first
end

.mcp_configsObject

Returns the accumulated use_mcp configurations for this agent class.

: () -> Array[Hash[Symbol, untyped]]



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

def self.mcp_configs
  config.mcp_configs
end

.model(value = nil) ⇒ Object

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

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



41
42
43
# File 'lib/riffer/agent.rb', line 41

def self.model(value = nil)
  value.nil? ? config.model : (config.model = value)
end

.model_options(options = nil) ⇒ Object

Gets or sets model options passed to generate_text/stream_text.

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



72
73
74
# File 'lib/riffer/agent.rb', line 72

def self.model_options(options = nil)
  options.nil? ? config.model_options : (config.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]



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

def self.provider_options(options = nil)
  options.nil? ? config.provider_options : (config.provider_options = options)
end

.skills(&block) ⇒ Object

Configures skills for this agent via a block DSL, or 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

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



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

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

.stream(prompt = nil, files: nil, context: nil) ⇒ Object

Streams a response using a new agent instance. – : (?String?, ?files: Array[Hash[Symbol, untyped] | Riffer::Messages::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]



180
181
182
# File 'lib/riffer/agent.rb', line 180

def self.stream(prompt = nil, files: nil, context: nil)
  new(context: context).stream(prompt, files: files)
end

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

Gets or sets the structured output schema for this agent. – : (?Riffer::Params?) ?{ (Riffer::Params) [self: Riffer::Params] -> void } -> Riffer::Params?



79
80
81
82
83
84
85
86
# File 'lib/riffer/agent.rb', line 79

def self.structured_output(params = nil, &block)
  if block
    params = Riffer::Params.new
    params.instance_eval(&block)
  end
  config.structured_output = params if params
  config.structured_output
end

.tool_runtime(value = nil) ⇒ Object

Gets or sets the tool runtime for this agent; defaults to Riffer.config.tool_runtime when unset. – : (?(singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)?) -> (singleton(Riffer::Tools::Runtime) | Riffer::Tools::Runtime | Proc)



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

def self.tool_runtime(value = nil)
  value.nil? ? config.tool_runtime : (config.tool_runtime = value)
end

.use_mcp(tag, progressive: true) ⇒ Object

Opts this agent into MCP tools from registrations matching the given tag. Progressive registrations expose mcp_search instead of every schema up front.

: (String | Symbol, ?progressive: bool) -> void



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

def self.use_mcp(tag, progressive: true)
  config.add_mcp(tag, progressive: progressive)
end

.uses_tools(value = nil) ⇒ Object

Gets or sets the tools used by this agent.

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



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

def self.uses_tools(value = nil)
  value.nil? ? config.tools_config : (config.tools_config = value)
end

Instance Method Details

#generate(prompt = nil, files: nil) ⇒ Object

Generates a response from the agent.

With prompt, a new user message is appended (silently — on_message does not fire for user inputs) before the loop runs. Without it, the loop runs against the current session, resuming a persisted conversation or pending tool calls. files: requires prompt.

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



293
294
295
# File 'lib/riffer/agent.rb', line 293

def generate(prompt = nil, files: nil)
  Riffer::Agent::Run.generate(agent: self, prompt: prompt, files: files)
end

#interrupt!(reason = nil) ⇒ Object

Interrupts the agent loop from an on_message callback. Equivalent to throw :riffer_interrupt, reason. – : (?(String | Symbol)?) -> void



313
314
315
# File 'lib/riffer/agent.rb', line 313

def interrupt!(reason = nil)
  throw :riffer_interrupt, reason
end

#stream(prompt = nil, files: nil) ⇒ Object

Streams a response from the agent, returning an Enumerator of Riffer::StreamEvents. See #generate for prompt/files semantics.

Raises Riffer::ArgumentError if structured output is configured.

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



304
305
306
307
# File 'lib/riffer/agent.rb', line 304

def stream(prompt = nil, files: nil)
  raise Riffer::ArgumentError, "Structured output is not supported with streaming. Use #generate instead." if @structured_output
  Riffer::Agent::Run.stream(agent: self, prompt: prompt, files: files)
end

#to_hObject

Snapshots this resolved agent into a self-contained, provider-neutral wire hash. – : () -> Hash[Symbol, untyped]



321
322
323
# File 'lib/riffer/agent.rb', line 321

def to_h
  Riffer::Agent::Serializer.to_h(agent: self)
end

#to_jsonObject

Snapshots this resolved agent into a wire JSON string. The * absorbs the JSON generator state argument so JSON.generate(agent) works too. – : (*untyped) -> String



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

def to_json(*)
  Riffer::Agent::Serializer.to_json(agent: self)
end