Class: Phronomy::Agent::Context::Capability::Base

Inherits:
RubyLLM::Tool
  • Object
show all
Defined in:
lib/phronomy/agent/context/capability/base.rb

Overview

Base class extending RubyLLM::Tool with Phronomy-specific DSL.

Direct Known Subclasses

Tools::Agent, Tools::Mcp, Tools::VectorSearch

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.approval_facts(&block) ⇒ Object



191
192
193
194
195
196
197
198
199
# File 'lib/phronomy/agent/context/capability/base.rb', line 191

def approval_facts(&block)
  if block
    @approval_facts = block
  elsif instance_variable_defined?(:@approval_facts)
    @approval_facts
  elsif superclass.respond_to?(:approval_facts)
    superclass.approval_facts
  end
end

.description(text = nil) ⇒ Object Also known as: desc

RubyLLM stores Tool descriptions in a class-instance variable. Preserve normal class inheritance semantics so Phronomy's anonymous decorator subclasses do not lose their parent's description.



21
22
23
24
25
26
27
28
29
30
# File 'lib/phronomy/agent/context/capability/base.rb', line 21

def description(text = nil)
  unless text
    return @description if instance_variable_defined?(:@description)
    return superclass.description if superclass.respond_to?(:description)

    return nil
  end

  @description = text
end

.execution_mode(value = nil) ⇒ Object

Declares whether Tool work is safe to run cooperatively on the EventLoop or must be offloaded to a bounded worker pool.

Phronomy does not classify the reason for offloading. Blocking I/O, CPU-bound synchronous work, and other long synchronous calls all use :offloaded. The application owns that workload classification.



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/phronomy/agent/context/capability/base.rb', line 123

def execution_mode(value = nil)
  if value.nil?
    return @execution_mode if instance_variable_defined?(:@execution_mode)
    return superclass.execution_mode if superclass.respond_to?(:execution_mode)

    return :offloaded
  end

  valid = %i[cooperative offloaded]
  unless valid.include?(value)
    raise ArgumentError,
      "execution_mode must be one of #{valid.inspect}, got #{value.inspect}"
  end
  @execution_mode = value
end

.max_result_size(value = :__unset__) ⇒ Object



212
213
214
215
216
217
218
219
220
221
# File 'lib/phronomy/agent/context/capability/base.rb', line 212

def max_result_size(value = :__unset__)
  if value == :__unset__
    return @max_result_size if instance_variable_defined?(:@max_result_size)
    return superclass.max_result_size if superclass.respond_to?(:max_result_size)

    return nil
  end

  @max_result_size = value
end

.on_error(behavior = nil) ⇒ Object

Configures execution-error handling. Supported values are :raise and :suppress only.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/phronomy/agent/context/capability/base.rb', line 142

def on_error(behavior = nil)
  if behavior.nil?
    return @on_error if instance_variable_defined?(:@on_error)
    return superclass.on_error if superclass.respond_to?(:on_error)

    return :raise
  end

  valid = %i[raise suppress]
  unless valid.include?(behavior)
    raise ArgumentError,
      "on_error must be one of #{valid.inspect}, got #{behavior.inspect}"
  end
  @on_error = behavior
end

.on_schema_error(behavior = nil) ⇒ Object



159
160
161
162
163
164
165
166
167
168
# File 'lib/phronomy/agent/context/capability/base.rb', line 159

def on_schema_error(behavior = nil)
  if behavior.nil?
    return @on_schema_error if instance_variable_defined?(:@on_schema_error)
    return superclass.on_schema_error if superclass.respond_to?(:on_schema_error)

    return :return_error
  end

  @on_schema_error = behavior
end

.param(name, enum: nil, properties: nil, **options) ⇒ Object



66
67
68
69
70
# File 'lib/phronomy/agent/context/capability/base.rb', line 66

def param(name, enum: nil, properties: nil, **options)
  super(name, **options)
  param_enums[name] = duplicate_configuration(enum) if enum
  param_schemas[name] = normalize_nested_schema(properties) if properties
end

.param_enumsObject



73
74
75
76
77
78
# File 'lib/phronomy/agent/context/capability/base.rb', line 73

def param_enums
  return @param_enums if instance_variable_defined?(:@param_enums)

  parent = superclass.respond_to?(:param_enums) ? superclass.param_enums : {}
  @param_enums = duplicate_configuration(parent)
end

.param_schemasObject



81
82
83
84
85
86
# File 'lib/phronomy/agent/context/capability/base.rb', line 81

def param_schemas
  return @param_schemas if instance_variable_defined?(:@param_schemas)

  parent = superclass.respond_to?(:param_schemas) ? superclass.param_schemas : {}
  @param_schemas = duplicate_configuration(parent)
end

.parametersObject

RubyLLM stores declared parameters in a class-instance variable. Copy the parent's registry on first access so child classes inherit existing parameters while remaining free to add their own.



37
38
39
40
41
42
# File 'lib/phronomy/agent/context/capability/base.rb', line 37

def parameters
  return @parameters if instance_variable_defined?(:@parameters)

  parent = superclass.respond_to?(:parameters) ? superclass.parameters : {}
  @parameters = parent.dup
end

.params_schema_definitionObject

RubyLLM stores an explicit .params schema definition in a class-instance variable. Readers must fall back to the parent.



