Class: Phronomy::Tool::Base
- Inherits:
-
RubyLLM::Tool
- Object
- RubyLLM::Tool
- Phronomy::Tool::Base
- Defined in:
- lib/phronomy/tool/base.rb
Overview
Base class extending RubyLLM::Tool with Phronomy-specific DSL.
Additional DSL over RubyLLM::Tool:
- tool_name : explicit function name exposed to the LLM (overrides auto-conversion)
- scope : access-scope metadata (:read_only, :write, etc.)
- on_error : error-handling policy (:raise or :return_empty)
- on_schema_error : behavior when LLM passes schema-violating arguments :return_error (default), :raise, or :coerce
- requires_approval : require human approval before execution
- param :name, enum: [...] : restrict allowed values in the JSON Schema
Class Attribute Summary collapse
-
._sleep_proc ⇒ #call
private
Injectable sleep callable for testing.
Class Method Summary collapse
-
.on_error(behavior = nil) ⇒ Object
Configures error-handling behavior when +execute+ raises an unexpected error.
-
.on_schema_error(behavior = nil) ⇒ Object
Configures how this tool responds when the LLM passes arguments that violate the declared parameter types or enum constraints.
-
.param(name, enum: nil, properties: nil, **options) ⇒ Object
Extends RubyLLM::Tool.param with optional +enum:+ and +properties:+ keywords.
-
.param_enums ⇒ Hash{Symbol => Array}
Returns the enum constraints registered via .param.
-
.param_schemas ⇒ Hash{Symbol => Hash}
Returns nested schema definitions registered via .param(properties: ...).
-
.requires_approval(value = nil) ⇒ Object
Configures whether human approval is required before executing this tool.
-
.retry_on(*exception_classes, times: 1, wait: 0, base: 1.0) ⇒ Object
Registers a retry policy for one or more exception classes.
-
.retry_policies ⇒ Array<Hash>
Returns all retry policies registered on this tool class.
-
.scope(value = nil) ⇒ Object
Sets the access scope for this tool (metadata; enforcement is the responsibility of the Workflow/Guardrail layer).
-
.tool_name(value = nil) ⇒ Object
Sets an explicit function name to expose to the LLM, bypassing RubyLLM's automatic CamelCase-to-snake_case conversion.
Instance Method Summary collapse
-
#call(args, cancellation_token: nil) ⇒ Object
Overrides RubyLLM::Tool#call to apply schema validation, the retry policy, the on_error policy, and wrap errors as ToolError.
-
#execute(**_args) ⇒ String
abstract
Override this method to implement the tool's logic.
-
#name ⇒ Object
Returns the function name exposed to the LLM.
-
#params_schema ⇒ Object
Returns the JSON Schema for this tool's parameters.
-
#requires_approval ⇒ Object
Instance method accessor — delegates to the class-level flag.
-
#requires_approval? ⇒ Boolean
Instance method for requires_approval? (convenience accessor).
Class Attribute Details
._sleep_proc ⇒ #call
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Injectable sleep callable for testing. Defaults to Kernel#sleep.
181 182 183 |
# File 'lib/phronomy/tool/base.rb', line 181 def _sleep_proc @_sleep_proc || method(:sleep) end |
Class Method Details
.on_error(behavior = nil) ⇒ Object
Configures error-handling behavior when +execute+ raises an unexpected error.
109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/phronomy/tool/base.rb', line 109 def on_error(behavior = nil) return @on_error || :raise if behavior.nil? if behavior == :return_empty msg = "[Phronomy] on_error :return_empty is deprecated; use :suppress instead" if Phronomy.configuration.logger Phronomy.configuration.logger.warn(msg) else warn msg end end @on_error = behavior end |
.on_schema_error(behavior = nil) ⇒ Object
Configures how this tool responds when the LLM passes arguments that violate the declared parameter types or enum constraints.
133 134 135 136 137 |
# File 'lib/phronomy/tool/base.rb', line 133 def on_schema_error(behavior = nil) return @on_schema_error || :return_error if behavior.nil? @on_schema_error = behavior end |
.param(name, enum: nil, properties: nil, **options) ⇒ Object
Extends RubyLLM::Tool.param with optional +enum:+ and +properties:+ keywords.
- +enum:+ restricts allowed values; injected into the JSON Schema.
- +properties:+ declares nested fields for :object type params. Each entry is a Hash mapping field name (Symbol) to a spec Hash with keys: :type (Symbol, default :string), :required (Boolean, default false), and optionally :properties (for further nesting).
55 56 57 58 59 |
# File 'lib/phronomy/tool/base.rb', line 55 def param(name, enum: nil, properties: nil, **) super(name, **) param_enums[name] = enum if enum param_schemas[name] = normalize_nested_schema(properties) if properties end |
.param_enums ⇒ Hash{Symbol => Array}
Returns the enum constraints registered via .param.
64 65 66 |
# File 'lib/phronomy/tool/base.rb', line 64 def param_enums @param_enums ||= {} end |
.param_schemas ⇒ Hash{Symbol => Hash}
Returns nested schema definitions registered via .param(properties: ...).
71 72 73 |
# File 'lib/phronomy/tool/base.rb', line 71 def param_schemas @param_schemas ||= {} end |
.requires_approval(value = nil) ⇒ Object
Configures whether human approval is required before executing this tool.
142 143 144 145 146 |
# File 'lib/phronomy/tool/base.rb', line 142 def requires_approval(value = nil) return @requires_approval || false if value.nil? @requires_approval = value end |
.retry_on(*exception_classes, times: 1, wait: 0, base: 1.0) ⇒ Object
Registers a retry policy for one or more exception classes.
When the tool raises one of the listed exception classes, it will be retried up to +times+ times with the specified wait strategy. Multiple policies can be registered and are evaluated in order.
GuardrailError is never retried regardless of this configuration.
165 166 167 168 |
# File 'lib/phronomy/tool/base.rb', line 165 def retry_on(*exception_classes, times: 1, wait: 0, base: 1.0) @retry_policies ||= [] @retry_policies << {exceptions: exception_classes, times: times, wait: wait, base: base} end |
.retry_policies ⇒ Array<Hash>
Returns all retry policies registered on this tool class.
173 174 175 |
# File 'lib/phronomy/tool/base.rb', line 173 def retry_policies @retry_policies || [] end |
.scope(value = nil) ⇒ Object
Sets the access scope for this tool (metadata; enforcement is the responsibility of the Workflow/Guardrail layer).
94 95 96 97 98 |
# File 'lib/phronomy/tool/base.rb', line 94 def scope(value = nil) return @scope if value.nil? @scope = value end |
.tool_name(value = nil) ⇒ Object
Sets an explicit function name to expose to the LLM, bypassing RubyLLM's automatic CamelCase-to-snake_case conversion. When omitted, RubyLLM's default conversion applies (e.g. WeatherTool → "weather").
37 38 39 40 41 |
# File 'lib/phronomy/tool/base.rb', line 37 def tool_name(value = nil) return @tool_name if value.nil? @tool_name = value.to_s end |
Instance Method Details
#call(args, cancellation_token: nil) ⇒ Object
Overrides RubyLLM::Tool#call to apply schema validation, the retry policy, the on_error policy, and wrap errors as ToolError.
Execution order:
- Early cancellation check (kwarg token takes precedence over thread-local).
- Schema validation (type + enum checks).
- Inject +cancellation_token:+ into args when +execute+ opts in.
- Call super(validated_args) inside a retry loop.
- On persistent failure, apply on_error policy.
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 |
# File 'lib/phronomy/tool/base.rb', line 250 def call(args, cancellation_token: nil) ct = cancellation_token || Thread.current[:phronomy_cancellation_token] ct&.raise_if_cancelled! validated_args, schema_error = validate_and_coerce(args) if schema_error case self.class.on_schema_error when :raise raise Phronomy::ToolError, "#{self.class.name} schema error: #{schema_error}" else # :return_error (default) and coerce fallback return "Schema validation failed: #{schema_error}" end end validated_args = validated_args.merge(cancellation_token: ct) if ct && execute_accepts_cancellation_token? with_tool_retry { super(validated_args) } rescue Phronomy::ToolError raise rescue Phronomy::CancellationError raise rescue => e case self.class.on_error when :return_empty, :suppress msg = "[Phronomy] Tool #{self.class.name} suppressed error: #{e.class}: #{e.}" if Phronomy.configuration.logger Phronomy.configuration.logger.warn(msg) else warn msg end "Tool error suppressed: #{e.}" else raise Phronomy::ToolError, "#{self.class.name} execution failed: #{e.}" end end |
#execute(**_args) ⇒ String
Subclasses must implement this method.
Override this method to implement the tool's logic.
The method receives the declared param fields as keyword arguments. The return value is passed back to the LLM as the tool result.
311 312 313 |
# File 'lib/phronomy/tool/base.rb', line 311 def execute(**_args) raise NotImplementedError, "#{self.class}#execute is not implemented" end |
#name ⇒ Object
Returns the function name exposed to the LLM. Uses the class-level tool_name if set; otherwise falls back to RubyLLM's automatic conversion (CamelCase → snake_case, strips trailing "_tool").
193 194 195 |
# File 'lib/phronomy/tool/base.rb', line 193 def name self.class.tool_name || super end |
#params_schema ⇒ Object
Returns the JSON Schema for this tool's parameters. Injects "enum" entries for any param declared with enum: [...].
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 230 231 232 233 234 235 |
# File 'lib/phronomy/tool/base.rb', line 199 def params_schema schema = super return schema if schema.nil? properties = schema.dig("properties") || schema.dig(:properties) return schema unless properties # Inject enum values for params declared with enum: [...]. unless self.class.param_enums.empty? enums = self.class.param_enums enums.each do |param_name, values| key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym next unless properties[key] param_type = properties[key]["type"] properties[key]["enum"] = values.map do |v| case param_type when "integer" then v.is_a?(Integer) ? v : Integer(v.to_s) when "number" then v.is_a?(Numeric) ? v : Float(v.to_s) else v.to_s end end end end # Inject nested properties for :object params (issue #162). # Without this the LLM sees only { "type": "object" } with no field # definitions, making it unable to populate nested object params. self.class.param_schemas.each do |param_name, nested| key = properties.key?(param_name.to_s) ? param_name.to_s : param_name.to_sym next unless properties[key] properties[key]["properties"] = nested_schema_to_json_schema(nested) end schema end |
#requires_approval ⇒ Object
Instance method accessor — delegates to the class-level flag.
285 286 287 |
# File 'lib/phronomy/tool/base.rb', line 285 def requires_approval self.class.requires_approval end |
#requires_approval? ⇒ Boolean
Instance method for requires_approval? (convenience accessor).
290 291 292 |
# File 'lib/phronomy/tool/base.rb', line 290 def requires_approval? self.class.requires_approval end |