Class: RubyLLM::Toolbox::Base

Inherits:
RubyLLM::Tool
  • Object
show all
Defined in:
lib/ruby_llm/toolbox/base.rb

Overview

Every toolbox tool subclasses this instead of RubyLLM::Tool directly. It adds four things on top of the base ruby_llm DSL:

1. A per-instance config snapshot (overridable at construction).
2. An exec gate: tools marked `exec_tool!` refuse to run unless
   config.enable_exec_tools is true.
3. A uniform failure contract: tools return { error:, code: } and never
   raise into the harness. (This matches ruby_llm's own convention of
   returning { error: ... } for bad arguments.)
4. Token-budgeted output via #truncate.

Success returns are whatever the tool produces (usually a String, or a Hash for structured results). Failures are always { error:, code: }.

Defined Under Namespace

Classes: UnsafeDenied

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**overrides) ⇒ Base

Returns a new instance of Base.



36
37
38
39
# File 'lib/ruby_llm/toolbox/base.rb', line 36

def initialize(**overrides)
  super()
  @config = RubyLLM::Toolbox.config.dup_with(**overrides)
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



41
42
43
# File 'lib/ruby_llm/toolbox/base.rb', line 41

def config
  @config
end

Class Method Details

.exec_tool!Object

Mark a subclass as part of the dangerous set.



27
28
29
# File 'lib/ruby_llm/toolbox/base.rb', line 27

def exec_tool!
  @exec_tool = true
end

.exec_tool?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/ruby_llm/toolbox/base.rb', line 31

def exec_tool?
  @exec_tool == true
end

Instance Method Details

#call(args) ⇒ Object

Wraps the base #call to enforce the exec gate and guarantee that no exception ever escapes into the model loop.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ruby_llm/toolbox/base.rb', line 58

def call(args)
  if self.class.exec_tool? && !config.enable_exec_tools
    return error(
      "Exec tools are disabled. Set RubyLLM::Toolbox.config.enable_exec_tools = true " \
      "(and an allowlist where relevant) to use #{self.class.name}.",
      code: :exec_disabled
    )
  end

  super
rescue UnsafeDenied => e
  error(e.message, code: :unsafe_denied)
rescue StandardError => e
  error("#{self.class.name} failed: #{e.message}", code: :tool_exception)
end

#nameObject

ruby_llm derives the tool name from the full class name, which would turn RubyLLM::Toolbox::Tools::ReadFile into an ugly namespaced string. Demodulize first so tools get clean names (“read_file”, “bash”, …).



46
47
48
49
50
51
52
53
54
# File 'lib/ruby_llm/toolbox/base.rb', line 46

def name
  @name ||= begin
    base = self.class.name.to_s.split("::").last.to_s
    base.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
        .gsub(/([a-z\d])([A-Z])/, '\1_\2')
        .downcase
        .delete_suffix("_tool")
  end
end