Class: RubyPi::Tools::Definition

Inherits:
Object
  • Object
show all
Defined in:
lib/ruby_pi/tools/definition.rb

Constant Summary collapse

NAME_FORMAT =

Tool names must satisfy the strictest provider constraint (Anthropic’s ^[a-zA-Z0-9_-]1,64$). Without this guard, a name like “send.email” registers fine and then 400s on every API request with an opaque server-side validation error that doesn’t point back to the tool.

/\A[a-zA-Z0-9_-]{1,64}\z/

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name:, description:, category: nil, parameters: {}) {|Hash| ... } ⇒ Definition

Creates a new tool definition.

Parameters:

  • name (String, Symbol)

    Unique identifier for the tool. Must match NAME_FORMAT (letters, digits, underscore, hyphen; max 64 chars).

  • description (String)

    What the tool does (shown to the LLM).

  • category (Symbol, nil) (defaults to: nil)

    Optional grouping category.

  • parameters (Hash) (defaults to: {})

    JSON Schema hash for the tool’s input parameters.

Yields:

  • (Hash)

    Block that implements the tool logic. Receives a hash of symbol-keyed arguments, or keyword arguments if the block declares keyword parameters (see #call).

Raises:

  • (ArgumentError)

    If name is missing or violates NAME_FORMAT, description is missing, or no block given.



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/ruby_pi/tools/definition.rb', line 58

def initialize(name:, description:, category: nil, parameters: {}, &block)
  raise ArgumentError, "Tool name is required" if name.nil? || name.to_s.strip.empty?
  unless name.to_s.match?(NAME_FORMAT)
    raise ArgumentError,
          "Tool name #{name.to_s.inspect} is invalid — provider APIs require " \
          "names matching #{NAME_FORMAT.inspect} (letters, digits, underscore, " \
          "hyphen; 1-64 characters)"
  end
  raise ArgumentError, "Tool description is required" if description.nil? || description.strip.empty?
  raise ArgumentError, "Tool implementation block is required" unless block_given?

  @name = name.to_sym
  @description = description
  @category = category&.to_sym
  @parameters = parameters
  @implementation = block
  # On Ruby 3.x a positional Hash is never auto-splatted to keywords, so
  # a block written `{ |content:, platform:| ... }` — the natural style
  # given named schema parameters — would fail every call with
  # "missing keyword". Detect keyword parameters once here and splat in
  # #call accordingly.
  @expects_keywords = block.parameters.any? { |type, _| %i[key keyreq keyrest].include?(type) }
end

Instance Attribute Details

#categorySymbol? (readonly)

Returns An optional category for grouping related tools.

Returns:

  • (Symbol, nil)

    An optional category for grouping related tools.



35
36
37
# File 'lib/ruby_pi/tools/definition.rb', line 35

def category
  @category
end

#descriptionString (readonly)

Returns A human-readable description of what this tool does.

Returns:

  • (String)

    A human-readable description of what this tool does.



32
33
34
# File 'lib/ruby_pi/tools/definition.rb', line 32

def description
  @description
end

#nameSymbol (readonly)

Returns The unique name identifying this tool.

Returns:

  • (Symbol)

    The unique name identifying this tool.



29
30
31
# File 'lib/ruby_pi/tools/definition.rb', line 29

def name
  @name
end

#parametersHash (readonly)

Returns A JSON Schema hash describing the tool’s parameters.

Returns:

  • (Hash)

    A JSON Schema hash describing the tool’s parameters.



38
39
40
# File 'lib/ruby_pi/tools/definition.rb', line 38

def parameters
  @parameters
end

Instance Method Details

#call(args = {}) ⇒ Object

Invokes the tool with the given arguments.

Blocks may be written either style:

{ |args| args[:content] }            # single positional Hash
{ |content:, platform: "x"| ... }    # keyword parameters

When the block declares keyword parameters, the arguments hash is splatted to keywords. Note that a keyword-style block without **rest raises ArgumentError on unexpected keys — strict by design, since the keys come from the LLM.

Parameters:

  • args (Hash) (defaults to: {})

    The arguments to pass to the tool implementation.

Returns:

  • (Object)

    Whatever the implementation block returns.



95
96
97
98
99
100
101
# File 'lib/ruby_pi/tools/definition.rb', line 95

def call(args = {})
  if @expects_keywords
    @implementation.call(**args)
  else
    @implementation.call(args)
  end
end

#inspectString

Provides a human-readable string representation of the definition.

Returns:

  • (String)

    A summary string for debugging/logging.



154
155
156
# File 'lib/ruby_pi/tools/definition.rb', line 154

def inspect
  "#<RubyPi::Tools::Definition name=#{@name.inspect} category=#{@category.inspect}>"
end

#to_anthropic_formatHash

Converts this tool definition to Anthropic’s tool format.

Anthropic expects:

{ name: "...", description: "...", input_schema: { ... } }

Returns:

  • (Hash)

    The tool in Anthropic’s tool format.



124
125
126
127
128
129
130
131
# File 'lib/ruby_pi/tools/definition.rb', line 124

def to_anthropic_format
  tool = {
    name: @name.to_s,
    description: @description
  }
  tool[:input_schema] = @parameters unless @parameters.empty?
  tool
end

#to_gemini_formatHash

Converts this tool definition to Google Gemini function declaration format.

Gemini expects:

{ name: "...", description: "...", parameters: { ... } }

Returns:

  • (Hash)

    The tool in Gemini’s function declaration format.



109
110
111
112
113
114
115
116
# File 'lib/ruby_pi/tools/definition.rb', line 109

def to_gemini_format
  declaration = {
    name: @name.to_s,
    description: @description
  }
  declaration[:parameters] = @parameters unless @parameters.empty?
  declaration
end

#to_openai_formatHash

Converts this tool definition to OpenAI’s function calling format.

OpenAI expects:

{ type: "function", function: { name: "...", description: "...", parameters: { ... } } }

Returns:

  • (Hash)

    The tool in OpenAI’s function format.



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ruby_pi/tools/definition.rb', line 139

def to_openai_format
  function = {
    name: @name.to_s,
    description: @description
  }
  function[:parameters] = @parameters unless @parameters.empty?
  {
    type: "function",
    function: function
  }
end