Class: Legate::Tool

Inherits:
Object
  • Object
show all
Includes:
MetadataDsl
Defined in:
lib/legate/tool.rb,
lib/legate/tool/metadata_dsl.rb

Overview

Base class for all tools that can be used by Agents.

Tools are the way agents interact with the outside world. To create a new tool, inherit from this class, use the DSL to define metadata, and implement the #perform_execution method.

Examples:

Creating a custom weather tool

class WeatherTool < Legate::Tool
  tool_description 'Fetches current weather for a location'

  parameter :location, type: :string, required: true,
            description: 'City name (e.g. "San Francisco")'

  parameter :unit, type: :string, required: false,
            description: 'Temperature unit (celsius/fahrenheit)'

  private

  def perform_execution(params, context)
    location = params[:location]
    # ... fetch weather logic ...
    weather_data = "Sunny, 25C"

    {
      status: :success,
      result: weather_data
    }
  rescue => e
    {
      status: :error,
      error_message: "Failed to fetch weather: #{e.message}"
    }
  end
end

Defined Under Namespace

Modules: MetadataDsl

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from MetadataDsl

included

Constructor Details

#initialize(**_options) ⇒ Tool

Initialize - Sets instance vars from class metadata



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/legate/tool.rb', line 92

def initialize(**_options)
  # Fetch metadata using the primary tool_metadata method (defined by DSL)
   = self.class.
  @name = [:name]
  @description = [:description]
  @parameters = [:parameters] || {}

  # Lenient check for missing metadata
  return unless @name.nil? || @name == :'' || @description.nil? || @description.empty?

  is_anonymous = !self.class.name || self.class.name.empty? || self.class.name.start_with?('#<Class:')
  unless is_anonymous
    missing = []
    missing << ':name' if @name.nil? || @name == :''
    missing << ':description' if @description.nil? || @description.empty?
    Legate.logger.warn("Tool class #{self.class} initialized with missing metadata: [#{missing.join(', ')}] using #{self.class.}. Tool may not function correctly.")
  end
  @description ||= ''
end

Class Attribute Details

.descriptionObject (readonly)

Keep old readers for define_metadata compatibility



55
56
57
# File 'lib/legate/tool.rb', line 55

def description
  @description
end

.parameters_definitionObject (readonly)

Keep old readers for define_metadata compatibility



55
56
57
# File 'lib/legate/tool.rb', line 55

def parameters_definition
  @parameters_definition
end

.tool_nameObject (readonly)

Keep old readers for define_metadata compatibility



55
56
57
# File 'lib/legate/tool.rb', line 55

def tool_name
  @tool_name
end

Instance Attribute Details

#descriptionObject (readonly)

Instance readers



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

def description
  @description
end

#nameObject (readonly)

Instance readers



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

def name
  @name
end

#parametersObject (readonly)

Instance readers



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

def parameters
  @parameters
end

Class Method Details

.define_metadata(name:, description:, parameters: {}) ⇒ Object

Define the tool’s static metadata. DEPRECATED: Use ‘tool_description`, `parameter`, and automatic name inference instead.



59
60
61
62
63
64
65
66
67
68
# File 'lib/legate/tool.rb', line 59

def (name:, description:, parameters: {})
  warn "[DEPRECATION] `define_metadata` is deprecated. Use `tool_description`, `parameter`, and rely on class name inference (or `self.explicit_tool_name = :my_name`) instead. Called from #{caller_locations(
    1, 1
  )[0].label}"

  @tool_name = name.to_sym
  @description = description
  @parameters_definition = parameters
  @_tool_metadata_cache = nil # Invalidate cache
end

.inherited(subclass) ⇒ Object

— Self-Registration Hook — NOTE: We intentionally do NOT auto-register tools in the inherited hook. The reason is that ‘inherited` is called BEFORE the class body executes, so explicit_tool_name and other DSL methods haven’t run yet. This would cause tools to be registered under their inferred name (e.g., :random_number_tool) instead of their explicit name (e.g., :random_number).

Instead, built-in tools are explicitly registered in lib/legate.rb after their class definitions are complete. Custom tools should either:

  1. Call ‘Legate::GlobalToolManager.register_tool(MyTool)` explicitly after definition

  2. Be discovered via tool_paths when creating agents



82
83
84
85
# File 'lib/legate/tool.rb', line 82

def self.inherited(subclass)
  super # Call parent's inherited if necessary
  Legate.logger.debug("Tool subclass #{subclass} inherited. Tool will be registered when explicitly added to GlobalToolManager or an agent's tool registry.")
end

Instance Method Details

#execute(params = {}, context = nil) ⇒ Hash

Execute the tool

Parameters:

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

    Input parameters for the tool.

  • context (Legate::ToolContext, nil) (defaults to: nil)

    Contextual information (session details).

Returns:

  • (Hash)

    A hash with :status (:success, :error, :pending) and :result/:error_message/:job_id.



116
117
118
119
120
121
122
123
# File 'lib/legate/tool.rb', line 116

def execute(params = {}, context = nil)
  coerced_params = validate_and_coerce_params(params)
  Legate.logger.debug("Executing tool '#{@name}' with validated params: #{coerced_params.inspect} and context: #{context&.to_h.inspect}")
  result = perform_execution(coerced_params, context)
  # perform_execution may return a typed ToolResult or the canonical hash;
  # normalize to the hash here so everything downstream is unchanged.
  result.is_a?(Legate::ToolResult) ? result.to_h : result
end

#validate_and_coerce_params(params) ⇒ Hash

Validate and coerce parameters based on metadata types

Parameters:

  • params (Hash)

    Input parameters

Returns:

  • (Hash)

    New hash with symbol keys and coerced values

Raises:



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/legate/tool.rb', line 137

def validate_and_coerce_params(params)
  # 1. Normalize keys to symbols
  normalized_params = params.transform_keys(&:to_sym)

  current_parameters = @parameters || {}

  # 2. Check for missing required parameters
  # Use symbol keys for check
  required_param_names = current_parameters.select { |_, p| p[:required] }.keys
  present_keys = normalized_params.keys
  missing_params = required_param_names - present_keys

  unless missing_params.empty?
    msg = "Missing required parameters for tool '#{@name}': #{missing_params.join(', ')}."
    msg += " Provided: [#{present_keys.empty? ? 'None' : present_keys.join(', ')}]."

    Legate.logger.error("Validation failed: #{msg} Params: #{params.inspect}")
    raise Legate::ToolArgumentError, msg
  end

  # 3. Type Validation & Coercion
  coerced_params = normalized_params.dup

  current_parameters.each do |param_name, param_def|
    # Only process if present
    next unless coerced_params.key?(param_name)

    value = coerced_params[param_name]
    expected_type = param_def[:type]
    next unless expected_type

    begin
      coerced_value = coerce_value(value, expected_type)
      coerced_params[param_name] = coerced_value
    rescue Legate::ToolArgumentError => e
      # coerce_value raises ToolArgumentError (not ArgumentError/TypeError);
      # re-raise with the parameter/tool context the bare message lacks.
      raise Legate::ToolArgumentError, "Parameter '#{param_name}' for tool '#{@name}': #{e.message}"
    end
  end

  coerced_params
end

#validate_params(params) ⇒ Object

Validate the parameters (Deprecated: Use validate_and_coerce_params) This method is kept for backward compatibility and wraps the new logic, but ignores the coerced return value.



128
129
130
131
# File 'lib/legate/tool.rb', line 128

def validate_params(params)
  validate_and_coerce_params(params)
  nil
end