47
48
49
50
51
52
# File 'lib/phronomy/agent/context/capability/base.rb', line 47

def params_schema_definition
  return @params_schema_definition if instance_variable_defined?(:@params_schema_definition)
  return superclass.params_schema_definition if superclass.respond_to?(:params_schema_definition)

  nil
end

.provider_paramsObject

RubyLLM provider params are also class-instance state. Copy them on first access to preserve inheritance without sharing the top-level mutable Hash between parent and child.



58
59
60
61
62
63
# File 'lib/phronomy/agent/context/capability/base.rb', line 58

def provider_params
  return @provider_params if instance_variable_defined?(:@provider_params)

  parent = superclass.respond_to?(:provider_params) ? superclass.provider_params : {}
  @provider_params = duplicate_configuration(parent)
end

.redact_params(*names) ⇒ Object



202
203
204
205
206
207
208
209
# File 'lib/phronomy/agent/context/capability/base.rb', line 202

def redact_params(*names)
  if names.empty?
    parent = superclass.respond_to?(:redact_params) ? superclass.redact_params : []
    ((@redacted_params || []) + parent).uniq
  else
    @redacted_params = ((@redacted_params || []) + names.map(&:to_sym)).uniq
  end
end

.requires_approval(value = :__unset__, &block) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/phronomy/agent/context/capability/base.rb', line 171

def requires_approval(value = :__unset__, &block)
  if block
    unless value == :__unset__
      raise ArgumentError, "pass either a value or a block to requires_approval"
    end
    @requires_approval = block
  elsif value == :__unset__
    return @requires_approval if instance_variable_defined?(:@requires_approval)
    return superclass.requires_approval if superclass.respond_to?(:requires_approval)

    false
  else
    unless value == true || value == false || value.respond_to?(:call)
      raise ArgumentError, "requires_approval must be true, false, or callable"
    end
    @requires_approval = value
  end
end

.tool_name(value = nil) ⇒ Object



11
12
13
14
15
# File 'lib/phronomy/agent/context/capability/base.rb', line 11

def tool_name(value = nil)
  return @tool_name if value.nil?

  @tool_name = value.to_s
end

Instance Method Details

#approval_metadataObject



332
333
334
# File 'lib/phronomy/agent/context/capability/base.rb', line 332

def 
  {}
end

#call(args, cancellation_token: nil) ⇒ Object



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/phronomy/agent/context/capability/base.rb', line 268

def call(args, cancellation_token: nil)
  cancellation_token&.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 "Schema validation failed: #{schema_error}"
    end
  end

  if cancellation_token && execute_accepts_cancellation_token?
    validated_args = validated_args.merge(cancellation_token: cancellation_token)
  end
  result = super(validated_args)
  truncate_result_if_needed(result)
rescue Phronomy::ToolError, Phronomy::CancellationError
  raise
rescue => error
  if self.class.on_error == :suppress
    msg = "[Phronomy] Tool #{self.class.name} suppressed error: " \
      "#{error.class}: #{error.message}"
    if Phronomy.configuration.logger
      Phronomy.configuration.logger.warn(msg)
    else
      warn msg
    end
    "Tool error suppressed: #{error.message}"
  else
    raise Phronomy::ToolError,
      "#{self.class.name} execution failed: #{error.message}"
  end
end

#call_async(args, cancellation_token: nil, config: {}) ⇒ Object



305
306
307
308
309
310
311
312
313
314
315
316
# File 'lib/phronomy/agent/context/capability/base.rb', line 305

def call_async(
  args,
  cancellation_token: nil,
  config: {}
)
  Phronomy::Agent::ToolExecutor.call_async(
    tool: self,
    args: args,
    cancellation_token: cancellation_token,
    config: config
  )
end

#execute(**_args) ⇒ Object

Raises:

  • (NotImplementedError)


337
338
339
# File 'lib/phronomy/agent/context/capability/base.rb', line 337

def execute(**_args)
  raise NotImplementedError, "#{self.class}#execute is not implemented"
end

#nameObject



224
225
226
# File 'lib/phronomy/agent/context/capability/base.rb', line 224

def name
  self.class.tool_name || super
end

#params_schemaObject



228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
# File 'lib/phronomy/agent/context/capability/base.rb', line 228

def params_schema
  schema = super
  return schema if schema.nil?

  properties = schema.dig("properties") || schema.dig(:properties)
  return schema unless properties

  self.class.param_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 |value|
      case param_type
      when "integer"
        value.is_a?(Integer) ? value : Integer(value.to_s)
      when "number"
        value.is_a?(Numeric) ? value : Float(value.to_s)
      when "boolean"
        unless value == true || value == false
          raise ArgumentError,
            "boolean enum values must be true or false (got: #{value.inspect})"
        end
        value
      else
        value.to_s
      end
    end
  end

  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_approvalObject



318
319
320
# File 'lib/phronomy/agent/context/capability/base.rb', line 318

def requires_approval
  self.class.requires_approval
end

#requires_approval?Boolean

Returns:

  • (Boolean)


322
323
324
# File 'lib/phronomy/agent/context/capability/base.rb', line 322

def requires_approval?
  self.class.requires_approval
end

#tool_originObject



327
328
329
# File 'lib/phronomy/agent/context/capability/base.rb', line 327

def tool_origin
  :local
end