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 collapse
- DEFAULT_MAX_STEPS =
: Integer
16- INTERRUPT_MAX_STEPS =
: Symbol
:max_steps- HEALING_PLACEHOLDER =
Placeholder used to fill orphan
tool_useblocks whenRiffer.config.experimental_history_healingis enabled and an interrupt fires mid-tool-use. ->(_tool_call) { Riffer::Tools::Response.error("Tool call interrupted before completion.", type: :interrupted) }
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_or_proc = nil) ⇒ Object
Gets or sets the agent instructions.
-
.max_steps(value = nil) ⇒ Object
Gets or sets the maximum number of LLM call steps in the tool-use loop.
-
.mcp_configs ⇒ Object
Returns the accumulated
use_mcpconfigurations for this agent class. -
.model(model_string_or_proc = nil) ⇒ Object
Gets or sets the model string (e.g., “openai/gpt-4o”) or Proc.
-
.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.
-
.resolved_tool_classes(context: nil) ⇒ Object
Returns the tool classes the LLM should see for this agent.
-
.skills(&block) ⇒ Object
Configures skills for this agent via a block DSL.
-
.stream ⇒ Object
Streams a response using a new agent instance.
-
.structured_output(params = nil, &block) ⇒ Object
Gets or sets the structured output schema for this agent.
-
.tool_runtime(value = nil) ⇒ Object
Gets or sets the tool runtime for this agent.
-
.use_mcp(tag) ⇒ Object
Opts this agent into tools from all MCP registrations that share any of the given tag(s).
-
.uses_tools(tools_or_lambda = nil) ⇒ Object
Gets or sets the tools used by this agent.
Instance Method Summary collapse
-
#generate(prompt_or_messages, files: nil, context: nil) ⇒ Object
Generates a response from the agent.
-
#generate_instruction_message(context: nil) ⇒ Object
Generates the instruction system message for this agent.
-
#generate_skills_message(context: nil) ⇒ Object
Generates the skills catalog system message for this agent.
-
#initialize ⇒ Agent
constructor
Initializes a new agent.
-
#interrupt!(reason = nil) ⇒ Object
Interrupts the agent loop.
-
#last_assistant ⇒ Object
Returns the most recent
Riffer::Messages::Assistantin history, ornilwhen no assistant message has been recorded yet. -
#message_by_id(id) ⇒ Object
Returns the message with the given id, or
nilwhen no message matches. -
#on_message(&block) ⇒ Object
Registers a callback to be invoked when messages are added during generation.
-
#orphaned_tool_call_ids ⇒ Object
Returns the call_ids of every
tool_callon any assistant message that has no matchingRiffer::Messages::Toolresult anywhere in history. -
#remove_message(id:) ⇒ Object
Removes a message by id.
-
#replace_assistant_content(id:, content:) ⇒ Object
Replaces the content of an assistant message in place.
-
#replace_tool_result(tool_call_id:, content:, error: nil, error_type: nil) ⇒ Object
Replaces a tool result’s content (and optional error fields) in place.
-
#stream(prompt_or_messages, files: nil, context: nil) ⇒ Object
Streams a response from the agent.
-
#tool_message_for(tool_call_id) ⇒ Object
Returns the
Riffer::Messages::Toolmessage that satisfiestool_call_id, ornilwhen no such tool result exists in history.
Methods included from Helpers::ClassNameConverter
Methods included from Helpers::Validations
Methods included from Messages::Converter
#convert_to_file_part, #convert_to_message_object
Constructor Details
#initialize ⇒ Agent
Initializes a new agent.
Raises Riffer::ArgumentError if the configured model string is invalid (must be “provider/model” format).
– : () -> void
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/riffer/agent.rb', line 337 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
#messages ⇒ Object (readonly)
The message history for the agent.
325 326 327 |
# File 'lib/riffer/agent.rb', line 325 def @messages end |
#token_usage ⇒ Object (readonly)
Cumulative token usage across all LLM calls.
328 329 330 |
# File 'lib/riffer/agent.rb', line 328 def token_usage @token_usage end |
Class Method Details
.all ⇒ Object
Returns all agent subclasses.
– : () -> Array
261 262 263 |
# File 'lib/riffer/agent.rb', line 261 def self.all subclasses #: Array[singleton(Riffer::Agent)] end |
.find(identifier) ⇒ Object
Finds an agent class by identifier.
– : (String) -> singleton(Riffer::Agent)?
253 254 255 |
# File 'lib/riffer/agent.rb', line 253 def self.find(identifier) all.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
271 272 273 |
# File 'lib/riffer/agent.rb', line 271 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
294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 |
# File 'lib/riffer/agent.rb', line 294 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]]
319 320 321 322 |
# File 'lib/riffer/agent.rb', line 319 def self.guardrails_for(phase) @guardrails ||= {before: [], after: []} @guardrails[phase] || [] end |
.identifier(value = nil) ⇒ Object
Gets or sets the agent identifier.
– : (?String?) -> String
40 41 42 43 |
# File 'lib/riffer/agent.rb', line 40 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)?
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/riffer/agent.rb', line 74 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
128 129 130 131 |
# File 'lib/riffer/agent.rb', line 128 def self.max_steps(value = nil) return @max_steps || DEFAULT_MAX_STEPS if value.nil? @max_steps = value end |
.mcp_configs ⇒ Object
Returns the accumulated use_mcp configurations for this agent class.
: () -> Array[Hash[Symbol, untyped]]
206 207 208 |
# File 'lib/riffer/agent.rb', line 206 def self.mcp_configs @mcp_configs || [] 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)?
49 50 51 52 53 54 55 56 57 58 |
# File 'lib/riffer/agent.rb', line 49 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]
98 99 100 101 |
# File 'lib/riffer/agent.rb', line 98 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]
89 90 91 92 |
# File 'lib/riffer/agent.rb', line 89 def self.( = nil) return @provider_options || {} if .nil? @provider_options = end |
.resolved_tool_classes(context: nil) ⇒ Object
Returns the tool classes the LLM should see for this agent.
Class-level companion to the instance #resolved_tools. Resolves the Proc form of uses_tools and appends the skill activation tool when a skills block is configured. Does not read the skills backend —the LLM-facing tool schema reflects class-level intent, not the runtime state of any backend.
When uses_tools is a Proc, context is forwarded to it.
The activation tool class is resolved from the agent’s skills do; activate_tool ...; end override when set, otherwise from Riffer.config.skills.default_activate_tool.
Each returned tool class is validated via validate_as_tool!, so callers serializing this list to a provider can rely on every entry having the metadata required for tool use (name + description).
Raises Riffer::ArgumentError on tool name conflicts with the skill activation tool, or when a tool class fails validate_as_tool!.
– : (?context: Hash[Symbol, untyped]?) -> Array
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/riffer/agent.rb', line 165 def self.resolved_tool_classes(context: nil) base = resolve_uses_tools_config(context) tools = if skills skill_activate_tool_class = skills.activate_tool || Riffer.config.skills.default_activate_tool if base.any? { |t| t.name == skill_activate_tool_class.name } raise Riffer::ArgumentError, "Tool name conflict with skill tools: #{skill_activate_tool_class.name}" end base + [skill_activate_tool_class] else base end tools.each(&:validate_as_tool!) tools 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?
241 242 243 244 245 246 247 |
# File 'lib/riffer/agent.rb', line 241 def self.skills(&block) if block @skills_config = Riffer::Skills::Config.new @skills_config.instance_eval(&block) end @skills_config 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]
281 282 283 |
# File 'lib/riffer/agent.rb', line 281 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?
109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/riffer/agent.rb', line 109 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)?
220 221 222 223 224 225 226 227 |
# File 'lib/riffer/agent.rb', line 220 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 |
.use_mcp(tag) ⇒ Object
Opts this agent into tools from all MCP registrations that share any of the given tag(s).
tag - a String or Symbol; matched against registration manifest tags.
: (String | Symbol) -> void
198 199 200 201 |
# File 'lib/riffer/agent.rb', line 198 def self.use_mcp(tag) @mcp_configs ||= [] @mcp_configs << {tags: [tag.to_sym]} end |
.uses_tools(tools_or_lambda = nil) ⇒ Object
137 138 139 140 |
# File 'lib/riffer/agent.rb', line 137 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.
When Riffer.config.experimental_history_healing is enabled, seeded message arrays that violate the tool_use ↔ tool_result invariant are silently repaired before the run begins.
– : ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Riffer::Agent::Response
361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/riffer/agent.rb', line 361 def generate(, files: nil, context: nil) @context = context prepare_run @structured_output = resolve_structured_output (, 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?
426 427 428 |
# File 'lib/riffer/agent.rb', line 426 def (context: nil) (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?
439 440 441 |
# File 'lib/riffer/agent.rb', line 439 def (context: nil) (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.
When Riffer.config.experimental_history_healing is enabled, riffer fills any orphaned tool_use on the way out with a placeholder Riffer::Messages::Tool carrying error_type: :interrupted. The filled call_ids are exposed on Riffer::Agent::Response#healed_tool_call_ids (and the streaming Riffer::StreamEvents::Interrupt event).
– : (?(String | Symbol)?) -> void
457 458 459 |
# File 'lib/riffer/agent.rb', line 457 def interrupt!(reason = nil) throw :riffer_interrupt, reason end |
#last_assistant ⇒ Object
Returns the most recent Riffer::Messages::Assistant in history, or nil when no assistant message has been recorded yet.
– : () -> Riffer::Messages::Assistant? TODO: Replace with rfind when minimum Ruby is 4.0+ rubocop:disable Style/ReverseFind
488 489 490 |
# File 'lib/riffer/agent.rb', line 488 def last_assistant @messages.reverse.find { |m| m.is_a?(Riffer::Messages::Assistant) } #: Riffer::Messages::Assistant? end |
#message_by_id(id) ⇒ Object
Returns the message with the given id, or nil when no message matches.
– : (String) -> Riffer::Messages::Base?
465 466 467 |
# File 'lib/riffer/agent.rb', line 465 def (id) @messages.find { |m| m.id == id } 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
411 412 413 414 415 |
# File 'lib/riffer/agent.rb', line 411 def (&block) raise Riffer::ArgumentError, "on_message requires a block" unless block_given? @message_callbacks << block self end |
#orphaned_tool_call_ids ⇒ Object
Returns the call_ids of every tool_call on any assistant message that has no matching Riffer::Messages::Tool result anywhere in history.
Zero-cost validation hook for callers that want to check the tool_use ↔ tool_result invariant before mutating or persisting.
– : () -> Array
501 502 503 504 505 506 507 |
# File 'lib/riffer/agent.rb', line 501 def orphaned_tool_call_ids result_ids = @messages.filter_map { |m| m.tool_call_id if m.is_a?(Riffer::Messages::Tool) } @messages.flat_map { |m| next [] unless m.is_a?(Riffer::Messages::Assistant) m.tool_calls.reject { |tc| result_ids.include?(tc.call_id) }.map(&:call_id) } end |
#remove_message(id:) ⇒ Object
Removes a message by id. When the target is an assistant message that carries tool_calls, every Riffer::Messages::Tool result whose tool_call_id matches one of those calls is removed atomically — keeping the tool_use ↔ tool_result invariant intact.
Raises Riffer::ArgumentError when called on a Riffer::Messages::Tool message — that would orphan the parent’s tool_use. Use replace_tool_result instead.
Returns the removed message, or nil when no message has the given id (idempotent).
– : (id: String) -> Riffer::Messages::Base?
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 |
# File 'lib/riffer/agent.rb', line 551 def (id:) idx = @messages.index { |m| m.id == id } return nil unless idx target = @messages[idx] if target.is_a?(Riffer::Messages::Tool) raise Riffer::ArgumentError, "remove_message cannot drop a Tool message (would orphan the parent's tool_use); use replace_tool_result instead" end if target.is_a?(Riffer::Messages::Assistant) && !target.tool_calls.empty? child_ids = target.tool_calls.map(&:call_id) @messages.reject! { |m| m.is_a?(Riffer::Messages::Tool) && child_ids.include?(m.tool_call_id) } @messages.delete(target) else @messages.delete_at(idx) end target end |
#replace_assistant_content(id:, content:) ⇒ Object
Replaces the content of an assistant message in place. Preserves id, tool_calls, token_usage, and structured_output. Lookup is by id.
When content is empty, delegates to remove_message — including the cascade that drops dependent Riffer::Messages::Tool children.
Raises Riffer::ArgumentError when no assistant message has the given id.
– : (id: String, content: String) -> Riffer::Messages::Base?
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 |
# File 'lib/riffer/agent.rb', line 519 def replace_assistant_content(id:, content:) return (id: id) if content.empty? idx = @messages.index { |m| m.is_a?(Riffer::Messages::Assistant) && m.id == id } raise Riffer::ArgumentError, "no assistant message with id #{id.inspect}" unless idx old = @messages[idx] #: Riffer::Messages::Assistant replacement = Riffer::Messages::Assistant.new( content, id: old.id, tool_calls: old.tool_calls, token_usage: old.token_usage, structured_output: old.structured_output ) @messages[idx] = replacement replacement end |
#replace_tool_result(tool_call_id:, content:, error: nil, error_type: nil) ⇒ Object
Replaces a tool result’s content (and optional error fields) in place. Lookup is by tool_call_id. Preserves the existing message’s name and id.
Raises Riffer::ArgumentError when no Tool message exists for the given tool_call_id.
– : (tool_call_id: String, content: String, ?error: String?, ?error_type: Symbol?) -> Riffer::Messages::Tool
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 |
# File 'lib/riffer/agent.rb', line 580 def replace_tool_result(tool_call_id:, content:, error: nil, error_type: nil) idx = @messages.index { |m| m.is_a?(Riffer::Messages::Tool) && m.tool_call_id == tool_call_id } raise Riffer::ArgumentError, "no tool result for tool_call_id #{tool_call_id.inspect}" unless idx old = @messages[idx] #: Riffer::Messages::Tool replacement = Riffer::Messages::Tool.new( content, id: old.id, tool_call_id: old.tool_call_id, name: old.name, error: error, error_type: error_type ) @messages[idx] = replacement replacement end |
#stream(prompt_or_messages, files: nil, context: nil) ⇒ Object
Streams a response from the agent.
Raises Riffer::ArgumentError if structured output is configured.
See #generate for the experimental_history_healing behavior on seeded arrays.
– : ((String | Array[Hash[Symbol, untyped] | Riffer::Messages::Base]), ?files: Array[Hash[Symbol, untyped] | Riffer::FilePart]?, ?context: Hash[Symbol, untyped]?) -> Enumerator[Riffer::StreamEvents::Base, void]
385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 |
# File 'lib/riffer/agent.rb', line 385 def stream(, 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 (, 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 |
#tool_message_for(tool_call_id) ⇒ Object
Returns the Riffer::Messages::Tool message that satisfies tool_call_id, or nil when no such tool result exists in history.
– : (String) -> Riffer::Messages::Tool? TODO: Replace with rfind when minimum Ruby is 4.0+ rubocop:disable Style/ReverseFind
476 477 478 |
# File 'lib/riffer/agent.rb', line 476 def (tool_call_id) @messages.reverse.find { |m| m.is_a?(Riffer::Messages::Tool) && m.tool_call_id == tool_call_id } #: Riffer::Messages::Tool? end |