Class: Silas::Tool

Inherits:
Object
  • Object
show all
Defined in:
lib/silas/tool.rb

Overview

Base class for agent tools. The keyword signature of #call IS the schema:

class Agent::Tools::IssueRefund < Silas::Tool
description "Refund an order."
param :amount, :integer, desc: "Pence"   # optional type refinement
approval :always                          # :never | :once | :always | lambda
transactional!                            # or at_most_once! (default) / idempotent!

def call(order_id:, amount:, note: nil)
  ...
end
end

Tool identity is the FILENAME (app/agent/tools/issue_refund.rb -> "issue_refund"), eve's convention, enforced by the Registry.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#sessionObject

Execution context, set by the Ledger before #call.



87
88
89
# File 'lib/silas/tool.rb', line 87

def session
  @session
end

Class Method Details

.approval(policy = nil, &block) ⇒ Object Also known as: approval_policy



28
29
30
31
# File 'lib/silas/tool.rb', line 28

def approval(policy = nil, &block)
  @approval_policy = block || policy unless policy.nil? && block.nil?
  @approval_policy || :never
end

.at_most_once!Object



36
# File 'lib/silas/tool.rb', line 36

def at_most_once!  = @effect_mode = :at_most_once

.description(text = nil) ⇒ Object



19
20
21
22
# File 'lib/silas/tool.rb', line 19

def description(text = nil)
  @description = text if text
  @description || ""
end

.effect_modeObject



38
# File 'lib/silas/tool.rb', line 38

def effect_mode = @effect_mode || :at_most_once

.idempotent!Object



35
# File 'lib/silas/tool.rb', line 35

def idempotent!    = @effect_mode = :idempotent

.param(name, type = :string, desc: nil) ⇒ Object



24
25
26
# File 'lib/silas/tool.rb', line 24

def param(name, type = :string, desc: nil)
  param_refinements[name.to_sym] = { type: type.to_s, desc: desc }
end

.schemaObject

description:, input_schema: — provider-agnostic JSON schema.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/silas/tool.rb', line 45

def schema
  validate_signature!
  properties = {}
  required = []
  instance_method(:call).parameters.each do |kind, pname|
    refinement = param_refinements[pname] || {}
    properties[pname.to_s] = { "type" => refinement[:type] || "string" }
    properties[pname.to_s]["description"] = refinement[:desc] if refinement[:desc]
    required << pname.to_s if kind == :keyreq
  end
  {
    "name" => tool_name,
    "description" => description,
    "input_schema" => {
      "type" => "object",
      "properties" => properties,
      "required" => required
    }
  }
end

.tool_nameObject



40
41
42
# File 'lib/silas/tool.rb', line 40

def tool_name
  name.demodulize.underscore
end

.transactional!Object



34
# File 'lib/silas/tool.rb', line 34

def transactional! = @effect_mode = :transactional

.validate_signature!Object

Raises:



66
67
68
69
70
71
72
73
# File 'lib/silas/tool.rb', line 66

def validate_signature!
  raise Error, "#{name} must define #call" unless method_defined?(:call)

  kinds = instance_method(:call).parameters.map(&:first)
  if kinds.any? { |k| %i[req opt rest].include?(k) }
    raise Error, "#{name}#call must accept keyword arguments only — the signature is the schema"
  end
end

Instance Method Details

#approval_policyObject

Instance-side delegation so a resolved instance answers the Ledger.



83
# File 'lib/silas/tool.rb', line 83

def approval_policy = self.class.approval_policy

#callObject

Raises:

  • (NotImplementedError)


89
90
91
# File 'lib/silas/tool.rb', line 89

def call(**)
  raise NotImplementedError, "#{self.class}#call"
end

#effect_modeObject



84
# File 'lib/silas/tool.rb', line 84

def effect_mode     = self.class.effect_mode