Class: RubyPi::Tools::Executor
- Inherits:
-
Object
- Object
- RubyPi::Tools::Executor
- Defined in:
- lib/ruby_pi/tools/executor.rb
Constant Summary collapse
- DEFAULT_TIMEOUT =
Default timeout for each tool execution, in seconds.
30- DEFAULT_MAX_CALLS =
32- DEFAULT_MAX_CONCURRENCY =
4- MAX_ACTIVE_EXECUTIONS =
16
Instance Attribute Summary collapse
-
#mode ⇒ Symbol
readonly
The execution mode (:parallel or :sequential).
-
#timeout ⇒ Numeric
readonly
The per-tool timeout in seconds.
Class Method Summary collapse
- .active_execution_count ⇒ Object
-
.deep_symbolize_keys(obj) ⇒ Object
Recursively converts all string keys in a hash to symbols.
Instance Method Summary collapse
-
#execute(calls) ⇒ Array<RubyPi::Tools::Result>
Executes a list of tool calls and returns their results.
-
#initialize(registry, mode: :parallel, timeout: DEFAULT_TIMEOUT, max_calls: DEFAULT_MAX_CALLS, max_concurrency: DEFAULT_MAX_CONCURRENCY) ⇒ Executor
constructor
Creates a new Executor.
Constructor Details
#initialize(registry, mode: :parallel, timeout: DEFAULT_TIMEOUT, max_calls: DEFAULT_MAX_CALLS, max_concurrency: DEFAULT_MAX_CONCURRENCY) ⇒ Executor
Creates a new Executor.
87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# File 'lib/ruby_pi/tools/executor.rb', line 87 def initialize(registry, mode: :parallel, timeout: DEFAULT_TIMEOUT, max_calls: DEFAULT_MAX_CALLS, max_concurrency: DEFAULT_MAX_CONCURRENCY) unless %i[parallel sequential].include?(mode) raise ArgumentError, "Mode must be :parallel or :sequential, got #{mode.inspect}" end validate_positive_numeric!(:timeout, timeout) validate_positive_integer!(:max_calls, max_calls) validate_positive_integer!(:max_concurrency, max_concurrency) @registry = registry @mode = mode @timeout = timeout @max_calls = max_calls @max_concurrency = max_concurrency end |
Instance Attribute Details
#mode ⇒ Symbol (readonly)
Returns The execution mode (:parallel or :sequential).
62 63 64 |
# File 'lib/ruby_pi/tools/executor.rb', line 62 def mode @mode end |
#timeout ⇒ Numeric (readonly)
Returns The per-tool timeout in seconds.
65 66 67 |
# File 'lib/ruby_pi/tools/executor.rb', line 65 def timeout @timeout end |
Class Method Details
.active_execution_count ⇒ Object
39 40 41 |
# File 'lib/ruby_pi/tools/executor.rb', line 39 def active_execution_count @capacity_mutex.synchronize { @active_executions } end |
.deep_symbolize_keys(obj) ⇒ Object
Recursively converts all string keys in a hash to symbols.
68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/ruby_pi/tools/executor.rb', line 68 def self.deep_symbolize_keys(obj) case obj when Hash obj.each_with_object({}) do |(key, value), result| result[key.to_sym] = deep_symbolize_keys(value) end when Array obj.map { |item| deep_symbolize_keys(item) } else obj end end |
Instance Method Details
#execute(calls) ⇒ Array<RubyPi::Tools::Result>
Executes a list of tool calls and returns their results.
Each call is a hash with :name (String or Symbol) and :arguments (Hash).
Tools are looked up in the registry; if a tool is not found, a failure
Result is returned for that call.
Issue #17: Raises NoToolsRegisteredError if the registry is nil and tool calls are attempted, preventing a confusing NoMethodError.
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
# File 'lib/ruby_pi/tools/executor.rb', line 116 def execute(calls) unless calls.is_a?(Array) raise ArgumentError, "calls must be an Array, got #{calls.class}" end if calls.size > @max_calls raise RubyPi::ToolCallLimitError, "Model returned #{calls.size} tool calls; maximum is #{@max_calls} per execution" end return [] if calls.empty? if Executor.active_execution_count >= MAX_ACTIVE_EXECUTIONS raise RubyPi::ToolExecutionCapacityError, "Refusing tool execution while #{MAX_ACTIVE_EXECUTIONS} tools are still running" end # Issue #17: Guard against nil registry — if the LLM hallucinated tool # calls but no tools are registered, raise a typed error immediately # rather than crashing with NoMethodError on nil.find. if @registry.nil? raise RubyPi::NoToolsRegisteredError, "Model returned #{calls.size} tool call(s) but no tools are registered" end case @mode when :parallel execute_parallel(calls) when :sequential execute_sequential(calls) end end |