Class: Pikuri::Tool::Parameters

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

Overview

Schema for a Pikuri::Tool‘s arguments. Built up via the fluent <required|optional>_<type> methods, then frozen by Parameters.build; serializes to the OpenAI JSON-Schema shape via #to_h and validates LLM-supplied argument hashes via #validate.

Examples:

params = Tool::Parameters.build { |p| p.required_string :query, 'The query.' }
params.to_h
# => {type: 'object',
#     properties: {query: {type: 'string', description: 'The query.'}},
#     required: ['query']}
params.validate('query' => 'cats') # => {query: 'cats'}

Defined Under Namespace

Classes: ValidationError

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeParameters



38
39
40
41
# File 'lib/pikuri/tool/parameters.rb', line 38

def initialize
  @properties = {}
  @required = []
end

Class Method Details

.build {|builder| ... } ⇒ Parameters

Yield a fresh builder, freeze it, and return it.

Yield Parameters:

Returns:

  • (Parameters)

    frozen builder, safe to share between calls



31
32
33
34
35
# File 'lib/pikuri/tool/parameters.rb', line 31

def self.build
  builder = new
  yield builder
  builder.freeze
end

Instance Method Details

#freezeself

Freeze the builder along with its internal collections, so post-build mutation attempts raise FrozenError instead of silently succeeding.

Returns:

  • (self)


47
48
49
50
51
# File 'lib/pikuri/tool/parameters.rb', line 47

def freeze
  @properties.freeze
  @required.freeze
  super
end

#optional_boolean(name, description) ⇒ self

Add an optional boolean property. See #required_boolean for accepted shapes.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


132
133
134
# File 'lib/pikuri/tool/parameters.rb', line 132

def optional_boolean(name, description)
  add(name, 'boolean', description, required: false)
end

#optional_enum(name, description, values:) ⇒ self

Add an optional enum property. See #required_enum for the values contract and validation behavior.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

  • values (Array<String>)

    non-empty list of allowed values

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if values is not a non-empty Array of non-empty Strings



164
165
166
# File 'lib/pikuri/tool/parameters.rb', line 164

def optional_enum(name, description, values:)
  add_enum(name, description, values, required: false)
end

#optional_integer(name, description) ⇒ self

Add an optional integer property. See #required_integer for accepted shapes.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


88
89
90
# File 'lib/pikuri/tool/parameters.rb', line 88

def optional_integer(name, description)
  add(name, 'integer', description, required: false)
end

#optional_number(name, description) ⇒ self

Add an optional number property. See #required_number for accepted shapes.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


109
110
111
# File 'lib/pikuri/tool/parameters.rb', line 109

def optional_number(name, description)
  add(name, 'number', description, required: false)
end

#optional_string(name, description) ⇒ self

Add an optional string property.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


67
68
69
# File 'lib/pikuri/tool/parameters.rb', line 67

def optional_string(name, description)
  add(name, 'string', description, required: false)
end

#required_boolean(name, description) ⇒ self

Add a required boolean property. Accepts Ruby true/false as-is, and the literal Strings “true”/“false” (some models surface JSON booleans as Strings) after trimming surrounding whitespace. Other Strings, numbers, and nil are rejected —there is no truthy-coercion of “yes” / 0 / etc.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


122
123
124
# File 'lib/pikuri/tool/parameters.rb', line 122

def required_boolean(name, description)
  add(name, 'boolean', description, required: true)
end

#required_enum(name, description, values:) ⇒ self

Add a required enum property — a string field constrained to one of a fixed set of values. Emits JSON-Schema enum alongside type: ‘string’, which the LLM treats as a closed choice. Validation rejects any string outside the set with an LLM-actionable error message listing the allowed values.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

  • values (Array<String>)

    non-empty list of allowed values; each entry must be a non-empty String. The list is dup‘d and frozen at insertion so callers can’t mutate it later.

Returns:

  • (self)

Raises:

  • (ArgumentError)

    if values is not a non-empty Array of non-empty Strings (build-time check — surfaces as a host-side bug rather than an LLM-facing validation error)



151
152
153
# File 'lib/pikuri/tool/parameters.rb', line 151

def required_enum(name, description, values:)
  add_enum(name, description, values, required: true)
end

#required_integer(name, description) ⇒ self

Add a required integer property. Accepts Integers, Floats with a zero fractional part (e.g. 1.0), and base-10 numeric Strings (after trimming) that resolve to whole numbers; rejects everything else.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


78
79
80
# File 'lib/pikuri/tool/parameters.rb', line 78

def required_integer(name, description)
  add(name, 'integer', description, required: true)
end

#required_number(name, description) ⇒ self

Add a required number property (JSON-Schema number: Integer or finite Float). Numeric Strings (after trimming) are parsed; NaN and Infinity are rejected.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


99
100
101
# File 'lib/pikuri/tool/parameters.rb', line 99

def required_number(name, description)
  add(name, 'number', description, required: true)
end

#required_string(name, description) ⇒ self

Add a required string property.

Parameters:

  • name (Symbol)

    property name

  • description (String)

    human-readable description shown to the LLM

Returns:

  • (self)


58
59
60
# File 'lib/pikuri/tool/parameters.rb', line 58

def required_string(name, description)
  add(name, 'string', description, required: true)
end

#to_hHash

Schema in OpenAI JSON-Schema shape.

Returns:

  • (Hash)

    {type: ‘object’, properties: {…}, required: […]}



171
172
173
# File 'lib/pikuri/tool/parameters.rb', line 171

def to_h
  { type: 'object', properties: @properties, required: @required }
end

#validate(args) ⇒ Hash{Symbol=>Object}

Validate a tool-call argument hash against the declared schema. Returns a symbol-keyed hash safe to splat as kwargs into a tool’s execute Proc; raises ValidationError with an LLM-actionable message listing every missing/unknown/mistyped field and reprinting the schema.

Strict: unknown keys are rejected (with DidYouMean suggestions), wrong types are rejected. All issues are collected and reported together so the LLM can fix them in one round trip.

Parameters:

  • args (Hash)

    arguments as decoded from the tool-call JSON; keys may be Strings or Symbols

Returns:

  • (Hash{Symbol=>Object})

    validated, symbol-keyed arguments

Raises:

  • (ValidationError)

    if args is not a Hash, contains unknown keys, omits a required key, or has a value of the wrong type



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/pikuri/tool/parameters.rb', line 189

def validate(args)
  raise ValidationError, "Arguments must be an object, got #{args.class}." unless args.is_a?(Hash)

  symbolized = args.transform_keys(&:to_sym)
  errors = []
  result = {}

  (symbolized.keys - @properties.keys).each do |unknown|
    errors << unknown_key_error(unknown)
  end

  @properties.each do |name, schema|
    if symbolized.key?(name)
      begin
        coerced = coerce(symbolized[name], schema[:type])
        raise CoercionError, enum_message(schema[:enum], coerced) if schema[:enum] && !schema[:enum].include?(coerced)

        result[name] = coerced
      rescue CoercionError => e
        errors << "Parameter `#{name}` #{e.message}."
      end
    elsif @required.include?(name.to_s)
      errors << missing_required_message(name, schema)
    end
  end

  return result if errors.empty?

  raise ValidationError, build_error_message(errors)
end