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



185
186
187
188
189
190
191
192
193
# File 'lib/phronomy/agent/context/capability/base.rb', line 185

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



117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/phronomy/agent/context/capability/base.rb', line 117

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 :blocking_io
  end

  valid = %i[cooperative blocking_io cpu_bound external_process]
  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



206
207
208
209
210
211
212
213
214
215
# File 'lib/phronomy/agent/context/capability/base.rb', line 206

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.



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/phronomy/agent/context/capability/base.rb', line 136

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



153
154
155
156
157
158
159
160
161
162
# File 'lib/phronomy/agent/context/capability/base.rb', line 153

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



196
197
198
199
200
201
202
203
# File 'lib/phronomy/agent/context/capability/base.rb', line 196

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



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/phronomy/agent/context/capability/base.rb', line 165

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



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

def 
  {}
end

#call(args, cancellation_token: nil) ⇒ Object



262
263
264
265
266
267
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
# File 'lib/phronomy/agent/context/capability/base.rb', line 262

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



299
300
301
302
303
304
305
306
# File 'lib/phronomy/agent/context/capability/base.rb', line 299

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)


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

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

#nameObject



218
219
220
# File 'lib/phronomy/agent/context/capability/base.rb', line 218

def name
  self.class.tool_name || super
end

#params_schemaObject



222
223
224
225
226
227
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
# File 'lib/phronomy/agent/context/capability/base.rb', line 222

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



308
309
310
# File 'lib/phronomy/agent/context/capability/base.rb', line 308

def requires_approval
  self.class.requires_approval
end

#requires_approval?Boolean

Returns:

  • (Boolean)


312
313
314
# File 'lib/phronomy/agent/context/capability/base.rb', line 312

def requires_approval?
  self.class.requires_approval
end

#tool_originObject



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

def tool_origin
  :local
end