class RubyLLM::Tool
Base class for creating tools that AI models can use
Constants
- POSITIONAL_PARAMETER_KINDS
Attributes
Public Class Methods
Source
# File lib/ruby_llm/tool.rb, line 38 def description(text = nil) return @description unless text @description = text end
Also aliased as: desc
Source
# File lib/ruby_llm/tool.rb, line 45 def param(name, **options) parameters[name] = Parameter.new(name, **options) end
# File lib/ruby_llm/tool.rb, line 53 def params(schema = nil, &block) @params_schema_definition = SchemaDefinition.new(schema:, block:) self end
Source
# File lib/ruby_llm/tool.rb, line 63 def provider_params @provider_params ||= {} end
Source
# File lib/ruby_llm/tool.rb, line 58 def with_params(**params) @provider_params = params self end
Public Instance Methods
Source
# File lib/ruby_llm/tool.rb, line 106 def call(args) normalized_args = normalize_args(args) validation_error = validate_keyword_arguments(normalized_args) return { error: "Invalid tool arguments: #{validation_error}" } if validation_error RubyLLM.logger.debug { "Tool #{name} called with: #{normalized_args.inspect}" } result = execute(**normalized_args) RubyLLM.logger.debug { "Tool #{name} returned: #{result.inspect}" } result end
Source
# File lib/ruby_llm/tool.rb, line 79 def description self.class.description end
Source
# File lib/ruby_llm/tool.rb, line 117 def execute(...) raise NotImplementedError, 'Subclasses must implement #execute' end
Source
# File lib/ruby_llm/tool.rb, line 68 def name klass_name = self.class.name normalized = klass_name.to_s.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
Source
# File lib/ruby_llm/tool.rb, line 91 def params_schema return @params_schema if defined?(@params_schema) @params_schema = begin definition = self.class.params_schema_definition if definition&.present? definition.json_schema elsif parameters.any? SchemaDefinition.from_parameters(parameters)&.json_schema else SchemaDefinition.from_parameters(inferred_parameters, allow_empty: true)&.json_schema end end end
Source
# File lib/ruby_llm/tool.rb, line 87 def provider_params self.class.provider_params end
Protected Instance Methods
Source
# File lib/ruby_llm/tool.rb, line 152 def execute_keyword_signature keyword_signature = method(:execute).parameters required_keywords = keyword_signature.filter_map { |kind, name| name if kind == :keyreq } optional_keywords = keyword_signature.filter_map { |kind, name| name if kind == :key } accepts_extra_keywords = keyword_signature.any? { |kind, _| kind == :keyrest } accepts_positional_arguments = keyword_signature.any? do |kind, _| POSITIONAL_PARAMETER_KINDS.include?(kind) end [required_keywords, optional_keywords, accepts_extra_keywords, accepts_positional_arguments] end
# File lib/ruby_llm/tool.rb, line 164 def first_missing_keyword(required_keywords, argument_keys) (required_keywords - argument_keys).first end
# File lib/ruby_llm/tool.rb, line 168 def first_unknown_keyword(argument_keys, allowed_keywords) (argument_keys - allowed_keywords).first end
Source
# File lib/ruby_llm/tool.rb, line 172 def inferred_parameters required_keywords, optional_keywords, = execute_keyword_signature (required_keywords + optional_keywords).to_h do |name| [name, Parameter.new(name, required: required_keywords.include?(name))] end end
Source
# File lib/ruby_llm/tool.rb, line 127 def normalize_args(args) return {} if args.nil? return args.transform_keys(&:to_sym) if args.respond_to?(:transform_keys) {} end
# File lib/ruby_llm/tool.rb, line 134 def validate_keyword_arguments(arguments) required_keywords, optional_keywords, accepts_extra_keywords, accepts_positional_arguments = execute_keyword_signature return nil if required_keywords.empty? && optional_keywords.empty? && accepts_positional_arguments argument_keys = arguments.keys missing_keyword = first_missing_keyword(required_keywords, argument_keys) return "missing keyword: #{missing_keyword}" if missing_keyword return nil if accepts_extra_keywords allowed_keywords = required_keywords + optional_keywords unknown_keyword = first_unknown_keyword(argument_keys, allowed_keywords) return "unknown keyword: #{unknown_keyword}" if unknown_keyword nil end