Class: Ask::Tool
- Inherits:
-
Object
- Object
- Ask::Tool
- Defined in:
- lib/ask/tools/tool.rb
Overview
Base class for defining tools that LLMs can call.
Subclass Ask::Tool, use the DSL to declare metadata and parameters, and implement #execute to perform the work.
class Greeter < Ask::Tool
description "Greets a person by name"
param :name, type: :string, desc: "The person's name", required: true
def execute(name:)
Ask::Result.ok(data: "Hello, #{name}!")
end
end
Greeter.new.name # => "greeter"
Greeter.new.call(name: "World")
# => #<Ask::Result ok=true output="Hello, World!">
Defined Under Namespace
Class Method Summary collapse
-
.desc ⇒ String?
Set or retrieve the tool’s human-readable description.
-
.description(text = nil) ⇒ String?
Set or retrieve the tool’s human-readable description.
- .inherited(subclass) ⇒ Object private
- .map_type(type) ⇒ Object
-
.param(name, type:, desc: nil, description: nil, required: true) ⇒ void
Declare a parameter the tool accepts.
- .parameters ⇒ Hash{Symbol => Ask::Tool::Parameter} private
-
.params(schema = nil, &block) ⇒ void
private
Define tool parameters using the Schema DSL.
- .provider_params ⇒ Object
- .validate_param_type!(type, name) ⇒ Object
Instance Method Summary collapse
-
#call(args = {}) ⇒ Ask::Result
Call the tool with the given arguments.
-
#description ⇒ String?
The tool’s description.
-
#execute ⇒ Ask::Result
Subclasses must implement this method.
-
#inspect ⇒ String
Inspect string.
-
#name ⇒ String
Auto-derive the tool name from the class name.
- #parameters ⇒ Hash{Symbol => Ask::Tool::Parameter}
-
#params_schema ⇒ Hash
Generate a JSON Schema hash describing this tool’s parameters.
-
#tool_definition ⇒ Hash
Full tool definition hash for LLM API calls.
Class Method Details
.desc ⇒ String?
Set or retrieve the tool’s human-readable description.
52 53 54 55 56 |
# File 'lib/ask/tools/tool.rb', line 52 def description(text = nil) return @description unless text @description = text end |
.description(text = nil) ⇒ String?
Set or retrieve the tool’s human-readable description.
47 48 49 50 51 |
# File 'lib/ask/tools/tool.rb', line 47 def description(text = nil) return @description unless text @description = text end |
.inherited(subclass) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
36 37 38 39 40 41 |
# File 'lib/ask/tools/tool.rb', line 36 def inherited(subclass) super @parameters = {} if @parameters.nil? subclass.instance_variable_set(:@description, nil) subclass.instance_variable_set(:@parameters, {}) end |
.map_type(type) ⇒ Object
231 232 233 234 235 236 237 |
# File 'lib/ask/tools/tool.rb', line 231 def self.map_type(type) case type when :int then "integer" when :float, :double then "number" else type.to_s end end |
.param(name, type:, desc: nil, description: nil, required: true) ⇒ void
This method returns an undefined value.
Declare a parameter the tool accepts.
62 63 64 65 66 67 68 69 70 71 |
# File 'lib/ask/tools/tool.rb', line 62 def param(name, type:, desc: nil, description: nil, required: true) type = type.to_s.downcase.to_sym validate_param_type!(type, name) parameters[name] = Parameter.new( name: name, type: map_type(type), description: desc || description, required: required ) end |
.parameters ⇒ Hash{Symbol => Ask::Tool::Parameter}
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
75 76 77 |
# File 'lib/ask/tools/tool.rb', line 75 def parameters @parameters ||= {} end |
.params(schema = nil, &block) ⇒ void
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
This method returns an undefined value.
Define tool parameters using the Schema DSL.
When a block is provided, it takes precedence over individual param declarations for schema generation.
94 95 96 |
# File 'lib/ask/tools/tool.rb', line 94 def params(schema = nil, &block) @params_schema_definition = schema || block end |
.provider_params ⇒ Object
98 99 100 |
# File 'lib/ask/tools/tool.rb', line 98 def provider_params @provider_params ||= {} end |
.validate_param_type!(type, name) ⇒ Object
223 224 225 226 227 228 229 |
# File 'lib/ask/tools/tool.rb', line 223 def self.validate_param_type!(type, name) return if VALID_JSON_SCHEMA_TYPES.include?(type) raise ArgumentError, "Invalid type #{type.inspect} for parameter #{name.inspect}. " \ "Valid types: #{VALID_JSON_SCHEMA_TYPES.map(&:inspect).join(', ')}" end |
Instance Method Details
#call(args = {}) ⇒ Ask::Result
Call the tool with the given arguments.
Normalizes keys to symbols, validates required parameters, and delegates to #execute.
136 137 138 139 140 141 142 143 144 145 146 |
# File 'lib/ask/tools/tool.rb', line 136 def call(args = {}) normalized = normalize_args(args) validation = validate(normalized) return Ask::Result.error(message: validation) if validation execute(**normalized) rescue Halt => e Ask::Result.ok(data: e.content, metadata: { halted: true }) rescue StandardError => e Ask::Result.error(message: "#{self.class.name.split('::').last} raised #{e.class}: #{e.}") end |
#description ⇒ String?
Returns the tool’s description.
120 121 122 |
# File 'lib/ask/tools/tool.rb', line 120 def description self.class.description end |
#execute ⇒ Ask::Result
Subclasses must implement this method.
152 153 154 |
# File 'lib/ask/tools/tool.rb', line 152 def execute(**) raise NotImplementedError, "#{self.class} must implement #execute(**args)" end |
#inspect ⇒ String
Returns inspect string.
199 200 201 |
# File 'lib/ask/tools/tool.rb', line 199 def inspect "#<#{self.class.name} name=#{name.inspect}>" end |
#name ⇒ String
Auto-derive the tool name from the class name. Converts CamelCase to snake_case and strips a trailing _tool suffix.
107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/ask/tools/tool.rb', line 107 def name # Use only the class name (last segment), ignoring module nesting klass_name = self.class.name.to_s.split("::").last || self.class.name.to_s normalized = klass_name.dup.force_encoding("UTF-8").unicode_normalize(:nfkd) normalized.encode("ASCII", replace: "") .gsub(/[^a-zA-Z0-9_-]/, "-") .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2') .gsub(/([a-z\d])([A-Z])/, '\1_\2') .downcase .delete_suffix("_tool") end |
#parameters ⇒ Hash{Symbol => Ask::Tool::Parameter}
125 126 127 |
# File 'lib/ask/tools/tool.rb', line 125 def parameters self.class.parameters end |
#params_schema ⇒ Hash
Generate a JSON Schema hash describing this tool’s parameters. Suitable for LLM function-calling APIs (OpenAI, Anthropic, etc.).
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/ask/tools/tool.rb', line 160 def params_schema return @params_schema if defined?(@params_schema) @params_schema = begin if parameters.empty? nil else properties = parameters.to_h do |_name, param| schema = { type: param.type } schema[:description] = param.description if param.description schema[:items] = { type: "string" } if param.type == "array" [param.name.to_s, schema] end required = parameters.select { |_, p| p.required }.keys.map(&:to_s) { type: "object", properties: properties, required: required, additionalProperties: false } end end end |
#tool_definition ⇒ Hash
Full tool definition hash for LLM API calls.
189 190 191 192 193 194 195 196 |
# File 'lib/ask/tools/tool.rb', line 189 def tool_definition defn = { name: name, description: description } defn[:input_schema] = params_schema if params_schema defn end |